diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..30f66ed --- /dev/null +++ b/.clang-format @@ -0,0 +1,94 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +#AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false +# SplitEmptyFunction: true +# SplitEmptyRecord: true +# SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +#BreakConstructorInitializers: AfterColon +BreakConstructorInitializersBeforeComma: false +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: [ foreach ] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '$' +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: Always +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: ForIndentation +... + diff --git a/.gitignore b/.gitignore index 9b4e0d2..a46b511 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,9 @@ *.nso *.pfs0 *.smdh +.gdb_history build.*/ ftpd +romfs.3ds/*.t3x +romfs.switch/*.zst +romfs.switch/shaders/*.dksh diff --git a/LICENSE b/LICENSE index a84c395..f288702 100644 --- a/LICENSE +++ b/LICENSE @@ -1,25 +1,674 @@ -This is free and unencumbered software released into the public domain. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -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. + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -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. + Preamble -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. + The GNU General Public License is a free, copyleft license for +software and other kinds of works. -For more information, please refer to + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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. But first, please read +. diff --git a/Makefile b/Makefile index 3f76a8b..2feff58 100644 --- a/Makefile +++ b/Makefile @@ -1,42 +1,79 @@ -.PHONY: all nro 3dsx cia clean linux +.PHONY: all nro 3dsx cia clean linux 3dslink nxlink format release release-3dsx release-cia release-3ds release-nro + +TARGET := $(notdir $(CURDIR)) export GITREV := $(shell git rev-parse HEAD 2>/dev/null | cut -c1-8) -export VERSION_MAJOR := 2 -export VERSION_MINOR := 3 -export VERSION_MICRO := 1 -export VERSION := $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_MICRO) +export VERSION_MAJOR := 3 +export VERSION_MINOR := 0 +export VERSION_MICRO := 0 +export VERSION := $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_MICRO)-rc1 ifneq ($(strip $(GITREV)),) export VERSION := $(VERSION)-$(GITREV) endif -all: - @echo please choose 3dsx, cia, linux, or nro +all: 3dsx nro linux -release: - # can't let these three run in parallel with each other due to using same - # ftpd.elf file name - @$(MAKE) -f Makefile.switch all - @$(MAKE) -f Makefile.3ds 3dsx - @$(MAKE) -f Makefile.3ds cia - @xz -c ftpd.3dsx.xz - @xz -c ftpd.cia.xz - @xz -c ftpd.nro.xz +nxlink: + @$(MAKE) -f Makefile.switch nxlink + +3dslink: 3dsx + @/opt/devkitpro/tools/bin/3dslink $(TARGET)-3ds.3dsx + +format: + @clang-format -style=file -i $(filter-out \ + include/imgui.h \ + source/pc/imgui_impl_glfw.cpp \ + source/pc/imgui_impl_glfw.h \ + source/pc/imgui_impl_opengl3.cpp \ + source/pc/imgui_impl_opengl3.h \ + source/pc/KHR/khrplatform.h \ + source/pc/glad.c \ + source/pc/glad/glad.h \ + source/imgui/imgui.cpp \ + source/imgui/imgui_demo.cpp \ + source/imgui/imgui_draw.cpp \ + source/imgui/imgui_widgets.cpp \ + source/imgui/imstb_rectpack.h \ + source/imgui/imstb_textedit.h \ + source/imgui/imstb_truetype.h \ + source/imgui/imgui_internal.h, \ + $(shell find source include -type f -name \*.c -o -name \*.cpp -o -name \*.h)) + +release: release-3ds release-nro + @xz -c <$(TARGET)-3ds.3dsx >ftpd.3dsx.xz + @echo xz -c <$(TARGET)-3ds.cia >ftpd.cia.xz + @echo xz -c <$(TARGET)-nx.nro >ftpd.nro.xz nro: @$(MAKE) -f Makefile.switch all +release-nro: + @$(MAKE) DEFINES=-DNDEBUG -f Makefile.switch all + 3dsx: @$(MAKE) -f Makefile.3ds 3dsx -cia: +release-3dsx: + @$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds 3dsx + +cia: 3dsx @$(MAKE) -f Makefile.3ds cia +release-cia: release-3dsx + @$(MAKE) DEFINES=-NDEBUG -f Makefile.3ds cia + +release-3ds: + # can't let these three run in parallel with each other due to using same + # .elf file name + @$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds 3dsx + @$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds cia + linux: @$(MAKE) -f Makefile.linux clean: - @$(MAKE) -f Makefile.switch clean - @$(MAKE) -f Makefile.3ds clean + @$(MAKE) -f Makefile.switch clean + @$(MAKE) -f Makefile.3ds clean @$(MAKE) -f Makefile.linux clean @$(RM) ftpd.3dsx.xz ftpd.cia.xz ftpd.nro.xz diff --git a/Makefile.3ds b/Makefile.3ds index 6db2125..4603a50 100644 --- a/Makefile.3ds +++ b/Makefile.3ds @@ -15,6 +15,10 @@ include $(DEVKITARM)/3ds_rules # 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 +# GRAPHICS is a list of directories containing graphics files +# GFXBUILD is the directory where converted graphics files will be placed +# If set to $(BUILD), it will statically link in the converted +# files as if they were data files. # # NO_SMDH: if set to anything, no SMDH file is generated. # ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) @@ -27,14 +31,16 @@ include $(DEVKITARM)/3ds_rules # - icon.png # - /default_icon.png #--------------------------------------------------------------------------------- -TARGET := ftpd +TARGET := $(notdir $(CURDIR))-3ds BUILD := build.3ds -SOURCES := source +SOURCES := source source/3ds source/imgui DATA := data INCLUDES := include -ROMFS := +GRAPHICS := gfx.3ds +ROMFS := romfs.3ds +GFXBUILD := $(ROMFS) -APP_TITLE := ftpd snap! +APP_TITLE := ftpd APP_DESCRIPTION := v$(VERSION) APP_AUTHOR := mtheall @@ -46,21 +52,23 @@ RSF_FILE := meta/ftpd-cia.rsf #--------------------------------------------------------------------------------- # options for code generation #--------------------------------------------------------------------------------- +OPTIMIZE := -O2 ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft -CFLAGS := -g -Wall -O3 -mword-relocations \ - -fomit-frame-pointer -ffunction-sections \ - $(ARCH) \ - -DSTATUS_STRING="\"ftpd v$(VERSION)\"" +CFLAGS := -g -Wall $(OPTIMIZE) -mword-relocations \ + -fomit-frame-pointer -ffunction-sections -fdata-sections \ + $(ARCH) $(DEFINES) -CFLAGS += $(INCLUDE) -DARM11 -D_3DS +CFLAGS += $(INCLUDE) -DARM11 -D_3DS \ + -DSTATUS_STRING="\"ftpd v$(VERSION)\"" \ + -DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 ASFLAGS := -g $(ARCH) -LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(TARGET).map +LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(TARGET).map $(OPTIMIZE) -LIBS := -lctru -lm +LIBS := -lcitro3d -lctru -lm #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing @@ -80,14 +88,18 @@ export OUTPUT := $(CURDIR)/$(TARGET) export TOPDIR := $(CURDIR) export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(GRAPHICS),$(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)/*.*))) +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) +SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) +GFXFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.t3s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) #--------------------------------------------------------------------------------- # use CXX for linking C++ projects, CC for standard C @@ -99,16 +111,38 @@ else endif #--------------------------------------------------------------------------------- -export OFILES := $(addsuffix .o,$(BINFILES)) \ - $(CPPFILES:.cpp=.o) \ - $(CFILES:.c=.o) \ - $(SFILES:.s=.o) +#--------------------------------------------------------------------------------- +ifeq ($(GFXBUILD),$(BUILD)) +#--------------------------------------------------------------------------------- +export T3XFILES := $(GFXFILES:.t3s=.t3x) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- +export ROMFS_T3XFILES := $(patsubst %.t3s,$(GFXBUILD)/%.t3x,$(GFXFILES)) +export T3XHFILES := $(patsubst %.t3s,$(BUILD)/%.h,$(GFXFILES)) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- -export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ - $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ - -I$(CURDIR)/$(BUILD) +export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) -export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) \ + $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ + $(addsuffix .o,$(T3XFILES)) + +export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) + +export HFILES := $(PICAFILES:.v.pica=_shbin.h) $(SHLISTFILES:.shlist=_shbin.h) \ + $(addsuffix .h,$(subst .,_,$(BINFILES))) \ + $(GFXFILES:.t3s=.h) + +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 _3DSXDEPS := $(if $(NO_SMDH),,$(OUTPUT).smdh) ifeq ($(strip $(ICON)),) icons := $(wildcard *.png) @@ -131,36 +165,54 @@ ifneq ($(ROMFS),) export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) endif -.PHONY: $(BUILD) clean all +.PHONY: $(BUILD) clean all 3dsx cia #--------------------------------------------------------------------------------- -all: $(BUILD) +all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.3ds -3dsx: $(BUILD) +$(BUILD): + @mkdir -p $@ + +ifneq ($(GFXBUILD),$(BUILD)) +$(GFXBUILD): + @mkdir -p $@ +endif + +ifneq ($(DEPSDIR),$(BUILD)) +$(DEPSDIR): + mkdir -p $@ +endif + +#--------------------------------------------------------------------------------- +$(GFXBUILD)/%.t3x $(BUILD)/%.h: %.t3s +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + tex3ds -i $< -H $(BUILD)/$*.h -d $(DEPSDIR)/$*.d -o $(GFXBUILD)/$*.t3x + +3dsx: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.3ds 3dsx -cia: $(BUILD) +cia: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.3ds cia -$(BUILD): - @[ -d $@ ] || mkdir -p $@ - #--------------------------------------------------------------------------------- clean: @echo clean ... - @$(RM) -r $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf $(TARGET).cia output/ - + @$(RM) -r $(BUILD) \ + $(TARGET).3dsx \ + $(OUTPUT).smdh \ + $(TARGET).elf \ + $(TARGET).cia \ + $(ROMFS_T3XFILES) \ + output/ #--------------------------------------------------------------------------------- else - -DEPENDS := $(OFILES:.o=.d) - #--------------------------------------------------------------------------------- # main targets #--------------------------------------------------------------------------------- -#all: $(OUTPUT).cia $(OUTPUT).3dsx +all: $(OUTPUT).cia $(OUTPUT).3dsx 3dsx: $(OUTPUT).3dsx @@ -173,11 +225,56 @@ $(OUTPUT).smdh: $(TOPDIR)/Makefile $(TOPDIR)/Makefile.3ds $(OUTPUT).3dsx: $(OUTPUT).smdh endif -$(OUTPUT).3dsx: $(OUTPUT).elf +$(OUTPUT).3dsx: $(OUTPUT).elf $(_3DSXDEPS) + +$(OFILES_SOURCES): $(HFILES) + $(OUTPUT).elf: $(OFILES) +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h: %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + $(OFILES): $(TOPDIR)/Makefile $(TOPDIR)/Makefile.3ds +#--------------------------------------------------------------------------------- +.PRECIOUS: %.t3x +#--------------------------------------------------------------------------------- +%.t3x.o %_t3x.h: %.t3x +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +# rules for assembling GPU shaders +#--------------------------------------------------------------------------------- +define shader-as + $(eval CURBIN := $*.shbin) + $(eval DEPSFILE := $(DEPSDIR)/$*.shbin.d) + echo "$(CURBIN).o: $< $1" > $(DEPSFILE) + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h + echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h + picasso -o $(CURBIN) $1 + bin2s $(CURBIN) | $(AS) -o $*.shbin.o +endef + +%.shbin.o %_shbin.h : %.v.pica %.g.pica + @echo $(notdir $^) + @$(call shader-as,$^) + +%.shbin.o %_shbin.h : %.v.pica + @echo $(notdir $<) + @$(call shader-as,$<) + +%.shbin.o %_shbin.h : %.shlist + @echo $(notdir $<) + @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)$(file))) + $(OUTPUT).cia: $(OUTPUT).elf $(OUTPUT).smdh $(TARGET).bnr $(TOPDIR)/$(RSF_FILE) @makerom -f cia -target t -exefslogo -o $@ \ -elf $(OUTPUT).elf -rsf $(TOPDIR)/$(RSF_FILE) \ @@ -190,15 +287,8 @@ $(TARGET).bnr: $(TOPDIR)/$(BNR_IMAGE) $(TOPDIR)/$(BNR_AUDIO) @bannertool makebanner -o $@ -i $(TOPDIR)/$(BNR_IMAGE) -a $(TOPDIR)/$(BNR_AUDIO) @echo "built ... $@" -#--------------------------------------------------------------------------------- -# you need a rule like this for each extension you use as binary data -#--------------------------------------------------------------------------------- -%.bin.o: %.bin -#--------------------------------------------------------------------------------- - @echo $(notdir $<) - @$(bin2o) --include $(DEPENDS) +-include $(DEPSDIR)/*.d #--------------------------------------------------------------------------------------- endif diff --git a/Makefile.linux b/Makefile.linux index f8964af..a8c02f0 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -1,23 +1,36 @@ TARGET := ftpd +BUILD := build.linux -CFILES := $(wildcard source/*.c) -OFILES := $(patsubst source/%,build.linux/%,$(CFILES:.c=.o)) +CFILES := $(wildcard source/pc/*.c) +OFILES := $(patsubst source/%,$(BUILD)/%,$(CFILES:.c=.c.o)) +CXXFILES := $(wildcard source/*.cpp source/imgui/*.cpp source/pc/*.cpp) +OXXFILES := $(patsubst source/%,$(BUILD)/%,$(CXXFILES:.cpp=.cpp.o)) -CFLAGS := -g -Wall -Iinclude -DSTATUS_STRING="\"ftpd v$(VERSION)\"" -LDFLAGS := +CPPFLAGS := -g -Wall -pthread -Iinclude -Isource/pc \ + `pkg-config --cflags gl glfw3` \ + -DSTATUS_STRING="\"ftpd v$(VERSION)\"" \ + -DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \ + -DIMGUI_IMPL_OPENGL_LOADER_GLAD=1 +CFLAGS := $(CPPFLAGS) +CXXFLAGS := $(CPPFLAGS) -std=gnu++17 +LDFLAGS := -pthread `pkg-config --libs gl glfw3` -ldl .PHONY: all clean -all: build.linux $(TARGET) +all: $(TARGET) -build.linux: - @mkdir build.linux/ +$(TARGET): $(OFILES) $(OXXFILES) + $(CXX) -o $@ $^ $(LDFLAGS) -$(TARGET): $(OFILES) - @$(CC) -o $@ $^ $(LDFLAGS) +$(OFILES): $(BUILD)/%.c.o : source/%.c + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + $(CC) -MMD -MP -MF $(BUILD)/$*.c.d $(CFLAGS) -c $< -o $@ -$(OFILES): build.linux/%.o : source/%.c - @$(CC) -o $@ -c $< $(CFLAGS) +$(OXXFILES): $(BUILD)/%.cpp.o : source/%.cpp + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + $(CXX) -MMD -MP -MF $(BUILD)/$*.c.d $(CXXFLAGS) -c $< -o $@ clean: - @$(RM) -r build.linux/ $(TARGET) + @$(RM) -r $(BUILD) $(TARGET) + +-include $(shell find $(BUILD) -name \*.d 2>/dev/null) diff --git a/Makefile.switch b/Makefile.switch index 000989d..03329a6 100644 --- a/Makefile.switch +++ b/Makefile.switch @@ -15,7 +15,7 @@ include $(DEVKITPRO)/libnx/switch_rules # 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. @@ -28,36 +28,51 @@ include $(DEVKITPRO)/libnx/switch_rules # - .jpg # - icon.jpg # - /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): +# - .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. #--------------------------------------------------------------------------------- APP_TITLE := ftpd snap! $(VERSION) APP_AUTHOR := mtheall, TuxSH, WinterMute -ICON := meta/ftpd.jpg +ICON := meta/ftpd.jpg APP_VERSION := $(VERSION) -#--------------------------------------------------------------------------------- -TARGET := ftpd + +TARGET := $(notdir $(CURDIR))-nx BUILD := build.switch -SOURCES := source +SOURCES := source source/imgui source/nx DATA := data INCLUDES := include -EXEFS_SRC := exefs_src +GRAPHICS := gfx.switch +ROMFS := romfs.switch + +# Output folders for autogenerated files in romfs +OUT_SHADERS := shaders #--------------------------------------------------------------------------------- # options for code generation #--------------------------------------------------------------------------------- -ARCH := -march=armv8-a -mtp=soft -fPIE +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE -CFLAGS := -g -Wall -O2 \ - -ffast-math \ +CFLAGS := -g -Wall -Wno-narrowing -Os -ffunction-sections -fdata-sections -save-temps \ $(ARCH) $(DEFINES) -CFLAGS += $(INCLUDE) -D__SWITCH__ -DSTATUS_STRING="\"ftpd v$(VERSION)\"" +CFLAGS += $(INCLUDE) -D__SWITCH__ \ + -DSTATUS_STRING="\"ftpd v$(VERSION)\"" \ + -DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \ + `$(PREFIX)pkg-config --cflags libzstd` -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 +CXXFLAGS := $(CFLAGS) -std=gnu++17 -fno-exceptions -fno-rtti ASFLAGS := -g $(ARCH) -LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) \ + -Wl,-Map,$(notdir $*.map) -Wl,--gc-sections -LIBS := -lnx +LIBS := `$(PREFIX)pkg-config --libs libzstd` -ldeko3dd -lnx #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing @@ -84,6 +99,8 @@ 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))) +GFXFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) #--------------------------------------------------------------------------------- @@ -91,17 +108,35 @@ BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) #--------------------------------------------------------------------------------- ifeq ($(strip $(CPPFILES)),) #--------------------------------------------------------------------------------- - export LD := $(CC) +export LD := $(CC) #--------------------------------------------------------------------------------- else #--------------------------------------------------------------------------------- - export LD := $(CXX) +export LD := $(CXX) #--------------------------------------------------------------------------------- endif #--------------------------------------------------------------------------------- -export OFILES := $(addsuffix .o,$(BINFILES)) \ - $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +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))) + +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 + + ROMFS_GFX := $(addprefix $(ROMFS)/,$(GFXFILES:.png=.rgba.zst)) + ROMFS_TARGETS += $(ROMFS_GFX) + ROMFS_FOLDERS += $(ROMFS) + + export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file)) +endif export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ @@ -109,7 +144,18 @@ export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 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 ifeq ($(strip $(ICON)),) icons := $(wildcard *.jpg) @@ -136,19 +182,69 @@ ifneq ($(APP_TITLEID),) export NACPFLAGS += --titleid=$(APP_TITLEID) endif -.PHONY: $(BUILD) clean all +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: all clean #--------------------------------------------------------------------------------- -all: $(BUILD) +all: $(ROMFS_TARGETS) | $(BUILD) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch + +nxlink: all + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch nxlink $(BUILD): - @[ -d $@ ] || mkdir -p $@ - @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch + @mkdir -p $@ + +ifneq ($(strip $(ROMFS_TARGETS)),) + +$(ROMFS_TARGETS): | $(ROMFS_FOLDERS) + +$(ROMFS_FOLDERS): + @mkdir -p $@ + +$(BUILD)/%.rgba: $(GRAPHICS)/%.png | $(BUILD) + @convert $< $@ + +$(ROMFS_GFX): $(ROMFS)/%.rgba.zst: $(BUILD)/%.rgba + @zstd $< -o $@ --ultra -22 + +$(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 ... - @rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf +ifeq ($(strip $(APP_JSON)),) + @$(RM) -r $(BUILD) $(ROMFS)/*.zst $(ROMFS_FOLDERS) $(TARGET).nro $(TARGET).nacp $(TARGET).elf +else + @$(RM) -r $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf +endif #--------------------------------------------------------------------------------- @@ -160,24 +256,37 @@ DEPENDS := $(OFILES:.o=.d) #--------------------------------------------------------------------------------- # main targets #--------------------------------------------------------------------------------- -all : $(OUTPUT).pfs0 $(OUTPUT).nro +ifeq ($(strip $(APP_JSON)),) -$(OUTPUT).pfs0 : $(OUTPUT).nso +all : $(OUTPUT).nro + +nxlink: $(OUTPUT).nro + @nxlink -s $(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 -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 +%.bin.o %_bin.h : %.bin #--------------------------------------------------------------------------------- @echo $(notdir $<) @$(bin2o) diff --git a/README.md b/README.md index 7ecc378..8c11397 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,15 @@ FTP Server for 3DS/Switch/Linux. ## Latest Builds -CIA: https://mtheall.com/~mtheall/ftpd.cia +CIA: https://mtheall.com/~mtheall/ftpd-3ds.cia -3DSX: https://mtheall.com/~mtheall/ftpd.3dsx +3DSX: https://mtheall.com/~mtheall/ftpd-3ds.3dsx -NRO: https://mtheall.com/~mtheall/ftpd.nro +NRO: https://mtheall.com/~mtheall/ftpd-nx.nro CIA QR Code -![ftpd.cia](https://github.com/mtheall/ftpd/raw/master/ftpd_qr.png) +![ftpd-3ds.cia](https://github.com/mtheall/ftpd/raw/master/ftpd_qr.png) ## Build and install @@ -26,7 +26,7 @@ You must set up the [development environment](https://devkitpro.org/wiki/Getting ### 3DSX -The following pacman packages are required to build `ftpd.3dsx`: +The following pacman packages are required to build `ftpd-3ds.3dsx`: 3dstools devkitARM @@ -34,13 +34,13 @@ The following pacman packages are required to build `ftpd.3dsx`: They are available as part of the `3ds-dev` meta-package. -Build `ftpd.3dsx`: +Build `ftpd-3ds.3dsx`: make 3dsx ### NRO -The following pacman packages are required to build `ftpd.nro`: +The following pacman packages are required to build `ftpd-nx.nro`: devkitA64 libnx @@ -48,7 +48,7 @@ The following pacman packages are required to build `ftpd.nro`: They are available as part of the `switch-dev` meta-package. -Build `ftpd.nro`: +Build `ftpd-nx.nro`: make nro diff --git a/delog.py b/delog.py deleted file mode 100755 index bfe3b77..0000000 --- a/delog.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python - -import sys -import re - -if __name__ == '__main__': - regex = re.compile('\x1b\[[0-9]*;[0-9]*H') - for line in sys.stdin: - print(regex.sub('', line).strip()) diff --git a/ftpd_qr.png b/ftpd_qr.png index 9d04fbb..a15c750 100644 Binary files a/ftpd_qr.png and b/ftpd_qr.png differ diff --git a/gfx.3ds/battery0.png b/gfx.3ds/battery0.png new file mode 100644 index 0000000..eed0f49 Binary files /dev/null and b/gfx.3ds/battery0.png differ diff --git a/gfx.3ds/battery1.png b/gfx.3ds/battery1.png new file mode 100644 index 0000000..855d8a8 Binary files /dev/null and b/gfx.3ds/battery1.png differ diff --git a/gfx.3ds/battery2.png b/gfx.3ds/battery2.png new file mode 100644 index 0000000..45c5875 Binary files /dev/null and b/gfx.3ds/battery2.png differ diff --git a/gfx.3ds/battery3.png b/gfx.3ds/battery3.png new file mode 100644 index 0000000..713d244 Binary files /dev/null and b/gfx.3ds/battery3.png differ diff --git a/gfx.3ds/battery4.png b/gfx.3ds/battery4.png new file mode 100644 index 0000000..05d7b8b Binary files /dev/null and b/gfx.3ds/battery4.png differ diff --git a/gfx.3ds/batteryCharge.png b/gfx.3ds/batteryCharge.png new file mode 100644 index 0000000..792398c Binary files /dev/null and b/gfx.3ds/batteryCharge.png differ diff --git a/gfx.3ds/c3dlogo.png b/gfx.3ds/c3dlogo.png new file mode 100644 index 0000000..78634d8 Binary files /dev/null and b/gfx.3ds/c3dlogo.png differ diff --git a/gfx.3ds/gfx.t3s b/gfx.3ds/gfx.t3s new file mode 100644 index 0000000..7a959ef --- /dev/null +++ b/gfx.3ds/gfx.t3s @@ -0,0 +1,12 @@ +-f rgba -z auto --atlas +battery0.png +battery1.png +battery2.png +battery3.png +battery4.png +batteryCharge.png +c3dlogo.png +wifiNull.png +wifi1.png +wifi2.png +wifi3.png diff --git a/gfx.3ds/wifi1.png b/gfx.3ds/wifi1.png new file mode 100644 index 0000000..bf5d1d4 Binary files /dev/null and b/gfx.3ds/wifi1.png differ diff --git a/gfx.3ds/wifi2.png b/gfx.3ds/wifi2.png new file mode 100644 index 0000000..428d1d8 Binary files /dev/null and b/gfx.3ds/wifi2.png differ diff --git a/gfx.3ds/wifi3.png b/gfx.3ds/wifi3.png new file mode 100644 index 0000000..92ea210 Binary files /dev/null and b/gfx.3ds/wifi3.png differ diff --git a/gfx.3ds/wifiNull.png b/gfx.3ds/wifiNull.png new file mode 100644 index 0000000..91c4176 Binary files /dev/null and b/gfx.3ds/wifiNull.png differ diff --git a/gfx.switch/deko3d.png b/gfx.switch/deko3d.png new file mode 100644 index 0000000..fbf1adf Binary files /dev/null and b/gfx.switch/deko3d.png differ diff --git a/include/console.h b/include/console.h deleted file mode 100644 index cc489f7..0000000 --- a/include/console.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#ifdef _3DS -#include <3ds.h> -#elif defined(__SWITCH__) -#include -#endif - -#if defined(_3DS) || defined(__SWITCH__) -#define ESC(x) "\x1b[" #x -#define RESET ESC(0m) -#define BLACK ESC(30m) -#define RED ESC(31;1m) -#define GREEN ESC(32;1m) -#define YELLOW ESC(33;1m) -#define BLUE ESC(34;1m) -#define MAGENTA ESC(35;1m) -#define CYAN ESC(36;1m) -#define WHITE ESC(37;1m) -#else -#define ESC(x) -#define RESET -#define BLACK -#define RED -#define GREEN -#define YELLOW -#define BLUE -#define MAGENTA -#define CYAN -#define WHITE -#endif - -void console_init(void); - -__attribute__((format(printf,1,2))) -void console_set_status(const char *fmt, ...); - -__attribute__((format(printf,1,2))) -void console_print(const char *fmt, ...); - -__attribute__((format(printf,1,2))) -void debug_print(const char *fmt, ...); - -void console_render(void); diff --git a/include/fs.h b/include/fs.h new file mode 100644 index 0000000..a50fc5c --- /dev/null +++ b/include/fs.h @@ -0,0 +1,113 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +#include + +#include +#include +#include +#include + +namespace fs +{ +std::string printSize (std::uint64_t size_); + +class File +{ +public: + ~File (); + + File (); + + File (File const &that_) = delete; + + File (File &&that_); + + File &operator= (File const &that_) = delete; + + File &operator= (File &&that_); + + operator bool () const; + operator FILE * () const; + + void setBufferSize (std::size_t size_); + + bool open (char const *path_, char const *mode_ = "rb"); + void close (); + + ssize_t seek (std::size_t pos_, int origin_); + + ssize_t read (void *data_, std::size_t size_); + bool readAll (void *data_, std::size_t size_); + + ssize_t write (void const *data_, std::size_t size_); + bool writeAll (void const *data_, std::size_t size_); + + template + T read () + { + T data; + if (read (&data, sizeof (data)) != sizeof (data)) + std::abort (); + + return data; + } + + template + void write (T const &data_) + { + if (write (&data_, sizeof (data_)) != sizeof (data_)) + std::abort (); + } + +private: + std::unique_ptr m_fp{nullptr, nullptr}; + std::unique_ptr m_buffer; + std::size_t m_bufferSize = 0; +}; + +class Dir +{ +public: + ~Dir (); + + Dir (); + + Dir (Dir const &that_) = delete; + + Dir (Dir &&that_); + + Dir &operator= (Dir const &that_) = delete; + + Dir &operator= (Dir &&that_); + + operator bool () const; + operator DIR * () const; + + bool open (char const *const path_); + void close (); + struct dirent *read (); + +private: + std::unique_ptr m_dp{nullptr, nullptr}; +}; +} diff --git a/include/ftp.h b/include/ftp.h deleted file mode 100644 index 279586e..0000000 --- a/include/ftp.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -/*! Loop status */ -typedef enum -{ - LOOP_CONTINUE, /*!< Continue looping */ - LOOP_RESTART, /*!< Reinitialize */ - LOOP_EXIT, /*!< Terminate looping */ -} loop_status_t; - -int ftp_init(void); -loop_status_t ftp_loop(void); -void ftp_exit(void); diff --git a/include/ftpServer.h b/include/ftpServer.h new file mode 100644 index 0000000..cabe8a0 --- /dev/null +++ b/include/ftpServer.h @@ -0,0 +1,73 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +#include "ftpSession.h" +#include "log.h" +#include "platform.h" +#include "socket.h" + +#include +#include +#include +#include +#include + +class FtpServer; +using UniqueFtpServer = std::unique_ptr; + +class FtpServer +{ +public: + ~FtpServer (); + + void draw (); + + static UniqueFtpServer create (std::uint16_t port_); + + static void updateFreeSpace (); + + static std::time_t startTime (); + +private: + FtpServer (std::uint16_t port_); + + void handleStartButton (); + void handleStopButton (); + + void loop (); + void threadFunc (); + + platform::Thread m_thread; + platform::Mutex m_lock; + + UniqueSocket m_socket; + + std::string m_name; + + SharedLog m_log; + + std::vector m_sessions; + + std::uint16_t m_port; + + std::atomic m_quit; +}; diff --git a/include/ftpSession.h b/include/ftpSession.h new file mode 100644 index 0000000..fbcdbbb --- /dev/null +++ b/include/ftpSession.h @@ -0,0 +1,205 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +#include "fs.h" +#include "ioBuffer.h" +#include "platform.h" +#include "socket.h" + +#include +#include +#include +#include +#include + +class FtpSession; +using UniqueFtpSession = std::unique_ptr; + +class FtpSession +{ +public: + ~FtpSession (); + + bool dead (); + + void draw (); + + static UniqueFtpSession create (UniqueSocket commandSocket_); + + static void poll (std::vector const &sessions_); + +private: + constexpr static auto COMMAND_BUFFERSIZE = 4096; + constexpr static auto RESPONSE_BUFFERSIZE = 32768; + constexpr static auto XFER_BUFFERSIZE = 65536; + constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE; + +#ifdef _3DS + constexpr static auto SOCK_BUFFERSIZE = 32768; + constexpr static auto POSITION_HISTORY = 100; +#else + constexpr static auto SOCK_BUFFERSIZE = XFER_BUFFERSIZE; + constexpr static auto POSITION_HISTORY = 300; +#endif + + enum class State + { + COMMAND, + DATA_CONNECT, + DATA_TRANSFER, + }; + + enum class XferFileMode + { + RETR, + STOR, + APPE, + }; + + enum class XferDirMode + { + LIST, + MLSD, + MLST, + NLST, + STAT, + }; + + FtpSession (UniqueSocket commandSocket_); + + void setState (State state_, bool closePasv_, bool closeData_); + + void closeData (); + + bool changeDir (char const *args_); + + bool dataAccept (); + bool dataConnect (); + + void updateFreeSpace (); + + int fillDirent (struct stat const &st_, std::string_view path_, char const *type_ = nullptr); + int fillDirent (std::string const &path_, char const *type_ = nullptr); + void xferFile (char const *args_, XferFileMode mode_); + void xferDir (char const *args_, XferDirMode mode_, bool workaround_); + + void readCommand (int events_); + void writeResponse (); + + __attribute__ ((format (printf, 2, 3))) void sendResponse (char const *fmt_, ...); + void sendResponse (std::string_view response_); + + bool (FtpSession::*m_transfer) () = nullptr; + + bool listTransfer (); + bool retrieveTransfer (); + bool storeTransfer (); + + platform::Mutex m_lock; + + SharedSocket m_commandSocket; + UniqueSocket m_pasvSocket; + SharedSocket m_dataSocket; + std::vector m_pendingCloseSocket; + + IOBuffer m_commandBuffer; + IOBuffer m_responseBuffer; + IOBuffer m_xferBuffer; + + SockAddr m_pasvAddr; + SockAddr m_portAddr; + + std::string m_cwd = "/"; + std::string m_lwd; + std::string m_rename; + std::string m_workItem; + + std::string m_windowName; + std::string m_plotName; + + std::uint64_t m_restartPosition = 0; + std::uint64_t m_filePosition = 0; + std::uint64_t m_fileSize = 0; + + platform::steady_clock::time_point m_filePositionTime; + std::uint64_t m_filePositionHistory[POSITION_HISTORY]; + float m_filePositionDeltas[POSITION_HISTORY]; + float m_xferRate; + + State m_state = State::COMMAND; + + fs::File m_file; + fs::Dir m_dir; + + XferDirMode m_xferDirMode; + + bool m_pasv : 1; + bool m_port : 1; + bool m_recv : 1; + bool m_send : 1; + bool m_urgent : 1; + + bool m_mlstType : 1; + bool m_mlstSize : 1; + bool m_mlstModify : 1; + bool m_mlstPerm : 1; + bool m_mlstUnixMode : 1; + + void ABOR (char const *args_); + void ALLO (char const *args_); + void APPE (char const *args_); + void CDUP (char const *args_); + void CWD (char const *args_); + void DELE (char const *args_); + void FEAT (char const *args_); + void HELP (char const *args_); + void LIST (char const *args_); + void MDTM (char const *args_); + void MKD (char const *args_); + void MLSD (char const *args_); + void MLST (char const *args_); + void MODE (char const *args_); + void NLST (char const *args_); + void NOOP (char const *args_); + void OPTS (char const *args_); + void PASS (char const *args_); + void PASV (char const *args_); + void PORT (char const *args_); + void PWD (char const *args_); + void QUIT (char const *args_); + void REST (char const *args_); + void RETR (char const *args_); + void RMD (char const *args_); + void RNFR (char const *args_); + void RNTO (char const *args_); + void SIZE (char const *args_); + void STAT (char const *args_); + void STOR (char const *args_); + void STOU (char const *args_); + void STRU (char const *args_); + void SYST (char const *args_); + void TYPE (char const *args_); + void USER (char const *args_); + + static std::vector> const + handlers; +}; diff --git a/include/imgui.h b/include/imgui.h new file mode 100644 index 0000000..4b71f2a --- /dev/null +++ b/include/imgui.h @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui/releases/tag/v1.75" diff --git a/include/ioBuffer.h b/include/ioBuffer.h new file mode 100644 index 0000000..fd8c961 --- /dev/null +++ b/include/ioBuffer.h @@ -0,0 +1,51 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +#include +#include + +class IOBuffer +{ +public: + ~IOBuffer (); + + IOBuffer (std::size_t size_); + + char *freeArea () const; + std::size_t freeSize () const; + void markFree (std::size_t size_); + + char *usedArea () const; + std::size_t usedSize () const; + void markUsed (std::size_t size_); + + bool empty () const; + std::size_t capacity () const; + void clear (); + void coalesce (); + +private: + std::unique_ptr m_buffer; + std::size_t const m_size; + std::size_t m_start = 0; + std::size_t m_end = 0; +}; diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..1afcb05 --- /dev/null +++ b/include/log.h @@ -0,0 +1,81 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +#include "platform.h" + +#include +#include +#include +#include +#include + +class Log; +using SharedLog = std::shared_ptr; +using WeakLog = std::weak_ptr; + +class Log +{ +public: + enum Level + { + DEBUG, + INFO, + ERROR, + COMMAND, + RESPONSE, + }; + + ~Log (); + + void draw (); + + static SharedLog create (); + static void bind (SharedLog log_); + + __attribute__ ((format (printf, 1, 2))) static void debug (char const *fmt_, ...); + __attribute__ ((format (printf, 1, 2))) static void info (char const *fmt_, ...); + __attribute__ ((format (printf, 1, 2))) static void error (char const *fmt_, ...); + __attribute__ ((format (printf, 1, 2))) static void command (char const *fmt_, ...); + __attribute__ ((format (printf, 1, 2))) static void response (char const *fmt_, ...); + + static void log (Level level_, char const *fmt_, va_list ap_); + static void log (Level level_, std::string_view message_); + +private: + Log (); + + void _log (Level level_, char const *fmt_, va_list ap_); + + struct Message + { + Message (Level const level_, std::string message_) + : level (level_), message (std::move (message_)) + { + } + + Level level; + std::string message; + }; + + std::vector m_messages; + platform::Mutex m_lock; +}; diff --git a/include/platform.h b/include/platform.h new file mode 100644 index 0000000..689d3b5 --- /dev/null +++ b/include/platform.h @@ -0,0 +1,95 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +#ifdef _3DS +#include <3ds.h> +#endif + +#include +#include +#include +#include + +namespace platform +{ +bool init (); +bool loop (); +void render (); +void exit (); + +#ifdef _3DS +struct steady_clock +{ + using rep = std::uint64_t; + using period = std::ratio<1, SYSCLOCK_ARM11>; + using duration = std::chrono::duration; + using time_point = std::chrono::time_point; + + constexpr static bool is_steady = true; + static time_point now () noexcept + { + return time_point (duration (svcGetSystemTick ())); + } +}; +#else +using steady_clock = std::chrono::steady_clock; +#endif + +class Thread +{ +public: + ~Thread (); + Thread (); + + Thread (std::function func_); + + Thread (Thread const &that_) = delete; + + Thread (Thread &&that_); + + Thread &operator= (Thread const &that_) = delete; + + Thread &operator= (Thread &&that_); + + void join (); + + static void sleep (std::chrono::milliseconds timeout_); + +private: + class privateData_t; + std::unique_ptr m_d; +}; + +class Mutex +{ +public: + ~Mutex (); + Mutex (); + + void lock (); + void unlock (); + +private: + class privateData_t; + std::unique_ptr m_d; +}; +} diff --git a/include/sockAddr.h b/include/sockAddr.h new file mode 100644 index 0000000..d44f30e --- /dev/null +++ b/include/sockAddr.h @@ -0,0 +1,70 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +#include +#include + +#include + +class SockAddr +{ +public: + ~SockAddr (); + + SockAddr (); + + SockAddr (SockAddr const &that_); + + SockAddr (SockAddr &&that_); + + SockAddr &operator= (SockAddr const &that_); + + SockAddr &operator= (SockAddr &&that_); + + SockAddr (struct sockaddr const &addr_); + + SockAddr (struct sockaddr_in const &addr_); + +#ifndef _3DS + SockAddr (struct sockaddr_in6 const &addr_); +#endif + + SockAddr (struct sockaddr_storage const &addr_); + + operator struct sockaddr_in const & () const; + +#ifndef _3DS + operator struct sockaddr_in6 const & () const; +#endif + + operator struct sockaddr_storage const & () const; + + operator struct sockaddr * (); + operator struct sockaddr const * () const; + + std::uint16_t port () const; + char const *name (char *buffer_, std::size_t size_) const; + char const *name () const; + +private: + struct sockaddr_storage m_addr = {}; +}; diff --git a/include/socket.h b/include/socket.h new file mode 100644 index 0000000..c3fac2a --- /dev/null +++ b/include/socket.h @@ -0,0 +1,97 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +#include "ioBuffer.h" +#include "sockAddr.h" + +#include +#include + +class Socket; +using UniqueSocket = std::unique_ptr; +using SharedSocket = std::shared_ptr; + +class Socket +{ +public: + struct PollInfo + { + std::reference_wrapper socket; + int events; + int revents; + }; + + ~Socket (); + + UniqueSocket accept (); + int atMark (); + bool bind (SockAddr const &addr_); + bool connect (SockAddr const &addr_); + bool listen (int backlog_); + bool shutdown (int how_); + + bool setLinger (bool enable_, std::chrono::seconds time_); + bool setNonBlocking (bool nonBlocking_ = true); + bool setReuseAddress (bool reuse_ = true); + bool setRecvBufferSize (std::size_t size_); + bool setSendBufferSize (std::size_t size_); + + ssize_t read (void *buffer_, std::size_t size_, bool oob_ = false); + ssize_t read (IOBuffer &buffer_, bool oob_ = false); + ssize_t write (void const *buffer_, std::size_t size_); + ssize_t write (IOBuffer &buffer_); + + SockAddr const &sockName () const; + SockAddr const &peerName () const; + + static UniqueSocket create (); + + static int poll (PollInfo *info_, std::size_t count_, std::chrono::milliseconds timeout_); + + int fd () const + { + return m_fd; + } + +private: + Socket () = delete; + + Socket (int fd_); + + Socket (int fd_, SockAddr const &sockName_, SockAddr const &peerName_); + + Socket (Socket const &that_) = delete; + + Socket (Socket &&that_) = delete; + + Socket &operator= (Socket const &that_) = delete; + + Socket &operator= (Socket &&that_) = delete; + + SockAddr m_sockName; + SockAddr m_peerName; + + int const m_fd; + + bool m_listening : 1; + bool m_connected : 1; +}; diff --git a/source/3ds/imgui_citro3d.cpp b/source/3ds/imgui_citro3d.cpp new file mode 100644 index 0000000..48e7c74 --- /dev/null +++ b/source/3ds/imgui_citro3d.cpp @@ -0,0 +1,623 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "imgui_citro3d.h" + +#include + +#include "vshader_shbin.h" + +#include "imgui.h" + +#include +#include +#include +#include + +namespace +{ +std::vector s_fontRanges; + +constexpr auto CLEAR_COLOR = 0x204B7AFF; + +constexpr auto DISPLAY_TRANSFER_FLAGS = + GX_TRANSFER_FLIP_VERT (0) | GX_TRANSFER_OUT_TILED (0) | GX_TRANSFER_RAW_COPY (0) | + GX_TRANSFER_IN_FORMAT (GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT (GX_TRANSFER_FMT_RGB8) | + GX_TRANSFER_SCALING (GX_TRANSFER_SCALE_NO); + +C3D_RenderTarget *s_top = nullptr; +C3D_RenderTarget *s_bottom = nullptr; + +DVLB_s *s_vsh = nullptr; +shaderProgram_s s_program; + +int s_projLocation; +C3D_Mtx s_projTop; +C3D_Mtx s_projBottom; + +std::vector s_fontTextures; +float s_textScale; + +std::uint32_t s_boundScissor[4]; +ImDrawVert *s_boundVtxData; +C3D_Tex *s_boundTexture; + +ImDrawVert *s_vtxData = nullptr; +std::size_t s_vtxSize = 0; +ImDrawIdx *s_idxData = nullptr; +std::size_t s_idxSize = 0; + +std::uint32_t fontCodePointFromGlyphIndex (CFNT_s *const font_, int const glyphIndex_) +{ + for (auto cmap = fontGetInfo (font_)->cmap; cmap; cmap = cmap->next) + { + switch (cmap->mappingMethod) + { + case CMAP_TYPE_DIRECT: + assert (cmap->codeEnd >= cmap->codeBegin); + if (glyphIndex_ >= cmap->indexOffset && + glyphIndex_ <= cmap->codeEnd - cmap->codeBegin + cmap->indexOffset) + return glyphIndex_ - cmap->indexOffset + cmap->codeBegin; + break; + + case CMAP_TYPE_TABLE: + for (int i = 0; i <= cmap->codeEnd - cmap->codeBegin; ++i) + { + if (cmap->indexTable[i] == glyphIndex_) + return cmap->codeBegin + i; + } + break; + + case CMAP_TYPE_SCAN: + for (unsigned i = 0; i < cmap->nScanEntries; ++i) + { + assert (cmap->scanEntries[i].code >= cmap->codeBegin); + assert (cmap->scanEntries[i].code <= cmap->codeEnd); + if (glyphIndex_ == cmap->scanEntries[i].glyphIndex) + return cmap->scanEntries[i].code; + } + break; + } + } + + return 0; +} + +void setupRenderState (gfxScreen_t const screen_) +{ + C3D_CullFace (GPU_CULL_NONE); + + // configure attributes for user with vertex shader + auto const attrInfo = C3D_GetAttrInfo (); + AttrInfo_Init (attrInfo); + AttrInfo_AddLoader (attrInfo, 0, GPU_FLOAT, 2); // v0 = inPos + AttrInfo_AddLoader (attrInfo, 1, GPU_FLOAT, 2); // v1 = inUv + AttrInfo_AddLoader (attrInfo, 2, GPU_UNSIGNED_BYTE, 4); // v2 = inColor + + std::memset (s_boundScissor, 0xFF, sizeof (s_boundScissor)); + s_boundVtxData = nullptr; + s_boundTexture = nullptr; + + C3D_BindProgram (&s_program); + + C3D_DepthTest (true, GPU_GREATER, GPU_WRITE_COLOR); + + C3D_AlphaBlend (GPU_BLEND_ADD, + GPU_BLEND_ADD, + GPU_SRC_ALPHA, + GPU_ONE_MINUS_SRC_ALPHA, + GPU_SRC_ALPHA, + GPU_ONE_MINUS_SRC_ALPHA); + + if (screen_ == GFX_TOP) + C3D_FVUnifMtx4x4 (GPU_VERTEX_SHADER, s_projLocation, &s_projTop); + else + C3D_FVUnifMtx4x4 (GPU_VERTEX_SHADER, s_projLocation, &s_projBottom); +} +} + +void imgui::citro3d::init () +{ + // Setup back-end capabilities flags + ImGuiIO &io = ImGui::GetIO (); + + io.BackendRendererName = "citro3d"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; + + C3D_Init (C3D_DEFAULT_CMDBUF_SIZE); + + s_top = C3D_RenderTargetCreate (240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); + C3D_RenderTargetSetOutput (s_top, GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS); + + s_bottom = C3D_RenderTargetCreate (240, 320, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); + C3D_RenderTargetSetOutput (s_bottom, GFX_BOTTOM, GFX_LEFT, DISPLAY_TRANSFER_FLAGS); + + s_vsh = DVLB_ParseFile ( + const_cast (reinterpret_cast (vshader_shbin)), + vshader_shbin_size); + shaderProgramInit (&s_program); + shaderProgramSetVsh (&s_program, &s_vsh->DVLE[0]); + + s_projLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "proj"); + + Mtx_OrthoTilt (&s_projTop, 0.0f, 800.0f, 480.0f, 0.0f, -1.0f, 1.0f, false); + Mtx_OrthoTilt (&s_projBottom, 80.0f, 720.0f, 960.0f, 480.0f, -1.0f, 1.0f, false); + + s_vtxSize = 65536; + s_vtxData = reinterpret_cast (linearAlloc (sizeof (ImDrawVert) * s_vtxSize)); + if (!s_vtxData) + svcBreak (USERBREAK_PANIC); + + s_idxSize = 65536; + s_idxData = reinterpret_cast (linearAlloc (sizeof (ImDrawIdx) * s_idxSize)); + if (!s_idxData) + svcBreak (USERBREAK_PANIC); + + // ensure the shared system font is mapped + if (R_FAILED (fontEnsureMapped ())) + svcBreak (USERBREAK_PANIC); + + // load the glyph texture sheets + auto const font = fontGetSystemFont (); + auto const fontInfo = fontGetInfo (font); + auto const glyphInfo = fontGetGlyphInfo (font); + assert (s_fontTextures.empty ()); + s_fontTextures.resize (glyphInfo->nSheets + 1); + std::memset (s_fontTextures.data (), 0x00, s_fontTextures.size () * sizeof (s_fontTextures[0])); + + s_textScale = 30.0f / glyphInfo->cellHeight; + + for (unsigned i = 0; i < glyphInfo->nSheets; ++i) + { + auto &tex = s_fontTextures[i]; + tex.data = fontGetGlyphSheetTex (font, i); + if (!tex.data) + svcBreak (USERBREAK_PANIC); + tex.fmt = static_cast (glyphInfo->sheetFmt); + tex.size = glyphInfo->sheetSize; + tex.width = glyphInfo->sheetWidth; + tex.height = glyphInfo->sheetHeight; + tex.param = GPU_TEXTURE_MAG_FILTER (GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER (GPU_LINEAR) | + GPU_TEXTURE_WRAP_S (GPU_REPEAT) | GPU_TEXTURE_WRAP_T (GPU_REPEAT); + tex.border = 0xFFFFFFFF; + tex.lodParam = 0; + } + + { + auto &tex = s_fontTextures[glyphInfo->nSheets]; + C3D_TexInit (&tex, 8, 8, GPU_A4); + + std::uint32_t size; + auto data = C3D_Tex2DGetImagePtr (&tex, 0, &size); + if (!data || !size) + svcBreak (USERBREAK_PANIC); + std::memset (data, 0xFF, size); + } + + ImWchar alterChar = fontCodePointFromGlyphIndex (font, fontInfo->alterCharIndex); + if (!alterChar) + alterChar = '?'; + + std::vector charSet; + for (auto cmap = fontInfo->cmap; cmap; cmap = cmap->next) + { + switch (cmap->mappingMethod) + { + case CMAP_TYPE_DIRECT: + case CMAP_TYPE_TABLE: + assert (cmap->codeEnd >= cmap->codeBegin); + charSet.reserve (charSet.size () + cmap->codeEnd - cmap->codeBegin + 1); + for (auto i = cmap->codeBegin; i <= cmap->codeEnd; ++i) + charSet.emplace_back (i); + break; + case CMAP_TYPE_SCAN: + charSet.reserve (charSet.size () + cmap->nScanEntries); + for (unsigned i = 0; i < cmap->nScanEntries; ++i) + { + assert (cmap->scanEntries[i].code >= cmap->codeBegin); + assert (cmap->scanEntries[i].code <= cmap->codeEnd); + charSet.emplace_back (cmap->scanEntries[i].code); + } + break; + } + } + + if (charSet.empty ()) + svcBreak (USERBREAK_PANIC); + + std::sort (std::begin (charSet), std::end (charSet)); + charSet.erase (std::unique (std::begin (charSet), std::end (charSet)), std::end (charSet)); + + auto it = std::begin (charSet); + ImWchar start = *it++; + ImWchar prev = start; + while (it != std::end (charSet)) + { + if (*it != prev + 1) + { + s_fontRanges.emplace_back (start); + s_fontRanges.emplace_back (prev); + + start = *it; + } + + prev = *it++; + } + s_fontRanges.emplace_back (start); + s_fontRanges.emplace_back (prev); + s_fontRanges.emplace_back (0); + + auto const atlas = ImGui::GetIO ().Fonts; + atlas->Clear (); + atlas->TexWidth = glyphInfo->sheetWidth; + atlas->TexHeight = glyphInfo->sheetHeight * glyphInfo->nSheets; + atlas->TexUvScale = ImVec2 (1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); + atlas->TexUvWhitePixel = ImVec2 (0.5f / 8.0f, glyphInfo->nSheets + 0.5f / 8.0f); + atlas->TexPixelsAlpha8 = static_cast (IM_ALLOC (1)); // dummy allocation + + ImFontConfig config; + config.FontData = nullptr; + config.FontDataSize = 0; + config.FontDataOwnedByAtlas = true; + config.FontNo = 0; + config.SizePixels = 14.0f; + config.OversampleH = 3; + config.OversampleV = 1; + config.PixelSnapH = false; + config.GlyphExtraSpacing = ImVec2 (0.0f, 0.0f); + config.GlyphOffset = ImVec2 (0.0f, 0.0f); + config.GlyphRanges = s_fontRanges.data (); + config.GlyphMinAdvanceX = 0.0f; + config.GlyphMaxAdvanceX = std::numeric_limits::max (); + config.MergeMode = false; + config.RasterizerFlags = 0; + config.RasterizerMultiply = 1.0f; + config.EllipsisChar = 0x2026; + std::memset (config.Name, 0, sizeof (config.Name)); + + auto const imFont = IM_NEW (ImFont); + config.DstFont = imFont; + + atlas->ConfigData.push_back (config); + atlas->Fonts.push_back (imFont); + // atlas->CustomRectIds[0] = atlas->AddCustomRectRegular (0x80000000, 108 * 2 + 1, 27); + // atlas->CustomRects[0].X = 0; + // atlas->CustomRects[0].Y = 0; + atlas->SetTexID (s_fontTextures.data ()); + + imFont->FallbackAdvanceX = fontInfo->defaultWidth.charWidth; + imFont->FontSize = fontInfo->lineFeed; + + fontGlyphPos_s glyphPos; + for (auto const &code : charSet) + { + auto const glyphIndex = fontGlyphIndexFromCodePoint (font, code); + if (glyphIndex < 0) + svcBreak (USERBREAK_PANIC); + + fontCalcGlyphPos (&glyphPos, + font, + glyphIndex, + GLYPH_POS_CALC_VTXCOORD | GLYPH_POS_AT_BASELINE, + 1.0f, + 1.0f); + + ImFontGlyph glyph; + + glyph.Codepoint = code; + glyph.AdvanceX = glyphPos.xAdvance; + glyph.X0 = glyphPos.vtxcoord.left; + glyph.Y0 = glyphPos.vtxcoord.top; + glyph.X1 = glyphPos.vtxcoord.right; + glyph.Y1 = glyphPos.vtxcoord.bottom; + glyph.U0 = glyphPos.texcoord.left; + glyph.V0 = glyphPos.sheetIndex + glyphPos.texcoord.top; + glyph.U1 = glyphPos.texcoord.right; + glyph.V1 = glyphPos.sheetIndex + glyphPos.texcoord.bottom; + + imFont->Glyphs.push_back (glyph); + imFont->MetricsTotalSurface += + static_cast ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) * + static_cast ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f); + } + + imFont->BuildLookupTable (); + + imFont->DisplayOffset.x = 0.0f; + imFont->DisplayOffset.y = fontInfo->ascent; + + imFont->ContainerAtlas = atlas; + imFont->ConfigData = &atlas->ConfigData[0]; + imFont->ConfigDataCount = 1; + imFont->FallbackChar = alterChar; + imFont->EllipsisChar = config.EllipsisChar; + imFont->Scale = 1.0f; + imFont->Ascent = fontInfo->ascent; + imFont->Descent = 0.0f; +} + +void imgui::citro3d::exit () +{ + linearFree (s_idxData); + linearFree (s_vtxData); + + assert (!s_fontTextures.empty ()); + C3D_TexDelete (&s_fontTextures.back ()); + + shaderProgramFree (&s_program); + DVLB_Free (s_vsh); + + C3D_RenderTargetDelete (s_bottom); + C3D_RenderTargetDelete (s_top); + + C3D_Fini (); +} + +void imgui::citro3d::newFrame () +{ +} + +void imgui::citro3d::render () +{ + C3D_FrameBegin (C3D_FRAME_SYNCDRAW); + C3D_RenderTargetClear (s_top, C3D_CLEAR_ALL, CLEAR_COLOR, 0); + C3D_RenderTargetClear (s_bottom, C3D_CLEAR_ALL, CLEAR_COLOR, 0); + + auto const drawData = ImGui::GetDrawData (); + if (drawData->CmdListsCount <= 0) + { + C3D_FrameEnd (0); + return; + } + + unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x; + unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y; + if (width <= 0 || height <= 0) + { + C3D_FrameEnd (0); + return; + } + + if (s_vtxSize < static_cast (drawData->TotalVtxCount)) + { + linearFree (s_vtxData); + + // add 10% to avoid growing many frames in a row + s_vtxSize = drawData->TotalVtxCount * 1.1f; + s_vtxData = reinterpret_cast (linearAlloc (sizeof (ImDrawVert) * s_vtxSize)); + if (!s_vtxData) + svcBreak (USERBREAK_PANIC); + } + + if (s_idxSize < static_cast (drawData->TotalIdxCount)) + { + // add 10% to avoid growing many frames in a row + s_idxSize = drawData->TotalIdxCount * 1.1f; + s_idxData = reinterpret_cast (linearAlloc (sizeof (ImDrawIdx) * s_idxSize)); + if (!s_vtxData) + svcBreak (USERBREAK_PANIC); + } + + // Will project scissor/clipping rectangles into framebuffer space + // (0,0) unless using multi-viewports + auto const clipOff = drawData->DisplayPos; + // (1,1) unless using retina display which are often (2,2) + auto const clipScale = drawData->FramebufferScale; + + // copy data into vertex/index buffers + std::size_t offsetVtx = 0; + std::size_t offsetIdx = 0; + for (int i = 0; i < drawData->CmdListsCount; ++i) + { + auto const &cmdList = *drawData->CmdLists[i]; + if (s_vtxSize - offsetVtx < static_cast (cmdList.VtxBuffer.Size)) + svcBreak (USERBREAK_PANIC); + if (s_idxSize - offsetIdx < static_cast (cmdList.IdxBuffer.Size)) + svcBreak (USERBREAK_PANIC); + + std::memcpy (&s_vtxData[offsetVtx], + cmdList.VtxBuffer.Data, + sizeof (ImDrawVert) * cmdList.VtxBuffer.Size); + std::memcpy (&s_idxData[offsetIdx], + cmdList.IdxBuffer.Data, + sizeof (ImDrawIdx) * cmdList.IdxBuffer.Size); + + offsetVtx += cmdList.VtxBuffer.Size; + offsetIdx += cmdList.IdxBuffer.Size; + } + + for (auto const &screen : {GFX_TOP, GFX_BOTTOM}) + { + if (screen == GFX_TOP) + C3D_FrameDrawOn (s_top); + else + C3D_FrameDrawOn (s_bottom); + + setupRenderState (screen); + + offsetVtx = 0; + offsetIdx = 0; + + // Render command lists + for (int i = 0; i < drawData->CmdListsCount; ++i) + { + auto const &cmdList = *drawData->CmdLists[i]; + for (auto const &cmd : cmdList.CmdBuffer) + { + if (cmd.UserCallback) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user + // to request the renderer to reset render state.) + if (cmd.UserCallback == ImDrawCallback_ResetRenderState) + setupRenderState (screen); + else + cmd.UserCallback (&cmdList, &cmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec4 clip; + clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x; + clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y; + clip.z = (cmd.ClipRect.z - clipOff.x) * clipScale.x; + clip.w = (cmd.ClipRect.w - clipOff.y) * clipScale.y; + + if (clip.x >= width || clip.y >= height || clip.z < 0.0f || clip.w < 0.0f) + continue; + if (clip.x < 0.0f) + clip.x = 0.0f; + if (clip.y < 0.0f) + clip.y = 0.0f; + + if (screen == GFX_TOP) + { + // check if clip starts on bottom screen + if (clip.y > 240.0f) + continue; + + auto const x1 = std::clamp (240.0f - clip.w, 0, 240); + auto const y1 = std::clamp (400.0f - clip.z, 0, 400); + auto const x2 = std::clamp (240.0f - clip.y, 0, 240); + auto const y2 = std::clamp (400.0f - clip.x, 0, 400); + + C3D_SetScissor (GPU_SCISSOR_NORMAL, x1, y1, x2, y2); + } + else + { + // check if clip ends on top screen + if (clip.w < 240.0f) + continue; + + // check if clip ends before left edge of bottom screen + if (clip.z < 40.0f) + continue; + + // check if clip starts after right edge of bottom screen + if (clip.x > 360.0f) + continue; + + auto const x1 = std::clamp (480.0f - clip.w, 0, 240); + auto const y1 = std::clamp (360.0f - clip.z, 0, 320); + auto const x2 = std::clamp (480.0f - clip.y, 0, 240); + auto const y2 = std::clamp (360.0f - clip.x, 0, 320); + + if (s_boundScissor[0] != x1 || s_boundScissor[1] != y1 || + s_boundScissor[2] != x2 || s_boundScissor[3] != y2) + { + s_boundScissor[0] = x1; + s_boundScissor[1] = y1; + s_boundScissor[2] = x2; + s_boundScissor[3] = y2; + C3D_SetScissor (GPU_SCISSOR_NORMAL, x1, y1, x2, y2); + } + } + + auto const vtxData = &s_vtxData[cmd.VtxOffset + offsetVtx]; + if (vtxData != s_boundVtxData) + { + s_boundVtxData = &s_vtxData[cmd.VtxOffset + offsetVtx]; + auto const bufInfo = C3D_GetBufInfo (); + BufInfo_Init (bufInfo); + BufInfo_Add (bufInfo, s_boundVtxData, sizeof (ImDrawVert), 3, 0x210); + } + + auto tex = static_cast (cmd.TextureId); + if (tex == s_fontTextures.data ()) + { + assert (cmd.ElemCount % 3 == 0); + + // TODO get by idx not consecutive vtx + auto const getSheet = [] (auto const vtx_, auto const idx_) { + unsigned const sheet = std::min ( + {vtx_[idx_[0]].uv.y, vtx_[idx_[1]].uv.y, vtx_[idx_[2]].uv.y}); + for (unsigned i = 0; i < 3; ++i) + assert (vtx_[idx_[i]].uv.y - sheet <= 1.0f); + return sheet; + }; + + unsigned boundSheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx], + &s_idxData[cmd.IdxOffset + offsetIdx]); + + unsigned offset = 0; + + C3D_TexBind (0, &s_fontTextures[boundSheet]); + + auto const env = C3D_GetTexEnv (0); + C3D_TexEnvInit (env); + C3D_TexEnvSrc ( + env, C3D_RGB, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc (env, C3D_RGB, GPU_REPLACE); + C3D_TexEnvSrc ( + env, C3D_Alpha, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc (env, C3D_Alpha, GPU_MODULATE); + + for (unsigned i = 3; i < cmd.ElemCount; i += 3) + { + unsigned const sheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx], + &s_idxData[cmd.IdxOffset + offsetIdx + i]); + if (boundSheet != sheet) + { + C3D_DrawElements (GPU_TRIANGLES, + i - offset, + C3D_UNSIGNED_SHORT, + &s_idxData[cmd.IdxOffset + offsetIdx + offset]); + + boundSheet = sheet; + offset = i; + C3D_TexBind (0, &s_fontTextures[boundSheet]); + } + } + + assert ((cmd.ElemCount - offset) % 3 == 0); + C3D_DrawElements (GPU_TRIANGLES, + cmd.ElemCount - offset, + C3D_UNSIGNED_SHORT, + &s_idxData[cmd.IdxOffset + offsetIdx + offset]); + } + else + { + if (tex != s_boundTexture) + { + C3D_TexBind (0, tex); + auto const env = C3D_GetTexEnv (0); + C3D_TexEnvInit (env); + C3D_TexEnvSrc ( + env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc (env, C3D_Both, GPU_MODULATE); + } + + C3D_DrawElements (GPU_TRIANGLES, + cmd.ElemCount, + C3D_UNSIGNED_SHORT, + &s_idxData[cmd.IdxOffset + offsetIdx]); + } + + s_boundTexture = tex; + } + } + + offsetVtx += cmdList.VtxBuffer.Size; + offsetIdx += cmdList.IdxBuffer.Size; + } + } + + C3D_FrameEnd (0); +} diff --git a/source/3ds/imgui_citro3d.h b/source/3ds/imgui_citro3d.h new file mode 100644 index 0000000..a8e53ab --- /dev/null +++ b/source/3ds/imgui_citro3d.h @@ -0,0 +1,33 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +namespace imgui +{ +namespace citro3d +{ +void init (); +void exit (); + +void newFrame (); +void render (); +} +} diff --git a/source/3ds/imgui_ctru.cpp b/source/3ds/imgui_ctru.cpp new file mode 100644 index 0000000..b04d314 --- /dev/null +++ b/source/3ds/imgui_ctru.cpp @@ -0,0 +1,163 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "imgui_ctru.h" + +#include "imgui.h" + +#include "fs.h" + +#include <3ds.h> + +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +namespace +{ +constexpr auto SCREEN_WIDTH = 400.0f; +constexpr auto SCREEN_HEIGHT = 480.0f; + +std::string s_clipboard; + +char const *getClipboardText (void *const userData_) +{ + (void)userData_; + return s_clipboard.c_str (); +} + +void setClipboardText (void *const userData_, char const *const text_) +{ + (void)userData_; + s_clipboard = text_; +} + +void updateTouch (ImGuiIO &io_) +{ + if (!((hidKeysDown () | hidKeysHeld ()) & KEY_TOUCH)) + { + io_.MousePos = ImVec2 (-10.0f, -10.0f); + io_.MouseDown[0] = false; + return; + } + + touchPosition pos; + hidTouchRead (&pos); + + // transform to bottom-screen space + io_.MousePos = ImVec2 ((pos.px + 40.0f) * 2.0f, (pos.py + 240.0f) * 2.0f); + io_.MouseDown[0] = true; +} + +void updateGamepads (ImGuiIO &io_) +{ + std::memset (io_.NavInputs, 0, sizeof (io_.NavInputs)); + + auto const buttonMapping = { + std::make_pair (KEY_A, ImGuiNavInput_Activate), + std::make_pair (KEY_B, ImGuiNavInput_Cancel), + std::make_pair (KEY_X, ImGuiNavInput_Input), + std::make_pair (KEY_Y, ImGuiNavInput_Menu), + std::make_pair (KEY_L, ImGuiNavInput_FocusPrev), + std::make_pair (KEY_L, ImGuiNavInput_TweakSlow), + std::make_pair (KEY_R, ImGuiNavInput_FocusNext), + std::make_pair (KEY_R, ImGuiNavInput_TweakFast), + std::make_pair (KEY_DUP, ImGuiNavInput_DpadUp), + std::make_pair (KEY_DRIGHT, ImGuiNavInput_DpadRight), + std::make_pair (KEY_DDOWN, ImGuiNavInput_DpadDown), + std::make_pair (KEY_DLEFT, ImGuiNavInput_DpadLeft), + }; + + auto const keys = hidKeysHeld (); + for (auto const &[in, out] : buttonMapping) + { + if (keys & in) + io_.NavInputs[out] = 1.0f; + } + + circlePosition cpad; + auto const analogMapping = { + std::make_tuple (std::ref (cpad.dx), ImGuiNavInput_LStickLeft, -0.3f, -0.9f), + std::make_tuple (std::ref (cpad.dx), ImGuiNavInput_LStickRight, +0.3f, +0.9f), + std::make_tuple (std::ref (cpad.dy), ImGuiNavInput_LStickUp, +0.3f, +0.9f), + std::make_tuple (std::ref (cpad.dy), ImGuiNavInput_LStickDown, -0.3f, -0.9f), + }; + + hidCircleRead (&cpad); + for (auto const &[in, out, min, max] : analogMapping) + { + auto const value = in / static_cast (0x9C); + auto const v = std::min (1.0f, (value - min) / (max - min)); + io_.NavInputs[out] = std::max (io_.NavInputs[out], v); + } +} +} + +bool imgui::ctru::init () +{ + ImGuiIO &io = ImGui::GetIO (); + + io.IniFilename = nullptr; + + io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + + io.BackendPlatformName = "3ds"; + + io.MouseDrawCursor = false; + + io.SetClipboardTextFn = setClipboardText; + io.GetClipboardTextFn = getClipboardText; + io.ClipboardUserData = nullptr; + + return true; +} + +void imgui::ctru::newFrame () +{ + ImGuiIO &io = ImGui::GetIO (); + + IM_ASSERT (io.Fonts->IsBuilt () && + "Font atlas not built! It is generally built by the renderer back-end. Missing call " + "to renderer _NewFrame() function?"); + + io.DisplaySize = ImVec2 (SCREEN_WIDTH * 2.0f, SCREEN_HEIGHT * 2.0f); + io.DisplayFramebufferScale = ImVec2 (0.5f, 0.5f); + + // Setup time step + static auto const start = svcGetSystemTick (); + static auto prev = start; + auto const now = svcGetSystemTick (); + + io.DeltaTime = (now - prev) / static_cast (SYSCLOCK_ARM11); + prev = now; + + updateTouch (io); + updateGamepads (io); +} + +void imgui::ctru::exit () +{ +} diff --git a/source/3ds/imgui_ctru.h b/source/3ds/imgui_ctru.h new file mode 100644 index 0000000..2684b72 --- /dev/null +++ b/source/3ds/imgui_ctru.h @@ -0,0 +1,32 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +namespace imgui +{ +namespace ctru +{ +bool init (); +void exit (); + +void newFrame (); +} +} diff --git a/source/3ds/platform.cpp b/source/3ds/platform.cpp new file mode 100644 index 0000000..c1e4f5d --- /dev/null +++ b/source/3ds/platform.cpp @@ -0,0 +1,348 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "platform.h" + +#include "fs.h" +#include "log.h" + +#include "imgui_citro3d.h" +#include "imgui_ctru.h" + +#include "imgui.h" + +#include <3ds.h> +#include +#include + +#include "gfx.h" + +#include +#include +#include +#include + +namespace +{ +constexpr auto STACK_SIZE = 32768; +constexpr auto SOCU_ALIGN = 0x1000; +constexpr auto SOCU_BUFFERSIZE = 0x100000; + +static_assert (SOCU_BUFFERSIZE % SOCU_ALIGN == 0); + +bool s_socuActive = false; +u32 *s_socuBuffer = nullptr; + +C3D_Tex s_gfxTexture; +Tex3DS_Texture s_gfxT3x; + +void startNetwork () +{ + if (s_socuActive) + return; + + std::uint32_t wifi = 0; + if (R_FAILED (ACU_GetWifiStatus (&wifi)) || !wifi) + return; + + if (!s_socuBuffer) + s_socuBuffer = static_cast (::memalign (SOCU_ALIGN, SOCU_BUFFERSIZE)); + + if (!s_socuBuffer) + return; + + if (R_FAILED (socInit (s_socuBuffer, SOCU_BUFFERSIZE))) + return; + + s_socuActive = true; + Log::info ("Wifi connected\n"); +} + +void drawLogo () +{ + auto subTex = Tex3DS_GetSubTexture (s_gfxT3x, gfx_c3dlogo_idx); + + ImGuiIO &io = ImGui::GetIO (); + auto const screenWidth = io.DisplaySize.x; + auto const screenHeight = io.DisplaySize.y; + auto const logoWidth = subTex->width / io.DisplayFramebufferScale.x; + auto const logoHeight = subTex->height / io.DisplayFramebufferScale.y; + + auto const x1 = (screenWidth - logoWidth) / 2.0f; + auto const x2 = x1 + logoWidth; + auto const y1 = (screenHeight / 2.0f - logoHeight) / 2.0f; + auto const y2 = y1 + logoHeight; + + auto const uv1 = ImVec2 (subTex->left, subTex->top); + auto const uv2 = ImVec2 (subTex->right, subTex->bottom); + + ImGui::GetBackgroundDrawList ()->AddImage ( + &s_gfxTexture, ImVec2 (x1, y1), ImVec2 (x2, y2), uv1, uv2); + + ImGui::GetBackgroundDrawList ()->AddImage (&s_gfxTexture, + ImVec2 (x1, y1 + screenHeight / 2.0f), + ImVec2 (x2, y2 + screenHeight / 2.0f), + uv1, + uv2); +} + +void drawStatus () +{ + constexpr unsigned batteryLevels[] = { + gfx_battery0_idx, + gfx_battery0_idx, + gfx_battery1_idx, + gfx_battery2_idx, + gfx_battery3_idx, + gfx_battery4_idx, + }; + + constexpr unsigned wifiLevels[] = { + gfx_wifiNull_idx, + gfx_wifi1_idx, + gfx_wifi2_idx, + gfx_wifi3_idx, + }; + + static u8 charging = 0; + static u8 level = 5; + PTMU_GetBatteryChargeState (&charging); + if (!charging) + { + PTMU_GetBatteryLevel (&level); + if (level >= std::extent_v) + svcBreak (USERBREAK_PANIC); + } + + auto const &io = ImGui::GetIO (); + auto const &style = ImGui::GetStyle (); + + auto const screenWidth = io.DisplaySize.x; + + auto const battery = + Tex3DS_GetSubTexture (s_gfxT3x, charging ? gfx_batteryCharge_idx : batteryLevels[level]); + auto const batteryWidth = battery->width / io.DisplayFramebufferScale.x; + auto const batteryHeight = battery->height / io.DisplayFramebufferScale.y; + + auto const p1 = ImVec2 (screenWidth - batteryWidth, 0.0f); + auto const p2 = ImVec2 (screenWidth, batteryHeight); + + auto const uv1 = ImVec2 (battery->left, battery->top); + auto const uv2 = ImVec2 (battery->right, battery->bottom); + + ImGui::GetForegroundDrawList ()->AddImage (&s_gfxTexture, p1, p2, uv1, uv2); + + auto const wifiStrength = osGetWifiStrength (); + + auto const wifi = Tex3DS_GetSubTexture (s_gfxT3x, wifiLevels[wifiStrength]); + auto const wifiWidth = wifi->width / io.DisplayFramebufferScale.x; + auto const wifiHeight = wifi->height / io.DisplayFramebufferScale.y; + + auto const p3 = ImVec2 (p1.x - wifiWidth - 4.0f, 0.0f); + auto const p4 = ImVec2 (p1.x - 4.0f, wifiHeight); + + auto const uv3 = ImVec2 (wifi->left, wifi->top); + auto const uv4 = ImVec2 (wifi->right, wifi->bottom); + + ImGui::GetForegroundDrawList ()->AddImage (&s_gfxTexture, p3, p4, uv3, uv4); + + char buffer[64]; + auto const now = std::time (nullptr); + std::strftime (buffer, sizeof (buffer), "%H:%M:%S", std::localtime (&now)); + ImGui::GetForegroundDrawList ()->AddText ( + ImVec2 (p3.x - 130.0f, style.FramePadding.y), 0xFFFFFFFF, buffer); +} +} + +bool platform::init () +{ + osSetSpeedupEnable (true); + + acInit (); + ptmuInit (); + romfsInit (); + gfxInitDefault (); + gfxSet3D (false); + sdmcWriteSafe (false); + +#ifndef NDEBUG + consoleDebugInit (debugDevice_SVC); + std::setvbuf (stderr, nullptr, _IOLBF, 0); +#endif + + IMGUI_CHECKVERSION (); + ImGui::CreateContext (); + + if (!imgui::ctru::init ()) + { + ImGui::DestroyContext (); + return false; + } + + imgui::citro3d::init (); + + { + fs::File file; + if (!file.open ("romfs:/gfx.t3x")) + svcBreak (USERBREAK_PANIC); + + s_gfxT3x = Tex3DS_TextureImportStdio (file, &s_gfxTexture, nullptr, true); + if (!s_gfxT3x) + svcBreak (USERBREAK_PANIC); + } + + C3D_TexSetFilter (&s_gfxTexture, GPU_LINEAR, GPU_LINEAR); + + return true; +} + +bool platform::loop () +{ + if (!aptMainLoop ()) + return false; + + startNetwork (); + + hidScanInput (); + + if (hidKeysDown () & KEY_START) + return false; + + imgui::ctru::newFrame (); + imgui::citro3d::newFrame (); + ImGui::NewFrame (); + + return true; +} + +void platform::render () +{ + drawLogo (); + drawStatus (); + + ImGui::Render (); + + imgui::citro3d::render (); +} + +void platform::exit () +{ + Tex3DS_TextureFree (s_gfxT3x); + C3D_TexDelete (&s_gfxTexture); + + ImGui::DestroyContext (); + + imgui::citro3d::exit (); + imgui::ctru::exit (); + + socExit (); + s_socuActive = false; + std::free (s_socuBuffer); + + gfxExit (); + ptmuExit (); + acExit (); +} + +/////////////////////////////////////////////////////////////////////////// +class platform::Thread::privateData_t +{ +public: + privateData_t () + { + if (thread) + threadFree (thread); + } + + privateData_t (std::function func_) : thread (nullptr) + { + s32 priority = 0x30; + svcGetThreadPriority (&priority, CUR_THREAD_HANDLE); + + thread = threadCreate (&privateData_t::threadFunc, this, STACK_SIZE, priority, -1, false); + assert (thread); + } + + static void threadFunc (void *const arg_) + { + auto const t = static_cast (arg_); + t->func (); + } + + ::Thread thread = nullptr; + std::function func; +}; + +/////////////////////////////////////////////////////////////////////////// +platform::Thread::~Thread () = default; + +platform::Thread::Thread () : m_d (new privateData_t ()) +{ +} + +platform::Thread::Thread (std::function func_) : m_d (new privateData_t (func_)) +{ +} + +platform::Thread::Thread (Thread &&that_) : m_d (new privateData_t ()) +{ + std::swap (m_d, that_.m_d); +} + +platform::Thread &platform::Thread::operator= (Thread &&that_) +{ + std::swap (m_d, that_.m_d); + return *this; +} + +void platform::Thread::join () +{ + threadJoin (m_d->thread, UINT64_MAX); +} + +void platform::Thread::sleep (std::chrono::milliseconds const timeout_) +{ + svcSleepThread (std::chrono::nanoseconds (timeout_).count ()); +} + +/////////////////////////////////////////////////////////////////////////// +class platform::Mutex::privateData_t +{ +public: + LightLock mutex; +}; + +/////////////////////////////////////////////////////////////////////////// +platform::Mutex::~Mutex () = default; + +platform::Mutex::Mutex () : m_d (new privateData_t ()) +{ + LightLock_Init (&m_d->mutex); +} + +void platform::Mutex::lock () +{ + LightLock_Lock (&m_d->mutex); +} + +void platform::Mutex::unlock () +{ + LightLock_Unlock (&m_d->mutex); +} diff --git a/source/3ds/vshader.v.pica b/source/3ds/vshader.v.pica new file mode 100644 index 0000000..5b9955e --- /dev/null +++ b/source/3ds/vshader.v.pica @@ -0,0 +1,61 @@ +; ftpd is a server implementation based on the following: +; - RFC 959 (https://tools.ietf.org/html/rfc959) +; - RFC 3659 (https://tools.ietf.org/html/rfc3659) +; - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +; +; Copyright (C) 2020 Michael Theall +; +; 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 3 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, see . + +; Example PICA200 vertex shader + +; Uniforms +.fvec proj[4] + +; Constants +.constf constants(1.0, 0.0, 0.00392156862745, 0.0) + +; Outputs +.out outPos position +.out outUv texcoord0 +.out outColor color + +; Inputs (defined as aliases for convenience) +.alias inPos v0 +.alias inUv v1 +.alias inColor v2 + +.proc main + ; Force inPos.z = 0.0, inPos.w = 1.0 + mov r0.xy, inPos.xy + mov r0.zw, constants.yx + + ; outPos = proj * inPos + dp4 outPos.x, proj[0], r0 + dp4 outPos.y, proj[1], r0 + dp4 outPos.z, proj[2], r0 + dp4 outPos.w, proj[3], r0 + + ; outUv = inUv + mov outUv, inUv + + ; normalize inColor + mul r1, constants.zzzz, inColor + + ; outColor = inColor + mov outColor, r1 + + ; We're finished + end +.end diff --git a/source/console.c b/source/console.c deleted file mode 100644 index 478beb1..0000000 --- a/source/console.c +++ /dev/null @@ -1,276 +0,0 @@ -#include "console.h" -#include -#include -#include -#include -#include -#include - -#ifdef _3DS -#include <3ds.h> -#define CONSOLE_WIDTH 50 -#define CONSOLE_HEIGHT 30 -#elif defined(__SWITCH__) -#include -#define CONSOLE_WIDTH 80 -#define CONSOLE_HEIGHT 45 -#endif - -#if defined(_3DS) || defined (__SWITCH__) -static PrintConsole status_console; -static PrintConsole main_console; -#endif - -#if ENABLE_LOGGING -static bool disable_logging = false; -#endif - -#if defined(_3DS) -static PrintConsole tcp_console; - -/*! initialize console subsystem */ -void -console_init(void) -{ - consoleInit(GFX_TOP, &status_console); - consoleSetWindow(&status_console, 0, 0, 50, 1); - - consoleInit(GFX_TOP, &main_console); - consoleSetWindow(&main_console, 0, 1, 50, 29); - - consoleInit(GFX_BOTTOM, &tcp_console); - - consoleSelect(&main_console); -} - - -/*! print tcp tables */ -static void -print_tcp_table(void) -{ - static SOCU_TCPTableEntry tcp_entries[32]; - socklen_t optlen; - size_t i; - int rc, lines = 0; - -#ifdef ENABLE_LOGGING - disable_logging = true; -#endif - - consoleSelect(&tcp_console); - console_print("\x1b[0;0H\x1b[K"); - optlen = sizeof(tcp_entries); - rc = SOCU_GetNetworkOpt(SOL_CONFIG, NETOPT_TCP_TABLE, tcp_entries, &optlen); - if(rc != 0 && errno != ENODEV) - console_print(RED "tcp table: %d %s\n\x1b[J\n" RESET, errno, strerror(errno)); - else if(rc == 0) - { - for(i = 0; lines < 30 && i < optlen / sizeof(SOCU_TCPTableEntry); ++i) - { - SOCU_TCPTableEntry *entry = &tcp_entries[i]; - struct sockaddr_in *local = (struct sockaddr_in*)&entry->local; - struct sockaddr_in *remote = (struct sockaddr_in*)&entry->remote; - - console_print(GREEN "%stcp[%zu]: ", i == 0 ? "" : "\n", i); - switch(entry->state) - { - case TCP_STATE_CLOSED: - console_print("CLOSED\x1b[K"); - local = remote = NULL; - break; - - case TCP_STATE_LISTEN: - console_print("LISTEN\x1b[K"); - remote = NULL; - break; - - case TCP_STATE_ESTABLISHED: - console_print("ESTABLISHED\x1b[K"); - break; - - case TCP_STATE_FINWAIT1: - console_print("FINWAIT1\x1b[K"); - break; - - case TCP_STATE_FINWAIT2: - console_print("FINWAIT2\x1b[K"); - break; - - case TCP_STATE_CLOSE_WAIT: - console_print("CLOSE_WAIT\x1b[K"); - break; - - case TCP_STATE_LAST_ACK: - console_print("LAST_ACK\x1b[K"); - break; - - case TCP_STATE_TIME_WAIT: - console_print("TIME_WAIT\x1b[K"); - break; - - default: - console_print("State %lu\x1b[K", entry->state); - break; - } - - ++lines; - - if(local && (lines++ < 30)) - console_print("\n Local %s:%u\x1b[K", inet_ntoa(local->sin_addr), - ntohs(local->sin_port)); - - if(remote && (lines++ < 30)) - console_print("\n Peer %s:%u\x1b[K", inet_ntoa(remote->sin_addr), - ntohs(remote->sin_port)); - } - - console_print(RESET "\x1b[J"); - } - else - console_print("\x1b[2J"); - - consoleSelect(&main_console); - -#ifdef ENABLE_LOGGING - disable_logging = false; -#endif -} - -#elif defined(__SWITCH__) -/*! initialize console subsystem */ -void -console_init(void) -{ - consoleInit(&status_console); - consoleSetWindow(&status_console, 0, 0, CONSOLE_WIDTH, 1); - - consoleInit( &main_console); - consoleSetWindow(&main_console, 0, 1, CONSOLE_WIDTH, CONSOLE_HEIGHT-1); - - consoleSelect(&main_console); -} -#endif - -#if defined(_3DS) || defined(__SWITCH__) - - -/*! set status bar contents - * - * @param[in] fmt format string - * @param[in] ... format arguments - */ -void -console_set_status(const char *fmt, ...) -{ - va_list ap; - - consoleSelect(&status_console); - va_start(ap, fmt); - vprintf(fmt, ap); -#ifdef ENABLE_LOGGING - vfprintf(stderr, fmt, ap); -#endif - va_end(ap); - consoleSelect(&main_console); -} - -/*! add text to the console - * - * @param[in] fmt format string - * @param[in] ... format arguments - */ -void -console_print(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vprintf(fmt, ap); -#ifdef ENABLE_LOGGING - if(!disable_logging) - vfprintf(stderr, fmt, ap); -#endif - va_end(ap); -} - -/*! print debug message - * - * @param[in] fmt format string - * @param[in] ... format arguments - */ -void -debug_print(const char *fmt, ...) -{ -#ifdef ENABLE_LOGGING - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); -#endif -} - -/*! draw console to screen */ -void -console_render(void) -{ - /* print tcp table */ -#ifdef _3DS - print_tcp_table(); -#endif - /* flush framebuffer */ -#ifdef _3DS - gfxFlushBuffers(); - gspWaitForVBlank(); - gfxSwapBuffers(); -#endif -#ifdef __SWITCH__ - consoleUpdate(NULL); -#endif -} - - -#else - -/* this is a lot easier when you have a real console */ - -void -console_init(void) -{ -} - -void -console_set_status(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - fputc('\n', stdout); -} - -void -console_print(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); -} - -void -debug_print(const char *fmt, ...) -{ -#ifdef ENABLE_LOGGING - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); -#endif -} - -void console_render(void) -{ -} -#endif - diff --git a/source/fs.cpp b/source/fs.cpp new file mode 100644 index 0000000..43c4c61 --- /dev/null +++ b/source/fs.cpp @@ -0,0 +1,211 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "fs.h" + +#include +#include + +std::string fs::printSize (std::uint64_t const size_) +{ + constexpr std::uint64_t const KiB = 1024; + constexpr std::uint64_t const MiB = 1024 * KiB; + constexpr std::uint64_t const GiB = 1024 * MiB; + constexpr std::uint64_t const TiB = 1024 * GiB; + constexpr std::uint64_t const PiB = 1024 * TiB; + constexpr std::uint64_t const EiB = 1024 * PiB; + + char buffer[64] = {}; + + for (auto const &[name, bin] : { + // clang-format off + std::make_pair ("EiB", EiB), + std::make_pair ("PiB", PiB), + std::make_pair ("TiB", TiB), + std::make_pair ("GiB", GiB), + std::make_pair ("MiB", MiB), + std::make_pair ("KiB", KiB) + // clang-format on + }) + { + auto const whole = size_ / bin; + if (size_ >= 100 * bin) + { + std::sprintf (buffer, "%" PRIu64 "%s", whole, name); + return buffer; + } + + auto const frac = size_ - whole * bin; + if (size_ >= 10 * bin) + { + std::sprintf (buffer, "%" PRIu64 ".%" PRIu64 "%s", whole, frac * 10 / bin, name); + return buffer; + } + + if (size_ >= 1000 * (bin / KiB)) + { + std::sprintf (buffer, "%" PRIu64 ".%02" PRIu64 "%s", whole, frac * 100 / bin, name); + return buffer; + } + } + + std::sprintf (buffer, "%" PRIu64, size_); + return buffer; +} + +/////////////////////////////////////////////////////////////////////////// +fs::File::~File () = default; + +fs::File::File () = default; + +fs::File::File (File &&that_) = default; + +fs::File &fs::File::operator= (File &&that_) = default; + +fs::File::operator bool () const +{ + return static_cast (m_fp); +} + +fs::File::operator FILE * () const +{ + return m_fp.get (); +} + +void fs::File::setBufferSize (std::size_t const size_) +{ + if (m_bufferSize != size_) + { + m_buffer = std::make_unique (size_); + m_bufferSize = size_; + } + + if (m_fp) + std::setvbuf (m_fp.get (), m_buffer.get (), _IOFBF, m_bufferSize); +} + +bool fs::File::open (char const *const path_, char const *const mode_) +{ + auto const fp = std::fopen (path_, mode_); + if (!fp) + return false; + + m_fp = std::unique_ptr (fp, &std::fclose); + + if (m_buffer) + std::setvbuf (m_fp.get (), m_buffer.get (), _IOFBF, m_bufferSize); + + return true; +} + +void fs::File::close () +{ + m_fp.reset (); +} + +ssize_t fs::File::seek (std::size_t const pos_, int const origin_) +{ + return std::fseek (m_fp.get (), pos_, origin_); +} + +ssize_t fs::File::read (void *const data_, std::size_t const size_) +{ + return std::fread (data_, 1, size_, m_fp.get ()); +} + +bool fs::File::readAll (void *const data_, std::size_t const size_) +{ + auto p = static_cast (data_); + std::size_t bytes = 0; + + while (bytes < size_) + { + auto const rc = read (p, size_ - bytes); + if (rc <= 0) + return false; + + p += rc; + bytes += rc; + } + + return true; +} + +ssize_t fs::File::write (void const *const data_, std::size_t const size_) +{ + return std::fwrite (data_, 1, size_, m_fp.get ()); +} + +bool fs::File::writeAll (void const *const data_, std::size_t const size_) +{ + auto p = static_cast (data_); + std::size_t bytes = 0; + + while (bytes < size_) + { + auto const rc = write (p, size_ - bytes); + if (rc <= 0) + return false; + + p += rc; + bytes += rc; + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////// +fs::Dir::~Dir () = default; + +fs::Dir::Dir () = default; + +fs::Dir::Dir (Dir &&that_) = default; + +fs::Dir &fs::Dir::operator= (Dir &&that_) = default; + +fs::Dir::operator bool () const +{ + return static_cast (m_dp); +} + +fs::Dir::operator DIR * () const +{ + return m_dp.get (); +} + +bool fs::Dir::open (char const *const path_) +{ + auto const dp = ::opendir (path_); + if (!dp) + return false; + + m_dp = std::unique_ptr (dp, &::closedir); + return true; +} + +void fs::Dir::close () +{ + m_dp.reset (); +} + +struct dirent *fs::Dir::read () +{ + return ::readdir (m_dp.get ()); +} diff --git a/source/ftp.c b/source/ftp.c deleted file mode 100644 index 4778095..0000000 --- a/source/ftp.c +++ /dev/null @@ -1,4114 +0,0 @@ -/* This FTP server implementation is based on RFC 959, - * (https://tools.ietf.org/html/rfc959), RFC 3659 - * (https://tools.ietf.org/html/rfc3659) and suggested implementation details - * from https://cr.yp.to/ftp/filesystem.html - */ -#include "ftp.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef _3DS -#include <3ds.h> -#define lstat stat -#elif defined(__SWITCH__) -#include -#define lstat stat -#else -#include -#define BIT(x) (1<<(x)) -#endif -#include "console.h" - -#define POLL_UNKNOWN (~(POLLIN|POLLPRI|POLLOUT)) - -#ifndef __SWITCH__ -#define XFER_BUFFERSIZE 32768 -#define SOCK_BUFFERSIZE 32768 -#define FILE_BUFFERSIZE 65536 -#define CMD_BUFFERSIZE 4096 -#else -/* we have a lot of memory to waste on the Switch */ -#define XFER_BUFFERSIZE 65536 -#define SOCK_BUFFERSIZE 65536 -#define FILE_BUFFERSIZE 1048576 -#define CMD_BUFFERSIZE 4096 -#endif - -#ifdef _3DS -#define SOCU_ALIGN 0x1000 -#define SOCU_BUFFERSIZE 0x100000 -#endif -#define LISTEN_PORT 5000 -#ifdef _3DS -#define DATA_PORT (LISTEN_PORT+1) -#else -#define DATA_PORT 0 /* ephemeral port */ -#endif - -typedef struct ftp_session_t ftp_session_t; - -#define FTP_DECLARE(x) static void x(ftp_session_t *session, const char *args) -FTP_DECLARE(ABOR); -FTP_DECLARE(ALLO); -FTP_DECLARE(APPE); -FTP_DECLARE(CDUP); -FTP_DECLARE(CWD); -FTP_DECLARE(DELE); -FTP_DECLARE(FEAT); -FTP_DECLARE(HELP); -FTP_DECLARE(LIST); -FTP_DECLARE(MDTM); -FTP_DECLARE(MKD); -FTP_DECLARE(MLSD); -FTP_DECLARE(MLST); -FTP_DECLARE(MODE); -FTP_DECLARE(NLST); -FTP_DECLARE(NOOP); -FTP_DECLARE(OPTS); -FTP_DECLARE(PASS); -FTP_DECLARE(PASV); -FTP_DECLARE(PORT); -FTP_DECLARE(PWD); -FTP_DECLARE(QUIT); -FTP_DECLARE(REST); -FTP_DECLARE(RETR); -FTP_DECLARE(RMD); -FTP_DECLARE(RNFR); -FTP_DECLARE(RNTO); -FTP_DECLARE(SIZE); -FTP_DECLARE(STAT); -FTP_DECLARE(STOR); -FTP_DECLARE(STOU); -FTP_DECLARE(STRU); -FTP_DECLARE(SYST); -FTP_DECLARE(TYPE); -FTP_DECLARE(USER); - -/*! session state */ -typedef enum -{ - COMMAND_STATE, /*!< waiting for a command */ - DATA_CONNECT_STATE, /*!< waiting for connection after PASV command */ - DATA_TRANSFER_STATE, /*!< data transfer in progress */ -} session_state_t; - -/*! ftp_session_set_state flags */ -typedef enum -{ - CLOSE_PASV = BIT(0), /*!< Close the pasv_fd */ - CLOSE_DATA = BIT(1), /*!< Close the data_fd */ -} set_state_flags_t; - -/*! ftp_session_t flags */ -typedef enum -{ - SESSION_BINARY = BIT(0), /*!< data transfers in binary mode */ - SESSION_PASV = BIT(1), /*!< have pasv_addr ready for data transfer command */ - SESSION_PORT = BIT(2), /*!< have peer_addr ready for data transfer command */ - SESSION_RECV = BIT(3), /*!< data transfer in source mode */ - SESSION_SEND = BIT(4), /*!< data transfer in sink mode */ - SESSION_RENAME = BIT(5), /*!< last command was RNFR and buffer contains path */ - SESSION_URGENT = BIT(6), /*!< in telnet urgent mode */ -} session_flags_t; - -/*! ftp_xfer_dir mode */ -typedef enum -{ - XFER_DIR_LIST, /*!< Long list */ - XFER_DIR_MLSD, /*!< Machine list directory */ - XFER_DIR_MLST, /*!< Machine list */ - XFER_DIR_NLST, /*!< Short list */ - XFER_DIR_STAT, /*!< Stat command */ -} xfer_dir_mode_t; - -typedef enum -{ - SESSION_MLST_TYPE = BIT(0), - SESSION_MLST_SIZE = BIT(1), - SESSION_MLST_MODIFY = BIT(2), - SESSION_MLST_PERM = BIT(3), - SESSION_MLST_UNIX_MODE = BIT(4), -} session_mlst_flags_t; - -/*! ftp session */ -struct ftp_session_t -{ - char cwd[4096]; /*!< current working directory */ - char lwd[4096]; /*!< list working directory */ - struct sockaddr_in peer_addr; /*!< peer address for data connection */ - struct sockaddr_in pasv_addr; /*!< listen address for PASV connection */ - int cmd_fd; /*!< socket for command connection */ - int pasv_fd; /*!< listen socket for PASV */ - int data_fd; /*!< socket for data transfer */ - time_t timestamp; /*!< time from last command */ - session_flags_t flags; /*!< session flags */ - xfer_dir_mode_t dir_mode; /*!< dir transfer mode */ - session_mlst_flags_t mlst_flags; /*!< session MLST flags */ - session_state_t state; /*!< session state */ - ftp_session_t *next; /*!< link to next session */ - ftp_session_t *prev; /*!< link to prev session */ - - loop_status_t (*transfer)(ftp_session_t*); /*! data transfer callback */ - char buffer[XFER_BUFFERSIZE]; /*! persistent data between callbacks */ - char file_buffer[FILE_BUFFERSIZE]; /*! stdio file buffer */ - char cmd_buffer[CMD_BUFFERSIZE]; /*! command buffer */ - size_t bufferpos; /*! persistent buffer position between callbacks */ - size_t buffersize; /*! persistent buffer size between callbacks */ - size_t cmd_buffersize; - uint64_t filepos; /*! persistent file position between callbacks */ - uint64_t filesize; /*! persistent file size between callbacks */ - FILE *fp; /*! persistent open file pointer between callbacks */ - DIR *dp; /*! persistent open directory pointer between callbacks */ -}; - -/*! ftp command descriptor */ -typedef struct ftp_command -{ - const char *name; /*!< command name */ - void (*handler)(ftp_session_t*, const char*); /*!< command callback */ -} ftp_command_t; - -/*! ftp command list */ -static ftp_command_t ftp_commands[] = -{ -/*! ftp command */ -#define FTP_COMMAND(x) { #x, x, } -/*! ftp alias */ -#define FTP_ALIAS(x,y) { #x, y, } - FTP_COMMAND(ABOR), - FTP_COMMAND(ALLO), - FTP_COMMAND(APPE), - FTP_COMMAND(CDUP), - FTP_COMMAND(CWD), - FTP_COMMAND(DELE), - FTP_COMMAND(FEAT), - FTP_COMMAND(HELP), - FTP_COMMAND(LIST), - FTP_COMMAND(MDTM), - FTP_COMMAND(MKD), - FTP_COMMAND(MLSD), - FTP_COMMAND(MLST), - FTP_COMMAND(MODE), - FTP_COMMAND(NLST), - FTP_COMMAND(NOOP), - FTP_COMMAND(OPTS), - FTP_COMMAND(PASS), - FTP_COMMAND(PASV), - FTP_COMMAND(PORT), - FTP_COMMAND(PWD), - FTP_COMMAND(QUIT), - FTP_COMMAND(REST), - FTP_COMMAND(RETR), - FTP_COMMAND(RMD), - FTP_COMMAND(RNFR), - FTP_COMMAND(RNTO), - FTP_COMMAND(SIZE), - FTP_COMMAND(STAT), - FTP_COMMAND(STOR), - FTP_COMMAND(STOU), - FTP_COMMAND(STRU), - FTP_COMMAND(SYST), - FTP_COMMAND(TYPE), - FTP_COMMAND(USER), - FTP_ALIAS(XCUP, CDUP), - FTP_ALIAS(XCWD, CWD), - FTP_ALIAS(XMKD, MKD), - FTP_ALIAS(XPWD, PWD), - FTP_ALIAS(XRMD, RMD), -}; -/*! number of ftp commands */ -static const size_t num_ftp_commands = sizeof(ftp_commands)/sizeof(ftp_commands[0]); - -static void update_free_space(void); - -/*! compare ftp command descriptors - * - * @param[in] p1 left side of comparison (ftp_command_t*) - * @param[in] p2 right side of comparison (ftp_command_t*) - * - * @returns <0 if p1 < p2 - * @returns 0 if p1 == p2 - * @returns >0 if p1 > p2 - */ -static int -ftp_command_cmp(const void *p1, - const void *p2) -{ - ftp_command_t *c1 = (ftp_command_t*)p1; - ftp_command_t *c2 = (ftp_command_t*)p2; - - /* ordered by command name */ - return strcasecmp(c1->name, c2->name); -} - -#ifdef _3DS -/*! SOC service buffer */ -static u32 *SOCU_buffer = NULL; - -/*! Whether LCD is powered */ -static bool lcd_power = true; - -/*! aptHook cookie */ -static aptHookCookie cookie; -#elif defined(__SWITCH__) - -/*! appletHook cookie */ -static AppletHookCookie cookie; -#endif - -/*! server listen address */ -static struct sockaddr_in serv_addr; -/*! listen file descriptor */ -static int listenfd = -1; -#ifdef _3DS -/*! current data port */ -static in_port_t data_port = DATA_PORT; -#endif -/*! list of ftp sessions */ -static ftp_session_t *sessions = NULL; -/*! socket buffersize */ -static int sock_buffersize = SOCK_BUFFERSIZE; -/*! server start time */ -static time_t start_time = 0; - -/*! Allocate a new data port - * - * @returns next data port - */ -static in_port_t -next_data_port(void) -{ -#ifdef _3DS - if(++data_port >= 10000) - data_port = DATA_PORT; - return data_port; -#else - return 0; /* ephemeral port */ -#endif -} - -/*! set a socket to non-blocking - * - * @param[in] fd socket - * - * @returns error - */ -static int -ftp_set_socket_nonblocking(int fd) -{ - int rc, flags; - - /* get the socket flags */ - flags = fcntl(fd, F_GETFL, 0); - if(flags == -1) - { - console_print(RED "fcntl: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* add O_NONBLOCK to the socket flags */ - rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK); - if(rc != 0) - { - console_print(RED "fcntl: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - return 0; -} - -/*! set socket options - * - * @param[in] fd socket - * - * @returns failure - */ -static int -ftp_set_socket_options(int fd) -{ - int rc; - - /* increase receive buffer size */ - rc = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, - &sock_buffersize, sizeof(sock_buffersize)); - if(rc != 0) - { - console_print(RED "setsockopt: SO_RCVBUF %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* increase send buffer size */ - rc = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, - &sock_buffersize, sizeof(sock_buffersize)); - if(rc != 0) - { - console_print(RED "setsockopt: SO_SNDBUF %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - return 0; -} - -/*! close a socket - * - * @param[in] fd socket to close - * @param[in] connected whether this socket is connected - */ -static void -ftp_closesocket(int fd, - bool connected) -{ - int rc; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - struct pollfd pollinfo; - -// console_print("0x%X\n", socketGetLastBsdResult()); - - if(connected) - { - /* get peer address and print */ - rc = getpeername(fd, (struct sockaddr*)&addr, &addrlen); - if(rc != 0) - { - console_print(RED "getpeername: %d %s\n" RESET, errno, strerror(errno)); - console_print(YELLOW "closing connection to fd=%d\n" RESET, fd); - } - else - console_print(YELLOW "closing connection to %s:%u\n" RESET, - inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - - /* shutdown connection */ - rc = shutdown(fd, SHUT_WR); - if(rc != 0) - console_print(RED "shutdown: %d %s\n" RESET, errno, strerror(errno)); - - /* wait for client to close connection */ - pollinfo.fd = fd; - pollinfo.events = POLLIN; - pollinfo.revents = 0; - rc = poll(&pollinfo, 1, 250); - if(rc < 0) - console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); - } - - /* set linger to 0 */ - struct linger linger; - linger.l_onoff = 1; - linger.l_linger = 0; - rc = setsockopt(fd, SOL_SOCKET, SO_LINGER, - &linger, sizeof(linger)); - if(rc != 0) - console_print(RED "setsockopt: SO_LINGER %d %s\n" RESET, - errno, strerror(errno)); - - /* close socket */ - rc = close(fd); - if(rc != 0) - console_print(RED "close: %d %s\n" RESET, errno, strerror(errno)); -} - -/*! close command socket on ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_cmd(ftp_session_t *session) -{ - /* close command socket */ - if(session->cmd_fd >= 0) - ftp_closesocket(session->cmd_fd, true); - session->cmd_fd = -1; -} - -/*! close listen socket on ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_pasv(ftp_session_t *session) -{ - /* close pasv socket */ - if(session->pasv_fd >= 0) - { - console_print(YELLOW "stop listening on %s:%u\n" RESET, - inet_ntoa(session->pasv_addr.sin_addr), - ntohs(session->pasv_addr.sin_port)); - - ftp_closesocket(session->pasv_fd, false); - } - - session->pasv_fd = -1; -} - -/*! close data socket on ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_data(ftp_session_t *session) -{ - /* close data connection */ - if(session->data_fd >= 0 && session->data_fd != session->cmd_fd) - ftp_closesocket(session->data_fd, true); - session->data_fd = -1; - - /* clear send/recv flags */ - session->flags &= ~(SESSION_RECV|SESSION_SEND); -} - -/*! close open file for ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_file(ftp_session_t *session) -{ - int rc; - - if(session->fp != NULL) - { - rc = fclose(session->fp); - if(rc != 0) - console_print(RED "fclose: %d %s\n" RESET, errno, strerror(errno)); - } - - session->fp = NULL; - session->filepos = 0; -} - -/*! open file for reading for ftp session - * - * @param[in] session ftp session - * - * @returns -1 for error - */ -static int -ftp_session_open_file_read(ftp_session_t *session) -{ - int rc; - struct stat st; - - /* open file in read mode */ - session->fp = fopen(session->buffer, "rb"); - if(session->fp == NULL) - { - console_print(RED "fopen '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } - - /* it's okay if this fails */ - errno = 0; - rc = setvbuf(session->fp, session->file_buffer, _IOFBF, FILE_BUFFERSIZE); - if(rc != 0) - { - console_print(RED "setvbuf: %d %s\n" RESET, errno, strerror(errno)); - } - - /* get the file size */ - rc = fstat(fileno(session->fp), &st); - if(rc != 0) - { - console_print(RED "fstat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } - session->filesize = st.st_size; - - if(session->filepos != 0) - { - rc = fseek(session->fp, session->filepos, SEEK_SET); - if(rc != 0) - { - console_print(RED "fseek '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } - } - - return 0; -} - -/*! read from an open file for ftp session - * - * @param[in] session ftp session - * - * @returns bytes read - */ -static ssize_t -ftp_session_read_file(ftp_session_t *session) -{ - ssize_t rc; - - /* read file at current position */ - rc = fread(session->buffer, 1, sizeof(session->buffer), session->fp); - if(rc < 0) - { - console_print(RED "fread: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* adjust file position */ - session->filepos += rc; - - return rc; -} - -/*! open file for writing for ftp session - * - * @param[in] session ftp session - * @param[in] append whether to append - * - * @returns -1 for error - * - * @note truncates file - */ -static int -ftp_session_open_file_write(ftp_session_t *session, - bool append) -{ - int rc; - const char *mode = "wb"; - - if(append) - mode = "ab"; - else if(session->filepos != 0) - mode = "r+b"; - - /* open file in write mode */ - session->fp = fopen(session->buffer, mode); - if(session->fp == NULL) - { - console_print(RED "fopen '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } - - update_free_space(); - - /* it's okay if this fails */ - errno = 0; - rc = setvbuf(session->fp, session->file_buffer, _IOFBF, FILE_BUFFERSIZE); - if(rc != 0) - { - console_print(RED "setvbuf: %d %s\n" RESET, errno, strerror(errno)); - } - - /* check if this had REST but not APPE */ - if(session->filepos != 0 && !append) - { - /* seek to the REST offset */ - rc = fseek(session->fp, session->filepos, SEEK_SET); - if(rc != 0) - { - console_print(RED "fseek '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } - } - - return 0; -} - -/*! write to an open file for ftp session - * - * @param[in] session ftp session - * - * @returns bytes written - */ -static ssize_t -ftp_session_write_file(ftp_session_t *session) -{ - ssize_t rc; - - /* write to file at current position */ - rc = fwrite(session->buffer + session->bufferpos, - 1, session->buffersize - session->bufferpos, - session->fp); - if(rc < 0) - { - console_print(RED "fwrite: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - else if(rc == 0) - console_print(RED "fwrite: wrote 0 bytes\n" RESET); - - /* adjust file position */ - session->filepos += rc; - - update_free_space(); - return rc; -} - -/*! close current working directory for ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_cwd(ftp_session_t *session) -{ - int rc; - - /* close open directory pointer */ - if(session->dp != NULL) - { - rc = closedir(session->dp); - if(rc != 0) - console_print(RED "closedir: %d %s\n" RESET, errno, strerror(errno)); - } - session->dp = NULL; -} - -/*! open current working directory for ftp session - * - * @param[in] session ftp session - * - * @return -1 for failure - */ -static int -ftp_session_open_cwd(ftp_session_t *session) -{ - /* open current working directory */ - session->dp = opendir(session->cwd); - if(session->dp == NULL) - { - console_print(RED "opendir '%s': %d %s\n" RESET, session->cwd, errno, strerror(errno)); - return -1; - } - - return 0; -} - -/*! set state for ftp session - * - * @param[in] session ftp session - * @param[in] state state to set - * @param[in] flags flags - */ -static void -ftp_session_set_state(ftp_session_t *session, - session_state_t state, - set_state_flags_t flags) -{ - session->state = state; - - /* close pasv and data sockets */ - if(flags & CLOSE_PASV) - ftp_session_close_pasv(session); - if(flags & CLOSE_DATA) - ftp_session_close_data(session); - - if(state == COMMAND_STATE) - { - /* close file/cwd */ - ftp_session_close_file(session); - ftp_session_close_cwd(session); - } -} - -/*! fill directory entry - * - * @param[in] session ftp session - * @param[in] st stat data - * @param[in] path path to fill - * @param[in] len path length - * @param[in] type type fact - * - * @returns errno - */ -static int -ftp_session_fill_dirent_type(ftp_session_t *session, const struct stat *st, - const char *path, size_t len, const char *type) -{ - session->buffersize = 0; - - if(session->dir_mode == XFER_DIR_MLSD - || session->dir_mode == XFER_DIR_MLST) - { - if(session->dir_mode == XFER_DIR_MLST) - session->buffer[session->buffersize++] = ' '; - - if(session->mlst_flags & SESSION_MLST_TYPE) - { - /* type fact */ - if(!type) - { - type = "???"; - if(S_ISREG(st->st_mode)) - type = "file"; - else if(S_ISDIR(st->st_mode)) - type = "dir"; -#if !defined(_3DS) && !defined(__SWITCH__) - else if(S_ISLNK(st->st_mode)) - type = "os.unix=symlink"; - else if(S_ISCHR(st->st_mode)) - type = "os.unix=character"; - else if(S_ISBLK(st->st_mode)) - type = "os.unix=block"; - else if(S_ISFIFO(st->st_mode)) - type = "os.unix=fifo"; - else if(S_ISSOCK(st->st_mode)) - type = "os.unix=socket"; -#endif - } - - session->buffersize += - sprintf(session->buffer + session->buffersize, "Type=%s;", type); - } - - if(session->mlst_flags & SESSION_MLST_SIZE) - { - /* size fact */ - session->buffersize += - sprintf(session->buffer + session->buffersize, "Size=%lld;", - (signed long long)st->st_size); - } - - if(session->mlst_flags & SESSION_MLST_MODIFY) - { - /* mtime fact */ - struct tm *tm = gmtime(&st->st_mtime); - if(tm == NULL) - return errno; - - session->buffersize += - strftime(session->buffer + session->buffersize, - sizeof(session->buffer) - session->buffersize, - "Modify=%Y%m%d%H%M%S;", tm); - if(session->buffersize == 0) - return EOVERFLOW; - } - - if(session->mlst_flags & SESSION_MLST_PERM) - { - /* permission fact */ - strcpy(session->buffer + session->buffersize, "Perm="); - session->buffersize += strlen("Perm="); - - /* append permission */ - if(S_ISREG(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'a'; - - /* create permission */ - if(S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'c'; - - /* delete permission */ - session->buffer[session->buffersize++] = 'd'; - - /* chdir permission */ - if(S_ISDIR(st->st_mode) && (st->st_mode & S_IXUSR)) - session->buffer[session->buffersize++] = 'e'; - - /* rename permission */ - session->buffer[session->buffersize++] = 'f'; - - /* list permission */ - if(S_ISDIR(st->st_mode) && (st->st_mode & S_IRUSR)) - session->buffer[session->buffersize++] = 'l'; - - /* mkdir permission */ - if(S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'm'; - - /* delete permission */ - if(S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'p'; - - /* read permission */ - if(S_ISREG(st->st_mode) && (st->st_mode & S_IRUSR)) - session->buffer[session->buffersize++] = 'r'; - - /* write permission */ - if(S_ISREG(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'w'; - - session->buffer[session->buffersize++] = ';'; - } - - if(session->mlst_flags & SESSION_MLST_UNIX_MODE) - { - /* unix mode fact */ - mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX | S_ISGID | S_ISUID; - session->buffersize += - sprintf(session->buffer + session->buffersize, "UNIX.mode=0%lo;", - (unsigned long)(st->st_mode & mask)); - } - - /* make sure space precedes name */ - if(session->buffer[session->buffersize-1] != ' ') - session->buffer[session->buffersize++] = ' '; - } - else if(session->dir_mode != XFER_DIR_NLST) - { - if(session->dir_mode == XFER_DIR_STAT) - session->buffer[session->buffersize++] = ' '; - - /* perms nlinks owner group size */ - session->buffersize += - sprintf(session->buffer + session->buffersize, - "%c%c%c%c%c%c%c%c%c%c %lu 3DS 3DS %lld ", - S_ISREG(st->st_mode) ? '-' : - S_ISDIR(st->st_mode) ? 'd' : -#if !defined(_3DS) && !defined(__SWITCH__) - S_ISLNK(st->st_mode) ? 'l' : - S_ISCHR(st->st_mode) ? 'c' : - S_ISBLK(st->st_mode) ? 'b' : - S_ISFIFO(st->st_mode) ? 'p' : - S_ISSOCK(st->st_mode) ? 's' : -#endif - '?', - st->st_mode & S_IRUSR ? 'r' : '-', - st->st_mode & S_IWUSR ? 'w' : '-', - st->st_mode & S_IXUSR ? 'x' : '-', - st->st_mode & S_IRGRP ? 'r' : '-', - st->st_mode & S_IWGRP ? 'w' : '-', - st->st_mode & S_IXGRP ? 'x' : '-', - st->st_mode & S_IROTH ? 'r' : '-', - st->st_mode & S_IWOTH ? 'w' : '-', - st->st_mode & S_IXOTH ? 'x' : '-', - (unsigned long)st->st_nlink, - (signed long long)st->st_size); - - /* timestamp */ - struct tm *tm = gmtime(&st->st_mtime); - if(tm) - { - const char *fmt = "%b %e %Y "; - if(session->timestamp > st->st_mtime - && session->timestamp - st->st_mtime < (60*60*24*365/2)) - { - fmt = "%b %e %H:%M "; - } - - session->buffersize += - strftime(session->buffer + session->buffersize, - sizeof(session->buffer) - session->buffersize, - fmt, tm); - } - else - { - session->buffersize += - sprintf(session->buffer + session->buffersize, "Jan 1 1970 "); - } - } - - if(session->buffersize + len + 2 > sizeof(session->buffer)) - { - /* buffer will overflow */ - return EOVERFLOW; - } - - /* copy path */ - memcpy(session->buffer+session->buffersize, path, len); - len = session->buffersize + len; - session->buffer[len++] = '\r'; - session->buffer[len++] = '\n'; - session->buffersize = len; - - return 0; -} - -/*! fill directory entry - * - * @param[in] session ftp session - * @param[in] st stat data - * @param[in] path path to fill - * @param[in] len path length - * - * @returns errno - */ -static int -ftp_session_fill_dirent(ftp_session_t *session, const struct stat *st, - const char *path, size_t len) -{ - return ftp_session_fill_dirent_type(session, st, path, len, NULL); -} - -/*! transfer loop - * - * Try to transfer as much data as the sockets will allow without blocking - * - * @param[in] session ftp session - */ -static void -ftp_session_transfer(ftp_session_t *session) -{ - int rc; - do - { - rc = session->transfer(session); - } while(rc == 0); -} - -/*! encode a path - * - * @param[in] path path to encode - * @param[in,out] len path length - * @param[in] quotes whether to encode quotes - * - * @returns encoded path - * - * @note The caller must free the returned path - */ -static char* -encode_path(const char *path, - size_t *len, - bool quotes) -{ - bool enc = false; - size_t i, diff = 0; - char *out, *p = (char*)path; - - /* check for \n that needs to be encoded */ - if(memchr(p, '\n', *len) != NULL) - enc = true; - - if(quotes) - { - /* check for " that needs to be encoded */ - p = (char*)path; - do - { - p = memchr(p, '"', path + *len - p); - if(p != NULL) - { - ++p; - ++diff; - } - } while(p != NULL); - } - - /* check if an encode was needed */ - if(!enc && diff == 0) - return strdup(path); - - /* allocate space for encoded path */ - p = out = (char*)malloc(*len + diff); - if(out == NULL) - return NULL; - - /* copy the path while performing encoding */ - for(i = 0; i < *len; ++i) - { - if(*path == '\n') - { - /* encoded \n is \0 */ - *p++ = 0; - } - else if(quotes && *path == '"') - { - /* encoded " is "" */ - *p++ = '"'; - *p++ = '"'; - } - else - *p++ = *path; - ++path; - } - - *len += diff; - return out; -} - -/*! decode a path - * - * @param[in] session ftp session - * @param[in] len command length - */ -static void -decode_path(ftp_session_t *session, - size_t len) -{ - size_t i; - - /* decode \0 from the first command */ - for(i = 0; i < len; ++i) - { - /* this is an encoded \n */ - if(session->cmd_buffer[i] == 0) - session->cmd_buffer[i] = '\n'; - } -} - -/*! fill cdir directory entry - * - * @param[in] session ftp session - * @param[in] path path to fill - * - * @returns errno - */ -static int -ftp_session_fill_dirent_cdir(ftp_session_t *session, const char *path) -{ - int rc; - struct stat st; - char *buffer; - size_t len; - - rc = stat(path, &st); - /* double-check this was a directory */ - if(rc == 0 && !S_ISDIR(st.st_mode)) - { - /* shouldn't happen but just in case */ - rc = -1; - errno = ENOTDIR; - } - if(rc != 0) - return errno; - - /* encode \n in path */ - len = strlen(path); - buffer = encode_path(path, &len, false); - if(!buffer) - return ENOMEM; - - /* fill dirent with listed directory as type=cdir */ - rc = ftp_session_fill_dirent_type(session, &st, buffer, len, "cdir"); - free(buffer); - - return rc; -} - -/*! send a response on the command socket - * - * @param[in] session ftp session - * @param[in] buffer buffer to send - * @param[in] len buffer length - */ -static void -ftp_send_response_buffer(ftp_session_t *session, - const char *buffer, - size_t len) -{ - ssize_t rc, to_send; - - if(session->cmd_fd < 0) - return; - - /* send response */ - to_send = len; - console_print(GREEN "%s" RESET, buffer); - rc = send(session->cmd_fd, buffer, to_send, 0); - if(rc < 0) - { - console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - } - else if(rc != to_send) - { - console_print(RED "only sent %u/%u bytes\n" RESET, - (unsigned int)rc, (unsigned int)to_send); - ftp_session_close_cmd(session); - } -} - -__attribute__((format(printf,3,4))) -/*! send ftp response to ftp session's peer - * - * @param[in] session ftp session - * @param[in] code response code - * @param[in] fmt format string - * @param[in] ... format arguments - */ -static void -ftp_send_response(ftp_session_t *session, - int code, - const char *fmt, ...) -{ - static char buffer[CMD_BUFFERSIZE]; - ssize_t rc; - va_list ap; - - if(session->cmd_fd < 0) - return; - - /* print response code and message to buffer */ - va_start(ap, fmt); - if(code > 0) - rc = sprintf(buffer, "%d ", code); - else - rc = sprintf(buffer, "%d-", -code); - rc += vsnprintf(buffer+rc, sizeof(buffer)-rc, fmt, ap); - va_end(ap); - - if(rc >= sizeof(buffer)) - { - /* couldn't fit message; just send code */ - console_print(RED "%s: buffersize too small\n" RESET, __func__); - if(code > 0) - rc = sprintf(buffer, "%d \r\n", code); - else - rc = sprintf(buffer, "%d-\r\n", -code); - } - - ftp_send_response_buffer(session, buffer, rc); -} - -/*! destroy ftp session - * - * @param[in] session ftp session - * - * @returns the next session in the list - */ -static ftp_session_t* -ftp_session_destroy(ftp_session_t *session) -{ - ftp_session_t *next = session->next; - - /* close all sockets/files */ - ftp_session_close_cmd(session); - ftp_session_close_pasv(session); - ftp_session_close_data(session); - ftp_session_close_file(session); - ftp_session_close_cwd(session); - - /* unlink from sessions list */ - if(session->next) - session->next->prev = session->prev; - if(session == sessions) - sessions = session->next; - else - { - session->prev->next = session->next; - if(session == sessions->prev) - sessions->prev = session->prev; - } - - /* deallocate */ - free(session); - - return next; -} - -/*! allocate new ftp session - * - * @param[in] listen_fd socket to accept connection from - */ -static void -ftp_session_new(int listen_fd) -{ - ssize_t rc; - int new_fd; - ftp_session_t *session; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - - /* accept connection */ - new_fd = accept(listen_fd, (struct sockaddr*)&addr, &addrlen); - if(new_fd < 0) - { - console_print(RED "accept: %d %s\n" RESET, errno, strerror(errno)); - return; - } - - console_print(CYAN "accepted connection from %s:%u\n" RESET, - inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - - /* allocate a new session */ - session = (ftp_session_t*)calloc(1, sizeof(ftp_session_t)); - if(session == NULL) - { - console_print(RED "failed to allocate session\n" RESET); - ftp_closesocket(new_fd, true); - return; - } - - /* initialize session */ - strcpy(session->cwd, "/"); - session->peer_addr.sin_addr.s_addr = INADDR_ANY; - session->cmd_fd = new_fd; - session->pasv_fd = -1; - session->data_fd = -1; - session->mlst_flags = SESSION_MLST_TYPE - | SESSION_MLST_SIZE - | SESSION_MLST_MODIFY - | SESSION_MLST_PERM; - session->state = COMMAND_STATE; - - /* link to the sessions list */ - if(sessions == NULL) - { - sessions = session; - session->prev = session; - } - else - { - sessions->prev->next = session; - session->prev = sessions->prev; - sessions->prev = session; - } - - /* copy socket address to pasv address */ - addrlen = sizeof(session->pasv_addr); - rc = getsockname(new_fd, (struct sockaddr*)&session->pasv_addr, &addrlen); - if(rc != 0) - { - console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 451, "Failed to get connection info\r\n"); - ftp_session_destroy(session); - return; - } - - session->cmd_fd = new_fd; - - /* send initiator response */ - ftp_send_response(session, 220, "Hello!\r\n"); -} - -/*! accept PASV connection for ftp session - * - * @param[in] session ftp session - * - * @returns -1 for failure - */ -static int -ftp_session_accept(ftp_session_t *session) -{ - int rc, new_fd; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - - if(session->flags & SESSION_PASV) - { - /* clear PASV flag */ - session->flags &= ~SESSION_PASV; - - /* tell the peer that we're ready to accept the connection */ - ftp_send_response(session, 150, "Ready\r\n"); - - /* accept connection from peer */ - new_fd = accept(session->pasv_fd, (struct sockaddr*)&addr, &addrlen); - if(new_fd < 0) - { - console_print(RED "accept: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "Failed to establish connection\r\n"); - return -1; - } - - /* set the socket to non-blocking */ - rc = ftp_set_socket_nonblocking(new_fd); - if(rc != 0) - { - ftp_closesocket(new_fd, true); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "Failed to establish connection\r\n"); - return -1; - } - - console_print(CYAN "accepted connection from %s:%u\n" RESET, - inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - - /* we are ready to transfer data */ - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); - session->data_fd = new_fd; - - return 0; - } - else - { - /* peer didn't send PASV command */ - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); - return -1; - } -} - -/*! connect to peer for ftp session - * - * @param[in] session ftp session - * - * @returns -1 for failure - */ -static int -ftp_session_connect(ftp_session_t *session) -{ - int rc; - - /* clear PORT flag */ - session->flags &= ~SESSION_PORT; - - /* create a new socket */ - session->data_fd = socket(AF_INET, SOCK_STREAM, 0); - if(session->data_fd < 0) - { - console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* set socket options */ - rc = ftp_set_socket_options(session->data_fd); - if(rc != 0) - { - ftp_closesocket(session->data_fd, false); - session->data_fd = -1; - return -1; - } - - /* set socket to non-blocking */ - rc = ftp_set_socket_nonblocking(session->data_fd); - if(rc != 0) - return -1; - - /* connect to peer */ - rc = connect(session->data_fd, (struct sockaddr*)&session->peer_addr, - sizeof(session->peer_addr)); - if(rc != 0) - { - if(errno != EINPROGRESS) - { - console_print(RED "connect: %d %s\n" RESET, errno, strerror(errno)); - ftp_closesocket(session->data_fd, false); - session->data_fd = -1; - return -1; - } - } - else - { - console_print(CYAN "connected to %s:%u\n" RESET, - inet_ntoa(session->peer_addr.sin_addr), - ntohs(session->peer_addr.sin_port)); - - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); - ftp_send_response(session, 150, "Ready\r\n"); - } - - return 0; -} - -/*! read command for ftp session - * - * @param[in] session ftp session - * @param[in] events poll events - */ -static void -ftp_session_read_command(ftp_session_t *session, - int events) -{ - char *buffer, *args, *next = NULL; - size_t i, len; - int atmark; - ssize_t rc; - ftp_command_t key, *command; - - /* check out-of-band data */ - if(events & POLLPRI) - { - session->flags |= SESSION_URGENT; - - /* check if we are at the urgent marker */ - atmark = sockatmark(session->cmd_fd); - if(atmark < 0) - { - console_print(RED "sockatmark: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - return; - } - - if(!atmark) - { - /* discard in-band data */ - rc = recv(session->cmd_fd, session->cmd_buffer, sizeof(session->cmd_buffer), 0); - if(rc < 0 && errno != EWOULDBLOCK) - { - console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - } - - return; - } - - /* retrieve the urgent data */ - rc = recv(session->cmd_fd, session->cmd_buffer, sizeof(session->cmd_buffer), MSG_OOB); - if(rc < 0) - { - /* EWOULDBLOCK means out-of-band data is on the way */ - if(errno == EWOULDBLOCK) - return; - - /* error retrieving out-of-band data */ - console_print(RED "recv (oob): %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - return; - } - - /* reset the command buffer */ - session->cmd_buffersize = 0; - return; - } - - /* prepare to receive data */ - buffer = session->cmd_buffer + session->cmd_buffersize; - len = sizeof(session->cmd_buffer) - session->cmd_buffersize; - if(len == 0) - { - /* error retrieving command */ - console_print(RED "Exceeded command buffer size\n" RESET); - ftp_session_close_cmd(session); - return; - } - - /* retrieve command data */ - rc = recv(session->cmd_fd, buffer, len, 0); - if(rc < 0) - { - /* error retrieving command */ - console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - return; - } - if(rc == 0) - { - /* peer closed connection */ - debug_print("peer closed connection\n"); - ftp_session_close_cmd(session); - return; - } - else - { - session->cmd_buffersize += rc; - len = sizeof(session->cmd_buffer) - session->cmd_buffersize; - - if(session->flags & SESSION_URGENT) - { - /* look for telnet data mark */ - for(i = 0; i < session->cmd_buffersize; ++i) - { - if((unsigned char)session->cmd_buffer[i] == 0xF2) - { - /* ignore all data that precedes the data mark */ - if(i < session->cmd_buffersize - 1) - memmove(session->cmd_buffer, session->cmd_buffer + i + 1, len - i - 1); - session->cmd_buffersize -= i + 1; - session->flags &= ~SESSION_URGENT; - break; - } - } - } - - /* loop through commands */ - while(true) - { - /* must have at least enough data for the delimiter */ - if(session->cmd_buffersize < 1) - return; - - /* look for \r\n or \n delimiter */ - for(i = 0; i < session->cmd_buffersize; ++i) - { - if(i < session->cmd_buffersize-1 - && session->cmd_buffer[i] == '\r' - && session->cmd_buffer[i+1] == '\n') - { - /* we found a \r\n delimiter */ - session->cmd_buffer[i] = 0; - next = &session->cmd_buffer[i+2]; - break; - } - else if(session->cmd_buffer[i] == '\n') - { - /* we found a \n delimiter */ - session->cmd_buffer[i] = 0; - next = &session->cmd_buffer[i+1]; - break; - } - } - - /* check if a delimiter was found */ - if(i == session->cmd_buffersize) - return; - - /* decode the command */ - decode_path(session, i); - - /* split command from arguments */ - args = buffer = session->cmd_buffer; - while(*args && !isspace((int)*args)) - ++args; - if(*args) - *args++ = 0; - - /* look up the command */ - key.name = buffer; - command = bsearch(&key, ftp_commands, - num_ftp_commands, sizeof(ftp_command_t), - ftp_command_cmp); - - /* update command timestamp */ - session->timestamp = time(NULL); - - /* execute the command */ - if(command == NULL) - { - /* send header */ - ftp_send_response(session, 502, "Invalid command \""); - - /* send command */ - len = strlen(buffer); - buffer = encode_path(buffer, &len, false); - if(buffer != NULL) - ftp_send_response_buffer(session, buffer, len); - else - ftp_send_response_buffer(session, key.name, strlen(key.name)); - free(buffer); - - /* send args (if any) */ - if(*args != 0) - { - ftp_send_response_buffer(session, " ", 1); - - len = strlen(args); - buffer = encode_path(args, &len, false); - if(buffer != NULL) - ftp_send_response_buffer(session, buffer, len); - else - ftp_send_response_buffer(session, args, strlen(args)); - free(buffer); - } - - /* send footer */ - ftp_send_response_buffer(session, "\"\r\n", 3); - } - else if(session->state != COMMAND_STATE) - { - /* only some commands are available during data transfer */ - if(strcasecmp(command->name, "ABOR") != 0 - && strcasecmp(command->name, "STAT") != 0 - && strcasecmp(command->name, "QUIT") != 0) - { - ftp_send_response(session, 503, "Invalid command during transfer\r\n"); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_session_close_cmd(session); - } - else - command->handler(session, args); - } - else - { - /* clear RENAME flag for all commands except RNTO */ - if(strcasecmp(command->name, "RNTO") != 0) - session->flags &= ~SESSION_RENAME; - - command->handler(session, args); - } - - /* remove executed command from the command buffer */ - len = session->cmd_buffer + session->cmd_buffersize - next; - if(len > 0) - memmove(session->cmd_buffer, next, len); - session->cmd_buffersize = len; - } - } -} - -/*! poll sockets for ftp session - * - * @param[in] session ftp session - * - * @returns next session - */ -static ftp_session_t* -ftp_session_poll(ftp_session_t *session) -{ - int rc; - struct pollfd pollinfo[2]; - nfds_t nfds = 1; - - /* the first pollfd is the command socket */ - pollinfo[0].fd = session->cmd_fd; - pollinfo[0].events = POLLIN | POLLPRI; - pollinfo[0].revents = 0; - - switch(session->state) - { - case COMMAND_STATE: - /* we are waiting to read a command */ - break; - - case DATA_CONNECT_STATE: - if(session->flags & SESSION_PASV) - { - /* we are waiting for a PASV connection */ - pollinfo[1].fd = session->pasv_fd; - pollinfo[1].events = POLLIN; - } - else - { - /* we are waiting to complete a PORT connection */ - pollinfo[1].fd = session->data_fd; - pollinfo[1].events = POLLOUT; - } - pollinfo[1].revents = 0; - nfds = 2; - break; - - case DATA_TRANSFER_STATE: - /* we need to transfer data */ - pollinfo[1].fd = session->data_fd; - if(session->flags & SESSION_RECV) - pollinfo[1].events = POLLIN; - else - pollinfo[1].events = POLLOUT; - pollinfo[1].revents = 0; - nfds = 2; - break; - } - - /* poll the selected sockets */ - rc = poll(pollinfo, nfds, 0); - if(rc < 0) - { - console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - } - else if(rc > 0) - { - /* check the command socket */ - if(pollinfo[0].revents != 0) - { - /* handle command */ - if(pollinfo[0].revents & POLL_UNKNOWN) - console_print(YELLOW "cmd_fd: revents=0x%08X\n" RESET, pollinfo[0].revents); - - /* we need to read a new command */ - if(pollinfo[0].revents & (POLLERR|POLLHUP)) - { - debug_print("cmd revents=0x%x\n", pollinfo[0].revents); - ftp_session_close_cmd(session); - } - else if(pollinfo[0].revents & (POLLIN | POLLPRI)) - ftp_session_read_command(session, pollinfo[0].revents); - } - - /* check the data/pasv socket */ - if(nfds > 1 && pollinfo[1].revents != 0) - { - switch(session->state) - { - case COMMAND_STATE: - /* this shouldn't happen? */ - break; - - case DATA_CONNECT_STATE: - if(pollinfo[1].revents & POLL_UNKNOWN) - console_print(YELLOW "pasv_fd: revents=0x%08X\n" RESET, pollinfo[1].revents); - - /* we need to accept the PASV connection */ - if(pollinfo[1].revents & (POLLERR|POLLHUP)) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Data connection failed\r\n"); - } - else if(pollinfo[1].revents & POLLIN) - { - if(ftp_session_accept(session) != 0) - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - } - else if(pollinfo[1].revents & POLLOUT) - { - - console_print(CYAN "connected to %s:%u\n" RESET, - inet_ntoa(session->peer_addr.sin_addr), - ntohs(session->peer_addr.sin_port)); - - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); - ftp_send_response(session, 150, "Ready\r\n"); - } - break; - - case DATA_TRANSFER_STATE: - if(pollinfo[1].revents & POLL_UNKNOWN) - console_print(YELLOW "data_fd: revents=0x%08X\n" RESET, pollinfo[1].revents); - - /* we need to transfer data */ - if(pollinfo[1].revents & (POLLERR|POLLHUP)) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Data connection failed\r\n"); - } - else if(pollinfo[1].revents & (POLLIN|POLLOUT)) - ftp_session_transfer(session); - break; - } - } - } - - /* still connected to peer; return next session */ - if(session->cmd_fd >= 0) - return session->next; - - /* disconnected from peer; destroy it and return next session */ - debug_print("disconnected from peer\n"); - return ftp_session_destroy(session); -} - -/* Update free space in status bar */ -static void -update_free_space(void) -{ -#if defined(_3DS) || defined(__SWITCH__) -#define KiB (1024.0) -#define MiB (1024.0*KiB) -#define GiB (1024.0*MiB) - char buffer[16]; - struct statvfs st; - double bytes_free; - int rc, len; - - rc = statvfs("sdmc:/", &st); - if(rc != 0) - console_print(RED "statvfs: %d %s\n" RESET, errno, strerror(errno)); - else - { - bytes_free = (double)st.f_bsize * st.f_bfree; - - if (bytes_free < 1000.0) - len = snprintf(buffer, sizeof(buffer), "%.0lfB", bytes_free); - else if(bytes_free < 10.0*KiB) - len = snprintf(buffer, sizeof(buffer), "%.2lfKiB", floor((bytes_free*100.0)/KiB)/100.0); - else if(bytes_free < 100.0*KiB) - len = snprintf(buffer, sizeof(buffer), "%.1lfKiB", floor((bytes_free*10.0)/KiB)/10.0); - else if(bytes_free < 1000.0*KiB) - len = snprintf(buffer, sizeof(buffer), "%.0lfKiB", floor(bytes_free/KiB)); - else if(bytes_free < 10.0*MiB) - len = snprintf(buffer, sizeof(buffer), "%.2lfMiB", floor((bytes_free*100.0)/MiB)/100.0); - else if(bytes_free < 100.0*MiB) - len = snprintf(buffer, sizeof(buffer), "%.1lfMiB", floor((bytes_free*10.0)/MiB)/10.0); - else if(bytes_free < 1000.0*MiB) - len = snprintf(buffer, sizeof(buffer), "%.0lfMiB", floor(bytes_free/MiB)); - else if(bytes_free < 10.0*GiB) - len = snprintf(buffer, sizeof(buffer), "%.2lfGiB", floor((bytes_free*100.0)/GiB)/100.0); - else if(bytes_free < 100.0*GiB) - len = snprintf(buffer, sizeof(buffer), "%.1lfGiB", floor((bytes_free*10.0)/GiB)/10.0); - else - len = snprintf(buffer, sizeof(buffer), "%.0lfGiB", floor(bytes_free/GiB)); - - console_set_status("\x1b[0;%dH" GREEN "%s", 50-len, buffer); - } -#endif -} - -/*! Update status bar */ -static int -update_status(void) -{ -#if defined(_3DS) || defined(__SWITCH__) - console_set_status("\n" GREEN STATUS_STRING " " -#ifdef ENABLE_LOGGING - "DEBUG " -#endif - CYAN "%s:%u" RESET, - inet_ntoa(serv_addr.sin_addr), - ntohs(serv_addr.sin_port)); - update_free_space(); -#else - char hostname[128]; - socklen_t addrlen = sizeof(serv_addr); - int rc; - - rc = getsockname(listenfd, (struct sockaddr*)&serv_addr, &addrlen); - if(rc != 0) - { - console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - rc = gethostname(hostname, sizeof(hostname)); - if(rc != 0) - { - console_print(RED "gethostname: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - console_set_status(GREEN STATUS_STRING " " -#ifdef ENABLE_LOGGING - "DEBUG " -#endif - YELLOW "IP:" CYAN "%s " - YELLOW "Port:" CYAN "%u" - RESET, - hostname, - ntohs(serv_addr.sin_port)); -#endif - - return 0; -} - -#ifdef _3DS -/*! Handle apt events - * - * @param[in] type Event type - * @param[in] closure Callback closure - */ -static void -apt_hook(APT_HookType type, - void *closure) -{ - switch(type) - { - case APTHOOK_ONSUSPEND: - case APTHOOK_ONSLEEP: - /* turn on backlight, or you can't see the home menu! */ - if(R_SUCCEEDED(gspLcdInit())) - { - GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH); - gspLcdExit(); - } - break; - - case APTHOOK_ONRESTORE: - case APTHOOK_ONWAKEUP: - /* restore backlight power state */ - if(R_SUCCEEDED(gspLcdInit())) - { - (lcd_power ? GSPLCD_PowerOnBacklight : GSPLCD_PowerOffBacklight)(GSPLCD_SCREEN_BOTH); - gspLcdExit(); - } - break; - - default: - break; - } -} -#elif defined(__SWITCH__) -/*! Handle applet events - * - * @param[in] type Event type - * @param[in] closure Callback closure - */ -static void -applet_hook(AppletHookType type, - void *closure) -{ - (void)closure; - (void)type; - /* stubbed for now */ - switch(type) - { - default: - break; - } -} -#endif - -/*! initialize ftp subsystem */ -int -ftp_init(void) -{ - int rc; - - start_time = time(NULL); - -#ifdef _3DS - Result ret = 0; - u32 wifi = 0; - bool loop; - - /* register apt hook */ - aptHook(&cookie, apt_hook, NULL); - - console_print(GREEN "Waiting for wifi...\n" RESET); - - /* wait for wifi to be available */ - while((loop = aptMainLoop()) && !wifi && (ret == 0 || ret == 0xE0A09D2E)) - { - ret = 0; - - hidScanInput(); - if(hidKeysDown() & KEY_B) - { - /* user canceled */ - loop = false; - break; - } - - /* update the wifi status */ - ret = ACU_GetWifiStatus(&wifi); - if(ret != 0) - wifi = 0; - } - - /* check if there was a wifi error */ - if(ret != 0) - console_print(RED "ACU_GetWifiStatus returns 0x%lx\n" RESET, ret); - - /* check if we need to exit */ - if(!loop || ret != 0) - return -1; - - console_print(GREEN "Ready!\n" RESET); - - /* allocate buffer for SOC service */ - SOCU_buffer = (u32*)memalign(SOCU_ALIGN, SOCU_BUFFERSIZE); - if(SOCU_buffer == NULL) - { - console_print(RED "memalign: failed to allocate\n" RESET); - goto memalign_fail; - } - - /* initialize SOC service */ - ret = socInit(SOCU_buffer, SOCU_BUFFERSIZE); - if(ret != 0) - { - console_print(RED "socInit: %08X\n" RESET, (unsigned int)ret); - goto soc_fail; - } -#elif defined(__SWITCH__) - static const SocketInitConfig socketInitConfig = { - .bsdsockets_version = 1, - - .tcp_tx_buf_size = 8 * SOCK_BUFFERSIZE, - .tcp_rx_buf_size = 8 * SOCK_BUFFERSIZE, - .tcp_tx_buf_max_size = 16 * SOCK_BUFFERSIZE, - .tcp_rx_buf_max_size = 16 * SOCK_BUFFERSIZE, - - .udp_tx_buf_size = 0x2400, - .udp_rx_buf_size = 0xA500, - - .sb_efficiency = 8, - }; - - Result ret = socketInitialize(&socketInitConfig); - if(ret != 0) - { - console_print(RED "socketInitialize: %X\n" RESET, (unsigned int)ret); - return -1; - } - - /* register applet hook */ - appletHook(&cookie, applet_hook, NULL); -#endif - - /* allocate socket to listen for clients */ - listenfd = socket(AF_INET, SOCK_STREAM, 0); - if(listenfd < 0) - { - console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - - /* get address to listen on */ - serv_addr.sin_family = AF_INET; -#if defined(_3DS) || defined(__SWITCH__) - serv_addr.sin_addr.s_addr = gethostid(); - serv_addr.sin_port = htons(LISTEN_PORT); -#else - serv_addr.sin_addr.s_addr = INADDR_ANY; - serv_addr.sin_port = htons(LISTEN_PORT); -#endif - - /* reuse address */ - { - int yes = 1; - rc = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - if(rc != 0) - { - console_print(RED "setsockopt: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - } - - /* bind socket to listen address */ - rc = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); - if(rc != 0) - { - console_print(RED "bind: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - - /* listen on socket */ - rc = listen(listenfd, 5); - if(rc != 0) - { - console_print(RED "listen: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - - /* print server address */ - rc = update_status(); - if(rc != 0) - { - ftp_exit(); - return -1; - } - - return 0; - -#ifdef _3DS -soc_fail: - free(SOCU_buffer); - SOCU_buffer = NULL; - -memalign_fail: - return -1; -#endif -} - -/*! deinitialize ftp subsystem */ -void -ftp_exit(void) -{ -#if defined(_3DS) - Result ret; -#endif - - debug_print("exiting ftp server\n"); - - /* clean up all sessions */ - while(sessions != NULL) - ftp_session_destroy(sessions); - - /* stop listening for new clients */ - if(listenfd >= 0) - ftp_closesocket(listenfd, false); - -#ifdef _3DS - /* deinitialize SOC service */ - console_render(); - console_print(CYAN "Waiting for socExit()...\n" RESET); - - if(SOCU_buffer != NULL) - { - ret = socExit(); - if(ret != 0) - console_print(RED "socExit: 0x%08X\n" RESET, (unsigned int)ret); - free(SOCU_buffer); - } -#elif defined(__SWITCH__) - /* deinitialize socket driver */ - console_render(); - console_print(CYAN "Waiting for socketExit()...\n" RESET); - - socketExit(); - -#endif -} - -/*! ftp look - * - * @returns whether to keep looping - */ -loop_status_t -ftp_loop(void) -{ - int rc; - struct pollfd pollinfo; - ftp_session_t *session; - - /* we will poll for new client connections */ - pollinfo.fd = listenfd; - pollinfo.events = POLLIN; - pollinfo.revents = 0; - - /* poll for a new client */ - rc = poll(&pollinfo, 1, 0); - if(rc < 0) - { - /* wifi got disabled */ - if(errno == ENETDOWN) - return LOOP_RESTART; - - console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); - return LOOP_EXIT; - } - else if(rc > 0) - { - if(pollinfo.revents & POLLIN) - { - /* we got a new client */ - ftp_session_new(listenfd); - } - else - { - console_print(YELLOW "listenfd: revents=0x%08X\n" RESET, pollinfo.revents); - } - } - - /* poll each session */ - session = sessions; - while(session != NULL) - session = ftp_session_poll(session); - -#ifdef _3DS - /* check if the user wants to exit */ - hidScanInput(); - u32 down = hidKeysDown(); - - if(down & KEY_B) - return LOOP_EXIT; - - /* check if the user wants to toggle the LCD power */ - if(down & KEY_START) - { - lcd_power = !lcd_power; - apt_hook(APTHOOK_ONRESTORE, NULL); - } -#elif defined(__SWITCH__) - /* check if the user wants to exit */ - hidScanInput(); - u32 down = hidKeysDown(CONTROLLER_P1_AUTO); - - if(down & KEY_B) - return LOOP_EXIT; -#endif - - return LOOP_CONTINUE; -} - -/*! change to parent directory - * - * @param[in] session ftp session - */ -static void -cd_up(ftp_session_t *session) -{ - char *slash = NULL, *p; - - /* remove basename from cwd */ - for(p = session->cwd; *p; ++p) - { - if(*p == '/') - slash = p; - } - *slash = 0; - if(strlen(session->cwd) == 0) - strcat(session->cwd, "/"); -} - -/*! validate a path - * - * @param[in] args path to validate - */ -static int -validate_path(const char *args) -{ - const char *p; - - /* make sure no path components are '..' */ - p = args; - while((p = strstr(p, "/..")) != NULL) - { - if(p[3] == 0 || p[3] == '/') - return -1; - } - - /* make sure there are no '//' */ - if(strstr(args, "//") != NULL) - return -1; - - return 0; -} - -/*! get a path relative to cwd - * - * @param[in] session ftp session - * @param[in] cwd working directory - * @param[in] args path to make - * - * @returns error - * - * @note the output goes to session->buffer - */ -static int -build_path(ftp_session_t *session, - const char *cwd, - const char *args) -{ - int rc; - char *p; - - session->buffersize = 0; - memset(session->buffer, 0, sizeof(session->buffer)); - - /* make sure the input is a valid path */ - if(validate_path(args) != 0) - { - errno = EINVAL; - return -1; - } - - if(args[0] == '/') - { - /* this is an absolute path */ - size_t len = strlen(args); - if(len > sizeof(session->buffer)-1) - { - errno = ENAMETOOLONG; - return -1; - } - - memcpy(session->buffer, args, len); - session->buffersize = len; - } - else - { - /* this is a relative path */ - if(strcmp(cwd, "/") == 0) - rc = snprintf(session->buffer, sizeof(session->buffer), "/%s", - args); - else - rc = snprintf(session->buffer, sizeof(session->buffer), "%s/%s", - cwd, args); - - if(rc >= sizeof(session->buffer)) - { - errno = ENAMETOOLONG; - return -1; - } - - session->buffersize = rc; - } - - /* remove trailing / */ - p = session->buffer + session->buffersize; - while(p > session->buffer && *--p == '/') - { - *p = 0; - --session->buffersize; - } - - /* if we ended with an empty path, it is the root directory */ - if(session->buffersize == 0) - session->buffer[session->buffersize++] = '/'; - - return 0; -} - -/*! transfer a directory listing - * - * @param[in] session ftp session - * - * @returns whether to call again - */ -static loop_status_t -list_transfer(ftp_session_t *session) -{ - ssize_t rc; - size_t len; - char *buffer; - struct stat st; - struct dirent *dent; - - /* check if we sent all available data */ - if(session->bufferpos == session->buffersize) - { - /* check xfer dir type */ - if(session->dir_mode == XFER_DIR_STAT) - rc = 213; - else - rc = 226; - - /* check if this was for a file */ - if(session->dp == NULL) - { - /* we already sent the file's listing */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, rc, "OK\r\n"); - return LOOP_EXIT; - } - - /* get the next directory entry */ - dent = readdir(session->dp); - if(dent == NULL) - { - /* we have exhausted the directory listing */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, rc, "OK\r\n"); - return LOOP_EXIT; - } - - /* TODO I think we are supposed to return entries for . and .. */ - if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) - return LOOP_CONTINUE; - - /* check if this was a NLST */ - if(session->dir_mode == XFER_DIR_NLST) - { - /* NLST gives the whole path name */ - session->buffersize = 0; - if(build_path(session, session->lwd, dent->d_name) == 0) - { - /* encode \n in path */ - len = session->buffersize; - buffer = encode_path(session->buffer, &len, false); - if(buffer != NULL) - { - /* copy to the session buffer to send */ - memcpy(session->buffer, buffer, len); - free(buffer); - session->buffer[len++] = '\r'; - session->buffer[len++] = '\n'; - session->buffersize = len; - } - } - } - else - { -#ifdef _3DS - /* the sdmc directory entry already has the type and size, so no need to do a slow stat */ - u32 magic = *(u32*)session->dp->dirData->dirStruct; - - if(magic == SDMC_DIRITER_MAGIC) - { - sdmc_dir_t *dir = (sdmc_dir_t*)session->dp->dirData->dirStruct; - FS_DirectoryEntry *entry = &dir->entry_data[dir->index]; - - if(entry->attributes & FS_ATTRIBUTE_DIRECTORY) - st.st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; - else - st.st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; - - if(!(entry->attributes & FS_ATTRIBUTE_READ_ONLY)) - st.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; - - st.st_size = entry->fileSize; - st.st_mtime = 0; - - bool getmtime = true; - if(session->dir_mode == XFER_DIR_MLSD - || session->dir_mode == XFER_DIR_MLST) - { - if(!(session->mlst_flags & SESSION_MLST_MODIFY)) - getmtime = false; - } - else if(session->dir_mode == XFER_DIR_NLST) - getmtime = false; - - if((rc = build_path(session, session->lwd, dent->d_name)) != 0) - console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); - else if(getmtime) - { - uint64_t mtime = 0; - if((rc = sdmc_getmtime(session->buffer, &mtime)) != 0) - console_print(RED "sdmc_getmtime '%s': 0x%x\n" RESET, session->buffer, rc); - else - st.st_mtime = mtime; - } - } - else - { - /* lstat the entry */ - if((rc = build_path(session, session->lwd, dent->d_name)) != 0) - console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); - else if((rc = lstat(session->buffer, &st)) != 0) - console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - - if(rc != 0) - { - /* an error occurred */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "unavailable\r\n"); - return LOOP_EXIT; - } - } -#else - /* lstat the entry */ - if((rc = build_path(session, session->lwd, dent->d_name)) != 0) - console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); - else if((rc = lstat(session->buffer, &st)) != 0) - console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - - if(rc != 0) - { - /* an error occurred */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "unavailable\r\n"); - return LOOP_EXIT; - } -#endif - /* encode \n in path */ - len = strlen(dent->d_name); - buffer = encode_path(dent->d_name, &len, false); - if(buffer != NULL) - { - rc = ftp_session_fill_dirent(session, &st, buffer, len); - free(buffer); - if(rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "%s\r\n", strerror(rc)); - return LOOP_EXIT; - } - } - else - session->buffersize = 0; - } - session->bufferpos = 0; - } - - /* send any pending data */ - rc = send(session->data_fd, session->buffer + session->bufferpos, - session->buffersize - session->bufferpos, 0); - if(rc <= 0) - { - /* error sending data */ - if(rc < 0) - { - if(errno == EWOULDBLOCK) - return LOOP_EXIT; - console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); - } - else - console_print(YELLOW "send: %d %s\n" RESET, ECONNRESET, strerror(ECONNRESET)); - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Connection broken during transfer\r\n"); - return LOOP_EXIT; - } - - /* we can try to send more data */ - session->bufferpos += rc; - return LOOP_CONTINUE; -} - -/*! send a file to the client - * - * @param[in] session ftp session - * - * @returns whether to call again - */ -static loop_status_t -retrieve_transfer(ftp_session_t *session) -{ - ssize_t rc; - - if(session->bufferpos == session->buffersize) - { - /* we have sent all the data so read some more */ - rc = ftp_session_read_file(session); - if(rc <= 0) - { - /* can't read any more data */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - if(rc < 0) - ftp_send_response(session, 451, "Failed to read file\r\n"); - else - ftp_send_response(session, 226, "OK\r\n"); - return LOOP_EXIT; - } - - /* we read some data so reset the session buffer to send */ - session->bufferpos = 0; - session->buffersize = rc; - } - - /* send any pending data */ - rc = send(session->data_fd, session->buffer + session->bufferpos, - session->buffersize - session->bufferpos, 0); - if(rc <= 0) - { - /* error sending data */ - if(rc < 0) - { - if(errno == EWOULDBLOCK) - return LOOP_EXIT; - console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); - } - else - console_print(YELLOW "send: %d %s\n" RESET, ECONNRESET, strerror(ECONNRESET)); - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Connection broken during transfer\r\n"); - return LOOP_EXIT; - } - - /* we can try to send more data */ - session->bufferpos += rc; - return LOOP_CONTINUE; -} - -/*! send a file to the client - * - * @param[in] session ftp session - * - * @returns whether to call again - */ -static loop_status_t -store_transfer(ftp_session_t *session) -{ - ssize_t rc; - - if(session->bufferpos == session->buffersize) - { - /* we have written all the received data, so try to get some more */ - rc = recv(session->data_fd, session->buffer, sizeof(session->buffer), 0); - if(rc <= 0) - { - /* can't read any more data */ - if(rc < 0) - { - if(errno == EWOULDBLOCK) - return LOOP_EXIT; - console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); - } - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - - if(rc == 0) - ftp_send_response(session, 226, "OK\r\n"); - else - ftp_send_response(session, 426, "Connection broken during transfer\r\n"); - return LOOP_EXIT; - } - - /* we received some data so reset the session buffer to write */ - session->bufferpos = 0; - session->buffersize = rc; - } - - rc = ftp_session_write_file(session); - if(rc <= 0) - { - /* error writing data */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 451, "Failed to write file\r\n"); - return LOOP_EXIT; - } - - /* we can try to receive more data */ - session->bufferpos += rc; - return LOOP_CONTINUE; -} - -/*! ftp_xfer_file mode */ -typedef enum -{ - XFER_FILE_RETR, /*!< Retrieve a file */ - XFER_FILE_STOR, /*!< Store a file */ - XFER_FILE_APPE, /*!< Append a file */ -} xfer_file_mode_t; - -/*! Transfer a file - * - * @param[in] session ftp session - * @param[in] args ftp arguments - * @param[in] mode transfer mode - * - * @returns failure - */ -static void -ftp_xfer_file(ftp_session_t *session, - const char *args, - xfer_file_mode_t mode) -{ - int rc; - - /* build the path of the file to transfer */ - if(build_path(session, session->cwd, args) != 0) - { - rc = errno; - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 553, "%s\r\n", strerror(rc)); - return; - } - - /* open the file for retrieving or storing */ - if(mode == XFER_FILE_RETR) - rc = ftp_session_open_file_read(session); - else - rc = ftp_session_open_file_write(session, mode == XFER_FILE_APPE); - - if(rc != 0) - { - /* error opening the file */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 450, "failed to open file\r\n"); - return; - } - - if(session->flags & (SESSION_PORT|SESSION_PASV)) - { - ftp_session_set_state(session, DATA_CONNECT_STATE, CLOSE_DATA); - - if(session->flags & SESSION_PORT) - { - /* setup connection */ - rc = ftp_session_connect(session); - if(rc != 0) - { - /* error connecting */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "can't open data connection\r\n"); - return; - } - } - - /* set up the transfer */ - session->flags &= ~(SESSION_RECV|SESSION_SEND); - if(mode == XFER_FILE_RETR) - { - session->flags |= SESSION_SEND; - session->transfer = retrieve_transfer; - } - else - { - session->flags |= SESSION_RECV; - session->transfer = store_transfer; - } - - session->bufferpos = 0; - session->buffersize = 0; - - return; - } - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); -} - -/*! Transfer a directory - * - * @param[in] session ftp session - * @param[in] args ftp arguments - * @param[in] mode transfer mode - * @param[in] workaround whether to workaround LIST -a - */ -static void -ftp_xfer_dir(ftp_session_t *session, - const char *args, - xfer_dir_mode_t mode, - bool workaround) -{ - ssize_t rc; - size_t len; - struct stat st; - char *buffer; - - /* set up the transfer */ - session->dir_mode = mode; - session->flags &= ~SESSION_RECV; - session->flags |= SESSION_SEND; - - session->transfer = list_transfer; - session->buffersize = 0; - session->bufferpos = 0; - - if(strlen(args) > 0) - { - /* an argument was provided */ - if(build_path(session, session->cwd, args) != 0) - { - /* error building path */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - - /* check if this is a directory */ - session->dp = opendir(session->buffer); - if(session->dp == NULL) - { - /* not a directory; check if it is a file */ - rc = stat(session->buffer, &st); - if(rc != 0) - { - /* error getting stat */ - rc = errno; - - /* work around broken clients that think LIST -a is valid */ - if(workaround && mode == XFER_DIR_LIST) - { - if(args[0] == '-' && (args[1] == 'a' || args[1] == 'l')) - { - if(args[2] == 0) - buffer = strdup(args+2); - else - buffer = strdup(args+3); - - if(buffer != NULL) - { - ftp_xfer_dir(session, buffer, mode, false); - free(buffer); - return; - } - - rc = ENOMEM; - } - } - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - else if(mode == XFER_DIR_MLSD) - { - /* specified file instead of directory for MLSD */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - else if(mode == XFER_DIR_NLST) - { - /* NLST uses full path name */ - len = session->buffersize; - buffer = encode_path(session->buffer, &len, false); - } - else - { - /* everything else uses base name */ - const char *base = strrchr(session->buffer, '/') + 1; - - len = strlen(base); - buffer = encode_path(base, &len, false); - } - - if(buffer) - { - rc = ftp_session_fill_dirent(session, &st, buffer, len); - free(buffer); - } - else - rc = ENOMEM; - - if(rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - } - else - { - /* it was a directory, so set it as the lwd */ - memcpy(session->lwd, session->buffer, session->buffersize); - session->lwd[session->buffersize] = 0; - session->buffersize = 0; - - if(session->dir_mode == XFER_DIR_MLSD - && (session->mlst_flags & SESSION_MLST_TYPE)) - { - /* send this directory as type=cdir */ - rc = ftp_session_fill_dirent_cdir(session, session->lwd); - if(rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - } - } - } - else if(ftp_session_open_cwd(session) != 0) - { - /* no argument, but opening cwd failed */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - else - { - /* set the cwd as the lwd */ - strcpy(session->lwd, session->cwd); - session->buffersize = 0; - - if(session->dir_mode == XFER_DIR_MLSD - && (session->mlst_flags & SESSION_MLST_TYPE)) - { - /* send this directory as type=cdir */ - rc = ftp_session_fill_dirent_cdir(session, session->lwd); - if(rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - } - } - - if(mode == XFER_DIR_MLST || mode == XFER_DIR_STAT) - { - /* this is a little different; we have to send the data over the command socket */ - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV | CLOSE_DATA); - session->data_fd = session->cmd_fd; - session->flags |= SESSION_SEND; - ftp_send_response(session, -213, "Status\r\n"); - return; - } - else if(session->flags & (SESSION_PORT|SESSION_PASV)) - { - ftp_session_set_state(session, DATA_CONNECT_STATE, CLOSE_DATA); - - if(session->flags & SESSION_PORT) - { - /* setup connection */ - rc = ftp_session_connect(session); - if(rc != 0) - { - /* error connecting */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "can't open data connection\r\n"); - } - } - - return; - } - - /* we must have got LIST/MLSD/MLST/NLST without a preceding PORT or PASV */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * F T P C O M M A N D S * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -/*! @fn static void ABOR(ftp_session_t *session, const char *args) - * - * @brief abort a transfer - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(ABOR) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - if(session->state == COMMAND_STATE) - { - ftp_send_response(session, 225, "No transfer to abort\r\n"); - return; - } - - /* abort the transfer */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - - /* send response for this request */ - ftp_send_response(session, 225, "Aborted\r\n"); - - /* send response for transfer */ - ftp_send_response(session, 425, "Transfer aborted\r\n"); -} - -/*! @fn static void ALLO(ftp_session_t *session, const char *args) - * - * @brief allocate space - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(ALLO) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - ftp_send_response(session, 202, "superfluous command\r\n"); -} - -/*! @fn static void APPE(ftp_session_t *session, const char *args) - * - * @brief append data to a file - * - * @note requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(APPE) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the file in append mode */ - ftp_xfer_file(session, args, XFER_FILE_APPE); -} - -/*! @fn static void CDUP(ftp_session_t *session, const char *args) - * - * @brief CWD to parent directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(CDUP) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* change to parent directory */ - cd_up(session); - - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void CWD(ftp_session_t *session, const char *args) - * - * @brief change working directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(CWD) -{ - struct stat st; - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* .. is equivalent to CDUP */ - if(strcmp(args, "..") == 0) - { - cd_up(session); - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - /* build the new cwd path */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* get the path status */ - rc = stat(session->buffer, &st); - if(rc != 0) - { - console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - ftp_send_response(session, 550, "unavailable\r\n"); - return; - } - - /* make sure it is a directory */ - if(!S_ISDIR(st.st_mode)) - { - ftp_send_response(session, 553, "not a directory\r\n"); - return; - } - - /* copy the path into the cwd */ - strncpy(session->cwd, session->buffer, sizeof(session->cwd)); - session->cwd[sizeof(session->cwd)-1] = '\0'; - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void DELE(ftp_session_t *session, const char *args) - * - * @brief delete a file - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(DELE) -{ - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the file path */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* try to unlink the path */ - rc = unlink(session->buffer); - if(rc != 0) - { - /* error unlinking the file */ - console_print(RED "unlink: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to delete file\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void FEAT(ftp_session_t *session, const char *args) - * - * @brief list server features - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(FEAT) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* list our features */ - ftp_send_response(session, -211, "\r\n" - " MDTM\r\n" - " MLST Type%s;Size%s;Modify%s;Perm%s;UNIX.mode%s;\r\n" - " PASV\r\n" - " SIZE\r\n" - " TVFS\r\n" - " UTF8\r\n" - "\r\n" - "211 End\r\n", - session->mlst_flags & SESSION_MLST_TYPE ? "*" : "", - session->mlst_flags & SESSION_MLST_SIZE ? "*" : "", - session->mlst_flags & SESSION_MLST_MODIFY ? "*" : "", - session->mlst_flags & SESSION_MLST_PERM ? "*" : "", - session->mlst_flags & SESSION_MLST_UNIX_MODE ? "*" : ""); -} - -/*! @fn static void HELP(ftp_session_t *session, const char *args) - * - * @brief print server help - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(HELP) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* list our accepted commands */ - ftp_send_response(session, -214, - "The following commands are recognized\r\n" - " ABOR ALLO APPE CDUP CWD DELE FEAT HELP LIST MDTM MKD MLSD MLST MODE\r\n" - " NLST NOOP OPTS PASS PASV PORT PWD QUIT REST RETR RMD RNFR RNTO STAT\r\n" - " STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD XPWD XRMD\r\n" - "214 End\r\n"); -} - -/*! @fn static void LIST(ftp_session_t *session, const char *args) - * - * @brief retrieve a directory listing - * - * @note Requires a PORT or PASV connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(LIST) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the path in LIST mode */ - ftp_xfer_dir(session, args, XFER_DIR_LIST, true); -} - -/*! @fn static void MDTM(ftp_session_t *session, const char *args) - * - * @brief get last modification time - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MDTM) -{ - int rc; -#ifdef _3DS - uint64_t mtime; -#else - struct stat st; -#endif - time_t t_mtime; - struct tm *tm; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - -#ifdef _3DS - rc = sdmc_getmtime(session->buffer, &mtime); - if(rc != 0) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - t_mtime = mtime; -#else - rc = stat(session->buffer, &st); - if(rc != 0) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - t_mtime = st.st_mtime; -#endif - - tm = gmtime(&t_mtime); - if(tm == NULL) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - - session->buffersize = strftime(session->buffer, sizeof(session->buffer), "%Y%m%d%H%M%S", tm); - if(session->buffersize == 0) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - - session->buffer[session->buffersize] = 0; - - ftp_send_response(session, 213, "%s\r\n", session->buffer); -} -/*! @fn static void MKD(ftp_session_t *session, const char *args) - * - * @brief create a directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MKD) -{ - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* try to create the directory */ - rc = mkdir(session->buffer, 0755); - if(rc != 0 && errno != EEXIST) - { - /* mkdir failure */ - console_print(RED "mkdir: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to create directory\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void MLSD(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MLSD) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the path in MLSD mode */ - ftp_xfer_dir(session, args, XFER_DIR_MLSD, true); -} - -/*! @fn static void MLST(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MLST) -{ - struct stat st; - int rc; - char *path; - size_t len; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 501, "%s\r\n", strerror(errno)); - return; - } - - /* stat path */ - rc = lstat(session->buffer, &st); - if(rc != 0) - { - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - - /* encode \n in path */ - len = session->buffersize; - path = encode_path(session->buffer, &len, true); - if(!path) - { - ftp_send_response(session, 550, "%s\r\n", strerror(ENOMEM)); - return; - } - - session->dir_mode = XFER_DIR_MLST; - rc = ftp_session_fill_dirent(session, &st, path, len); - free(path); - if(rc != 0) - { - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - - path = malloc(session->buffersize + 1); - if(!path) - { - ftp_send_response(session, 550, "%s\r\n", strerror(ENOMEM)); - return; - } - - memcpy(path, session->buffer, session->buffersize); - path[session->buffersize] = 0; - ftp_send_response(session, -250, "Status\r\n%s250 End\r\n", path); - free(path); -} - -/*! @fn static void MODE(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MODE) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we only accept S (stream) mode */ - if(strcasecmp(args, "S") == 0) - { - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - ftp_send_response(session, 504, "unavailable\r\n"); -} - -/*! @fn static void NLST(ftp_session_t *session, const char *args) - * - * @brief retrieve a name list - * - * @note Requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(NLST) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the path in NLST mode */ - return ftp_xfer_dir(session, args, XFER_DIR_NLST, false); -} - -/*! @fn static void NOOP(ftp_session_t *session, const char *args) - * - * @brief no-op - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(NOOP) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* this is a no-op */ - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void OPTS(ftp_session_t *session, const char *args) - * - * @brief set options - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(OPTS) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we accept the following UTF8 options */ - if(strcasecmp(args, "UTF8") == 0 - || strcasecmp(args, "UTF8 ON") == 0 - || strcasecmp(args, "UTF8 NLST") == 0) - { - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - /* check MLST options */ - if(strncasecmp(args, "MLST ", 5) == 0) - { - static const struct - { - const char *name; - session_mlst_flags_t flag; - } mlst_flags[] = - { - { "Type;", SESSION_MLST_TYPE, }, - { "Size;", SESSION_MLST_SIZE, }, - { "Modify;", SESSION_MLST_MODIFY, }, - { "Perm;", SESSION_MLST_PERM, }, - { "UNIX.mode;", SESSION_MLST_UNIX_MODE, }, - }; - static const size_t num_mlst_flags = sizeof(mlst_flags)/sizeof(mlst_flags[0]); - - session_mlst_flags_t flags = 0; - args += 5; - const char *p = args; - while(*p) - { - for(size_t i = 0; i < num_mlst_flags; ++i) - { - if(strncasecmp(mlst_flags[i].name, p, strlen(mlst_flags[i].name)) == 0) - { - flags |= mlst_flags[i].flag; - p += strlen(mlst_flags[i].name)-1; - break; - } - } - - while(*p && *p != ';') - ++p; - - if(*p == ';') - ++p; - } - - session->mlst_flags = flags; - ftp_send_response(session, 200, "MLST OPTS%s%s%s%s%s%s\r\n", - flags ? " " : "", - flags & SESSION_MLST_TYPE ? "Type;" : "", - flags & SESSION_MLST_SIZE ? "Size;" : "", - flags & SESSION_MLST_MODIFY ? "Modify;" : "", - flags & SESSION_MLST_PERM ? "Perm;" : "", - flags & SESSION_MLST_UNIX_MODE ? "UNIX.mode;" : ""); - return; - } - - ftp_send_response(session, 504, "invalid argument\r\n"); -} - -/*! @fn static void PASS(ftp_session_t *session, const char *args) - * - * @brief provide password - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PASS) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* we accept any password */ - ftp_session_set_state(session, COMMAND_STATE, 0); - - ftp_send_response(session, 230, "OK\r\n"); -} - -/*! @fn static void PASV(ftp_session_t *session, const char *args) - * - * @brief request an address to connect to - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PASV) -{ - int rc; - char buffer[INET_ADDRSTRLEN + 10]; - char *p; - in_port_t port; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - memset(buffer, 0, sizeof(buffer)); - - /* reset the state */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - session->flags &= ~(SESSION_PASV|SESSION_PORT); - - /* create a socket to listen on */ - session->pasv_fd = socket(AF_INET, SOCK_STREAM, 0); - if(session->pasv_fd < 0) - { - console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 451, "\r\n"); - return; - } - - /* set the socket options */ - rc = ftp_set_socket_options(session->pasv_fd); - if(rc != 0) - { - /* failed to set socket options */ - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - - /* grab a new port */ - session->pasv_addr.sin_port = htons(next_data_port()); - -#if defined(_3DS) || defined(__SWITCH__) - console_print(YELLOW "binding to %s:%u\n" RESET, - inet_ntoa(session->pasv_addr.sin_addr), - ntohs(session->pasv_addr.sin_port)); -#endif - - /* bind to the port */ - rc = bind(session->pasv_fd, (struct sockaddr*)&session->pasv_addr, - sizeof(session->pasv_addr)); - if(rc != 0) - { - /* failed to bind */ - console_print(RED "bind: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - - /* listen on the socket */ - rc = listen(session->pasv_fd, 1); - if(rc != 0) - { - /* failed to listen */ - console_print(RED "listen: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - -#ifndef _3DS - { - /* get the socket address since we requested an ephemeral port */ - socklen_t addrlen = sizeof(session->pasv_addr); - rc = getsockname(session->pasv_fd, (struct sockaddr*)&session->pasv_addr, - &addrlen); - if(rc != 0) - { - /* failed to get socket address */ - console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - } -#endif - - /* we are now listening on the socket */ - console_print(YELLOW "listening on %s:%u\n" RESET, - inet_ntoa(session->pasv_addr.sin_addr), - ntohs(session->pasv_addr.sin_port)); - session->flags |= SESSION_PASV; - - /* print the address in the ftp format */ - port = ntohs(session->pasv_addr.sin_port); - strcpy(buffer, inet_ntoa(session->pasv_addr.sin_addr)); - sprintf(buffer+strlen(buffer), ",%u,%u", - port >> 8, port & 0xFF); - for(p = buffer; *p; ++p) - { - if(*p == '.') - *p = ','; - } - - ftp_send_response(session, 227, "%s\r\n", buffer); -} - -/*! @fn static void PORT(ftp_session_t *session, const char *args) - * - * @brief provide an address for the server to connect to - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PORT) -{ - char *addrstr, *p, *portstr; - int commas = 0, rc; - short port = 0; - unsigned long val; - struct sockaddr_in addr; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* reset the state */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - session->flags &= ~(SESSION_PASV|SESSION_PORT); - - /* dup the args since they are const and we need to change it */ - addrstr = strdup(args); - if(addrstr == NULL) - { - ftp_send_response(session, 425, "%s\r\n", strerror(ENOMEM)); - return; - } - - /* replace a,b,c,d,e,f with a.b.c.d\0e.f */ - for(p = addrstr; *p; ++p) - { - if(*p == ',') - { - if(commas != 3) - *p = '.'; - else - { - *p = 0; - portstr = p+1; - } - ++commas; - } - } - - /* make sure we got the right number of values */ - if(commas != 5) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - - /* parse the address */ - rc = inet_aton(addrstr, &addr.sin_addr); - if(rc == 0) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - - /* parse the port */ - val = 0; - port = 0; - for(p = portstr; *p; ++p) - { - if(!isdigit((int)*p)) - { - if(p == portstr || *p != '.' || val > 0xFF) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - port <<= 8; - port += val; - val = 0; - } - else - { - val *= 10; - val += *p - '0'; - } - } - - /* validate the port */ - if(val > 0xFF || port > 0xFF) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - port <<= 8; - port += val; - - /* fill in the address port and family */ - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - - free(addrstr); - - memcpy(&session->peer_addr, &addr, sizeof(addr)); - - /* we are ready to connect to the client */ - session->flags |= SESSION_PORT; - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void PWD(ftp_session_t *session, const char *args) - * - * @brief print working directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PWD) -{ - static char buffer[CMD_BUFFERSIZE]; - size_t len = sizeof(buffer), i; - char *path; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* encode the cwd */ - len = strlen(session->cwd); - path = encode_path(session->cwd, &len, true); - if(path != NULL) - { - i = sprintf(buffer, "257 \""); - if(i + len + 3 > sizeof(buffer)) - { - /* buffer will overflow */ - free(path); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "unavailable\r\n"); - ftp_send_response(session, 425, "%s\r\n", strerror(EOVERFLOW)); - return; - } - memcpy(buffer+i, path, len); - free(path); - len += i; - buffer[len++] = '"'; - buffer[len++] = '\r'; - buffer[len++] = '\n'; - - ftp_send_response_buffer(session, buffer, len); - return; - } - - ftp_send_response(session, 425, "%s\r\n", strerror(ENOMEM)); -} - -/*! @fn static void QUIT(ftp_session_t *session, const char *args) - * - * @brief terminate ftp session - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(QUIT) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* disconnect from the client */ - ftp_send_response(session, 221, "disconnecting\r\n"); - ftp_session_close_cmd(session); -} - -/*! @fn static void REST(ftp_session_t *session, const char *args) - * - * @brief restart a transfer - * - * @note sets file position for a subsequent STOR operation - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(REST) -{ - const char *p; - uint64_t pos = 0; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* make sure an argument is provided */ - if(args == NULL) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - /* parse the offset */ - for(p = args; *p; ++p) - { - if(!isdigit((int)*p)) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - if(UINT64_MAX / 10 < pos) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - pos *= 10; - - if(UINT64_MAX - (*p - '0') < pos) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - pos += (*p - '0'); - } - - /* set the restart offset */ - session->filepos = pos; - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void RETR(ftp_session_t *session, const char *args) - * - * @brief retrieve a file - * - * @note Requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RETR) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the file to retrieve */ - return ftp_xfer_file(session, args, XFER_FILE_RETR); -} - -/*! @fn static void RMD(ftp_session_t *session, const char *args) - * - * @brief remove a directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RMD) -{ - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path to remove */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* remove the directory */ - rc = rmdir(session->buffer); - if(rc != 0) - { - /* rmdir error */ - console_print(RED "rmdir: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to delete directory\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void RNFR(ftp_session_t *session, const char *args) - * - * @brief rename from - * - * @note Must be followed by RNTO - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RNFR) -{ - int rc; - struct stat st; - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path to rename from */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* make sure the path exists */ - rc = lstat(session->buffer, &st); - if(rc != 0) - { - /* error getting path status */ - console_print(RED "lstat: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 450, "no such file or directory\r\n"); - return; - } - - /* we are ready for RNTO */ - session->flags |= SESSION_RENAME; - ftp_send_response(session, 350, "OK\r\n"); -} - -/*! @fn static void RNTO(ftp_session_t *session, const char *args) - * - * @brief rename to - * - * @note Must be preceded by RNFR - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RNTO) -{ - static char rnfr[XFER_BUFFERSIZE]; // rename-from buffer - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* make sure the previous command was RNFR */ - if(!(session->flags & SESSION_RENAME)) - { - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); - return; - } - - /* clear the rename state */ - session->flags &= ~SESSION_RENAME; - - /* copy the RNFR path */ - memcpy(rnfr, session->buffer, XFER_BUFFERSIZE); - - /* build the path to rename to */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 554, "%s\r\n", strerror(errno)); - return; - } - - /* rename the file */ - rc = rename(rnfr, session->buffer); - if(rc != 0) - { - /* rename failure */ - console_print(RED "rename: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to rename file/directory\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void SIZE(ftp_session_t *session, const char *args) - * - * @brief get file size - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(SIZE) -{ - int rc; - struct stat st; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path to stat */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - rc = stat(session->buffer, &st); - if(rc != 0 || !S_ISREG(st.st_mode)) - { - ftp_send_response(session, 550, "Could not get file size.\r\n"); - return; - } - - ftp_send_response(session, 213, "%" PRIu64 "\r\n", - (uint64_t)st.st_size); -} - -/*! @fn static void STAT(ftp_session_t *session, const char *args) - * - * @brief get status - * - * @note If no argument is supplied, and a transfer is occurring, get the - * current transfer status. If no argument is supplied, and no transfer - * is occurring, get the server status. If an argument is supplied, this - * is equivalent to LIST, except the data is sent over the command - * socket. - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STAT) -{ - time_t uptime = time(NULL) - start_time; - int hours = uptime / 3600; - int minutes = (uptime / 60) % 60; - int seconds = uptime % 60; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - if(session->state == DATA_CONNECT_STATE) - { - /* we are waiting to connect to the client */ - ftp_send_response(session, -211, "FTP server status\r\n" - " Waiting for data connection\r\n" - "211 End\r\n"); - return; - } - else if(session->state == DATA_TRANSFER_STATE) - { - /* we are in the middle of a transfer */ - ftp_send_response(session, -211, "FTP server status\r\n" - " Transferred %" PRIu64 " bytes\r\n" - "211 End\r\n", - session->filepos); - return; - } - - if(strlen(args) == 0) - { - /* no argument provided, send the server status */ - ftp_send_response(session, -211, "FTP server status\r\n" - " Uptime: %02d:%02d:%02d\r\n" - "211 End\r\n", - hours, minutes, seconds); - return; - } - - /* argument provided, open the path in STAT mode */ - ftp_xfer_dir(session, args, XFER_DIR_STAT, false); -} - -/*! @fn static void STOR(ftp_session_t *session, const char *args) - * - * @brief store a file - * - * @note Requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STOR) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the file to store */ - return ftp_xfer_file(session, args, XFER_FILE_STOR); -} - -/*! @fn static void STOU(ftp_session_t *session, const char *args) - * - * @brief store a unique file - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STOU) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* we do not support this yet */ - ftp_session_set_state(session, COMMAND_STATE, 0); - - ftp_send_response(session, 502, "unavailable\r\n"); -} - -/*! @fn static void STRU(ftp_session_t *session, const char *args) - * - * @brief set file structure - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STRU) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we only support F (no structure) mode */ - if(strcasecmp(args, "F") == 0) - { - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - ftp_send_response(session, 504, "unavailable\r\n"); -} - -/*! @fn static void SYST(ftp_session_t *session, const char *args) - * - * @brief identify system - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(SYST) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we are UNIX compliant with 8-bit characters */ - ftp_send_response(session, 215, "UNIX Type: L8\r\n"); -} - -/*! @fn static void TYPE(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @note transfer mode is always binary - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(TYPE) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we always transfer in binary mode */ - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void USER(ftp_session_t *session, const char *args) - * - * @brief provide user name - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(USER) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we accept any user name */ - ftp_send_response(session, 230, "OK\r\n"); -} diff --git a/source/ftpServer.cpp b/source/ftpServer.cpp new file mode 100644 index 0000000..935bc62 --- /dev/null +++ b/source/ftpServer.cpp @@ -0,0 +1,253 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "ftpServer.h" + +#include "fs.h" + +#include "imgui.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +#ifndef _3DS +#define MULTITHREADED 1 +#else +#define MULTITHREADED 0 +#endif + +namespace +{ +auto const s_startTime = std::chrono::system_clock::to_time_t (std::chrono::system_clock::now ()); +platform::Mutex s_lock; +std::string s_freeSpace; +} + +/////////////////////////////////////////////////////////////////////////// +FtpServer::~FtpServer () +{ + m_quit = true; + + m_thread.join (); +} + +FtpServer::FtpServer (std::uint16_t const port_) + : m_log (Log::create ()), m_port (port_), m_quit (false) +{ + Log::bind (m_log); + + handleStartButton (); + +#if MULTITHREADED + m_thread = platform::Thread (std::bind (&FtpServer::threadFunc, this)); +#endif +} + +void FtpServer::draw () +{ +#if !MULTITHREADED + loop (); +#endif + + ImGuiIO &io = ImGui::GetIO (); + auto const width = io.DisplaySize.x; + auto const height = io.DisplaySize.y; + + ImGui::SetNextWindowPos (ImVec2 (0, 0), ImGuiCond_FirstUseEver); +#ifdef _3DS + // top screen + ImGui::SetNextWindowSize (ImVec2 (width, height / 2.0f)); +#else + ImGui::SetNextWindowSize (ImVec2 (width, height)); +#endif + ImGui::Begin (STATUS_STRING, + nullptr, + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + { + auto const lock = std::scoped_lock (m_lock); + if (!m_socket) + { + if (ImGui::Button ("Start")) + handleStartButton (); + } + else if (ImGui::Button ("Stop")) + handleStopButton (); + + if (m_socket) + { + ImGui::SameLine (); + ImGui::TextUnformatted (m_name.c_str ()); + } + } + + { + auto const lock = std::scoped_lock (s_lock); + if (!s_freeSpace.empty ()) + { + ImGui::SameLine (); + ImGui::TextUnformatted (s_freeSpace.c_str ()); + } + } + + ImGui::Separator (); + +#ifdef _3DS + ImGui::BeginChild ("Logs", ImVec2 (0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); +#else + ImGui::BeginChild ("Logs", ImVec2 (0, 200), false, ImGuiWindowFlags_HorizontalScrollbar); +#endif + m_log->draw (); + ImGui::EndChild (); + +#ifdef _3DS + ImGui::End (); + + // bottom screen + ImGui::SetNextWindowSize (ImVec2 (width * 0.8f, height / 2.0f)); + ImGui::SetNextWindowPos (ImVec2 (width * 0.1f, height / 2.0f), ImGuiCond_FirstUseEver); + ImGui::Begin ("Sessions", + nullptr, + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); +#else + ImGui::Separator (); +#endif + + for (auto &session : m_sessions) + session->draw (); + + ImGui::End (); +} + +UniqueFtpServer FtpServer::create (std::uint16_t const port_) +{ + updateFreeSpace (); + return UniqueFtpServer (new FtpServer (port_)); +} + +void FtpServer::updateFreeSpace () +{ +#if defined(_3DS) || defined(__SWITCH__) + struct statvfs st; + if (::statvfs ("sdmc:/", &st) != 0) + return; + + auto const lock = std::scoped_lock (s_lock); + s_freeSpace = fs::printSize (static_cast (st.f_bsize) * st.f_bfree); +#endif +} + +std::time_t FtpServer::startTime () +{ + return s_startTime; +} + +void FtpServer::handleStartButton () +{ + if (m_socket) + return; + + struct sockaddr_in addr; + addr.sin_family = AF_INET; +#if defined(_3DS) || defined(__SWITCH__) + addr.sin_addr.s_addr = gethostid (); +#else + addr.sin_addr.s_addr = INADDR_ANY; +#endif + addr.sin_port = htons (m_port); + + auto socket = Socket::create (); + if (!socket) + return; + + if (m_port != 0 && !socket->setReuseAddress (true)) + return; + + if (!socket->bind (addr)) + return; + + if (!socket->listen (10)) + return; + + auto const &sockName = socket->sockName (); + auto const name = sockName.name (); + + m_name.resize (std::strlen (name) + 3 + 5); + m_name.resize (std::sprintf (&m_name[0], "[%s]:%u", name, sockName.port ())); + + Log::info ("Started server at %s\n", m_name.c_str ()); + + m_socket = std::move (socket); +} + +void FtpServer::handleStopButton () +{ + m_socket.reset (); + Log::info ("Stopped server at %s\n", m_name.c_str ()); +} + +void FtpServer::loop () +{ + { + auto const lock = std::scoped_lock (m_lock); + if (m_socket) + { + Socket::PollInfo info{*m_socket, POLLIN, 0}; + if (Socket::poll (&info, 1, 0ms) > 0) + { + auto socket = m_socket->accept (); + if (socket) + m_sessions.emplace_back (FtpSession::create (std::move (socket))); + } + } + } + + for (auto it = std::begin (m_sessions); it != std::end (m_sessions);) + { + auto const &session = *it; + if (session->dead ()) + it = m_sessions.erase (it); + else + ++it; + } + + if (!m_sessions.empty ()) + FtpSession::poll (m_sessions); +#if MULTITHREADED + else + platform::Thread::sleep (16ms); +#endif +} + +void FtpServer::threadFunc () +{ + Log::bind (m_log); + + while (!m_quit) + loop (); +} diff --git a/source/ftpSession.cpp b/source/ftpSession.cpp new file mode 100644 index 0000000..1f9239f --- /dev/null +++ b/source/ftpSession.cpp @@ -0,0 +1,2482 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "ftpSession.h" + +#include "ftpServer.h" + +#include "log.h" + +#include "imgui.h" + +#ifdef _3DS +#include <3ds.h> +#endif + +#ifdef __SWITCH__ +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +#if defined(_3DS) || defined(__SWITCH__) +#define lstat stat +#endif + +namespace +{ +std::pair parseCommand (char *const buffer_, std::size_t const size_) +{ + auto const end = &buffer_[size_]; + for (auto p = buffer_; p < end; ++p) + { + if (p[0] == '\r' && p < end - 1 && p[1] == '\n') + return {p, &p[2]}; + + if (p[0] == '\n') + return {p, &p[1]}; + } + + return {nullptr, nullptr}; +} + +void decodePath (char *const buffer_, std::size_t const size_) +{ + auto const end = &buffer_[size_]; + for (auto p = buffer_; p < end; ++p) + { + // this is an encoded \n + if (*p == '\0') + *p = '\n'; + } +} + +std::string encodePath (std::string_view const buffer_, bool const quotes_ = false) +{ + // check if the buffer has \n + bool const lf = std::memchr (buffer_.data (), '\n', buffer_.size ()); + + auto end = std::end (buffer_); + + std::size_t numQuotes = 0; + if (quotes_) + { + // check for \" that needs to be encoded + auto p = buffer_.data (); + do + { + p = static_cast (std::memchr (p, '"', end - p)); + if (p) + { + ++p; + ++numQuotes; + } + } while (p); + } + + if (!lf && !numQuotes) + return std::string (buffer_); + + std::string path (buffer_.size () + numQuotes, '\0'); + auto in = buffer_.data (); + auto out = path.data (); + + while (in < end) + { + if (*in == '\n') + { + // encoded \n is \0 + *out++ = '\0'; + } + else if (quotes_ && *in == '"') + { + // encoded \" is \"\" + *out++ = '"'; + *out++ = '"'; + } + else + *out++ = *in; + ++in; + } + + return path; +} + +std::string dirName (std::string_view const path_) +{ + // remove last path component + auto const dir = std::string (path_.substr (0, path_.rfind ('/'))); + if (dir.empty ()) + return "/"; + + return dir; +} + +std::string resolvePath (std::string_view const path_) +{ + assert (!path_.empty ()); + assert (path_[0] == '/'); + + // make sure parent is a directory + struct stat st; + if (::stat (dirName (path_).c_str (), &st) != 0) + return {}; + + if (!S_ISDIR (st.st_mode)) + { + errno = ENOTDIR; + return {}; + } + + // split path components + std::vector components; + + std::size_t pos = 1; + auto next = path_.find ('/', pos); + while (next != std::string::npos) + { + if (next != pos) + components.emplace_back (path_.substr (pos, next - pos)); + pos = next + 1; + next = path_.find ('/', pos); + } + + if (pos != path_.size ()) + components.emplace_back (path_.substr (pos)); + + // collapse . and .. + auto it = std::begin (components); + while (it != std::end (components)) + { + if (*it == ".") + { + it = components.erase (it); + continue; + } + + if (*it == "..") + { + if (it != std::begin (components)) + it = components.erase (std::prev (it)); + it = components.erase (it); + continue; + } + + ++it; + } + + // join path components + std::string outPath = "/"; + for (auto const &component : components) + { + outPath += component; + outPath.push_back ('/'); + } + + if (outPath.size () > 1) + outPath.pop_back (); + + return outPath; +} + +std::string buildPath (std::string_view const cwd_, std::string_view const args_) +{ + // absolute path + if (args_[0] == '/') + return std::string (args_); + + // root directory + if (cwd_.size () == 1) + return std::string (cwd_) + std::string (args_); + + return std::string (cwd_) + '/' + std::string (args_); +} + +std::string buildResolvedPath (std::string_view const cwd_, std::string_view const args_) +{ + return resolvePath (buildPath (cwd_, args_)); +} +} + +/////////////////////////////////////////////////////////////////////////// +FtpSession::~FtpSession () +{ + m_commandSocket.reset (); + m_pasvSocket.reset (); + closeData (); +} + +FtpSession::FtpSession (UniqueSocket commandSocket_) + : m_commandSocket (std::move (commandSocket_)), + m_commandBuffer (COMMAND_BUFFERSIZE), + m_responseBuffer (RESPONSE_BUFFERSIZE), + m_xferBuffer (XFER_BUFFERSIZE), + m_pasv (false), + m_port (false), + m_recv (false), + m_send (false), + m_urgent (false), + m_mlstType (true), + m_mlstSize (true), + m_mlstModify (true), + m_mlstPerm (true), + m_mlstUnixMode (false) +{ + char buffer[32]; + std::sprintf (buffer, "Session#%p", this); + m_windowName = buffer; + + std::sprintf (buffer, "Plot#%p", this); + m_plotName = buffer; + + m_commandSocket->setNonBlocking (); + + auto const lock = std::scoped_lock (m_lock); + sendResponse ("220 Hello!\r\n"); +} + +bool FtpSession::dead () +{ + auto const lock = std::scoped_lock (m_lock); + if (m_commandSocket || m_pasvSocket || m_dataSocket) + return false; + + return true; +} + +void FtpSession::draw () +{ + auto const lock = std::scoped_lock (m_lock); + + ImGuiIO &io = ImGui::GetIO (); + auto const scale = io.DisplayFramebufferScale.y; + + ImGui::BeginChild (m_windowName.c_str (), ImVec2 (0.0f, 50.0f / scale), true); + + if (!m_workItem.empty ()) + ImGui::TextUnformatted (m_workItem.c_str ()); + else + ImGui::TextUnformatted (m_cwd.c_str ()); + + if (m_fileSize) + ImGui::Text ( + "%s/%s", fs::printSize (m_filePosition).c_str (), fs::printSize (m_fileSize).c_str ()); + else if (m_filePosition) + ImGui::Text ("%s/???", fs::printSize (m_filePosition).c_str ()); + + if (m_fileSize || m_filePosition) + { + // MiB/s plot lines + for (std::size_t i = 0; i < POSITION_HISTORY - 1; ++i) + { + m_filePositionDeltas[i] = m_filePositionHistory[i + 1] - m_filePositionHistory[i]; + m_filePositionHistory[i] = m_filePositionHistory[i + 1]; + } + + auto const diff = m_filePosition - m_filePositionHistory[POSITION_HISTORY - 1]; + m_filePositionDeltas[POSITION_HISTORY - 1] = diff; + m_filePositionHistory[POSITION_HISTORY - 1] = m_filePosition; + + if (m_xferRate == -1.0f) + { + m_xferRate = 0.0f; + m_filePositionTime = platform::steady_clock::now (); + } + else + { + auto const now = platform::steady_clock::now (); + auto const timeDiff = now - m_filePositionTime; + m_filePositionTime = now; + + auto const rate = diff / std::chrono::duration (timeDiff).count (); + auto const alpha = 0.01f; + m_xferRate = alpha * rate + (1.0f - alpha) * m_xferRate; + } + + auto const rateString = fs::printSize (m_xferRate) + "/s"; + + ImGui::SameLine (); + ImGui::PlotLines ("Rate", + m_filePositionDeltas, + IM_ARRAYSIZE (m_filePositionDeltas), + 0, + rateString.c_str ()); + } + + ImGui::EndChild (); +} + +UniqueFtpSession FtpSession::create (UniqueSocket commandSocket_) +{ + return UniqueFtpSession (new FtpSession (std::move (commandSocket_))); +} + +void FtpSession::poll (std::vector const &sessions_) +{ +#if 0 + auto const printEvents = [] (int const events_) { + std::string out; + if (events_ & POLLIN) + out += "[IN]"; + if (events_ & POLLPRI) + out += "[PRI]"; + if (events_ & POLLOUT) + out += "[OUT]"; + if (events_ & POLLHUP) + out += "[HUP]"; + if (events_ & POLLERR) + out += "[ERR]"; + + return out; + }; +#endif + + // poll for pending close sockets first + std::vector info; + for (auto &session : sessions_) + { + auto const lock = std::scoped_lock (session->m_lock); + for (auto &pending : session->m_pendingCloseSocket) + { + assert (pending.unique ()); + info.emplace_back (Socket::PollInfo{*pending, POLLIN, 0}); + } + } + + if (!info.empty ()) + { + auto const rc = Socket::poll (info.data (), info.size (), 0ms); + if (rc < 0) + Log::error ("poll: %s\n", std::strerror (errno)); + else + { + for (auto const &i : info) + { + if (!i.revents) + continue; + + for (auto &session : sessions_) + { + auto const lock = std::scoped_lock (session->m_lock); + for (auto it = std::begin (session->m_pendingCloseSocket); + it != std::end (session->m_pendingCloseSocket);) + { + auto &socket = *it; + if (&i.socket.get () != socket.get ()) + { + ++it; + continue; + } + + it = session->m_pendingCloseSocket.erase (it); + } + } + } + } + } + + // poll for everything else + info.clear (); + for (auto &session : sessions_) + { + auto const lock = std::scoped_lock (session->m_lock); + if (session->m_commandSocket) + { + info.emplace_back (Socket::PollInfo{*session->m_commandSocket, POLLIN | POLLPRI, 0}); + if (session->m_responseBuffer.usedSize () != 0) + info.back ().events |= POLLOUT; + } + + switch (session->m_state) + { + case State::COMMAND: + // we are waiting to read a command + break; + + case State::DATA_CONNECT: + if (session->m_pasv) + { + assert (!session->m_port); + // we are waiting for a PASV connection + info.emplace_back (Socket::PollInfo{*session->m_pasvSocket, POLLIN, 0}); + } + else + { + // we are waiting to complete a PORT connection + info.emplace_back (Socket::PollInfo{*session->m_dataSocket, POLLOUT, 0}); + } + break; + + case State::DATA_TRANSFER: + // we need to transfer data + if (session->m_recv) + { + assert (!session->m_send); + info.emplace_back (Socket::PollInfo{*session->m_dataSocket, POLLIN, 0}); + } + else + { + assert (session->m_send); + info.emplace_back (Socket::PollInfo{*session->m_dataSocket, POLLOUT, 0}); + } + break; + } + } + + if (info.empty ()) + return; + + // poll for activity +#if MULTITHREADED + auto const rc = Socket::poll (info.data (), info.size (), 16ms); +#else + auto const rc = Socket::poll (info.data (), info.size (), 0ms); +#endif + if (rc < 0) + { + Log::error ("poll: %s\n", std::strerror (errno)); + return; + } + + if (rc == 0) + return; + + for (auto &session : sessions_) + { + auto const lock = std::scoped_lock (session->m_lock); + + for (auto const &i : info) + { + if (!i.revents) + continue; + + // check command socket + if (&i.socket.get () == session->m_commandSocket.get ()) + { + if (i.revents & ~(POLLIN | POLLPRI | POLLOUT)) + Log::debug ("Command revents 0x%X\n", i.revents); + + if (i.revents & POLLOUT) + session->writeResponse (); + + if (i.revents & (POLLIN | POLLPRI)) + session->readCommand (i.revents); + + if (i.revents & (POLLERR | POLLHUP)) + session->m_commandSocket.reset (); + } + + // check the data socket + if (&i.socket.get () == session->m_pasvSocket.get () || + &i.socket.get () == session->m_dataSocket.get ()) + { + switch (session->m_state) + { + case State::COMMAND: + assert (false); + break; + + case State::DATA_CONNECT: + if (i.revents & ~(POLLIN | POLLPRI | POLLOUT)) + Log::debug ("Data revents 0x%X\n", i.revents); + + if (i.revents & (POLLERR | POLLHUP)) + { + session->sendResponse ("426 Data connection failed\r\n"); + session->setState (State::COMMAND, true, true); + } + else if (i.revents & POLLIN) + { + // we need to accept the PASV connection + session->dataAccept (); + } + else if (i.revents & POLLOUT) + { + // PORT connection completed + auto const &sockName = session->m_dataSocket->peerName (); + Log::info ("Connected to [%s]:%u\n", sockName.name (), sockName.port ()); + + session->sendResponse ("150 Ready\r\n"); + session->setState (State::DATA_TRANSFER, true, false); + } + break; + + case State::DATA_TRANSFER: + if (i.revents & ~(POLLIN | POLLPRI | POLLOUT)) + Log::debug ("Data revents 0x%X\n", i.revents); + + // we need to transfer data + if (i.revents & (POLLERR | POLLHUP)) + { + session->sendResponse ("426 Data connection failed\r\n"); + session->setState (State::COMMAND, true, true); + } + else if (i.revents & (POLLIN | POLLOUT)) + { + while (((*session).*(session->m_transfer)) ()) + ; + } + } + } + } + } +} + +void FtpSession::setState (State const state_, bool const closePasv_, bool const closeData_) +{ + m_state = state_; + + if (closePasv_) + m_pasvSocket.reset (); + if (closeData_) + closeData (); + + if (state_ == State::COMMAND) + { + m_restartPosition = 0; + m_fileSize = 0; + m_filePosition = 0; + + for (auto &pos : m_filePositionHistory) + pos = 0; + m_xferRate = -1.0f; + + m_workItem.clear (); + + m_file.close (); + m_dir.close (); + } +} + +void FtpSession::closeData () +{ + if (m_dataSocket && m_dataSocket.unique ()) + { + m_dataSocket->shutdown (SHUT_WR); + m_dataSocket->setLinger (true, 0s); + m_pendingCloseSocket.emplace_back (std::move (m_dataSocket)); + } + m_dataSocket.reset (); + + m_recv = false; + m_send = false; +} + +bool FtpSession::changeDir (char const *const args_) +{ + if (std::strcmp (args_, "..") == 0) + { + // cd up + auto const pos = m_cwd.find_last_of ('/'); + assert (pos != std::string::npos); + if (pos == 0) + m_cwd = "/"; + else + m_cwd = m_cwd.substr (0, pos); + return true; + } + + auto const path = buildResolvedPath (m_cwd, args_); + if (path.empty ()) + return false; + + struct stat st; + if (::stat (path.c_str (), &st) != 0) + return false; + + if (!S_ISDIR (st.st_mode)) + { + errno = ENOTDIR; + return false; + } + + m_cwd = path; + return true; +} + +bool FtpSession::dataAccept () +{ + if (!m_pasv) + { + sendResponse ("503 Bad sequence of commands\r\n"); + setState (State::COMMAND, true, true); + return false; + } + + m_pasv = false; + + m_dataSocket = m_pasvSocket->accept (); + if (!m_dataSocket) + { + sendResponse ("425 Failed to establish connection\r\n"); + setState (State::COMMAND, true, true); + return false; + } + +#ifndef _3DS + m_dataSocket->setRecvBufferSize (SOCK_BUFFERSIZE); + m_dataSocket->setSendBufferSize (SOCK_BUFFERSIZE); +#endif + + if (!m_dataSocket->setNonBlocking ()) + { + sendResponse ("425 Failed to establish connection\r\n"); + setState (State::COMMAND, true, true); + return false; + } + + // we are ready to transfer data + sendResponse ("150 Ready\r\n"); + setState (State::DATA_TRANSFER, true, false); + return true; +} + +bool FtpSession::dataConnect () +{ + assert (m_port); + + m_port = false; + + m_dataSocket = Socket::create (); + if (!m_dataSocket) + return false; + + m_dataSocket->setRecvBufferSize (SOCK_BUFFERSIZE); + m_dataSocket->setSendBufferSize (SOCK_BUFFERSIZE); + + if (!m_dataSocket->setNonBlocking ()) + return false; + + if (!m_dataSocket->connect (m_portAddr)) + { + if (errno != EINPROGRESS) + { + m_dataSocket.reset (); + return false; + } + + return true; + } + + // we are ready to transfer data + sendResponse ("150 Ready\r\n"); + setState (State::DATA_TRANSFER, true, false); + return true; +} + +int FtpSession::fillDirent (struct stat const &st_, std::string_view const path_, char const *type_) +{ + auto const buffer = m_xferBuffer.freeArea (); + auto const size = m_xferBuffer.freeSize (); + + std::size_t pos = 0; + + if (m_xferDirMode == XferDirMode::MLSD || m_xferDirMode == XferDirMode::MLST) + { + if (m_xferDirMode == XferDirMode::MLST) + { + if (pos >= size) + return EAGAIN; + buffer[pos++] = ' '; + } + + // type fact + if (m_mlstType) + { + if (!type_) + { + type_ = "???"; + if (S_ISREG (st_.st_mode)) + type_ = "file"; + else if (S_ISDIR (st_.st_mode)) + type_ = "dir"; +#if !defined(_3DS) && !defined(__SWITCH__) + else if (S_ISLNK (st_.st_mode)) + type_ = "os.unix=symlink"; + else if (S_ISCHR (st_.st_mode)) + type_ = "os.unix=character"; + else if (S_ISBLK (st_.st_mode)) + type_ = "os.unix=block"; + else if (S_ISFIFO (st_.st_mode)) + type_ = "os.unix=fifo"; + else if (S_ISSOCK (st_.st_mode)) + type_ = "os.unix=socket"; +#endif + } + + auto const rc = std::snprintf (&buffer[pos], size - pos, "Type=%s;", type_); + if (rc < 0) + return errno; + if (static_cast (rc) > size - pos) + return EAGAIN; + + pos += rc; + } + + // size fact + if (m_mlstSize) + { + auto const rc = std::snprintf (&buffer[pos], + size - pos, + "Size=%llu;", + static_cast (st_.st_size)); + if (rc < 0) + return errno; + if (static_cast (rc) > size - pos) + return EAGAIN; + + pos += rc; + } + + // mtime fact + if (m_mlstModify) + { + auto const tm = std::gmtime (&st_.st_mtime); + if (!tm) + return errno; + + auto const rc = std::strftime (&buffer[pos], size - pos, "Modify=%Y%m%d%H%M%S;", tm); + if (rc == 0) + return EAGAIN; + + pos += rc; + } + + // permission fact + if (m_mlstPerm) + { + auto const header = "Perm="; + if (size - pos < std::strlen (header)) + return EAGAIN; + + std::strcpy (&buffer[pos], header); + pos += std::strlen (header); + + // append permission + if (S_ISREG (st_.st_mode) && (st_.st_mode & S_IWUSR)) + { + if (pos >= size) + return EAGAIN; + buffer[pos++] = 'a'; + } + + // create permission + if (S_ISDIR (st_.st_mode) && (st_.st_mode & S_IWUSR)) + { + if (pos >= size) + return EAGAIN; + buffer[pos++] = 'c'; + } + + // delete permission + if (pos >= size) + return EAGAIN; + buffer[pos++] = 'd'; + + // chdir permission + if (S_ISDIR (st_.st_mode) && (st_.st_mode & S_IXUSR)) + { + if (pos >= size) + return EAGAIN; + buffer[pos++] = 'e'; + } + + // rename permission + if (pos >= size) + return EAGAIN; + buffer[pos++] = 'f'; + + // list permission + if (S_ISDIR (st_.st_mode) && (st_.st_mode & S_IRUSR)) + { + if (pos >= size) + return EAGAIN; + buffer[pos++] = 'l'; + } + + // mkdir permission + if (S_ISDIR (st_.st_mode) && (st_.st_mode & S_IWUSR)) + { + if (pos >= size) + return EAGAIN; + buffer[pos++] = 'm'; + } + + // purge permission + if (S_ISDIR (st_.st_mode) && (st_.st_mode & S_IWUSR)) + { + if (pos >= size) + return EAGAIN; + buffer[pos++] = 'p'; + } + + // read permission + if (S_ISREG (st_.st_mode) && (st_.st_mode & S_IRUSR)) + { + if (pos >= size) + return EAGAIN; + buffer[pos++] = 'r'; + } + + // write permission + if (S_ISREG (st_.st_mode) && (st_.st_mode & S_IWUSR)) + { + if (pos >= size) + return EAGAIN; + buffer[pos++] = 'w'; + } + + if (pos >= size) + return EAGAIN; + buffer[pos++] = ';'; + } + + // unix mode fact + if (m_mlstUnixMode) + { + auto const mask = S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX | S_ISGID | S_ISUID; + + auto const rc = std::snprintf (&buffer[pos], + size - pos, + "UNIX.mode=0%lo;", + static_cast (st_.st_mode & mask)); + if (rc < 0) + return errno; + if (static_cast (rc) > size - pos) + return EAGAIN; + + pos += rc; + } + + // make sure space precedes name + if (buffer[pos - 1] != ' ') + { + if (pos >= size) + return EAGAIN; + + buffer[pos++] = ' '; + } + } + else if (m_xferDirMode != XferDirMode::NLST) + { + if (m_xferDirMode == XferDirMode::STAT) + { + if (pos >= size) + return EAGAIN; + + buffer[pos++] = ' '; + } + +#ifdef _3DS + auto const owner = "3DS"; + auto const group = "3DS"; +#elif defined(__SWITCH__) + auto const owner = "Switch"; + auto const group = "Switch"; +#else + char owner[32]; + char group[32]; + std::sprintf (owner, "%d", st_.st_uid); + std::sprintf (group, "%d", st_.st_gid); +#endif + // perms nlinks owner group size + auto rc = std::snprintf (&buffer[pos], + size - pos, + "%c%c%c%c%c%c%c%c%c%c %lu %s %s %llu ", + // clang-format off + S_ISREG (st_.st_mode) ? '-' : + S_ISDIR (st_.st_mode) ? 'd' : +#if !defined(_3DS) && !defined(__SWITCH__) + S_ISLNK (st_.st_mode) ? 'l' : + S_ISCHR (st_.st_mode) ? 'c' : + S_ISBLK (st_.st_mode) ? 'b' : + S_ISFIFO (st_.st_mode) ? 'p' : + S_ISSOCK (st_.st_mode) ? 's' : +#endif + '?', + // clang-format on + st_.st_mode & S_IRUSR ? 'r' : '-', + st_.st_mode & S_IWUSR ? 'w' : '-', + st_.st_mode & S_IXUSR ? 'x' : '-', + st_.st_mode & S_IRGRP ? 'r' : '-', + st_.st_mode & S_IWGRP ? 'w' : '-', + st_.st_mode & S_IXGRP ? 'x' : '-', + st_.st_mode & S_IROTH ? 'r' : '-', + st_.st_mode & S_IWOTH ? 'w' : '-', + st_.st_mode & S_IXOTH ? 'x' : '-', + static_cast (st_.st_nlink), + owner, + group, + static_cast (st_.st_size)); + if (rc < 0) + return errno; + + if (static_cast (rc) > size - pos) + return EAGAIN; + + pos += rc; + + // timestamp + auto const tm = std::gmtime (&st_.st_mtime); + if (!tm) + return errno; + + auto fmt = "%b %e %H:%M "; + rc = std::strftime (&buffer[pos], size - pos, fmt, tm); + if (rc < 0) + return errno; + if (static_cast (rc) > size - pos) + return EAGAIN; + + pos += rc; + } + + if (size - pos < path_.size () + 2) + return EAGAIN; + + // path + std::memcpy (&buffer[pos], path_.data (), path_.size ()); + pos += path_.size (); + buffer[pos++] = '\r'; + buffer[pos++] = '\n'; + + m_xferBuffer.markUsed (pos); + + return 0; +} + +int FtpSession::fillDirent (std::string const &path_, char const *type_) +{ + struct stat st; + if (::stat (path_.c_str (), &st) != 0) + return errno; + + return fillDirent (st, encodePath (path_), type_); +} + +void FtpSession::xferFile (char const *const args_, XferFileMode const mode_) +{ + m_xferBuffer.clear (); + + // build the path of the file to transfer + auto const path = buildResolvedPath (m_cwd, args_); + if (path.empty ()) + { + sendResponse ("553 %s\r\n", std::strerror (errno)); + setState (State::COMMAND, true, true); + return; + } + + if (mode_ == XferFileMode::RETR) + { + // stat the file + struct stat st; + if (::stat (path.c_str (), &st) != 0) + { + sendResponse ("450 %s\r\n", std::strerror (errno)); + return; + } + + // open the file in read mode + if (!m_file.open (path.c_str (), "rb")) + { + sendResponse ("450 %s\r\n", std::strerror (errno)); + return; + } + + m_fileSize = st.st_size; + + m_file.setBufferSize (FILE_BUFFERSIZE); + + if (m_restartPosition != 0) + { + if (m_file.seek (m_restartPosition, SEEK_SET) != 0) + { + sendResponse ("450 %s\r\n", std::strerror (errno)); + return; + } + } + + m_filePosition = m_restartPosition; + } + else + { + auto const append = mode_ == XferFileMode::APPE; + + char const *mode = "wb"; + if (append) + mode = "ab"; + else if (m_restartPosition != 0) + mode = "r+b"; + + // open file in write mode + if (!m_file.open (path.c_str (), mode)) + { + sendResponse ("450 %s\r\n", std::strerror (errno)); + return; + } + + FtpServer::updateFreeSpace (); + + m_file.setBufferSize (FILE_BUFFERSIZE); + + // check if this had REST but not APPE + if (m_restartPosition != 0 && !append) + { + // seek to the REST offset + if (m_file.seek (m_restartPosition, SEEK_SET) != 0) + { + sendResponse ("450 %s\r\n", std::strerror (errno)); + return; + } + } + + m_filePosition = m_restartPosition; + } + + if (!m_port && !m_pasv) + { + sendResponse ("503 Bad sequence of commands\r\n"); + setState (State::COMMAND, true, true); + return; + } + + setState (State::DATA_CONNECT, false, true); + + // setup connection + if (m_port && !dataConnect ()) + { + sendResponse ("425 Can't open data connection\r\n"); + setState (State::COMMAND, true, true); + return; + } + + // set up the transfer + if (mode_ == XferFileMode::RETR) + { + m_recv = false; + m_send = true; + m_transfer = &FtpSession::retrieveTransfer; + } + else + { + m_recv = true; + m_send = false; + m_transfer = &FtpSession::storeTransfer; + } + + m_xferBuffer.clear (); + + m_workItem = path; +} + +void FtpSession::xferDir (char const *const args_, XferDirMode const mode_, bool const workaround_) +{ + // set up the transfer + m_xferDirMode = mode_; + m_recv = false; + m_send = true; + + m_xferBuffer.clear (); + + m_transfer = &FtpSession::listTransfer; + + if (std::strlen (args_) > 0) + { + // an argument was provided + auto const path = buildResolvedPath (m_cwd, args_); + if (path.empty ()) + { + sendResponse ("550 %s\r\n", std::strerror (errno)); + setState (State::COMMAND, true, true); + return; + } + + struct stat st; + if (::stat (path.c_str (), &st) != 0) + { + auto const rc = errno; + + // work around broken clients that think LIST -a/-l is valid + if (workaround_ && mode_ == XferDirMode::LIST) + { + if (args_[0] == '-' && (args_[1] == 'a' || args_[1] == 'l')) + { + char const *args = &args_[2]; + if (*args == '\0' || *args == ' ') + { + if (*args == ' ') + ++args; + + xferDir (args, mode_, false); + return; + } + } + } + + sendResponse ("550 %s\r\n", std::strerror (rc)); + setState (State::COMMAND, true, true); + return; + } + + if (S_ISDIR (st.st_mode)) + { + if (!m_dir.open (path.c_str ())) + { + sendResponse ("550 %s\r\n", std::strerror (errno)); + setState (State::COMMAND, true, true); + return; + } + + // set as lwd + m_lwd = std::move (path); + + if (mode_ == XferDirMode::MLSD && m_mlstType) + { + // send this directory as type=cdir + auto const rc = fillDirent (m_lwd, "cdir"); + if (rc != 0) + { + sendResponse ("550 %s\r\n", std::strerror (rc)); + setState (State::COMMAND, true, true); + return; + } + } + + m_workItem = m_lwd; + } + else if (mode_ == XferDirMode::MLSD) + { + // specified file instead of directory for MLSD + sendResponse ("501 %s\r\n", std::strerror (ENOTDIR)); + setState (State::COMMAND, true, true); + return; + } + else + { + std::string name; + if (mode_ == XferDirMode::NLST) + { + // NLST uses full path name + name = encodePath (path); + } + else + { + // everything else uses basename + auto const pos = path.find_last_of ('/'); + assert (pos != std::string::npos); + name = encodePath (std::string_view (path).substr (pos)); + } + + auto const rc = fillDirent (st, name); + if (rc != 0) + { + sendResponse ("550 %s\r\n", std::strerror (rc)); + setState (State::COMMAND, true, true); + return; + } + + m_workItem = path; + } + } + else if (!m_dir.open (m_cwd.c_str ())) + { + // no argument, but opening cwd failed + sendResponse ("550 %s\r\n", std::strerror (errno)); + setState (State::COMMAND, true, true); + return; + } + else + { + // set the cwd as the lwd + m_lwd = m_cwd; + + if (mode_ == XferDirMode::MLSD && m_mlstType) + { + // send this directory as type=cdir + auto const rc = fillDirent (m_lwd, "cdir"); + if (rc != 0) + { + sendResponse ("550 %s\r\n", std::strerror (rc)); + setState (State::COMMAND, true, true); + return; + } + } + + m_workItem = m_lwd; + } + + if (mode_ == XferDirMode::MLST || mode_ == XferDirMode::STAT) + { + // this is a little different; we have to send the data over the command socket + sendResponse ("213-Status\r\n"); + setState (State::DATA_TRANSFER, true, true); + m_dataSocket = m_commandSocket; + m_send = true; + return; + } + + if (!m_port && !m_pasv) + { + // Prior PORT or PASV required + sendResponse ("503 Bad sequence of commands\r\n"); + setState (State::COMMAND, true, true); + return; + } + + setState (State::DATA_CONNECT, false, true); + m_send = true; + + // setup connection + if (m_port && !dataConnect ()) + { + sendResponse ("425 Can't open data connection\r\n"); + setState (State::COMMAND, true, true); + } +} + +void FtpSession::readCommand (int const events_) +{ + // check out-of-band data + if (events_ & POLLPRI) + { + m_urgent = true; + + // check if we are at the urgent marker + auto const atMark = m_commandSocket->atMark (); + if (atMark < 0) + { + m_commandSocket.reset (); + return; + } + + if (!atMark) + { + // discard in-band data + m_commandBuffer.clear (); + m_lock.unlock (); + auto const rc = m_commandSocket->read (m_commandBuffer); + m_lock.lock (); + if (rc < 0 && errno != EWOULDBLOCK) + m_commandSocket.reset (); + + return; + } + + // retrieve the urgent data + m_commandBuffer.clear (); + m_lock.unlock (); + auto const rc = m_commandSocket->read (m_commandBuffer, true); + m_lock.lock (); + if (rc < 0) + { + // EWOULDBLOCK means out-of-band data is on the way + if (errno != EWOULDBLOCK) + m_commandSocket.reset (); + return; + } + + // reset the command buffer + m_commandBuffer.clear (); + return; + } + + if (events_ & POLLIN) + { + // prepare to receive data + if (m_commandBuffer.freeSize () == 0) + { + Log::error ("Exceeded command buffer size\n"); + m_commandSocket.reset (); + return; + } + + m_lock.unlock (); + auto const rc = m_commandSocket->read (m_commandBuffer); + m_lock.lock (); + if (rc < 0) + { + m_commandSocket.reset (); + return; + } + + if (rc == 0) + { + // peer closed connection + Log::info ("Peer closed connection\n"); + m_commandSocket.reset (); + return; + } + + if (m_urgent) + { + // look for telnet data mark + auto const buffer = m_commandBuffer.usedArea (); + auto const size = m_commandBuffer.usedSize (); + auto const mark = static_cast (std::memchr (buffer, 0xF2, size)); + if (!mark) + return; + + // ignore all data that precedes the data mark + m_commandBuffer.markFree (mark + 1 - buffer); + m_commandBuffer.coalesce (); + m_urgent = false; + } + } + + // loop through commands + while (true) + { + // must have at least enough data for the delimiter + auto const size = m_commandBuffer.usedSize (); + if (size < 1) + return; + + auto const buffer = m_commandBuffer.usedArea (); + auto const [delim, next] = parseCommand (buffer, size); + if (!next) + return; + + *delim = '\0'; + decodePath (buffer, delim - buffer); + Log::command ("%s\n", buffer); + + char const *const command = buffer; + + char *args = buffer; + while (*args && !std::isspace (*args)) + ++args; + if (*args) + *args++ = 0; + + auto const it = std::lower_bound (std::begin (handlers), + std::end (handlers), + command, + [] (auto const &lhs_, auto const &rhs_) { + return ::strcasecmp (lhs_.first.data (), rhs_) < 0; + }); + + if (it == std::end (handlers) || ::strcasecmp (it->first.data (), command) != 0) + { + std::string response = "502 Invalid command \""; + response += encodePath (command); + + if (*args) + { + response.push_back (' '); + response += encodePath (args); + } + + response += "\"\r\n"; + + sendResponse (response); + } + else if (m_state != State::COMMAND) + { + // only some commands are available during data transfer + if (::strcasecmp (command, "ABOR") != 0 && ::strcasecmp (command, "STAT") != 0 && + ::strcasecmp (command, "QUIT") != 0) + { + sendResponse ("503 Invalid command during transfer\r\n"); + setState (State::COMMAND, true, true); + m_commandSocket.reset (); + } + else + { + auto const handler = it->second; + (this->*handler) (args); + } + } + else + { + // clear rename for all commands except RNTO + if (::strcasecmp (command, "RNTO") != 0) + m_rename.clear (); + + auto const handler = it->second; + (this->*handler) (args); + } + + m_commandBuffer.markFree (next - buffer); + m_commandBuffer.coalesce (); + } +} + +void FtpSession::writeResponse () +{ + m_lock.unlock (); + auto const rc = m_commandSocket->write (m_responseBuffer); + m_lock.lock (); + if (rc <= 0) + { + m_commandSocket.reset (); + return; + } + + m_responseBuffer.coalesce (); +} + +void FtpSession::sendResponse (char const *fmt_, ...) +{ + if (!m_commandSocket) + return; + + auto const buffer = m_responseBuffer.freeArea (); + auto const size = m_responseBuffer.freeSize (); + + va_list ap; + + va_start (ap, fmt_); + Log::log (Log::RESPONSE, fmt_, ap); + va_end (ap); + + va_start (ap, fmt_); + auto const rc = std::vsnprintf (buffer, size, fmt_, ap); + va_end (ap); + + if (rc < 0) + { + Log::error ("vsnprintf: %s\n", std::strerror (errno)); + m_commandSocket.reset (); + return; + } + + if (static_cast (rc) > size) + { + Log::error ("Not enough space for response\n"); + m_commandSocket.reset (); + return; + } + + m_responseBuffer.markUsed (rc); + + // try to write data immediately + assert (m_commandSocket); + m_lock.unlock (); + auto const bytes = + m_commandSocket->write (m_responseBuffer.usedArea (), m_responseBuffer.usedSize ()); + m_lock.lock (); + if (bytes < 0 && errno != EWOULDBLOCK) + m_commandSocket.reset (); + else if (bytes > 0) + { + m_responseBuffer.markFree (bytes); + m_responseBuffer.coalesce (); + } +} + +void FtpSession::sendResponse (std::string_view const response_) +{ + if (!m_commandSocket) + return; + + Log::log (Log::RESPONSE, response_); + + auto const buffer = m_responseBuffer.freeArea (); + auto const size = m_responseBuffer.freeSize (); + + if (response_.size () > size) + { + Log::error ("Not enough space for response\n"); + m_commandSocket.reset (); + return; + } + + std::memcpy (buffer, response_.data (), response_.size ()); + m_responseBuffer.markUsed (response_.size ()); +} + +bool FtpSession::listTransfer () +{ + // check if we sent all available data + if (m_xferBuffer.empty ()) + { + m_xferBuffer.clear (); + + // check xfer dir type + int rc = 226; + if (m_xferDirMode == XferDirMode::STAT) + rc = 213; + + // check if this was for a file + if (!m_dir) + { + // we already sent the file's listing + sendResponse ("%d OK\r\n", rc); + setState (State::COMMAND, true, true); + return false; + } + + // get the next directory entry + m_lock.unlock (); + auto const dent = m_dir.read (); + m_lock.lock (); + if (!dent) + { + // we have exhausted the directory listing + sendResponse ("%d OK\r\n", rc); + setState (State::COMMAND, true, true); + return false; + } + + // I think we are supposed to return entries for . and .. + if (std::strcmp (dent->d_name, ".") == 0 || std::strcmp (dent->d_name, "..") == 0) + return true; + + // check if this was NLST + if (m_xferDirMode == XferDirMode::NLST) + { + // NLST gives the whole path name + auto const path = encodePath (buildPath (m_lwd, dent->d_name)); + if (m_xferBuffer.freeSize () < path.size ()) + { + sendResponse ("501 %s\r\n", std::strerror (ENOMEM)); + setState (State::COMMAND, true, true); + return false; + } + } + else + { + // build the path + auto const fullPath = buildPath (m_lwd, dent->d_name); + struct stat st; + +#ifdef _3DS + // the sdmc directory entry already has the type and size, so no need to do a slow stat + auto const dp = static_cast (m_dir); + auto const magic = *reinterpret_cast (dp->dirData->dirStruct); + + if (magic == SDMC_DIRITER_MAGIC) + { + auto const dir = reinterpret_cast (dp->dirData->dirStruct); + auto const entry = &dir->entry_data[dir->index]; + + if (entry->attributes & FS_ATTRIBUTE_DIRECTORY) + st.st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; + else + st.st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + + if (!(entry->attributes & FS_ATTRIBUTE_READ_ONLY)) + st.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; + + st.st_size = entry->fileSize; + st.st_mtime = 0; + + bool getmtime = true; + if (m_xferDirMode == XferDirMode::MLSD || m_xferDirMode == XferDirMode::MLST) + { + if (!m_mlstModify) + getmtime = false; + } + else if (m_xferDirMode == XferDirMode::NLST) + getmtime = false; + + if (getmtime) + { + std::uint64_t mtime = 0; + auto const rc = sdmc_getmtime (fullPath.c_str (), &mtime); + if (rc != 0) + Log::error ("sdmc_getmtime %s 0x%lx\n", fullPath.c_str (), rc); + else + st.st_mtime = mtime; + } + } + else +#endif + // lstat the entry + if (::lstat (fullPath.c_str (), &st) != 0) + { + sendResponse ("550 %s\r\n", std::strerror (errno)); + setState (State::COMMAND, true, true); + return false; + } + + auto const path = encodePath (dent->d_name); + auto const rc = fillDirent (st, path); + if (rc != 0) + { + sendResponse ("425 %s\r\n", std::strerror (errno)); + setState (State::COMMAND, true, true); + return false; + } + } + } + + // send any pending data + m_lock.unlock (); + auto const rc = m_dataSocket->write (m_xferBuffer.usedArea (), m_xferBuffer.usedSize ()); + m_lock.lock (); + if (rc <= 0) + { + // error sending data + if (rc < 0 && errno == EWOULDBLOCK) + return false; + + sendResponse ("426 Connection broken during transfer\r\n"); + setState (State::COMMAND, true, true); + return false; + } + + // we can try to send more data + m_xferBuffer.markFree (rc); + return true; +} + +bool FtpSession::retrieveTransfer () +{ + if (m_xferBuffer.empty ()) + { + m_xferBuffer.clear (); + + auto const buffer = m_xferBuffer.freeArea (); + auto const size = m_xferBuffer.freeSize (); + + // we have sent all the data, so read some more + m_lock.unlock (); + auto const rc = m_file.read (buffer, size); + m_lock.lock (); + if (rc < 0) + { + // failed to read data + sendResponse ("451 %s\r\n", std::strerror (errno)); + setState (State::COMMAND, true, true); + return false; + } + + if (rc == 0) + { + // reached end of file + sendResponse ("226 OK\r\n"); + setState (State::COMMAND, true, true); + return false; + } + + // we read some data + m_xferBuffer.markUsed (rc); + } + + // send any pending data + m_lock.unlock (); + auto const rc = m_dataSocket->write (m_xferBuffer.usedArea (), m_xferBuffer.usedSize ()); + m_lock.lock (); + if (rc <= 0) + { + // error sending data + if (rc < 0 && errno == EWOULDBLOCK) + return false; + + sendResponse ("426 Connection broken during transfer\r\n"); + setState (State::COMMAND, true, true); + return false; + } + + // we can try to read/send more data + m_filePosition += rc; + m_xferBuffer.markFree (rc); + return true; +} + +bool FtpSession::storeTransfer () +{ + if (m_xferBuffer.empty ()) + { + m_xferBuffer.clear (); + + auto const buffer = m_xferBuffer.freeArea (); + auto const size = m_xferBuffer.freeSize (); + + // we have written all the received data, so try to get some more + m_lock.unlock (); + auto const rc = m_dataSocket->read (buffer, size); + m_lock.lock (); + if (rc < 0) + { + // failed to read data + if (errno == EWOULDBLOCK) + return false; + + sendResponse ("451 %s\r\n", std::strerror (errno)); + setState (State::COMMAND, true, true); + return false; + } + + if (rc == 0) + { + // reached end of file + sendResponse ("226 OK\r\n"); + setState (State::COMMAND, true, true); + return false; + } + + // we received some data + m_xferBuffer.markUsed (rc); + } + + // write any pending data + m_lock.unlock (); + auto const rc = m_file.write (m_xferBuffer.usedArea (), m_xferBuffer.usedSize ()); + m_lock.lock (); + if (rc <= 0) + { + // error writing data + sendResponse ("426 %s\r\n", rc < 0 ? std::strerror (errno) : "Failed to write data"); + setState (State::COMMAND, true, true); + return false; + } + + // we can try to recv/write more data + m_filePosition += rc; + m_xferBuffer.markFree (rc); + return true; +} + +/////////////////////////////////////////////////////////////////////////// +void FtpSession::ABOR (char const *args_) +{ + if (m_state == State::COMMAND) + { + sendResponse ("225 No transfer to abort\r\n"); + return; + } + + // abort the transfer + sendResponse ("225 Aborted\r\n"); + sendResponse ("425 Transfer aborted\r\n"); + setState (State::COMMAND, true, true); +} + +void FtpSession::ALLO (char const *args_) +{ + sendResponse ("202 Superfluous command\r\n"); + setState (State::COMMAND, false, false); +} + +void FtpSession::APPE (char const *args_) +{ + // open the file in append mode + xferFile (args_, XferFileMode::APPE); +} + +void FtpSession::CDUP (char const *args_) +{ + setState (State::COMMAND, false, false); + + if (!changeDir ("..")) + { + sendResponse ("550 %s\r\n", std::strerror (errno)); + return; + } + + sendResponse ("200 OK\r\n"); +} + +void FtpSession::CWD (char const *args_) +{ + setState (State::COMMAND, false, false); + + if (!changeDir (args_)) + { + sendResponse ("550 %s\r\n", std::strerror (errno)); + return; + } + + sendResponse ("200 OK\r\n"); +} + +void FtpSession::DELE (char const *args_) +{ + setState (State::COMMAND, false, false); + + // build the path to remove + auto const path = buildResolvedPath (m_cwd, args_); + if (path.empty ()) + { + sendResponse ("553 %s\r\n", std::strerror (errno)); + return; + } + + // unlink the path + if (::unlink (path.c_str ()) != 0) + { + sendResponse ("550 %s\r\n", std::strerror (errno)); + return; + } + + FtpServer::updateFreeSpace (); + sendResponse ("250 OK\r\n"); +} +void FtpSession::FEAT (char const *args_) +{ + setState (State::COMMAND, false, false); + sendResponse ("211-\r\n" + " MDTM\r\n" + " MLST Type%s;Size%s;Modify%s;Perm%s;UNIX.mode%s;\r\n" + " PASV\r\n" + " SIZE\r\n" + " TVFS\r\n" + " UTF8\r\n" + "\r\n" + "211 End\r\n", + m_mlstType ? "*" : "", + m_mlstSize ? "*" : "", + m_mlstModify ? "*" : "", + m_mlstPerm ? "*" : "", + m_mlstUnixMode ? "*" : ""); +} + +void FtpSession::HELP (char const *args_) +{ + setState (State::COMMAND, false, false); + sendResponse ("214-\r\n" + "The following commands are recognized\r\n" + " ABOR ALLO APPE CDUP CWD DELE FEAT HELP LIST MDTM MKD MLSD MLST MODE\r\n" + " NLST NOOP OPTS PASS PASV PORT PWD QUIT REST RETR RMD RNFR RNTO STAT\r\n" + " STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD XPWD XRMD\r\n" + "214 End\r\n"); +} + +void FtpSession::LIST (char const *args_) +{ + // open the path in LIST mode + xferDir (args_, XferDirMode::LIST, true); +} + +void FtpSession::MDTM (char const *args_) +{ + setState (State::COMMAND, false, false); + sendResponse ("502 Command not implemented\r\n"); +} + +void FtpSession::MKD (char const *args_) +{ + setState (State::COMMAND, false, false); + + // build the path to create + auto const path = buildResolvedPath (m_cwd, args_); + if (path.empty ()) + { + sendResponse ("553 %s\r\n", std::strerror (errno)); + return; + } + + // create the directory + if (::mkdir (path.c_str (), 0755) != 0) + { + sendResponse ("550 %s\r\n", std::strerror (errno)); + return; + } + + FtpServer::updateFreeSpace (); + sendResponse ("250 OK\r\n"); +} + +void FtpSession::MLSD (char const *args_) +{ + // open the path in MLSD mode + xferDir (args_, XferDirMode::MLSD, true); +} + +void FtpSession::MLST (char const *args_) +{ + setState (State::COMMAND, false, false); + + // build the path to list + auto const path = buildResolvedPath (m_cwd, args_); + if (path.empty ()) + { + sendResponse ("501 %s\r\n", std::strerror (errno)); + return; + } + + // stat path + struct stat st; + if (::lstat (path.c_str (), &st) != 0) + { + sendResponse ("550 %s\r\n", std::strerror (errno)); + return; + } + + // encode path + auto const encodedPath = encodePath (path); + + m_xferDirMode = XferDirMode::MLST; + auto const rc = fillDirent (st, path); + if (rc != 0) + { + sendResponse ("550 %s\r\n", std::strerror (rc)); + return; + } + + sendResponse ("250-Status\r\n" + " %s\r\n" + "250 End\r\n", + encodedPath.c_str ()); +} + +void FtpSession::MODE (char const *args_) +{ + setState (State::COMMAND, false, false); + + // we only accept S (stream) mode + if (::strcasecmp (args_, "S") == 0) + { + sendResponse ("200 OK\r\n"); + return; + } + + sendResponse ("504 Unavailable\r\n"); +} + +void FtpSession::NLST (char const *args_) +{ + // open the path in NLST mode + xferDir (args_, XferDirMode::NLST, false); +} + +void FtpSession::NOOP (char const *args_) +{ + sendResponse ("200 OK\r\n"); +} + +void FtpSession::OPTS (char const *args_) +{ + setState (State::COMMAND, false, false); + + // check UTF8 options + if (::strcasecmp (args_, "UTF8") == 0 || ::strcasecmp (args_, "UTF8 ON") == 0 || + ::strcasecmp (args_, "UTF8 NLST") == 0) + { + sendResponse ("200 OK\r\n"); + return; + } + + // check MLST options + if (::strncasecmp (args_, "MLST ", 5) == 0) + { + m_mlstType = false; + m_mlstSize = false; + m_mlstModify = false; + m_mlstPerm = false; + m_mlstUnixMode = false; + + auto p = args_ + 5; + while (*p) + { + auto const match = [] (auto const &name_, auto const &arg_) { + return ::strncasecmp (name_, arg_, std::strlen (name_)) == 0; + }; + + if (match ("Type;", p)) + m_mlstType = true; + else if (match ("Size;", p)) + m_mlstSize = true; + else if (match ("Modify;", p)) + m_mlstModify = true; + else if (match ("Perm;", p)) + m_mlstPerm = true; + else if (match ("UNIX.mode;", p)) + m_mlstUnixMode = true; + + p = std::strchr (p, ';'); + if (!p) + break; + + ++p; + } + + sendResponse ("200 MLST OPTS%s%s%s%s%s%s\r\n", + m_mlstType || m_mlstSize || m_mlstModify || m_mlstPerm || m_mlstUnixMode ? " " : "", + m_mlstType ? "Type;" : "", + m_mlstSize ? "Size;" : "", + m_mlstModify ? "Modify;" : "", + m_mlstPerm ? "Perm;" : "", + m_mlstUnixMode ? "UNIX.mode;" : ""); + return; + } + + sendResponse ("504 %s\r\n", std::strerror (EINVAL)); +} + +void FtpSession::PASS (char const *args_) +{ + setState (State::COMMAND, false, false); + sendResponse ("230 OK\r\n"); +} + +void FtpSession::PASV (char const *args_) +{ + // reset state + setState (State::COMMAND, true, true); + m_pasv = false; + m_port = false; + + // create a socket to listen on + m_pasvSocket = Socket::create (); + if (!m_pasvSocket) + { + sendResponse ("451 Failed to create listening socket\r\n"); + return; + } + + // set the socket options + m_pasvSocket->setRecvBufferSize (SOCK_BUFFERSIZE); + m_pasvSocket->setSendBufferSize (SOCK_BUFFERSIZE); + + // create an address to bind + struct sockaddr_in addr = m_commandSocket->sockName (); +#ifdef _3DS + static std::uint16_t ephemeralPort = 5001; + if (ephemeralPort > 10000) + ephemeralPort = 5001; + addr.sin_port = htons (ephemeralPort++); +#else + addr.sin_port = htons (0); +#endif + + // bind to the address + if (!m_pasvSocket->bind (addr)) + { + m_pasvSocket.reset (); + sendResponse ("451 Failed to bind address\r\n"); + return; + } + + // listen on the socket + if (!m_pasvSocket->listen (1)) + { + m_pasvSocket.reset (); + sendResponse ("451 Failed to listen on socket\r\n"); + return; + } + + // we are now listening on the socket + auto const &sockName = m_pasvSocket->sockName (); + std::string name = sockName.name (); + auto const port = sockName.port (); + Log::info ("Listening on [%s]:%u\n", name.c_str (), port); + + // send the address in the ftp format + for (auto &c : name) + { + if (c == '.') + c = ','; + } + + m_pasv = true; + sendResponse ("227 %s,%u,%u\r\n", name.c_str (), port >> 8, port & 0xFF); +} + +void FtpSession::PORT (char const *args_) +{ + // reset state + setState (State::COMMAND, true, true); + m_pasv = false; + m_port = false; + + std::string addrString = args_; + + // convert a,b,c,d,e,f with a.b.c.d\0e.f + unsigned commas = 0; + char const *portString = nullptr; + for (auto &p : addrString) + { + if (p == ',') + { + if (commas++ != 3) + p = '.'; + else + { + p = '\0'; + portString = &p + 1; + } + } + } + + // check for the expected number of fields + if (commas != 5) + { + sendResponse ("501 %s\r\n", std::strerror (EINVAL)); + return; + } + + struct sockaddr_in addr = {}; + + // parse the address + if (!inet_aton (addrString.data (), &addr.sin_addr)) + { + sendResponse ("501 %s\r\n", std::strerror (EINVAL)); + return; + } + + // parse the port + int val = 0; + std::uint16_t port = 0; + for (auto p = portString; *p; ++p) + { + if (!std::isdigit (*p)) + { + if (p == portString || *p != '.' || val > 0xFF) + { + sendResponse ("501 %s\r\n", std::strerror (EINVAL)); + return; + } + + port <<= 8; + port += val; + val = 0; + } + else + { + val *= 10; + val += *p - '0'; + } + } + + if (val > 0xFF || port > 0xFF) + { + sendResponse ("501 %s\r\n", std::strerror (EINVAL)); + return; + } + + port <<= 8; + port += val; + + addr.sin_family = AF_INET; + addr.sin_port = htons (port); + + // we are ready to connect to the client + m_portAddr = addr; + m_port = true; + sendResponse ("200 OK\r\n"); +} + +void FtpSession::PWD (char const *args_) +{ + setState (State::COMMAND, false, false); + + auto const path = encodePath (m_cwd); + + std::string response = "257 \""; + response += encodePath (m_cwd, true); + response += "\"\r\n"; + + sendResponse (response); +} + +void FtpSession::QUIT (char const *args_) +{ + sendResponse ("221 Disconnecting\r\n"); + m_commandSocket.reset (); +} + +void FtpSession::REST (char const *args_) +{ + setState (State::COMMAND, false, false); + + // parse the offset + std::uint64_t pos = 0; + for (auto p = args_; *p; ++p) + { + if (!std::isdigit (*p) || UINT64_MAX / 10 < pos) + { + sendResponse ("504 %s\r\n", std::strerror (errno)); + return; + } + + pos *= 10; + + if (UINT64_MAX - (*p - '0') < pos) + { + sendResponse ("504 %s\r\n", std::strerror (errno)); + return; + } + + pos += (*p - '0'); + } + + // set the restart offset + m_restartPosition = pos; + sendResponse ("200 OK\r\n"); +} + +void FtpSession::RETR (char const *args_) +{ + // open the file to retrieve + xferFile (args_, XferFileMode::RETR); +} + +void FtpSession::RMD (char const *args_) +{ + setState (State::COMMAND, false, false); + + // build the path to remove + auto const path = buildResolvedPath (m_cwd, args_); + if (path.empty ()) + { + sendResponse ("553 %s\r\n", std::strerror (errno)); + return; + } + + // remove the directory + if (::rmdir (path.c_str ()) != 0) + { + sendResponse ("550 %s\r\n", std::strerror (errno)); + return; + } + + FtpServer::updateFreeSpace (); + sendResponse ("250 OK\r\n"); +} + +void FtpSession::RNFR (char const *args_) +{ + setState (State::COMMAND, false, false); + + // build the path to rename from + auto const path = buildResolvedPath (m_cwd, args_); + if (path.empty ()) + { + sendResponse ("553 %s\r\n", std::strerror (errno)); + return; + } + + // make sure the path exists + struct stat st; + if (::lstat (path.c_str (), &st) != 0) + { + sendResponse ("450 %s\r\n", std::strerror (errno)); + return; + } + + // we are ready for RNTO + m_rename = path; + sendResponse ("350 OK\r\n"); +} + +void FtpSession::RNTO (char const *args_) +{ + setState (State::COMMAND, false, false); + + // make sure the previous command was RNFR + if (m_rename.empty ()) + { + sendResponse ("503 Bad sequence of commands\r\n"); + return; + } + + // build the path to rename to + auto const path = buildResolvedPath (m_cwd, args_); + if (path.empty ()) + { + m_rename.clear (); + sendResponse ("554 %s\r\n", std::strerror (errno)); + return; + } + + // rename the file + if (::rename (m_rename.c_str (), path.c_str ()) != 0) + { + m_rename.clear (); + sendResponse ("550 %s\r\n", std::strerror (errno)); + return; + } + + // clear the rename state + m_rename.clear (); + + FtpServer::updateFreeSpace (); + sendResponse ("250 OK\r\n"); +} + +void FtpSession::SIZE (char const *args_) +{ + setState (State::COMMAND, false, false); + + // build the path to stat + auto const path = buildResolvedPath (m_cwd, args_); + if (path.empty ()) + { + sendResponse ("553 %s\r\n", std::strerror (errno)); + return; + } + + // stat the path + struct stat st; + if (::stat (path.c_str (), &st) != 0) + { + sendResponse ("550 %s\r\n", std::strerror (errno)); + return; + } + + if (!S_ISREG (st.st_mode)) + { + sendResponse ("550 Not a file\r\n"); + return; + } + + sendResponse ("213 %" PRIu64 "\r\n", static_cast (st.st_size)); +} + +void FtpSession::STAT (char const *args_) +{ + if (m_state == State::DATA_CONNECT) + { + sendResponse ("211-FTP server status\r\n" + " Waitin for data connection\r\n" + "211 End\r\n"); + return; + } + + if (m_state == State::DATA_TRANSFER) + { + sendResponse ("211-FTP server status\r\n" + " Transferred %" PRIu64 " bytes\r\n" + "211 End\r\n", + m_filePosition); + return; + } + + if (std::strlen (args_) == 0) + { + // TODO keep track of start time + auto const uptime = + std::chrono::system_clock::to_time_t (std::chrono::system_clock::now ()) - + FtpServer::startTime (); + unsigned const hours = uptime / 3600; + unsigned const minutes = (uptime / 60) % 60; + unsigned const seconds = uptime % 60; + + sendResponse ("211-FTP server status\r\n" + " Uptime: %02u:%02u:%02u\r\n" + "211 End\r\n", + hours, + minutes, + seconds); + return; + } + + xferDir (args_, XferDirMode::STAT, false); +} + +void FtpSession::STOR (char const *args_) +{ + // open the file to store + xferFile (args_, XferFileMode::STOR); +} + +void FtpSession::STOU (char const *args_) +{ + setState (State::COMMAND, false, false); + sendResponse ("502 Command not implemented\r\n"); +} + +void FtpSession::STRU (char const *args_) +{ + setState (State::COMMAND, false, false); + + // we only support F (no structure) mode + if (::strcasecmp (args_, "F") == 0) + { + sendResponse ("200 OK\r\n"); + return; + } + + sendResponse ("504 Unavailable\r\n"); +} + +void FtpSession::SYST (char const *args_) +{ + setState (State::COMMAND, false, false); + sendResponse ("215 UNIX Type: L8\r\n"); +} + +void FtpSession::TYPE (char const *args_) +{ + setState (State::COMMAND, false, false); + + // we always transfer in binary mode + sendResponse ("200 OK\r\n"); +} + +void FtpSession::USER (char const *args_) +{ + setState (State::COMMAND, false, false); + sendResponse ("230 OK\r\n"); +} + +// clang-format off +std::vector> const + FtpSession::handlers = +{ + {"ABOR", &FtpSession::ABOR}, + {"ALLO", &FtpSession::ALLO}, + {"APPE", &FtpSession::APPE}, + {"CDUP", &FtpSession::CDUP}, + {"CWD", &FtpSession::CWD}, + {"DELE", &FtpSession::DELE}, + {"FEAT", &FtpSession::FEAT}, + {"HELP", &FtpSession::HELP}, + {"LIST", &FtpSession::LIST}, + {"MDTM", &FtpSession::MDTM}, + {"MKD", &FtpSession::MKD}, + {"MLSD", &FtpSession::MLSD}, + {"MLST", &FtpSession::MLST}, + {"MODE", &FtpSession::MODE}, + {"NLST", &FtpSession::NLST}, + {"NOOP", &FtpSession::NOOP}, + {"OPTS", &FtpSession::OPTS}, + {"PASS", &FtpSession::PASS}, + {"PASV", &FtpSession::PASV}, + {"PORT", &FtpSession::PORT}, + {"PWD", &FtpSession::PWD}, + {"QUIT", &FtpSession::QUIT}, + {"REST", &FtpSession::REST}, + {"RETR", &FtpSession::RETR}, + {"RMD", &FtpSession::RMD}, + {"RNFR", &FtpSession::RNFR}, + {"RNTO", &FtpSession::RNTO}, + {"SIZE", &FtpSession::SIZE}, + {"STAT", &FtpSession::STAT}, + {"STOR", &FtpSession::STOR}, + {"STOU", &FtpSession::STOU}, + {"STRU", &FtpSession::STRU}, + {"SYST", &FtpSession::SYST}, + {"TYPE", &FtpSession::TYPE}, + {"USER", &FtpSession::USER}, + {"XCUP", &FtpSession::CDUP}, + {"XCWD", &FtpSession::CWD}, + {"XMKD", &FtpSession::MKD}, + {"XPWD", &FtpSession::PWD}, + {"XRMD", &FtpSession::RMD}, +}; +// clang-format on diff --git a/source/imgui/imgui.cpp b/source/imgui/imgui.cpp new file mode 100644 index 0000000..4b71f2a --- /dev/null +++ b/source/imgui/imgui.cpp @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui/releases/tag/v1.75" diff --git a/source/imgui/imgui_draw.cpp b/source/imgui/imgui_draw.cpp new file mode 100644 index 0000000..4b71f2a --- /dev/null +++ b/source/imgui/imgui_draw.cpp @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui/releases/tag/v1.75" diff --git a/source/imgui/imgui_internal.h b/source/imgui/imgui_internal.h new file mode 100644 index 0000000..4b71f2a --- /dev/null +++ b/source/imgui/imgui_internal.h @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui/releases/tag/v1.75" diff --git a/source/imgui/imgui_widgets.cpp b/source/imgui/imgui_widgets.cpp new file mode 100644 index 0000000..4b71f2a --- /dev/null +++ b/source/imgui/imgui_widgets.cpp @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui/releases/tag/v1.75" diff --git a/source/imgui/imstb_rectpack.h b/source/imgui/imstb_rectpack.h new file mode 100644 index 0000000..4b71f2a --- /dev/null +++ b/source/imgui/imstb_rectpack.h @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui/releases/tag/v1.75" diff --git a/source/imgui/imstb_textedit.h b/source/imgui/imstb_textedit.h new file mode 100644 index 0000000..4b71f2a --- /dev/null +++ b/source/imgui/imstb_textedit.h @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui/releases/tag/v1.75" diff --git a/source/imgui/imstb_truetype.h b/source/imgui/imstb_truetype.h new file mode 100644 index 0000000..4b71f2a --- /dev/null +++ b/source/imgui/imstb_truetype.h @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui/releases/tag/v1.75" diff --git a/source/ioBuffer.cpp b/source/ioBuffer.cpp new file mode 100644 index 0000000..2eb42d0 --- /dev/null +++ b/source/ioBuffer.cpp @@ -0,0 +1,107 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "ioBuffer.h" + +#include +#include + +/////////////////////////////////////////////////////////////////////////// +IOBuffer::~IOBuffer () = default; + +IOBuffer::IOBuffer (std::size_t const size_) + : m_buffer (std::make_unique (size_)), m_size (size_) +{ +} + +char *IOBuffer::freeArea () const +{ + assert (m_end < m_size); + return &m_buffer[m_end]; +} + +std::size_t IOBuffer::freeSize () const +{ + assert (m_size >= m_end); + return m_size - m_end; +} + +void IOBuffer::markFree (std::size_t size_) +{ + assert (m_end >= m_start); + assert (m_end - m_start >= size_); + m_start += size_; + + // reset back to beginning + if (m_start == m_end) + { + m_start = 0; + m_end = 0; + } +} + +char *IOBuffer::usedArea () const +{ + assert (m_start < m_size); + return &m_buffer[m_start]; +} + +std::size_t IOBuffer::usedSize () const +{ + assert (m_end >= m_start); + return m_end - m_start; +} + +void IOBuffer::markUsed (std::size_t size_) +{ + assert (m_size >= m_end); + assert (m_size - m_end >= size_); + m_end += size_; +} + +bool IOBuffer::empty () const +{ + assert (m_end >= m_start); + return (m_end - m_start) == 0; +} + +std::size_t IOBuffer::capacity () const +{ + return m_size; +} + +void IOBuffer::clear () +{ + m_start = 0; + m_end = 0; +} + +void IOBuffer::coalesce () +{ + assert (m_size >= m_end); + assert (m_end >= m_start); + + auto const size = m_end - m_start; + if (size != 0) + std::memmove (&m_buffer[0], &m_buffer[m_start], size); + + m_end -= m_start; + m_start = 0; +} diff --git a/source/log.cpp b/source/log.cpp new file mode 100644 index 0000000..5b83cfb --- /dev/null +++ b/source/log.cpp @@ -0,0 +1,196 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "log.h" + +#include "imgui.h" + +#include + +namespace +{ +#ifdef _3DS +constexpr auto MAX_LOGS = 250; +#else +constexpr auto MAX_LOGS = 10000; +#endif +thread_local WeakLog s_log; + +static char const *const s_prefix[] = { + [Log::DEBUG] = "[DEBUG]", + [Log::INFO] = "[INFO]", + [Log::ERROR] = "[ERROR]", + [Log::COMMAND] = "[COMMAND]", + [Log::RESPONSE] = "[RESPONSE]", +}; +} + +/////////////////////////////////////////////////////////////////////////// +Log::~Log () = default; + +Log::Log () = default; + +void Log::draw () +{ + auto const lock = std::scoped_lock (m_lock); + + if (m_messages.size () > MAX_LOGS) + { + auto const begin = std::begin (m_messages); + auto const end = std::next (begin, m_messages.size () - MAX_LOGS); + m_messages.erase (begin, end); + } + + static ImVec4 const s_colors[] = { + [Log::DEBUG] = ImVec4 (1.0f, 1.0f, 0.4f, 1.0f), + [Log::INFO] = ImVec4 (1.0f, 1.0f, 1.0f, 1.0f), + [Log::ERROR] = ImVec4 (1.0f, 0.4f, 0.4f, 1.0f), + [Log::COMMAND] = ImVec4 (0.4f, 1.0f, 0.4f, 1.0f), + [Log::RESPONSE] = ImVec4 (0.4f, 1.0f, 1.0f, 1.0f), + }; + + static char const *const s_prefix[] = { + [Log::DEBUG] = "[DEBUG]", + [Log::INFO] = "[INFO]", + [Log::ERROR] = "[ERROR]", + [Log::COMMAND] = "[COMMAND]", + [Log::RESPONSE] = "[RESPONSE]", + }; + + for (auto const &message : m_messages) + { + ImGui::PushStyleColor (ImGuiCol_Text, s_colors[message.level]); + ImGui::TextUnformatted (s_prefix[message.level]); + ImGui::SameLine (); + ImGui::TextUnformatted (message.message.c_str ()); + ImGui::PopStyleColor (); + } + + // auto scroll if scroll bar is at end + if (ImGui::GetScrollY () >= ImGui::GetScrollMaxY ()) + ImGui::SetScrollHereY (1.0f); +} + +SharedLog Log::create () +{ + return std::shared_ptr (new Log ()); +} + +void Log::bind (SharedLog log_) +{ + s_log = log_; +} + +void Log::debug (char const *const fmt_, ...) +{ +#ifndef NDEBUG + va_list ap; + + va_start (ap, fmt_); + log (DEBUG, fmt_, ap); + va_end (ap); +#endif +} + +void Log::info (char const *const fmt_, ...) +{ + va_list ap; + + va_start (ap, fmt_); + log (INFO, fmt_, ap); + va_end (ap); +} + +void Log::error (char const *const fmt_, ...) +{ + va_list ap; + + va_start (ap, fmt_); + log (ERROR, fmt_, ap); + va_end (ap); +} + +void Log::command (char const *const fmt_, ...) +{ + va_list ap; + + va_start (ap, fmt_); + log (COMMAND, fmt_, ap); + va_end (ap); +} + +void Log::response (char const *const fmt_, ...) +{ + va_list ap; + + va_start (ap, fmt_); + log (RESPONSE, fmt_, ap); + va_end (ap); +} + +void Log::log (Level const level_, char const *const fmt_, va_list ap_) +{ +#ifdef NDEBUG + if (level_ == DEBUG) + return; +#endif + + auto log = s_log.lock (); + if (!log) + return; + + thread_local static char buffer[1024]; + + std::vsnprintf (buffer, sizeof (buffer), fmt_, ap_); + buffer[sizeof (buffer) - 1] = '\0'; + + auto const lock = std::scoped_lock (log->m_lock); +#ifndef NDEBUG + std::fprintf (stderr, "%s", s_prefix[level_]); + std::fputs (buffer, stderr); +#endif + log->m_messages.emplace_back (level_, buffer); +} + +void Log::log (Level const level_, std::string_view const message_) +{ +#ifdef NDEBUG + if (level_ == DEBUG) + return; +#endif + + auto log = s_log.lock (); + if (!log) + return; + + auto msg = std::string (message_); + for (auto &c : msg) + { + if (c == '\0') + c = '?'; + } + + auto const lock = std::scoped_lock (log->m_lock); +#ifndef NDEBUG + std::fprintf (stderr, "%s", s_prefix[level_]); + std::fwrite (msg.data (), 1, msg.size (), stderr); +#endif + log->m_messages.emplace_back (level_, msg); +} diff --git a/source/main.c b/source/main.c deleted file mode 100644 index e3cfe4f..0000000 --- a/source/main.c +++ /dev/null @@ -1,182 +0,0 @@ - -#include -#include -#include -#include -#include -#ifdef _3DS -#include <3ds.h> -#elif defined(__SWITCH__) -#include -#endif -#include "console.h" -#include "ftp.h" - -/*! looping mechanism - * - * @param[in] callback function to call during each iteration - * - * @returns loop status - */ -static loop_status_t -loop(loop_status_t (*callback)(void)) -{ - loop_status_t status = LOOP_CONTINUE; - -#ifdef _3DS - while(aptMainLoop()) - { - status = callback(); - console_render(); - if(status != LOOP_CONTINUE) - return status; - } - return LOOP_EXIT; -#elif defined(__SWITCH__) - while(appletMainLoop()) - { - console_render(); - status = callback(); - if(status != LOOP_CONTINUE) - return status; - } - return LOOP_EXIT; -#else - while(status == LOOP_CONTINUE) - status = callback(); - return status; -#endif -} - -#ifdef _3DS -/*! wait until the B button is pressed - * - * @returns loop status - */ -static loop_status_t -wait_for_b(void) -{ - /* update button state */ - hidScanInput(); - - /* check if B was pressed */ - if(hidKeysDown() & KEY_B) - return LOOP_EXIT; - - /* B was not pressed */ - return LOOP_CONTINUE; -} -#elif defined(__SWITCH__) -/*! wait until the B button is pressed - * - * @returns loop status - */ -static loop_status_t -wait_for_b(void) -{ - /* update button state */ - hidScanInput(); - - /* check if B was pressed */ - if(hidKeysDown(CONTROLLER_P1_AUTO) & KEY_B) - return LOOP_EXIT; - - /* B was not pressed */ - return LOOP_CONTINUE; -} -#endif - -/*! entry point - * - * @param[in] argc unused - * @param[in] argv unused - * - * returns exit status - */ -int -main(int argc, - char *argv[]) -{ - loop_status_t status = LOOP_RESTART; - -#ifdef _3DS - /* initialize needed 3DS services */ - acInit(); - gfxInitDefault(); - gfxSet3D(false); - sdmcWriteSafe(false); -#elif defined(__SWITCH__) - /* initialize needed Switch services */ - nifmInitialize(NifmServiceType_User); -#endif - - /* initialize console subsystem */ - console_init(); - -#ifdef ENABLE_LOGGING - /* open log file */ -#ifdef _3DS - FILE *fp = freopen("/ftpd.log", "wb", stderr); -#else - FILE *fp = freopen("ftpd.log", "wb", stderr); -#endif - if(fp == NULL) - { - console_print(RED "freopen: 0x%08X\n" RESET, errno); - goto log_fail; - } - - /* truncate log file */ - if(ftruncate(fileno(fp), 0) != 0) - { - console_print(RED "ftruncate: 0x%08X\n" RESET, errno); - goto log_fail; - } -#endif - - console_set_status("\n" GREEN STATUS_STRING -#ifdef ENABLE_LOGGING - " DEBUG" -#endif - RESET); - - while(status == LOOP_RESTART) - { - /* initialize ftp subsystem */ - if(ftp_init() == 0) - { - /* ftp loop */ - status = loop(ftp_loop); - - /* done with ftp */ - ftp_exit(); - } - else - status = LOOP_EXIT; - } - -#if defined(_3DS) || defined(__SWITCH__) - console_print("Press B to exit\n"); -#endif - -#ifdef ENABLE_LOGGING -log_fail: - if(fclose(stderr) != 0) - console_print(RED "fclose(%d): 0x%08X\n" RESET, fileno(stderr), errno); -#endif - -#ifdef _3DS - loop(wait_for_b); - - /* deinitialize 3DS services */ - gfxExit(); - acExit(); -#elif defined(__SWITCH__) - loop(wait_for_b); - - /* deinitialize Switch services */ - consoleExit(NULL); - nifmExit(); -#endif - return 0; -} diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..cfc6703 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,52 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "platform.h" + +#include "ftpServer.h" + +#include "imgui.h" + +#include +#include + +int main (int argc_, char *argv_[]) +{ + if (!platform::init ()) + return EXIT_FAILURE; + + auto &style = ImGui::GetStyle (); + style.WindowRounding = 0.0f; + +#ifdef _3DS + style.Colors[ImGuiCol_WindowBg].w = 0.5f; +#endif + + auto server = FtpServer::create (5000); + + while (platform::loop ()) + { + server->draw (); + + platform::render (); + } + + platform::exit (); +} diff --git a/source/nx/imgui_deko3d.cpp b/source/nx/imgui_deko3d.cpp new file mode 100644 index 0000000..4e86617 --- /dev/null +++ b/source/nx/imgui_deko3d.cpp @@ -0,0 +1,681 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "imgui_deko3d.h" + +#include "imgui.h" + +#include "fs.h" +#include "log.h" + +#include +#include + +#include + +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES +#define GLM_FORCE_INTRINSICS +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace +{ +constexpr auto LOGO_WIDTH = 500; +constexpr auto LOGO_HEIGHT = 493; + +constexpr auto FB_NUM = 2u; + +constexpr auto CMDBUF_SIZE = 1024 * 1024; +constexpr auto DATABUF_SIZE = 1024 * 1024; +constexpr auto INDEXBUF_SIZE = 1024 * 1024; +constexpr auto IMAGEBUF_SIZE = 16 * 1024 * 1024; + +struct VertUBO +{ + glm::mat4 projMtx; +}; + +struct FragUBO +{ + std::uint32_t font; +}; + +constexpr std::array VertexAttribState = { + // clang-format off + DkVtxAttribState{0, 0, offsetof (ImDrawVert, pos), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0}, + DkVtxAttribState{0, 0, offsetof (ImDrawVert, uv), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0}, + DkVtxAttribState{0, 0, offsetof (ImDrawVert, col), DkVtxAttribSize_4x8, DkVtxAttribType_Unorm, 0}, + // clang-format on +}; + +constexpr std::array VertexBufferState = { + DkVtxBufferState{sizeof (ImDrawVert), 0}, +}; + +dk::UniqueDevice s_device; + +dk::UniqueMemBlock s_depthMemBlock; +dk::Image s_depthBuffer; + +dk::UniqueMemBlock s_fbMemBlock; +dk::Image s_frameBuffers[FB_NUM]; + +dk::Image s_fontTexture; +dk::Image s_logoTexture; + +dk::UniqueSwapchain s_swapchain; + +dk::UniqueMemBlock s_codeMemBlock; +dk::Shader s_shaders[2]; + +dk::UniqueMemBlock s_uboMemBlock; + +dk::UniqueMemBlock s_vtxMemBlock[FB_NUM]; +dk::UniqueMemBlock s_idxMemBlock[FB_NUM]; +dk::UniqueMemBlock s_cmdMemBlock[FB_NUM]; +dk::UniqueCmdBuf s_cmdBuf[FB_NUM]; + +dk::UniqueMemBlock s_imageMemBlock; +dk::UniqueMemBlock s_descriptorMemBlock; + +dk::UniqueQueue s_queue; + +constexpr auto MAX_SAMPLERS = 1; +constexpr auto MAX_IMAGES = 2; +dk::SamplerDescriptor *s_samplerDescriptors = nullptr; +dk::ImageDescriptor *s_imageDescriptors = nullptr; + +std::uintptr_t s_boundDescriptor = 0; + +unsigned s_width = 0; +unsigned s_height = 0; + +template +constexpr inline std::uint32_t align (T const &size_, U const &align_) +{ + return static_cast (size_ + align_ - 1) & ~(align_ - 1); +} + +void rebuildSwapchain (unsigned const width_, unsigned const height_) +{ + s_swapchain = nullptr; + + dk::ImageLayout depthLayout; + dk::ImageLayoutMaker{s_device} + .setFlags (DkImageFlags_UsageRender | DkImageFlags_HwCompression) + .setFormat (DkImageFormat_Z24S8) + .setDimensions (width_, height_) + .initialize (depthLayout); + + auto const depthAlign = depthLayout.getAlignment (); + auto const depthSize = depthLayout.getSize (); + + if (!s_depthMemBlock) + { + s_depthMemBlock = dk::MemBlockMaker{s_device, + align (depthSize, std::max (depthAlign, DK_MEMBLOCK_ALIGNMENT))} + .setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) + .create (); + } + + s_depthBuffer.initialize (depthLayout, s_depthMemBlock, 0); + + dk::ImageLayout fbLayout; + dk::ImageLayoutMaker{s_device} + .setFlags ( + DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression) + .setFormat (DkImageFormat_RGBA8_Unorm) + .setDimensions (width_, height_) + .initialize (fbLayout); + + auto const fbAlign = fbLayout.getAlignment (); + auto const fbSize = fbLayout.getSize (); + + if (!s_fbMemBlock) + { + s_fbMemBlock = dk::MemBlockMaker{s_device, + align (FB_NUM * fbSize, std::max (fbAlign, DK_MEMBLOCK_ALIGNMENT))} + .setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) + .create (); + } + + std::array swapchainImages; + for (unsigned i = 0; i < FB_NUM; ++i) + { + swapchainImages[i] = &s_frameBuffers[i]; + s_frameBuffers[i].initialize (fbLayout, s_fbMemBlock, i * fbSize); + } + + s_swapchain = dk::SwapchainMaker{s_device, nwindowGetDefault (), swapchainImages}.create (); +} + +void loadShaders () +{ + struct ShaderFile + { + ShaderFile (dk::Shader &shader_, char const *const path_) + : shader (shader_), path (path_), size (getSize (path_)) + { + } + + static std::size_t getSize (char const *const path_) + { + struct stat st; + auto const rc = stat (path_, &st); + if (rc != 0) + { + std::fprintf (stderr, "stat(%s): %s\n", path_, std::strerror (errno)); + std::abort (); + } + + return st.st_size; + } + + dk::Shader &shader; + char const *const path; + std::size_t const size; + }; + + auto shaderFiles = {ShaderFile{s_shaders[0], "romfs:/shaders/imgui_vsh.dksh"}, + ShaderFile{s_shaders[1], "romfs:/shaders/imgui_fsh.dksh"}}; + + auto const codeSize = std::accumulate (std::begin (shaderFiles), + std::end (shaderFiles), + DK_SHADER_CODE_UNUSABLE_SIZE, + [] (auto const sum_, auto const &file_) { + return sum_ + align (file_.size, DK_SHADER_CODE_ALIGNMENT); + }); + + s_codeMemBlock = dk::MemBlockMaker{s_device, align (codeSize, DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | + DkMemBlockFlags_Code) + .create (); + + auto const addr = static_cast (s_codeMemBlock.getCpuAddr ()); + std::size_t offset = 0; + + for (auto &file : shaderFiles) + { + std::uint32_t const codeOffset = offset; + + fs::File fp; + if (!fp.open (file.path)) + { + std::fprintf (stderr, "open(%s): %s\n", file.path, std::strerror (errno)); + std::abort (); + } + + if (!fp.readAll (&addr[offset], file.size)) + { + std::fprintf (stderr, "read(%s): %s\n", file.path, std::strerror (errno)); + std::abort (); + } + + dk::ShaderMaker{s_codeMemBlock, codeOffset}.initialize (file.shader); + + offset = align (offset + file.size, DK_SHADER_CODE_ALIGNMENT); + } +} + +DkCmdList setupRenderState (int const slot_, + ImDrawData *const drawData_, + unsigned const width_, + unsigned const height_) +{ + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from drawData_->DisplayPos (top left) to + // drawData_->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single + // viewport apps. + auto const L = drawData_->DisplayPos.x; + auto const R = drawData_->DisplayPos.x + drawData_->DisplaySize.x; + auto const T = drawData_->DisplayPos.y; + auto const B = drawData_->DisplayPos.y + drawData_->DisplaySize.y; + + VertUBO vertUBO; + vertUBO.projMtx = glm::orthoRH_ZO (L, R, B, T, -1.0f, 1.0f); + + s_cmdBuf[slot_].setViewports (0, DkViewport{0.0f, 0.0f, width_, height_}); + s_cmdBuf[slot_].bindShaders (DkStageFlag_GraphicsMask, {&s_shaders[0], &s_shaders[1]}); + s_cmdBuf[slot_].bindUniformBuffer (DkStage_Vertex, + 0, + s_uboMemBlock.getGpuAddr (), + align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT)); + s_cmdBuf[slot_].pushConstants (s_uboMemBlock.getGpuAddr (), + align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT), + 0, + sizeof (VertUBO), + &vertUBO); + s_cmdBuf[slot_].bindUniformBuffer (DkStage_Fragment, + 0, + s_uboMemBlock.getGpuAddr () + align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT), + align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT)); + s_cmdBuf[slot_].bindRasterizerState (dk::RasterizerState{}.setCullMode (DkFace_None)); + s_cmdBuf[slot_].bindColorState (dk::ColorState{}.setBlendEnable (0, true)); + s_cmdBuf[slot_].bindColorWriteState (dk::ColorWriteState{}); + s_cmdBuf[slot_].bindDepthStencilState (dk::DepthStencilState{}.setDepthTestEnable (false)); + s_cmdBuf[slot_].bindBlendStates (0, + dk::BlendState{}.setFactors (DkBlendFactor_SrcAlpha, + DkBlendFactor_InvSrcAlpha, + DkBlendFactor_InvSrcAlpha, + DkBlendFactor_Zero)); + s_cmdBuf[slot_].bindVtxAttribState (VertexAttribState); + s_cmdBuf[slot_].bindVtxBufferState (VertexBufferState); + + return s_cmdBuf[slot_].finishList (); +} +} + +void imgui::deko3d::init () +{ + // Setup back-end capabilities flags + ImGuiIO &io = ImGui::GetIO (); + + io.BackendRendererName = "deko3d"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; +} + +void imgui::deko3d::exit () +{ + s_queue.waitIdle (); + + s_queue = nullptr; + s_descriptorMemBlock = nullptr; + s_imageMemBlock = nullptr; + + for (unsigned i = 0; i < FB_NUM; ++i) + { + s_cmdBuf[i] = nullptr; + s_cmdMemBlock[i] = nullptr; + s_idxMemBlock[i] = nullptr; + s_vtxMemBlock[i] = nullptr; + } + + s_uboMemBlock = nullptr; + s_codeMemBlock = nullptr; + s_swapchain = nullptr; + s_fbMemBlock = nullptr; + s_depthMemBlock = nullptr; + s_device = nullptr; +} + +void imgui::deko3d::newFrame () +{ + if (s_device) + return; + + s_device = dk::DeviceMaker{}.create (); + + rebuildSwapchain (1920, 1080); + + loadShaders (); + + s_uboMemBlock = dk::MemBlockMaker{s_device, + align (align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT) + + align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT), + DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) + .create (); + + for (std::size_t i = 0; i < FB_NUM; ++i) + { + s_vtxMemBlock[i] = dk::MemBlockMaker{s_device, align (DATABUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) + .create (); + + s_idxMemBlock[i] = dk::MemBlockMaker{s_device, align (INDEXBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) + .create (); + + s_cmdMemBlock[i] = dk::MemBlockMaker{s_device, align (CMDBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) + .create (); + + s_cmdBuf[i] = dk::CmdBufMaker{s_device}.create (); + + s_cmdBuf[i].addMemory (s_cmdMemBlock[i], 0, s_cmdMemBlock[i].getSize ()); + } + + s_queue = dk::QueueMaker{s_device}.setFlags (DkQueueFlags_Graphics).create (); + + s_imageMemBlock = dk::MemBlockMaker{s_device, align (IMAGEBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) + .create (); + + // Build texture atlas + ImGuiIO &io = ImGui::GetIO (); + io.Fonts->SetTexID (nullptr); + unsigned char *pixels; + int width; + int height; + io.Fonts->GetTexDataAsAlpha8 (&pixels, &width, &height); + + dk::UniqueMemBlock memBlock = + dk::MemBlockMaker{s_device, align (width * height, DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) + .create (); + std::memcpy (memBlock.getCpuAddr (), pixels, width * height); + + static_assert (sizeof (dk::ImageDescriptor) == DK_IMAGE_DESCRIPTOR_ALIGNMENT); + static_assert (sizeof (dk::SamplerDescriptor) == DK_SAMPLER_DESCRIPTOR_ALIGNMENT); + static_assert (DK_IMAGE_DESCRIPTOR_ALIGNMENT == DK_SAMPLER_DESCRIPTOR_ALIGNMENT); + s_descriptorMemBlock = dk::MemBlockMaker{s_device, + align ((MAX_SAMPLERS + MAX_IMAGES) * sizeof (dk::ImageDescriptor), DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) + .create (); + + s_samplerDescriptors = + static_cast (s_descriptorMemBlock.getCpuAddr ()); + s_imageDescriptors = + reinterpret_cast (&s_samplerDescriptors[MAX_SAMPLERS]); + + s_samplerDescriptors[0].initialize ( + dk::Sampler{} + .setFilter (DkFilter_Linear, DkFilter_Linear) + .setWrapMode (DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge)); + + auto &cmdBuf = s_cmdBuf[0]; + dk::ImageLayout layout; + dk::ImageLayoutMaker{s_device} + .setFlags (0) + .setFormat (DkImageFormat_R8_Unorm) + .setDimensions (width, height) + .initialize (layout); + + s_fontTexture.initialize (layout, s_imageMemBlock, 0); + s_imageDescriptors[0].initialize (s_fontTexture); + + dk::ImageView imageView{s_fontTexture}; + cmdBuf.copyBufferToImage ({memBlock.getGpuAddr ()}, imageView, {0, 0, 0, width, height, 1}); + + cmdBuf.bindSamplerDescriptorSet (s_descriptorMemBlock.getGpuAddr (), MAX_SAMPLERS); + cmdBuf.bindImageDescriptorSet ( + s_descriptorMemBlock.getGpuAddr () + MAX_SAMPLERS * sizeof (dk::SamplerDescriptor), + MAX_IMAGES); + + s_queue.submitCommands (cmdBuf.finishList ()); + s_queue.waitIdle (); + + { + auto const path = "romfs:/deko3d.rgba.zst"; + + struct stat st; + if (stat (path, &st) != 0) + { + std::fprintf (stderr, "stat(%s): %s\n", path, std::strerror (errno)); + std::abort (); + } + + fs::File fp; + if (!fp.open (path)) + { + std::fprintf (stderr, "open(%s): %s\n", path, std::strerror (errno)); + std::abort (); + } + + std::vector buffer (st.st_size); + if (!fp.readAll (buffer.data (), st.st_size)) + { + std::fprintf (stderr, "read(%s): %s\n", path, std::strerror (errno)); + std::abort (); + } + + fp.close (); + + auto const size = ZSTD_getFrameContentSize (buffer.data (), st.st_size); + if (ZSTD_isError (size)) + { + std::fprintf (stderr, "ZSTD_getFrameContentSize: %s\n", ZSTD_getErrorName (size)); + std::abort (); + } + + memBlock = dk::MemBlockMaker{s_device, align (size, DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) + .create (); + + auto const decoded = + ZSTD_decompress (memBlock.getCpuAddr (), size, buffer.data (), st.st_size); + if (ZSTD_isError (decoded)) + { + std::fprintf (stderr, "ZSTD_decompress: %s\n", ZSTD_getErrorName (decoded)); + std::abort (); + } + + dk::ImageLayout layout; + dk::ImageLayoutMaker{s_device} + .setFlags (0) + .setFormat (DkImageFormat_RGBA8_Unorm) + .setDimensions (LOGO_WIDTH, LOGO_HEIGHT) + .initialize (layout); + + auto const offset = align (width * height, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT); + s_logoTexture.initialize (layout, s_imageMemBlock, offset); + s_imageDescriptors[1].initialize (s_logoTexture); + + dk::ImageView imageView{s_logoTexture}; + cmdBuf.copyBufferToImage ( + {memBlock.getGpuAddr ()}, imageView, {0, 0, 0, LOGO_WIDTH, LOGO_HEIGHT, 1}); + + s_queue.submitCommands (cmdBuf.finishList ()); + s_queue.waitIdle (); + } + + cmdBuf.clear (); +} + +void imgui::deko3d::render () +{ + auto const drawData = ImGui::GetDrawData (); + if (drawData->CmdListsCount <= 0) + return; + + unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x; + unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y; + if (width <= 0 || height <= 0) + return; + + if (width != s_width || height != s_height) + { + s_width = width; + s_height = height; + + s_queue.waitIdle (); + rebuildSwapchain (width, height); + } + + auto const slot = s_queue.acquireImage (s_swapchain); + s_cmdBuf[slot].clear (); + + dk::ImageView colorTarget{s_frameBuffers[slot]}; + dk::ImageView depthTarget{s_depthBuffer}; + s_cmdBuf[slot].bindRenderTargets (&colorTarget, &depthTarget); + s_cmdBuf[slot].clearColor (0, DkColorMask_RGBA, 0.125f, 0.294f, 0.478f, 1.0f); + s_cmdBuf[slot].clearDepthStencil (true, 1.0f, 0xFF, 0); + s_queue.submitCommands (s_cmdBuf[slot].finishList ()); + + // Setup desired render state + auto const setupCmd = setupRenderState (slot, drawData, width, height); + s_queue.submitCommands (setupCmd); + + s_boundDescriptor = ~static_cast (0); + + // Will project scissor/clipping rectangles into framebuffer space + // (0,0) unless using multi-viewports + auto const clipOff = drawData->DisplayPos; + // (1,1) unless using retina display which are often (2,2) + auto const clipScale = drawData->FramebufferScale; + + if (s_vtxMemBlock[slot].getSize () < drawData->TotalVtxCount * sizeof (ImDrawVert)) + { + s_vtxMemBlock[slot] = nullptr; + s_vtxMemBlock[slot] = + dk::MemBlockMaker{s_device, + align (drawData->TotalVtxCount * sizeof (ImDrawVert), DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) + .create (); + } + + if (s_idxMemBlock[slot].getSize () < drawData->TotalIdxCount * sizeof (ImDrawIdx)) + { + s_idxMemBlock[slot] = nullptr; + s_idxMemBlock[slot] = + dk::MemBlockMaker{s_device, + align (drawData->TotalIdxCount * sizeof (ImDrawIdx), DK_MEMBLOCK_ALIGNMENT)} + .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) + .create (); + } + + auto const cpuVtx = static_cast (s_vtxMemBlock[slot].getCpuAddr ()); + auto const cpuIdx = static_cast (s_idxMemBlock[slot].getCpuAddr ()); + + auto const gpuVtx = s_vtxMemBlock[slot].getGpuAddr (); + auto const gpuIdx = s_idxMemBlock[slot].getGpuAddr (); + + auto const sizeVtx = s_vtxMemBlock[slot].getSize (); + auto const sizeIdx = s_idxMemBlock[slot].getSize (); + + static_assert (sizeof (ImDrawIdx) == 2); + s_cmdBuf[slot].bindVtxBuffer (0, gpuVtx, sizeVtx); + s_cmdBuf[slot].bindIdxBuffer (DkIdxFormat_Uint16, gpuIdx); + + // Render command lists + std::size_t offsetVtx = 0; + std::size_t offsetIdx = 0; + for (int i = 0; i < drawData->CmdListsCount; ++i) + { + auto const &cmdList = *drawData->CmdLists[i]; + + auto const vtxSize = cmdList.VtxBuffer.Size * sizeof (ImDrawVert); + auto const idxSize = cmdList.IdxBuffer.Size * sizeof (ImDrawIdx); + + if (sizeVtx - offsetVtx < vtxSize) + { + std::fprintf (stderr, "Not enough vertex buffer\n"); + std::fprintf (stderr, "\t%zu/%u used, need %zu\n", offsetVtx, sizeVtx, vtxSize); + continue; + } + + if (sizeIdx - offsetIdx < idxSize) + { + std::fprintf (stderr, "Not enough index buffer\n"); + std::fprintf (stderr, "\t%zu/%u used, need %zu\n", offsetIdx, sizeIdx, idxSize); + continue; + } + + std::memcpy (cpuVtx + offsetVtx, cmdList.VtxBuffer.Data, vtxSize); + std::memcpy (cpuIdx + offsetIdx, cmdList.IdxBuffer.Data, idxSize); + + for (auto const &cmd : cmdList.CmdBuffer) + { + if (cmd.UserCallback) + { + s_queue.submitCommands (s_cmdBuf[slot].finishList ()); + + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to + // request the renderer to reset render state.) + if (cmd.UserCallback == ImDrawCallback_ResetRenderState) + s_queue.submitCommands (setupCmd); + else + cmd.UserCallback (&cmdList, &cmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec4 clip; + clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x; + clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y; + clip.z = (cmd.ClipRect.z - clipOff.x) * clipScale.x; + clip.w = (cmd.ClipRect.w - clipOff.y) * clipScale.y; + + if (clip.x < width && clip.y < height && clip.z >= 0.0f && clip.w >= 0.0f) + { + if (clip.x < 0.0f) + clip.x = 0.0f; + if (clip.y < 0.0f) + clip.y = 0.0f; + + s_cmdBuf[slot].setScissors ( + 0, DkScissor{clip.x, clip.y, clip.z - clip.x, clip.w - clip.y}); + + auto const descriptor = reinterpret_cast (cmd.TextureId); + if (descriptor >= MAX_IMAGES) + continue; + + if (descriptor != s_boundDescriptor) + { + s_boundDescriptor = descriptor; + + s_cmdBuf[slot].bindTextures ( + DkStage_Fragment, 0, dkMakeTextureHandle (descriptor, 0)); + + FragUBO fragUBO; + fragUBO.font = (descriptor == 0); + + s_cmdBuf[slot].pushConstants ( + s_uboMemBlock.getGpuAddr () + + align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT), + align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT), + 0, + sizeof (FragUBO), + &fragUBO); + } + + s_cmdBuf[slot].drawIndexed (DkPrimitive_Triangles, + cmd.ElemCount, + 1, + cmd.IdxOffset + offsetIdx / sizeof (ImDrawIdx), + cmd.VtxOffset + offsetVtx / sizeof (ImDrawVert), + 0); + } + } + } + + offsetVtx += vtxSize; + offsetIdx += idxSize; + } + + s_cmdBuf[slot].barrier (DkBarrier_Fragments, 0); + s_cmdBuf[slot].discardDepthStencil (); + s_queue.submitCommands (s_cmdBuf[slot].finishList ()); + + s_queue.presentImage (s_swapchain, slot); +} + +void imgui::deko3d::test () +{ + auto const x1 = (s_width - LOGO_WIDTH) / 2.0f; + auto const x2 = x1 + LOGO_WIDTH; + auto const y1 = (s_height - LOGO_HEIGHT) / 2.0f; + auto const y2 = y1 + LOGO_HEIGHT; + + ImGui::GetBackgroundDrawList ()->AddImage ( + reinterpret_cast (1), ImVec2 (x1, y1), ImVec2 (x2, y2)); +} diff --git a/source/nx/imgui_deko3d.h b/source/nx/imgui_deko3d.h new file mode 100644 index 0000000..6b196af --- /dev/null +++ b/source/nx/imgui_deko3d.h @@ -0,0 +1,35 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +namespace imgui +{ +namespace deko3d +{ +void init (); +void exit (); + +void newFrame (); +void render (); + +void test (); +} +} diff --git a/source/nx/imgui_fsh.glsl b/source/nx/imgui_fsh.glsl new file mode 100644 index 0000000..8398b4f --- /dev/null +++ b/source/nx/imgui_fsh.glsl @@ -0,0 +1,40 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#version 460 + +layout (location = 0) in vec2 vtxUv; +layout (location = 1) in vec4 vtxColor; + +layout (binding = 0) uniform sampler2D tex; + +layout (std140, binding = 0) uniform FragUBO { + uint font; +} ubo; + +layout (location = 0) out vec4 outColor; + +void main() +{ + if (ubo.font != 0) + outColor = vtxColor * vec4 (vec3 (1.0), texture (tex, vtxUv).r); + else + outColor = vtxColor * texture (tex, vtxUv); +} diff --git a/source/nx/imgui_nx.cpp b/source/nx/imgui_nx.cpp new file mode 100644 index 0000000..ae1ef48 --- /dev/null +++ b/source/nx/imgui_nx.cpp @@ -0,0 +1,1655 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "imgui_nx.h" + +#include "imgui.h" + +#include "fs.h" + +#include + +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +namespace +{ +constexpr auto FONT_ATLAS_BIN = "ftpd-font.bin"; + +bool s_mouseJustPressed[IM_ARRAYSIZE (ImGuiIO::MouseDown)]; + +std::chrono::high_resolution_clock::time_point s_lastMouseUpdate; + +bool s_focused = true; +float s_width = 1280.0f; +float s_height = 720.0f; + +float s_showMouse = false; +ImVec2 s_mousePos = ImVec2 (0.0f, 0.0f); + +std::string s_clipboard; + +AppletHookCookie s_appletHookCookie; + +ImWchar const nxFontRanges[] = { + // clang-format off + 0x0020, 0x007e, 0x00a0, 0x017f, 0x0192, 0x0192, 0x01c0, 0x01c0, + 0x01c5, 0x01c6, 0x01ce, 0x01ce, 0x01d0, 0x01d0, 0x01d2, 0x01d2, + 0x01d4, 0x01d4, 0x01d6, 0x01d6, 0x01d8, 0x01d8, 0x01da, 0x01da, + 0x01dc, 0x01dc, 0x01f2, 0x01f3, 0x01f9, 0x01ff, 0x0218, 0x021b, + 0x0251, 0x0251, 0x0261, 0x0261, 0x02bc, 0x02bd, 0x02c6, 0x02c7, + 0x02c9, 0x02cb, 0x02cd, 0x02cd, 0x02d0, 0x02d0, 0x02d8, 0x02dd, + 0x0300, 0x0308, 0x030a, 0x030c, 0x0326, 0x0328, 0x0332, 0x0332, + 0x0335, 0x0337, 0x0361, 0x0361, 0x0363, 0x0364, 0x0366, 0x0366, + 0x036c, 0x036c, 0x037a, 0x037a, 0x037e, 0x037e, 0x0384, 0x038a, + 0x038c, 0x038c, 0x038e, 0x03a1, 0x03a3, 0x03ce, 0x0400, 0x045f, + 0x0490, 0x0491, 0x1e02, 0x1e03, 0x1e0a, 0x1e0b, 0x1e1e, 0x1e1f, + 0x1e3f, 0x1e41, 0x1e56, 0x1e57, 0x1e60, 0x1e61, 0x1e6a, 0x1e6b, + 0x1e80, 0x1e85, 0x1e9e, 0x1e9e, 0x1ef2, 0x1ef3, 0x2010, 0x2022, + 0x2024, 0x2027, 0x2030, 0x2030, 0x2032, 0x2036, 0x2039, 0x203c, + 0x203e, 0x203e, 0x2042, 0x2042, 0x2044, 0x2044, 0x2060, 0x2060, + 0x2074, 0x2074, 0x207a, 0x207f, 0x2081, 0x2084, 0x20a3, 0x20a4, + 0x20a7, 0x20a7, 0x20a9, 0x20a9, 0x20ac, 0x20ac, 0x20af, 0x20af, + 0x20bd, 0x20bd, 0x20dd, 0x20dd, 0x2103, 0x2103, 0x2105, 0x2105, + 0x2109, 0x210a, 0x2113, 0x2113, 0x2116, 0x2116, 0x2121, 0x2122, + 0x2126, 0x2126, 0x212b, 0x212b, 0x212e, 0x212e, 0x2153, 0x2154, + 0x215b, 0x215e, 0x2160, 0x216b, 0x2170, 0x217b, 0x217f, 0x217f, + 0x2190, 0x2199, 0x21a8, 0x21a8, 0x21b0, 0x21b4, 0x21bc, 0x21bc, + 0x21c0, 0x21c0, 0x21c4, 0x21c6, 0x21cd, 0x21cd, 0x21cf, 0x21d4, + 0x21e0, 0x21e3, 0x21e6, 0x21e9, 0x2200, 0x2200, 0x2202, 0x2203, + 0x2206, 0x2209, 0x220b, 0x220c, 0x220f, 0x220f, 0x2211, 0x2213, + 0x2215, 0x2215, 0x2219, 0x221a, 0x221d, 0x2220, 0x2222, 0x2222, + 0x2225, 0x222c, 0x222e, 0x222e, 0x2234, 0x2237, 0x223c, 0x223d, + 0x2243, 0x2243, 0x2245, 0x2245, 0x2248, 0x2248, 0x2250, 0x2253, + 0x225a, 0x225a, 0x2260, 0x2262, 0x2264, 0x2267, 0x226a, 0x226b, + 0x226e, 0x2273, 0x2276, 0x2277, 0x2279, 0x227b, 0x2280, 0x2287, + 0x228a, 0x228b, 0x2295, 0x2297, 0x2299, 0x2299, 0x22a3, 0x22a5, + 0x22bb, 0x22bc, 0x22bf, 0x22bf, 0x22ce, 0x22cf, 0x22da, 0x22db, + 0x22ee, 0x22ef, 0x2302, 0x2302, 0x2306, 0x2306, 0x2310, 0x2310, + 0x2312, 0x2312, 0x2314, 0x2314, 0x2320, 0x2321, 0x2460, 0x2487, + 0x249c, 0x24f4, 0x2500, 0x2503, 0x250c, 0x254b, 0x2550, 0x256c, + 0x2573, 0x2573, 0x2580, 0x2580, 0x2584, 0x2584, 0x2588, 0x2588, + 0x258c, 0x258c, 0x2590, 0x2593, 0x25a0, 0x25a1, 0x25a3, 0x25ac, + 0x25b1, 0x25b3, 0x25b5, 0x25b7, 0x25b9, 0x25ba, 0x25bc, 0x25bd, + 0x25bf, 0x25c1, 0x25c3, 0x25c4, 0x25c6, 0x25cc, 0x25ce, 0x25d1, + 0x25d8, 0x25d9, 0x25e6, 0x25e6, 0x25ef, 0x25ef, 0x2600, 0x2603, + 0x2605, 0x2606, 0x260e, 0x260f, 0x261c, 0x261f, 0x262f, 0x262f, + 0x263a, 0x263c, 0x2640, 0x2640, 0x2642, 0x2642, 0x2660, 0x266d, + 0x266f, 0x266f, 0x2716, 0x2716, 0x271a, 0x271a, 0x273d, 0x273d, + 0x2756, 0x2756, 0x2776, 0x277f, 0x278a, 0x2793, 0x2f00, 0x2f00, + 0x2f04, 0x2f04, 0x2f06, 0x2f06, 0x2f08, 0x2f08, 0x2f0a, 0x2f0b, + 0x2f11, 0x2f12, 0x2f14, 0x2f14, 0x2f17, 0x2f18, 0x2f1c, 0x2f1d, + 0x2f1f, 0x2f20, 0x2f23, 0x2f26, 0x2f28, 0x2f29, 0x2f2b, 0x2f2b, + 0x2f2d, 0x2f2d, 0x2f2f, 0x2f32, 0x2f38, 0x2f38, 0x2f3c, 0x2f40, + 0x2f42, 0x2f4c, 0x2f4f, 0x2f52, 0x2f54, 0x2f58, 0x2f5a, 0x2f66, + 0x2f69, 0x2f70, 0x2f72, 0x2f76, 0x2f78, 0x2f78, 0x2f7a, 0x2f7d, + 0x2f7f, 0x2f8b, 0x2f8e, 0x2f90, 0x2f92, 0x2f97, 0x2f99, 0x2fa0, + 0x2fa2, 0x2fa3, 0x2fa5, 0x2fa9, 0x2fac, 0x2fb1, 0x2fb3, 0x2fbc, + 0x2fc1, 0x2fca, 0x2fcd, 0x2fd4, 0x3000, 0x3019, 0x301c, 0x3020, + 0x3036, 0x3036, 0x3041, 0x3094, 0x309b, 0x309e, 0x30a1, 0x30f6, + 0x30fb, 0x30fe, 0x3131, 0x318e, 0x3200, 0x321c, 0x322a, 0x3243, + 0x3260, 0x327b, 0x327f, 0x327f, 0x328a, 0x3290, 0x3294, 0x3294, + 0x329e, 0x329e, 0x32a4, 0x32a8, 0x3303, 0x3303, 0x330d, 0x330d, + 0x3314, 0x3314, 0x3318, 0x3318, 0x3322, 0x3323, 0x3326, 0x3327, + 0x332b, 0x332b, 0x3336, 0x3336, 0x333b, 0x333b, 0x3349, 0x334a, + 0x334d, 0x334d, 0x3351, 0x3351, 0x3357, 0x3357, 0x337b, 0x33cd, + 0x33cf, 0x33d0, 0x33d3, 0x33d4, 0x33d6, 0x33d6, 0x33d8, 0x33d8, + 0x33db, 0x33dd, 0x4e00, 0x4e01, 0x4e03, 0x4e03, 0x4e07, 0x4e0b, + 0x4e0d, 0x4e0e, 0x4e10, 0x4e11, 0x4e14, 0x4e19, 0x4e1e, 0x4e1e, + 0x4e21, 0x4e21, 0x4e26, 0x4e26, 0x4e28, 0x4e28, 0x4e2a, 0x4e2a, + 0x4e2d, 0x4e2d, 0x4e31, 0x4e32, 0x4e36, 0x4e36, 0x4e38, 0x4e39, + 0x4e3b, 0x4e3c, 0x4e3f, 0x4e3f, 0x4e42, 0x4e43, 0x4e45, 0x4e45, + 0x4e4b, 0x4e4b, 0x4e4d, 0x4e4f, 0x4e55, 0x4e59, 0x4e5d, 0x4e5f, + 0x4e62, 0x4e62, 0x4e6b, 0x4e6b, 0x4e6d, 0x4e6d, 0x4e71, 0x4e71, + 0x4e73, 0x4e73, 0x4e76, 0x4e77, 0x4e7e, 0x4e7e, 0x4e80, 0x4e80, + 0x4e82, 0x4e82, 0x4e85, 0x4e86, 0x4e88, 0x4e8c, 0x4e8e, 0x4e8e, + 0x4e90, 0x4e92, 0x4e94, 0x4e95, 0x4e98, 0x4e99, 0x4e9b, 0x4e9c, + 0x4e9e, 0x4ea2, 0x4ea4, 0x4ea6, 0x4ea8, 0x4ea8, 0x4eab, 0x4eae, + 0x4eb0, 0x4eb0, 0x4eb3, 0x4eb3, 0x4eb6, 0x4eb6, 0x4eba, 0x4eba, + 0x4ec0, 0x4ec2, 0x4ec4, 0x4ec4, 0x4ec6, 0x4ec7, 0x4eca, 0x4ecb, + 0x4ecd, 0x4ecf, 0x4ed4, 0x4ed9, 0x4edd, 0x4edf, 0x4ee1, 0x4ee1, + 0x4ee3, 0x4ee5, 0x4eed, 0x4eee, 0x4ef0, 0x4ef0, 0x4ef2, 0x4ef2, + 0x4ef6, 0x4ef7, 0x4efb, 0x4efc, 0x4f00, 0x4f01, 0x4f03, 0x4f03, + 0x4f09, 0x4f0b, 0x4f0d, 0x4f11, 0x4f1a, 0x4f1a, 0x4f1c, 0x4f1d, + 0x4f2f, 0x4f30, 0x4f34, 0x4f34, 0x4f36, 0x4f36, 0x4f38, 0x4f3a, + 0x4f3c, 0x4f3d, 0x4f43, 0x4f43, 0x4f46, 0x4f48, 0x4f4d, 0x4f51, + 0x4f53, 0x4f53, 0x4f55, 0x4f57, 0x4f59, 0x4f5e, 0x4f69, 0x4f69, + 0x4f6f, 0x4f70, 0x4f73, 0x4f73, 0x4f75, 0x4f76, 0x4f7a, 0x4f7c, + 0x4f7e, 0x4f7f, 0x4f81, 0x4f81, 0x4f83, 0x4f84, 0x4f86, 0x4f86, + 0x4f88, 0x4f88, 0x4f8a, 0x4f8b, 0x4f8d, 0x4f8d, 0x4f8f, 0x4f8f, + 0x4f91, 0x4f92, 0x4f94, 0x4f94, 0x4f96, 0x4f96, 0x4f98, 0x4f98, + 0x4f9a, 0x4f9b, 0x4f9d, 0x4f9d, 0x4fa0, 0x4fa1, 0x4fab, 0x4fab, + 0x4fad, 0x4faf, 0x4fb5, 0x4fb6, 0x4fbf, 0x4fbf, 0x4fc2, 0x4fc4, + 0x4fc9, 0x4fca, 0x4fcd, 0x4fce, 0x4fd0, 0x4fd1, 0x4fd3, 0x4fd4, + 0x4fd7, 0x4fd8, 0x4fda, 0x4fdb, 0x4fdd, 0x4fdd, 0x4fdf, 0x4fe1, + 0x4fe3, 0x4fe5, 0x4fee, 0x4fef, 0x4ff1, 0x4ff1, 0x4ff3, 0x4ff3, + 0x4ff5, 0x4ff6, 0x4ff8, 0x4ff8, 0x4ffa, 0x4ffa, 0x4ffe, 0x4fff, + 0x5002, 0x5002, 0x5005, 0x5006, 0x5009, 0x5009, 0x500b, 0x500b, + 0x500d, 0x500d, 0x500f, 0x500f, 0x5011, 0x5012, 0x5014, 0x5014, + 0x5016, 0x5016, 0x5019, 0x501a, 0x501c, 0x501c, 0x501e, 0x501f, + 0x5021, 0x502d, 0x5036, 0x5036, 0x5039, 0x5039, 0x503b, 0x503b, + 0x5040, 0x5040, 0x5042, 0x5043, 0x5046, 0x5049, 0x504f, 0x5050, + 0x5055, 0x5056, 0x505a, 0x505a, 0x505c, 0x505c, 0x5065, 0x5065, + 0x506c, 0x506c, 0x5070, 0x5070, 0x5072, 0x5072, 0x5074, 0x5076, + 0x5078, 0x5078, 0x507d, 0x507d, 0x5080, 0x5080, 0x5085, 0x5085, + 0x508d, 0x508d, 0x5091, 0x5091, 0x5094, 0x5094, 0x5098, 0x509a, + 0x50ac, 0x50ad, 0x50b2, 0x50b5, 0x50b7, 0x50b7, 0x50be, 0x50be, + 0x50c2, 0x50c2, 0x50c5, 0x50c5, 0x50c9, 0x50ca, 0x50cd, 0x50cd, + 0x50cf, 0x50cf, 0x50d1, 0x50d1, 0x50d5, 0x50d6, 0x50d8, 0x50d8, + 0x50da, 0x50da, 0x50de, 0x50de, 0x50e3, 0x50e3, 0x50e5, 0x50e5, + 0x50e7, 0x50e7, 0x50ed, 0x50ee, 0x50f4, 0x50f5, 0x50f9, 0x50f9, + 0x50fb, 0x50fb, 0x50ff, 0x5102, 0x5104, 0x5104, 0x5106, 0x5106, + 0x5109, 0x5109, 0x5112, 0x5112, 0x5114, 0x5116, 0x5118, 0x5118, + 0x511a, 0x511a, 0x511f, 0x511f, 0x5121, 0x5121, 0x512a, 0x512a, + 0x5132, 0x5132, 0x5137, 0x5137, 0x513a, 0x513c, 0x513f, 0x5141, + 0x5143, 0x514e, 0x5150, 0x5150, 0x5152, 0x5152, 0x5154, 0x5154, + 0x515a, 0x515a, 0x515c, 0x515c, 0x5162, 0x5162, 0x5164, 0x5165, + 0x5167, 0x516e, 0x5171, 0x5171, 0x5175, 0x5178, 0x517c, 0x517c, + 0x5180, 0x5180, 0x5182, 0x5182, 0x5185, 0x5186, 0x5189, 0x518a, + 0x518c, 0x518d, 0x518f, 0x5193, 0x5195, 0x5197, 0x5199, 0x5199, + 0x519d, 0x519d, 0x51a0, 0x51a0, 0x51a2, 0x51a2, 0x51a4, 0x51a6, + 0x51a8, 0x51ac, 0x51b0, 0x51b7, 0x51bd, 0x51be, 0x51c4, 0x51c6, + 0x51c9, 0x51c9, 0x51cb, 0x51cd, 0x51d6, 0x51d6, 0x51db, 0x51de, + 0x51e0, 0x51e1, 0x51e6, 0x51e7, 0x51e9, 0x51ea, 0x51ec, 0x51ed, + 0x51f0, 0x51f1, 0x51f5, 0x51f6, 0x51f8, 0x51fa, 0x51fd, 0x51fe, + 0x5200, 0x5200, 0x5203, 0x5204, 0x5206, 0x5208, 0x520a, 0x520b, + 0x520e, 0x520e, 0x5211, 0x5211, 0x5214, 0x5215, 0x5217, 0x5217, + 0x521d, 0x521d, 0x5224, 0x5225, 0x5227, 0x5227, 0x5229, 0x522a, + 0x522e, 0x522e, 0x5230, 0x5230, 0x5233, 0x5233, 0x5236, 0x523b, + 0x5243, 0x5244, 0x5247, 0x5247, 0x524a, 0x524d, 0x524f, 0x524f, + 0x5254, 0x5254, 0x5256, 0x5256, 0x525b, 0x525b, 0x525d, 0x525e, + 0x5261, 0x5261, 0x5263, 0x5265, 0x5269, 0x526a, 0x526f, 0x5275, + 0x527d, 0x527d, 0x527f, 0x527f, 0x5283, 0x5283, 0x5287, 0x5289, + 0x528d, 0x528d, 0x5291, 0x5292, 0x5294, 0x5294, 0x529b, 0x529c, + 0x529f, 0x52a0, 0x52a3, 0x52a4, 0x52a6, 0x52a6, 0x52a9, 0x52ad, + 0x52af, 0x52af, 0x52b1, 0x52b1, 0x52b4, 0x52b5, 0x52b9, 0x52b9, + 0x52bc, 0x52bc, 0x52be, 0x52be, 0x52c0, 0x52c1, 0x52c3, 0x52c3, + 0x52c5, 0x52c5, 0x52c7, 0x52c7, 0x52c9, 0x52c9, 0x52cd, 0x52cd, + 0x52d2, 0x52d2, 0x52d5, 0x52d9, 0x52db, 0x52db, 0x52dd, 0x52e0, + 0x52e2, 0x52e4, 0x52e6, 0x52e7, 0x52f2, 0x52f3, 0x52f5, 0x52f5, + 0x52f8, 0x52fb, 0x52fe, 0x5302, 0x5305, 0x5308, 0x530d, 0x530d, + 0x530f, 0x5310, 0x5315, 0x5317, 0x5319, 0x531a, 0x531d, 0x531d, + 0x5320, 0x5321, 0x5323, 0x5324, 0x532a, 0x532a, 0x532f, 0x532f, + 0x5331, 0x5331, 0x5333, 0x5333, 0x5338, 0x533b, 0x533f, 0x5341, + 0x5343, 0x534a, 0x534d, 0x534d, 0x5351, 0x5354, 0x5357, 0x5358, + 0x535a, 0x535a, 0x535c, 0x535c, 0x535e, 0x535e, 0x5360, 0x5360, + 0x5366, 0x5366, 0x5368, 0x5369, 0x536e, 0x5375, 0x5377, 0x5378, + 0x537b, 0x537b, 0x537d, 0x537d, 0x537f, 0x537f, 0x5382, 0x5382, + 0x5384, 0x5384, 0x5393, 0x5393, 0x5396, 0x5396, 0x5398, 0x5398, + 0x539a, 0x539a, 0x539f, 0x53a0, 0x53a5, 0x53a6, 0x53a8, 0x53a9, + 0x53ad, 0x53ae, 0x53b0, 0x53b0, 0x53b2, 0x53b3, 0x53b6, 0x53b6, + 0x53bb, 0x53bb, 0x53c2, 0x53c3, 0x53c8, 0x53ce, 0x53d4, 0x53d4, + 0x53d6, 0x53d7, 0x53d9, 0x53d9, 0x53db, 0x53db, 0x53dd, 0x53dd, + 0x53df, 0x53df, 0x53e1, 0x53e5, 0x53e8, 0x53f3, 0x53f6, 0x53f8, + 0x53fa, 0x53fa, 0x5401, 0x5401, 0x5403, 0x5404, 0x5408, 0x5411, + 0x541b, 0x541b, 0x541d, 0x541d, 0x541f, 0x5420, 0x5426, 0x5426, + 0x5429, 0x5429, 0x542b, 0x542e, 0x5433, 0x5433, 0x5436, 0x5436, + 0x5438, 0x5439, 0x543b, 0x543e, 0x5440, 0x5440, 0x5442, 0x5442, + 0x5446, 0x5446, 0x5448, 0x544a, 0x544e, 0x544e, 0x5451, 0x5451, + 0x545f, 0x545f, 0x5468, 0x5468, 0x546a, 0x546a, 0x5470, 0x5471, + 0x5473, 0x5473, 0x5475, 0x5477, 0x547b, 0x547d, 0x5480, 0x5480, + 0x5484, 0x5484, 0x5486, 0x5486, 0x548a, 0x548c, 0x548e, 0x5490, + 0x5492, 0x5492, 0x549c, 0x549c, 0x54a2, 0x54a2, 0x54a4, 0x54a5, + 0x54a8, 0x54a9, 0x54ab, 0x54ac, 0x54af, 0x54af, 0x54b2, 0x54b3, + 0x54b8, 0x54b8, 0x54bc, 0x54be, 0x54c0, 0x54c2, 0x54c4, 0x54c4, + 0x54c7, 0x54c9, 0x54d8, 0x54d8, 0x54e1, 0x54e2, 0x54e5, 0x54e6, + 0x54e8, 0x54e9, 0x54ed, 0x54ee, 0x54f2, 0x54f2, 0x54fa, 0x54fa, + 0x54fd, 0x54fd, 0x54ff, 0x54ff, 0x5504, 0x5504, 0x5506, 0x5507, + 0x550e, 0x5510, 0x5514, 0x5514, 0x5516, 0x5516, 0x551c, 0x551c, + 0x552e, 0x552f, 0x5531, 0x5531, 0x5533, 0x5533, 0x5535, 0x5535, + 0x5538, 0x5539, 0x553e, 0x553e, 0x5540, 0x5540, 0x5544, 0x5546, + 0x554c, 0x554c, 0x554f, 0x554f, 0x5553, 0x5553, 0x5556, 0x5557, + 0x555c, 0x555e, 0x5563, 0x5563, 0x557b, 0x557c, 0x557e, 0x557e, + 0x5580, 0x5580, 0x5583, 0x5584, 0x5586, 0x5587, 0x5589, 0x558b, + 0x5598, 0x559a, 0x559c, 0x559f, 0x55a7, 0x55ac, 0x55ae, 0x55ae, + 0x55b0, 0x55b0, 0x55b6, 0x55b6, 0x55c4, 0x55c5, 0x55c7, 0x55c7, + 0x55d4, 0x55d4, 0x55da, 0x55da, 0x55dc, 0x55dc, 0x55df, 0x55df, + 0x55e3, 0x55e4, 0x55f7, 0x55f7, 0x55f9, 0x55f9, 0x55fd, 0x55fe, + 0x5606, 0x5606, 0x5609, 0x5609, 0x5614, 0x5614, 0x5616, 0x5618, + 0x561b, 0x561b, 0x5629, 0x5629, 0x562f, 0x562f, 0x5631, 0x5632, + 0x5634, 0x5634, 0x5636, 0x5636, 0x5638, 0x5638, 0x5642, 0x5642, + 0x564c, 0x564c, 0x564e, 0x564e, 0x5650, 0x5650, 0x5653, 0x5653, + 0x565b, 0x565b, 0x5664, 0x5664, 0x5668, 0x5668, 0x566a, 0x566c, + 0x5674, 0x5674, 0x5678, 0x5678, 0x567a, 0x567a, 0x5680, 0x5680, + 0x5686, 0x5687, 0x568a, 0x568a, 0x568f, 0x568f, 0x5694, 0x5694, + 0x56a0, 0x56a0, 0x56a2, 0x56a2, 0x56a5, 0x56a5, 0x56ac, 0x56ac, + 0x56ae, 0x56ae, 0x56b4, 0x56b4, 0x56b6, 0x56b6, 0x56bc, 0x56bc, + 0x56c0, 0x56c3, 0x56c8, 0x56c8, 0x56ca, 0x56ca, 0x56cd, 0x56ce, + 0x56d1, 0x56d1, 0x56d3, 0x56d3, 0x56d7, 0x56d8, 0x56da, 0x56db, + 0x56de, 0x56de, 0x56e0, 0x56e0, 0x56e3, 0x56e3, 0x56ee, 0x56ee, + 0x56f0, 0x56f0, 0x56f2, 0x56f3, 0x56f9, 0x56fa, 0x56fd, 0x56fd, + 0x56ff, 0x5700, 0x5703, 0x5704, 0x5708, 0x5709, 0x570b, 0x570b, + 0x570d, 0x570d, 0x570f, 0x570f, 0x5712, 0x5713, 0x5716, 0x5716, + 0x5718, 0x5718, 0x571c, 0x571c, 0x571f, 0x571f, 0x5726, 0x5728, + 0x572d, 0x572d, 0x5730, 0x5730, 0x5737, 0x5738, 0x573b, 0x573b, + 0x5740, 0x5740, 0x5742, 0x5742, 0x5747, 0x5747, 0x574a, 0x574a, + 0x574d, 0x5751, 0x5759, 0x5759, 0x5761, 0x5761, 0x5764, 0x5766, + 0x5769, 0x576a, 0x576e, 0x576e, 0x5770, 0x5770, 0x5775, 0x5775, + 0x577c, 0x577c, 0x577f, 0x577f, 0x5782, 0x5782, 0x5788, 0x5789, + 0x578b, 0x578b, 0x5793, 0x5793, 0x57a0, 0x57a0, 0x57a2, 0x57a4, + 0x57aa, 0x57aa, 0x57ac, 0x57ac, 0x57b0, 0x57b0, 0x57b3, 0x57b3, + 0x57c0, 0x57c0, 0x57c3, 0x57c3, 0x57c6, 0x57c8, 0x57cb, 0x57cb, + 0x57ce, 0x57ce, 0x57d2, 0x57d4, 0x57d6, 0x57d6, 0x57dc, 0x57dc, + 0x57df, 0x57e0, 0x57e3, 0x57e3, 0x57f0, 0x57f0, 0x57f4, 0x57f4, + 0x57f7, 0x57f7, 0x57f9, 0x57fa, 0x57fc, 0x57fc, 0x5800, 0x5800, + 0x5802, 0x5802, 0x5805, 0x5806, 0x5808, 0x580b, 0x5815, 0x5815, + 0x5819, 0x5819, 0x581d, 0x581e, 0x5821, 0x5821, 0x5824, 0x5824, + 0x5827, 0x5827, 0x582a, 0x582a, 0x582f, 0x5831, 0x5834, 0x5835, + 0x583a, 0x583a, 0x583d, 0x583d, 0x5840, 0x5841, 0x584a, 0x584b, + 0x584f, 0x584f, 0x5851, 0x5852, 0x5854, 0x5854, 0x5857, 0x585a, + 0x585e, 0x585e, 0x5861, 0x5862, 0x5864, 0x5864, 0x5869, 0x5869, + 0x586b, 0x586b, 0x5870, 0x5870, 0x5872, 0x5872, 0x5875, 0x5875, + 0x5879, 0x5879, 0x587c, 0x587c, 0x587e, 0x587e, 0x5883, 0x5883, + 0x5885, 0x5885, 0x5889, 0x5889, 0x5893, 0x5893, 0x5897, 0x5897, + 0x589c, 0x589c, 0x589e, 0x589f, 0x58a8, 0x58a9, 0x58ab, 0x58ab, + 0x58ae, 0x58ae, 0x58b2, 0x58b3, 0x58b8, 0x58bb, 0x58be, 0x58be, + 0x58c1, 0x58c1, 0x58c5, 0x58c5, 0x58c7, 0x58c7, 0x58ca, 0x58ca, + 0x58cc, 0x58cc, 0x58ce, 0x58ce, 0x58d1, 0x58d1, 0x58d3, 0x58d3, + 0x58d5, 0x58d5, 0x58d7, 0x58d9, 0x58dc, 0x58dc, 0x58de, 0x58df, + 0x58e4, 0x58e5, 0x58eb, 0x58ec, 0x58ee, 0x58f2, 0x58f7, 0x58f7, + 0x58f9, 0x58fd, 0x5902, 0x5902, 0x5909, 0x590b, 0x590f, 0x5910, + 0x5914, 0x5916, 0x5918, 0x591c, 0x5922, 0x5922, 0x5925, 0x5925, + 0x5927, 0x5927, 0x5929, 0x592e, 0x5931, 0x5932, 0x5937, 0x5938, + 0x593e, 0x593e, 0x5944, 0x5944, 0x5947, 0x5949, 0x594e, 0x5951, + 0x5953, 0x5955, 0x5957, 0x5958, 0x595a, 0x595b, 0x595d, 0x595d, + 0x5960, 0x5960, 0x5962, 0x5963, 0x5965, 0x5965, 0x5967, 0x596e, + 0x5973, 0x5974, 0x5978, 0x5978, 0x597d, 0x597d, 0x5981, 0x5984, + 0x598a, 0x598a, 0x598d, 0x598d, 0x5993, 0x5993, 0x5996, 0x5997, + 0x5999, 0x5999, 0x599b, 0x599b, 0x599d, 0x599d, 0x59a3, 0x59a5, + 0x59a8, 0x59a8, 0x59ac, 0x59ac, 0x59b2, 0x59b2, 0x59b9, 0x59bb, + 0x59be, 0x59be, 0x59c3, 0x59c3, 0x59c6, 0x59c6, 0x59c9, 0x59c9, + 0x59cb, 0x59cb, 0x59d0, 0x59d1, 0x59d3, 0x59d4, 0x59d9, 0x59da, + 0x59dc, 0x59dd, 0x59e5, 0x59e6, 0x59e8, 0x59e8, 0x59ea, 0x59ec, + 0x59ee, 0x59ee, 0x59f6, 0x59f6, 0x59f8, 0x59f8, 0x59fb, 0x59fb, + 0x59ff, 0x59ff, 0x5a01, 0x5a01, 0x5a03, 0x5a03, 0x5a09, 0x5a09, + 0x5a11, 0x5a11, 0x5a18, 0x5a18, 0x5a1a, 0x5a1c, 0x5a1f, 0x5a20, + 0x5a25, 0x5a25, 0x5a29, 0x5a29, 0x5a2f, 0x5a2f, 0x5a35, 0x5a36, + 0x5a3c, 0x5a3c, 0x5a40, 0x5a41, 0x5a46, 0x5a46, 0x5a49, 0x5a49, + 0x5a5a, 0x5a5a, 0x5a62, 0x5a62, 0x5a66, 0x5a66, 0x5a6a, 0x5a6a, + 0x5a6c, 0x5a6c, 0x5a7f, 0x5a7f, 0x5a92, 0x5a92, 0x5a9a, 0x5a9b, + 0x5aa4, 0x5aa4, 0x5abc, 0x5abe, 0x5ac1, 0x5ac2, 0x5ac4, 0x5ac4, + 0x5ac9, 0x5ac9, 0x5acb, 0x5acc, 0x5ad0, 0x5ad0, 0x5ad6, 0x5ad7, + 0x5ae1, 0x5ae1, 0x5ae3, 0x5ae3, 0x5ae6, 0x5ae6, 0x5ae9, 0x5ae9, + 0x5afa, 0x5afb, 0x5b05, 0x5b05, 0x5b09, 0x5b09, 0x5b0b, 0x5b0c, + 0x5b16, 0x5b16, 0x5b22, 0x5b22, 0x5b2a, 0x5b2a, 0x5b2c, 0x5b2c, + 0x5b30, 0x5b30, 0x5b32, 0x5b32, 0x5b36, 0x5b36, 0x5b3e, 0x5b3e, + 0x5b40, 0x5b40, 0x5b43, 0x5b43, 0x5b45, 0x5b45, 0x5b50, 0x5b51, + 0x5b54, 0x5b58, 0x5b5a, 0x5b5d, 0x5b5f, 0x5b5f, 0x5b63, 0x5b66, + 0x5b69, 0x5b69, 0x5b6b, 0x5b6b, 0x5b70, 0x5b71, 0x5b73, 0x5b73, + 0x5b75, 0x5b75, 0x5b78, 0x5b78, 0x5b7a, 0x5b7a, 0x5b7c, 0x5b7c, + 0x5b80, 0x5b80, 0x5b83, 0x5b83, 0x5b85, 0x5b85, 0x5b87, 0x5b89, + 0x5b8b, 0x5b8d, 0x5b8f, 0x5b8f, 0x5b93, 0x5b93, 0x5b95, 0x5b9d, + 0x5b9f, 0x5b9f, 0x5ba2, 0x5ba6, 0x5bac, 0x5bac, 0x5bae, 0x5bae, + 0x5bb0, 0x5bb0, 0x5bb3, 0x5bb6, 0x5bb8, 0x5bb9, 0x5bbf, 0x5bc0, + 0x5bc2, 0x5bc7, 0x5bc9, 0x5bc9, 0x5bcc, 0x5bcc, 0x5bd0, 0x5bd0, + 0x5bd2, 0x5bd4, 0x5bd7, 0x5bd8, 0x5bdb, 0x5bdb, 0x5bdd, 0x5bdf, + 0x5be1, 0x5be2, 0x5be4, 0x5be9, 0x5beb, 0x5bec, 0x5bee, 0x5bf0, + 0x5bf3, 0x5bf3, 0x5bf5, 0x5bf6, 0x5bf8, 0x5bf8, 0x5bfa, 0x5bfa, + 0x5bfe, 0x5bff, 0x5c01, 0x5c02, 0x5c04, 0x5c0b, 0x5c0d, 0x5c0f, + 0x5c11, 0x5c11, 0x5c13, 0x5c13, 0x5c16, 0x5c16, 0x5c19, 0x5c1a, + 0x5c1e, 0x5c1e, 0x5c20, 0x5c20, 0x5c22, 0x5c22, 0x5c24, 0x5c24, + 0x5c28, 0x5c28, 0x5c2d, 0x5c2d, 0x5c31, 0x5c31, 0x5c38, 0x5c41, + 0x5c45, 0x5c46, 0x5c48, 0x5c48, 0x5c4a, 0x5c4b, 0x5c4d, 0x5c51, + 0x5c53, 0x5c53, 0x5c55, 0x5c55, 0x5c5b, 0x5c5b, 0x5c5e, 0x5c5e, + 0x5c60, 0x5c62, 0x5c64, 0x5c65, 0x5c6c, 0x5c6c, 0x5c6e, 0x5c6f, + 0x5c71, 0x5c71, 0x5c76, 0x5c76, 0x5c79, 0x5c79, 0x5c8c, 0x5c8c, + 0x5c90, 0x5c91, 0x5c94, 0x5c94, 0x5ca1, 0x5ca1, 0x5ca6, 0x5ca6, + 0x5ca8, 0x5ca9, 0x5cab, 0x5cac, 0x5cb1, 0x5cb1, 0x5cb3, 0x5cb3, + 0x5cb5, 0x5cb8, 0x5cba, 0x5cbc, 0x5cbe, 0x5cbe, 0x5cc0, 0x5cc0, + 0x5cc5, 0x5cc5, 0x5cc7, 0x5cc7, 0x5cd9, 0x5cd9, 0x5ce0, 0x5ce1, + 0x5ce8, 0x5cea, 0x5ced, 0x5ced, 0x5cef, 0x5cf0, 0x5cf4, 0x5cf6, + 0x5cfa, 0x5cfb, 0x5cfd, 0x5cfd, 0x5d07, 0x5d07, 0x5d0b, 0x5d0b, + 0x5d0d, 0x5d0e, 0x5d11, 0x5d11, 0x5d14, 0x5d1b, 0x5d1f, 0x5d1f, + 0x5d22, 0x5d22, 0x5d27, 0x5d27, 0x5d29, 0x5d29, 0x5d42, 0x5d42, + 0x5d4b, 0x5d4c, 0x5d4e, 0x5d4e, 0x5d50, 0x5d50, 0x5d52, 0x5d53, + 0x5d5c, 0x5d5c, 0x5d69, 0x5d69, 0x5d6c, 0x5d6d, 0x5d6f, 0x5d6f, + 0x5d73, 0x5d73, 0x5d76, 0x5d76, 0x5d82, 0x5d82, 0x5d84, 0x5d84, + 0x5d87, 0x5d87, 0x5d8b, 0x5d8c, 0x5d90, 0x5d90, 0x5d9d, 0x5d9d, + 0x5da0, 0x5da0, 0x5da2, 0x5da2, 0x5daa, 0x5daa, 0x5dac, 0x5dac, + 0x5dae, 0x5dae, 0x5db7, 0x5dba, 0x5dbc, 0x5dbd, 0x5dc9, 0x5dc9, + 0x5dcc, 0x5dcd, 0x5dd0, 0x5dd0, 0x5dd2, 0x5dd3, 0x5dd6, 0x5dd6, + 0x5ddb, 0x5ddb, 0x5ddd, 0x5dde, 0x5de1, 0x5de3, 0x5de5, 0x5de8, + 0x5deb, 0x5deb, 0x5dee, 0x5dee, 0x5df1, 0x5df5, 0x5df7, 0x5df7, + 0x5dfb, 0x5dfb, 0x5dfd, 0x5dfe, 0x5e02, 0x5e03, 0x5e06, 0x5e06, + 0x5e0b, 0x5e0c, 0x5e11, 0x5e11, 0x5e16, 0x5e16, 0x5e19, 0x5e1b, + 0x5e1d, 0x5e1d, 0x5e25, 0x5e25, 0x5e2b, 0x5e2b, 0x5e2d, 0x5e2d, + 0x5e2f, 0x5e30, 0x5e33, 0x5e33, 0x5e36, 0x5e38, 0x5e3d, 0x5e3d, + 0x5e3f, 0x5e40, 0x5e43, 0x5e45, 0x5e47, 0x5e47, 0x5e4c, 0x5e4c, + 0x5e4e, 0x5e4e, 0x5e54, 0x5e55, 0x5e57, 0x5e57, 0x5e5f, 0x5e5f, + 0x5e61, 0x5e64, 0x5e72, 0x5e7f, 0x5e81, 0x5e81, 0x5e83, 0x5e84, + 0x5e87, 0x5e87, 0x5e8a, 0x5e8a, 0x5e8f, 0x5e8f, 0x5e95, 0x5e97, + 0x5e9a, 0x5e9a, 0x5e9c, 0x5e9c, 0x5ea0, 0x5ea0, 0x5ea6, 0x5ea7, + 0x5eab, 0x5eab, 0x5ead, 0x5ead, 0x5eb5, 0x5eb8, 0x5ebe, 0x5ebe, + 0x5ec1, 0x5ec3, 0x5ec8, 0x5eca, 0x5ecf, 0x5ed0, 0x5ed3, 0x5ed3, + 0x5ed6, 0x5ed6, 0x5eda, 0x5edb, 0x5edd, 0x5edd, 0x5edf, 0x5ee3, + 0x5ee8, 0x5ee9, 0x5eec, 0x5eec, 0x5ef0, 0x5ef1, 0x5ef3, 0x5ef4, + 0x5ef6, 0x5ef8, 0x5efa, 0x5efc, 0x5efe, 0x5eff, 0x5f01, 0x5f01, + 0x5f03, 0x5f04, 0x5f09, 0x5f0d, 0x5f0f, 0x5f11, 0x5f13, 0x5f18, + 0x5f1b, 0x5f1b, 0x5f1f, 0x5f1f, 0x5f21, 0x5f21, 0x5f25, 0x5f27, + 0x5f29, 0x5f29, 0x5f2d, 0x5f2d, 0x5f2f, 0x5f2f, 0x5f31, 0x5f31, + 0x5f34, 0x5f35, 0x5f37, 0x5f38, 0x5f3a, 0x5f3a, 0x5f3c, 0x5f3c, + 0x5f3e, 0x5f3e, 0x5f41, 0x5f41, 0x5f45, 0x5f45, 0x5f48, 0x5f48, + 0x5f4a, 0x5f4a, 0x5f4c, 0x5f4c, 0x5f4e, 0x5f4e, 0x5f51, 0x5f51, + 0x5f53, 0x5f53, 0x5f56, 0x5f57, 0x5f59, 0x5f59, 0x5f5b, 0x5f5d, + 0x5f61, 0x5f62, 0x5f66, 0x5f67, 0x5f69, 0x5f6d, 0x5f70, 0x5f71, + 0x5f73, 0x5f73, 0x5f77, 0x5f77, 0x5f79, 0x5f79, 0x5f7c, 0x5f7c, + 0x5f7f, 0x5f85, 0x5f87, 0x5f88, 0x5f8a, 0x5f8c, 0x5f90, 0x5f93, + 0x5f97, 0x5f99, 0x5f9e, 0x5f9e, 0x5fa0, 0x5fa1, 0x5fa8, 0x5faa, + 0x5fad, 0x5fae, 0x5fb3, 0x5fb5, 0x5fb7, 0x5fb7, 0x5fb9, 0x5fb9, + 0x5fbc, 0x5fbd, 0x5fc3, 0x5fc3, 0x5fc5, 0x5fc5, 0x5fcc, 0x5fcd, + 0x5fd6, 0x5fd9, 0x5fdc, 0x5fde, 0x5fe0, 0x5fe0, 0x5fe4, 0x5fe4, + 0x5feb, 0x5feb, 0x5ff0, 0x5ff1, 0x5ff5, 0x5ff5, 0x5ff8, 0x5ff8, + 0x5ffb, 0x5ffb, 0x5ffd, 0x5ffd, 0x5fff, 0x5fff, 0x600e, 0x6010, + 0x6012, 0x6012, 0x6015, 0x6016, 0x6019, 0x6019, 0x601b, 0x601d, + 0x6020, 0x6021, 0x6025, 0x602b, 0x602f, 0x602f, 0x6031, 0x6031, + 0x603a, 0x603a, 0x6041, 0x6043, 0x6046, 0x6046, 0x604a, 0x604b, + 0x604d, 0x604d, 0x6050, 0x6050, 0x6052, 0x6052, 0x6055, 0x6055, + 0x6059, 0x605a, 0x605d, 0x605d, 0x605f, 0x6060, 0x6062, 0x6065, + 0x6068, 0x606d, 0x606f, 0x6070, 0x6075, 0x6075, 0x6077, 0x6077, + 0x6081, 0x6081, 0x6083, 0x6085, 0x6089, 0x608d, 0x6092, 0x6092, + 0x6094, 0x6094, 0x6096, 0x6097, 0x609a, 0x609b, 0x609f, 0x60a0, + 0x60a3, 0x60a4, 0x60a6, 0x60a7, 0x60a9, 0x60aa, 0x60b0, 0x60b0, + 0x60b2, 0x60b6, 0x60b8, 0x60b8, 0x60bc, 0x60bd, 0x60c5, 0x60c7, + 0x60d1, 0x60d1, 0x60d3, 0x60d3, 0x60d5, 0x60d5, 0x60d8, 0x60d8, + 0x60da, 0x60da, 0x60dc, 0x60dc, 0x60de, 0x60e1, 0x60e3, 0x60e3, + 0x60e7, 0x60e8, 0x60f0, 0x60f4, 0x60f6, 0x60f7, 0x60f9, 0x60fb, + 0x6100, 0x6101, 0x6103, 0x6103, 0x6106, 0x6106, 0x6108, 0x6109, + 0x610d, 0x610f, 0x6111, 0x6111, 0x6115, 0x6115, 0x611a, 0x611b, + 0x611f, 0x6121, 0x6127, 0x6128, 0x612c, 0x612c, 0x6130, 0x6130, + 0x6134, 0x6134, 0x6137, 0x6137, 0x613c, 0x613f, 0x6142, 0x6142, + 0x6144, 0x6144, 0x6147, 0x6148, 0x614a, 0x614e, 0x6153, 0x6153, + 0x6155, 0x6155, 0x6158, 0x615a, 0x615d, 0x615d, 0x615f, 0x615f, + 0x6162, 0x6165, 0x6167, 0x6168, 0x616b, 0x616b, 0x616e, 0x6171, + 0x6173, 0x6177, 0x617d, 0x617e, 0x6181, 0x6182, 0x6187, 0x6187, + 0x618a, 0x618a, 0x618e, 0x618e, 0x6190, 0x6191, 0x6194, 0x6194, + 0x6196, 0x6196, 0x6198, 0x619a, 0x61a4, 0x61a4, 0x61a7, 0x61a7, + 0x61a9, 0x61a9, 0x61ab, 0x61ac, 0x61ae, 0x61ae, 0x61b2, 0x61b2, + 0x61b6, 0x61b6, 0x61ba, 0x61ba, 0x61be, 0x61be, 0x61c3, 0x61c3, + 0x61c6, 0x61cd, 0x61d0, 0x61d0, 0x61e3, 0x61e3, 0x61e6, 0x61e6, + 0x61f2, 0x61f2, 0x61f4, 0x61f4, 0x61f6, 0x61f8, 0x61fa, 0x61fa, + 0x61fc, 0x6200, 0x6207, 0x620a, 0x620c, 0x620e, 0x6210, 0x6214, + 0x6216, 0x6216, 0x621a, 0x621b, 0x621d, 0x621f, 0x6221, 0x6221, + 0x6226, 0x6226, 0x622a, 0x622a, 0x622e, 0x6234, 0x6236, 0x6236, + 0x6238, 0x6238, 0x623b, 0x623b, 0x623e, 0x6241, 0x6247, 0x6249, + 0x624b, 0x624b, 0x624d, 0x624e, 0x6253, 0x6253, 0x6255, 0x6255, + 0x6258, 0x6258, 0x625b, 0x625b, 0x625e, 0x625e, 0x6260, 0x6260, + 0x6263, 0x6263, 0x6268, 0x6268, 0x626e, 0x626e, 0x6271, 0x6271, + 0x6276, 0x6276, 0x6279, 0x6279, 0x627c, 0x627c, 0x627e, 0x6280, + 0x6282, 0x6284, 0x6289, 0x628a, 0x6291, 0x6298, 0x629b, 0x629c, + 0x629e, 0x629e, 0x62a6, 0x62a6, 0x62ab, 0x62ac, 0x62b1, 0x62b1, + 0x62b5, 0x62b5, 0x62b9, 0x62b9, 0x62bb, 0x62bd, 0x62c2, 0x62c2, + 0x62c5, 0x62ca, 0x62cc, 0x62cd, 0x62cf, 0x62d4, 0x62d6, 0x62d9, + 0x62db, 0x62dd, 0x62e0, 0x62e1, 0x62ec, 0x62ef, 0x62f1, 0x62f1, + 0x62f3, 0x62f3, 0x62f5, 0x62f7, 0x62fe, 0x62ff, 0x6301, 0x6302, + 0x6307, 0x6309, 0x630c, 0x630c, 0x6311, 0x6311, 0x6319, 0x6319, + 0x631f, 0x631f, 0x6327, 0x6328, 0x632b, 0x632b, 0x632f, 0x632f, + 0x633a, 0x633b, 0x633d, 0x633f, 0x6349, 0x6349, 0x634c, 0x634d, + 0x634f, 0x6350, 0x6355, 0x6355, 0x6357, 0x6357, 0x635c, 0x635c, + 0x6367, 0x6369, 0x636b, 0x636b, 0x636e, 0x636e, 0x6372, 0x6372, + 0x6376, 0x6377, 0x637a, 0x637b, 0x637f, 0x6380, 0x6383, 0x6383, + 0x6388, 0x6389, 0x638c, 0x638c, 0x638e, 0x638f, 0x6392, 0x6392, + 0x6396, 0x6396, 0x6398, 0x6398, 0x639b, 0x639b, 0x639f, 0x63a3, + 0x63a5, 0x63a5, 0x63a7, 0x63ac, 0x63b2, 0x63b2, 0x63b4, 0x63b5, + 0x63bb, 0x63bb, 0x63be, 0x63be, 0x63c0, 0x63c0, 0x63c3, 0x63c4, + 0x63c6, 0x63c6, 0x63c9, 0x63c9, 0x63cf, 0x63d0, 0x63d2, 0x63d2, + 0x63d6, 0x63d6, 0x63da, 0x63db, 0x63e1, 0x63e1, 0x63e3, 0x63e3, + 0x63e9, 0x63e9, 0x63ed, 0x63ee, 0x63f4, 0x63f7, 0x63fa, 0x63fa, + 0x6406, 0x6406, 0x640d, 0x640d, 0x640f, 0x640f, 0x6413, 0x6414, + 0x6416, 0x6417, 0x641c, 0x641c, 0x6422, 0x6422, 0x6426, 0x6426, + 0x6428, 0x6428, 0x642c, 0x642d, 0x6434, 0x6434, 0x6436, 0x6436, + 0x643a, 0x643a, 0x643e, 0x643e, 0x6442, 0x6442, 0x644e, 0x644e, + 0x6458, 0x6458, 0x6460, 0x6460, 0x6467, 0x6467, 0x6469, 0x6469, + 0x646f, 0x646f, 0x6476, 0x6476, 0x6478, 0x647a, 0x6483, 0x6483, + 0x6488, 0x6488, 0x6491, 0x6493, 0x6495, 0x6495, 0x649a, 0x649a, + 0x649d, 0x649e, 0x64a4, 0x64a5, 0x64a9, 0x64a9, 0x64ab, 0x64ab, + 0x64ad, 0x64ae, 0x64b0, 0x64b0, 0x64b2, 0x64b2, 0x64b9, 0x64b9, + 0x64bb, 0x64bc, 0x64c1, 0x64c2, 0x64c4, 0x64c5, 0x64c7, 0x64c7, + 0x64ca, 0x64ca, 0x64cd, 0x64ce, 0x64d2, 0x64d2, 0x64d4, 0x64d4, + 0x64d8, 0x64d8, 0x64da, 0x64da, 0x64e0, 0x64e3, 0x64e5, 0x64e7, + 0x64ec, 0x64ec, 0x64ef, 0x64ef, 0x64f1, 0x64f2, 0x64f4, 0x64f4, + 0x64f6, 0x64f6, 0x64fa, 0x64fa, 0x64fd, 0x64fe, 0x6500, 0x6500, + 0x6504, 0x6505, 0x6518, 0x6518, 0x651c, 0x651d, 0x6523, 0x6524, + 0x652a, 0x652c, 0x652f, 0x652f, 0x6534, 0x6539, 0x653b, 0x653b, + 0x653e, 0x653f, 0x6545, 0x6545, 0x6548, 0x6548, 0x654d, 0x654f, + 0x6551, 0x6551, 0x6555, 0x6559, 0x655d, 0x655e, 0x6562, 0x6563, + 0x6566, 0x6566, 0x656c, 0x656d, 0x6570, 0x6570, 0x6572, 0x6572, + 0x6574, 0x6575, 0x6577, 0x6578, 0x657e, 0x657e, 0x6582, 0x6583, + 0x6585, 0x6585, 0x6587, 0x6589, 0x658c, 0x658c, 0x658e, 0x658e, + 0x6590, 0x6591, 0x6597, 0x6597, 0x6599, 0x6599, 0x659b, 0x659c, + 0x659f, 0x659f, 0x65a1, 0x65a1, 0x65a4, 0x65a5, 0x65a7, 0x65a7, + 0x65ab, 0x65ad, 0x65af, 0x65b0, 0x65b7, 0x65b7, 0x65b9, 0x65b9, + 0x65bc, 0x65bd, 0x65c1, 0x65c1, 0x65c3, 0x65c6, 0x65cb, 0x65cc, + 0x65cf, 0x65cf, 0x65d2, 0x65d2, 0x65d7, 0x65d7, 0x65d9, 0x65d9, + 0x65db, 0x65db, 0x65e0, 0x65e3, 0x65e5, 0x65e9, 0x65ec, 0x65ed, + 0x65f1, 0x65f1, 0x65f4, 0x65f4, 0x65fa, 0x65fd, 0x65ff, 0x6600, + 0x6602, 0x6603, 0x6606, 0x6607, 0x6609, 0x660a, 0x660c, 0x660c, + 0x660e, 0x6611, 0x6613, 0x6615, 0x661c, 0x661c, 0x661e, 0x6620, + 0x6624, 0x6625, 0x6627, 0x6628, 0x662d, 0x6631, 0x6634, 0x6636, + 0x663a, 0x663c, 0x663f, 0x663f, 0x6641, 0x6644, 0x6649, 0x6649, + 0x664b, 0x664b, 0x664f, 0x664f, 0x6652, 0x6652, 0x6657, 0x6657, + 0x6659, 0x6659, 0x665b, 0x665b, 0x665d, 0x665f, 0x6662, 0x6662, + 0x6664, 0x6669, 0x666b, 0x666b, 0x666e, 0x6670, 0x6673, 0x6674, + 0x6676, 0x6678, 0x667a, 0x667a, 0x6681, 0x6681, 0x6683, 0x6684, + 0x6687, 0x6689, 0x668e, 0x668e, 0x6690, 0x6691, 0x6696, 0x6699, + 0x669d, 0x669d, 0x66a0, 0x66a0, 0x66a2, 0x66a2, 0x66a6, 0x66a6, + 0x66ab, 0x66ab, 0x66ae, 0x66ae, 0x66b2, 0x66b4, 0x66b8, 0x66b9, + 0x66bb, 0x66bc, 0x66be, 0x66bf, 0x66c1, 0x66c1, 0x66c4, 0x66c4, + 0x66c6, 0x66c7, 0x66c9, 0x66c9, 0x66d6, 0x66d6, 0x66d9, 0x66da, + 0x66dc, 0x66dd, 0x66e0, 0x66e0, 0x66e6, 0x66e6, 0x66e9, 0x66e9, + 0x66f0, 0x66f0, 0x66f2, 0x66f5, 0x66f7, 0x6700, 0x6703, 0x6703, + 0x6708, 0x6709, 0x670b, 0x670b, 0x670d, 0x670f, 0x6714, 0x6717, + 0x671b, 0x671b, 0x671d, 0x671f, 0x6726, 0x6728, 0x672a, 0x672e, + 0x6731, 0x6731, 0x6734, 0x6734, 0x6736, 0x6738, 0x673a, 0x673a, + 0x673d, 0x673d, 0x673f, 0x673f, 0x6741, 0x6741, 0x6746, 0x6746, + 0x6749, 0x6749, 0x674e, 0x6751, 0x6753, 0x6753, 0x6756, 0x6756, + 0x6759, 0x6759, 0x675c, 0x675c, 0x675e, 0x6766, 0x676a, 0x676a, + 0x676d, 0x676d, 0x676f, 0x6773, 0x6775, 0x6775, 0x6777, 0x6777, + 0x677b, 0x677c, 0x677e, 0x677f, 0x6785, 0x6785, 0x6787, 0x6787, + 0x6789, 0x6789, 0x678b, 0x678c, 0x678f, 0x6790, 0x6793, 0x6793, + 0x6795, 0x6795, 0x6797, 0x6797, 0x679a, 0x679a, 0x679c, 0x679d, + 0x67a0, 0x67a2, 0x67a6, 0x67a6, 0x67a9, 0x67a9, 0x67af, 0x67b0, + 0x67b3, 0x67b4, 0x67b6, 0x67b9, 0x67bb, 0x67bb, 0x67be, 0x67be, + 0x67c0, 0x67c1, 0x67c4, 0x67c4, 0x67c6, 0x67c6, 0x67ca, 0x67ca, + 0x67ce, 0x67d4, 0x67d8, 0x67d8, 0x67da, 0x67da, 0x67dd, 0x67de, + 0x67e2, 0x67e2, 0x67e4, 0x67e4, 0x67e7, 0x67e7, 0x67e9, 0x67e9, + 0x67ec, 0x67ec, 0x67ee, 0x67f1, 0x67f3, 0x67f6, 0x67fb, 0x67fb, + 0x67fe, 0x67ff, 0x6801, 0x6804, 0x6812, 0x6813, 0x6816, 0x6817, + 0x681e, 0x681e, 0x6821, 0x6822, 0x6829, 0x682b, 0x682f, 0x682f, + 0x6832, 0x6832, 0x6834, 0x6834, 0x6838, 0x6839, 0x683c, 0x683d, + 0x6840, 0x6844, 0x6846, 0x6846, 0x6848, 0x6848, 0x684d, 0x684e, + 0x6850, 0x6854, 0x6859, 0x6859, 0x685c, 0x685d, 0x685f, 0x685f, + 0x6863, 0x6863, 0x6867, 0x6867, 0x686d, 0x686d, 0x6874, 0x6874, + 0x6876, 0x6877, 0x687e, 0x687f, 0x6881, 0x6881, 0x6883, 0x6883, + 0x6885, 0x6885, 0x688d, 0x688d, 0x688f, 0x688f, 0x6893, 0x6894, + 0x6897, 0x6897, 0x689b, 0x689b, 0x689d, 0x689d, 0x689f, 0x68a2, + 0x68a6, 0x68a8, 0x68ad, 0x68ad, 0x68af, 0x68b1, 0x68b3, 0x68b3, + 0x68b5, 0x68b6, 0x68b9, 0x68ba, 0x68bc, 0x68bc, 0x68c4, 0x68c6, + 0x68c8, 0x68cb, 0x68cd, 0x68cd, 0x68cf, 0x68cf, 0x68d2, 0x68d2, + 0x68d4, 0x68d5, 0x68d7, 0x68d8, 0x68da, 0x68da, 0x68df, 0x68e1, + 0x68e3, 0x68e3, 0x68e7, 0x68e8, 0x68ee, 0x68ef, 0x68f2, 0x68f2, + 0x68f9, 0x68fa, 0x6900, 0x6901, 0x6904, 0x6905, 0x6908, 0x6908, + 0x690b, 0x690f, 0x6912, 0x6912, 0x6919, 0x691c, 0x6921, 0x6923, + 0x6925, 0x6928, 0x692a, 0x692a, 0x6930, 0x6930, 0x6934, 0x6934, + 0x6936, 0x6936, 0x6939, 0x6939, 0x693d, 0x693d, 0x693f, 0x693f, + 0x694a, 0x694a, 0x6953, 0x6955, 0x6957, 0x6957, 0x6959, 0x695a, + 0x695c, 0x695e, 0x6960, 0x6963, 0x6968, 0x6968, 0x696a, 0x696b, + 0x696d, 0x696f, 0x6973, 0x6975, 0x6977, 0x6979, 0x697c, 0x697e, + 0x6981, 0x6982, 0x698a, 0x698a, 0x698e, 0x698e, 0x6991, 0x6991, + 0x6994, 0x6995, 0x6998, 0x6998, 0x699b, 0x699c, 0x69a0, 0x69a0, + 0x69a5, 0x69a5, 0x69a7, 0x69a7, 0x69ae, 0x69ae, 0x69b1, 0x69b2, + 0x69b4, 0x69b4, 0x69bb, 0x69bb, 0x69be, 0x69bf, 0x69c1, 0x69c1, + 0x69c3, 0x69c3, 0x69c7, 0x69c7, 0x69ca, 0x69ce, 0x69d0, 0x69d0, + 0x69d3, 0x69d3, 0x69d8, 0x69d9, 0x69dd, 0x69de, 0x69e2, 0x69e2, + 0x69e7, 0x69e8, 0x69ea, 0x69eb, 0x69ed, 0x69ed, 0x69f2, 0x69f2, + 0x69f9, 0x69f9, 0x69fb, 0x69fb, 0x69fd, 0x69fd, 0x69ff, 0x69ff, + 0x6a02, 0x6a02, 0x6a05, 0x6a05, 0x6a0a, 0x6a0c, 0x6a11, 0x6a14, + 0x6a17, 0x6a17, 0x6a19, 0x6a19, 0x6a1b, 0x6a1b, 0x6a1e, 0x6a1f, + 0x6a21, 0x6a23, 0x6a29, 0x6a2b, 0x6a2e, 0x6a2e, 0x6a30, 0x6a30, + 0x6a35, 0x6a36, 0x6a38, 0x6a3a, 0x6a3d, 0x6a3d, 0x6a44, 0x6a44, + 0x6a46, 0x6a48, 0x6a4b, 0x6a4b, 0x6a52, 0x6a53, 0x6a58, 0x6a59, + 0x6a5f, 0x6a5f, 0x6a61, 0x6a62, 0x6a66, 0x6a66, 0x6a6b, 0x6a6b, + 0x6a72, 0x6a73, 0x6a78, 0x6a78, 0x6a7e, 0x6a80, 0x6a84, 0x6a84, + 0x6a89, 0x6a89, 0x6a8d, 0x6a8e, 0x6a90, 0x6a90, 0x6a97, 0x6a97, + 0x6a9c, 0x6a9c, 0x6aa0, 0x6aa0, 0x6aa2, 0x6aa3, 0x6aaa, 0x6aaa, + 0x6aac, 0x6aac, 0x6aae, 0x6aae, 0x6ab3, 0x6ab3, 0x6ab8, 0x6ab8, + 0x6abb, 0x6abb, 0x6ac1, 0x6ac3, 0x6ad1, 0x6ad1, 0x6ad3, 0x6ad3, + 0x6ada, 0x6adb, 0x6ade, 0x6adf, 0x6ae2, 0x6ae2, 0x6ae4, 0x6ae4, + 0x6ae8, 0x6ae8, 0x6aea, 0x6aea, 0x6af6, 0x6af6, 0x6afa, 0x6afb, + 0x6b04, 0x6b05, 0x6b0a, 0x6b0a, 0x6b0c, 0x6b0c, 0x6b12, 0x6b12, + 0x6b16, 0x6b16, 0x6b1d, 0x6b1d, 0x6b1f, 0x6b21, 0x6b23, 0x6b23, + 0x6b27, 0x6b27, 0x6b32, 0x6b32, 0x6b37, 0x6b3a, 0x6b3d, 0x6b3e, + 0x6b43, 0x6b43, 0x6b46, 0x6b47, 0x6b49, 0x6b49, 0x6b4c, 0x6b4c, + 0x6b4e, 0x6b4e, 0x6b50, 0x6b50, 0x6b53, 0x6b54, 0x6b59, 0x6b59, + 0x6b5b, 0x6b5b, 0x6b5f, 0x6b5f, 0x6b61, 0x6b66, 0x6b69, 0x6b6a, + 0x6b6f, 0x6b6f, 0x6b72, 0x6b74, 0x6b77, 0x6b79, 0x6b7b, 0x6b7b, + 0x6b7f, 0x6b80, 0x6b83, 0x6b84, 0x6b86, 0x6b86, 0x6b89, 0x6b8b, + 0x6b8d, 0x6b8d, 0x6b95, 0x6b96, 0x6b98, 0x6b98, 0x6b9e, 0x6b9e, + 0x6ba4, 0x6ba4, 0x6baa, 0x6bab, 0x6bae, 0x6baf, 0x6bb1, 0x6bb5, + 0x6bb7, 0x6bb7, 0x6bba, 0x6bbc, 0x6bbf, 0x6bc1, 0x6bc5, 0x6bc6, + 0x6bcb, 0x6bcb, 0x6bcd, 0x6bcf, 0x6bd2, 0x6bd4, 0x6bd6, 0x6bd8, + 0x6bdb, 0x6bdb, 0x6bdf, 0x6bdf, 0x6beb, 0x6bec, 0x6bef, 0x6bef, + 0x6bf3, 0x6bf3, 0x6c08, 0x6c08, 0x6c0f, 0x6c0f, 0x6c11, 0x6c11, + 0x6c13, 0x6c14, 0x6c17, 0x6c17, 0x6c1b, 0x6c1b, 0x6c23, 0x6c24, + 0x6c34, 0x6c34, 0x6c37, 0x6c38, 0x6c3e, 0x6c42, 0x6c4e, 0x6c4e, + 0x6c50, 0x6c50, 0x6c55, 0x6c55, 0x6c57, 0x6c57, 0x6c5a, 0x6c5a, + 0x6c5c, 0x6c60, 0x6c62, 0x6c62, 0x6c68, 0x6c68, 0x6c6a, 0x6c6a, + 0x6c6d, 0x6c6d, 0x6c6f, 0x6c70, 0x6c72, 0x6c73, 0x6c76, 0x6c76, + 0x6c7a, 0x6c7a, 0x6c7d, 0x6c7e, 0x6c81, 0x6c83, 0x6c85, 0x6c88, + 0x6c8c, 0x6c8d, 0x6c90, 0x6c90, 0x6c92, 0x6c96, 0x6c99, 0x6c9b, + 0x6ca1, 0x6ca2, 0x6cab, 0x6cab, 0x6cae, 0x6cae, 0x6cb1, 0x6cb1, + 0x6cb3, 0x6cb3, 0x6cb8, 0x6cbf, 0x6cc1, 0x6cc2, 0x6cc4, 0x6cc5, + 0x6cc9, 0x6cca, 0x6ccc, 0x6ccc, 0x6cd3, 0x6cd3, 0x6cd5, 0x6cd5, + 0x6cd7, 0x6cd7, 0x6cd9, 0x6cdb, 0x6cdd, 0x6cdd, 0x6ce1, 0x6ce3, + 0x6ce5, 0x6ce5, 0x6ce8, 0x6ce8, 0x6cea, 0x6ceb, 0x6cee, 0x6cf1, + 0x6cf3, 0x6cf3, 0x6d04, 0x6d04, 0x6d0b, 0x6d0c, 0x6d11, 0x6d12, + 0x6d17, 0x6d17, 0x6d19, 0x6d19, 0x6d1b, 0x6d1b, 0x6d1e, 0x6d1f, + 0x6d25, 0x6d25, 0x6d27, 0x6d27, 0x6d29, 0x6d2b, 0x6d32, 0x6d33, + 0x6d35, 0x6d36, 0x6d38, 0x6d39, 0x6d3b, 0x6d3b, 0x6d3d, 0x6d3e, + 0x6d41, 0x6d41, 0x6d44, 0x6d45, 0x6d59, 0x6d5a, 0x6d5c, 0x6d5c, + 0x6d63, 0x6d64, 0x6d66, 0x6d66, 0x6d69, 0x6d6a, 0x6d6c, 0x6d6c, + 0x6d6e, 0x6d6f, 0x6d74, 0x6d74, 0x6d77, 0x6d79, 0x6d7f, 0x6d7f, + 0x6d85, 0x6d85, 0x6d87, 0x6d89, 0x6d8c, 0x6d8e, 0x6d91, 0x6d91, + 0x6d93, 0x6d93, 0x6d95, 0x6d96, 0x6d99, 0x6d99, 0x6d9b, 0x6d9c, + 0x6dac, 0x6dac, 0x6daf, 0x6daf, 0x6db2, 0x6db2, 0x6db5, 0x6db5, + 0x6db8, 0x6db8, 0x6dbc, 0x6dbc, 0x6dc0, 0x6dc0, 0x6dc3, 0x6dc7, + 0x6dcb, 0x6dcc, 0x6dcf, 0x6dcf, 0x6dd1, 0x6dd2, 0x6dd5, 0x6dd5, + 0x6dd8, 0x6dda, 0x6dde, 0x6dde, 0x6de1, 0x6de1, 0x6de4, 0x6de4, + 0x6de6, 0x6de6, 0x6de8, 0x6de8, 0x6dea, 0x6dec, 0x6dee, 0x6dee, + 0x6df1, 0x6df3, 0x6df5, 0x6df5, 0x6df7, 0x6dfc, 0x6e05, 0x6e05, + 0x6e07, 0x6e0b, 0x6e13, 0x6e13, 0x6e15, 0x6e15, 0x6e17, 0x6e17, + 0x6e19, 0x6e1b, 0x6e1d, 0x6e1d, 0x6e1f, 0x6e21, 0x6e23, 0x6e27, + 0x6e29, 0x6e29, 0x6e2b, 0x6e2f, 0x6e32, 0x6e32, 0x6e34, 0x6e34, + 0x6e36, 0x6e36, 0x6e38, 0x6e3a, 0x6e3c, 0x6e3e, 0x6e43, 0x6e44, + 0x6e4a, 0x6e4a, 0x6e4d, 0x6e4e, 0x6e56, 0x6e56, 0x6e58, 0x6e58, + 0x6e5b, 0x6e5c, 0x6e5e, 0x6e5f, 0x6e67, 0x6e67, 0x6e6b, 0x6e6b, + 0x6e6e, 0x6e6f, 0x6e72, 0x6e73, 0x6e76, 0x6e76, 0x6e7a, 0x6e7a, + 0x6e7e, 0x6e80, 0x6e82, 0x6e82, 0x6e8c, 0x6e8c, 0x6e8f, 0x6e90, + 0x6e96, 0x6e96, 0x6e98, 0x6e98, 0x6e9c, 0x6e9d, 0x6e9f, 0x6e9f, + 0x6ea2, 0x6ea2, 0x6ea5, 0x6ea5, 0x6eaa, 0x6eab, 0x6eaf, 0x6eaf, + 0x6eb1, 0x6eb2, 0x6eb6, 0x6eb7, 0x6eba, 0x6eba, 0x6ebd, 0x6ebd, + 0x6ebf, 0x6ebf, 0x6ec2, 0x6ec2, 0x6ec4, 0x6ec5, 0x6ec9, 0x6ec9, + 0x6ecb, 0x6ecc, 0x6ece, 0x6ece, 0x6ed1, 0x6ed1, 0x6ed3, 0x6ed5, + 0x6edd, 0x6ede, 0x6eec, 0x6eec, 0x6eef, 0x6eef, 0x6ef2, 0x6ef2, + 0x6ef4, 0x6ef4, 0x6ef7, 0x6ef8, 0x6efe, 0x6eff, 0x6f01, 0x6f02, + 0x6f06, 0x6f06, 0x6f09, 0x6f09, 0x6f0f, 0x6f0f, 0x6f11, 0x6f11, + 0x6f13, 0x6f15, 0x6f20, 0x6f20, 0x6f22, 0x6f23, 0x6f2b, 0x6f2c, + 0x6f31, 0x6f32, 0x6f38, 0x6f38, 0x6f3e, 0x6f3f, 0x6f41, 0x6f41, + 0x6f45, 0x6f45, 0x6f51, 0x6f51, 0x6f54, 0x6f54, 0x6f57, 0x6f58, + 0x6f5a, 0x6f5c, 0x6f5e, 0x6f5f, 0x6f62, 0x6f62, 0x6f64, 0x6f64, + 0x6f66, 0x6f66, 0x6f6d, 0x6f70, 0x6f74, 0x6f74, 0x6f78, 0x6f78, + 0x6f7a, 0x6f7a, 0x6f7c, 0x6f7e, 0x6f80, 0x6f82, 0x6f84, 0x6f84, + 0x6f86, 0x6f86, 0x6f88, 0x6f88, 0x6f8d, 0x6f8e, 0x6f90, 0x6f91, + 0x6f94, 0x6f94, 0x6f97, 0x6f97, 0x6fa1, 0x6fa1, 0x6fa3, 0x6fa4, + 0x6fa7, 0x6fa7, 0x6faa, 0x6faa, 0x6fae, 0x6faf, 0x6fb1, 0x6fb1, + 0x6fb3, 0x6fb3, 0x6fb5, 0x6fb5, 0x6fb9, 0x6fb9, 0x6fbe, 0x6fbe, + 0x6fc0, 0x6fc3, 0x6fc6, 0x6fc6, 0x6fca, 0x6fca, 0x6fd4, 0x6fd5, + 0x6fd8, 0x6fd8, 0x6fda, 0x6fdb, 0x6fdf, 0x6fe1, 0x6fe4, 0x6fe4, + 0x6fe9, 0x6fe9, 0x6feb, 0x6fec, 0x6fee, 0x6fef, 0x6ff1, 0x6ff1, + 0x6ff3, 0x6ff3, 0x6ff5, 0x6ff6, 0x6ffa, 0x6ffa, 0x6ffe, 0x6ffe, + 0x7001, 0x7001, 0x7005, 0x7007, 0x7009, 0x7009, 0x700b, 0x700b, + 0x700f, 0x700f, 0x7011, 0x7011, 0x7015, 0x7015, 0x7018, 0x7018, + 0x701a, 0x701f, 0x7023, 0x7023, 0x7026, 0x7028, 0x702c, 0x702c, + 0x702f, 0x7030, 0x7032, 0x7032, 0x7037, 0x7037, 0x703e, 0x703e, + 0x704c, 0x704c, 0x7050, 0x7051, 0x7058, 0x7058, 0x705d, 0x705d, + 0x7063, 0x7063, 0x706b, 0x706b, 0x706f, 0x7070, 0x7078, 0x7078, + 0x707c, 0x707d, 0x7085, 0x7085, 0x7089, 0x708a, 0x708e, 0x708e, + 0x7092, 0x7092, 0x7098, 0x709a, 0x70a1, 0x70a1, 0x70a4, 0x70a4, + 0x70ab, 0x70af, 0x70b3, 0x70b3, 0x70b7, 0x70bb, 0x70c8, 0x70c8, + 0x70cb, 0x70cb, 0x70cf, 0x70cf, 0x70d8, 0x70d9, 0x70dd, 0x70dd, + 0x70df, 0x70df, 0x70f1, 0x70f1, 0x70f9, 0x70f9, 0x70fd, 0x70fd, + 0x7104, 0x7104, 0x7109, 0x7109, 0x710c, 0x710c, 0x710f, 0x710f, + 0x7114, 0x7114, 0x7119, 0x711a, 0x711c, 0x711c, 0x711e, 0x711e, + 0x7121, 0x7121, 0x7126, 0x7126, 0x7130, 0x7130, 0x7136, 0x7136, + 0x713c, 0x713c, 0x7146, 0x7147, 0x7149, 0x714a, 0x714c, 0x714c, + 0x714e, 0x714e, 0x7150, 0x7150, 0x7155, 0x7156, 0x7159, 0x7159, + 0x715c, 0x715c, 0x715e, 0x715e, 0x7162, 0x7162, 0x7164, 0x7167, + 0x7169, 0x7169, 0x716c, 0x716c, 0x716e, 0x716e, 0x717d, 0x717d, + 0x7184, 0x7184, 0x7188, 0x718a, 0x718f, 0x718f, 0x7192, 0x7192, + 0x7194, 0x7195, 0x7199, 0x7199, 0x719f, 0x719f, 0x71a2, 0x71a2, + 0x71a8, 0x71a8, 0x71ac, 0x71ac, 0x71b1, 0x71b1, 0x71b9, 0x71ba, + 0x71be, 0x71be, 0x71c1, 0x71c1, 0x71c3, 0x71c3, 0x71c8, 0x71c9, + 0x71ce, 0x71ce, 0x71d0, 0x71d0, 0x71d2, 0x71d2, 0x71d4, 0x71d5, + 0x71d7, 0x71d7, 0x71df, 0x71e0, 0x71e5, 0x71e7, 0x71ec, 0x71ee, + 0x71f5, 0x71f5, 0x71f9, 0x71f9, 0x71fb, 0x71fc, 0x71fe, 0x7200, + 0x7206, 0x7206, 0x720d, 0x720d, 0x7210, 0x7210, 0x721b, 0x721b, + 0x7228, 0x7228, 0x722a, 0x722a, 0x722c, 0x722d, 0x7230, 0x7230, + 0x7232, 0x7232, 0x7235, 0x7236, 0x723a, 0x7240, 0x7246, 0x7248, + 0x724b, 0x724c, 0x7252, 0x7252, 0x7258, 0x7259, 0x725b, 0x725b, + 0x725d, 0x725d, 0x725f, 0x725f, 0x7261, 0x7262, 0x7267, 0x7267, + 0x7269, 0x7269, 0x7272, 0x7272, 0x7274, 0x7274, 0x7279, 0x7279, + 0x727d, 0x727e, 0x7280, 0x7282, 0x7287, 0x7287, 0x7292, 0x7292, + 0x7296, 0x7296, 0x72a0, 0x72a0, 0x72a2, 0x72a2, 0x72a7, 0x72a7, + 0x72ac, 0x72ac, 0x72af, 0x72af, 0x72b1, 0x72b2, 0x72b6, 0x72b6, + 0x72b9, 0x72b9, 0x72be, 0x72be, 0x72c0, 0x72c0, 0x72c2, 0x72c4, + 0x72c6, 0x72c6, 0x72ce, 0x72ce, 0x72d0, 0x72d0, 0x72d2, 0x72d2, + 0x72d7, 0x72d7, 0x72d9, 0x72d9, 0x72db, 0x72db, 0x72e0, 0x72e2, + 0x72e9, 0x72e9, 0x72ec, 0x72ed, 0x72f7, 0x72f9, 0x72fc, 0x72fd, + 0x730a, 0x730a, 0x7316, 0x7317, 0x731b, 0x731d, 0x731f, 0x731f, + 0x7324, 0x7325, 0x7329, 0x732b, 0x732e, 0x732f, 0x7334, 0x7334, + 0x7336, 0x7337, 0x733e, 0x733f, 0x7344, 0x7345, 0x734e, 0x7350, + 0x7352, 0x7352, 0x7357, 0x7357, 0x7363, 0x7363, 0x7368, 0x7368, + 0x736a, 0x736a, 0x7370, 0x7370, 0x7372, 0x7372, 0x7375, 0x7375, + 0x7377, 0x7378, 0x737a, 0x737b, 0x7384, 0x7384, 0x7386, 0x7387, + 0x7389, 0x7389, 0x738b, 0x738b, 0x738e, 0x738e, 0x7394, 0x7394, + 0x7396, 0x7398, 0x739f, 0x739f, 0x73a7, 0x73a7, 0x73a9, 0x73a9, + 0x73ad, 0x73ad, 0x73b2, 0x73b3, 0x73b9, 0x73b9, 0x73bb, 0x73bb, + 0x73bd, 0x73bd, 0x73c0, 0x73c0, 0x73c2, 0x73c2, 0x73c8, 0x73ca, + 0x73cc, 0x73cf, 0x73d2, 0x73d2, 0x73d6, 0x73d6, 0x73d9, 0x73d9, + 0x73dd, 0x73de, 0x73e0, 0x73e0, 0x73e3, 0x73e6, 0x73e9, 0x73ea, + 0x73ed, 0x73ee, 0x73f1, 0x73f1, 0x73f5, 0x73f5, 0x73f7, 0x73f9, + 0x73fd, 0x73fe, 0x7401, 0x7401, 0x7403, 0x7403, 0x7405, 0x7407, + 0x7409, 0x7409, 0x7413, 0x7413, 0x741b, 0x741b, 0x7420, 0x7422, + 0x7425, 0x7426, 0x7428, 0x742c, 0x742e, 0x7430, 0x7432, 0x7436, + 0x7438, 0x7438, 0x743a, 0x743a, 0x743f, 0x7441, 0x7443, 0x7444, + 0x744b, 0x744b, 0x7455, 0x7455, 0x7457, 0x7457, 0x7459, 0x745c, + 0x745e, 0x7460, 0x7462, 0x7465, 0x7468, 0x746a, 0x746f, 0x7470, + 0x7473, 0x7473, 0x7476, 0x7476, 0x747e, 0x747e, 0x7482, 0x7483, + 0x7487, 0x7487, 0x7489, 0x7489, 0x748b, 0x748b, 0x7498, 0x7498, + 0x749c, 0x749c, 0x749e, 0x749f, 0x74a1, 0x74a3, 0x74a5, 0x74a5, + 0x74a7, 0x74a8, 0x74aa, 0x74aa, 0x74b0, 0x74b0, 0x74b2, 0x74b2, + 0x74b5, 0x74b5, 0x74b9, 0x74b9, 0x74bd, 0x74bd, 0x74bf, 0x74bf, + 0x74c6, 0x74c6, 0x74ca, 0x74ca, 0x74cf, 0x74cf, 0x74d4, 0x74d4, + 0x74d8, 0x74d8, 0x74da, 0x74da, 0x74dc, 0x74dc, 0x74e0, 0x74e0, + 0x74e2, 0x74e3, 0x74e6, 0x74e7, 0x74e9, 0x74e9, 0x74ee, 0x74ee, + 0x74f0, 0x74f2, 0x74f6, 0x74f8, 0x7501, 0x7501, 0x7503, 0x7505, + 0x750c, 0x750e, 0x7511, 0x7511, 0x7513, 0x7513, 0x7515, 0x7515, + 0x7518, 0x7518, 0x751a, 0x751c, 0x751e, 0x751f, 0x7523, 0x7523, + 0x7525, 0x7526, 0x7528, 0x7528, 0x752b, 0x752c, 0x752f, 0x7533, + 0x7537, 0x7538, 0x753a, 0x753c, 0x7544, 0x7544, 0x7546, 0x7547, + 0x7549, 0x754d, 0x754f, 0x754f, 0x7551, 0x7551, 0x7553, 0x7554, + 0x7559, 0x755d, 0x7560, 0x7560, 0x7562, 0x7562, 0x7564, 0x7567, + 0x7569, 0x756b, 0x756d, 0x756d, 0x756f, 0x7570, 0x7573, 0x7578, + 0x757a, 0x757a, 0x757f, 0x757f, 0x7582, 0x7582, 0x7586, 0x7587, + 0x7589, 0x758b, 0x758e, 0x758f, 0x7591, 0x7591, 0x7594, 0x7594, + 0x759a, 0x759a, 0x759d, 0x759d, 0x75a3, 0x75a3, 0x75a5, 0x75a5, + 0x75ab, 0x75ab, 0x75b1, 0x75b3, 0x75b5, 0x75b5, 0x75b8, 0x75b9, + 0x75bc, 0x75be, 0x75c2, 0x75c3, 0x75c5, 0x75c5, 0x75c7, 0x75c7, + 0x75ca, 0x75ca, 0x75cd, 0x75cd, 0x75d2, 0x75d2, 0x75d4, 0x75d5, + 0x75d8, 0x75d9, 0x75db, 0x75db, 0x75de, 0x75de, 0x75e2, 0x75e3, + 0x75e9, 0x75e9, 0x75f0, 0x75f0, 0x75f2, 0x75f4, 0x75fa, 0x75fa, + 0x75fc, 0x75fc, 0x75fe, 0x7601, 0x7609, 0x7609, 0x760b, 0x760b, + 0x760d, 0x760d, 0x7619, 0x7619, 0x761f, 0x7622, 0x7624, 0x7624, + 0x7626, 0x7627, 0x7630, 0x7630, 0x7634, 0x7634, 0x763b, 0x763b, + 0x7642, 0x7642, 0x7646, 0x7648, 0x764c, 0x764c, 0x764e, 0x764e, + 0x7652, 0x7652, 0x7656, 0x7656, 0x7658, 0x7658, 0x765c, 0x765c, + 0x7661, 0x7662, 0x7664, 0x7664, 0x7667, 0x766a, 0x766c, 0x766c, + 0x7670, 0x7670, 0x7672, 0x7672, 0x7676, 0x7676, 0x7678, 0x7678, + 0x767a, 0x767e, 0x7680, 0x7680, 0x7682, 0x7684, 0x7686, 0x7688, + 0x768b, 0x768b, 0x768e, 0x768e, 0x7690, 0x7690, 0x7693, 0x7693, + 0x7696, 0x7696, 0x7699, 0x769c, 0x769e, 0x769e, 0x76a6, 0x76a6, + 0x76ae, 0x76ae, 0x76b0, 0x76b0, 0x76b4, 0x76b4, 0x76b7, 0x76ba, + 0x76bf, 0x76bf, 0x76c2, 0x76c3, 0x76c6, 0x76c6, 0x76c8, 0x76c8, + 0x76ca, 0x76ca, 0x76cd, 0x76cd, 0x76d2, 0x76d2, 0x76d6, 0x76d7, + 0x76db, 0x76dc, 0x76de, 0x76df, 0x76e1, 0x76e1, 0x76e3, 0x76e5, + 0x76e7, 0x76e7, 0x76ea, 0x76ea, 0x76ee, 0x76ee, 0x76f2, 0x76f2, + 0x76f4, 0x76f4, 0x76f8, 0x76f8, 0x76fb, 0x76fc, 0x76fe, 0x76fe, + 0x7701, 0x7701, 0x7704, 0x7704, 0x7707, 0x7709, 0x770b, 0x770c, + 0x771b, 0x771b, 0x771e, 0x7720, 0x7724, 0x7726, 0x7729, 0x7729, + 0x7737, 0x7738, 0x773a, 0x773a, 0x773c, 0x773c, 0x7740, 0x7740, + 0x7746, 0x7747, 0x774d, 0x774d, 0x775a, 0x775b, 0x7761, 0x7761, + 0x7763, 0x7763, 0x7765, 0x7766, 0x7768, 0x7768, 0x776b, 0x776b, + 0x7779, 0x7779, 0x777e, 0x777f, 0x778b, 0x778b, 0x778e, 0x778e, + 0x7791, 0x7791, 0x779e, 0x779e, 0x77a0, 0x77a0, 0x77a5, 0x77a5, + 0x77ac, 0x77ad, 0x77b0, 0x77b0, 0x77b3, 0x77b3, 0x77b6, 0x77b6, + 0x77b9, 0x77b9, 0x77bb, 0x77bd, 0x77bf, 0x77bf, 0x77c7, 0x77c7, + 0x77cd, 0x77cd, 0x77d7, 0x77d7, 0x77da, 0x77dc, 0x77e2, 0x77e3, + 0x77e5, 0x77e5, 0x77e7, 0x77e7, 0x77e9, 0x77e9, 0x77ed, 0x77ef, + 0x77f3, 0x77f3, 0x77fc, 0x77fc, 0x7802, 0x7802, 0x780c, 0x780c, + 0x7812, 0x7812, 0x7814, 0x7815, 0x7820, 0x7821, 0x7825, 0x7827, + 0x782c, 0x782c, 0x7832, 0x7832, 0x7834, 0x7834, 0x783a, 0x783a, + 0x783f, 0x783f, 0x7845, 0x7845, 0x784e, 0x784f, 0x785d, 0x785d, + 0x7864, 0x7864, 0x786b, 0x786c, 0x786f, 0x786f, 0x7872, 0x7872, + 0x7874, 0x7874, 0x787a, 0x787a, 0x787c, 0x787c, 0x7881, 0x7881, + 0x7886, 0x7887, 0x788c, 0x788e, 0x7891, 0x7891, 0x7893, 0x7893, + 0x7895, 0x7895, 0x7897, 0x7897, 0x789a, 0x789a, 0x78a3, 0x78a3, + 0x78a7, 0x78a7, 0x78a9, 0x78aa, 0x78af, 0x78af, 0x78b5, 0x78b5, + 0x78ba, 0x78bc, 0x78be, 0x78be, 0x78c1, 0x78c1, 0x78c5, 0x78c6, + 0x78ca, 0x78cb, 0x78ce, 0x78ce, 0x78d0, 0x78d1, 0x78d4, 0x78d4, + 0x78da, 0x78da, 0x78e7, 0x78e8, 0x78ec, 0x78ec, 0x78ef, 0x78ef, + 0x78f4, 0x78f5, 0x78fb, 0x78fb, 0x78fd, 0x78fd, 0x7901, 0x7901, + 0x7907, 0x7907, 0x790e, 0x790e, 0x7911, 0x7912, 0x7916, 0x7916, + 0x7919, 0x7919, 0x7926, 0x7926, 0x792a, 0x792c, 0x7930, 0x7930, + 0x793a, 0x793a, 0x793c, 0x793c, 0x793e, 0x793e, 0x7940, 0x7941, + 0x7947, 0x7949, 0x7950, 0x7950, 0x7953, 0x7953, 0x7955, 0x7957, + 0x795a, 0x7960, 0x7962, 0x7962, 0x7965, 0x7965, 0x7968, 0x7968, + 0x796d, 0x796d, 0x7977, 0x7977, 0x797a, 0x797a, 0x797f, 0x7981, + 0x7984, 0x7985, 0x798a, 0x798a, 0x798d, 0x798f, 0x7991, 0x7991, + 0x7994, 0x7994, 0x799b, 0x799b, 0x799d, 0x799d, 0x79a6, 0x79a7, + 0x79aa, 0x79aa, 0x79ae, 0x79ae, 0x79b0, 0x79b1, 0x79b3, 0x79b3, + 0x79b9, 0x79ba, 0x79bd, 0x79c1, 0x79c9, 0x79cb, 0x79d1, 0x79d2, + 0x79d5, 0x79d5, 0x79d8, 0x79d8, 0x79df, 0x79df, 0x79e1, 0x79e1, + 0x79e3, 0x79e4, 0x79e6, 0x79e7, 0x79e9, 0x79e9, 0x79ec, 0x79ec, + 0x79f0, 0x79f0, 0x79fb, 0x79fb, 0x7a00, 0x7a00, 0x7a05, 0x7a05, + 0x7a08, 0x7a08, 0x7a0b, 0x7a0b, 0x7a0d, 0x7a0e, 0x7a14, 0x7a14, + 0x7a17, 0x7a1a, 0x7a1c, 0x7a1c, 0x7a1f, 0x7a20, 0x7a2e, 0x7a2e, + 0x7a31, 0x7a32, 0x7a36, 0x7a37, 0x7a3b, 0x7a40, 0x7a42, 0x7a43, + 0x7a46, 0x7a46, 0x7a49, 0x7a49, 0x7a4d, 0x7a50, 0x7a57, 0x7a57, + 0x7a61, 0x7a63, 0x7a69, 0x7a69, 0x7a6b, 0x7a6b, 0x7a70, 0x7a70, + 0x7a74, 0x7a74, 0x7a76, 0x7a76, 0x7a79, 0x7a7a, 0x7a7d, 0x7a7d, + 0x7a7f, 0x7a7f, 0x7a81, 0x7a81, 0x7a83, 0x7a84, 0x7a88, 0x7a88, + 0x7a92, 0x7a93, 0x7a95, 0x7a98, 0x7a9f, 0x7a9f, 0x7aa9, 0x7aaa, + 0x7aae, 0x7ab0, 0x7ab6, 0x7ab6, 0x7aba, 0x7aba, 0x7abf, 0x7abf, + 0x7ac3, 0x7ac5, 0x7ac7, 0x7ac8, 0x7aca, 0x7acb, 0x7acd, 0x7acd, + 0x7acf, 0x7acf, 0x7ad1, 0x7ad3, 0x7ad5, 0x7ad5, 0x7ad7, 0x7ad7, + 0x7ad9, 0x7ada, 0x7adc, 0x7add, 0x7adf, 0x7ae3, 0x7ae5, 0x7ae7, + 0x7aea, 0x7aeb, 0x7aed, 0x7aed, 0x7aef, 0x7af0, 0x7af6, 0x7af6, + 0x7af8, 0x7afa, 0x7aff, 0x7aff, 0x7b02, 0x7b02, 0x7b04, 0x7b04, + 0x7b06, 0x7b06, 0x7b08, 0x7b08, 0x7b0a, 0x7b0b, 0x7b0f, 0x7b0f, + 0x7b11, 0x7b11, 0x7b18, 0x7b19, 0x7b1b, 0x7b1b, 0x7b1e, 0x7b1e, + 0x7b20, 0x7b20, 0x7b25, 0x7b26, 0x7b28, 0x7b28, 0x7b2c, 0x7b2d, + 0x7b33, 0x7b33, 0x7b35, 0x7b36, 0x7b39, 0x7b39, 0x7b45, 0x7b46, + 0x7b48, 0x7b49, 0x7b4b, 0x7b4d, 0x7b4f, 0x7b52, 0x7b54, 0x7b54, + 0x7b56, 0x7b56, 0x7b5d, 0x7b5d, 0x7b60, 0x7b60, 0x7b65, 0x7b65, + 0x7b67, 0x7b67, 0x7b6c, 0x7b6c, 0x7b6e, 0x7b6e, 0x7b70, 0x7b71, + 0x7b74, 0x7b75, 0x7b7a, 0x7b7a, 0x7b7d, 0x7b7d, 0x7b86, 0x7b87, + 0x7b8b, 0x7b8b, 0x7b8d, 0x7b8d, 0x7b8f, 0x7b8f, 0x7b92, 0x7b92, + 0x7b94, 0x7b95, 0x7b97, 0x7b9a, 0x7b9c, 0x7b9f, 0x7ba1, 0x7ba1, + 0x7baa, 0x7baa, 0x7bad, 0x7bad, 0x7bb1, 0x7bb1, 0x7bb4, 0x7bb4, + 0x7bb8, 0x7bb8, 0x7bc0, 0x7bc1, 0x7bc4, 0x7bc4, 0x7bc6, 0x7bc7, + 0x7bc9, 0x7bc9, 0x7bcb, 0x7bcc, 0x7bcf, 0x7bcf, 0x7bd2, 0x7bd2, + 0x7bdd, 0x7bdd, 0x7be0, 0x7be0, 0x7be4, 0x7be6, 0x7be9, 0x7be9, + 0x7bed, 0x7bed, 0x7bf3, 0x7bf3, 0x7bf6, 0x7bf7, 0x7c00, 0x7c00, + 0x7c07, 0x7c07, 0x7c0d, 0x7c0d, 0x7c11, 0x7c14, 0x7c17, 0x7c17, + 0x7c1e, 0x7c1f, 0x7c21, 0x7c21, 0x7c23, 0x7c23, 0x7c27, 0x7c27, + 0x7c2a, 0x7c2b, 0x7c37, 0x7c38, 0x7c3d, 0x7c40, 0x7c43, 0x7c43, + 0x7c4c, 0x7c4d, 0x7c4f, 0x7c50, 0x7c54, 0x7c54, 0x7c56, 0x7c56, + 0x7c58, 0x7c58, 0x7c5f, 0x7c60, 0x7c64, 0x7c65, 0x7c6c, 0x7c6c, + 0x7c73, 0x7c73, 0x7c75, 0x7c75, 0x7c7e, 0x7c7e, 0x7c81, 0x7c83, + 0x7c89, 0x7c89, 0x7c8b, 0x7c8b, 0x7c8d, 0x7c8d, 0x7c90, 0x7c90, + 0x7c92, 0x7c92, 0x7c95, 0x7c95, 0x7c97, 0x7c98, 0x7c9b, 0x7c9b, + 0x7c9f, 0x7c9f, 0x7ca1, 0x7ca2, 0x7ca4, 0x7ca5, 0x7ca7, 0x7ca8, + 0x7cab, 0x7cab, 0x7cad, 0x7cae, 0x7cb1, 0x7cb3, 0x7cb9, 0x7cb9, + 0x7cbd, 0x7cbe, 0x7cc0, 0x7cc0, 0x7cc2, 0x7cc2, 0x7cc5, 0x7cc5, + 0x7cca, 0x7cca, 0x7cce, 0x7cce, 0x7cd2, 0x7cd2, 0x7cd6, 0x7cd6, + 0x7cd8, 0x7cd8, 0x7cdc, 0x7cdc, 0x7cde, 0x7ce0, 0x7ce2, 0x7ce2, + 0x7ce7, 0x7ce7, 0x7cef, 0x7cef, 0x7cf2, 0x7cf2, 0x7cf4, 0x7cf4, + 0x7cf6, 0x7cf6, 0x7cf8, 0x7cf8, 0x7cfa, 0x7cfb, 0x7cfe, 0x7cfe, + 0x7d00, 0x7d00, 0x7d02, 0x7d02, 0x7d04, 0x7d08, 0x7d0a, 0x7d0b, + 0x7d0d, 0x7d0d, 0x7d10, 0x7d10, 0x7d14, 0x7d15, 0x7d17, 0x7d1c, + 0x7d20, 0x7d22, 0x7d2b, 0x7d2c, 0x7d2e, 0x7d30, 0x7d32, 0x7d33, + 0x7d35, 0x7d35, 0x7d39, 0x7d3a, 0x7d3f, 0x7d3f, 0x7d42, 0x7d46, + 0x7d48, 0x7d48, 0x7d4b, 0x7d4c, 0x7d4e, 0x7d50, 0x7d56, 0x7d56, + 0x7d5b, 0x7d5c, 0x7d5e, 0x7d5e, 0x7d61, 0x7d63, 0x7d66, 0x7d66, + 0x7d68, 0x7d68, 0x7d6a, 0x7d6a, 0x7d6e, 0x7d6e, 0x7d71, 0x7d73, + 0x7d75, 0x7d76, 0x7d79, 0x7d79, 0x7d7d, 0x7d7d, 0x7d7f, 0x7d7f, + 0x7d89, 0x7d89, 0x7d8e, 0x7d8f, 0x7d93, 0x7d93, 0x7d99, 0x7d9c, + 0x7d9f, 0x7da0, 0x7da2, 0x7da3, 0x7dab, 0x7db2, 0x7db4, 0x7db5, + 0x7db7, 0x7db8, 0x7dba, 0x7dbb, 0x7dbd, 0x7dbf, 0x7dc7, 0x7dc7, + 0x7dca, 0x7dcb, 0x7dcf, 0x7dcf, 0x7dd1, 0x7dd2, 0x7dd5, 0x7dd6, + 0x7dd8, 0x7dd8, 0x7dda, 0x7dda, 0x7ddc, 0x7dde, 0x7de0, 0x7de1, + 0x7de3, 0x7de4, 0x7de8, 0x7de9, 0x7dec, 0x7dec, 0x7def, 0x7def, + 0x7df2, 0x7df2, 0x7df4, 0x7df4, 0x7dfb, 0x7dfb, 0x7e01, 0x7e01, + 0x7e04, 0x7e05, 0x7e09, 0x7e0b, 0x7e12, 0x7e12, 0x7e15, 0x7e15, + 0x7e1b, 0x7e1b, 0x7e1d, 0x7e1f, 0x7e21, 0x7e23, 0x7e26, 0x7e26, + 0x7e2b, 0x7e2b, 0x7e2e, 0x7e2f, 0x7e31, 0x7e32, 0x7e35, 0x7e35, + 0x7e37, 0x7e37, 0x7e39, 0x7e3b, 0x7e3d, 0x7e3e, 0x7e41, 0x7e41, + 0x7e43, 0x7e43, 0x7e46, 0x7e47, 0x7e4a, 0x7e4b, 0x7e4d, 0x7e4d, + 0x7e52, 0x7e52, 0x7e54, 0x7e56, 0x7e59, 0x7e5a, 0x7e5d, 0x7e5e, + 0x7e61, 0x7e61, 0x7e66, 0x7e67, 0x7e69, 0x7e6b, 0x7e6d, 0x7e6d, + 0x7e70, 0x7e70, 0x7e79, 0x7e79, 0x7e7b, 0x7e7d, 0x7e7f, 0x7e7f, + 0x7e82, 0x7e83, 0x7e88, 0x7e8a, 0x7e8c, 0x7e8c, 0x7e8e, 0x7e90, + 0x7e92, 0x7e94, 0x7e96, 0x7e96, 0x7e98, 0x7e98, 0x7e9b, 0x7e9c, + 0x7f36, 0x7f36, 0x7f38, 0x7f38, 0x7f3a, 0x7f3a, 0x7f45, 0x7f45, + 0x7f47, 0x7f47, 0x7f4c, 0x7f4e, 0x7f50, 0x7f51, 0x7f54, 0x7f55, + 0x7f58, 0x7f58, 0x7f5f, 0x7f60, 0x7f67, 0x7f6b, 0x7f6e, 0x7f6e, + 0x7f70, 0x7f70, 0x7f72, 0x7f72, 0x7f75, 0x7f75, 0x7f77, 0x7f79, + 0x7f82, 0x7f83, 0x7f85, 0x7f88, 0x7f8a, 0x7f8a, 0x7f8c, 0x7f8c, + 0x7f8e, 0x7f8e, 0x7f94, 0x7f94, 0x7f9a, 0x7f9a, 0x7f9d, 0x7f9e, + 0x7fa1, 0x7fa1, 0x7fa3, 0x7fa4, 0x7fa8, 0x7fa9, 0x7fae, 0x7faf, + 0x7fb2, 0x7fb2, 0x7fb6, 0x7fb6, 0x7fb8, 0x7fb9, 0x7fbd, 0x7fbd, + 0x7fc1, 0x7fc1, 0x7fc5, 0x7fc6, 0x7fca, 0x7fca, 0x7fcc, 0x7fcc, + 0x7fce, 0x7fce, 0x7fd2, 0x7fd2, 0x7fd4, 0x7fd5, 0x7fdf, 0x7fe1, + 0x7fe6, 0x7fe6, 0x7fe9, 0x7fe9, 0x7feb, 0x7feb, 0x7ff0, 0x7ff0, + 0x7ff3, 0x7ff3, 0x7ff9, 0x7ff9, 0x7ffb, 0x7ffc, 0x8000, 0x8001, + 0x8003, 0x8006, 0x8009, 0x8009, 0x800b, 0x800c, 0x8010, 0x8010, + 0x8012, 0x8012, 0x8015, 0x8015, 0x8017, 0x8019, 0x801c, 0x801c, + 0x8021, 0x8021, 0x8028, 0x8028, 0x802d, 0x802d, 0x8033, 0x8033, + 0x8036, 0x8036, 0x803b, 0x803b, 0x803d, 0x803d, 0x803f, 0x803f, + 0x8043, 0x8043, 0x8046, 0x8046, 0x804a, 0x804a, 0x8052, 0x8052, + 0x8056, 0x8056, 0x8058, 0x8058, 0x805a, 0x805a, 0x805e, 0x805f, + 0x8061, 0x8062, 0x8068, 0x8068, 0x806f, 0x8070, 0x8072, 0x8074, + 0x8076, 0x8077, 0x8079, 0x8079, 0x807d, 0x807f, 0x8084, 0x8087, + 0x8089, 0x8089, 0x808b, 0x808c, 0x8093, 0x8093, 0x8096, 0x8096, + 0x8098, 0x8098, 0x809a, 0x809b, 0x809d, 0x809d, 0x80a1, 0x80a2, + 0x80a5, 0x80a5, 0x80a9, 0x80aa, 0x80ac, 0x80ad, 0x80af, 0x80af, + 0x80b1, 0x80b2, 0x80b4, 0x80b4, 0x80ba, 0x80ba, 0x80c3, 0x80c4, + 0x80c6, 0x80c6, 0x80cc, 0x80cc, 0x80ce, 0x80ce, 0x80d6, 0x80d6, + 0x80d9, 0x80db, 0x80dd, 0x80de, 0x80e1, 0x80e1, 0x80e4, 0x80e5, + 0x80ef, 0x80ef, 0x80f1, 0x80f1, 0x80f4, 0x80f4, 0x80f8, 0x80f8, + 0x80fc, 0x80fd, 0x8102, 0x8102, 0x8105, 0x810a, 0x8118, 0x8118, + 0x811a, 0x811b, 0x8123, 0x8123, 0x8129, 0x8129, 0x812b, 0x812b, + 0x812f, 0x812f, 0x8131, 0x8131, 0x8133, 0x8133, 0x8139, 0x8139, + 0x813e, 0x813e, 0x8146, 0x8146, 0x814b, 0x814b, 0x814e, 0x814e, + 0x8150, 0x8151, 0x8153, 0x8155, 0x815f, 0x815f, 0x8165, 0x8166, + 0x816b, 0x816b, 0x816e, 0x816e, 0x8170, 0x8171, 0x8174, 0x8174, + 0x8178, 0x817a, 0x817f, 0x8180, 0x8182, 0x8183, 0x8188, 0x8188, + 0x818a, 0x818a, 0x818f, 0x818f, 0x8193, 0x8193, 0x8195, 0x8195, + 0x819a, 0x819a, 0x819c, 0x819d, 0x81a0, 0x81a0, 0x81a3, 0x81a4, + 0x81a8, 0x81a9, 0x81b0, 0x81b0, 0x81b3, 0x81b3, 0x81b5, 0x81b5, + 0x81b8, 0x81b8, 0x81ba, 0x81ba, 0x81bd, 0x81c0, 0x81c2, 0x81c2, + 0x81c6, 0x81c6, 0x81c8, 0x81c9, 0x81cd, 0x81cd, 0x81d1, 0x81d1, + 0x81d3, 0x81d3, 0x81d8, 0x81da, 0x81df, 0x81e0, 0x81e3, 0x81e3, + 0x81e5, 0x81e5, 0x81e7, 0x81e8, 0x81ea, 0x81ea, 0x81ed, 0x81ed, + 0x81f3, 0x81f4, 0x81fa, 0x81fc, 0x81fe, 0x81fe, 0x8201, 0x8202, + 0x8205, 0x8205, 0x8207, 0x820a, 0x820c, 0x820e, 0x8210, 0x8210, + 0x8212, 0x8212, 0x8216, 0x8218, 0x821b, 0x821c, 0x821e, 0x821f, + 0x8221, 0x8221, 0x8229, 0x822c, 0x822e, 0x822e, 0x8233, 0x8233, + 0x8235, 0x8239, 0x8240, 0x8240, 0x8245, 0x8245, 0x8247, 0x8247, + 0x8258, 0x825a, 0x825d, 0x825d, 0x825f, 0x825f, 0x8262, 0x8262, + 0x8264, 0x8264, 0x8266, 0x8266, 0x8268, 0x8268, 0x826a, 0x826b, + 0x826e, 0x826f, 0x8271, 0x8272, 0x8276, 0x8278, 0x827e, 0x827e, + 0x828b, 0x828b, 0x828d, 0x828e, 0x8292, 0x8292, 0x8299, 0x829a, + 0x829d, 0x829d, 0x829f, 0x829f, 0x82a5, 0x82a6, 0x82a9, 0x82a9, + 0x82ab, 0x82af, 0x82b1, 0x82b1, 0x82b3, 0x82b3, 0x82b7, 0x82b9, + 0x82bb, 0x82bd, 0x82bf, 0x82bf, 0x82c5, 0x82c5, 0x82d1, 0x82d5, + 0x82d7, 0x82d7, 0x82d9, 0x82d9, 0x82db, 0x82dc, 0x82de, 0x82df, + 0x82e1, 0x82e1, 0x82e3, 0x82e3, 0x82e5, 0x82e7, 0x82eb, 0x82eb, + 0x82f1, 0x82f1, 0x82f3, 0x82f4, 0x82f9, 0x82fb, 0x82fd, 0x82fe, + 0x8301, 0x8306, 0x8309, 0x8309, 0x830e, 0x830e, 0x8316, 0x8318, + 0x831c, 0x831c, 0x8323, 0x8323, 0x8328, 0x8328, 0x832b, 0x832b, + 0x832f, 0x832f, 0x8331, 0x8332, 0x8334, 0x8336, 0x8338, 0x8339, + 0x8340, 0x8340, 0x8345, 0x8345, 0x8347, 0x8347, 0x8349, 0x834a, + 0x834f, 0x8352, 0x8358, 0x8358, 0x8362, 0x8362, 0x8373, 0x8373, + 0x8375, 0x8375, 0x8377, 0x8377, 0x837b, 0x837c, 0x837f, 0x837f, + 0x8385, 0x8385, 0x8387, 0x8387, 0x8389, 0x838a, 0x838e, 0x838e, + 0x8393, 0x8393, 0x8396, 0x8396, 0x8398, 0x8398, 0x839a, 0x839a, + 0x839e, 0x83a0, 0x83a2, 0x83a2, 0x83a8, 0x83ab, 0x83b1, 0x83b1, + 0x83b5, 0x83b5, 0x83bd, 0x83bd, 0x83c1, 0x83c1, 0x83c5, 0x83c5, + 0x83c7, 0x83c7, 0x83c9, 0x83ca, 0x83cc, 0x83cc, 0x83ce, 0x83ce, + 0x83d3, 0x83d3, 0x83d6, 0x83d6, 0x83d8, 0x83d8, 0x83dc, 0x83dc, + 0x83df, 0x83e0, 0x83e9, 0x83e9, 0x83eb, 0x83eb, 0x83ef, 0x83f2, + 0x83f4, 0x83f4, 0x83f6, 0x83f7, 0x83f9, 0x83f9, 0x83fb, 0x83fb, + 0x83fd, 0x83fd, 0x8403, 0x8404, 0x8407, 0x8407, 0x840a, 0x840e, + 0x8413, 0x8413, 0x8420, 0x8420, 0x8422, 0x8422, 0x8429, 0x842a, + 0x842c, 0x842c, 0x8431, 0x8431, 0x8435, 0x8435, 0x8438, 0x8438, + 0x843c, 0x843d, 0x8446, 0x8446, 0x8448, 0x8449, 0x844e, 0x844e, + 0x8457, 0x8457, 0x845b, 0x845b, 0x8461, 0x8463, 0x8466, 0x8466, + 0x8469, 0x8469, 0x846b, 0x846f, 0x8471, 0x8471, 0x8475, 0x8475, + 0x8477, 0x8477, 0x8479, 0x847a, 0x8482, 0x8482, 0x8484, 0x8484, + 0x848b, 0x848b, 0x8490, 0x8490, 0x8494, 0x8494, 0x8499, 0x8499, + 0x849c, 0x849c, 0x849f, 0x849f, 0x84a1, 0x84a1, 0x84ad, 0x84ad, + 0x84b2, 0x84b2, 0x84b4, 0x84b4, 0x84b8, 0x84b9, 0x84bb, 0x84bc, + 0x84bf, 0x84c2, 0x84c4, 0x84c4, 0x84c6, 0x84c6, 0x84c9, 0x84cb, + 0x84cd, 0x84cd, 0x84d0, 0x84d1, 0x84d6, 0x84d6, 0x84d9, 0x84da, + 0x84dc, 0x84dc, 0x84ec, 0x84ec, 0x84ee, 0x84ee, 0x84f4, 0x84f4, + 0x84fc, 0x84fc, 0x84ff, 0x8500, 0x8506, 0x8506, 0x8511, 0x8511, + 0x8513, 0x8515, 0x8517, 0x8518, 0x851a, 0x851a, 0x851e, 0x851f, + 0x8521, 0x8521, 0x8523, 0x8523, 0x8525, 0x8526, 0x852c, 0x852d, + 0x852f, 0x852f, 0x8535, 0x8535, 0x853d, 0x853d, 0x853f, 0x8541, + 0x8543, 0x8543, 0x8548, 0x854b, 0x854e, 0x854e, 0x8553, 0x8553, + 0x8555, 0x8555, 0x8557, 0x855a, 0x8563, 0x8563, 0x8568, 0x856b, + 0x856d, 0x856d, 0x8577, 0x8577, 0x857e, 0x857e, 0x8580, 0x8580, + 0x8584, 0x8584, 0x8587, 0x8588, 0x858a, 0x858a, 0x858f, 0x8591, + 0x8594, 0x8594, 0x8597, 0x8597, 0x8599, 0x8599, 0x859b, 0x859c, + 0x85a4, 0x85a4, 0x85a6, 0x85a6, 0x85a8, 0x85ac, 0x85ae, 0x85b0, + 0x85b9, 0x85ba, 0x85c1, 0x85c1, 0x85c9, 0x85c9, 0x85cd, 0x85d0, + 0x85d5, 0x85d5, 0x85dc, 0x85dd, 0x85e4, 0x85e5, 0x85e9, 0x85ea, + 0x85f7, 0x85f7, 0x85f9, 0x85fb, 0x85fe, 0x85ff, 0x8602, 0x8602, + 0x8606, 0x8607, 0x860a, 0x860b, 0x8613, 0x8613, 0x8616, 0x8617, + 0x861a, 0x861a, 0x8622, 0x8622, 0x862d, 0x862d, 0x862f, 0x8630, + 0x863f, 0x863f, 0x864d, 0x864e, 0x8650, 0x8650, 0x8654, 0x8655, + 0x865a, 0x865c, 0x865e, 0x865f, 0x8667, 0x8667, 0x866b, 0x866b, + 0x8671, 0x8671, 0x8679, 0x8679, 0x867b, 0x867b, 0x868a, 0x868c, + 0x8693, 0x8693, 0x8695, 0x8695, 0x86a3, 0x86a4, 0x86a9, 0x86ab, + 0x86af, 0x86b0, 0x86b6, 0x86b6, 0x86c4, 0x86c4, 0x86c6, 0x86c7, + 0x86c9, 0x86c9, 0x86cb, 0x86cb, 0x86cd, 0x86ce, 0x86d4, 0x86d4, + 0x86d9, 0x86d9, 0x86db, 0x86db, 0x86de, 0x86df, 0x86e4, 0x86e4, + 0x86e9, 0x86e9, 0x86ec, 0x86ef, 0x86f8, 0x86f9, 0x86fb, 0x86fb, + 0x86fe, 0x86fe, 0x8700, 0x8700, 0x8702, 0x8703, 0x8706, 0x8706, + 0x8708, 0x870a, 0x870d, 0x870d, 0x8711, 0x8712, 0x8718, 0x8718, + 0x871a, 0x871a, 0x871c, 0x871c, 0x8725, 0x8725, 0x8729, 0x8729, + 0x8734, 0x8734, 0x8737, 0x8737, 0x873b, 0x873b, 0x873f, 0x873f, + 0x8749, 0x8749, 0x874b, 0x874c, 0x874e, 0x874e, 0x8753, 0x8753, + 0x8755, 0x8755, 0x8757, 0x8757, 0x8759, 0x8759, 0x875f, 0x8760, + 0x8763, 0x8763, 0x8766, 0x8766, 0x8768, 0x8768, 0x876a, 0x876a, + 0x876e, 0x876e, 0x8774, 0x8774, 0x8776, 0x8776, 0x8778, 0x8778, + 0x877f, 0x877f, 0x8782, 0x8782, 0x878d, 0x878d, 0x879f, 0x879f, + 0x87a2, 0x87a2, 0x87ab, 0x87ab, 0x87af, 0x87af, 0x87b3, 0x87b3, + 0x87ba, 0x87bb, 0x87bd, 0x87bd, 0x87c0, 0x87c0, 0x87c4, 0x87c4, + 0x87c6, 0x87c7, 0x87cb, 0x87cb, 0x87d0, 0x87d0, 0x87d2, 0x87d2, + 0x87e0, 0x87e0, 0x87ec, 0x87ec, 0x87ef, 0x87ef, 0x87f2, 0x87f2, + 0x87f6, 0x87f7, 0x87f9, 0x87f9, 0x87fb, 0x87fb, 0x87fe, 0x87fe, + 0x8805, 0x8805, 0x8807, 0x8807, 0x880d, 0x880f, 0x8811, 0x8811, + 0x8815, 0x8816, 0x881f, 0x881f, 0x8821, 0x8823, 0x8827, 0x8827, + 0x8831, 0x8831, 0x8836, 0x8836, 0x8839, 0x8839, 0x883b, 0x883b, + 0x8840, 0x8840, 0x8842, 0x8842, 0x8844, 0x8844, 0x8846, 0x8846, + 0x884c, 0x884d, 0x8852, 0x8853, 0x8857, 0x8857, 0x8859, 0x8859, + 0x885b, 0x885b, 0x885d, 0x885e, 0x8861, 0x8863, 0x8868, 0x8868, + 0x886b, 0x886b, 0x8870, 0x8870, 0x8872, 0x8872, 0x8875, 0x8875, + 0x8877, 0x8877, 0x887d, 0x887f, 0x8881, 0x8882, 0x8888, 0x8888, + 0x888b, 0x888b, 0x888d, 0x888d, 0x8892, 0x8892, 0x8896, 0x8897, + 0x8899, 0x8899, 0x889e, 0x889e, 0x88a2, 0x88a2, 0x88a4, 0x88a4, + 0x88ab, 0x88ab, 0x88ae, 0x88ae, 0x88b0, 0x88b1, 0x88b4, 0x88b5, + 0x88b7, 0x88b7, 0x88bf, 0x88bf, 0x88c1, 0x88c5, 0x88cf, 0x88cf, + 0x88d4, 0x88d5, 0x88d8, 0x88d9, 0x88dc, 0x88dd, 0x88df, 0x88df, + 0x88e1, 0x88e1, 0x88e8, 0x88e8, 0x88f2, 0x88f5, 0x88f8, 0x88f9, + 0x88fc, 0x88fe, 0x8902, 0x8902, 0x8904, 0x8904, 0x8907, 0x8907, + 0x890a, 0x890a, 0x890c, 0x890c, 0x8910, 0x8910, 0x8912, 0x8913, + 0x8918, 0x8919, 0x891c, 0x891e, 0x8925, 0x8925, 0x892a, 0x892b, + 0x8936, 0x8936, 0x8938, 0x8938, 0x893b, 0x893b, 0x8941, 0x8941, + 0x8943, 0x8944, 0x894c, 0x894d, 0x8956, 0x8956, 0x895e, 0x8960, + 0x8964, 0x8964, 0x8966, 0x8966, 0x896a, 0x896a, 0x896d, 0x896d, + 0x896f, 0x896f, 0x8972, 0x8972, 0x8974, 0x8974, 0x8977, 0x8977, + 0x897e, 0x897f, 0x8981, 0x8981, 0x8983, 0x8983, 0x8986, 0x8988, + 0x898a, 0x898b, 0x898f, 0x898f, 0x8993, 0x8993, 0x8996, 0x8998, + 0x899a, 0x899a, 0x89a1, 0x89a1, 0x89a6, 0x89a7, 0x89a9, 0x89aa, + 0x89ac, 0x89ac, 0x89af, 0x89af, 0x89b2, 0x89b3, 0x89ba, 0x89ba, + 0x89bd, 0x89bd, 0x89bf, 0x89c0, 0x89d2, 0x89d2, 0x89da, 0x89da, + 0x89dc, 0x89dd, 0x89e3, 0x89e3, 0x89e6, 0x89e7, 0x89f4, 0x89f4, + 0x89f8, 0x89f8, 0x8a00, 0x8a00, 0x8a02, 0x8a03, 0x8a08, 0x8a08, + 0x8a0a, 0x8a0a, 0x8a0c, 0x8a0c, 0x8a0e, 0x8a0e, 0x8a10, 0x8a10, + 0x8a12, 0x8a13, 0x8a16, 0x8a18, 0x8a1b, 0x8a1b, 0x8a1d, 0x8a1d, + 0x8a1f, 0x8a1f, 0x8a23, 0x8a23, 0x8a25, 0x8a25, 0x8a2a, 0x8a2a, + 0x8a2d, 0x8a2d, 0x8a31, 0x8a31, 0x8a33, 0x8a34, 0x8a36, 0x8a37, + 0x8a3a, 0x8a3c, 0x8a41, 0x8a41, 0x8a46, 0x8a46, 0x8a48, 0x8a48, + 0x8a50, 0x8a52, 0x8a54, 0x8a55, 0x8a5b, 0x8a5b, 0x8a5e, 0x8a5e, + 0x8a60, 0x8a60, 0x8a62, 0x8a63, 0x8a66, 0x8a66, 0x8a69, 0x8a69, + 0x8a6b, 0x8a6e, 0x8a70, 0x8a73, 0x8a75, 0x8a75, 0x8a79, 0x8a79, + 0x8a7c, 0x8a7c, 0x8a82, 0x8a82, 0x8a84, 0x8a85, 0x8a87, 0x8a87, + 0x8a89, 0x8a89, 0x8a8c, 0x8a8d, 0x8a91, 0x8a91, 0x8a93, 0x8a93, + 0x8a95, 0x8a95, 0x8a98, 0x8a98, 0x8a9a, 0x8a9a, 0x8a9e, 0x8a9e, + 0x8aa0, 0x8aa1, 0x8aa3, 0x8aa8, 0x8aaa, 0x8aaa, 0x8aac, 0x8aad, + 0x8ab0, 0x8ab0, 0x8ab2, 0x8ab2, 0x8ab9, 0x8ab9, 0x8abc, 0x8abc, + 0x8abe, 0x8abf, 0x8ac2, 0x8ac2, 0x8ac4, 0x8ac4, 0x8ac7, 0x8ac7, + 0x8acb, 0x8acd, 0x8acf, 0x8acf, 0x8ad2, 0x8ad2, 0x8ad6, 0x8ad6, + 0x8ada, 0x8adc, 0x8ade, 0x8ae2, 0x8ae4, 0x8ae4, 0x8ae6, 0x8ae7, + 0x8aea, 0x8aeb, 0x8aed, 0x8aee, 0x8af1, 0x8af1, 0x8af3, 0x8af3, + 0x8af6, 0x8af8, 0x8afa, 0x8afa, 0x8afe, 0x8afe, 0x8b00, 0x8b02, + 0x8b04, 0x8b04, 0x8b07, 0x8b07, 0x8b0c, 0x8b0c, 0x8b0e, 0x8b0e, + 0x8b10, 0x8b10, 0x8b14, 0x8b14, 0x8b16, 0x8b17, 0x8b19, 0x8b1b, + 0x8b1d, 0x8b1d, 0x8b20, 0x8b21, 0x8b26, 0x8b26, 0x8b28, 0x8b28, + 0x8b2b, 0x8b2c, 0x8b33, 0x8b33, 0x8b39, 0x8b39, 0x8b3e, 0x8b3e, + 0x8b41, 0x8b41, 0x8b49, 0x8b49, 0x8b4c, 0x8b4c, 0x8b4e, 0x8b4f, + 0x8b53, 0x8b53, 0x8b56, 0x8b56, 0x8b58, 0x8b58, 0x8b5a, 0x8b5c, + 0x8b5f, 0x8b5f, 0x8b66, 0x8b66, 0x8b6b, 0x8b6c, 0x8b6f, 0x8b72, + 0x8b74, 0x8b74, 0x8b77, 0x8b77, 0x8b7d, 0x8b7d, 0x8b7f, 0x8b80, + 0x8b83, 0x8b83, 0x8b8a, 0x8b8a, 0x8b8c, 0x8b8c, 0x8b8e, 0x8b8e, + 0x8b90, 0x8b90, 0x8b92, 0x8b93, 0x8b96, 0x8b96, 0x8b99, 0x8b9a, + 0x8c37, 0x8c37, 0x8c3a, 0x8c3a, 0x8c3f, 0x8c3f, 0x8c41, 0x8c41, + 0x8c46, 0x8c46, 0x8c48, 0x8c48, 0x8c4a, 0x8c4a, 0x8c4c, 0x8c4c, + 0x8c4e, 0x8c4e, 0x8c50, 0x8c50, 0x8c55, 0x8c55, 0x8c5a, 0x8c5a, + 0x8c61, 0x8c62, 0x8c6a, 0x8c6c, 0x8c78, 0x8c7a, 0x8c7c, 0x8c7c, + 0x8c82, 0x8c82, 0x8c85, 0x8c85, 0x8c89, 0x8c8a, 0x8c8c, 0x8c8e, + 0x8c94, 0x8c94, 0x8c98, 0x8c98, 0x8c9d, 0x8c9e, 0x8ca0, 0x8ca2, + 0x8ca7, 0x8cb0, 0x8cb2, 0x8cb4, 0x8cb6, 0x8cb8, 0x8cbb, 0x8cbd, + 0x8cbf, 0x8cc4, 0x8cc7, 0x8cc8, 0x8cca, 0x8cca, 0x8ccd, 0x8cce, + 0x8cd1, 0x8cd1, 0x8cd3, 0x8cd3, 0x8cda, 0x8cdc, 0x8cde, 0x8cde, + 0x8ce0, 0x8ce0, 0x8ce2, 0x8ce4, 0x8ce6, 0x8ce6, 0x8cea, 0x8cea, + 0x8ced, 0x8ced, 0x8cf0, 0x8cf0, 0x8cf4, 0x8cf4, 0x8cfa, 0x8cfd, + 0x8d04, 0x8d05, 0x8d07, 0x8d08, 0x8d0a, 0x8d0b, 0x8d0d, 0x8d0d, + 0x8d0f, 0x8d10, 0x8d12, 0x8d14, 0x8d16, 0x8d16, 0x8d64, 0x8d64, + 0x8d66, 0x8d67, 0x8d6b, 0x8d6b, 0x8d6d, 0x8d6d, 0x8d70, 0x8d71, + 0x8d73, 0x8d74, 0x8d76, 0x8d77, 0x8d81, 0x8d81, 0x8d85, 0x8d85, + 0x8d8a, 0x8d8a, 0x8d99, 0x8d99, 0x8da3, 0x8da3, 0x8da8, 0x8da8, + 0x8db3, 0x8db3, 0x8dba, 0x8dba, 0x8dbe, 0x8dbe, 0x8dc2, 0x8dc2, + 0x8dc6, 0x8dc6, 0x8dcb, 0x8dcc, 0x8dcf, 0x8dcf, 0x8dd6, 0x8dd6, + 0x8dda, 0x8ddb, 0x8ddd, 0x8ddd, 0x8ddf, 0x8ddf, 0x8de1, 0x8de1, + 0x8de3, 0x8de3, 0x8de8, 0x8de8, 0x8dea, 0x8deb, 0x8def, 0x8def, + 0x8df3, 0x8df3, 0x8df5, 0x8df5, 0x8dfc, 0x8dfc, 0x8dff, 0x8dff, + 0x8e08, 0x8e0a, 0x8e0f, 0x8e10, 0x8e1d, 0x8e1f, 0x8e2a, 0x8e2a, + 0x8e30, 0x8e30, 0x8e34, 0x8e35, 0x8e42, 0x8e42, 0x8e44, 0x8e44, + 0x8e47, 0x8e4a, 0x8e4c, 0x8e4c, 0x8e50, 0x8e50, 0x8e55, 0x8e55, + 0x8e59, 0x8e59, 0x8e5f, 0x8e60, 0x8e63, 0x8e64, 0x8e72, 0x8e72, + 0x8e74, 0x8e74, 0x8e76, 0x8e76, 0x8e7c, 0x8e7c, 0x8e81, 0x8e81, + 0x8e84, 0x8e85, 0x8e87, 0x8e87, 0x8e8a, 0x8e8b, 0x8e8d, 0x8e8d, + 0x8e91, 0x8e91, 0x8e93, 0x8e94, 0x8e99, 0x8e99, 0x8ea1, 0x8ea1, + 0x8eaa, 0x8eac, 0x8eaf, 0x8eb1, 0x8ebe, 0x8ebe, 0x8ec0, 0x8ec0, + 0x8ec5, 0x8ec6, 0x8ec8, 0x8ec8, 0x8eca, 0x8ecd, 0x8ecf, 0x8ecf, + 0x8ed2, 0x8ed2, 0x8edb, 0x8edb, 0x8edf, 0x8edf, 0x8ee2, 0x8ee3, + 0x8eeb, 0x8eeb, 0x8ef8, 0x8ef8, 0x8efb, 0x8efe, 0x8f03, 0x8f03, + 0x8f05, 0x8f05, 0x8f09, 0x8f0a, 0x8f0c, 0x8f0c, 0x8f12, 0x8f15, + 0x8f19, 0x8f19, 0x8f1b, 0x8f1f, 0x8f26, 0x8f27, 0x8f29, 0x8f2a, + 0x8f2f, 0x8f2f, 0x8f33, 0x8f33, 0x8f38, 0x8f39, 0x8f3b, 0x8f3b, + 0x8f3e, 0x8f3f, 0x8f42, 0x8f42, 0x8f44, 0x8f46, 0x8f49, 0x8f49, + 0x8f4c, 0x8f4e, 0x8f57, 0x8f57, 0x8f5c, 0x8f5d, 0x8f5f, 0x8f5f, + 0x8f61, 0x8f64, 0x8f9b, 0x8f9c, 0x8f9e, 0x8f9f, 0x8fa3, 0x8fa3, + 0x8fa6, 0x8fa8, 0x8fad, 0x8fb2, 0x8fb7, 0x8fb7, 0x8fba, 0x8fbc, + 0x8fbf, 0x8fbf, 0x8fc2, 0x8fc2, 0x8fc4, 0x8fc5, 0x8fce, 0x8fce, + 0x8fd1, 0x8fd1, 0x8fd4, 0x8fd4, 0x8fda, 0x8fda, 0x8fe2, 0x8fe2, + 0x8fe5, 0x8fe6, 0x8fe9, 0x8feb, 0x8fed, 0x8fed, 0x8fef, 0x8ff0, + 0x8ff2, 0x8ff2, 0x8ff4, 0x8ff4, 0x8ff7, 0x8ffa, 0x8ffd, 0x8ffd, + 0x9000, 0x9003, 0x9005, 0x9006, 0x9008, 0x9008, 0x900b, 0x900b, + 0x900d, 0x9011, 0x9013, 0x9017, 0x9019, 0x901a, 0x901d, 0x9023, + 0x9027, 0x9027, 0x902e, 0x902e, 0x9031, 0x9032, 0x9035, 0x9036, + 0x9038, 0x9039, 0x903c, 0x903c, 0x903e, 0x903e, 0x9041, 0x9042, + 0x9045, 0x9045, 0x9047, 0x9047, 0x9049, 0x904b, 0x904d, 0x9056, + 0x9058, 0x9059, 0x905c, 0x905e, 0x9060, 0x9061, 0x9063, 0x9063, + 0x9065, 0x9065, 0x9067, 0x9069, 0x906d, 0x906f, 0x9072, 0x9072, + 0x9075, 0x9078, 0x907a, 0x907a, 0x907c, 0x907d, 0x907f, 0x9084, + 0x9087, 0x908a, 0x908f, 0x908f, 0x9091, 0x9091, 0x9095, 0x9095, + 0x9099, 0x9099, 0x90a2, 0x90a3, 0x90a6, 0x90a6, 0x90a8, 0x90a8, + 0x90aa, 0x90aa, 0x90af, 0x90b1, 0x90b5, 0x90b5, 0x90b8, 0x90b8, + 0x90c1, 0x90c1, 0x90ca, 0x90ca, 0x90ce, 0x90ce, 0x90db, 0x90db, + 0x90de, 0x90de, 0x90e1, 0x90e2, 0x90e4, 0x90e4, 0x90e8, 0x90e8, + 0x90ed, 0x90ed, 0x90f5, 0x90f5, 0x90f7, 0x90f7, 0x90fd, 0x90fd, + 0x9102, 0x9102, 0x9112, 0x9112, 0x9115, 0x9115, 0x9119, 0x9119, + 0x9127, 0x9127, 0x912d, 0x912d, 0x9130, 0x9130, 0x9132, 0x9132, + 0x9149, 0x914e, 0x9152, 0x9152, 0x9154, 0x9154, 0x9156, 0x9156, + 0x9158, 0x9158, 0x9162, 0x9163, 0x9165, 0x9165, 0x9169, 0x916a, + 0x916c, 0x916c, 0x9172, 0x9173, 0x9175, 0x9175, 0x9177, 0x9178, + 0x9182, 0x9182, 0x9187, 0x9187, 0x9189, 0x9189, 0x918b, 0x918b, + 0x918d, 0x918d, 0x9190, 0x9190, 0x9192, 0x9192, 0x9197, 0x9197, + 0x919c, 0x919c, 0x91a2, 0x91a2, 0x91a4, 0x91a4, 0x91aa, 0x91ac, + 0x91ae, 0x91af, 0x91b1, 0x91b1, 0x91b4, 0x91b5, 0x91b8, 0x91b8, + 0x91ba, 0x91ba, 0x91c0, 0x91c1, 0x91c6, 0x91c9, 0x91cb, 0x91d1, + 0x91d6, 0x91d8, 0x91da, 0x91df, 0x91e1, 0x91e1, 0x91e3, 0x91e7, + 0x91ea, 0x91ea, 0x91ed, 0x91ee, 0x91f5, 0x91f6, 0x91fc, 0x91fc, + 0x91ff, 0x91ff, 0x9206, 0x9206, 0x920a, 0x920a, 0x920d, 0x920e, + 0x9210, 0x9212, 0x9214, 0x9215, 0x9217, 0x9217, 0x921e, 0x921e, + 0x9229, 0x9229, 0x922c, 0x922c, 0x9234, 0x9234, 0x9237, 0x9237, + 0x9239, 0x923a, 0x923c, 0x923c, 0x923f, 0x9240, 0x9244, 0x9245, + 0x9248, 0x9249, 0x924b, 0x924b, 0x924e, 0x924e, 0x9250, 0x9251, + 0x9257, 0x9257, 0x9259, 0x925b, 0x925e, 0x925e, 0x9262, 0x9262, + 0x9264, 0x9267, 0x9271, 0x9271, 0x9277, 0x9278, 0x927e, 0x927e, + 0x9280, 0x9280, 0x9283, 0x9283, 0x9285, 0x9285, 0x9288, 0x9288, + 0x9291, 0x9291, 0x9293, 0x9293, 0x9295, 0x9296, 0x9298, 0x9298, + 0x929a, 0x929c, 0x92a7, 0x92a7, 0x92ad, 0x92ad, 0x92b3, 0x92b3, + 0x92b6, 0x92b7, 0x92b9, 0x92b9, 0x92cc, 0x92cc, 0x92cf, 0x92d0, + 0x92d2, 0x92d3, 0x92d5, 0x92d5, 0x92d7, 0x92d7, 0x92d9, 0x92d9, + 0x92e0, 0x92e0, 0x92e4, 0x92e4, 0x92e7, 0x92e7, 0x92e9, 0x92ea, + 0x92ed, 0x92ed, 0x92f2, 0x92f3, 0x92f8, 0x92fc, 0x92ff, 0x92ff, + 0x9302, 0x9302, 0x9304, 0x9304, 0x9306, 0x9306, 0x930f, 0x9310, + 0x9318, 0x931a, 0x931d, 0x9326, 0x9328, 0x9328, 0x932b, 0x932c, + 0x932e, 0x932f, 0x9332, 0x9332, 0x9335, 0x9335, 0x933a, 0x933b, + 0x9344, 0x9344, 0x9348, 0x9348, 0x934a, 0x934b, 0x934d, 0x934d, + 0x9354, 0x9354, 0x9356, 0x9357, 0x935b, 0x935c, 0x9360, 0x9360, + 0x936c, 0x936c, 0x936e, 0x936e, 0x9370, 0x9370, 0x9375, 0x9375, + 0x937c, 0x937c, 0x937e, 0x937e, 0x938c, 0x938c, 0x9394, 0x9394, + 0x9396, 0x9397, 0x939a, 0x939a, 0x93a3, 0x93a4, 0x93a7, 0x93a7, + 0x93ac, 0x93ae, 0x93b0, 0x93b0, 0x93b9, 0x93b9, 0x93c3, 0x93c3, + 0x93c6, 0x93c6, 0x93c8, 0x93c8, 0x93d0, 0x93d1, 0x93d6, 0x93d8, + 0x93dd, 0x93de, 0x93e1, 0x93e1, 0x93e4, 0x93e5, 0x93e8, 0x93e8, + 0x93f6, 0x93f6, 0x93f8, 0x93f8, 0x9403, 0x9404, 0x9407, 0x9407, + 0x9410, 0x9410, 0x9413, 0x9414, 0x9418, 0x941a, 0x9421, 0x9421, + 0x9425, 0x9425, 0x942b, 0x942b, 0x9431, 0x9431, 0x9435, 0x9436, + 0x9438, 0x9438, 0x943a, 0x943a, 0x9441, 0x9441, 0x9444, 0x9445, + 0x9448, 0x9448, 0x9451, 0x9453, 0x945a, 0x945b, 0x945e, 0x945e, + 0x9460, 0x9460, 0x9462, 0x9462, 0x946a, 0x946a, 0x9470, 0x9470, + 0x9475, 0x9475, 0x9477, 0x9477, 0x947c, 0x947f, 0x9481, 0x9481, + 0x9577, 0x9577, 0x9580, 0x9580, 0x9582, 0x9583, 0x9587, 0x9587, + 0x9589, 0x958b, 0x958f, 0x958f, 0x9591, 0x9594, 0x9596, 0x9596, + 0x9598, 0x9599, 0x95a0, 0x95a0, 0x95a2, 0x95a5, 0x95a7, 0x95a8, + 0x95ad, 0x95ad, 0x95b1, 0x95b2, 0x95b9, 0x95b9, 0x95bb, 0x95bc, + 0x95be, 0x95be, 0x95c3, 0x95c3, 0x95c7, 0x95c7, 0x95ca, 0x95ca, + 0x95cc, 0x95cd, 0x95d4, 0x95d6, 0x95d8, 0x95d8, 0x95dc, 0x95dc, + 0x95e1, 0x95e2, 0x95e5, 0x95e5, 0x961c, 0x961c, 0x9621, 0x9621, + 0x9628, 0x9628, 0x962a, 0x962a, 0x962e, 0x962f, 0x9632, 0x9632, + 0x963b, 0x963b, 0x963f, 0x9640, 0x9642, 0x9642, 0x9644, 0x9644, + 0x964b, 0x964d, 0x964f, 0x9650, 0x965b, 0x965f, 0x9662, 0x9666, + 0x966a, 0x966a, 0x966c, 0x966c, 0x9670, 0x9670, 0x9672, 0x9673, + 0x9675, 0x9678, 0x967a, 0x967a, 0x967d, 0x967d, 0x9685, 0x9686, + 0x9688, 0x9688, 0x968a, 0x968b, 0x968d, 0x968f, 0x9694, 0x9695, + 0x9697, 0x9699, 0x969b, 0x969d, 0x96a0, 0x96a0, 0x96a3, 0x96a3, + 0x96a7, 0x96a8, 0x96aa, 0x96aa, 0x96af, 0x96b2, 0x96b4, 0x96b4, + 0x96b6, 0x96b9, 0x96bb, 0x96bc, 0x96c0, 0x96c1, 0x96c4, 0x96c7, + 0x96c9, 0x96c9, 0x96cb, 0x96ce, 0x96d1, 0x96d1, 0x96d5, 0x96d6, + 0x96d9, 0x96d9, 0x96db, 0x96dc, 0x96e2, 0x96e3, 0x96e8, 0x96eb, + 0x96ef, 0x96f0, 0x96f2, 0x96f2, 0x96f6, 0x96f7, 0x96f9, 0x96f9, + 0x96fb, 0x96fb, 0x9700, 0x9700, 0x9704, 0x9704, 0x9706, 0x9708, + 0x970a, 0x970a, 0x970d, 0x970f, 0x9711, 0x9711, 0x9713, 0x9713, + 0x9716, 0x9716, 0x9719, 0x9719, 0x971c, 0x971c, 0x971e, 0x971e, + 0x9724, 0x9724, 0x9727, 0x9727, 0x972a, 0x972a, 0x9730, 0x9730, + 0x9732, 0x9733, 0x9738, 0x9739, 0x973b, 0x973b, 0x973d, 0x973e, + 0x9742, 0x9744, 0x9746, 0x9746, 0x9748, 0x9749, 0x974d, 0x974d, + 0x974f, 0x974f, 0x9751, 0x9752, 0x9755, 0x9756, 0x9759, 0x9759, + 0x975c, 0x975c, 0x975e, 0x975e, 0x9760, 0x9762, 0x9764, 0x9764, + 0x9766, 0x9766, 0x9768, 0x9769, 0x976b, 0x976b, 0x976d, 0x976d, + 0x9771, 0x9771, 0x9774, 0x9774, 0x9777, 0x9777, 0x9779, 0x977a, + 0x977c, 0x977c, 0x9781, 0x9781, 0x9784, 0x9786, 0x978b, 0x978b, + 0x978d, 0x978d, 0x978f, 0x9790, 0x9798, 0x9798, 0x979c, 0x979c, + 0x97a0, 0x97a0, 0x97a3, 0x97a3, 0x97a6, 0x97a6, 0x97a8, 0x97a8, + 0x97ab, 0x97ab, 0x97ad, 0x97ad, 0x97b3, 0x97b4, 0x97c3, 0x97c3, + 0x97c6, 0x97c6, 0x97c8, 0x97c8, 0x97cb, 0x97cb, 0x97d3, 0x97d3, + 0x97dc, 0x97dc, 0x97ed, 0x97ee, 0x97f2, 0x97f3, 0x97f5, 0x97f6, + 0x97fb, 0x97fb, 0x97ff, 0x9803, 0x9805, 0x9806, 0x9808, 0x9808, + 0x980a, 0x980a, 0x980c, 0x980c, 0x980f, 0x9813, 0x9817, 0x9818, + 0x981a, 0x981a, 0x9821, 0x9821, 0x9824, 0x9824, 0x982c, 0x982d, + 0x9830, 0x9830, 0x9834, 0x9834, 0x9837, 0x9839, 0x983b, 0x983d, + 0x9846, 0x9846, 0x984b, 0x984f, 0x9854, 0x9855, 0x9857, 0x9858, + 0x985a, 0x985b, 0x985e, 0x985e, 0x9865, 0x9865, 0x9867, 0x9867, + 0x986b, 0x986b, 0x986f, 0x9871, 0x9873, 0x9874, 0x98a8, 0x98a8, + 0x98aa, 0x98aa, 0x98af, 0x98af, 0x98b1, 0x98b1, 0x98b6, 0x98b6, + 0x98c3, 0x98c4, 0x98c6, 0x98c7, 0x98db, 0x98dc, 0x98df, 0x98df, + 0x98e1, 0x98e2, 0x98e9, 0x98e9, 0x98eb, 0x98eb, 0x98ed, 0x98ef, + 0x98f2, 0x98f2, 0x98f4, 0x98f4, 0x98fc, 0x98fe, 0x9903, 0x9903, + 0x9905, 0x9905, 0x9909, 0x990a, 0x990c, 0x990c, 0x9910, 0x9910, + 0x9912, 0x9914, 0x9918, 0x9918, 0x991d, 0x991e, 0x9920, 0x9921, + 0x9924, 0x9924, 0x9927, 0x9928, 0x992c, 0x992c, 0x992e, 0x992e, + 0x993d, 0x993e, 0x9942, 0x9942, 0x9945, 0x9945, 0x9949, 0x9949, + 0x994b, 0x994d, 0x9950, 0x9952, 0x9954, 0x9955, 0x9957, 0x9957, + 0x9996, 0x9999, 0x999d, 0x999e, 0x99a5, 0x99a5, 0x99a8, 0x99a8, + 0x99ac, 0x99ae, 0x99b1, 0x99b1, 0x99b3, 0x99b4, 0x99b9, 0x99b9, + 0x99bc, 0x99bc, 0x99c1, 0x99c1, 0x99c4, 0x99c6, 0x99c8, 0x99c8, + 0x99d0, 0x99d2, 0x99d5, 0x99d5, 0x99d8, 0x99d9, 0x99db, 0x99db, + 0x99dd, 0x99dd, 0x99df, 0x99df, 0x99e2, 0x99e2, 0x99ed, 0x99ee, + 0x99f1, 0x99f2, 0x99f8, 0x99f8, 0x99fb, 0x99fb, 0x99ff, 0x99ff, + 0x9a01, 0x9a01, 0x9a05, 0x9a05, 0x9a08, 0x9a08, 0x9a0e, 0x9a0f, + 0x9a12, 0x9a13, 0x9a19, 0x9a19, 0x9a28, 0x9a28, 0x9a2b, 0x9a2b, + 0x9a30, 0x9a30, 0x9a36, 0x9a37, 0x9a3e, 0x9a3e, 0x9a40, 0x9a40, + 0x9a42, 0x9a43, 0x9a45, 0x9a45, 0x9a4d, 0x9a4e, 0x9a55, 0x9a55, + 0x9a57, 0x9a57, 0x9a5a, 0x9a5b, 0x9a5f, 0x9a5f, 0x9a62, 0x9a62, + 0x9a64, 0x9a65, 0x9a69, 0x9a6b, 0x9aa8, 0x9aa8, 0x9aad, 0x9aad, + 0x9ab0, 0x9ab0, 0x9ab8, 0x9ab8, 0x9abc, 0x9abc, 0x9ac0, 0x9ac0, + 0x9ac4, 0x9ac4, 0x9acf, 0x9acf, 0x9ad1, 0x9ad1, 0x9ad3, 0x9ad4, + 0x9ad8, 0x9ad9, 0x9adc, 0x9adc, 0x9ade, 0x9adf, 0x9ae2, 0x9ae3, + 0x9ae5, 0x9ae6, 0x9aea, 0x9aeb, 0x9aed, 0x9aef, 0x9af1, 0x9af1, + 0x9af4, 0x9af4, 0x9af7, 0x9af7, 0x9afb, 0x9afb, 0x9b06, 0x9b06, + 0x9b18, 0x9b18, 0x9b1a, 0x9b1a, 0x9b1f, 0x9b1f, 0x9b22, 0x9b23, + 0x9b25, 0x9b25, 0x9b27, 0x9b2a, 0x9b2e, 0x9b2f, 0x9b31, 0x9b32, + 0x9b3b, 0x9b3c, 0x9b41, 0x9b45, 0x9b4d, 0x9b4f, 0x9b51, 0x9b51, + 0x9b54, 0x9b54, 0x9b58, 0x9b58, 0x9b5a, 0x9b5a, 0x9b6f, 0x9b6f, + 0x9b72, 0x9b72, 0x9b74, 0x9b75, 0x9b83, 0x9b83, 0x9b8e, 0x9b8f, + 0x9b91, 0x9b93, 0x9b96, 0x9b97, 0x9b9f, 0x9ba0, 0x9ba8, 0x9ba8, + 0x9baa, 0x9bab, 0x9bad, 0x9bae, 0x9bb1, 0x9bb1, 0x9bb4, 0x9bb4, + 0x9bb9, 0x9bb9, 0x9bbb, 0x9bbb, 0x9bc0, 0x9bc0, 0x9bc6, 0x9bc6, + 0x9bc9, 0x9bca, 0x9bcf, 0x9bcf, 0x9bd1, 0x9bd2, 0x9bd4, 0x9bd4, + 0x9bd6, 0x9bd6, 0x9bdb, 0x9bdb, 0x9be1, 0x9be4, 0x9be8, 0x9be8, + 0x9bf0, 0x9bf2, 0x9bf5, 0x9bf5, 0x9c00, 0x9c00, 0x9c04, 0x9c04, + 0x9c06, 0x9c06, 0x9c08, 0x9c0a, 0x9c0c, 0x9c0d, 0x9c10, 0x9c10, + 0x9c12, 0x9c15, 0x9c1b, 0x9c1b, 0x9c21, 0x9c21, 0x9c24, 0x9c25, + 0x9c2d, 0x9c30, 0x9c32, 0x9c32, 0x9c39, 0x9c3b, 0x9c3e, 0x9c3e, + 0x9c46, 0x9c49, 0x9c52, 0x9c52, 0x9c57, 0x9c57, 0x9c5a, 0x9c5a, + 0x9c60, 0x9c60, 0x9c67, 0x9c67, 0x9c76, 0x9c76, 0x9c78, 0x9c78, + 0x9ce5, 0x9ce5, 0x9ce7, 0x9ce7, 0x9ce9, 0x9ce9, 0x9ceb, 0x9cec, + 0x9cf0, 0x9cf0, 0x9cf3, 0x9cf4, 0x9cf6, 0x9cf6, 0x9d03, 0x9d03, + 0x9d06, 0x9d09, 0x9d0e, 0x9d0e, 0x9d12, 0x9d12, 0x9d15, 0x9d15, + 0x9d1b, 0x9d1b, 0x9d1f, 0x9d1f, 0x9d23, 0x9d23, 0x9d26, 0x9d26, + 0x9d28, 0x9d28, 0x9d2a, 0x9d2c, 0x9d3b, 0x9d3b, 0x9d3e, 0x9d3f, + 0x9d41, 0x9d41, 0x9d44, 0x9d44, 0x9d46, 0x9d46, 0x9d48, 0x9d48, + 0x9d50, 0x9d51, 0x9d59, 0x9d59, 0x9d5c, 0x9d5e, 0x9d60, 0x9d61, + 0x9d64, 0x9d64, 0x9d6b, 0x9d6c, 0x9d6f, 0x9d70, 0x9d72, 0x9d72, + 0x9d7a, 0x9d7a, 0x9d87, 0x9d87, 0x9d89, 0x9d89, 0x9d8f, 0x9d8f, + 0x9d9a, 0x9d9a, 0x9da4, 0x9da4, 0x9da9, 0x9da9, 0x9dab, 0x9dab, + 0x9daf, 0x9daf, 0x9db2, 0x9db2, 0x9db4, 0x9db4, 0x9db8, 0x9db8, + 0x9dba, 0x9dbb, 0x9dc1, 0x9dc2, 0x9dc4, 0x9dc4, 0x9dc6, 0x9dc6, + 0x9dcf, 0x9dcf, 0x9dd3, 0x9dd3, 0x9dd7, 0x9dd7, 0x9dd9, 0x9dd9, + 0x9de6, 0x9de6, 0x9ded, 0x9ded, 0x9def, 0x9def, 0x9df2, 0x9df2, + 0x9df8, 0x9dfa, 0x9dfd, 0x9dfd, 0x9e19, 0x9e1b, 0x9e1e, 0x9e1e, + 0x9e75, 0x9e75, 0x9e78, 0x9e79, 0x9e7d, 0x9e7d, 0x9e7f, 0x9e7f, + 0x9e81, 0x9e81, 0x9e88, 0x9e88, 0x9e8b, 0x9e8c, 0x9e91, 0x9e93, + 0x9e95, 0x9e95, 0x9e97, 0x9e97, 0x9e9d, 0x9e9d, 0x9e9f, 0x9e9f, + 0x9ea5, 0x9ea6, 0x9ea9, 0x9eaa, 0x9ead, 0x9ead, 0x9eb4, 0x9eb5, + 0x9eb8, 0x9ebc, 0x9ebe, 0x9ebf, 0x9ec3, 0x9ec4, 0x9ecc, 0x9ed2, + 0x9ed4, 0x9ed4, 0x9ed8, 0x9ed9, 0x9edb, 0x9ede, 0x9ee0, 0x9ee0, + 0x9ee5, 0x9ee5, 0x9ee8, 0x9ee8, 0x9eef, 0x9eef, 0x9ef4, 0x9ef4, + 0x9ef6, 0x9ef7, 0x9ef9, 0x9ef9, 0x9efb, 0x9efd, 0x9f07, 0x9f08, + 0x9f0e, 0x9f0e, 0x9f13, 0x9f13, 0x9f15, 0x9f15, 0x9f20, 0x9f21, + 0x9f2c, 0x9f2c, 0x9f3b, 0x9f3b, 0x9f3e, 0x9f3e, 0x9f4a, 0x9f4b, + 0x9f4e, 0x9f4f, 0x9f52, 0x9f52, 0x9f54, 0x9f54, 0x9f5f, 0x9f63, + 0x9f66, 0x9f67, 0x9f6a, 0x9f6a, 0x9f6c, 0x9f6c, 0x9f72, 0x9f72, + 0x9f76, 0x9f77, 0x9f8d, 0x9f8d, 0x9f90, 0x9f90, 0x9f95, 0x9f95, + 0x9f9c, 0x9f9d, 0x9fa0, 0x9fa0, 0xac00, 0xd7a3, 0xe000, 0xe06b, + 0xe070, 0xe07e, 0xe080, 0xe099, 0xe0a0, 0xe0ba, 0xe0c0, 0xe0d6, + 0xe0e0, 0xe0f5, 0xe100, 0xe105, 0xe110, 0xe116, 0xe121, 0xe12c, + 0xe130, 0xe13c, 0xe140, 0xe14d, 0xe150, 0xe153, 0xf900, 0xfa0b, + 0xfa0e, 0xfa2d, 0xfb01, 0xfb02, 0xfe30, 0xfe33, 0xfe35, 0xfe44, + 0xff01, 0xff5e, 0xff61, 0xff9f, 0xffe0, 0xffe6, + 0 + // clang-format on +}; + +void handleAppletHook (AppletHookType const hook_, void *const param_) +{ + (void)param_; + switch (hook_) + { + case AppletHookType_OnFocusState: + s_focused = (appletGetFocusState () == AppletFocusState_Focused); + break; + + case AppletHookType_OnOperationMode: + switch (appletGetOperationMode ()) + { + default: + case AppletOperationMode_Handheld: + s_width = 1280.0f; + s_height = 720.0f; + break; + + case AppletOperationMode_Docked: +#if 0 + s_width = 1920.0f; + s_height = 1080.0f; +#else + s_width = 1280.0f; + s_height = 720.0f; +#endif + break; + } + break; + + default: + break; + } +} + +char const *getClipboardText (void *const userData_) +{ + (void)userData_; + return s_clipboard.c_str (); +} + +void setClipboardText (void *const userData_, char const *const text_) +{ + (void)userData_; + s_clipboard = text_; +} + +void moveMouse (ImGuiIO &io_, ImVec2 const &pos_, bool const force_ = false) +{ + auto const now = std::chrono::high_resolution_clock::now (); + + if (!force_ && pos_.x == s_mousePos.x && pos_.y == s_mousePos.y) + { + if (now - s_lastMouseUpdate > 1s) + s_showMouse = false; + + return; + } + + s_showMouse = true; + s_lastMouseUpdate = now; + s_mousePos = pos_; + + io_.MousePos = s_mousePos; +} + +void updateMouseButtons (ImGuiIO &io_) +{ + auto const buttons = hidMouseButtonsHeld (); + + for (std::size_t i = 0; i < IM_ARRAYSIZE (io_.MouseDown); ++i) + { + // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss + // click-release events that are shorter than 1 frame. + io_.MouseDown[i] = s_mouseJustPressed[i] || (buttons & BIT (i)); + s_mouseJustPressed[i] = false; + + if (io_.MouseDown[i]) + moveMouse (io_, s_mousePos, true); + } +} + +void updateMousePos (ImGuiIO &io_) +{ + if (!s_focused) + return; + + MousePosition pos; + hidMouseRead (&pos); + + io_.MouseWheelH += pos.scrollVelocityX; + io_.MouseWheel += pos.scrollVelocityY; + + moveMouse ( + io_, ImVec2 (s_mousePos.x + 2.0f * pos.velocityX, s_mousePos.y + 2.0f * pos.velocityY)); +} + +void updateTouch (ImGuiIO &io_) +{ + if (!s_focused) + return; + + auto const touchCount = hidTouchCount (); + if (touchCount < 1) + return; + + touchPosition pos; + hidTouchRead (&pos, 0); + + moveMouse (io_, ImVec2 (pos.px, pos.py)); + io_.MouseDown[0] = true; + s_showMouse = false; +} + +void updateGamepads (ImGuiIO &io_) +{ + std::memset (io_.NavInputs, 0, sizeof (io_.NavInputs)); + + auto const buttonMapping = { + std::make_pair (KEY_A, ImGuiNavInput_Activate), + std::make_pair (KEY_B, ImGuiNavInput_Cancel), + std::make_pair (KEY_X, ImGuiNavInput_Input), + std::make_pair (KEY_Y, ImGuiNavInput_Menu), + std::make_pair (KEY_L, ImGuiNavInput_FocusPrev), + std::make_pair (KEY_L, ImGuiNavInput_TweakSlow), + std::make_pair (KEY_R, ImGuiNavInput_FocusNext), + std::make_pair (KEY_R, ImGuiNavInput_TweakFast), + std::make_pair (KEY_DUP, ImGuiNavInput_DpadUp), + std::make_pair (KEY_DRIGHT, ImGuiNavInput_DpadRight), + std::make_pair (KEY_DDOWN, ImGuiNavInput_DpadDown), + std::make_pair (KEY_DLEFT, ImGuiNavInput_DpadLeft), + }; + + auto const keys = hidKeysHeld (CONTROLLER_P1_AUTO); + for (auto const &[in, out] : buttonMapping) + { + if (keys & in) + io_.NavInputs[out] = 1.0f; + } + + // Use ZR/ZL as Mouse0/Mouse1, respectively + if (keys & KEY_ZR) + { + io_.MouseDown[0] = true; + moveMouse (io_, s_mousePos, true); + } + if (keys & KEY_ZL) + { + io_.MouseDown[1] = true; + moveMouse (io_, s_mousePos, true); + } + + JoystickPosition js; + auto const analogMapping = { + std::make_tuple (std::ref (js.dx), ImGuiNavInput_LStickLeft, -0.3f, -0.9f), + std::make_tuple (std::ref (js.dx), ImGuiNavInput_LStickRight, +0.3f, +0.9f), + std::make_tuple (std::ref (js.dy), ImGuiNavInput_LStickUp, +0.3f, +0.9f), + std::make_tuple (std::ref (js.dy), ImGuiNavInput_LStickDown, -0.3f, -0.9f), + }; + + hidJoystickRead (&js, CONTROLLER_P1_AUTO, JOYSTICK_LEFT); + for (auto const &[in, out, min, max] : analogMapping) + { + auto const value = in / static_cast (JOYSTICK_MAX); + auto const v = std::min (1.0f, (value - min) / (max - min)); + io_.NavInputs[out] = std::max (io_.NavInputs[out], v); + } + + // Use right stick as mouse + auto scale = 5.0f; + if (keys & KEY_L) + scale = 1.0f; + if (keys & KEY_R) + scale = 20.0f; + hidJoystickRead (&js, CONTROLLER_P1_AUTO, JOYSTICK_RIGHT); + + moveMouse (io_, + ImVec2 (s_mousePos.x + js.dx / static_cast (JOYSTICK_MAX) * scale, + s_mousePos.y - js.dy / static_cast (JOYSTICK_MAX) * scale)); +} + +void updateKeyboard (ImGuiIO &io_) +{ + io_.KeyCtrl = + hidKeyboardModifierHeld (static_cast (KBD_MOD_LCTRL | KBD_MOD_RCTRL)); + io_.KeyShift = hidKeyboardModifierHeld ( + static_cast (KBD_MOD_LSHIFT | KBD_MOD_RSHIFT)); + io_.KeyAlt = + hidKeyboardModifierHeld (static_cast (KBD_MOD_LALT | KBD_MOD_RALT)); + io_.KeySuper = + hidKeyboardModifierHeld (static_cast (KBD_MOD_LMETA | KBD_MOD_RMETA)); + + for (int i = 0; i < 256; ++i) + io_.KeysDown[i] = hidKeyboardHeld (static_cast (i)); + + if (!io_.WantTextInput) + return; + + // io_.AddInputCharacter (c); +} + +bool loadFontAtlas () +{ + fs::File fp; + if (!fp.open (FONT_ATLAS_BIN)) + return false; + + auto const atlas = ImGui::GetIO ().Fonts; + atlas->Clear (); + atlas->TexWidth = fp.read (); + atlas->TexHeight = fp.read (); + atlas->TexUvScale = ImVec2 (1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); + atlas->TexUvWhitePixel = ImVec2 (0.5f / atlas->TexWidth, 0.5f / atlas->TexHeight); + atlas->TexPixelsAlpha8 = + reinterpret_cast (IM_ALLOC (atlas->TexWidth * atlas->TexHeight)); + + if (!fp.readAll (atlas->TexPixelsAlpha8, atlas->TexWidth * atlas->TexHeight)) + return false; + + ImFontConfig config; + config.FontData = nullptr; + config.FontDataSize = 0; + config.FontDataOwnedByAtlas = true; + config.FontNo = 0; + config.SizePixels = 14.0f; + config.OversampleH = 3; + config.OversampleV = 1; + config.PixelSnapH = false; + config.GlyphExtraSpacing = ImVec2 (0.0f, 0.0f); + config.GlyphOffset = ImVec2 (0.0f, 0.0f); + config.GlyphRanges = nxFontRanges; + config.GlyphMinAdvanceX = 0.0f; + config.GlyphMaxAdvanceX = std::numeric_limits::max (); + config.MergeMode = false; + config.RasterizerFlags = 0; + config.RasterizerMultiply = 1.0f; + config.EllipsisChar = 0x2026; + std::memset (config.Name, 0, sizeof (config.Name)); + + auto const font = IM_NEW (ImFont); + config.DstFont = font; + + atlas->ConfigData.push_back (config); + atlas->Fonts.push_back (font); + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular (0x80000000, 108 * 2 + 1, 27); + atlas->CustomRects[0].X = 0; + atlas->CustomRects[0].Y = 0; + + font->FallbackAdvanceX = fp.read (); + font->FontSize = fp.read (); + + auto const glyphCount = fp.read (); + for (unsigned i = 0; i < glyphCount; ++i) + { + ImFontGlyph glyph; + + glyph.Codepoint = fp.read (); + glyph.AdvanceX = fp.read (); + glyph.X0 = fp.read (); + glyph.Y0 = fp.read (); + glyph.X1 = fp.read (); + glyph.Y1 = fp.read (); + glyph.U0 = fp.read (); + glyph.V0 = fp.read (); + glyph.U1 = fp.read (); + glyph.V1 = fp.read (); + + font->Glyphs.push_back (glyph); + font->MetricsTotalSurface += + static_cast ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) * + static_cast ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f); + } + + font->BuildLookupTable (); + + font->DisplayOffset.x = fp.read (); + font->DisplayOffset.y = fp.read (); + + font->ContainerAtlas = atlas; + font->ConfigData = &atlas->ConfigData[0]; + font->ConfigDataCount = 1; + font->FallbackChar = '?'; + font->EllipsisChar = config.EllipsisChar; + font->Scale = 1.0f; + font->Ascent = fp.read (); + font->Descent = fp.read (); + + return true; +} + +bool saveFontAtlas () +{ + auto const atlas = ImGui::GetIO ().Fonts; + + unsigned char *pixels; + int width; + int height; + atlas->GetTexDataAsAlpha8 (&pixels, &width, &height); + + fs::File fp; + if (!fp.open (FONT_ATLAS_BIN, "wb")) + return false; + + fp.write (width); + fp.write (height); + + if (!fp.writeAll (pixels, width * height)) + return false; + + auto const font = atlas->ConfigData[0].DstFont; + + fp.write (font->FallbackAdvanceX); + fp.write (font->FontSize); + + fp.write (font->Glyphs.size ()); + for (auto const &glyph : font->Glyphs) + { + fp.write (glyph.Codepoint); + fp.write (glyph.AdvanceX); + fp.write (glyph.X0); + fp.write (glyph.Y0); + fp.write (glyph.X1); + fp.write (glyph.Y1); + fp.write (glyph.U0); + fp.write (glyph.V0); + fp.write (glyph.U1); + fp.write (glyph.V1); + } + + fp.write (font->DisplayOffset.x); + fp.write (font->DisplayOffset.y); + fp.write (font->Ascent); + fp.write (font->Descent); + + return true; +} +} + +bool imgui::nx::init () +{ + u64 languageCode; + auto rc = setInitialize (); + if (R_FAILED (rc)) + return false; + + rc = setGetSystemLanguage (&languageCode); + if (R_FAILED (rc)) + { + setExit (); + return false; + } + setExit (); + + std::vector fonts (PlSharedFontType_Total); + s32 numFonts = 0; + rc = plGetSharedFont (languageCode, fonts.data (), fonts.size (), &numFonts); + if (R_FAILED (rc)) + return false; + fonts.resize (numFonts); + + appletSetFocusHandlingMode (AppletFocusHandlingMode_NoSuspend); + appletHook (&s_appletHookCookie, handleAppletHook, nullptr); + handleAppletHook (AppletHookType_OnFocusState, nullptr); + handleAppletHook (AppletHookType_OnOperationMode, nullptr); + + ImGuiIO &io = ImGui::GetIO (); + + io.IniFilename = nullptr; + + io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + + io.BackendPlatformName = "switch"; + + // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + io.KeyMap[ImGuiKey_Tab] = KBD_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = KBD_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = KBD_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = KBD_UP; + io.KeyMap[ImGuiKey_DownArrow] = KBD_DOWN; + io.KeyMap[ImGuiKey_PageUp] = KBD_PAGEUP; + io.KeyMap[ImGuiKey_PageDown] = KBD_PAGEDOWN; + io.KeyMap[ImGuiKey_Home] = KBD_HOME; + io.KeyMap[ImGuiKey_End] = KBD_END; + io.KeyMap[ImGuiKey_Insert] = KBD_INSERT; + io.KeyMap[ImGuiKey_Delete] = KBD_DELETE; + io.KeyMap[ImGuiKey_Backspace] = KBD_BACKSPACE; + io.KeyMap[ImGuiKey_Space] = KBD_SPACE; + io.KeyMap[ImGuiKey_Enter] = KBD_ENTER; + io.KeyMap[ImGuiKey_Escape] = KBD_ESC; + io.KeyMap[ImGuiKey_KeyPadEnter] = KBD_KPENTER; + io.KeyMap[ImGuiKey_A] = KBD_A; + io.KeyMap[ImGuiKey_C] = KBD_C; + io.KeyMap[ImGuiKey_V] = KBD_V; + io.KeyMap[ImGuiKey_X] = KBD_X; + io.KeyMap[ImGuiKey_Y] = KBD_Y; + io.KeyMap[ImGuiKey_Z] = KBD_Z; + + io.MouseDrawCursor = false; + + io.SetClipboardTextFn = setClipboardText; + io.GetClipboardTextFn = getClipboardText; + io.ClipboardUserData = nullptr; + + if (!loadFontAtlas ()) + { + ImFontConfig config; + config.MergeMode = false; + config.FontDataOwnedByAtlas = false; + + for (auto const &font : fonts) + { + io.Fonts->AddFontFromMemoryTTF (font.address, font.size, 14.0f, &config, nxFontRanges); + config.MergeMode = true; + } + io.Fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight; + io.Fonts->Build (); + + saveFontAtlas (); + } + else + std::printf ("Loaded font atlas from disk\n"); + + return true; +} + +void imgui::nx::newFrame () +{ + ImGuiIO &io = ImGui::GetIO (); + IM_ASSERT (io.Fonts->IsBuilt () && + "Font atlas not built! It is generally built by the renderer back-end. Missing call " + "to renderer _NewFrame() function?"); + + io.DisplaySize = ImVec2 (s_width, s_height); + io.DisplayFramebufferScale = ImVec2 (1.0f, 1.0f); + + // Setup time step + static auto const start = std::chrono::high_resolution_clock::now (); + static auto prev = start; + auto const now = std::chrono::high_resolution_clock::now (); + + io.DeltaTime = std::chrono::duration (now - prev).count (); + prev = now; + + updateMouseButtons (io); + updateMousePos (io); + updateTouch (io); + updateGamepads (io); + updateKeyboard (io); + + io.MouseDrawCursor = s_showMouse; + + // Clamp mouse to screen + s_mousePos.x = std::clamp (s_mousePos.x, 0.0f, s_width); + s_mousePos.y = std::clamp (s_mousePos.y, 0.0f, s_height); +} + +void imgui::nx::exit () +{ + appletUnhook (&s_appletHookCookie); +} diff --git a/source/nx/imgui_nx.h b/source/nx/imgui_nx.h new file mode 100644 index 0000000..a8ed0ae --- /dev/null +++ b/source/nx/imgui_nx.h @@ -0,0 +1,32 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#pragma once + +namespace imgui +{ +namespace nx +{ +bool init (); +void exit (); + +void newFrame (); +} +} diff --git a/source/nx/imgui_vsh.glsl b/source/nx/imgui_vsh.glsl new file mode 100644 index 0000000..5682eae --- /dev/null +++ b/source/nx/imgui_vsh.glsl @@ -0,0 +1,40 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#version 460 + +layout (location = 0) in vec2 inPos; +layout (location = 1) in vec2 inUv; +layout (location = 2) in vec4 inColor; + +layout (location = 0) out vec2 vtxUv; +layout (location = 1) out vec4 vtxColor; + +layout (std140, binding = 0) uniform VertUBO +{ + mat4 projMtx; +} ubo; + +void main() +{ + gl_Position = ubo.projMtx * vec4 (inPos, 0.0, 1.0); + vtxUv = inUv; + vtxColor = inColor; +} diff --git a/source/nx/init.c b/source/nx/init.c new file mode 100644 index 0000000..6f084e1 --- /dev/null +++ b/source/nx/init.c @@ -0,0 +1,77 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include + +#include + +#ifndef NDEBUG +static int s_fd = -1; +#endif + +static SocketInitConfig const s_socketInitConfig = { + .bsdsockets_version = 1, + + .tcp_tx_buf_size = 1 * 1024 * 1024, + .tcp_rx_buf_size = 1 * 1024 * 1024, + .tcp_tx_buf_max_size = 4 * 1024 * 1024, + .tcp_rx_buf_max_size = 4 * 1024 * 1024, + + .udp_tx_buf_size = 0x2400, + .udp_rx_buf_size = 0xA500, + + .sb_efficiency = 8, + + .num_bsd_sessions = 3, + .bsd_service_type = BsdServiceType_User, +}; + +u32 __nx_fs_num_sessions = 3; + +void userAppInit () +{ + appletLockExit (); + + romfsInit (); + plInitialize (); + + if (R_FAILED (socketInitialize (&s_socketInitConfig))) + return; + +#ifndef NDEBUG + s_fd = nxlinkStdio (); +#endif +} + +void userAppExit () +{ +#ifndef NDEBUG + if (s_fd >= 0) + { + close (s_fd); + socketExit (); + s_fd = -1; + } +#endif + + plExit (); + romfsExit (); + appletUnlockExit (); +} diff --git a/source/nx/platform.cpp b/source/nx/platform.cpp new file mode 100644 index 0000000..0c6b1ed --- /dev/null +++ b/source/nx/platform.cpp @@ -0,0 +1,167 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "platform.h" + +#include "imgui_deko3d.h" +#include "imgui_nx.h" + +#include "imgui.h" + +#include + +#include +#include + +bool platform::init () +{ + IMGUI_CHECKVERSION (); + ImGui::CreateContext (); + + if (!imgui::nx::init ()) + { + ImGui::DestroyContext (); + return false; + } + + imgui::deko3d::init (); + + return true; +} + +bool platform::loop () +{ + if (!appletMainLoop ()) + return false; + + hidScanInput (); + + auto const keysDown = hidKeysDown (CONTROLLER_P1_AUTO); + if (keysDown & KEY_PLUS) + return false; + + imgui::nx::newFrame (); + imgui::deko3d::newFrame (); + ImGui::NewFrame (); + + imgui::deko3d::test (); + + return true; +} + +void platform::render () +{ + ImGui::Render (); + + imgui::deko3d::render (); +} + +void platform::exit () +{ + imgui::nx::exit (); + imgui::deko3d::exit (); + + ImGui::DestroyContext (); +} + +/////////////////////////////////////////////////////////////////////////// +class platform::Thread::privateData_t +{ +public: + privateData_t () = default; + + privateData_t (std::function func_) : thread (func_) + { + } + + std::thread thread; +}; + +/////////////////////////////////////////////////////////////////////////// +platform::Thread::~Thread () = default; + +platform::Thread::Thread () : m_d (new privateData_t ()) +{ +} + +platform::Thread::Thread (std::function func_) : m_d (new privateData_t (func_)) +{ +} + +platform::Thread::Thread (Thread &&that_) : m_d (new privateData_t ()) +{ + std::swap (m_d, that_.m_d); +} + +platform::Thread &platform::Thread::operator= (Thread &&that_) +{ + std::swap (m_d, that_.m_d); + return *this; +} + +void platform::Thread::join () +{ + m_d->thread.join (); +} + +void platform::Thread::sleep (std::chrono::milliseconds const timeout_) +{ + std::this_thread::sleep_for (timeout_); +} + +/////////////////////////////////////////////////////////////////////////// +#define USE_STD_MUTEX 1 +class platform::Mutex::privateData_t +{ +public: +#if USE_STD_MUTEX + std::mutex mutex; +#else + ::Mutex mutex; +#endif +}; + +/////////////////////////////////////////////////////////////////////////// +platform::Mutex::~Mutex () = default; + +platform::Mutex::Mutex () : m_d (new privateData_t ()) +{ +#if !USE_STD_MUTEX + mutexInit (&m_d->mutex); +#endif +} + +void platform::Mutex::lock () +{ +#if USE_STD_MUTEX + m_d->mutex.lock (); +#else + mutexLock (&m_d->mutex); +#endif +} + +void platform::Mutex::unlock () +{ +#if USE_STD_MUTEX + m_d->mutex.unlock (); +#else + mutexUnlock (&m_d->mutex); +#endif +} diff --git a/source/pc/KHR/khrplatform.h b/source/pc/KHR/khrplatform.h new file mode 100644 index 0000000..7783cad --- /dev/null +++ b/source/pc/KHR/khrplatform.h @@ -0,0 +1 @@ +#error "Please use https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.3" diff --git a/source/pc/glad.c b/source/pc/glad.c new file mode 100644 index 0000000..7783cad --- /dev/null +++ b/source/pc/glad.c @@ -0,0 +1 @@ +#error "Please use https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.3" diff --git a/source/pc/glad/glad.h b/source/pc/glad/glad.h new file mode 100644 index 0000000..7783cad --- /dev/null +++ b/source/pc/glad/glad.h @@ -0,0 +1 @@ +#error "Please use https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.3" diff --git a/source/pc/imgui_impl_glfw.cpp b/source/pc/imgui_impl_glfw.cpp new file mode 100644 index 0000000..10e67bc --- /dev/null +++ b/source/pc/imgui_impl_glfw.cpp @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui" diff --git a/source/pc/imgui_impl_glfw.h b/source/pc/imgui_impl_glfw.h new file mode 100644 index 0000000..10e67bc --- /dev/null +++ b/source/pc/imgui_impl_glfw.h @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui" diff --git a/source/pc/imgui_impl_opengl3.cpp b/source/pc/imgui_impl_opengl3.cpp new file mode 100644 index 0000000..10e67bc --- /dev/null +++ b/source/pc/imgui_impl_opengl3.cpp @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui" diff --git a/source/pc/imgui_impl_opengl3.h b/source/pc/imgui_impl_opengl3.h new file mode 100644 index 0000000..10e67bc --- /dev/null +++ b/source/pc/imgui_impl_opengl3.h @@ -0,0 +1 @@ +#error "Please use https://github.com/ocornut/imgui" diff --git a/source/pc/platform.cpp b/source/pc/platform.cpp new file mode 100644 index 0000000..3cc737f --- /dev/null +++ b/source/pc/platform.cpp @@ -0,0 +1,259 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "platform.h" + +#include "imgui.h" + +#include + +#include + +#include "imgui_impl_glfw.h" +#include "imgui_impl_opengl3.h" + +#include +#include +#include +#include + +namespace +{ +std::unique_ptr s_mainWindow (nullptr, glfwDestroyWindow); + +void windowResize (GLFWwindow *const window_, int const width_, int const height_) +{ + (void)window_; + + if (!width_ || !height_) + return; + + glViewport (0, 0, width_, height_); +} + +#ifndef NDEBUG +void logCallback (GLenum const source_, + GLenum const type_, + GLuint const id_, + GLenum const severity_, + GLsizei const length_, + GLchar const *const message_, + void const *const userParam_) +{ + (void)source_; + (void)type_; + (void)id_; + (void)severity_; + (void)length_; + (void)userParam_; + // std::fprintf (stderr, "%s\n", message_); +} +#endif +} + +bool platform::init () +{ + if (!glfwInit ()) + { + std::fprintf (stderr, "Failed to initialize GLFW\n"); + return false; + } + + glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +#ifndef NDEBUG + glfwWindowHint (GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); +#endif + + // set depth buffer size + glfwWindowHint (GLFW_DEPTH_BITS, 24); + glfwWindowHint (GLFW_STENCIL_BITS, 8); + + s_mainWindow.reset (glfwCreateWindow (1280, 720, "Test Game", nullptr, nullptr)); + if (!s_mainWindow) + { + std::fprintf (stderr, "Failed to create window\n"); + glfwTerminate (); + return false; + } + + glfwSwapInterval (1); + + // create context + glfwMakeContextCurrent (s_mainWindow.get ()); + glfwSetFramebufferSizeCallback (s_mainWindow.get (), windowResize); + + if (!gladLoadGL ()) + { + std::fprintf (stderr, "gladLoadGL: failed\n"); + platform::exit (); + return false; + } + +#ifndef NDEBUG + GLint flags; + glGetIntegerv (GL_CONTEXT_FLAGS, &flags); + if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) + { + glEnable (GL_DEBUG_OUTPUT); + glEnable (GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback (logCallback, nullptr); + glDebugMessageControl (GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); + } +#endif + + glEnable (GL_CULL_FACE); + glFrontFace (GL_CCW); + glCullFace (GL_BACK); + + glEnable (GL_DEPTH_TEST); + glDepthFunc (GL_LEQUAL); + + glClearColor (104.0f / 255.0f, 176.0f / 255.0f, 216.0f / 255.0f, 1.0f); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + std::printf ("Renderer: %s\n", glGetString (GL_RENDERER)); + std::printf ("OpenGL Version: %s\n", glGetString (GL_VERSION)); + + IMGUI_CHECKVERSION (); + ImGui::CreateContext (); + + ImGui_ImplGlfw_InitForOpenGL (s_mainWindow.get (), true); + ImGui_ImplOpenGL3_Init ("#version 150"); + + ImGuiIO &io = ImGui::GetIO (); + io.IniFilename = nullptr; + + return true; +} + +bool platform::loop () +{ + bool inactive; + do + { + inactive = glfwGetWindowAttrib (s_mainWindow.get (), GLFW_ICONIFIED); + (inactive ? glfwWaitEvents : glfwPollEvents) (); + + if (glfwWindowShouldClose (s_mainWindow.get ())) + return false; + } while (inactive); + + ImGui_ImplOpenGL3_NewFrame (); + ImGui_ImplGlfw_NewFrame (); + ImGui::NewFrame (); + + return true; +} + +void platform::render () +{ + ImGui::Render (); + + int width; + int height; + glfwGetFramebufferSize (s_mainWindow.get (), &width, &height); + glViewport (0, 0, width, height); + glClearColor (0.45f, 0.55f, 0.60f, 1.00f); + glClear (GL_COLOR_BUFFER_BIT); + + ImGui_ImplOpenGL3_RenderDrawData (ImGui::GetDrawData ()); + + glfwSwapBuffers (s_mainWindow.get ()); +} + +void platform::exit () +{ + ImGui::DestroyContext (); + + s_mainWindow.reset (); + glfwTerminate (); +} + +/////////////////////////////////////////////////////////////////////////// +class platform::Thread::privateData_t +{ +public: + privateData_t () = default; + + privateData_t (std::function func_) : thread (func_) + { + } + + std::thread thread; +}; + +/////////////////////////////////////////////////////////////////////////// +platform::Thread::~Thread () = default; + +platform::Thread::Thread () : m_d (new privateData_t ()) +{ +} + +platform::Thread::Thread (std::function func_) : m_d (new privateData_t (func_)) +{ +} + +platform::Thread::Thread (Thread &&that_) : m_d (new privateData_t ()) +{ + std::swap (m_d, that_.m_d); +} + +platform::Thread &platform::Thread::operator= (Thread &&that_) +{ + std::swap (m_d, that_.m_d); + return *this; +} + +void platform::Thread::join () +{ + m_d->thread.join (); +} + +void platform::Thread::sleep (std::chrono::milliseconds const timeout_) +{ + std::this_thread::sleep_for (timeout_); +} + +/////////////////////////////////////////////////////////////////////////// +class platform::Mutex::privateData_t +{ +public: + std::mutex mutex; +}; + +/////////////////////////////////////////////////////////////////////////// +platform::Mutex::~Mutex () = default; + +platform::Mutex::Mutex () : m_d (new privateData_t ()) +{ +} + +void platform::Mutex::lock () +{ + m_d->mutex.lock (); +} + +void platform::Mutex::unlock () +{ + m_d->mutex.unlock (); +} diff --git a/source/sockAddr.cpp b/source/sockAddr.cpp new file mode 100644 index 0000000..26fecec --- /dev/null +++ b/source/sockAddr.cpp @@ -0,0 +1,150 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "sockAddr.h" + +#include + +#include +#include + +/////////////////////////////////////////////////////////////////////////// +SockAddr::~SockAddr () = default; + +SockAddr::SockAddr () = default; + +SockAddr::SockAddr (SockAddr const &that_) = default; + +SockAddr::SockAddr (SockAddr &&that_) = default; + +SockAddr &SockAddr::operator= (SockAddr const &that_) = default; + +SockAddr &SockAddr::operator= (SockAddr &&that_) = default; + +SockAddr::SockAddr (struct sockaddr const &addr_) +{ + switch (addr_.sa_family) + { + case AF_INET: + std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in)); + break; + +#ifndef _3DS + case AF_INET6: + std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in6)); + break; +#endif + } +} + +SockAddr::SockAddr (struct sockaddr_in const &addr_) + : SockAddr (reinterpret_cast (addr_)) +{ +} + +#ifndef _3DS +SockAddr::SockAddr (struct sockaddr_in6 const &addr_) + : SockAddr (reinterpret_cast (addr_)) +{ +} +#endif + +SockAddr::SockAddr (struct sockaddr_storage const &addr_) + : SockAddr (reinterpret_cast (addr_)) +{ +} + +SockAddr::operator struct sockaddr_in const & () const +{ + assert (m_addr.ss_family == AF_INET); + return reinterpret_cast (m_addr); +} + +#ifndef _3DS +SockAddr::operator struct sockaddr_in6 const & () const +{ + assert (m_addr.ss_family == AF_INET6); + return reinterpret_cast (m_addr); +} +#endif + +SockAddr::operator struct sockaddr_storage const & () const +{ + return m_addr; +} + +SockAddr::operator struct sockaddr * () +{ + return reinterpret_cast (&m_addr); +} + +SockAddr::operator struct sockaddr const * () const +{ + return reinterpret_cast (&m_addr); +} + +std::uint16_t SockAddr::port () const +{ + switch (m_addr.ss_family) + { + case AF_INET: + return ntohs (reinterpret_cast (&m_addr)->sin_port); + +#ifndef _3DS + case AF_INET6: + return ntohs (reinterpret_cast (&m_addr)->sin6_port); +#endif + } + + return 0; +} + +char const *SockAddr::name (char *buffer_, std::size_t size_) const +{ + switch (m_addr.ss_family) + { + case AF_INET: + return inet_ntop (AF_INET, + &reinterpret_cast (&m_addr)->sin_addr, + buffer_, + size_); + +#ifndef _3DS + case AF_INET6: + return inet_ntop (AF_INET6, + &reinterpret_cast (&m_addr)->sin6_addr, + buffer_, + size_); +#endif + } + + return nullptr; +} + +char const *SockAddr::name () const +{ +#if defined(_3DS) + thread_local static char buffer[INET_ADDRSTRLEN]; +#else + thread_local static char buffer[INET6_ADDRSTRLEN]; +#endif + + return name (buffer, sizeof (buffer)); +} diff --git a/source/socket.cpp b/source/socket.cpp new file mode 100644 index 0000000..3c88ecf --- /dev/null +++ b/source/socket.cpp @@ -0,0 +1,334 @@ +// ftpd is a server implementation based on the following: +// - RFC 959 (https://tools.ietf.org/html/rfc959) +// - RFC 3659 (https://tools.ietf.org/html/rfc3659) +// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html +// +// Copyright (C) 2020 Michael Theall +// +// 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 3 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, see . + +#include "socket.h" + +#include "log.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// +Socket::~Socket () +{ + if (m_listening) + Log::info ("Stop listening on [%s]:%u\n", m_sockName.name (), m_sockName.port ()); + if (m_connected) + Log::info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ()); + if (::close (m_fd) != 0) + Log::error ("close: %s\n", std::strerror (errno)); +} + +Socket::Socket (int const fd_) : m_fd (fd_), m_listening (false), m_connected (false) +{ +} + +Socket::Socket (int const fd_, SockAddr const &sockName_, SockAddr const &peerName_) + : m_sockName (sockName_), + m_peerName (peerName_), + m_fd (fd_), + m_listening (false), + m_connected (true) +{ +} + +UniqueSocket Socket::accept () +{ + SockAddr addr; + socklen_t addrLen = sizeof (struct sockaddr_storage); + + auto const fd = ::accept (m_fd, addr, &addrLen); + if (fd < 0) + { + Log::error ("accept: %s\n", std::strerror (errno)); + return {nullptr, {}}; + } + + Log::info ("Accepted connection from [%s]:%u\n", addr.name (), addr.port ()); + return UniqueSocket (new Socket (fd, m_sockName, addr)); +} + +int Socket::atMark () +{ + auto const rc = ::sockatmark (m_fd); + if (rc < 0) + Log::error ("sockatmark: %s\n", std::strerror (errno)); + + return rc; +} + +bool Socket::bind (SockAddr const &addr_) +{ + switch (static_cast (addr_).ss_family) + { + case AF_INET: + if (::bind (m_fd, addr_, sizeof (struct sockaddr_in)) != 0) + { + Log::error ("bind: %s\n", std::strerror (errno)); + return false; + } + break; + +#ifndef _3DS + case AF_INET6: + if (::bind (m_fd, addr_, sizeof (struct sockaddr_in6)) != 0) + { + Log::error ("bind: %s\n", std::strerror (errno)); + return false; + } + break; +#endif + + default: + errno = EINVAL; + Log::error ("bind: %s\n", std::strerror (errno)); + break; + } + + if (addr_.port () == 0) + { + socklen_t addrLen = sizeof (struct sockaddr_storage); + if (::getsockname (m_fd, m_sockName, &addrLen) != 0) + Log::error ("getsockname: %s\n", std::strerror (errno)); + } + else + m_sockName = addr_; + + return true; +} + +bool Socket::connect (SockAddr const &addr_) +{ + if (::connect (m_fd, addr_, sizeof (struct sockaddr_storage)) != 0) + { + if (errno != EINPROGRESS) + Log::error ("connect: %s\n", std::strerror (errno)); + else + { + m_peerName = addr_; + m_connected = true; + Log::info ("Connecting to [%s]:%u\n", addr_.name (), addr_.port ()); + } + return false; + } + + m_peerName = addr_; + m_connected = true; + Log::info ("Connected to [%s]:%u\n", addr_.name (), addr_.port ()); + return true; +} + +bool Socket::listen (int const backlog_) +{ + if (::listen (m_fd, backlog_) != 0) + { + Log::error ("listen: %s\n", std::strerror (errno)); + return false; + } + + m_listening = true; + return true; +} + +bool Socket::shutdown (int const how_) +{ + if (::shutdown (m_fd, how_) != 0) + { + Log::info ("shutdown: %s\n", std::strerror (errno)); + return false; + } + + return true; +} + +bool Socket::setLinger (bool const enable_, std::chrono::seconds const time_) +{ + struct linger linger; + linger.l_onoff = enable_; + linger.l_linger = time_.count (); + + if (::setsockopt (m_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof (linger)) != 0) + { + Log::error ("setsockopt(SO_LINGER, %s, %lus): %s\n", + enable_ ? "on" : "off", + static_cast (time_.count ()), + std::strerror (errno)); + return false; + } + + return true; +} + +bool Socket::setNonBlocking (bool const nonBlocking_) +{ + auto flags = ::fcntl (m_fd, F_GETFL, 0); + if (flags == -1) + { + Log::error ("fcntl(F_GETFL): %s\n", std::strerror (errno)); + return false; + } + + if (nonBlocking_) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if (::fcntl (m_fd, F_SETFL, flags) != 0) + { + Log::error ("fcntl(F_SETFL): %s\n", std::strerror (errno)); + return false; + } + + return true; +} + +bool Socket::setReuseAddress (bool const reuse_) +{ + int reuse = reuse_; + if (::setsockopt (m_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse)) != 0) + { + Log::error ("setsockopt(SO_REUSEADDR, %u): %s\n", reuse_, std::strerror (errno)); + return false; + } + + return true; +} + +bool Socket::setRecvBufferSize (std::size_t const size_) +{ + int size = size_; + if (::setsockopt (m_fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size)) != 0) + { + Log::error ("setsockopt(SO_RCVBUF, %zu): %s\n", size_, std::strerror (errno)); + return false; + } + + return true; +} + +bool Socket::setSendBufferSize (std::size_t const size_) +{ + int size = size_; + if (::setsockopt (m_fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof (size)) != 0) + { + Log::error ("setsockopt(SO_SNDBUF, %zu): %s\n", size_, std::strerror (errno)); + return false; + } + + return true; +} + +ssize_t Socket::read (void *const buffer_, std::size_t const size_, bool const oob_) +{ + assert (buffer_); + assert (size_); + auto const rc = ::recv (m_fd, buffer_, size_, oob_ ? MSG_OOB : 0); + if (rc < 0 && errno != EWOULDBLOCK) + Log::error ("recv: %s\n", std::strerror (errno)); + + return rc; +} + +ssize_t Socket::read (IOBuffer &buffer_, bool const oob_) +{ + auto const rc = read (buffer_.freeArea (), buffer_.freeSize (), oob_); + if (rc > 0) + buffer_.markUsed (rc); + + return rc; +} + +ssize_t Socket::write (void const *const buffer_, std::size_t const size_) +{ + assert (buffer_); + assert (size_); + auto const rc = ::send (m_fd, buffer_, size_, 0); + if (rc < 0 && errno != EWOULDBLOCK) + Log::error ("send: %s\n", std::strerror (errno)); + + return rc; +} + +ssize_t Socket::write (IOBuffer &buffer_) +{ + auto const rc = write (buffer_.usedArea (), buffer_.usedSize ()); + if (rc > 0) + buffer_.markFree (rc); + + return rc; +} + +SockAddr const &Socket::sockName () const +{ + return m_sockName; +} + +SockAddr const &Socket::peerName () const +{ + return m_peerName; +} + +UniqueSocket Socket::create () +{ + auto const fd = ::socket (AF_INET, SOCK_STREAM, 0); + if (fd < 0) + { + Log::error ("socket: %s\n", std::strerror (errno)); + return nullptr; + } + + return UniqueSocket (new Socket (fd)); +} + +int Socket::poll (PollInfo *const info_, + std::size_t const count_, + std::chrono::milliseconds const timeout_) +{ + if (count_ == 0) + return 0; + + auto const pfd = std::make_unique (count_); + for (std::size_t i = 0; i < count_; ++i) + { + pfd[i].fd = info_[i].socket.get ().m_fd; + pfd[i].events = info_[i].events; + pfd[i].revents = 0; + } + + auto const rc = ::poll (pfd.get (), count_, timeout_.count ()); + if (rc < 0) + { + Log::error ("poll: %s\n", std::strerror (errno)); + return rc; + } + + for (std::size_t i = 0; i < count_; ++i) + info_[i].revents = pfd[i].revents; + + return rc; +}