├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── assets ├── app.rsf ├── audio.wav ├── banner.png ├── icon.png ├── romfs │ └── config.json └── splash.bin ├── include ├── base64.h ├── configManager.h ├── directoryMenu.h ├── helpers.h ├── json.hpp ├── lets_encrypt_rootca.h ├── menuSystem.h ├── menus.h ├── networkSystem.h ├── secureNetworkRequest.h ├── systemCore.h └── types │ ├── menuItemsType.h │ ├── responsePairType.h │ └── uploadTypeEnumType.h └── source ├── base64.cpp ├── configManager.cpp ├── directoryMenu.cpp ├── helpers.cpp ├── main.cpp ├── menuSystem.cpp ├── menus.cpp ├── networkSystem.cpp ├── secureNetworkRequest.cpp └── systemCore.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | build/ 3 | lib/ 4 | .*/ 5 | *.3dsx 6 | *.smdh 7 | *.elf 8 | *.cia 9 | *.t3x 10 | *.bcfnt 11 | *~.DS_Store 12 | *.lst 13 | run.sh 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITARM)),) 6 | $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") 7 | endif 8 | 9 | ifeq ($(strip $(BUILDTOOLS)),) 10 | $(error "Please set BUILDTOOLS in your environment. export BUILDTOOLS=BUILDTOOLS") 11 | endif 12 | 13 | TOPDIR ?= $(CURDIR) 14 | include $(DEVKITARM)/3ds_rules 15 | 16 | #--------------------------------------------------------------------------------- 17 | # TARGET is the name of the output 18 | # BUILD is the directory where object files & intermediate files will be placed 19 | # SOURCES is a list of directories containing source code 20 | # DATA is a list of directories containing data files 21 | # INCLUDES is a list of directories containing header files 22 | # GRAPHICS is a list of directories containing graphics files 23 | # GFXBUILD is the directory where converted graphics files will be placed 24 | # If set to $(BUILD), it will statically link in the converted 25 | # files as if they were data files. 26 | # 27 | # NO_SMDH: if set to anything, no SMDH file is generated. 28 | # ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) 29 | # APP_TITLE is the name of the app stored in the SMDH file (Optional) 30 | # APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) 31 | # APP_AUTHOR is the author of the app stored in the SMDH file (Optional) 32 | # ICON is the filename of the icon (.png), relative to the project folder. 33 | # If not set, it attempts to use one of the following (in this order): 34 | # - .png 35 | # - icon.png 36 | # - /default_icon.png 37 | #--------------------------------------------------------------------------------- 38 | 39 | APP_TITLE := Citrahold 40 | APP_DESCRIPTION := Simple cloud-sync save tool 41 | APP_AUTHOR := Jamie Adams, regimensocial 42 | 43 | TARGET := $(notdir $(CURDIR)) 44 | BUILD := build 45 | SOURCES := source 46 | DATA := data 47 | INCLUDES := include /opt/devkitpro/libctru/include 48 | GRAPHICS := gfx 49 | GFXBUILD := $(BUILD) 50 | ROMFS := romfs 51 | GRAPHICS := assets/gfx 52 | ROMFS := assets/romfs 53 | GFXBUILD := $(ROMFS)/gfx 54 | 55 | # If left blank, will try to use "icon.png", "$(TARGET).png", or the default ctrulib icon, in that order 56 | ICON := assets/icon.png 57 | BANNER_AUDIO := assets/audio.wav 58 | BANNER_IMAGE := assets/banner.png 59 | RSF_PATH := assets/app.rsf 60 | 61 | # If left blank, makerom will use the default Homebrew logo 62 | LOGO := assets/splash.bin 63 | 64 | # If left blank, makerom will use default values (0xff3ff and CTR-P-CTAP, respectively) 65 | UNIQUE_ID := 0xFF3FE 66 | PRODUCT_CODE := CTR-HB-CTHD 67 | 68 | # Don't really need to change this 69 | ICON_FLAGS := nosavebackups,visible 70 | 71 | VERSION_MAJOR := 1 72 | VERSION_MINOR := 1 73 | VERSION_MICRO := 2 74 | 75 | #--------------------------------------------------------------------------------- 76 | # options for code generation 77 | #--------------------------------------------------------------------------------- 78 | ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft 79 | 80 | CFLAGS := -g -Wall -O2 -mword-relocations \ 81 | -ffunction-sections \ 82 | $(ARCH) 83 | 84 | CFLAGS += $(INCLUDE) -D__3DS__ -Wall -Wextra -Wno-psabi -Wno-unused-parameter 85 | 86 | CXXFLAGS := -DVERSION_MAJOR=$(VERSION_MAJOR) -DVERSION_MINOR=$(VERSION_MINOR) -DVERSION_MICRO=$(VERSION_MICRO) $(CFLAGS) -fno-rtti -std=gnu++17 87 | 88 | ASFLAGS := -g $(ARCH) 89 | LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) 90 | 91 | LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lcitro2d -lcitro3d -lctru -lm -lm -lbz2 -llzma -lz 92 | 93 | #--------------------------------------------------------------------------------- 94 | # list of directories containing libraries, this must be the top level containing 95 | # include and lib 96 | #--------------------------------------------------------------------------------- 97 | LIBDIRS := $(PORTLIBS) $(CTRULIB) 98 | 99 | 100 | #--------------------------------------------------------------------------------- 101 | # no real need to edit anything past this point unless you need to add additional 102 | # rules for different file extensions 103 | #--------------------------------------------------------------------------------- 104 | ifneq ($(BUILD),$(notdir $(CURDIR))) 105 | #--------------------------------------------------------------------------------- 106 | 107 | export OUTPUT := $(CURDIR)/$(TARGET) 108 | export TOPDIR := $(CURDIR) 109 | 110 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 111 | $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) \ 112 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 113 | 114 | export DEPSDIR := $(CURDIR)/$(BUILD) 115 | 116 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 117 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 118 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 119 | PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) 120 | SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) 121 | GFXFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.t3s))) 122 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 123 | 124 | #--------------------------------------------------------------------------------- 125 | # use CXX for linking C++ projects, CC for standard C 126 | #--------------------------------------------------------------------------------- 127 | ifeq ($(strip $(CPPFILES)),) 128 | #--------------------------------------------------------------------------------- 129 | export LD := $(CC) 130 | #--------------------------------------------------------------------------------- 131 | else 132 | #--------------------------------------------------------------------------------- 133 | export LD := $(CXX) 134 | #--------------------------------------------------------------------------------- 135 | endif 136 | #--------------------------------------------------------------------------------- 137 | 138 | #--------------------------------------------------------------------------------- 139 | ifeq ($(GFXBUILD),$(BUILD)) 140 | #--------------------------------------------------------------------------------- 141 | export T3XFILES := $(GFXFILES:.t3s=.t3x) 142 | #--------------------------------------------------------------------------------- 143 | else 144 | #--------------------------------------------------------------------------------- 145 | export ROMFS_T3XFILES := $(patsubst %.t3s, $(GFXBUILD)/%.t3x, $(GFXFILES)) 146 | export T3XHFILES := $(patsubst %.t3s, $(BUILD)/%.h, $(GFXFILES)) 147 | #--------------------------------------------------------------------------------- 148 | endif 149 | #--------------------------------------------------------------------------------- 150 | 151 | export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 152 | 153 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) \ 154 | $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ 155 | $(addsuffix .o,$(T3XFILES)) 156 | 157 | export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) 158 | 159 | export HFILES := $(PICAFILES:.v.pica=_shbin.h) $(SHLISTFILES:.shlist=_shbin.h) \ 160 | $(addsuffix .h,$(subst .,_,$(BINFILES))) \ 161 | $(GFXFILES:.t3s=.h) 162 | 163 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 164 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 165 | -I$(CURDIR)/$(BUILD) 166 | 167 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 168 | 169 | export _3DSXDEPS := $(if $(NO_SMDH),,$(OUTPUT).smdh) 170 | 171 | ifeq ($(strip $(ICON)),) 172 | icons := $(wildcard *.png) 173 | ifneq (,$(findstring $(TARGET).png,$(icons))) 174 | export APP_ICON := $(TOPDIR)/$(TARGET).png 175 | else 176 | ifneq (,$(findstring icon.png,$(icons))) 177 | export APP_ICON := $(TOPDIR)/icon.png 178 | endif 179 | endif 180 | else 181 | export APP_ICON := $(TOPDIR)/$(ICON) 182 | endif 183 | 184 | ifeq ($(strip $(NO_SMDH)),) 185 | export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh 186 | endif 187 | 188 | ifneq ($(ROMFS),) 189 | export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) 190 | endif 191 | 192 | .PHONY: all clean 193 | 194 | #--------------------------------------------------------------------------------- 195 | all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) 196 | 197 | @mkdir -p $(BUILD) $(GFXBUILD) $(OUTDIR) 198 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile $(OUTPUT).3dsx 199 | @$(BUILDTOOLS)/bannertool makebanner -i "$(BANNER_IMAGE)" -a "$(BANNER_AUDIO)" -o $(BUILD)/banner.bnr 200 | @$(BUILDTOOLS)/bannertool makesmdh -s "$(APP_TITLE)" -l "$(APP_DESCRIPTION)" -p "$(APP_AUTHOR)" -i "$(APP_ICON)" -f "$(ICON_FLAGS)" -o $(BUILD)/icon.icn 201 | @$(BUILDTOOLS)/makerom -f cia -o $(OUTPUT).cia -target t -exefslogo -elf "$(OUTPUT).elf" -rsf "$(RSF_PATH)" -ver "$$(($(VERSION_MAJOR)*1024+$(VERSION_MINOR)*16+$(VERSION_MICRO)))" -banner "$(BUILD)/banner.bnr" -icon "$(BUILD)/icon.icn" -DAPP_TITLE="$(APP_TITLE)" -DAPP_PRODUCT_CODE="$(PRODUCT_CODE)" -DAPP_UNIQUE_ID="$(UNIQUE_ID)" -logo "$(LOGO)" 202 | 203 | 204 | $(BUILD): 205 | @mkdir -p $@ 206 | 207 | ifneq ($(GFXBUILD),$(BUILD)) 208 | $(GFXBUILD): 209 | @mkdir -p $@ 210 | endif 211 | 212 | ifneq ($(DEPSDIR),$(BUILD)) 213 | $(DEPSDIR): 214 | @mkdir -p $@ 215 | endif 216 | 217 | #--------------------------------------------------------------------------------- 218 | clean: 219 | @echo clean ... 220 | @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf $(GFXBUILD) 221 | 222 | #--------------------------------------------------------------------------------- 223 | $(GFXBUILD)/%.t3x $(BUILD)/%.h : %.t3s 224 | #--------------------------------------------------------------------------------- 225 | @echo $(notdir $<) 226 | @tex3ds -i $< -H $(BUILD)/$*.h -d $(DEPSDIR)/$*.d -o $(GFXBUILD)/$*.t3x 227 | 228 | #--------------------------------------------------------------------------------- 229 | else 230 | 231 | #--------------------------------------------------------------------------------- 232 | # main targets 233 | #--------------------------------------------------------------------------------- 234 | $(OUTPUT).3dsx : $(OUTPUT).elf $(_3DSXDEPS) 235 | 236 | $(OFILES_SOURCES) : $(HFILES) 237 | 238 | $(OUTPUT).elf : $(OFILES) 239 | 240 | #--------------------------------------------------------------------------------- 241 | # you need a rule like this for each extension you use as binary data 242 | #--------------------------------------------------------------------------------- 243 | %.bin.o %_bin.h : %.bin 244 | #--------------------------------------------------------------------------------- 245 | @echo $(notdir $<) 246 | @$(bin2o) 247 | 248 | #--------------------------------------------------------------------------------- 249 | .PRECIOUS : %.t3x %.shbin 250 | #--------------------------------------------------------------------------------- 251 | %.t3x.o %_t3x.h : %.t3x 252 | #--------------------------------------------------------------------------------- 253 | $(SILENTMSG) $(notdir $<) 254 | $(bin2o) 255 | 256 | #--------------------------------------------------------------------------------- 257 | %.shbin.o %_shbin.h : %.shbin 258 | #--------------------------------------------------------------------------------- 259 | $(SILENTMSG) $(notdir $<) 260 | $(bin2o) 261 | 262 | -include $(DEPSDIR)/*.d 263 | 264 | #--------------------------------------------------------------------------------------- 265 | endif 266 | #--------------------------------------------------------------------------------------- 267 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Citrahold-3DS 2 | Citrahold is a 3DS Homebrew App, a piece of software, and a web service which allows you to easily sync your saves between 3DSes and Emulators. 3 | 4 | **Get started, [check out the website](http://citrahold.com/)!** 5 | - **[Video Demo / Tutorial](https://www.youtube.com/watch?v=MsqyN6I3RAM)** 6 | - **[Download Citrahold 3DS](https://github.com/regimensocial/Citrahold-3DS/releases/latest)** 7 | - **[Download Citrahold PC](https://github.com/regimensocial/citraholdUI/releases/latest)** 8 | 9 | For **developers** curious about the other repos which make this work (they don't get nearly as much attraction so I thought I'd link them): 10 | - [Citrahold-PC](https://github.com/regimensocial/citraholdUI) 11 | - [Citrahold-Server](https://github.com/regimensocial/Citrahold-Server) 12 | 13 | I have future plans for Citrahold in 2025 14 | -------------------------------------------------------------------------------- /assets/app.rsf: -------------------------------------------------------------------------------- 1 | BasicInfo: 2 | Title : $(APP_TITLE) 3 | ProductCode : $(APP_PRODUCT_CODE) 4 | Logo : Homebrew 5 | 6 | RomFs: 7 | RootPath : assets/romfs 8 | 9 | TitleInfo: 10 | Category : Application 11 | UniqueId : $(APP_UNIQUE_ID) 12 | 13 | Option: 14 | UseOnSD : true # true if App is to be installed to SD 15 | FreeProductCode : true # Removes limitations on ProductCode 16 | MediaFootPadding : false # If true CCI files are created with padding 17 | EnableCrypt : false # Enables encryption for NCCH and CIA 18 | EnableCompress : true # Compresses where applicable (currently only exefs:/.code) 19 | 20 | AccessControlInfo: 21 | CoreVersion : 2 22 | 23 | # Exheader Format Version 24 | DescVersion : 2 25 | 26 | # Minimum Required Kernel Version (below is for 4.5.0) 27 | ReleaseKernelMajor : "02" 28 | ReleaseKernelMinor : "46" 29 | 30 | # ExtData 31 | UseExtSaveData : false # enables ExtData 32 | #ExtSaveDataId : 0x300 # only set this when the ID is different to the UniqueId 33 | 34 | # FS:USER Archive Access Permissions 35 | # Uncomment as required 36 | FileSystemAccess: 37 | - CategorySystemApplication 38 | - CategoryHardwareCheck 39 | - CategoryFileSystemTool 40 | - Debug 41 | - TwlCardBackup 42 | - TwlNandData 43 | - Boss 44 | - DirectSdmc 45 | - Core 46 | - CtrNandRo 47 | - CtrNandRw 48 | - CtrNandRoWrite 49 | - CategorySystemSettings 50 | - CardBoard 51 | - ExportImportIvs 52 | - DirectSdmcWrite 53 | - SwitchCleanup 54 | - SaveDataMove 55 | - Shop 56 | - Shell 57 | - CategoryHomeMenu 58 | - SeedDB 59 | IoAccessControl: 60 | - FsMountNand 61 | - FsMountNandRoWrite 62 | - FsMountTwln 63 | - FsMountWnand 64 | - FsMountCardSpi 65 | - UseSdif3 66 | - CreateSeed 67 | - UseCardSpi 68 | 69 | # Process Settings 70 | MemoryType : Application # Application/System/Base 71 | SystemMode : 64MB # 64MB(Default)/96MB/80MB/72MB/32MB 72 | IdealProcessor : 0 73 | AffinityMask : 1 74 | Priority : 16 75 | MaxCpu : 0x9E # Default 76 | HandleTableSize : 0x200 77 | DisableDebug : false 78 | EnableForceDebug : false 79 | CanWriteSharedPage : true 80 | CanUsePrivilegedPriority : false 81 | CanUseNonAlphabetAndNumber : true 82 | PermitMainFunctionArgument : true 83 | CanShareDeviceMemory : true 84 | RunnableOnSleep : false 85 | SpecialMemoryArrange : true 86 | 87 | # New3DS Exclusive Process Settings 88 | SystemModeExt : Legacy # Legacy(Default)/124MB/178MB Legacy:Use Old3DS SystemMode 89 | CpuSpeed : 268MHz # 268MHz(Default)/804MHz 90 | EnableL2Cache : false # false(default)/true 91 | CanAccessCore2 : false 92 | 93 | # Virtual Address Mappings 94 | IORegisterMapping: 95 | - 1ff00000-1ff7ffff # DSP memory 96 | MemoryMapping: 97 | - 1f000000-1f5fffff:r # VRAM 98 | 99 | # Accessible SVCs, : 100 | SystemCallAccess: 101 | ControlMemory: 1 102 | QueryMemory: 2 103 | ExitProcess: 3 104 | GetProcessAffinityMask: 4 105 | SetProcessAffinityMask: 5 106 | GetProcessIdealProcessor: 6 107 | SetProcessIdealProcessor: 7 108 | CreateThread: 8 109 | ExitThread: 9 110 | SleepThread: 10 111 | GetThreadPriority: 11 112 | SetThreadPriority: 12 113 | GetThreadAffinityMask: 13 114 | SetThreadAffinityMask: 14 115 | GetThreadIdealProcessor: 15 116 | SetThreadIdealProcessor: 16 117 | GetCurrentProcessorNumber: 17 118 | Run: 18 119 | CreateMutex: 19 120 | ReleaseMutex: 20 121 | CreateSemaphore: 21 122 | ReleaseSemaphore: 22 123 | CreateEvent: 23 124 | SignalEvent: 24 125 | ClearEvent: 25 126 | CreateTimer: 26 127 | SetTimer: 27 128 | CancelTimer: 28 129 | ClearTimer: 29 130 | CreateMemoryBlock: 30 131 | MapMemoryBlock: 31 132 | UnmapMemoryBlock: 32 133 | CreateAddressArbiter: 33 134 | ArbitrateAddress: 34 135 | CloseHandle: 35 136 | WaitSynchronization1: 36 137 | WaitSynchronizationN: 37 138 | SignalAndWait: 38 139 | DuplicateHandle: 39 140 | GetSystemTick: 40 141 | GetHandleInfo: 41 142 | GetSystemInfo: 42 143 | GetProcessInfo: 43 144 | GetThreadInfo: 44 145 | ConnectToPort: 45 146 | SendSyncRequest1: 46 147 | SendSyncRequest2: 47 148 | SendSyncRequest3: 48 149 | SendSyncRequest4: 49 150 | SendSyncRequest: 50 151 | OpenProcess: 51 152 | OpenThread: 52 153 | GetProcessId: 53 154 | GetProcessIdOfThread: 54 155 | GetThreadId: 55 156 | GetResourceLimit: 56 157 | GetResourceLimitLimitValues: 57 158 | GetResourceLimitCurrentValues: 58 159 | GetThreadContext: 59 160 | Break: 60 161 | OutputDebugString: 61 162 | ControlPerformanceCounter: 62 163 | CreatePort: 71 164 | CreateSessionToPort: 72 165 | CreateSession: 73 166 | AcceptSession: 74 167 | ReplyAndReceive1: 75 168 | ReplyAndReceive2: 76 169 | ReplyAndReceive3: 77 170 | ReplyAndReceive4: 78 171 | ReplyAndReceive: 79 172 | BindInterrupt: 80 173 | UnbindInterrupt: 81 174 | InvalidateProcessDataCache: 82 175 | StoreProcessDataCache: 83 176 | FlushProcessDataCache: 84 177 | StartInterProcessDma: 85 178 | StopDma: 86 179 | GetDmaState: 87 180 | RestartDma: 88 181 | DebugActiveProcess: 96 182 | BreakDebugProcess: 97 183 | TerminateDebugProcess: 98 184 | GetProcessDebugEvent: 99 185 | ContinueDebugEvent: 100 186 | GetProcessList: 101 187 | GetThreadList: 102 188 | GetDebugThreadContext: 103 189 | SetDebugThreadContext: 104 190 | QueryDebugProcessMemory: 105 191 | ReadProcessMemory: 106 192 | WriteProcessMemory: 107 193 | SetHardwareBreakPoint: 108 194 | GetDebugThreadParam: 109 195 | ControlProcessMemory: 112 196 | MapProcessMemory: 113 197 | UnmapProcessMemory: 114 198 | CreateCodeSet: 115 199 | CreateProcess: 117 200 | TerminateProcess: 118 201 | SetProcessResourceLimits: 119 202 | CreateResourceLimit: 120 203 | SetResourceLimitValues: 121 204 | AddCodeSegment: 122 205 | Backdoor: 123 206 | KernelSetState: 124 207 | QueryProcessMemory: 125 208 | 209 | # Service List 210 | # Maximum 34 services (32 if firmware is prior to 9.6.0) 211 | ServiceAccessControl: 212 | - APT:U 213 | - ac:u 214 | - am:net 215 | - boss:U 216 | - cam:u 217 | - cecd:u 218 | - cfg:nor 219 | - cfg:u 220 | - csnd:SND 221 | - dsp::DSP 222 | - frd:u 223 | - fs:USER 224 | - gsp::Gpu 225 | - gsp::Lcd 226 | - hid:USER 227 | - http:C 228 | - ir:rst 229 | - ir:u 230 | - ir:USER 231 | - mic:u 232 | - ndm:u 233 | - news:s 234 | - nwm::EXT 235 | - nwm::UDS 236 | - ptm:sysm 237 | - ptm:u 238 | - pxi:dev 239 | - soc:U 240 | - ssl:C 241 | - y2r:u 242 | 243 | 244 | SystemControlInfo: 245 | SaveDataSize: 0KB # Change if the app uses savedata 246 | RemasterVersion: 2 247 | StackSize: 0x40000 248 | 249 | # Modules that run services listed above should be included below 250 | # Maximum 48 dependencies 251 | # : 252 | Dependency: 253 | ac: 0x0004013000002402 254 | #act: 0x0004013000003802 255 | am: 0x0004013000001502 256 | boss: 0x0004013000003402 257 | camera: 0x0004013000001602 258 | cecd: 0x0004013000002602 259 | cfg: 0x0004013000001702 260 | codec: 0x0004013000001802 261 | csnd: 0x0004013000002702 262 | dlp: 0x0004013000002802 263 | dsp: 0x0004013000001a02 264 | friends: 0x0004013000003202 265 | gpio: 0x0004013000001b02 266 | gsp: 0x0004013000001c02 267 | hid: 0x0004013000001d02 268 | http: 0x0004013000002902 269 | i2c: 0x0004013000001e02 270 | ir: 0x0004013000003302 271 | mcu: 0x0004013000001f02 272 | mic: 0x0004013000002002 273 | ndm: 0x0004013000002b02 274 | news: 0x0004013000003502 275 | #nfc: 0x0004013000004002 276 | nim: 0x0004013000002c02 277 | nwm: 0x0004013000002d02 278 | pdn: 0x0004013000002102 279 | ps: 0x0004013000003102 280 | ptm: 0x0004013000002202 281 | #qtm: 0x0004013020004202 282 | ro: 0x0004013000003702 283 | socket: 0x0004013000002e02 284 | spi: 0x0004013000002302 285 | ssl: 0x0004013000002f02 -------------------------------------------------------------------------------- /assets/audio.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regimensocial/Citrahold-3DS/1c71efcacb23cd69f235dbc9492b099cf38e5fbb/assets/audio.wav -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regimensocial/Citrahold-3DS/1c71efcacb23cd69f235dbc9492b099cf38e5fbb/assets/banner.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regimensocial/Citrahold-3DS/1c71efcacb23cd69f235dbc9492b099cf38e5fbb/assets/icon.png -------------------------------------------------------------------------------- /assets/romfs/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "filter": [ 3 | 4 | ], 5 | "favorites": [ 6 | 7 | ], 8 | "additional_save_folders": { 9 | 10 | }, 11 | "additional_extdata_folders": { 12 | 13 | }, 14 | "nand_saves": false, 15 | "scan_cart": false, 16 | "version": 3 17 | } -------------------------------------------------------------------------------- /assets/splash.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regimensocial/Citrahold-3DS/1c71efcacb23cd69f235dbc9492b099cf38e5fbb/assets/splash.bin -------------------------------------------------------------------------------- /include/base64.h: -------------------------------------------------------------------------------- 1 | #ifndef _BASE64_H_ 2 | #define _BASE64_H_ 3 | 4 | #include 5 | #include 6 | typedef unsigned char BYTE; 7 | 8 | std::string base64_encode(BYTE const* buf, unsigned int bufLen); 9 | std::vector base64_decode(std::string const&); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /include/configManager.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIGMANAGER_H 2 | #define CONFIGMANAGER_H 3 | 4 | #include 5 | #include 6 | #include "types/uploadTypeEnumType.h" 7 | #include "json.hpp" 8 | 9 | class ConfigManager { 10 | public: 11 | ConfigManager(); 12 | 13 | std::filesystem::path getConfigDirectory() const; 14 | std::filesystem::path getGamesDirectory(UploadTypeEnum type = UploadTypeEnum::SAVES) const; 15 | 16 | void updateConfigFile(nlohmann::json newConfig); 17 | void setToken(std::string token); 18 | 19 | nlohmann::json getGameIDFile(UploadTypeEnum type); 20 | 21 | // consider addGameIDToFile 22 | 23 | std::string getGamePathFromID(UploadTypeEnum type, std::string gameID); 24 | std::string getGameIDFromPath(UploadTypeEnum type, std::string gamePath); 25 | int getNumberOfGameIDs(UploadTypeEnum type); 26 | 27 | void addGameIDToFile(UploadTypeEnum type, std::string gameID, std::string gamePath); 28 | void removeGameIDFromFile(UploadTypeEnum type, std::string gameID); 29 | void renameGameIDInFile(UploadTypeEnum type, std::string oldGameID, std::string newGameID); 30 | void redirectGameIDInFile(UploadTypeEnum type, std::string gameID, std::string newPath); 31 | 32 | nlohmann::json getConfig() const; 33 | std::string getToken() const; 34 | bool getDeleteSaveAfterUpload() const; 35 | void setDeleteSaveAfterUpload(bool deleteSaveAfterUpload); 36 | 37 | std::string userID; 38 | void resetBothGameIDFiles(); 39 | 40 | bool loggedIn(); 41 | 42 | private: 43 | void updateGameIDFile(UploadTypeEnum type, nlohmann::json newFile); 44 | 45 | std::filesystem::path configDirectory; 46 | std::filesystem::path gamesDirectory; 47 | nlohmann::json config; 48 | }; 49 | 50 | #endif // CONFIGMANAGER_H 51 | -------------------------------------------------------------------------------- /include/directoryMenu.h: -------------------------------------------------------------------------------- 1 | #ifndef DIRECTORYMENU_H 2 | #define DIRECTORYMENU_H 3 | #include 4 | #include 5 | #include "types/menuItemsType.h" 6 | #include "types/uploadTypeEnumType.h" 7 | #include <3ds.h> 8 | #include 9 | #include 10 | #include "json.hpp" 11 | 12 | class DirectoryMenu 13 | { 14 | 15 | public: 16 | 17 | DirectoryMenu(); 18 | std::filesystem::path getCurrentDirectory(); 19 | menuItems getCurrentDirectoryMenuItems(); 20 | 21 | menuItems getGameIDDirectoryMenuItems(nlohmann::json &gameIDJSON, menuFunctions nextAction); 22 | menuItems getSaveSelectionMenuItems(std::filesystem::path gamePath); 23 | 24 | void setCurrentDirectory(std::filesystem::path newDirectory); 25 | std::filesystem::path getGamePathFromGameID(std::string gameID, nlohmann::json &gameIDJSON); 26 | 27 | 28 | private: 29 | 30 | std::filesystem::path currentDirectory = "/3ds/Checkpoint"; 31 | 32 | }; 33 | 34 | #endif // DIRECTORYMENU_H -------------------------------------------------------------------------------- /include/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef HELPERS_H 2 | #define HELPERS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | std::string trim(const std::string& str); 9 | void safeDirectoryRemove(const std::filesystem::path &path); 10 | 11 | #endif // HELPERS_H -------------------------------------------------------------------------------- /include/lets_encrypt_rootca.h: -------------------------------------------------------------------------------- 1 | /* 2 | Had some trouble getting an actual PEM file to work, so this was generated using 3 | xxd -i ./lets_encrypt_rootca.pem > ./lets_encrypt_rootca_pem.h 4 | 5 | You can get the original PEM from https://letsencrypt.org/certificates/ 6 | This should work for everything but it definitely works with our backend since 7 | that uses Let's Encrypt. 8 | 9 | This specific certificate is valid 10 | Not Before: SOME TIME IN 2015 11 | Not After : JUNE 2035 12 | 13 | It will need to be replaced before 2035, and the project must 14 | be rebuilt and reinstalled on all devices. 15 | */ 16 | 17 | // THESE ARE MANUALLY ADDED AND MUST BE UPDATED WHEN THE CERTIFICATE IS REPLACED 18 | const int YEAR_EXPIRY = 2035; 19 | const int MONTH_EXPIRY = 6; 20 | // 21 | 22 | unsigned char __lets_encrypt_rootca_pem[] = { 23 | 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 24 | 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 25 | 0x2d, 0x2d, 0x2d, 0x0d, 0x0a, 0x4d, 0x49, 0x49, 0x46, 0x61, 0x7a, 0x43, 26 | 0x43, 0x41, 0x31, 0x4f, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 27 | 0x52, 0x41, 0x49, 0x49, 0x51, 0x7a, 0x37, 0x44, 0x53, 0x51, 0x4f, 0x4e, 28 | 0x5a, 0x52, 0x47, 0x50, 0x67, 0x75, 0x32, 0x4f, 0x43, 0x69, 0x77, 0x41, 29 | 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 30 | 0x4e, 0x41, 0x51, 0x45, 0x4c, 0x42, 0x51, 0x41, 0x77, 0x0d, 0x0a, 0x54, 31 | 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 32 | 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x4b, 0x54, 0x41, 0x6e, 0x42, 33 | 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x49, 0x45, 0x6c, 0x75, 0x64, 34 | 0x47, 0x56, 0x79, 0x62, 0x6d, 0x56, 0x30, 0x49, 0x46, 0x4e, 0x6c, 0x59, 35 | 0x33, 0x56, 0x79, 0x61, 0x58, 0x52, 0x35, 0x49, 0x46, 0x4a, 0x6c, 0x63, 36 | 0x32, 0x56, 0x68, 0x0d, 0x0a, 0x63, 0x6d, 0x4e, 0x6f, 0x49, 0x45, 0x64, 37 | 0x79, 0x62, 0x33, 0x56, 0x77, 0x4d, 0x52, 0x55, 0x77, 0x45, 0x77, 0x59, 38 | 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x77, 0x78, 0x4a, 0x55, 0x31, 0x4a, 39 | 0x48, 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, 0x51, 0x67, 0x57, 0x44, 0x45, 40 | 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x54, 0x55, 0x77, 0x4e, 0x6a, 0x41, 41 | 0x30, 0x4d, 0x54, 0x45, 0x77, 0x4e, 0x44, 0x4d, 0x34, 0x0d, 0x0a, 0x57, 42 | 0x68, 0x63, 0x4e, 0x4d, 0x7a, 0x55, 0x77, 0x4e, 0x6a, 0x41, 0x30, 0x4d, 43 | 0x54, 0x45, 0x77, 0x4e, 0x44, 0x4d, 0x34, 0x57, 0x6a, 0x42, 0x50, 0x4d, 44 | 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 45 | 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x70, 0x4d, 0x43, 0x63, 0x47, 0x41, 46 | 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x67, 0x53, 0x57, 0x35, 0x30, 0x5a, 47 | 0x58, 0x4a, 0x75, 0x0d, 0x0a, 0x5a, 0x58, 0x51, 0x67, 0x55, 0x32, 0x56, 48 | 0x6a, 0x64, 0x58, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x67, 0x55, 0x6d, 0x56, 49 | 0x7a, 0x5a, 0x57, 0x46, 0x79, 0x59, 0x32, 0x67, 0x67, 0x52, 0x33, 0x4a, 50 | 0x76, 0x64, 0x58, 0x41, 0x78, 0x46, 0x54, 0x41, 0x54, 0x42, 0x67, 0x4e, 51 | 0x56, 0x42, 0x41, 0x4d, 0x54, 0x44, 0x45, 0x6c, 0x54, 0x55, 0x6b, 0x63, 52 | 0x67, 0x55, 0x6d, 0x39, 0x76, 0x64, 0x43, 0x42, 0x59, 0x0d, 0x0a, 0x4d, 53 | 0x54, 0x43, 0x43, 0x41, 0x69, 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 54 | 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 55 | 0x51, 0x41, 0x44, 0x67, 0x67, 0x49, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, 56 | 0x67, 0x6f, 0x43, 0x67, 0x67, 0x49, 0x42, 0x41, 0x4b, 0x33, 0x6f, 0x4a, 57 | 0x48, 0x50, 0x30, 0x46, 0x44, 0x66, 0x7a, 0x6d, 0x35, 0x34, 0x72, 0x56, 58 | 0x79, 0x67, 0x63, 0x0d, 0x0a, 0x68, 0x37, 0x37, 0x63, 0x74, 0x39, 0x38, 59 | 0x34, 0x6b, 0x49, 0x78, 0x75, 0x50, 0x4f, 0x5a, 0x58, 0x6f, 0x48, 0x6a, 60 | 0x33, 0x64, 0x63, 0x4b, 0x69, 0x2f, 0x76, 0x56, 0x71, 0x62, 0x76, 0x59, 61 | 0x41, 0x54, 0x79, 0x6a, 0x62, 0x33, 0x6d, 0x69, 0x47, 0x62, 0x45, 0x53, 62 | 0x54, 0x74, 0x72, 0x46, 0x6a, 0x2f, 0x52, 0x51, 0x53, 0x61, 0x37, 0x38, 63 | 0x66, 0x30, 0x75, 0x6f, 0x78, 0x6d, 0x79, 0x46, 0x2b, 0x0d, 0x0a, 0x30, 64 | 0x54, 0x4d, 0x38, 0x75, 0x6b, 0x6a, 0x31, 0x33, 0x58, 0x6e, 0x66, 0x73, 65 | 0x37, 0x6a, 0x2f, 0x45, 0x76, 0x45, 0x68, 0x6d, 0x6b, 0x76, 0x42, 0x69, 66 | 0x6f, 0x5a, 0x78, 0x61, 0x55, 0x70, 0x6d, 0x5a, 0x6d, 0x79, 0x50, 0x66, 67 | 0x6a, 0x78, 0x77, 0x76, 0x36, 0x30, 0x70, 0x49, 0x67, 0x62, 0x7a, 0x35, 68 | 0x4d, 0x44, 0x6d, 0x67, 0x4b, 0x37, 0x69, 0x53, 0x34, 0x2b, 0x33, 0x6d, 69 | 0x58, 0x36, 0x55, 0x0d, 0x0a, 0x41, 0x35, 0x2f, 0x54, 0x52, 0x35, 0x64, 70 | 0x38, 0x6d, 0x55, 0x67, 0x6a, 0x55, 0x2b, 0x67, 0x34, 0x72, 0x6b, 0x38, 71 | 0x4b, 0x62, 0x34, 0x4d, 0x75, 0x30, 0x55, 0x6c, 0x58, 0x6a, 0x49, 0x42, 72 | 0x30, 0x74, 0x74, 0x6f, 0x76, 0x30, 0x44, 0x69, 0x4e, 0x65, 0x77, 0x4e, 73 | 0x77, 0x49, 0x52, 0x74, 0x31, 0x38, 0x6a, 0x41, 0x38, 0x2b, 0x6f, 0x2b, 74 | 0x75, 0x33, 0x64, 0x70, 0x6a, 0x71, 0x2b, 0x73, 0x57, 0x0d, 0x0a, 0x54, 75 | 0x38, 0x4b, 0x4f, 0x45, 0x55, 0x74, 0x2b, 0x7a, 0x77, 0x76, 0x6f, 0x2f, 76 | 0x37, 0x56, 0x33, 0x4c, 0x76, 0x53, 0x79, 0x65, 0x30, 0x72, 0x67, 0x54, 77 | 0x42, 0x49, 0x6c, 0x44, 0x48, 0x43, 0x4e, 0x41, 0x79, 0x6d, 0x67, 0x34, 78 | 0x56, 0x4d, 0x6b, 0x37, 0x42, 0x50, 0x5a, 0x37, 0x68, 0x6d, 0x2f, 0x45, 79 | 0x4c, 0x4e, 0x4b, 0x6a, 0x44, 0x2b, 0x4a, 0x6f, 0x32, 0x46, 0x52, 0x33, 80 | 0x71, 0x79, 0x48, 0x0d, 0x0a, 0x42, 0x35, 0x54, 0x30, 0x59, 0x33, 0x48, 81 | 0x73, 0x4c, 0x75, 0x4a, 0x76, 0x57, 0x35, 0x69, 0x42, 0x34, 0x59, 0x6c, 82 | 0x63, 0x4e, 0x48, 0x6c, 0x73, 0x64, 0x75, 0x38, 0x37, 0x6b, 0x47, 0x4a, 83 | 0x35, 0x35, 0x74, 0x75, 0x6b, 0x6d, 0x69, 0x38, 0x6d, 0x78, 0x64, 0x41, 84 | 0x51, 0x34, 0x51, 0x37, 0x65, 0x32, 0x52, 0x43, 0x4f, 0x46, 0x76, 0x75, 85 | 0x33, 0x39, 0x36, 0x6a, 0x33, 0x78, 0x2b, 0x55, 0x43, 0x0d, 0x0a, 0x42, 86 | 0x35, 0x69, 0x50, 0x4e, 0x67, 0x69, 0x56, 0x35, 0x2b, 0x49, 0x33, 0x6c, 87 | 0x67, 0x30, 0x32, 0x64, 0x5a, 0x37, 0x37, 0x44, 0x6e, 0x4b, 0x78, 0x48, 88 | 0x5a, 0x75, 0x38, 0x41, 0x2f, 0x6c, 0x4a, 0x42, 0x64, 0x69, 0x42, 0x33, 89 | 0x51, 0x57, 0x30, 0x4b, 0x74, 0x5a, 0x42, 0x36, 0x61, 0x77, 0x42, 0x64, 90 | 0x70, 0x55, 0x4b, 0x44, 0x39, 0x6a, 0x66, 0x31, 0x62, 0x30, 0x53, 0x48, 91 | 0x7a, 0x55, 0x76, 0x0d, 0x0a, 0x4b, 0x42, 0x64, 0x73, 0x30, 0x70, 0x6a, 92 | 0x42, 0x71, 0x41, 0x6c, 0x6b, 0x64, 0x32, 0x35, 0x48, 0x4e, 0x37, 0x72, 93 | 0x4f, 0x72, 0x46, 0x6c, 0x65, 0x61, 0x4a, 0x31, 0x2f, 0x63, 0x74, 0x61, 94 | 0x4a, 0x78, 0x51, 0x5a, 0x42, 0x4b, 0x54, 0x35, 0x5a, 0x50, 0x74, 0x30, 95 | 0x6d, 0x39, 0x53, 0x54, 0x4a, 0x45, 0x61, 0x64, 0x61, 0x6f, 0x30, 0x78, 96 | 0x41, 0x48, 0x30, 0x61, 0x68, 0x6d, 0x62, 0x57, 0x6e, 0x0d, 0x0a, 0x4f, 97 | 0x6c, 0x46, 0x75, 0x68, 0x6a, 0x75, 0x65, 0x66, 0x58, 0x4b, 0x6e, 0x45, 98 | 0x67, 0x56, 0x34, 0x57, 0x65, 0x30, 0x2b, 0x55, 0x58, 0x67, 0x56, 0x43, 99 | 0x77, 0x4f, 0x50, 0x6a, 0x64, 0x41, 0x76, 0x42, 0x62, 0x49, 0x2b, 0x65, 100 | 0x30, 0x6f, 0x63, 0x53, 0x33, 0x4d, 0x46, 0x45, 0x76, 0x7a, 0x47, 0x36, 101 | 0x75, 0x42, 0x51, 0x45, 0x33, 0x78, 0x44, 0x6b, 0x33, 0x53, 0x7a, 0x79, 102 | 0x6e, 0x54, 0x6e, 0x0d, 0x0a, 0x6a, 0x68, 0x38, 0x42, 0x43, 0x4e, 0x41, 103 | 0x77, 0x31, 0x46, 0x74, 0x78, 0x4e, 0x72, 0x51, 0x48, 0x75, 0x73, 0x45, 104 | 0x77, 0x4d, 0x46, 0x78, 0x49, 0x74, 0x34, 0x49, 0x37, 0x6d, 0x4b, 0x5a, 105 | 0x39, 0x59, 0x49, 0x71, 0x69, 0x6f, 0x79, 0x6d, 0x43, 0x7a, 0x4c, 0x71, 106 | 0x39, 0x67, 0x77, 0x51, 0x62, 0x6f, 0x6f, 0x4d, 0x44, 0x51, 0x61, 0x48, 107 | 0x57, 0x42, 0x66, 0x45, 0x62, 0x77, 0x72, 0x62, 0x77, 0x0d, 0x0a, 0x71, 108 | 0x48, 0x79, 0x47, 0x4f, 0x30, 0x61, 0x6f, 0x53, 0x43, 0x71, 0x49, 0x33, 109 | 0x48, 0x61, 0x61, 0x64, 0x72, 0x38, 0x66, 0x61, 0x71, 0x55, 0x39, 0x47, 110 | 0x59, 0x2f, 0x72, 0x4f, 0x50, 0x4e, 0x6b, 0x33, 0x73, 0x67, 0x72, 0x44, 111 | 0x51, 0x6f, 0x6f, 0x2f, 0x2f, 0x66, 0x62, 0x34, 0x68, 0x56, 0x43, 0x31, 112 | 0x43, 0x4c, 0x51, 0x4a, 0x31, 0x33, 0x68, 0x65, 0x66, 0x34, 0x59, 0x35, 113 | 0x33, 0x43, 0x49, 0x0d, 0x0a, 0x72, 0x55, 0x37, 0x6d, 0x32, 0x59, 0x73, 114 | 0x36, 0x78, 0x74, 0x30, 0x6e, 0x55, 0x57, 0x37, 0x2f, 0x76, 0x47, 0x54, 115 | 0x31, 0x4d, 0x30, 0x4e, 0x50, 0x41, 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, 116 | 0x6a, 0x51, 0x6a, 0x42, 0x41, 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, 117 | 0x64, 0x44, 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, 0x45, 0x41, 0x77, 0x49, 118 | 0x42, 0x42, 0x6a, 0x41, 0x50, 0x42, 0x67, 0x4e, 0x56, 0x0d, 0x0a, 0x48, 119 | 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x54, 0x41, 0x44, 0x41, 120 | 0x51, 0x48, 0x2f, 0x4d, 0x42, 0x30, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 121 | 0x67, 0x51, 0x57, 0x42, 0x42, 0x52, 0x35, 0x74, 0x46, 0x6e, 0x6d, 0x65, 122 | 0x37, 0x62, 0x6c, 0x35, 0x41, 0x46, 0x7a, 0x67, 0x41, 0x69, 0x49, 0x79, 123 | 0x42, 0x70, 0x59, 0x39, 0x75, 0x6d, 0x62, 0x62, 0x6a, 0x41, 0x4e, 0x42, 124 | 0x67, 0x6b, 0x71, 0x0d, 0x0a, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 125 | 0x42, 0x41, 0x51, 0x73, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x67, 0x45, 126 | 0x41, 0x56, 0x52, 0x39, 0x59, 0x71, 0x62, 0x79, 0x79, 0x71, 0x46, 0x44, 127 | 0x51, 0x44, 0x4c, 0x48, 0x59, 0x47, 0x6d, 0x6b, 0x67, 0x4a, 0x79, 0x6b, 128 | 0x49, 0x72, 0x47, 0x46, 0x31, 0x58, 0x49, 0x70, 0x75, 0x2b, 0x49, 0x4c, 129 | 0x6c, 0x61, 0x53, 0x2f, 0x56, 0x39, 0x6c, 0x5a, 0x4c, 0x0d, 0x0a, 0x75, 130 | 0x62, 0x68, 0x7a, 0x45, 0x46, 0x6e, 0x54, 0x49, 0x5a, 0x64, 0x2b, 0x35, 131 | 0x30, 0x78, 0x78, 0x2b, 0x37, 0x4c, 0x53, 0x59, 0x4b, 0x30, 0x35, 0x71, 132 | 0x41, 0x76, 0x71, 0x46, 0x79, 0x46, 0x57, 0x68, 0x66, 0x46, 0x51, 0x44, 133 | 0x6c, 0x6e, 0x72, 0x7a, 0x75, 0x42, 0x5a, 0x36, 0x62, 0x72, 0x4a, 0x46, 134 | 0x65, 0x2b, 0x47, 0x6e, 0x59, 0x2b, 0x45, 0x67, 0x50, 0x62, 0x6b, 0x36, 135 | 0x5a, 0x47, 0x51, 0x0d, 0x0a, 0x33, 0x42, 0x65, 0x62, 0x59, 0x68, 0x74, 136 | 0x46, 0x38, 0x47, 0x61, 0x56, 0x30, 0x6e, 0x78, 0x76, 0x77, 0x75, 0x6f, 137 | 0x37, 0x37, 0x78, 0x2f, 0x50, 0x79, 0x39, 0x61, 0x75, 0x4a, 0x2f, 0x47, 138 | 0x70, 0x73, 0x4d, 0x69, 0x75, 0x2f, 0x58, 0x31, 0x2b, 0x6d, 0x76, 0x6f, 139 | 0x69, 0x42, 0x4f, 0x76, 0x2f, 0x32, 0x58, 0x2f, 0x71, 0x6b, 0x53, 0x73, 140 | 0x69, 0x73, 0x52, 0x63, 0x4f, 0x6a, 0x2f, 0x4b, 0x4b, 0x0d, 0x0a, 0x4e, 141 | 0x46, 0x74, 0x59, 0x32, 0x50, 0x77, 0x42, 0x79, 0x56, 0x53, 0x35, 0x75, 142 | 0x43, 0x62, 0x4d, 0x69, 0x6f, 0x67, 0x7a, 0x69, 0x55, 0x77, 0x74, 0x68, 143 | 0x44, 0x79, 0x43, 0x33, 0x2b, 0x36, 0x57, 0x56, 0x77, 0x57, 0x36, 0x4c, 144 | 0x4c, 0x76, 0x33, 0x78, 0x4c, 0x66, 0x48, 0x54, 0x6a, 0x75, 0x43, 0x76, 145 | 0x6a, 0x48, 0x49, 0x49, 0x6e, 0x4e, 0x7a, 0x6b, 0x74, 0x48, 0x43, 0x67, 146 | 0x4b, 0x51, 0x35, 0x0d, 0x0a, 0x4f, 0x52, 0x41, 0x7a, 0x49, 0x34, 0x4a, 147 | 0x4d, 0x50, 0x4a, 0x2b, 0x47, 0x73, 0x6c, 0x57, 0x59, 0x48, 0x62, 0x34, 148 | 0x70, 0x68, 0x6f, 0x77, 0x69, 0x6d, 0x35, 0x37, 0x69, 0x61, 0x7a, 0x74, 149 | 0x58, 0x4f, 0x6f, 0x4a, 0x77, 0x54, 0x64, 0x77, 0x4a, 0x78, 0x34, 0x6e, 150 | 0x4c, 0x43, 0x67, 0x64, 0x4e, 0x62, 0x4f, 0x68, 0x64, 0x6a, 0x73, 0x6e, 151 | 0x76, 0x7a, 0x71, 0x76, 0x48, 0x75, 0x37, 0x55, 0x72, 0x0d, 0x0a, 0x54, 152 | 0x6b, 0x58, 0x57, 0x53, 0x74, 0x41, 0x6d, 0x7a, 0x4f, 0x56, 0x79, 0x79, 153 | 0x67, 0x68, 0x71, 0x70, 0x5a, 0x58, 0x6a, 0x46, 0x61, 0x48, 0x33, 0x70, 154 | 0x4f, 0x33, 0x4a, 0x4c, 0x46, 0x2b, 0x6c, 0x2b, 0x2f, 0x2b, 0x73, 0x4b, 155 | 0x41, 0x49, 0x75, 0x76, 0x74, 0x64, 0x37, 0x75, 0x2b, 0x4e, 0x78, 0x65, 156 | 0x35, 0x41, 0x57, 0x30, 0x77, 0x64, 0x65, 0x52, 0x6c, 0x4e, 0x38, 0x4e, 157 | 0x77, 0x64, 0x43, 0x0d, 0x0a, 0x6a, 0x4e, 0x50, 0x45, 0x6c, 0x70, 0x7a, 158 | 0x56, 0x6d, 0x62, 0x55, 0x71, 0x34, 0x4a, 0x55, 0x61, 0x67, 0x45, 0x69, 159 | 0x75, 0x54, 0x44, 0x6b, 0x48, 0x7a, 0x73, 0x78, 0x48, 0x70, 0x46, 0x4b, 160 | 0x56, 0x4b, 0x37, 0x71, 0x34, 0x2b, 0x36, 0x33, 0x53, 0x4d, 0x31, 0x4e, 161 | 0x39, 0x35, 0x52, 0x31, 0x4e, 0x62, 0x64, 0x57, 0x68, 0x73, 0x63, 0x64, 162 | 0x43, 0x62, 0x2b, 0x5a, 0x41, 0x4a, 0x7a, 0x56, 0x63, 0x0d, 0x0a, 0x6f, 163 | 0x79, 0x69, 0x33, 0x42, 0x34, 0x33, 0x6e, 0x6a, 0x54, 0x4f, 0x51, 0x35, 164 | 0x79, 0x4f, 0x66, 0x2b, 0x31, 0x43, 0x63, 0x65, 0x57, 0x78, 0x47, 0x31, 165 | 0x62, 0x51, 0x56, 0x73, 0x35, 0x5a, 0x75, 0x66, 0x70, 0x73, 0x4d, 0x6c, 166 | 0x6a, 0x71, 0x34, 0x55, 0x69, 0x30, 0x2f, 0x31, 0x6c, 0x76, 0x68, 0x2b, 167 | 0x77, 0x6a, 0x43, 0x68, 0x50, 0x34, 0x6b, 0x71, 0x4b, 0x4f, 0x4a, 0x32, 168 | 0x71, 0x78, 0x71, 0x0d, 0x0a, 0x34, 0x52, 0x67, 0x71, 0x73, 0x61, 0x68, 169 | 0x44, 0x59, 0x56, 0x76, 0x54, 0x48, 0x39, 0x77, 0x37, 0x6a, 0x58, 0x62, 170 | 0x79, 0x4c, 0x65, 0x69, 0x4e, 0x64, 0x64, 0x38, 0x58, 0x4d, 0x32, 0x77, 171 | 0x39, 0x55, 0x2f, 0x74, 0x37, 0x79, 0x30, 0x46, 0x66, 0x2f, 0x39, 0x79, 172 | 0x69, 0x30, 0x47, 0x45, 0x34, 0x34, 0x5a, 0x61, 0x34, 0x72, 0x46, 0x32, 173 | 0x4c, 0x4e, 0x39, 0x64, 0x31, 0x31, 0x54, 0x50, 0x41, 0x0d, 0x0a, 0x6d, 174 | 0x52, 0x47, 0x75, 0x6e, 0x55, 0x48, 0x42, 0x63, 0x6e, 0x57, 0x45, 0x76, 175 | 0x67, 0x4a, 0x42, 0x51, 0x6c, 0x39, 0x6e, 0x4a, 0x45, 0x69, 0x55, 0x30, 176 | 0x5a, 0x73, 0x6e, 0x76, 0x67, 0x63, 0x2f, 0x75, 0x62, 0x68, 0x50, 0x67, 177 | 0x58, 0x52, 0x52, 0x34, 0x58, 0x71, 0x33, 0x37, 0x5a, 0x30, 0x6a, 0x34, 178 | 0x72, 0x37, 0x67, 0x31, 0x53, 0x67, 0x45, 0x45, 0x7a, 0x77, 0x78, 0x41, 179 | 0x35, 0x37, 0x64, 0x0d, 0x0a, 0x65, 0x6d, 0x79, 0x50, 0x78, 0x67, 0x63, 180 | 0x59, 0x78, 0x6e, 0x2f, 0x65, 0x52, 0x34, 0x34, 0x2f, 0x4b, 0x4a, 0x34, 181 | 0x45, 0x42, 0x73, 0x2b, 0x6c, 0x56, 0x44, 0x52, 0x33, 0x76, 0x65, 0x79, 182 | 0x4a, 0x6d, 0x2b, 0x6b, 0x58, 0x51, 0x39, 0x39, 0x62, 0x32, 0x31, 0x2f, 183 | 0x2b, 0x6a, 0x68, 0x35, 0x58, 0x6f, 0x73, 0x31, 0x41, 0x6e, 0x58, 0x35, 184 | 0x69, 0x49, 0x74, 0x72, 0x65, 0x47, 0x43, 0x63, 0x3d, 0x0d, 0x0a, 0x2d, 185 | 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 186 | 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 187 | 0x0d, 0x0a 188 | }; 189 | unsigned int __lets_encrypt_rootca_pem_len = 1970; 190 | -------------------------------------------------------------------------------- /include/menuSystem.h: -------------------------------------------------------------------------------- 1 | #ifndef MENUSYSTEM_H 2 | #define MENUSYSTEM_H 3 | 4 | #include 5 | #include 6 | #include "types/menuItemsType.h" 7 | #include <3ds.h> 8 | #include 9 | #include 10 | #include 11 | 12 | class MenuSystem 13 | { 14 | 15 | public: 16 | 17 | MenuSystem(); 18 | 19 | void changeMenu(int &selection, menuItems *&oldMenuItems, menuItems &newMenuItems, std::vector &previousMenus, bool dontAddToPreviousMenus = false); 20 | void goToPreviousMenu(int &selection, menuItems *¤tMenuItems, std::vector &previousMenus); 21 | void handleExit(); 22 | 23 | std::string getMenuString(menuItems &items, bool directoryMenu); 24 | C2D_TextBuf* getMenuTextBuf(); 25 | C2D_Text* getMenuText(); 26 | menuItems* getCurrentMenuItems(); 27 | 28 | std::string* getHeader(menuItems* header); 29 | std::string* getFooter(menuItems* footer); 30 | 31 | private: 32 | C2D_TextBuf menuTextBuf; 33 | C2D_Text menuText; 34 | 35 | menuItems* currentMenuItems; 36 | 37 | 38 | std::map headers; 39 | 40 | std::map footers; 41 | 42 | }; 43 | 44 | #endif // MENU_H -------------------------------------------------------------------------------- /include/menus.h: -------------------------------------------------------------------------------- 1 | #ifndef MENUS_H 2 | #define MENUS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "types/menuItemsType.h" 8 | 9 | extern menuItems mainMenuItems; 10 | extern menuItems uploadMenuItems; 11 | extern menuItems downloadMenuItems; 12 | extern menuItems settingMenuItems; 13 | extern menuItems uploadDirectoryMenuItems; 14 | extern menuItems gameIDMenuItems; 15 | extern menuItems saveSelectionMenuItems; 16 | extern menuItems downloadGameMenuItems; 17 | extern menuItems gameIDDirectoryMenuItems; 18 | extern menuItems existingGameIDsMenuItems; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /include/networkSystem.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKSYSTEM_H 2 | #define NETWORKSYSTEM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "json.hpp" 9 | #include <3ds.h> 10 | #include 11 | 12 | class NetworkSystem 13 | { 14 | public: 15 | NetworkSystem(); // 16 | 17 | std::string getTokenFromShorthandToken(std::string shorthandToken); 18 | std::string verifyTokenToSetUserID(std::string fullToken); 19 | 20 | int uploadMultiple(UploadTypeEnum uploadType, nlohmann::json jsonObject); 21 | void checkVersion(std::string currentVersion); 22 | 23 | void cleanExit(); 24 | 25 | responsePair init(std::string serverAddress, std::string token); 26 | menuItems getGamesMenuItems(UploadTypeEnum type); 27 | std::string getBase64StringFromFile(std::string fullFilePath, std::string filename); 28 | bool download(UploadTypeEnum type, std::string gameID, std::filesystem::path gamePath); 29 | bool loggedIn = false; 30 | 31 | private: 32 | std::string serverAddress; 33 | std::string token; 34 | 35 | responsePair sendRequest(std::string address, nlohmann::json *dataToSend = nullptr); // 36 | 37 | void setTokenFromString(std::string token); 38 | }; 39 | 40 | #endif // NETWORKSYSTEM_H -------------------------------------------------------------------------------- /include/secureNetworkRequest.h: -------------------------------------------------------------------------------- 1 | #ifndef SECURE_NETWORK_REQUEST_H 2 | #define SECURE_NETWORK_REQUEST_H 3 | 4 | #include 5 | #include 6 | 7 | using responsePair = std::pair; 8 | 9 | responsePair network_request(std::string *address, std::string *jsonData = nullptr, std::string *downloadPath = nullptr); 10 | bool valid_certificate(); 11 | void network_init(); 12 | void network_exit(); 13 | 14 | #endif // SECURE_NETWORK_REQUEST_H -------------------------------------------------------------------------------- /include/systemCore.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEMCORE_H 2 | #define SYSTEMCORE_H 3 | 4 | #include 5 | #include 6 | #include <3ds.h> 7 | #include 8 | #include "menuSystem.h" 9 | #include "types/menuItemsType.h" 10 | #include "networkSystem.h" 11 | #include "configManager.h" 12 | #include "directoryMenu.h" 13 | 14 | class SystemCore 15 | { 16 | public: 17 | SystemCore(); 18 | 19 | void handleFunction(menuFunctions function, unsigned int key = 0); 20 | void handleInput(); 21 | 22 | std::string openKeyboard(std::string placeholder = "", std::string initialText = ""); 23 | std::string getVersion(); 24 | 25 | void sceneRender(); 26 | void cleanExit(); 27 | void checkServerConnection(); 28 | 29 | bool isHalted(); 30 | 31 | private: 32 | MenuSystem menuSystem; 33 | NetworkSystem networkSystem; 34 | ConfigManager configManager; 35 | DirectoryMenu directoryMenu; 36 | 37 | C2D_TextBuf selectorBuf; 38 | 39 | menuItems* currentMenuItems; 40 | std::vector previousMenus; 41 | int zoom = 0; 42 | bool dontUseVolumeBar = true; 43 | 44 | C3D_RenderTarget *screen; 45 | UploadTypeEnum currentUploadType; 46 | std::string currentGameID; 47 | 48 | bool isServerAccessible = false; 49 | int selection; 50 | float size; 51 | bool halt; 52 | bool shouldKeyboardBeOpen; 53 | }; 54 | 55 | #endif // SYSTEMCORE_H -------------------------------------------------------------------------------- /include/types/menuItemsType.h: -------------------------------------------------------------------------------- 1 | #ifndef MENU_ITEMS_TYPE_H 2 | #define MENU_ITEMS_TYPE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | enum class menuFunctions 9 | { 10 | noAction, 11 | 12 | mainMenuMenuItems, 13 | 14 | gameIDDirectoryMenuItems, 15 | directoryMenuItems, 16 | existingGameIDSavesMenuItems, 17 | existingGameIDExtdataMenuItems, 18 | uploadMenuItems, 19 | downloadMenuItems, 20 | settingsMenuItems, 21 | 22 | saveSelectionMenuItems, // for upload 23 | gameIDSavesMenuItems, 24 | gameIDExtdataMenuItems, 25 | 26 | downloadSavesMenuItems, 27 | downloadExtdataMenuItems, 28 | 29 | changeToPreviousMenu, 30 | 31 | // Settings menu items 32 | inputToken, 33 | checkServerConnection, 34 | resetGameIDFiles, 35 | deleteSavesAfterUpload, 36 | 37 | // Upload and download a game 38 | uploadGame, 39 | downloadGame, 40 | 41 | // Not used currently 42 | openKeyboard, 43 | changeServerAddress, 44 | 45 | 46 | traverseDirectory, 47 | 48 | specialB_goPreviousDirectory, 49 | specialX_exitDirectorySelection, 50 | specialY_enterGameID, 51 | 52 | renameGameID, 53 | specialX_redirectGameID, 54 | specialY_deleteGameID, 55 | specialB_exitExistingGameIDSelection, 56 | }; 57 | 58 | using menuItems = std::vector>; 59 | 60 | #endif // MENU_ITEMS_TYPE_H 61 | -------------------------------------------------------------------------------- /include/types/responsePairType.h: -------------------------------------------------------------------------------- 1 | #ifndef RESPONSE_PAIR_TYPE_H 2 | #define RESPONSE_PAIR_TYPE_H 3 | 4 | #include 5 | #include 6 | 7 | using responsePair = std::pair; 8 | 9 | #endif // RESPONSE_PAIR_TYPE_H -------------------------------------------------------------------------------- /include/types/uploadTypeEnumType.h: -------------------------------------------------------------------------------- 1 | #ifndef UPLOAD_TYPE_ENUM_TYPE_H 2 | #define UPLOAD_TYPE_ENUM_TYPE_H 3 | 4 | enum UploadTypeEnum 5 | { 6 | SAVES, 7 | EXTDATA 8 | }; 9 | 10 | #endif // UPLOAD_TYPE_ENUM_TYPE_H -------------------------------------------------------------------------------- /source/base64.cpp: -------------------------------------------------------------------------------- 1 | #include "base64.h" 2 | #include 3 | 4 | static const std::string base64_chars = 5 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 6 | "abcdefghijklmnopqrstuvwxyz" 7 | "0123456789+/"; 8 | 9 | 10 | static inline bool is_base64(BYTE c) { 11 | return (isalnum(c) || (c == '+') || (c == '/')); 12 | } 13 | 14 | std::string base64_encode(BYTE const* buf, unsigned int bufLen) { 15 | std::string ret; 16 | int i = 0; 17 | int j = 0; 18 | BYTE char_array_3[3]; 19 | BYTE char_array_4[4]; 20 | 21 | while (bufLen--) { 22 | char_array_3[i++] = *(buf++); 23 | if (i == 3) { 24 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 25 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 26 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 27 | char_array_4[3] = char_array_3[2] & 0x3f; 28 | 29 | for(i = 0; (i <4) ; i++) 30 | ret += base64_chars[char_array_4[i]]; 31 | i = 0; 32 | } 33 | } 34 | 35 | if (i) 36 | { 37 | for(j = i; j < 3; j++) 38 | char_array_3[j] = '\0'; 39 | 40 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 41 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 42 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 43 | char_array_4[3] = char_array_3[2] & 0x3f; 44 | 45 | for (j = 0; (j < i + 1); j++) 46 | ret += base64_chars[char_array_4[j]]; 47 | 48 | while((i++ < 3)) 49 | ret += '='; 50 | } 51 | 52 | return ret; 53 | } 54 | 55 | std::vector base64_decode(std::string const& encoded_string) { 56 | int in_len = encoded_string.size(); 57 | int i = 0; 58 | int j = 0; 59 | int in_ = 0; 60 | BYTE char_array_4[4], char_array_3[3]; 61 | std::vector ret; 62 | 63 | while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { 64 | char_array_4[i++] = encoded_string[in_]; in_++; 65 | if (i ==4) { 66 | for (i = 0; i <4; i++) 67 | char_array_4[i] = base64_chars.find(char_array_4[i]); 68 | 69 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 70 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 71 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 72 | 73 | for (i = 0; (i < 3); i++) 74 | ret.push_back(char_array_3[i]); 75 | i = 0; 76 | } 77 | } 78 | 79 | if (i) { 80 | for (j = i; j <4; j++) 81 | char_array_4[j] = 0; 82 | 83 | for (j = 0; j <4; j++) 84 | char_array_4[j] = base64_chars.find(char_array_4[j]); 85 | 86 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 87 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 88 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 89 | 90 | for (j = 0; (j < i - 1); j++) ret.push_back(char_array_3[j]); 91 | } 92 | 93 | return ret; 94 | } 95 | -------------------------------------------------------------------------------- /source/configManager.cpp: -------------------------------------------------------------------------------- 1 | #include "configManager.h" 2 | #include "json.hpp" 3 | #include 4 | #include 5 | #include 6 | #include "types/uploadTypeEnumType.h" 7 | 8 | ConfigManager::ConfigManager() 9 | { 10 | std::string directoryPath = "/3ds/Citrahold"; 11 | 12 | if (!std::filesystem::exists(directoryPath) || !std::filesystem::is_directory(directoryPath)) 13 | { 14 | if (std::filesystem::create_directories(directoryPath)) 15 | { 16 | std::cout << "Directory created successfully." << std::endl; 17 | } 18 | else 19 | { 20 | std::cerr << "Failed to create the directory." << std::endl; 21 | } 22 | } 23 | 24 | std::fstream file("/3ds/Citrahold/config.json", std::ios::in | std::ios::out); 25 | if (!file) 26 | { 27 | file.open("/3ds/Citrahold/config.json", std::ios::out); 28 | 29 | nlohmann::json data = { 30 | {"serverAddress", "https://api.citrahold.com"}, 31 | {"token", ""}, 32 | {"deleteSaveAfterUpload", "false"}, 33 | {"_note", "keep your token private!"} 34 | 35 | }; 36 | 37 | file << data.dump(); 38 | 39 | config = data; 40 | } 41 | else 42 | { 43 | file.seekp(0); 44 | 45 | // check all needed keys are present 46 | 47 | bool updateNeeded = false; 48 | if (!config.contains("serverAddress")) 49 | { 50 | config["serverAddress"] = "https://api.citrahold.com"; 51 | updateNeeded = true; 52 | } 53 | if (!config.contains("token")) 54 | { 55 | config["token"] = ""; 56 | updateNeeded = true; 57 | } 58 | if (!config.contains("deleteSaveAfterUpload")) 59 | { 60 | config["deleteSaveAfterUpload"] = "false"; 61 | updateNeeded = true; 62 | } 63 | 64 | std::string fileContents((std::istreambuf_iterator(file)), std::istreambuf_iterator()); 65 | config = nlohmann::json::parse(fileContents); 66 | 67 | if (updateNeeded) 68 | { 69 | updateConfigFile(config); 70 | } 71 | } 72 | 73 | configDirectory = directoryPath; 74 | 75 | // this may not be correct 76 | gamesDirectory = "/3ds/Checkpoint/"; 77 | 78 | getGameIDFile(UploadTypeEnum::SAVES); 79 | getGameIDFile(UploadTypeEnum::EXTDATA); 80 | } 81 | 82 | std::filesystem::path ConfigManager::getConfigDirectory() const 83 | { 84 | return configDirectory; 85 | } 86 | 87 | std::filesystem::path ConfigManager::getGamesDirectory(UploadTypeEnum type) const 88 | { 89 | return gamesDirectory; 90 | } 91 | 92 | void ConfigManager::updateConfigFile(nlohmann::json newConfig) 93 | { 94 | std::filesystem::path filePath = configDirectory / "config.json"; 95 | std::ofstream file(filePath); 96 | 97 | config = newConfig; 98 | 99 | if (file.is_open()) 100 | { 101 | 102 | std::string jsonString = newConfig.dump(); 103 | file << jsonString; 104 | file.close(); 105 | } 106 | else 107 | { 108 | std::cerr << "Failed to open the file for writing."; 109 | } 110 | } 111 | 112 | void ConfigManager::setToken(std::string token) 113 | { 114 | config["token"] = token; 115 | updateConfigFile(config); 116 | } 117 | 118 | nlohmann::json ConfigManager::getGameIDFile(UploadTypeEnum type) 119 | { 120 | 121 | std::filesystem::path filePath = configDirectory / (type == UploadTypeEnum::SAVES ? "gameIDSaves.json" : "gameIDExtdata.json"); 122 | 123 | std::fstream file(filePath, std::ios::in | std::ios::out); 124 | if (!file) 125 | { 126 | file.open(filePath, std::ios::out); 127 | 128 | nlohmann::json data; 129 | data["gameID"] = nlohmann::json::array(); 130 | nlohmann::json jsonDoc(data); 131 | std::string jsonString = jsonDoc.dump(); 132 | 133 | file << jsonString; 134 | file.close(); 135 | 136 | return jsonDoc; 137 | } 138 | else 139 | { 140 | file.seekp(0); 141 | std::string fileContents((std::istreambuf_iterator(file)), std::istreambuf_iterator()); 142 | file.close(); 143 | return nlohmann::json::parse(fileContents); 144 | } 145 | } 146 | 147 | void ConfigManager::resetBothGameIDFiles() 148 | { 149 | std::filesystem::path filePath = configDirectory / "gameIDSaves.json"; 150 | std::ofstream file(filePath); 151 | if (file.is_open()) 152 | { 153 | nlohmann::json data; 154 | data["gameID"] = nlohmann::json::array(); 155 | nlohmann::json jsonDoc(data); 156 | std::string jsonString = jsonDoc.dump(); 157 | 158 | file << jsonString; 159 | file.close(); 160 | } 161 | else 162 | { 163 | std::cerr << "Failed to open the file for writing."; 164 | } 165 | 166 | filePath = configDirectory / "gameIDExtdata.json"; 167 | file.open(filePath); 168 | if (file.is_open()) 169 | { 170 | nlohmann::json data; 171 | data["gameID"] = nlohmann::json::array(); 172 | nlohmann::json jsonDoc(data); 173 | std::string jsonString = jsonDoc.dump(); 174 | 175 | file << jsonString; 176 | file.close(); 177 | } 178 | else 179 | { 180 | std::cerr << "Failed to open the file for writing."; 181 | } 182 | 183 | getGameIDFile(UploadTypeEnum::SAVES); 184 | getGameIDFile(UploadTypeEnum::EXTDATA); 185 | } 186 | 187 | void ConfigManager::updateGameIDFile(UploadTypeEnum type, nlohmann::json newFile) 188 | { 189 | 190 | std::filesystem::path filePath = configDirectory / (type == UploadTypeEnum::SAVES ? "gameIDSaves.json" : "gameIDExtdata.json"); 191 | 192 | std::ofstream file(filePath); 193 | 194 | if (file.is_open()) 195 | { 196 | 197 | std::string jsonString = newFile.dump(); 198 | file << jsonString; 199 | file.close(); 200 | } 201 | else 202 | { 203 | std::cerr << "Failed to open the file for writing."; 204 | } 205 | } 206 | 207 | std::string ConfigManager::getGamePathFromID(UploadTypeEnum type, std::string gameID) 208 | { 209 | nlohmann::json gameIDFile = getGameIDFile(type); 210 | for (auto &entry : gameIDFile["gameID"]) 211 | { 212 | if (entry[0] == gameID) 213 | { 214 | return entry[1]; 215 | } 216 | } 217 | return ""; 218 | } 219 | 220 | std::string ConfigManager::getGameIDFromPath(UploadTypeEnum type, std::string gamePath) 221 | { 222 | nlohmann::json gameIDFile = getGameIDFile(type); 223 | for (auto &entry : gameIDFile["gameID"]) 224 | { 225 | if (entry[1] == gamePath) 226 | { 227 | return entry[0]; 228 | } 229 | } 230 | return ""; 231 | } 232 | 233 | int ConfigManager::getNumberOfGameIDs(UploadTypeEnum type) 234 | { 235 | nlohmann::json gameIDFile = getGameIDFile(type); 236 | return gameIDFile["gameID"].size(); 237 | } 238 | 239 | void ConfigManager::addGameIDToFile(UploadTypeEnum type, std::string gameID, std::string gamePath) 240 | { 241 | removeGameIDFromFile(type, gameID); 242 | 243 | nlohmann::json oldGameIDFile = getGameIDFile(type); 244 | nlohmann::json newEntry = nlohmann::json::array(); 245 | newEntry.push_back(gameID); 246 | newEntry.push_back(gamePath); 247 | oldGameIDFile["gameID"].push_back(newEntry); 248 | updateGameIDFile(type, oldGameIDFile); 249 | } 250 | 251 | void ConfigManager::removeGameIDFromFile(UploadTypeEnum type, std::string gameID) 252 | { 253 | nlohmann::json oldGameIDFile = getGameIDFile(type); 254 | nlohmann::json newGameIDFile = nlohmann::json::array(); 255 | for (auto &entry : oldGameIDFile["gameID"]) 256 | { 257 | if (entry[0] != gameID) 258 | { 259 | newGameIDFile.push_back(entry); 260 | } 261 | } 262 | oldGameIDFile["gameID"] = newGameIDFile; 263 | updateGameIDFile(type, oldGameIDFile); 264 | } 265 | 266 | void ConfigManager::renameGameIDInFile(UploadTypeEnum type, std::string oldGameID, std::string newGameID) 267 | { 268 | nlohmann::json oldGameIDFile = getGameIDFile(type); 269 | for (auto &entry : oldGameIDFile["gameID"]) 270 | { 271 | if (entry[0] == oldGameID) 272 | { 273 | entry[0] = newGameID; 274 | } 275 | } 276 | updateGameIDFile(type, oldGameIDFile); 277 | } 278 | 279 | void ConfigManager::redirectGameIDInFile(UploadTypeEnum type, std::string gameID, std::string newPath) 280 | { 281 | nlohmann::json oldGameIDFile = getGameIDFile(type); 282 | for (auto &entry : oldGameIDFile["gameID"]) 283 | { 284 | if (entry[0] == gameID) 285 | { 286 | entry[1] = newPath; 287 | } 288 | } 289 | updateGameIDFile(type, oldGameIDFile); 290 | } 291 | 292 | nlohmann::json ConfigManager::getConfig() const 293 | { 294 | return config; 295 | } 296 | 297 | std::string ConfigManager::getToken() const 298 | { 299 | return config["token"]; 300 | } 301 | 302 | bool ConfigManager::getDeleteSaveAfterUpload() const 303 | { 304 | if (!config.contains("deleteSaveAfterUpload")) 305 | { 306 | return false; 307 | } 308 | 309 | return config["deleteSaveAfterUpload"].dump() == "true"; 310 | } 311 | 312 | void ConfigManager::setDeleteSaveAfterUpload(bool deleteSaveAfterUpload) 313 | { 314 | config["deleteSaveAfterUpload"] = deleteSaveAfterUpload; 315 | updateConfigFile(config); 316 | } 317 | 318 | bool ConfigManager::loggedIn() 319 | { 320 | return userID != "invalid"; 321 | } 322 | -------------------------------------------------------------------------------- /source/directoryMenu.cpp: -------------------------------------------------------------------------------- 1 | #include "directoryMenu.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "types/menuItemsType.h" 7 | #include "json.hpp" 8 | 9 | DirectoryMenu::DirectoryMenu() 10 | { 11 | if (!std::filesystem::exists(currentDirectory) || !std::filesystem::is_directory(currentDirectory)) 12 | { 13 | currentDirectory = "/3ds/"; 14 | } 15 | } 16 | 17 | std::filesystem::path DirectoryMenu::getCurrentDirectory() 18 | { 19 | return currentDirectory; 20 | } 21 | 22 | menuItems DirectoryMenu::getCurrentDirectoryMenuItems() 23 | { 24 | 25 | menuItems directoryMenuItems = { 26 | {std::filesystem::path(".. "), menuFunctions::traverseDirectory}, 27 | }; 28 | 29 | for (const auto &dirEntry : std::filesystem::directory_iterator(currentDirectory)) 30 | { 31 | if (std::filesystem::is_directory(dirEntry.path())) 32 | { 33 | std::filesystem::path relativePath = std::filesystem::relative(dirEntry, currentDirectory); 34 | directoryMenuItems.push_back({relativePath, menuFunctions::traverseDirectory}); 35 | } 36 | } 37 | 38 | return directoryMenuItems; 39 | } 40 | 41 | std::filesystem::path DirectoryMenu::getGamePathFromGameID(std::string gameID, nlohmann::json &gameIDJSON) 42 | { 43 | for (auto &gameIDJSONEntry : gameIDJSON["gameID"]) 44 | { 45 | if (gameIDJSONEntry[0] == gameID) 46 | { 47 | return gameIDJSONEntry[1]; 48 | } 49 | } 50 | return ""; 51 | } 52 | 53 | menuItems DirectoryMenu::getGameIDDirectoryMenuItems(nlohmann::json &gameIDJSON, menuFunctions nextAction) 54 | { 55 | 56 | menuItems directoryMenuItems = {}; 57 | 58 | for (auto &gameID : gameIDJSON["gameID"]) 59 | { 60 | directoryMenuItems.push_back({gameID[0], nextAction}); 61 | } 62 | 63 | return directoryMenuItems; 64 | } 65 | 66 | menuItems DirectoryMenu::getSaveSelectionMenuItems(std::filesystem::path gamePath) 67 | { 68 | 69 | 70 | menuItems saveSelectionMenuItems; 71 | 72 | for (const auto &dirEntry : std::filesystem::directory_iterator(gamePath)) 73 | { 74 | if (std::filesystem::is_directory(dirEntry.path())) 75 | { 76 | std::filesystem::path relativePath = std::filesystem::relative(dirEntry, gamePath); 77 | saveSelectionMenuItems.push_back({relativePath, menuFunctions::uploadGame}); 78 | } 79 | } 80 | 81 | return saveSelectionMenuItems; 82 | } 83 | 84 | void DirectoryMenu::setCurrentDirectory(std::filesystem::path newDirectory) 85 | { 86 | currentDirectory = newDirectory; 87 | } 88 | -------------------------------------------------------------------------------- /source/helpers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "helpers.h" 6 | 7 | std::string trim(const std::string &str) 8 | { 9 | size_t first = str.find_first_not_of(' '); 10 | size_t last = str.find_last_not_of(' '); 11 | 12 | if (first == std::string::npos) 13 | { 14 | return ""; 15 | } 16 | 17 | return str.substr(first, last - first + 1); 18 | } 19 | 20 | void safeDirectoryRemove(const std::filesystem::path &path) 21 | { 22 | if (!std::filesystem::exists(path)) 23 | { 24 | return; 25 | } 26 | 27 | try 28 | { 29 | for (const auto &entry : std::filesystem::directory_iterator(path)) 30 | { 31 | if (std::filesystem::is_regular_file(entry.path())) 32 | { 33 | std::filesystem::remove(entry.path()); 34 | } 35 | else if (std::filesystem::is_directory(entry.path())) 36 | { 37 | safeDirectoryRemove(entry.path()); 38 | } 39 | } 40 | 41 | for (const auto &entry : std::filesystem::directory_iterator(path)) 42 | { 43 | if (std::filesystem::is_regular_file(entry.path())) 44 | { 45 | std::filesystem::remove(entry.path()); 46 | } 47 | } 48 | 49 | std::filesystem::remove(path); 50 | } 51 | catch (const std::exception &e) 52 | { 53 | std::cerr << "Error: " << e.what() << std::endl; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include <3ds.h> 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "types/menuItemsType.h" 12 | #include "menuSystem.h" 13 | #include "systemCore.h" 14 | 15 | // #include // for mallinfo() 16 | 17 | int main() 18 | { 19 | SystemCore systemCore; 20 | 21 | while (aptMainLoop()) 22 | { 23 | 24 | systemCore.handleInput(); 25 | systemCore.sceneRender(); 26 | 27 | // std::cout << std::endl 28 | // << " " << mallinfo().uordblks << " " << mallinfo().fordblks << " " << std::endl; 29 | 30 | if (systemCore.isHalted()) 31 | break; 32 | } 33 | 34 | systemCore.cleanExit(); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /source/menuSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "menuSystem.h" 2 | #include 3 | #include 4 | #include 5 | #include "types/menuItemsType.h" 6 | #include <3ds.h> 7 | #include 8 | #include "menus.h" 9 | 10 | MenuSystem::MenuSystem() 11 | { 12 | this->menuTextBuf = C2D_TextBufNew(4096); 13 | C2D_TextParse(&this->menuText, this->menuTextBuf, ""); 14 | 15 | headers = { 16 | {&gameIDMenuItems, "Select a game ID"}, 17 | {&saveSelectionMenuItems, "Select a save"}, 18 | {&downloadGameMenuItems, "Select a game ID"}, 19 | {&gameIDDirectoryMenuItems, "Select a game ID"}, 20 | // {&uploadDirectoryMenuItems, "Select a directory"}, 21 | {&mainMenuItems, "Main Menu"}, 22 | {&uploadMenuItems, "Upload Menu"}, 23 | {&downloadMenuItems, "Download Menu"}, 24 | {&settingMenuItems, "Settings Menu"}, 25 | }; 26 | 27 | //  ABXY 28 | footers = { 29 | // {&uploadDirectoryMenuItems, " Open Directory  Prev Directory\n Confirm Current  Cancel"}, 30 | {&existingGameIDsMenuItems, " Rename ID  Change Directory\n Delete ID  Cancel"}}; 31 | } 32 | 33 | C2D_TextBuf *MenuSystem::getMenuTextBuf() 34 | { 35 | return &this->menuTextBuf; 36 | } 37 | 38 | C2D_Text *MenuSystem::getMenuText() 39 | { 40 | return &this->menuText; 41 | } 42 | 43 | std::string MenuSystem::getMenuString(menuItems &items, bool normalMenu) 44 | { 45 | 46 | std::string str = ""; 47 | 48 | if (normalMenu) { 49 | for (auto &item : items) 50 | str += std::get<0>(item) + "\n"; 51 | } else { 52 | str = std::to_string(items.size() - 1) + " directories here"; 53 | } 54 | 55 | 56 | 57 | return str; 58 | } 59 | 60 | menuItems *MenuSystem::getCurrentMenuItems() 61 | { 62 | return currentMenuItems; 63 | } 64 | 65 | std::string *MenuSystem::getFooter(menuItems *footer) 66 | { 67 | return &footers[footer]; 68 | } 69 | 70 | std::string *MenuSystem::getHeader(menuItems *header) 71 | { 72 | return &headers[header]; 73 | } 74 | 75 | void MenuSystem::handleExit() 76 | { 77 | // Delete the text buffers 78 | if (this->menuTextBuf != nullptr) 79 | { 80 | C2D_TextBufDelete(this->menuTextBuf); 81 | this->menuTextBuf = nullptr; 82 | } 83 | } 84 | 85 | void MenuSystem::changeMenu(int &selection, menuItems *&oldMenuItems, menuItems &newMenuItems, std::vector &previousMenus, bool dontAddToPreviousMenus) 86 | { 87 | 88 | // This is used to do specific things depending on the menu 89 | currentMenuItems = &newMenuItems; 90 | 91 | if (!dontAddToPreviousMenus && &newMenuItems != &uploadDirectoryMenuItems && &newMenuItems != &existingGameIDsMenuItems) 92 | { 93 | previousMenus.push_back(&newMenuItems); 94 | } 95 | 96 | oldMenuItems = &newMenuItems; 97 | C2D_TextBufClear(this->menuTextBuf); 98 | 99 | std::string headerSpaceString = ""; 100 | if (getHeader(&newMenuItems) != nullptr) 101 | { 102 | headerSpaceString += "\n"; 103 | } 104 | 105 | C2D_TextParse(&this->menuText, this->menuTextBuf, (headerSpaceString + getMenuString(newMenuItems, &newMenuItems != &uploadDirectoryMenuItems)).c_str()); 106 | C2D_TextOptimize(&this->menuText); 107 | 108 | selection = 0; 109 | } 110 | 111 | void MenuSystem::goToPreviousMenu(int &selection, menuItems *¤tMenuItems, std::vector &previousMenus) 112 | { 113 | if (!previousMenus.empty() && previousMenus.size() >= 2) 114 | { 115 | menuItems *secondLastItem = previousMenus[previousMenus.size() - 2]; 116 | 117 | // previousMenus.pop_back(); 118 | 119 | // previousMenus.pop_back(); 120 | 121 | if (previousMenus.size() >= 2) 122 | { 123 | previousMenus.pop_back(); 124 | } 125 | 126 | if (previousMenus.size() >= 2) 127 | { 128 | previousMenus.pop_back(); 129 | } 130 | 131 | if (secondLastItem) 132 | { 133 | changeMenu(selection, currentMenuItems, *secondLastItem, previousMenus); 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /source/menus.cpp: -------------------------------------------------------------------------------- 1 | #include "menus.h" 2 | 3 | // Define the menu items in this source file 4 | menuItems mainMenuItems = { 5 | {"Game IDs", menuFunctions::gameIDDirectoryMenuItems}, 6 | {"Upload", menuFunctions::uploadMenuItems}, 7 | {"Download", menuFunctions::downloadMenuItems}, 8 | {"Settings", menuFunctions::settingsMenuItems}}; 9 | 10 | menuItems gameIDDirectoryMenuItems = { 11 | {"New Game ID", menuFunctions::directoryMenuItems}, 12 | {"Current saves Game IDs", menuFunctions::existingGameIDSavesMenuItems}, 13 | {"Current extdata Game IDs", menuFunctions::existingGameIDExtdataMenuItems}, 14 | {"Back ", menuFunctions::changeToPreviousMenu} 15 | }; 16 | 17 | menuItems existingGameIDsMenuItems = { 18 | {"loading", menuFunctions::noAction}, 19 | {"Cancel ", menuFunctions::changeToPreviousMenu} 20 | }; 21 | 22 | menuItems uploadMenuItems = { 23 | {"Upload Game saves", menuFunctions::gameIDSavesMenuItems}, 24 | {"Upload Game extdata", menuFunctions::gameIDExtdataMenuItems}, 25 | {"Back ", menuFunctions::changeToPreviousMenu}}; 26 | 27 | menuItems downloadMenuItems = { 28 | {"Download Game saves", menuFunctions::downloadSavesMenuItems}, 29 | {"Download Game extdata", menuFunctions::downloadExtdataMenuItems}, 30 | {"Back ", menuFunctions::changeToPreviousMenu}}; 31 | 32 | menuItems settingMenuItems = { 33 | {"Input a shorthand token", menuFunctions::inputToken}, 34 | {"Check server connection", menuFunctions::checkServerConnection}, 35 | {"Reset game ID files", menuFunctions::resetGameIDFiles}, 36 | {"Delete saves after upload?", menuFunctions::deleteSavesAfterUpload}, 37 | {"Back ", menuFunctions::changeToPreviousMenu}}; 38 | 39 | menuItems uploadDirectoryMenuItems = { 40 | {"loading", menuFunctions::noAction}, 41 | {"Cancel ", menuFunctions::changeToPreviousMenu}}; 42 | 43 | menuItems gameIDMenuItems = { 44 | {"loading", menuFunctions::noAction}, 45 | {"Cancel ", menuFunctions::changeToPreviousMenu}}; 46 | 47 | menuItems saveSelectionMenuItems = { 48 | {"loading", menuFunctions::noAction}, 49 | {"Cancel ", menuFunctions::changeToPreviousMenu} 50 | }; 51 | 52 | menuItems downloadGameMenuItems = { 53 | {"loading", menuFunctions::noAction}, 54 | {"Cancel ", menuFunctions::changeToPreviousMenu} 55 | }; -------------------------------------------------------------------------------- /source/networkSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "networkSystem.h" 2 | #include "json.hpp" 3 | #include <3ds.h> 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "json.hpp" 12 | #include "base64.h" 13 | #include "secureNetworkRequest.h" 14 | #include "helpers.h" 15 | 16 | NetworkSystem::NetworkSystem() 17 | { 18 | 19 | network_init(); 20 | 21 | } 22 | 23 | responsePair NetworkSystem::init(std::string serverAddress, std::string token) 24 | { 25 | this->serverAddress = serverAddress; 26 | 27 | if (!valid_certificate()) 28 | { 29 | printf("\nWARNING: The TLS certificate is going to be/is expired (soon). Please update your build!\nWe will not verify the certificate, this could be insecure!\n"); 30 | } 31 | 32 | return sendRequest(this->serverAddress + "/areyouawake"); 33 | } 34 | 35 | std::string NetworkSystem::getBase64StringFromFile(std::string fullFilePath, std::string filename) 36 | { 37 | 38 | std::ifstream file(fullFilePath, std::ios::binary); 39 | if (!file) 40 | { 41 | std::cout << "Failed to open file (" + fullFilePath + ")\n"; 42 | } 43 | else 44 | { 45 | file.seekg(0, std::ios::end); 46 | size_t size = file.tellg(); 47 | file.seekg(0); 48 | 49 | std::vector buffer(size); 50 | file.read((char *)buffer.data(), size); 51 | 52 | file.close(); 53 | 54 | return base64_encode(&buffer[0], buffer.size()); 55 | } 56 | return ""; 57 | } 58 | 59 | void NetworkSystem::checkVersion(std::string currentVersion) 60 | { 61 | std::cout << "\nChecking for updates..."; 62 | 63 | responsePair response = sendRequest(this->serverAddress + ("/softwareVersion")); 64 | nlohmann::json responseJSON = nlohmann::json::parse(response.second); 65 | nlohmann::json::array_t responseArray = responseJSON["3ds"]; 66 | 67 | bool found = false; 68 | for (const auto &element : responseArray) 69 | { 70 | if (element == currentVersion) 71 | { 72 | found = true; 73 | break; 74 | } 75 | } 76 | 77 | std::cout << "\r \r"; 78 | if (found) { 79 | std::cout << "You are running the latest version!\n"; 80 | } else { 81 | std::cout << "There is an update available (" 82 | << responseJSON["3ds"][0].dump() << ")\n"; 83 | std::cout << "Please visit the website for help.\n"; 84 | } 85 | 86 | if (responseJSON.contains("motd3ds")) { 87 | for (const auto &element : responseJSON["motd3ds"]) { 88 | std::cout << element.get() << "\n"; 89 | } 90 | } 91 | 92 | if (responseJSON.contains("motd")) { 93 | for (const auto &element : responseJSON["motd"]) { 94 | std::cout << element.get() << "\n"; 95 | } 96 | } 97 | } 98 | 99 | bool NetworkSystem::download(UploadTypeEnum type, std::string gameID, std::filesystem::path gamePath) 100 | { 101 | if (!loggedIn) 102 | { 103 | std::cout << "Not logged in\n"; 104 | return 0; 105 | } 106 | 107 | nlohmann::json data; 108 | data["token"] = this->token; 109 | data["game"] = gameID; 110 | 111 | safeDirectoryRemove(gamePath / "Citrahold-Download"); 112 | 113 | responsePair response = sendRequest(this->serverAddress + (type == UploadTypeEnum::EXTDATA ? "/downloadMultiExtdata" : "/downloadMultiSaves"), &data); 114 | if (response.first == 200) 115 | { 116 | bool successfulSoFar = true; 117 | 118 | nlohmann::json responseJSON = nlohmann::json::parse(response.second); 119 | int numberOfItems = responseJSON["files"].size(); 120 | int itemNumber = 0; 121 | 122 | std::cout << "Retrieved " << numberOfItems << " files\n"; 123 | 124 | for (const auto &element : responseJSON["files"].items()) 125 | { 126 | if (!successfulSoFar) 127 | { 128 | break; 129 | } 130 | 131 | // element [0] = filename 132 | // element [1] = base64 data 133 | itemNumber++; 134 | std::string filename = element.value()[0]; 135 | std::string base64Data = element.value()[1]; 136 | 137 | std::filesystem::path downloadPath = (gamePath / "Citrahold-Download" / filename).string(); 138 | std::vector base64DataDecoded = base64_decode(base64Data); 139 | 140 | // write the file 141 | std::filesystem::path parentPath = downloadPath.parent_path(); 142 | 143 | if (!std::filesystem::exists(parentPath)) 144 | { 145 | std::filesystem::create_directories(parentPath); 146 | } 147 | 148 | if (filename.find("citraholdDirectoryDummy") == std::string::npos) 149 | { 150 | 151 | std::ofstream file(downloadPath, std::ios::binary); 152 | 153 | if (!file) 154 | { 155 | std::cout << "[" << itemNumber << "/" << numberOfItems << "] " 156 | << "Failed to open file (" << downloadPath << ")\n"; 157 | successfulSoFar = false; 158 | } 159 | else 160 | { 161 | // check if filename contains "citraholdDirectoryDummy" 162 | 163 | std::cout << "[" << itemNumber << "/" << numberOfItems << "] " 164 | << "Writing " << filename << "\n"; 165 | file.write((char *)base64DataDecoded.data(), base64DataDecoded.size()); 166 | file.close(); 167 | } 168 | } 169 | else 170 | { 171 | std::cout << "[" << itemNumber << "/" << numberOfItems << "] " 172 | << "Ignoring dummy file " << filename << "\n"; 173 | } 174 | } 175 | 176 | return successfulSoFar; 177 | } 178 | return false; 179 | } 180 | 181 | menuItems NetworkSystem::getGamesMenuItems(UploadTypeEnum type) 182 | { 183 | 184 | nlohmann::json data; 185 | data["token"] = this->token; 186 | 187 | responsePair response = sendRequest(this->serverAddress + (type == UploadTypeEnum::EXTDATA ? "/getExtdata" : "/getSaves"), &data); 188 | if (response.first == 200) 189 | { 190 | 191 | nlohmann::json responseJSON = nlohmann::json::parse(response.second); 192 | menuItems gamesMenuItems = {}; 193 | for (const auto &element : responseJSON["games"].items()) 194 | { 195 | gamesMenuItems.push_back( 196 | {element.value(), menuFunctions::downloadGame}); 197 | } 198 | 199 | return gamesMenuItems; 200 | } 201 | 202 | return {}; 203 | } 204 | 205 | std::string NetworkSystem::getTokenFromShorthandToken(std::string shorthandToken) 206 | { 207 | nlohmann::json data; 208 | 209 | data["shorthandToken"] = shorthandToken; 210 | 211 | // this will return a full token 212 | responsePair responseAsString = sendRequest(this->serverAddress + "/getToken", &data); 213 | if (responseAsString.first == 200) 214 | { 215 | nlohmann::json response = nlohmann::json::parse(responseAsString.second); 216 | this->token = response["token"]; 217 | return response["token"]; 218 | } 219 | else 220 | { 221 | std::cout << "Failed to get token from shorthand token\n"; 222 | return "invalid"; 223 | } 224 | } 225 | 226 | std::string NetworkSystem::verifyTokenToSetUserID(std::string fullToken) 227 | { 228 | 229 | nlohmann::json data; 230 | 231 | data["token"] = fullToken; 232 | 233 | responsePair response = sendRequest(this->serverAddress + "/getUserID", &data); 234 | if (response.first == 200) 235 | { 236 | this->token = fullToken; 237 | return nlohmann::json::parse(response.second)["userID"]; 238 | } 239 | else 240 | { 241 | std::cout << "Failed to verify token\n"; 242 | return "invalid"; 243 | } 244 | } 245 | 246 | int NetworkSystem::uploadMultiple(UploadTypeEnum uploadType, nlohmann::json jsonObject) 247 | { 248 | if (!loggedIn) 249 | { 250 | std::cout << "Not logged in\n"; 251 | return 0; 252 | } 253 | 254 | responsePair response = sendRequest(this->serverAddress + (uploadType == UploadTypeEnum::SAVES ? "/uploadMultiSaves" : "/uploadMultiExtdata"), &jsonObject); 255 | return response.first; 256 | } 257 | 258 | void NetworkSystem::setTokenFromString(std::string token) 259 | { 260 | this->token = token; 261 | } 262 | 263 | responsePair NetworkSystem::sendRequest(std::string address, nlohmann::json *dataToSend) 264 | { 265 | 266 | responsePair response; 267 | 268 | std::string data = ""; 269 | if (dataToSend != nullptr) 270 | { 271 | data = dataToSend->dump(); 272 | } 273 | 274 | return network_request(&address, &data); 275 | } 276 | 277 | void NetworkSystem::cleanExit() 278 | { 279 | network_exit(); 280 | } 281 | -------------------------------------------------------------------------------- /source/secureNetworkRequest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include <3ds.h> 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include <3ds.h> 11 | #include 12 | #include 13 | #include 14 | 15 | #include "secureNetworkRequest.h" 16 | #include "lets_encrypt_rootca.h" 17 | 18 | #define SOC_ALIGN 0x1000 19 | #define SOC_BUFFERSIZE 0x100000 20 | bool socketsOpenWithoutError = true; 21 | static u32 *SOC_buffer = NULL; 22 | s32 sock = -1, csock = -1; 23 | 24 | bool DEBUG = false; 25 | 26 | struct CallbackData 27 | { 28 | FILE *fp; 29 | int is_binary; 30 | int writingCount; 31 | std::string jsonResponse; 32 | }; 33 | 34 | size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata) 35 | { 36 | size_t total_size = size * nitems; 37 | struct CallbackData *data = (struct CallbackData *)userdata; 38 | 39 | // Check if the Content-Type header is present 40 | if (strncasecmp(buffer, "Content-Type:", 13) == 0) 41 | { 42 | char *content_type = buffer + 13; 43 | 44 | if (DEBUG) 45 | printf("Content-Type: %s\n", content_type); 46 | 47 | if (std::string(content_type).find("application/json") != std::string::npos) 48 | data->is_binary = 0; 49 | else 50 | data->is_binary = 1; 51 | } 52 | 53 | return total_size; 54 | } 55 | 56 | size_t write_data(void *ptr, size_t size, size_t nmemb, void *userdata) 57 | { 58 | struct CallbackData *data = (struct CallbackData *)userdata; 59 | 60 | if (!data->is_binary) 61 | { 62 | data->jsonResponse.append(static_cast(ptr), size * nmemb); 63 | return nmemb * size; 64 | } 65 | 66 | data->jsonResponse = "{}"; 67 | 68 | if (data->fp != nullptr) 69 | { 70 | data->writingCount++; 71 | 72 | printf("Writing data! (%d/?)\n", data->writingCount); 73 | size_t written = fwrite(ptr, size, nmemb, data->fp); 74 | return written; 75 | } 76 | 77 | return 0; 78 | } 79 | 80 | bool valid_certificate() 81 | { 82 | auto now = std::chrono::system_clock::now(); 83 | auto now_time_t = std::chrono::system_clock::to_time_t(now); 84 | std::tm *now_tm = std::localtime(&now_time_t); 85 | 86 | return !(now_tm->tm_year + 1900 >= YEAR_EXPIRY && now_tm->tm_mon >= (MONTH_EXPIRY - 1)); 87 | } 88 | 89 | void network_init() 90 | { 91 | int ret; 92 | 93 | httpcInit(4 * 1024 * 1024); 94 | 95 | SOC_buffer = (u32 *)memalign(SOC_ALIGN, SOC_BUFFERSIZE); 96 | 97 | if (SOC_buffer == NULL) 98 | { 99 | socketsOpenWithoutError = false; 100 | printf("memalign: failed to allocate\n"); 101 | } 102 | 103 | if ((ret = socInit(SOC_buffer, SOC_BUFFERSIZE)) != 0 && socketsOpenWithoutError) 104 | { 105 | socketsOpenWithoutError = false; 106 | printf("socInit: 0x%08X\n", (unsigned int)ret); 107 | } 108 | 109 | } 110 | 111 | void network_exit() 112 | { 113 | socExit(); 114 | httpcExit(); 115 | } 116 | 117 | responsePair network_request(std::string *address, std::string *jsonData, std::string *downloadPath) 118 | { 119 | 120 | if (!socketsOpenWithoutError) 121 | { 122 | if (DEBUG) 123 | printf("socketsOpenWithoutError is false\n"); 124 | return std::make_pair(0, ""); 125 | } 126 | 127 | CURL *curl; 128 | CURLcode res; 129 | 130 | if (DEBUG) { 131 | 132 | printf("curl init\n"); 133 | printf(address->c_str()); 134 | printf("\n"); 135 | 136 | } 137 | curl_global_init(CURL_GLOBAL_DEFAULT); 138 | 139 | curl = curl_easy_init(); 140 | if (curl) 141 | { 142 | try 143 | { 144 | 145 | struct CallbackData data; 146 | 147 | if (downloadPath != nullptr) 148 | { 149 | 150 | std::filesystem::path path = *downloadPath; 151 | std::filesystem::path parentPath = path.parent_path(); 152 | 153 | if (!std::filesystem::exists(parentPath)) 154 | { 155 | std::filesystem::create_directories(parentPath); 156 | } 157 | 158 | data.writingCount = 0; 159 | 160 | data.fp = fopen(downloadPath->c_str(), "wb"); 161 | if (!data.fp) 162 | { 163 | if (DEBUG) 164 | printf("fopen failed\n"); 165 | return std::make_pair(0, ""); 166 | } 167 | } 168 | else 169 | { 170 | data.fp = nullptr; 171 | } 172 | 173 | struct curl_blob blob; 174 | blob.data = (char *)__lets_encrypt_rootca_pem; 175 | blob.len = sizeof(__lets_encrypt_rootca_pem); 176 | blob.flags = CURL_BLOB_COPY; 177 | curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob); 178 | 179 | if (!valid_certificate()) 180 | { 181 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); 182 | } 183 | else 184 | { 185 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); 186 | } 187 | 188 | curl_easy_setopt(curl, CURLOPT_URL, (*address).c_str()); 189 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "Citrahold 3DS Client (libcurl)/1.0"); 190 | 191 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5); // Max time to spend on connection setup 192 | 193 | if (jsonData == nullptr) 194 | { 195 | jsonData = new std::string("{}"); 196 | } 197 | 198 | struct curl_slist *headers = NULL; 199 | headers = curl_slist_append(headers, "Content-Type: application/json"); 200 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 201 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData->c_str()); 202 | 203 | curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); 204 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); 205 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); 206 | curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &data); 207 | 208 | if (DEBUG) 209 | printf("curl_easy_perform\n"); 210 | 211 | res = curl_easy_perform(curl); 212 | int httpStatusCode = 0; 213 | 214 | if (res != CURLE_OK) 215 | { 216 | if (DEBUG) 217 | { 218 | printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); 219 | printf("\n"); 220 | printf(address->c_str()); 221 | printf("\n"); 222 | } 223 | } 224 | 225 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpStatusCode); 226 | 227 | curl_slist_free_all(headers); 228 | 229 | if (data.fp != nullptr) 230 | fclose(data.fp); 231 | 232 | curl_easy_cleanup(curl); 233 | curl_global_cleanup(); 234 | 235 | // free other stuff?? 236 | 237 | return std::make_pair(httpStatusCode, data.jsonResponse); 238 | } 239 | catch (const std::exception &e) 240 | { 241 | printf("Exception: %s\n", e.what()); 242 | } 243 | } 244 | 245 | return std::make_pair(0, ""); 246 | } -------------------------------------------------------------------------------- /source/systemCore.cpp: -------------------------------------------------------------------------------- 1 | #include <3ds.h> 2 | #include 3 | #include 4 | #include 5 | 6 | #include "systemCore.h" 7 | #include "menuSystem.h" 8 | #include "menus.h" 9 | #include "networkSystem.h" 10 | #include "configManager.h" 11 | #include "directoryMenu.h" 12 | #include "json.hpp" 13 | #include "helpers.h" 14 | #include "base64.h" 15 | 16 | #ifndef VERSION_MAJOR 17 | #define VERSION_MAJOR 0 18 | #endif 19 | 20 | #ifndef VERSION_MINOR 21 | #define VERSION_MINOR 0 22 | #endif 23 | 24 | #ifndef VERSION_MICRO 25 | #define VERSION_MICRO 0 26 | #endif 27 | 28 | SystemCore::SystemCore() : networkSystem() 29 | { 30 | gfxInitDefault(); 31 | 32 | consoleInit(GFX_BOTTOM, NULL); 33 | 34 | std::cout << "\n- - - - - - - - - - - - - - - - -\n\nWelcome to Citrahold " 35 | << "v" << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_MICRO << std::endl; 36 | std::cout << "\nFor more information, please visit\n>> https://www.citrahold.com/ <<" << std::endl; 37 | std::cout << "\nPress START at any point to exit.\n\nPlease use the D-pad to navigate, \nand use A to confirm actions.\n\nPlease also use B to go back from\na menu.\n\n- - - - - - - - - - - - - - - - -" 38 | << std::endl; 39 | 40 | configManager = ConfigManager(); 41 | 42 | checkServerConnection(); 43 | 44 | if (isServerAccessible) 45 | { 46 | networkSystem.checkVersion("v" + std::to_string(VERSION_MAJOR) + "." + std::to_string(VERSION_MINOR) + "." + std::to_string(VERSION_MICRO)); 47 | } else { 48 | 49 | std::cout << "\nRetrying connection once."; 50 | 51 | checkServerConnection(); 52 | if (isServerAccessible) 53 | { 54 | networkSystem.checkVersion("v" + std::to_string(VERSION_MAJOR) + "." + std::to_string(VERSION_MINOR) + "." + std::to_string(VERSION_MICRO)); 55 | } 56 | 57 | } 58 | 59 | menuSystem = MenuSystem(); 60 | 61 | directoryMenu = DirectoryMenu(); 62 | 63 | selectorBuf = C2D_TextBufNew(4096); 64 | 65 | C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); 66 | C2D_Init(C2D_DEFAULT_MAX_OBJECTS); 67 | C2D_Prepare(); 68 | 69 | screen = C2D_CreateScreenTarget(GFX_TOP, GFX_LEFT); 70 | size = 0.6f; 71 | selection = 0; 72 | halt = false; 73 | shouldKeyboardBeOpen = false; 74 | currentUploadType = UploadTypeEnum::SAVES; 75 | 76 | menuSystem.changeMenu(selection, currentMenuItems, mainMenuItems, previousMenus); 77 | } 78 | 79 | void SystemCore::checkServerConnection() 80 | { 81 | std::cout << "\nGetting server status..."; 82 | 83 | responsePair serverStatus = networkSystem.init(configManager.getConfig()["serverAddress"], configManager.getToken()); 84 | 85 | std::cout << "\r \r"; 86 | 87 | std::cout << (serverStatus.first == 200 ? "Connected to Citrahold Server!" : "Server inaccessible.\nThis is NOT OK.\nIf this persists, please check the\nwebsite for a new build.") << "\n"; 88 | 89 | isServerAccessible = (serverStatus.first == 200); 90 | 91 | if (isServerAccessible) 92 | { 93 | if (configManager.getToken() != "" && configManager.getToken() != "unknown") 94 | { 95 | std::string userID = networkSystem.verifyTokenToSetUserID(configManager.getToken()); 96 | if (userID != "invalid") 97 | { 98 | networkSystem.loggedIn = true; 99 | std::cout << "\nSuccessfully authenticated as user\n" 100 | << userID << std::endl; 101 | } 102 | else 103 | { 104 | networkSystem.loggedIn = false; 105 | std::cout << "\nInvalid token currently saved." << std::endl; 106 | } 107 | } 108 | else 109 | { 110 | std::cout << "\nPlease go to Settings and input\na shorthand token!" << std::endl; 111 | } 112 | } 113 | } 114 | 115 | void SystemCore::handleFunction(menuFunctions function, unsigned int key) 116 | { 117 | switch (key) 118 | { 119 | 120 | case (KEY_B): 121 | { 122 | if (menuSystem.getCurrentMenuItems() == &uploadDirectoryMenuItems) 123 | { 124 | 125 | handleFunction(menuFunctions::specialB_goPreviousDirectory); 126 | } 127 | else if (menuSystem.getCurrentMenuItems() == &existingGameIDsMenuItems) 128 | { 129 | handleFunction(menuFunctions::specialB_exitExistingGameIDSelection); 130 | } 131 | else 132 | { 133 | menuSystem.goToPreviousMenu(selection, currentMenuItems, previousMenus); 134 | } 135 | break; 136 | } 137 | 138 | case (KEY_X): 139 | { 140 | if (menuSystem.getCurrentMenuItems() == &uploadDirectoryMenuItems) 141 | { 142 | handleFunction(menuFunctions::specialX_exitDirectorySelection); 143 | } 144 | else if (menuSystem.getCurrentMenuItems() == &existingGameIDsMenuItems) 145 | { 146 | 147 | handleFunction(menuFunctions::specialX_redirectGameID); 148 | } 149 | else 150 | { 151 | menuSystem.goToPreviousMenu(selection, currentMenuItems, previousMenus); 152 | } 153 | break; 154 | } 155 | 156 | case (KEY_Y): 157 | { 158 | if (menuSystem.getCurrentMenuItems() == &uploadDirectoryMenuItems) 159 | { 160 | handleFunction(menuFunctions::specialY_enterGameID); 161 | } 162 | else if (menuSystem.getCurrentMenuItems() == &existingGameIDsMenuItems) 163 | { 164 | handleFunction(menuFunctions::specialY_deleteGameID); 165 | } 166 | break; 167 | } 168 | 169 | case (KEY_LEFT): 170 | { 171 | if (menuSystem.getCurrentMenuItems() == &uploadDirectoryMenuItems) 172 | { 173 | handleFunction(menuFunctions::specialB_goPreviousDirectory); 174 | } 175 | break; 176 | } 177 | 178 | case (KEY_RIGHT): 179 | { 180 | if (menuSystem.getCurrentMenuItems() == &uploadDirectoryMenuItems) 181 | { 182 | handleFunction(menuFunctions::traverseDirectory); 183 | } 184 | break; 185 | } 186 | 187 | default: 188 | { 189 | switch (function) 190 | { 191 | 192 | case menuFunctions::specialX_exitDirectorySelection: 193 | { 194 | std::cout << currentGameID << std::endl; 195 | if (currentGameID == "") 196 | { 197 | menuSystem.changeMenu(selection, currentMenuItems, mainMenuItems, previousMenus); 198 | } 199 | else 200 | { 201 | handleFunction(currentUploadType == UploadTypeEnum::SAVES ? menuFunctions::existingGameIDSavesMenuItems : menuFunctions::existingGameIDExtdataMenuItems); 202 | } 203 | 204 | break; 205 | } 206 | 207 | case menuFunctions::specialB_exitExistingGameIDSelection: 208 | { 209 | 210 | menuSystem.changeMenu(selection, currentMenuItems, gameIDDirectoryMenuItems, previousMenus); 211 | break; 212 | } 213 | 214 | case menuFunctions::specialB_goPreviousDirectory: 215 | { 216 | std::filesystem::path newDirectory = directoryMenu.getCurrentDirectory().parent_path(); 217 | directoryMenu.setCurrentDirectory(newDirectory); 218 | uploadDirectoryMenuItems = directoryMenu.getCurrentDirectoryMenuItems(); 219 | menuSystem.changeMenu(selection, currentMenuItems, uploadDirectoryMenuItems, previousMenus, true); 220 | break; 221 | } 222 | 223 | case menuFunctions::specialY_enterGameID: 224 | { 225 | if (directoryMenu.getCurrentDirectory() != "/" && directoryMenu.getCurrentDirectory() != "" && directoryMenu.getCurrentDirectory() != "/3ds" && directoryMenu.getCurrentDirectory() != "/Nintendo 3DS") 226 | { 227 | 228 | std::string gameID = (currentGameID == "") ? openKeyboard("Enter a game ID (this must EXACTLY match your PC one)") : currentGameID; 229 | 230 | if (gameID != "") 231 | { 232 | 233 | UploadTypeEnum uploadType = UploadTypeEnum::SAVES; 234 | if (directoryMenu.getCurrentDirectory().string().find("extdata") != std::string::npos) 235 | { 236 | uploadType = UploadTypeEnum::EXTDATA; 237 | } 238 | 239 | configManager.addGameIDToFile(uploadType, gameID, directoryMenu.getCurrentDirectory()); 240 | 241 | handleFunction(menuFunctions::specialX_exitDirectorySelection); 242 | std::cout << "Game ID \"" << gameID << "\" added to " << ((uploadType == UploadTypeEnum::SAVES) ? "saves" : "extdata") << " file" << std::endl; 243 | 244 | currentGameID = ""; 245 | } 246 | } 247 | break; 248 | } 249 | 250 | case menuFunctions::specialX_redirectGameID: 251 | { 252 | currentGameID = std::get<0>(existingGameIDsMenuItems[selection]); 253 | directoryMenu.setCurrentDirectory(configManager.getGamePathFromID(currentUploadType, currentGameID)); 254 | uploadDirectoryMenuItems = directoryMenu.getCurrentDirectoryMenuItems(); 255 | menuSystem.changeMenu(selection, currentMenuItems, uploadDirectoryMenuItems, previousMenus, true); 256 | 257 | break; 258 | } 259 | 260 | case menuFunctions::specialY_deleteGameID: 261 | { 262 | configManager.removeGameIDFromFile(currentUploadType, std::get<0>(existingGameIDsMenuItems[selection])); 263 | 264 | if (configManager.getNumberOfGameIDs(currentUploadType) == 0) 265 | { 266 | handleFunction(menuFunctions::specialB_exitExistingGameIDSelection); 267 | } 268 | else 269 | { 270 | 271 | handleFunction(currentUploadType == UploadTypeEnum::SAVES ? menuFunctions::existingGameIDSavesMenuItems : menuFunctions::existingGameIDExtdataMenuItems); 272 | } 273 | 274 | break; 275 | } 276 | 277 | case menuFunctions::deleteSavesAfterUpload: 278 | { 279 | 280 | configManager.setDeleteSaveAfterUpload(!configManager.getDeleteSaveAfterUpload()); 281 | configManager.getDeleteSaveAfterUpload() ? std::cout << "Saves will now be deleted after\nupload" << std::endl : std::cout << "Saves will no longer be deleted\nafter upload" << std::endl; 282 | 283 | break; 284 | } 285 | 286 | case menuFunctions::renameGameID: 287 | { 288 | std::string newGameID = openKeyboard("Enter a new ID to replace " + std::get<0>(existingGameIDsMenuItems[selection]), std::get<0>(existingGameIDsMenuItems[selection])); 289 | if (newGameID != "") 290 | { 291 | configManager.renameGameIDInFile(currentUploadType, std::get<0>(existingGameIDsMenuItems[selection]), newGameID); 292 | handleFunction(currentUploadType == UploadTypeEnum::SAVES ? menuFunctions::existingGameIDSavesMenuItems : menuFunctions::existingGameIDExtdataMenuItems); 293 | } 294 | 295 | break; 296 | } 297 | 298 | case menuFunctions::gameIDDirectoryMenuItems: 299 | { 300 | currentGameID = ""; 301 | menuSystem.changeMenu(selection, currentMenuItems, gameIDDirectoryMenuItems, previousMenus); 302 | break; 303 | } 304 | 305 | case menuFunctions::uploadMenuItems: 306 | menuSystem.changeMenu(selection, currentMenuItems, uploadMenuItems, previousMenus); 307 | break; 308 | 309 | case menuFunctions::downloadMenuItems: 310 | menuSystem.changeMenu(selection, currentMenuItems, downloadMenuItems, previousMenus); 311 | break; 312 | 313 | case menuFunctions::settingsMenuItems: 314 | menuSystem.changeMenu(selection, currentMenuItems, settingMenuItems, previousMenus); 315 | break; 316 | 317 | case menuFunctions::mainMenuMenuItems: 318 | menuSystem.changeMenu(selection, currentMenuItems, mainMenuItems, previousMenus); 319 | break; 320 | 321 | case menuFunctions::changeToPreviousMenu: 322 | menuSystem.goToPreviousMenu(selection, currentMenuItems, previousMenus); 323 | break; 324 | 325 | case menuFunctions::inputToken: 326 | { 327 | std::string token = openKeyboard("Enter a shorthand token"); 328 | 329 | if (token != "") 330 | { 331 | std::cout << "Verifying token..." << std::endl; 332 | if (token.length() > 6) 333 | { 334 | // FULL TOKEN 335 | std::string userID = networkSystem.verifyTokenToSetUserID(token); 336 | if (userID != "invalid") 337 | { 338 | configManager.userID = userID; 339 | configManager.setToken(token); 340 | std::cout << "Successfully authenticated as user" << userID << std::endl; 341 | networkSystem.loggedIn = true; 342 | } 343 | else 344 | { 345 | std::cout << "Invalid token" << std::endl; 346 | } 347 | } 348 | else 349 | { 350 | // SHORTHAND TOKEN 351 | std::string fullToken = networkSystem.getTokenFromShorthandToken(token); 352 | if (fullToken != "invalid") 353 | { 354 | std::string userID = networkSystem.verifyTokenToSetUserID(fullToken); 355 | if (userID != "invalid") 356 | { 357 | configManager.userID = userID; 358 | configManager.setToken(fullToken); 359 | std::cout << "Successfully authenticated as user\n" 360 | << userID << std::endl; 361 | networkSystem.loggedIn = true; 362 | } 363 | } 364 | else 365 | { 366 | std::cout << "Invalid token" << std::endl; 367 | } 368 | } 369 | } 370 | break; 371 | } 372 | 373 | case menuFunctions::checkServerConnection: 374 | { 375 | 376 | checkServerConnection(); 377 | 378 | break; 379 | } 380 | 381 | case menuFunctions::openKeyboard: 382 | std::cout << openKeyboard() << std::endl; 383 | break; 384 | 385 | case menuFunctions::directoryMenuItems: 386 | { 387 | 388 | uploadDirectoryMenuItems = directoryMenu.getCurrentDirectoryMenuItems(); 389 | 390 | menuSystem.changeMenu(selection, currentMenuItems, uploadDirectoryMenuItems, previousMenus, false); 391 | 392 | break; 393 | } 394 | 395 | case menuFunctions::gameIDSavesMenuItems: 396 | { 397 | 398 | nlohmann::json gameIDJSON = configManager.getGameIDFile(UploadTypeEnum::SAVES); 399 | gameIDMenuItems = directoryMenu.getGameIDDirectoryMenuItems(gameIDJSON, menuFunctions::saveSelectionMenuItems); 400 | 401 | if (gameIDMenuItems.size() == 0) 402 | { 403 | std::cout << "No game IDs found, please add one" << std::endl; 404 | break; 405 | } 406 | 407 | currentUploadType = UploadTypeEnum::SAVES; 408 | menuSystem.changeMenu(selection, currentMenuItems, gameIDMenuItems, previousMenus, false); 409 | 410 | break; 411 | } 412 | 413 | case menuFunctions::gameIDExtdataMenuItems: 414 | { 415 | 416 | nlohmann::json gameIDJSON = configManager.getGameIDFile(UploadTypeEnum::EXTDATA); 417 | gameIDMenuItems = directoryMenu.getGameIDDirectoryMenuItems(gameIDJSON, menuFunctions::saveSelectionMenuItems); 418 | 419 | if (gameIDMenuItems.size() == 0) 420 | { 421 | std::cout << "No game IDs found, please add one" << std::endl; 422 | break; 423 | } 424 | 425 | currentUploadType = UploadTypeEnum::EXTDATA; 426 | menuSystem.changeMenu(selection, currentMenuItems, gameIDMenuItems, previousMenus, false); 427 | 428 | break; 429 | } 430 | 431 | case menuFunctions::traverseDirectory: 432 | { 433 | std::filesystem::path newDirectory = directoryMenu.getCurrentDirectory() / std::get<0>(uploadDirectoryMenuItems[selection]); 434 | if (selection == 0) 435 | { 436 | newDirectory = directoryMenu.getCurrentDirectory().parent_path(); 437 | } 438 | 439 | directoryMenu.setCurrentDirectory(newDirectory); 440 | uploadDirectoryMenuItems = directoryMenu.getCurrentDirectoryMenuItems(); 441 | menuSystem.changeMenu(selection, currentMenuItems, uploadDirectoryMenuItems, previousMenus, true); 442 | break; 443 | } 444 | 445 | case menuFunctions::saveSelectionMenuItems: 446 | { 447 | if (!networkSystem.loggedIn) 448 | { 449 | std::cout << "Not logged in" << std::endl; 450 | break; 451 | } 452 | 453 | nlohmann::json gameIDJSON = configManager.getGameIDFile(currentUploadType); 454 | currentGameID = std::get<0>(gameIDMenuItems[selection]); 455 | std::filesystem::path gamePath = directoryMenu.getGamePathFromGameID(currentGameID, gameIDJSON); 456 | saveSelectionMenuItems = directoryMenu.getSaveSelectionMenuItems(gamePath); 457 | if (saveSelectionMenuItems.size() == 0) 458 | { 459 | std::cout << "No saves found" << std::endl; 460 | break; 461 | } 462 | menuSystem.changeMenu(selection, currentMenuItems, saveSelectionMenuItems, previousMenus, false); 463 | break; 464 | } 465 | 466 | case menuFunctions::uploadGame: 467 | { 468 | if (!networkSystem.loggedIn) 469 | { 470 | std::cout << "Not logged in" << std::endl; 471 | break; 472 | } 473 | 474 | nlohmann::json gameIDJSON = configManager.getGameIDFile(currentUploadType); 475 | std::filesystem::path gamePath = directoryMenu.getGamePathFromGameID(currentGameID, gameIDJSON); 476 | std::filesystem::path savePath = gamePath / std::get<0>(saveSelectionMenuItems[selection]); 477 | 478 | std::cout << "Adding files to upload..." << std::endl; 479 | 480 | // make jsonArray called multiUpload 481 | nlohmann::json multiUpload; 482 | multiUpload["token"] = configManager.getToken(); 483 | multiUpload["game"] = currentGameID; 484 | multiUpload["multi"] = nlohmann::json::array(); 485 | 486 | std::uintmax_t totalFileSize = 0; 487 | 488 | try 489 | { 490 | int prevStatus = -1; 491 | std::filesystem::path gamePath = directoryMenu.getGamePathFromGameID(currentGameID, gameIDJSON); 492 | std::vector filesToUpload; 493 | 494 | for (const auto &dirEntry : std::filesystem::recursive_directory_iterator(savePath)) 495 | { 496 | 497 | if (std::filesystem::is_regular_file(dirEntry)) // change to if not 201/200 in future??? 498 | { 499 | std::filesystem::path fullPath = dirEntry.path(); 500 | filesToUpload.push_back(fullPath); 501 | 502 | totalFileSize += std::filesystem::file_size(fullPath); 503 | 504 | if (totalFileSize > 128000000) // magic number needs to be sorted at some point 505 | { 506 | std::cout << "File size too large for upload (over 128MB)" << std::endl; 507 | return; 508 | } 509 | 510 | std::cout << "Added " << (currentGameID / std::filesystem::relative(dirEntry, savePath)) << std::endl; 511 | } 512 | 513 | else if (std::filesystem::is_directory(dirEntry)) 514 | { 515 | std::filesystem::path fullPath = dirEntry.path(); 516 | std::filesystem::path relativePath = std::filesystem::relative(dirEntry, savePath); 517 | 518 | std::cout << "Added " << (currentGameID / relativePath) << std::endl; 519 | filesToUpload.push_back((currentGameID / relativePath / "citraholdDirectoryDummy")); 520 | } 521 | } 522 | 523 | for (unsigned int i = 0; i < filesToUpload.size(); i++) 524 | { 525 | 526 | std::string base64Data = ""; 527 | std::string filePath = ""; 528 | 529 | if (filesToUpload[i].string().find("citraholdDirectoryDummy") != std::string::npos) 530 | { 531 | 532 | base64Data = "citraholdDirectoryDummy"; 533 | filePath = filesToUpload[i]; 534 | } 535 | else 536 | { 537 | 538 | std::filesystem::path relativePath = std::filesystem::relative(filesToUpload[i], savePath); 539 | std::cout << "[" << i + 1 << "/" << filesToUpload.size() << "] " 540 | << "Encoding " << relativePath << std::endl; 541 | 542 | base64Data = networkSystem.getBase64StringFromFile(filesToUpload[i], relativePath); 543 | filePath = (currentGameID / relativePath).string(); 544 | } 545 | 546 | nlohmann::json entry = nlohmann::json::array(); 547 | entry.push_back(filePath); 548 | entry.push_back(base64Data); 549 | 550 | multiUpload["multi"].push_back(entry); 551 | } 552 | 553 | if (multiUpload["multi"].size() > 0) 554 | { 555 | std::cout << "All files encoded.\nUpload starting soon." << std::endl; 556 | 557 | int attempts = 0; 558 | while (prevStatus != 201 && prevStatus != 200 && attempts <= 3) 559 | { 560 | prevStatus = networkSystem.uploadMultiple(currentUploadType, multiUpload); 561 | 562 | if (attempts > 0) 563 | { 564 | std::cout << "Retrying... (attempt " << attempts << ")" << std::endl; 565 | } 566 | 567 | std::cout << "HTTP " << prevStatus << std::endl; 568 | 569 | attempts++; 570 | } 571 | } 572 | 573 | multiUpload.clear(); 574 | 575 | if (prevStatus == 201 || prevStatus == 200) 576 | { 577 | if (configManager.getDeleteSaveAfterUpload()) 578 | { 579 | std::cout << "Deleting save..." << std::endl; 580 | 581 | safeDirectoryRemove(savePath); 582 | } 583 | 584 | std::cout << "Upload fully successful (complete!)" << std::endl; 585 | 586 | menuSystem.changeMenu(selection, currentMenuItems, mainMenuItems, previousMenus); 587 | } 588 | else if (prevStatus == 0 || prevStatus == -1) 589 | { 590 | if (filesToUpload.size() == 0) 591 | { 592 | std::cout << "This is an empty directory" << std::endl; 593 | if (configManager.getDeleteSaveAfterUpload()) 594 | { 595 | std::cout << "Deleting..." << std::endl; 596 | safeDirectoryRemove(savePath); 597 | std::cout << "Deleted." << std::endl; 598 | } 599 | } 600 | else 601 | { 602 | std::cout << "HTTP " << prevStatus << std::endl; 603 | std::cout << "Upload failed. Is there anything to upload? (or could network be offline?)" << std::endl; 604 | } 605 | } 606 | else 607 | { 608 | std::cout << "Upload failed" << std::endl; 609 | } 610 | } 611 | catch (std::filesystem::filesystem_error &e) 612 | { 613 | std::cout << e.what() << std::endl; 614 | } 615 | 616 | break; 617 | } 618 | 619 | case menuFunctions::downloadSavesMenuItems: 620 | { 621 | if (!networkSystem.loggedIn) 622 | { 623 | std::cout << "Not logged in" << std::endl; 624 | break; 625 | } 626 | 627 | currentUploadType = UploadTypeEnum::SAVES; 628 | downloadGameMenuItems = networkSystem.getGamesMenuItems(UploadTypeEnum::SAVES); 629 | if (downloadGameMenuItems.size() == 0) 630 | { 631 | std::cout << "No saves found" << std::endl; 632 | break; 633 | } 634 | 635 | menuSystem.changeMenu(selection, currentMenuItems, downloadGameMenuItems, previousMenus, false); 636 | break; 637 | } 638 | 639 | case menuFunctions::downloadExtdataMenuItems: 640 | { 641 | if (!networkSystem.loggedIn) 642 | { 643 | std::cout << "Not logged in" << std::endl; 644 | break; 645 | } 646 | 647 | currentUploadType = UploadTypeEnum::EXTDATA; 648 | downloadGameMenuItems = networkSystem.getGamesMenuItems(UploadTypeEnum::EXTDATA); 649 | if (downloadGameMenuItems.size() == 0) 650 | { 651 | std::cout << "No saves found" << std::endl; 652 | break; 653 | } 654 | 655 | menuSystem.changeMenu(selection, currentMenuItems, downloadGameMenuItems, previousMenus, false); 656 | break; 657 | } 658 | 659 | case menuFunctions::downloadGame: 660 | { 661 | nlohmann::json gameIDJSON = configManager.getGameIDFile(currentUploadType); 662 | 663 | currentGameID = std::get<0>(downloadGameMenuItems[selection]); 664 | 665 | std::filesystem::path gamePath = directoryMenu.getGamePathFromGameID(currentGameID, gameIDJSON); 666 | if (gamePath == "") 667 | { 668 | std::cout << "You need to register this game as\n" 669 | << currentGameID << " before you download this save" << std::endl; 670 | break; 671 | } 672 | 673 | std::cout << "Downloading " << currentGameID << " to " << gamePath << std::endl; 674 | bool downloadSuccessful = networkSystem.download(currentUploadType, currentGameID, gamePath); 675 | std::cout << (downloadSuccessful ? "Download fully successful (complete!)" : "Download failed") << std::endl; 676 | 677 | break; 678 | } 679 | 680 | case menuFunctions::existingGameIDExtdataMenuItems: 681 | { 682 | nlohmann::json gameIDJSON = configManager.getGameIDFile(UploadTypeEnum::EXTDATA); 683 | gameIDMenuItems = directoryMenu.getGameIDDirectoryMenuItems(gameIDJSON, menuFunctions::renameGameID); 684 | 685 | if (gameIDMenuItems.size() == 0) 686 | { 687 | std::cout << "No game IDs found" << std::endl; 688 | break; 689 | } 690 | 691 | currentUploadType = UploadTypeEnum::EXTDATA; 692 | existingGameIDsMenuItems = gameIDMenuItems; 693 | menuSystem.changeMenu(selection, currentMenuItems, existingGameIDsMenuItems, previousMenus); 694 | 695 | break; 696 | } 697 | 698 | case menuFunctions::existingGameIDSavesMenuItems: 699 | { 700 | nlohmann::json gameIDJSON = configManager.getGameIDFile(UploadTypeEnum::SAVES); 701 | gameIDMenuItems = directoryMenu.getGameIDDirectoryMenuItems(gameIDJSON, menuFunctions::renameGameID); 702 | 703 | if (gameIDMenuItems.size() == 0) 704 | { 705 | std::cout << "No game IDs found" << std::endl; 706 | break; 707 | } 708 | 709 | currentUploadType = UploadTypeEnum::SAVES; 710 | existingGameIDsMenuItems = gameIDMenuItems; 711 | menuSystem.changeMenu(selection, currentMenuItems, existingGameIDsMenuItems, previousMenus); 712 | 713 | break; 714 | } 715 | 716 | case menuFunctions::resetGameIDFiles: 717 | { 718 | currentGameID = ""; 719 | configManager.resetBothGameIDFiles(); 720 | 721 | break; 722 | } 723 | 724 | default: 725 | 726 | break; 727 | } 728 | 729 | break; 730 | } 731 | } 732 | } 733 | 734 | void SystemCore::handleInput() 735 | { 736 | hidScanInput(); 737 | 738 | // volume can be used to increase/decrease text size 739 | u8 volume; 740 | HIDUSER_GetSoundVolume(&volume); 741 | if (!dontUseVolumeBar) 742 | { 743 | size = 0.6f * (1.0f - volume / 63.0f); 744 | } 745 | else 746 | { 747 | size = 0.6f * (1.0f - zoom / 63.0f); 748 | } 749 | 750 | u32 kDown = hidKeysDown(); 751 | u32 kHeld = hidKeysHeld(); 752 | 753 | if (kDown & KEY_START) 754 | halt = true; // break in order to return to hbmenu 755 | 756 | if (kDown & KEY_SELECT) 757 | { 758 | dontUseVolumeBar = !dontUseVolumeBar; 759 | dontUseVolumeBar ? std::cout << "Use left and right shoulder to zoom." << std::endl : std::cout << "Use volume bar to zoom." << std::endl; 760 | } 761 | 762 | if (dontUseVolumeBar) 763 | { 764 | if ((kHeld & KEY_L)) 765 | { 766 | zoom++; 767 | if (zoom > 63) 768 | { 769 | zoom = 63; 770 | } 771 | } 772 | else if (kHeld & KEY_R) 773 | { 774 | zoom--; 775 | if (zoom < 0) 776 | { 777 | zoom = 0; 778 | } 779 | } 780 | } 781 | 782 | if (kDown & KEY_UP) 783 | if (selection <= 0) 784 | selection = (*currentMenuItems).size() - 1; 785 | else 786 | selection--; 787 | else if (kDown & KEY_DOWN) 788 | { 789 | if (selection + 1 >= (int)(*currentMenuItems).size()) 790 | selection = 0; 791 | else 792 | selection++; 793 | } 794 | 795 | if (kDown & KEY_A) 796 | { 797 | handleFunction(std::get<1>((*currentMenuItems)[selection]), KEY_A); 798 | } 799 | else if (kDown & KEY_B) 800 | { 801 | handleFunction(std::get<1>((*currentMenuItems)[selection]), KEY_B); 802 | } 803 | else if (kDown & KEY_X) 804 | { 805 | handleFunction(std::get<1>((*currentMenuItems)[selection]), KEY_X); 806 | } 807 | else if (kDown & KEY_Y) 808 | { 809 | handleFunction(std::get<1>((*currentMenuItems)[selection]), KEY_Y); 810 | } 811 | else if (kDown & KEY_RIGHT) 812 | { 813 | handleFunction(std::get<1>((*currentMenuItems)[selection]), KEY_RIGHT); 814 | } 815 | else if (kDown & KEY_LEFT) 816 | { 817 | handleFunction(std::get<1>((*currentMenuItems)[selection]), KEY_LEFT); 818 | } 819 | } 820 | 821 | void SystemCore::sceneRender() 822 | { 823 | std::string pointerSymbol = ""; 824 | std::string str = ""; 825 | if (menuSystem.getCurrentMenuItems() == &uploadDirectoryMenuItems) 826 | { 827 | pointerSymbol = ">"; 828 | str += std::string(directoryMenu.getCurrentDirectory()) + "\n"; 829 | } 830 | else if (menuSystem.getCurrentMenuItems() == &existingGameIDsMenuItems) 831 | { 832 | pointerSymbol = ">"; 833 | std::string currentType = (currentUploadType == UploadTypeEnum::EXTDATA) ? "extdata" : "saves"; 834 | str += "Current " + currentType + " game IDs" + "\n"; 835 | } 836 | else if (menuSystem.getHeader(currentMenuItems) != nullptr) 837 | str += *(menuSystem.getHeader(currentMenuItems)) + "\n"; 838 | 839 | menuItems renderMenuItems = *currentMenuItems; 840 | 841 | C3D_FrameBegin(C3D_FRAME_SYNCDRAW); 842 | C2D_TargetClear(screen, C2D_Color32(0xFF, 0xDA, 0x75, 0xFF)); 843 | C2D_SceneBegin(screen); 844 | 845 | C2D_TextBufClear(*menuSystem.getMenuTextBuf()); 846 | C2D_TextBufClear(selectorBuf); 847 | C2D_DrawText(menuSystem.getMenuText(), 0, 38.0f, 16.0f, 0.5f, size, size); 848 | 849 | C2D_Text dynText; 850 | 851 | if (menuSystem.getCurrentMenuItems() != &uploadDirectoryMenuItems) 852 | { 853 | 854 | if (selection > 0) 855 | { 856 | for (int i = 0; i < selection; i++) 857 | { 858 | str += "\n"; 859 | } 860 | } 861 | 862 | str += pointerSymbol; 863 | 864 | 865 | for (size_t i = 0; i < ((renderMenuItems).size() - selection); i++) 866 | { 867 | str += "\n"; 868 | } 869 | } else { 870 | str += "\n\n"; 871 | } 872 | 873 | if (selection > (int)(renderMenuItems).size() - 1) 874 | { 875 | selection = (int)(renderMenuItems).size() - 1; 876 | } 877 | 878 | if (menuSystem.getCurrentMenuItems() != &uploadDirectoryMenuItems) 879 | { 880 | str += "Selected \"" + std::get<0>((renderMenuItems)[selection]) + "\""; 881 | } 882 | 883 | //  ABXY 884 | 885 | if (menuSystem.getCurrentMenuItems() == &uploadDirectoryMenuItems) 886 | { 887 | std::stringstream ss; 888 | 889 | std::string confirm = " Confirm " + std::string(directoryMenu.getCurrentDirectory()) + "\n\n"; 890 | std::string cancel = " Cancel\n"; 891 | std::string prevDir = ((selection == 0) ? "/ Prev Directory\n" : " Prev Directory\n"); 892 | 893 | // " (" + std::to_string(selection) + "/" + std::to_string(renderMenuItems.size() - 1) + ")" 894 | 895 | ss << (selection == 0 ? "Please press DOWN or UP" : " Open " + std::to_string(selection) + ". " + std::get<0>((renderMenuItems)[selection])) + "\n" << confirm << prevDir << cancel; 896 | str += ss.str(); 897 | } 898 | else if (menuSystem.getFooter(currentMenuItems) != nullptr) 899 | str += "\n" + *(menuSystem.getFooter(currentMenuItems)); 900 | 901 | C2D_TextParse(&dynText, selectorBuf, str.c_str()); 902 | C2D_TextOptimize(&dynText); 903 | C2D_DrawText(&dynText, 0, 16.0f, 16.0f, 0.5f, size, size); 904 | C3D_FrameEnd(0); 905 | } 906 | 907 | static SwkbdCallbackResult KeyboardCallback(void *user, const char **ppMessage, const char *text, size_t textlen) 908 | { 909 | // if (strstr(text, "lenny")) 910 | // { 911 | // *ppMessage = "Nice try but I'm not letting you use that meme right now"; 912 | // return SWKBD_CALLBACK_CONTINUE; 913 | // } 914 | 915 | std::regex regex("^[a-zA-Z0-9_]+$"); 916 | if (!(std::string(text).length() <= 32 && std::regex_match(text, regex))) 917 | { 918 | *ppMessage = "Please enter a valid ID (alphanumeric and underscores only)"; 919 | return SWKBD_CALLBACK_CONTINUE; 920 | } 921 | 922 | return SWKBD_CALLBACK_OK; 923 | } 924 | 925 | std::string SystemCore::openKeyboard(std::string placeholder, std::string initialText) 926 | { 927 | 928 | static SwkbdState swkbd; 929 | static char mybuf[60]; 930 | SwkbdButton button = SWKBD_BUTTON_NONE; 931 | 932 | shouldKeyboardBeOpen = true; 933 | swkbdInit(&swkbd, SWKBD_TYPE_WESTERN, 2, -1); 934 | swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, 0, 0); 935 | swkbdSetFeatures(&swkbd, SWKBD_DARKEN_TOP_SCREEN | SWKBD_ALLOW_HOME | SWKBD_ALLOW_RESET | SWKBD_ALLOW_POWER); 936 | swkbdSetFilterCallback(&swkbd, KeyboardCallback, NULL); 937 | 938 | if (placeholder != "") 939 | { 940 | swkbdSetHintText(&swkbd, placeholder.c_str()); 941 | } 942 | 943 | if (initialText != "") 944 | { 945 | swkbdSetInitialText(&swkbd, initialText.c_str()); 946 | } 947 | 948 | bool shouldQuit = false; 949 | mybuf[0] = 0; 950 | do 951 | { 952 | swkbdSetInitialText(&swkbd, mybuf); 953 | button = swkbdInputText(&swkbd, mybuf, sizeof(mybuf)); 954 | if (button != SWKBD_BUTTON_NONE) 955 | break; 956 | 957 | SwkbdResult res = swkbdGetResult(&swkbd); 958 | if (res == SWKBD_RESETPRESSED) 959 | { 960 | shouldQuit = true; 961 | aptSetChainloaderToSelf(); 962 | break; 963 | } 964 | else if (res != SWKBD_HOMEPRESSED && res != SWKBD_POWERPRESSED) 965 | break; 966 | 967 | shouldQuit = !aptMainLoop(); 968 | } while (!shouldQuit); 969 | 970 | std::string mybufAsString(mybuf); 971 | return trim(mybufAsString); 972 | } 973 | 974 | bool SystemCore::isHalted() 975 | { 976 | return halt; 977 | } 978 | 979 | std::string SystemCore::getVersion() 980 | { 981 | return "v" + std::to_string(VERSION_MAJOR) + "." + std::to_string(VERSION_MINOR) + "." + std::to_string(VERSION_MICRO); 982 | } 983 | 984 | void SystemCore::cleanExit() 985 | { 986 | // Delete the text buffers 987 | if (selectorBuf != nullptr) 988 | { 989 | C2D_TextBufDelete(selectorBuf); 990 | selectorBuf = nullptr; 991 | } 992 | 993 | previousMenus.clear(); 994 | 995 | previousMenus.shrink_to_fit(); 996 | 997 | // Deinitialise the scene 998 | selection = 0; 999 | 1000 | menuSystem.handleExit(); 1001 | 1002 | // safely remove mainMenuItems 1003 | 1004 | (*currentMenuItems).clear(); 1005 | (*currentMenuItems).shrink_to_fit(); 1006 | 1007 | mainMenuItems.clear(); 1008 | gameIDDirectoryMenuItems.clear(); 1009 | uploadMenuItems.clear(); 1010 | downloadMenuItems.clear(); 1011 | settingMenuItems.clear(); 1012 | uploadDirectoryMenuItems.clear(); 1013 | gameIDMenuItems.clear(); 1014 | saveSelectionMenuItems.clear(); 1015 | downloadGameMenuItems.clear(); 1016 | existingGameIDsMenuItems.clear(); 1017 | 1018 | networkSystem.cleanExit(); 1019 | 1020 | C2D_Fini(); 1021 | C3D_Fini(); 1022 | gfxExit(); 1023 | } --------------------------------------------------------------------------------