├── .github └── workflows │ └── build.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── data ├── cacert.pem ├── fa-solid-900.ttf └── ter-u32b.bdf ├── res ├── banner.png ├── icon.png ├── screenshot0.png ├── screenshot1.png ├── screenshot2.png ├── screenshot3.png ├── screenshot4.png └── screenshot5.png └── source ├── Gfx.cpp ├── Gfx.hpp ├── SDL_FontCache.c ├── SDL_FontCache.h ├── Screen.cpp ├── Screen.hpp ├── Utils.cpp ├── Utils.hpp ├── main.cpp ├── screens ├── AboutScreen.cpp ├── AboutScreen.hpp ├── DRXInfoScreen.cpp ├── DRXInfoScreen.hpp ├── GeneralScreen.cpp ├── GeneralScreen.hpp ├── MainScreen.cpp ├── MainScreen.hpp ├── MenuScreen.cpp ├── MenuScreen.hpp ├── StorageScreen.cpp ├── StorageScreen.hpp ├── SubmitScreen.cpp └── SubmitScreen.hpp └── system ├── MemoryDevice.cpp ├── MemoryDevice.hpp ├── OTP.cpp ├── OTP.hpp ├── SEEPROM.cpp └── SEEPROM.hpp /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-binary: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Build binary 11 | run: | 12 | docker run --rm -v ${PWD}:/project garyodernichts/wiiuident_builder make 13 | - uses: actions/upload-artifact@v4 14 | with: 15 | name: WiiUIdent 16 | path: | 17 | *.wuhb 18 | *.rpx 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /build 3 | *.elf 4 | *.rpx 5 | *.wuhb 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/wiiu-env/devkitppc:20241128 2 | 3 | COPY --from=ghcr.io/wiiu-env/libmocha:20240603 /artifacts $DEVKITPRO 4 | 5 | WORKDIR /project 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITPRO)),) 6 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | 11 | #------------------------------------------------------------------------------- 12 | # APP_NAME sets the long name of the application 13 | # APP_SHORTNAME sets the short name of the application 14 | # APP_AUTHOR sets the author of the application 15 | #------------------------------------------------------------------------------- 16 | APP_NAME := WiiUIdent 17 | APP_SHORTNAME := WiiUIdent 18 | APP_AUTHOR := GaryOderNichts 19 | APP_VERSION := 2.0 20 | DATABASE_URL := wiiu.gerbilsoft.com 21 | 22 | include $(DEVKITPRO)/wut/share/wut_rules 23 | 24 | #------------------------------------------------------------------------------- 25 | # TARGET is the name of the output 26 | # BUILD is the directory where object files & intermediate files will be placed 27 | # SOURCES is a list of directories containing source code 28 | # DATA is a list of directories containing data files 29 | # INCLUDES is a list of directories containing header files 30 | # CONTENT is the path to the bundled folder that will be mounted as /vol/content/ 31 | # ICON is the game icon, leave blank to use default rule 32 | # TV_SPLASH is the image displayed during bootup on the TV, leave blank to use default rule 33 | # DRC_SPLASH is the image displayed during bootup on the DRC, leave blank to use default rule 34 | #------------------------------------------------------------------------------- 35 | TARGET := wiiuident 36 | BUILD := build 37 | SOURCES := source source/screens source/system 38 | DATA := data 39 | INCLUDES := source include 40 | CONTENT := 41 | ICON := res/icon.png 42 | TV_SPLASH := 43 | DRC_SPLASH := 44 | 45 | #------------------------------------------------------------------------------- 46 | # options for code generation 47 | #------------------------------------------------------------------------------- 48 | CFLAGS := -Wall -O2 -ffunction-sections \ 49 | $(MACHDEP) 50 | 51 | CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -DAPP_VERSION=\"$(APP_VERSION)\" \ 52 | -DDATABASE_URL=\"$(DATABASE_URL)\" 53 | 54 | CXXFLAGS := $(CFLAGS) -std=gnu++20 55 | 56 | ASFLAGS := $(ARCH) 57 | LDFLAGS = $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) 58 | 59 | LIBS := -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lSDL2 -lSDL2_ttf -lfreetype -lharfbuzz -lfreetype -lpng -lbz2 -lz -lmocha -lwut 60 | 61 | #------------------------------------------------------------------------------- 62 | # list of directories containing libraries, this must be the top level 63 | # containing include and lib 64 | #------------------------------------------------------------------------------- 65 | LIBDIRS := $(PORTLIBS) $(WUT_ROOT) $(WUT_ROOT)/usr 66 | 67 | 68 | #------------------------------------------------------------------------------- 69 | # no real need to edit anything past this point unless you need to add additional 70 | # rules for different file extensions 71 | #------------------------------------------------------------------------------- 72 | ifneq ($(BUILD),$(notdir $(CURDIR))) 73 | #------------------------------------------------------------------------------- 74 | 75 | export OUTPUT := $(CURDIR)/$(TARGET) 76 | export TOPDIR := $(CURDIR) 77 | 78 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 79 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 80 | 81 | export DEPSDIR := $(CURDIR)/$(BUILD) 82 | 83 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 84 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 85 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 86 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 87 | 88 | #------------------------------------------------------------------------------- 89 | # use CXX for linking C++ projects, CC for standard C 90 | #------------------------------------------------------------------------------- 91 | ifeq ($(strip $(CPPFILES)),) 92 | #------------------------------------------------------------------------------- 93 | export LD := $(CC) 94 | #------------------------------------------------------------------------------- 95 | else 96 | #------------------------------------------------------------------------------- 97 | export LD := $(CXX) 98 | #------------------------------------------------------------------------------- 99 | endif 100 | #------------------------------------------------------------------------------- 101 | 102 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 103 | export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 104 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 105 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 106 | 107 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 108 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 109 | -I$(CURDIR)/$(BUILD) -I$(DEVKITPRO)/portlibs/wiiu/include/SDL2 110 | 111 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 112 | 113 | ifneq (,$(strip $(CONTENT))) 114 | export APP_CONTENT := $(TOPDIR)/$(CONTENT) 115 | endif 116 | 117 | ifneq (,$(strip $(ICON))) 118 | export APP_ICON := $(TOPDIR)/$(ICON) 119 | else ifneq (,$(wildcard $(TOPDIR)/$(TARGET).png)) 120 | export APP_ICON := $(TOPDIR)/$(TARGET).png 121 | else ifneq (,$(wildcard $(TOPDIR)/icon.png)) 122 | export APP_ICON := $(TOPDIR)/icon.png 123 | endif 124 | 125 | ifneq (,$(strip $(TV_SPLASH))) 126 | export APP_TV_SPLASH := $(TOPDIR)/$(TV_SPLASH) 127 | else ifneq (,$(wildcard $(TOPDIR)/tv-splash.png)) 128 | export APP_TV_SPLASH := $(TOPDIR)/tv-splash.png 129 | else ifneq (,$(wildcard $(TOPDIR)/splash.png)) 130 | export APP_TV_SPLASH := $(TOPDIR)/splash.png 131 | endif 132 | 133 | ifneq (,$(strip $(DRC_SPLASH))) 134 | export APP_DRC_SPLASH := $(TOPDIR)/$(DRC_SPLASH) 135 | else ifneq (,$(wildcard $(TOPDIR)/drc-splash.png)) 136 | export APP_DRC_SPLASH := $(TOPDIR)/drc-splash.png 137 | else ifneq (,$(wildcard $(TOPDIR)/splash.png)) 138 | export APP_DRC_SPLASH := $(TOPDIR)/splash.png 139 | endif 140 | 141 | .PHONY: $(BUILD) clean all 142 | 143 | #------------------------------------------------------------------------------- 144 | all: $(BUILD) 145 | 146 | $(BUILD): 147 | @[ -d $@ ] || mkdir -p $@ 148 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 149 | 150 | #------------------------------------------------------------------------------- 151 | clean: 152 | @echo clean ... 153 | @rm -fr $(BUILD) $(TARGET).wuhb $(TARGET).rpx $(TARGET).elf 154 | 155 | #------------------------------------------------------------------------------- 156 | else 157 | .PHONY: all 158 | 159 | DEPENDS := $(OFILES:.o=.d) 160 | 161 | #------------------------------------------------------------------------------- 162 | # main targets 163 | #------------------------------------------------------------------------------- 164 | all : $(OUTPUT).wuhb 165 | 166 | $(OUTPUT).wuhb : $(OUTPUT).rpx 167 | $(OUTPUT).rpx : $(OUTPUT).elf 168 | $(OUTPUT).elf : $(OFILES) 169 | 170 | $(OFILES_SRC) : $(HFILES_BIN) 171 | 172 | #------------------------------------------------------------------------------- 173 | # you need a rule like this for each extension you use as binary data 174 | #------------------------------------------------------------------------------- 175 | %.bin.o %_bin.h : %.bin 176 | #------------------------------------------------------------------------------- 177 | @echo $(notdir $<) 178 | @$(bin2o) 179 | 180 | #------------------------------------------------------------------------------- 181 | %.ttf.o %_ttf.h : %.ttf 182 | #------------------------------------------------------------------------------- 183 | @echo $(notdir $<) 184 | @$(bin2o) 185 | 186 | #------------------------------------------------------------------------------- 187 | %.bdf.o %_bdf.h : %.bdf 188 | #------------------------------------------------------------------------------- 189 | @echo $(notdir $<) 190 | @$(bin2o) 191 | 192 | #------------------------------------------------------------------------------- 193 | %.pem.o %_pem.h : %.pem 194 | #------------------------------------------------------------------------------- 195 | @echo $(notdir $<) 196 | @$(bin2o) 197 | 198 | -include $(DEPENDS) 199 | 200 | #------------------------------------------------------------------------------- 201 | endif 202 | #------------------------------------------------------------------------------- 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](res/banner.png) 2 | A Wii U Identification homebrew, which display information about console hardware components, software/hardware versions and much more. 3 | 4 | > :information_source: **WiiUIdent requires the [MochaPayload](https://github.com/wiiu-env/MochaPayload)!** 5 | > Make sure to update to [Aroma](https://aroma.foryour.cafe) or [Tiramisu](https://tiramisu.foryour.cafe), in order to use this application. 6 | 7 | ## Features 8 | WiiUIdent currently displays: 9 | 10 | - Identification 11 | - Model 12 | - Serial 13 | - Production Data 14 | - Keyset 15 | - Hardware 16 | - Type 17 | - Version 18 | - Board Type 19 | - SATA Device 20 | - DDR3 Size/Speed/Vendor 21 | - Region 22 | - Product Area 23 | - Game Region 24 | - Versions 25 | - System Version 26 | - Boot1 Version 27 | - MLC/SD Card information 28 | - Type 29 | - Manufacturer 30 | - Product Name 31 | - Product Revision 32 | - Production Date (MLC only) 33 | - Size 34 | - CID/CSD 35 | - DRC/DRH information 36 | - Running Version 37 | - Active Version 38 | - Board Version 39 | - Region 40 | - Ext IDs 41 | 42 | ## System Database 43 | WiiUIdent comes with an option to optionally upload system information to a database. This allows collecting various statistics about Wii U consoles. 44 | After submitting your data, your system information will be added to the database with a System ID, which will be displayed on the console. 45 | The database is publicly accessible but personally identifying information will be kept confidential. 46 | [The database can be found here!](https://wiiu.gerbilsoft.com/) 47 | 48 | ## Screenshots 49 | ![](res/screenshot0.png) 50 | ![](res/screenshot1.png) 51 | ![](res/screenshot2.png) 52 | ![](res/screenshot3.png) 53 | ![](res/screenshot4.png) 54 | ![](res/screenshot5.png) 55 | 56 | ## Building 57 | For building you need: 58 | - [wut](https://github.com/devkitPro/wut) 59 | - [libmocha](https://github.com/wiiu-env/libmocha) 60 | - [wiiu-sdl2](https://github.com/GaryOderNichts/SDL/tree/wiiu-sdl2-2.26) 61 | - wiiu-sdl2_ttf 62 | - wiiu-curl 63 | - wiiu-mbedtls 64 | 65 | You can also build WiiUIdent using docker: 66 | ```bash 67 | # Build docker image (only needed once) 68 | docker build . -t wiiuident_builder 69 | 70 | # make 71 | docker run -it --rm -v ${PWD}:/project wiiuident_builder make 72 | 73 | # make clean 74 | docker run -it --rm -v ${PWD}:/project wiiuident_builder make clean 75 | ``` 76 | A pre-built container is available as `garyodernichts/wiiuident_builder`. 77 | 78 | ## Additional Credits 79 | - [@GerbilSoft](https://github.com/GerbilSoft) for providing the System Database and the "System Information" implementation in the `recovery_menu`. 80 | - [FontAwesome](https://fontawesome.com/) for the icons. 81 | - [Terminus Font](https://terminus-font.sourceforge.net/) for the monospace font. 82 | -------------------------------------------------------------------------------- /data/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaryOderNichts/WiiUIdent/b39f7db86ff8bd588fbe40c44ef2a96136038fd5/data/fa-solid-900.ttf -------------------------------------------------------------------------------- /res/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaryOderNichts/WiiUIdent/b39f7db86ff8bd588fbe40c44ef2a96136038fd5/res/banner.png -------------------------------------------------------------------------------- /res/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaryOderNichts/WiiUIdent/b39f7db86ff8bd588fbe40c44ef2a96136038fd5/res/icon.png -------------------------------------------------------------------------------- /res/screenshot0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaryOderNichts/WiiUIdent/b39f7db86ff8bd588fbe40c44ef2a96136038fd5/res/screenshot0.png -------------------------------------------------------------------------------- /res/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaryOderNichts/WiiUIdent/b39f7db86ff8bd588fbe40c44ef2a96136038fd5/res/screenshot1.png -------------------------------------------------------------------------------- /res/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaryOderNichts/WiiUIdent/b39f7db86ff8bd588fbe40c44ef2a96136038fd5/res/screenshot2.png -------------------------------------------------------------------------------- /res/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaryOderNichts/WiiUIdent/b39f7db86ff8bd588fbe40c44ef2a96136038fd5/res/screenshot3.png -------------------------------------------------------------------------------- /res/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaryOderNichts/WiiUIdent/b39f7db86ff8bd588fbe40c44ef2a96136038fd5/res/screenshot4.png -------------------------------------------------------------------------------- /res/screenshot5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaryOderNichts/WiiUIdent/b39f7db86ff8bd588fbe40c44ef2a96136038fd5/res/screenshot5.png -------------------------------------------------------------------------------- /source/Gfx.cpp: -------------------------------------------------------------------------------- 1 | #include "Gfx.hpp" 2 | #include "SDL_FontCache.h" 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace 13 | { 14 | 15 | SDL_Window* window = nullptr; 16 | 17 | SDL_Renderer* renderer = nullptr; 18 | 19 | void* fontData = nullptr; 20 | 21 | uint32_t fontSize = 0; 22 | 23 | std::map fontMap; 24 | 25 | FC_Font* monospaceFont = nullptr; 26 | 27 | TTF_Font* iconFont = nullptr; 28 | 29 | std::map iconCache; 30 | 31 | FC_Font* GetFontForSize(int size) 32 | { 33 | if (fontMap.contains(size)) { 34 | return fontMap[size]; 35 | } 36 | 37 | FC_Font* font = FC_CreateFont(); 38 | if (!font) { 39 | return font; 40 | } 41 | 42 | if (!FC_LoadFont_RW(font, renderer, SDL_RWFromMem(fontData, fontSize), 1, size, Gfx::COLOR_BLACK, TTF_STYLE_NORMAL)) { 43 | FC_FreeFont(font); 44 | return nullptr; 45 | } 46 | 47 | fontMap.insert({size, font}); 48 | return font; 49 | } 50 | 51 | SDL_Texture* LoadIcon(Uint16 icon) 52 | { 53 | if (iconCache.contains(icon)) { 54 | return iconCache[icon]; 55 | } 56 | 57 | SDL_Surface* iconSurface = TTF_RenderGlyph_Blended(iconFont, icon, Gfx::COLOR_WHITE); 58 | if (!iconSurface) { 59 | return nullptr; 60 | } 61 | 62 | SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, iconSurface); 63 | SDL_FreeSurface(iconSurface); 64 | if (!texture) { 65 | return nullptr; 66 | } 67 | 68 | iconCache.insert({icon, texture}); 69 | return texture; 70 | } 71 | 72 | } 73 | 74 | namespace Gfx 75 | { 76 | 77 | bool Init() 78 | { 79 | if (SDL_Init(SDL_INIT_VIDEO) < 0) { 80 | return false; 81 | } 82 | 83 | window = SDL_CreateWindow("WiiUIdent", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, 0); 84 | if (!window) { 85 | OSReport("SDL_CreateWindow failed\n"); 86 | return false; 87 | } 88 | 89 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); 90 | if (!renderer) { 91 | OSReport("SDL_CreateRenderer failed\n"); 92 | SDL_DestroyWindow(window); 93 | window = nullptr; 94 | return false; 95 | } 96 | 97 | if (!OSGetSharedData(OS_SHAREDDATATYPE_FONT_STANDARD, 0, &fontData, &fontSize)) { 98 | OSReport("OSGetSharedData failed\n"); 99 | return false; 100 | } 101 | 102 | TTF_Init(); 103 | 104 | monospaceFont = FC_CreateFont(); 105 | if (!monospaceFont) { 106 | return false; 107 | } 108 | 109 | if (!FC_LoadFont_RW(monospaceFont, renderer, SDL_RWFromMem((void*)ter_u32b_bdf, ter_u32b_bdf_size), 1, 32, Gfx::COLOR_BLACK, TTF_STYLE_NORMAL)) { 110 | FC_FreeFont(monospaceFont); 111 | return false; 112 | } 113 | 114 | // icons @256 should be large enough for our needs 115 | iconFont = TTF_OpenFontRW(SDL_RWFromMem((void*)fa_solid_900_ttf, fa_solid_900_ttf_size), 1, 256); 116 | if (!iconFont) { 117 | return false; 118 | } 119 | 120 | return true; 121 | } 122 | 123 | void Shutdown() 124 | { 125 | for (const auto& [key, value] : fontMap) { 126 | FC_FreeFont(value); 127 | } 128 | 129 | for (const auto& [key, value] : iconCache) { 130 | SDL_DestroyTexture(value); 131 | } 132 | 133 | FC_FreeFont(monospaceFont); 134 | TTF_CloseFont(iconFont); 135 | TTF_Quit(); 136 | SDL_DestroyRenderer(renderer); 137 | SDL_DestroyWindow(window); 138 | SDL_Quit(); 139 | } 140 | 141 | void Clear(SDL_Color color) 142 | { 143 | SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); 144 | SDL_RenderClear(renderer); 145 | } 146 | 147 | void Render() 148 | { 149 | SDL_RenderPresent(renderer); 150 | } 151 | 152 | void DrawRectFilled(int x, int y, int w, int h, SDL_Color color) 153 | { 154 | SDL_Rect rect{x, y, w, h}; 155 | SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); 156 | SDL_RenderFillRect(renderer, &rect); 157 | } 158 | 159 | void DrawRect(int x, int y, int w, int h, int borderSize, SDL_Color color) 160 | { 161 | DrawRectFilled(x, y, w, borderSize, color); 162 | DrawRectFilled(x, y + h - borderSize, w, borderSize, color); 163 | DrawRectFilled(x, y, borderSize, h, color); 164 | DrawRectFilled(x + w - borderSize, y, borderSize, h, color); 165 | } 166 | 167 | void DrawIcon(int x, int y, int size, SDL_Color color, Uint16 icon, AlignFlags align, double angle) 168 | { 169 | SDL_Texture* iconTex = LoadIcon(icon); 170 | if (!iconTex) { 171 | return; 172 | } 173 | 174 | SDL_SetTextureColorMod(iconTex, color.r, color.g, color.b); 175 | SDL_SetTextureAlphaMod(iconTex, color.a); 176 | 177 | int w, h; 178 | SDL_QueryTexture(iconTex, nullptr, nullptr, &w, &h); 179 | 180 | SDL_Rect rect; 181 | rect.x = x; 182 | rect.y = y; 183 | // scale the width based on hight to keep AR 184 | rect.w = (int) (((float) w / h) * size); 185 | rect.h = size; 186 | 187 | if (align & ALIGN_RIGHT) { 188 | rect.x -= rect.w; 189 | } else if (align & ALIGN_HORIZONTAL) { 190 | rect.x -= rect.w / 2; 191 | } 192 | 193 | if (align & ALIGN_BOTTOM) { 194 | rect.y -= rect.h; 195 | } else if (align & ALIGN_VERTICAL) { 196 | rect.y -= rect.h / 2; 197 | } 198 | 199 | // draw the icon 200 | if (angle) { 201 | SDL_RenderCopyEx(renderer, iconTex, nullptr, &rect, angle, nullptr, SDL_FLIP_NONE); 202 | } else { 203 | SDL_RenderCopy(renderer, iconTex, nullptr, &rect); 204 | } 205 | } 206 | 207 | int GetIconWidth(int size, Uint16 icon) 208 | { 209 | SDL_Texture* iconTex = LoadIcon(icon); 210 | if (!iconTex) { 211 | return 0; 212 | } 213 | 214 | int w, h; 215 | SDL_QueryTexture(iconTex, nullptr, nullptr, &w, &h); 216 | 217 | return (int) (((float) w / h) * size); 218 | } 219 | 220 | void Print(int x, int y, int size, SDL_Color color, std::string text, AlignFlags align, bool monospace) 221 | { 222 | FC_Font* font = monospace ? monospaceFont : GetFontForSize(size); 223 | if (!font) { 224 | return; 225 | } 226 | 227 | FC_Effect effect; 228 | effect.color = color; 229 | 230 | // scale monospace font based on size 231 | if (monospace) { 232 | effect.scale = FC_MakeScale(size / 28.0f, size / 28.0f); 233 | // TODO figure out how to center this properly 234 | y += 5; 235 | } else { 236 | effect.scale = FC_MakeScale(1,1); 237 | } 238 | 239 | if (align & ALIGN_LEFT) { 240 | effect.alignment = FC_ALIGN_LEFT; 241 | } else if (align & ALIGN_RIGHT) { 242 | effect.alignment = FC_ALIGN_RIGHT; 243 | } else if (align & ALIGN_HORIZONTAL) { 244 | effect.alignment = FC_ALIGN_CENTER; 245 | } else { 246 | // left by default 247 | effect.alignment = FC_ALIGN_LEFT; 248 | } 249 | 250 | if (align & ALIGN_BOTTOM) { 251 | y -= GetTextHeight(size, text, monospace); 252 | } else if (align & ALIGN_VERTICAL) { 253 | y -= GetTextHeight(size, text, monospace) / 2; 254 | } 255 | 256 | FC_DrawEffect(font, renderer, x, y, effect, "%s", text.c_str()); 257 | } 258 | 259 | int GetTextWidth(int size, std::string text, bool monospace) 260 | { 261 | FC_Font* font = monospace ? monospaceFont : GetFontForSize(size); 262 | if (!font) { 263 | return 0; 264 | } 265 | 266 | float scale = monospace ? (size / 28.0f) : 1.0f; 267 | 268 | return FC_GetWidth(font, "%s", text.c_str()) * scale; 269 | } 270 | 271 | int GetTextHeight(int size, std::string text, bool monospace) 272 | { 273 | // TODO this doesn't work nicely with monospace yet 274 | monospace = false; 275 | 276 | FC_Font* font = monospace ? monospaceFont : GetFontForSize(size); 277 | if (!font) { 278 | return 0; 279 | } 280 | 281 | float scale = monospace ? (size / 28.0f) : 1.0f; 282 | 283 | return FC_GetHeight(GetFontForSize(size), "%s", text.c_str()) * scale; 284 | } 285 | 286 | } 287 | 288 | -------------------------------------------------------------------------------- /source/Gfx.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Gfx 7 | { 8 | 9 | constexpr uint32_t SCREEN_WIDTH = 1920; 10 | constexpr uint32_t SCREEN_HEIGHT = 1080; 11 | 12 | constexpr SDL_Color COLOR_BLACK = { 0x00, 0x00, 0x00, 0xff }; 13 | constexpr SDL_Color COLOR_WHITE = { 0xff, 0xff, 0xff, 0xff }; 14 | constexpr SDL_Color COLOR_BACKGROUND = { 0x1b, 0x1c, 0x20, 0xff }; 15 | constexpr SDL_Color COLOR_ALT_BACKGROUND = { 0x33, 0x34, 0x39, 0xff }; 16 | constexpr SDL_Color COLOR_HIGHLIGHTED = { 0x00, 0x91, 0xea, 0xff }; 17 | constexpr SDL_Color COLOR_TEXT = { 0xf8, 0xf8, 0xf8, 0xff }; 18 | constexpr SDL_Color COLOR_ALT_TEXT = { 0xb0, 0xb0, 0xb0, 0xff }; 19 | constexpr SDL_Color COLOR_ACCENT = { 0x32, 0xe6, 0xa6, 0xff }; 20 | constexpr SDL_Color COLOR_ALT_ACCENT = { 0x22, 0xb3, 0x7d, 0xff }; 21 | constexpr SDL_Color COLOR_BARS = { 0x2f, 0x3f, 0x38, 0xff }; 22 | constexpr SDL_Color COLOR_ERROR = { 0xff, 0x33, 0x33, 0xff }; 23 | constexpr SDL_Color COLOR_WIIU = { 0x00, 0x95, 0xc7, 0xff }; 24 | 25 | enum AlignFlags { 26 | ALIGN_LEFT = 1 << 0, 27 | ALIGN_RIGHT = 1 << 1, 28 | ALIGN_HORIZONTAL = 1 << 2, 29 | ALIGN_TOP = 1 << 3, 30 | ALIGN_BOTTOM = 1 << 4, 31 | ALIGN_VERTICAL = 1 << 5, 32 | ALIGN_CENTER = ALIGN_HORIZONTAL | ALIGN_VERTICAL, 33 | }; 34 | 35 | static constexpr inline AlignFlags operator|(AlignFlags lhs, AlignFlags rhs) { 36 | return static_cast(static_cast(lhs) | static_cast(rhs)); 37 | } 38 | 39 | bool Init(); 40 | 41 | void Shutdown(); 42 | 43 | void Clear(SDL_Color color); 44 | 45 | void Render(); 46 | 47 | void DrawRectFilled(int x, int y, int w, int h, SDL_Color color); 48 | 49 | void DrawRect(int x, int y, int w, int h, int borderSize, SDL_Color color); 50 | 51 | void DrawIcon(int x, int y, int size, SDL_Color color, Uint16 icon, AlignFlags align = ALIGN_CENTER, double angle = 0.0); 52 | 53 | int GetIconWidth(int size, Uint16 icon); 54 | 55 | static inline int GetIconHeight(int size, Uint16 icon) { return size; } 56 | 57 | void Print(int x, int y, int size, SDL_Color color, std::string text, AlignFlags align = ALIGN_LEFT | ALIGN_TOP, bool monospace = false); 58 | 59 | int GetTextWidth(int size, std::string text, bool monospace = false); 60 | 61 | int GetTextHeight(int size, std::string text, bool monospace = false); 62 | 63 | } 64 | -------------------------------------------------------------------------------- /source/SDL_FontCache.c: -------------------------------------------------------------------------------- 1 | /* 2 | SDL_FontCache: A font cache for SDL and SDL_ttf 3 | by Jonathan Dearborn 4 | 5 | See SDL_FontCache.h for license info. 6 | */ 7 | 8 | #include "SDL_FontCache.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | // Visual C does not support static inline 15 | #ifndef static_inline 16 | #ifdef _MSC_VER 17 | #define static_inline static 18 | #else 19 | #define static_inline static inline 20 | #endif 21 | #endif 22 | 23 | #if SDL_VERSION_ATLEAST(2,0,0) 24 | #define FC_GET_ALPHA(sdl_color) ((sdl_color).a) 25 | #else 26 | #define FC_GET_ALPHA(sdl_color) ((sdl_color).unused) 27 | #endif 28 | 29 | // Need SDL_RenderIsClipEnabled() for proper clipping support 30 | #if SDL_VERSION_ATLEAST(2,0,4) 31 | #define ENABLE_SDL_CLIPPING 32 | #endif 33 | 34 | #define FC_MIN(a,b) ((a) < (b)? (a) : (b)) 35 | #define FC_MAX(a,b) ((a) > (b)? (a) : (b)) 36 | 37 | 38 | // vsnprintf replacement from Valentin Milea: 39 | // http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 40 | #if defined(_MSC_VER) && _MSC_VER < 1900 41 | 42 | #define snprintf c99_snprintf 43 | #define vsnprintf c99_vsnprintf 44 | 45 | __inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) 46 | { 47 | int count = -1; 48 | 49 | if (size != 0) 50 | count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); 51 | if (count == -1) 52 | count = _vscprintf(format, ap); 53 | 54 | return count; 55 | } 56 | 57 | __inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...) 58 | { 59 | int count; 60 | va_list ap; 61 | 62 | va_start(ap, format); 63 | count = c99_vsnprintf(outBuf, size, format, ap); 64 | va_end(ap); 65 | 66 | return count; 67 | } 68 | 69 | #endif 70 | 71 | 72 | #define FC_EXTRACT_VARARGS(buffer, start_args) \ 73 | { \ 74 | va_list lst; \ 75 | va_start(lst, start_args); \ 76 | vsnprintf(buffer, fc_buffer_size, start_args, lst); \ 77 | va_end(lst); \ 78 | } 79 | 80 | // Extra pixels of padding around each glyph to avoid linear filtering artifacts 81 | #define FC_CACHE_PADDING 1 82 | 83 | 84 | 85 | static Uint8 has_clip(FC_Target* dest) 86 | { 87 | #ifdef FC_USE_SDL_GPU 88 | return dest->use_clip_rect; 89 | #elif defined(ENABLE_SDL_CLIPPING) 90 | return SDL_RenderIsClipEnabled(dest); 91 | #else 92 | return 0; 93 | #endif 94 | } 95 | 96 | static FC_Rect get_clip(FC_Target* dest) 97 | { 98 | #ifdef FC_USE_SDL_GPU 99 | return dest->clip_rect; 100 | #elif defined(ENABLE_SDL_CLIPPING) 101 | SDL_Rect r; 102 | SDL_RenderGetClipRect(dest, &r); 103 | return r; 104 | #else 105 | SDL_Rect r = {0, 0, 0, 0}; 106 | return r; 107 | #endif 108 | } 109 | 110 | static void set_clip(FC_Target* dest, FC_Rect* rect) 111 | { 112 | #ifdef FC_USE_SDL_GPU 113 | if(rect != NULL) 114 | GPU_SetClipRect(dest, *rect); 115 | else 116 | GPU_UnsetClip(dest); 117 | #elif defined(ENABLE_SDL_CLIPPING) 118 | SDL_RenderSetClipRect(dest, rect); 119 | #endif 120 | } 121 | 122 | static void set_color(FC_Image* src, Uint8 r, Uint8 g, Uint8 b, Uint8 a) 123 | { 124 | #ifdef FC_USE_SDL_GPU 125 | GPU_SetRGBA(src, r, g, b, a); 126 | #else 127 | SDL_SetTextureColorMod(src, r, g, b); 128 | SDL_SetTextureAlphaMod(src, a); 129 | #endif 130 | } 131 | 132 | 133 | 134 | static char* new_concat(const char* a, const char* b) 135 | { 136 | // Create new buffer 137 | unsigned int size = strlen(a) + strlen(b); 138 | char* new_string = (char*)malloc(size+1); 139 | 140 | // Concatenate strings in the new buffer 141 | strcpy(new_string, a); 142 | strcat(new_string, b); 143 | 144 | return new_string; 145 | } 146 | 147 | static char* replace_concat(char** a, const char* b) 148 | { 149 | char* new_string = new_concat(*a, b); 150 | free(*a); 151 | *a = new_string; 152 | return *a; 153 | } 154 | 155 | 156 | // Width of a tab in units of the space width (sorry, no tab alignment!) 157 | static unsigned int fc_tab_width = 4; 158 | 159 | // Shared buffer for variadic text 160 | static char* fc_buffer = NULL; 161 | static unsigned int fc_buffer_size = 1024; 162 | 163 | static Uint8 fc_has_render_target_support = 0; 164 | 165 | // The number of fonts that has been created but not freed 166 | static int NUM_EXISTING_FONTS = 0; 167 | 168 | // Globals for GetString functions 169 | static char* ASCII_STRING = NULL; 170 | static char* LATIN_1_STRING = NULL; 171 | static char* ASCII_LATIN_1_STRING = NULL; 172 | 173 | char* FC_GetStringASCII(void) 174 | { 175 | if(ASCII_STRING == NULL) 176 | { 177 | int i; 178 | char c; 179 | ASCII_STRING = (char*)malloc(512); 180 | memset(ASCII_STRING, 0, 512); 181 | i = 0; 182 | c = 32; 183 | while(1) 184 | { 185 | ASCII_STRING[i] = c; 186 | if(c == 126) 187 | break; 188 | ++i; 189 | ++c; 190 | } 191 | } 192 | return U8_strdup(ASCII_STRING); 193 | } 194 | 195 | char* FC_GetStringLatin1(void) 196 | { 197 | if(LATIN_1_STRING == NULL) 198 | { 199 | int i; 200 | unsigned char c; 201 | LATIN_1_STRING = (char*)malloc(512); 202 | memset(LATIN_1_STRING, 0, 512); 203 | i = 0; 204 | c = 0xA0; 205 | while(1) 206 | { 207 | LATIN_1_STRING[i] = 0xC2; 208 | LATIN_1_STRING[i+1] = c; 209 | if(c == 0xBF) 210 | break; 211 | i += 2; 212 | ++c; 213 | } 214 | i += 2; 215 | c = 0x80; 216 | while(1) 217 | { 218 | LATIN_1_STRING[i] = 0xC3; 219 | LATIN_1_STRING[i+1] = c; 220 | if(c == 0xBF) 221 | break; 222 | i += 2; 223 | ++c; 224 | } 225 | } 226 | return U8_strdup(LATIN_1_STRING); 227 | } 228 | 229 | char* FC_GetStringASCII_Latin1(void) 230 | { 231 | if(ASCII_LATIN_1_STRING == NULL) 232 | ASCII_LATIN_1_STRING = new_concat(FC_GetStringASCII(), FC_GetStringLatin1()); 233 | 234 | return U8_strdup(ASCII_LATIN_1_STRING); 235 | } 236 | 237 | FC_Rect FC_MakeRect(float x, float y, float w, float h) 238 | { 239 | FC_Rect r = {x, y, w, h}; 240 | return r; 241 | } 242 | 243 | FC_Scale FC_MakeScale(float x, float y) 244 | { 245 | FC_Scale s = {x, y}; 246 | 247 | return s; 248 | } 249 | 250 | SDL_Color FC_MakeColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a) 251 | { 252 | SDL_Color c = {r, g, b, a}; 253 | 254 | return c; 255 | } 256 | 257 | FC_Effect FC_MakeEffect(FC_AlignEnum alignment, FC_Scale scale, SDL_Color color) 258 | { 259 | FC_Effect e; 260 | 261 | e.alignment = alignment; 262 | e.scale = scale; 263 | e.color = color; 264 | 265 | return e; 266 | } 267 | 268 | FC_GlyphData FC_MakeGlyphData(int cache_level, Sint16 x, Sint16 y, Uint16 w, Uint16 h) 269 | { 270 | FC_GlyphData gd; 271 | 272 | gd.rect.x = x; 273 | gd.rect.y = y; 274 | gd.rect.w = w; 275 | gd.rect.h = h; 276 | gd.cache_level = cache_level; 277 | 278 | return gd; 279 | } 280 | 281 | // Enough to hold all of the ascii characters and some. 282 | #define FC_DEFAULT_NUM_BUCKETS 300 283 | 284 | typedef struct FC_MapNode 285 | { 286 | Uint32 key; 287 | FC_GlyphData value; 288 | struct FC_MapNode* next; 289 | 290 | } FC_MapNode; 291 | 292 | typedef struct FC_Map 293 | { 294 | int num_buckets; 295 | FC_MapNode** buckets; 296 | } FC_Map; 297 | 298 | 299 | 300 | static FC_Map* FC_MapCreate(int num_buckets) 301 | { 302 | int i; 303 | FC_Map* map = (FC_Map*)malloc(sizeof(FC_Map)); 304 | 305 | map->num_buckets = num_buckets; 306 | map->buckets = (FC_MapNode**)malloc(num_buckets * sizeof(FC_MapNode*)); 307 | 308 | for(i = 0; i < num_buckets; ++i) 309 | { 310 | map->buckets[i] = NULL; 311 | } 312 | 313 | return map; 314 | } 315 | 316 | /*static void FC_MapClear(FC_Map* map) 317 | { 318 | int i; 319 | if(map == NULL) 320 | return; 321 | 322 | // Go through each bucket 323 | for(i = 0; i < map->num_buckets; ++i) 324 | { 325 | // Delete the nodes in order 326 | FC_MapNode* node = map->buckets[i]; 327 | while(node != NULL) 328 | { 329 | FC_MapNode* last = node; 330 | node = node->next; 331 | free(last); 332 | } 333 | // Set the bucket to empty 334 | map->buckets[i] = NULL; 335 | } 336 | }*/ 337 | 338 | static void FC_MapFree(FC_Map* map) 339 | { 340 | int i; 341 | if(map == NULL) 342 | return; 343 | 344 | // Go through each bucket 345 | for(i = 0; i < map->num_buckets; ++i) 346 | { 347 | // Delete the nodes in order 348 | FC_MapNode* node = map->buckets[i]; 349 | while(node != NULL) 350 | { 351 | FC_MapNode* last = node; 352 | node = node->next; 353 | free(last); 354 | } 355 | } 356 | 357 | free(map->buckets); 358 | free(map); 359 | } 360 | 361 | // Note: Does not handle duplicates in any special way. 362 | static FC_GlyphData* FC_MapInsert(FC_Map* map, Uint32 codepoint, FC_GlyphData glyph) 363 | { 364 | Uint32 index; 365 | FC_MapNode* node; 366 | if(map == NULL) 367 | return NULL; 368 | 369 | // Get index for bucket 370 | index = codepoint % map->num_buckets; 371 | 372 | // If this bucket is empty, create a node and return its value 373 | if(map->buckets[index] == NULL) 374 | { 375 | node = map->buckets[index] = (FC_MapNode*)malloc(sizeof(FC_MapNode)); 376 | node->key = codepoint; 377 | node->value = glyph; 378 | node->next = NULL; 379 | return &node->value; 380 | } 381 | 382 | for(node = map->buckets[index]; node != NULL; node = node->next) 383 | { 384 | // Find empty node and add a new one on. 385 | if(node->next == NULL) 386 | { 387 | node->next = (FC_MapNode*)malloc(sizeof(FC_MapNode)); 388 | node = node->next; 389 | 390 | node->key = codepoint; 391 | node->value = glyph; 392 | node->next = NULL; 393 | return &node->value; 394 | } 395 | } 396 | 397 | return NULL; 398 | } 399 | 400 | static FC_GlyphData* FC_MapFind(FC_Map* map, Uint32 codepoint) 401 | { 402 | Uint32 index; 403 | FC_MapNode* node; 404 | if(map == NULL) 405 | return NULL; 406 | 407 | // Get index for bucket 408 | index = codepoint % map->num_buckets; 409 | 410 | // Go through list until we find a match 411 | for(node = map->buckets[index]; node != NULL; node = node->next) 412 | { 413 | if(node->key == codepoint) 414 | return &node->value; 415 | } 416 | 417 | return NULL; 418 | } 419 | 420 | 421 | 422 | struct FC_Font 423 | { 424 | #ifndef FC_USE_SDL_GPU 425 | SDL_Renderer* renderer; 426 | #endif 427 | 428 | TTF_Font* ttf_source; // TTF_Font source of characters 429 | Uint8 owns_ttf_source; // Can we delete the TTF_Font ourselves? 430 | 431 | FC_FilterEnum filter; 432 | 433 | SDL_Color default_color; 434 | Uint16 height; 435 | 436 | Uint16 maxWidth; 437 | Uint16 baseline; 438 | int ascent; 439 | int descent; 440 | 441 | int lineSpacing; 442 | int letterSpacing; 443 | 444 | // Uses 32-bit (4-byte) Unicode codepoints to refer to each glyph 445 | // Codepoints are little endian (reversed from UTF-8) so that something like 0x00000005 is ASCII 5 and the map can be indexed by ASCII values 446 | FC_Map* glyphs; 447 | 448 | FC_GlyphData last_glyph; // Texture packing cursor 449 | int glyph_cache_size; 450 | int glyph_cache_count; 451 | FC_Image** glyph_cache; 452 | 453 | char* loading_string; 454 | 455 | }; 456 | 457 | // Private 458 | static FC_GlyphData* FC_PackGlyphData(FC_Font* font, Uint32 codepoint, Uint16 width, Uint16 maxWidth, Uint16 maxHeight); 459 | 460 | 461 | static FC_Rect FC_RenderLeft(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); 462 | static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); 463 | static FC_Rect FC_RenderRight(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); 464 | 465 | 466 | static_inline SDL_Surface* FC_CreateSurface32(Uint32 width, Uint32 height) 467 | { 468 | #if SDL_BYTEORDER == SDL_BIG_ENDIAN 469 | return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); 470 | #else 471 | return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); 472 | #endif 473 | } 474 | 475 | 476 | char* U8_alloc(unsigned int size) 477 | { 478 | char* result; 479 | if(size == 0) 480 | return NULL; 481 | 482 | result = (char*)malloc(size); 483 | result[0] = '\0'; 484 | 485 | return result; 486 | } 487 | 488 | void U8_free(char* string) 489 | { 490 | free(string); 491 | } 492 | 493 | char* U8_strdup(const char* string) 494 | { 495 | char* result; 496 | if(string == NULL) 497 | return NULL; 498 | 499 | result = (char*)malloc(strlen(string)+1); 500 | strcpy(result, string); 501 | 502 | return result; 503 | } 504 | 505 | int U8_strlen(const char* string) 506 | { 507 | int length = 0; 508 | if(string == NULL) 509 | return 0; 510 | 511 | while(*string != '\0') 512 | { 513 | string = U8_next(string); 514 | ++length; 515 | } 516 | 517 | return length; 518 | } 519 | 520 | int U8_charsize(const char* character) 521 | { 522 | if(character == NULL) 523 | return 0; 524 | 525 | if((unsigned char)*character <= 0x7F) 526 | return 1; 527 | else if((unsigned char)*character < 0xE0) 528 | return 2; 529 | else if((unsigned char)*character < 0xF0) 530 | return 3; 531 | else 532 | return 4; 533 | return 1; 534 | } 535 | 536 | int U8_charcpy(char* buffer, const char* source, int buffer_size) 537 | { 538 | int charsize; 539 | if(buffer == NULL || source == NULL || buffer_size < 1) 540 | return 0; 541 | 542 | charsize = U8_charsize(source); 543 | if(charsize > buffer_size) 544 | return 0; 545 | 546 | memcpy(buffer, source, charsize); 547 | return charsize; 548 | } 549 | 550 | const char* U8_next(const char* string) 551 | { 552 | return string + U8_charsize(string); 553 | } 554 | 555 | int U8_strinsert(char* string, int position, const char* source, int max_bytes) 556 | { 557 | int pos_u8char; 558 | int len; 559 | int add_len; 560 | int ulen; 561 | const char* string_start = string; 562 | 563 | if(string == NULL || source == NULL) 564 | return 0; 565 | 566 | len = strlen(string); 567 | add_len = strlen(source); 568 | ulen = U8_strlen(string); 569 | 570 | if(position == -1) 571 | position = ulen; 572 | 573 | if(position < 0 || position > ulen || len + add_len + 1 > max_bytes) 574 | return 0; 575 | 576 | // Move string pointer to the proper position 577 | pos_u8char = 0; 578 | while(*string != '\0' && pos_u8char < position) 579 | { 580 | string = (char*)U8_next(string); 581 | ++pos_u8char; 582 | } 583 | 584 | // Move the rest of the string out of the way 585 | memmove(string + add_len, string, len - (string - string_start) + 1); 586 | 587 | // Copy in the new characters 588 | memcpy(string, source, add_len); 589 | 590 | return 1; 591 | } 592 | 593 | void U8_strdel(char* string, int position) 594 | { 595 | if(string == NULL || position < 0) 596 | return; 597 | 598 | while(*string != '\0') 599 | { 600 | if(position == 0) 601 | { 602 | int chars_to_erase = U8_charsize(string); 603 | int remaining_bytes = strlen(string) + 1; 604 | memmove(string, string + chars_to_erase, remaining_bytes); 605 | break; 606 | } 607 | 608 | string = (char*)U8_next(string); 609 | --position; 610 | } 611 | } 612 | 613 | 614 | 615 | 616 | 617 | static_inline FC_Rect FC_RectUnion(FC_Rect A, FC_Rect B) 618 | { 619 | float x,x2,y,y2; 620 | x = FC_MIN(A.x, B.x); 621 | y = FC_MIN(A.y, B.y); 622 | x2 = FC_MAX(A.x+A.w, B.x+B.w); 623 | y2 = FC_MAX(A.y+A.h, B.y+B.h); 624 | { 625 | FC_Rect result = {x, y, FC_MAX(0, x2 - x), FC_MAX(0, y2 - y)}; 626 | return result; 627 | } 628 | } 629 | 630 | // Adapted from SDL_IntersectRect 631 | static_inline FC_Rect FC_RectIntersect(FC_Rect A, FC_Rect B) 632 | { 633 | FC_Rect result; 634 | float Amin, Amax, Bmin, Bmax; 635 | 636 | // Horizontal intersection 637 | Amin = A.x; 638 | Amax = Amin + A.w; 639 | Bmin = B.x; 640 | Bmax = Bmin + B.w; 641 | if(Bmin > Amin) 642 | Amin = Bmin; 643 | result.x = Amin; 644 | if(Bmax < Amax) 645 | Amax = Bmax; 646 | result.w = Amax - Amin > 0 ? Amax - Amin : 0; 647 | 648 | // Vertical intersection 649 | Amin = A.y; 650 | Amax = Amin + A.h; 651 | Bmin = B.y; 652 | Bmax = Bmin + B.h; 653 | if(Bmin > Amin) 654 | Amin = Bmin; 655 | result.y = Amin; 656 | if(Bmax < Amax) 657 | Amax = Bmax; 658 | result.h = Amax - Amin > 0 ? Amax - Amin : 0; 659 | 660 | return result; 661 | } 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | FC_Rect FC_DefaultRenderCallback(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale) 677 | { 678 | float w = srcrect->w * xscale; 679 | float h = srcrect->h * yscale; 680 | FC_Rect result; 681 | 682 | // FIXME: Why does the scaled offset look so wrong? 683 | #ifdef FC_USE_SDL_GPU 684 | { 685 | GPU_Rect r = *srcrect; 686 | GPU_BlitScale(src, &r, dest, x + xscale*r.w/2.0f, y + r.h/2.0f, xscale, yscale); 687 | } 688 | #else 689 | { 690 | SDL_RendererFlip flip = SDL_FLIP_NONE; 691 | if(xscale < 0) 692 | { 693 | xscale = -xscale; 694 | flip = (SDL_RendererFlip) ((int)flip | (int)SDL_FLIP_HORIZONTAL); 695 | } 696 | if(yscale < 0) 697 | { 698 | yscale = -yscale; 699 | flip = (SDL_RendererFlip) ((int)flip | (int)SDL_FLIP_VERTICAL); 700 | } 701 | 702 | SDL_Rect r = *srcrect; 703 | SDL_Rect dr = {(int)x, (int)y, (int)(xscale*r.w), (int)(yscale*r.h)}; 704 | SDL_RenderCopyEx(dest, src, &r, &dr, 0, NULL, flip); 705 | } 706 | #endif 707 | 708 | result.x = x; 709 | result.y = y; 710 | result.w = w; 711 | result.h = h; 712 | return result; 713 | } 714 | 715 | static FC_Rect (*fc_render_callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale) = &FC_DefaultRenderCallback; 716 | 717 | void FC_SetRenderCallback(FC_Rect (*callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale)) 718 | { 719 | if(callback == NULL) 720 | fc_render_callback = &FC_DefaultRenderCallback; 721 | else 722 | fc_render_callback = callback; 723 | } 724 | 725 | void FC_GetUTF8FromCodepoint(char* result, Uint32 codepoint) 726 | { 727 | char a, b, c, d; 728 | 729 | if(result == NULL) 730 | return; 731 | 732 | a = (codepoint >> 24) & 0xFF; 733 | b = (codepoint >> 16) & 0xFF; 734 | c = (codepoint >> 8) & 0xFF; 735 | d = codepoint & 0xFF; 736 | 737 | if(a == 0) 738 | { 739 | if(b == 0) 740 | { 741 | if(c == 0) 742 | { 743 | result[0] = d; 744 | result[1] = '\0'; 745 | } 746 | else 747 | { 748 | result[0] = c; 749 | result[1] = d; 750 | result[2] = '\0'; 751 | } 752 | } 753 | else 754 | { 755 | result[0] = b; 756 | result[1] = c; 757 | result[2] = d; 758 | result[3] = '\0'; 759 | } 760 | } 761 | else 762 | { 763 | result[0] = a; 764 | result[1] = b; 765 | result[2] = c; 766 | result[3] = d; 767 | result[4] = '\0'; 768 | } 769 | } 770 | 771 | Uint32 FC_GetCodepointFromUTF8(const char** c, Uint8 advance_pointer) 772 | { 773 | Uint32 result = 0; 774 | const char* str; 775 | if(c == NULL || *c == NULL) 776 | return 0; 777 | 778 | str = *c; 779 | if((unsigned char)*str <= 0x7F) 780 | result = *str; 781 | else if((unsigned char)*str < 0xE0) 782 | { 783 | result |= (unsigned char)(*str) << 8; 784 | result |= (unsigned char)(*(str+1)); 785 | if(advance_pointer) 786 | *c += 1; 787 | } 788 | else if((unsigned char)*str < 0xF0) 789 | { 790 | result |= (unsigned char)(*str) << 16; 791 | result |= (unsigned char)(*(str+1)) << 8; 792 | result |= (unsigned char)(*(str+2)); 793 | if(advance_pointer) 794 | *c += 2; 795 | } 796 | else 797 | { 798 | result |= (unsigned char)(*str) << 24; 799 | result |= (unsigned char)(*(str+1)) << 16; 800 | result |= (unsigned char)(*(str+2)) << 8; 801 | result |= (unsigned char)(*(str+3)); 802 | if(advance_pointer) 803 | *c += 3; 804 | } 805 | return result; 806 | } 807 | 808 | 809 | void FC_SetLoadingString(FC_Font* font, const char* string) 810 | { 811 | if(font == NULL) 812 | return; 813 | 814 | free(font->loading_string); 815 | font->loading_string = U8_strdup(string); 816 | } 817 | 818 | 819 | unsigned int FC_GetBufferSize(void) 820 | { 821 | return fc_buffer_size; 822 | } 823 | 824 | void FC_SetBufferSize(unsigned int size) 825 | { 826 | free(fc_buffer); 827 | if(size > 0) 828 | { 829 | fc_buffer_size = size; 830 | fc_buffer = (char*)malloc(fc_buffer_size); 831 | } 832 | else 833 | fc_buffer = (char*)malloc(fc_buffer_size); 834 | } 835 | 836 | 837 | unsigned int FC_GetTabWidth(void) 838 | { 839 | return fc_tab_width; 840 | } 841 | 842 | void FC_SetTabWidth(unsigned int width_in_spaces) 843 | { 844 | fc_tab_width = width_in_spaces; 845 | } 846 | 847 | 848 | 849 | 850 | 851 | // Constructors 852 | 853 | static void FC_Init(FC_Font* font) 854 | { 855 | if(font == NULL) 856 | return; 857 | 858 | #ifndef FC_USE_SDL_GPU 859 | font->renderer = NULL; 860 | #endif 861 | 862 | font->ttf_source = NULL; 863 | font->owns_ttf_source = 0; 864 | 865 | font->filter = FC_FILTER_NEAREST; 866 | 867 | font->default_color.r = 0; 868 | font->default_color.g = 0; 869 | font->default_color.b = 0; 870 | FC_GET_ALPHA(font->default_color) = 255; 871 | 872 | font->height = 0; // ascent+descent 873 | 874 | font->maxWidth = 0; 875 | font->baseline = 0; 876 | font->ascent = 0; 877 | font->descent = 0; 878 | 879 | font->lineSpacing = 0; 880 | font->letterSpacing = 0; 881 | 882 | // Give a little offset for when filtering/mipmaps are used. Depending on mipmap level, this will still not be enough. 883 | font->last_glyph.rect.x = FC_CACHE_PADDING; 884 | font->last_glyph.rect.y = FC_CACHE_PADDING; 885 | font->last_glyph.rect.w = 0; 886 | font->last_glyph.rect.h = 0; 887 | font->last_glyph.cache_level = 0; 888 | 889 | if(font->glyphs != NULL) 890 | FC_MapFree(font->glyphs); 891 | 892 | font->glyphs = FC_MapCreate(FC_DEFAULT_NUM_BUCKETS); 893 | 894 | font->glyph_cache_size = 3; 895 | font->glyph_cache_count = 0; 896 | 897 | 898 | font->glyph_cache = (FC_Image**)malloc(font->glyph_cache_size * sizeof(FC_Image*)); 899 | 900 | if (font->loading_string == NULL) 901 | font->loading_string = FC_GetStringASCII(); 902 | 903 | if(fc_buffer == NULL) 904 | fc_buffer = (char*)malloc(fc_buffer_size); 905 | } 906 | 907 | static Uint8 FC_GrowGlyphCache(FC_Font* font) 908 | { 909 | if(font == NULL) 910 | return 0; 911 | #ifdef FC_USE_SDL_GPU 912 | GPU_Image* new_level = GPU_CreateImage(font->height * 12, font->height * 12, GPU_FORMAT_RGBA); 913 | GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different 914 | #else 915 | SDL_Texture* new_level = SDL_CreateTexture(font->renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, font->height * 12, font->height * 12); 916 | #endif 917 | if(new_level == NULL || !FC_SetGlyphCacheLevel(font, font->glyph_cache_count, new_level)) 918 | { 919 | FC_Log("Error: SDL_FontCache ran out of packing space and could not add another cache level.\n"); 920 | #ifdef FC_USE_SDL_GPU 921 | GPU_FreeImage(new_level); 922 | #else 923 | SDL_DestroyTexture(new_level); 924 | #endif 925 | return 0; 926 | } 927 | // bug: we do not have the correct color here, this might be the wrong color! 928 | // , most functions use set_color_for_all_caches() 929 | // - for evading this bug, you must use FC_SetDefaultColor(), before using any draw functions 930 | set_color(new_level, font->default_color.r, font->default_color.g, font->default_color.b, FC_GET_ALPHA(font->default_color)); 931 | #ifndef FC_USE_SDL_GPU 932 | { 933 | Uint8 r, g, b, a; 934 | SDL_Texture* prev_target = SDL_GetRenderTarget(font->renderer); 935 | SDL_Rect prev_clip, prev_viewport; 936 | int prev_logicalw, prev_logicalh; 937 | Uint8 prev_clip_enabled; 938 | float prev_scalex, prev_scaley; 939 | // only backup if previous target existed (SDL will preserve them for the default target) 940 | if (prev_target) { 941 | prev_clip_enabled = has_clip(font->renderer); 942 | if (prev_clip_enabled) 943 | prev_clip = get_clip(font->renderer); 944 | SDL_RenderGetViewport(font->renderer, &prev_viewport); 945 | SDL_RenderGetScale(font->renderer, &prev_scalex, &prev_scaley); 946 | SDL_RenderGetLogicalSize(font->renderer, &prev_logicalw, &prev_logicalh); 947 | } 948 | SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND); 949 | SDL_SetRenderTarget(font->renderer, new_level); 950 | SDL_GetRenderDrawColor(font->renderer, &r, &g, &b, &a); 951 | SDL_SetRenderDrawColor(font->renderer, 0, 0, 0, 0); 952 | SDL_RenderClear(font->renderer); 953 | SDL_SetRenderDrawColor(font->renderer, r, g, b, a); 954 | SDL_SetRenderTarget(font->renderer, prev_target); 955 | if (prev_target) { 956 | if (prev_clip_enabled) 957 | set_clip(font->renderer, &prev_clip); 958 | if (prev_logicalw && prev_logicalh) 959 | SDL_RenderSetLogicalSize(font->renderer, prev_logicalw, prev_logicalh); 960 | else { 961 | SDL_RenderSetViewport(font->renderer, &prev_viewport); 962 | SDL_RenderSetScale(font->renderer, prev_scalex, prev_scaley); 963 | } 964 | } 965 | } 966 | #endif 967 | return 1; 968 | } 969 | 970 | Uint8 FC_UploadGlyphCache(FC_Font* font, int cache_level, SDL_Surface* data_surface) 971 | { 972 | if(font == NULL || data_surface == NULL) 973 | return 0; 974 | #ifdef FC_USE_SDL_GPU 975 | GPU_Image* new_level = GPU_CopyImageFromSurface(data_surface); 976 | GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different 977 | if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) 978 | GPU_SetImageFilter(new_level, GPU_FILTER_LINEAR); 979 | else 980 | GPU_SetImageFilter(new_level, GPU_FILTER_NEAREST); 981 | #else 982 | SDL_Texture* new_level; 983 | if(!fc_has_render_target_support) 984 | new_level = SDL_CreateTextureFromSurface(font->renderer, data_surface); 985 | else 986 | { 987 | // Must upload with render target enabled so we can put more glyphs on later 988 | SDL_Renderer* renderer = font->renderer; 989 | 990 | // Set filter mode for new texture 991 | char old_filter_mode[16]; // Save it so we can change the hint value in the meantime 992 | const char* old_filter_hint = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY); 993 | if(!old_filter_hint) 994 | old_filter_hint = "nearest"; 995 | snprintf(old_filter_mode, 16, "%s", old_filter_hint); 996 | 997 | if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) 998 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); 999 | else 1000 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); 1001 | 1002 | new_level = SDL_CreateTexture(renderer, data_surface->format->format, SDL_TEXTUREACCESS_TARGET, data_surface->w, data_surface->h); 1003 | SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND); 1004 | 1005 | // Reset filter mode for the temp texture 1006 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); 1007 | 1008 | { 1009 | Uint8 r, g, b, a; 1010 | SDL_Texture* temp = SDL_CreateTextureFromSurface(renderer, data_surface); 1011 | SDL_Texture* prev_target = SDL_GetRenderTarget(renderer); 1012 | SDL_Rect prev_clip, prev_viewport; 1013 | int prev_logicalw, prev_logicalh; 1014 | Uint8 prev_clip_enabled; 1015 | float prev_scalex, prev_scaley; 1016 | // only backup if previous target existed (SDL will preserve them for the default target) 1017 | if (prev_target) { 1018 | prev_clip_enabled = has_clip(renderer); 1019 | if (prev_clip_enabled) 1020 | prev_clip = get_clip(renderer); 1021 | SDL_RenderGetViewport(renderer, &prev_viewport); 1022 | SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley); 1023 | SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh); 1024 | } 1025 | SDL_SetTextureBlendMode(temp, SDL_BLENDMODE_NONE); 1026 | SDL_SetRenderTarget(renderer, new_level); 1027 | 1028 | SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); 1029 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); 1030 | SDL_RenderClear(renderer); 1031 | SDL_SetRenderDrawColor(renderer, r, g, b, a); 1032 | 1033 | SDL_RenderCopy(renderer, temp, NULL, NULL); 1034 | SDL_SetRenderTarget(renderer, prev_target); 1035 | if (prev_target) { 1036 | if (prev_clip_enabled) 1037 | set_clip(renderer, &prev_clip); 1038 | if (prev_logicalw && prev_logicalh) 1039 | SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh); 1040 | else { 1041 | SDL_RenderSetViewport(renderer, &prev_viewport); 1042 | SDL_RenderSetScale(renderer, prev_scalex, prev_scaley); 1043 | } 1044 | } 1045 | 1046 | SDL_DestroyTexture(temp); 1047 | } 1048 | 1049 | // Reset to the old filter value 1050 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, old_filter_mode); 1051 | 1052 | } 1053 | #endif 1054 | if(new_level == NULL || !FC_SetGlyphCacheLevel(font, cache_level, new_level)) 1055 | { 1056 | FC_Log("Error: SDL_FontCache ran out of packing space and could not add another cache level.\n"); 1057 | #ifdef FC_USE_SDL_GPU 1058 | GPU_FreeImage(new_level); 1059 | #else 1060 | SDL_DestroyTexture(new_level); 1061 | #endif 1062 | return 0; 1063 | } 1064 | return 1; 1065 | } 1066 | 1067 | static FC_GlyphData* FC_PackGlyphData(FC_Font* font, Uint32 codepoint, Uint16 width, Uint16 maxWidth, Uint16 maxHeight) 1068 | { 1069 | FC_Map* glyphs = font->glyphs; 1070 | FC_GlyphData* last_glyph = &font->last_glyph; 1071 | Uint16 height = font->height + FC_CACHE_PADDING; 1072 | 1073 | // TAB is special! 1074 | if(codepoint == '\t') 1075 | { 1076 | FC_GlyphData spaceGlyph; 1077 | FC_GetGlyphData(font, &spaceGlyph, ' '); 1078 | width = fc_tab_width * spaceGlyph.rect.w; 1079 | } 1080 | 1081 | if(last_glyph->rect.x + last_glyph->rect.w + width >= maxWidth - FC_CACHE_PADDING) 1082 | { 1083 | if(last_glyph->rect.y + height + height >= maxHeight - FC_CACHE_PADDING) 1084 | { 1085 | // Get ready to pack on the next cache level when it is ready 1086 | last_glyph->cache_level = font->glyph_cache_count; 1087 | last_glyph->rect.x = FC_CACHE_PADDING; 1088 | last_glyph->rect.y = FC_CACHE_PADDING; 1089 | last_glyph->rect.w = 0; 1090 | return NULL; 1091 | } 1092 | else 1093 | { 1094 | // Go to next row 1095 | last_glyph->rect.x = FC_CACHE_PADDING; 1096 | last_glyph->rect.y += height; 1097 | last_glyph->rect.w = 0; 1098 | } 1099 | } 1100 | 1101 | // Move to next space 1102 | last_glyph->rect.x += last_glyph->rect.w + 1 + FC_CACHE_PADDING; 1103 | last_glyph->rect.w = width; 1104 | 1105 | return FC_MapInsert(glyphs, codepoint, FC_MakeGlyphData(last_glyph->cache_level, last_glyph->rect.x, last_glyph->rect.y, last_glyph->rect.w, last_glyph->rect.h)); 1106 | } 1107 | 1108 | 1109 | FC_Image* FC_GetGlyphCacheLevel(FC_Font* font, int cache_level) 1110 | { 1111 | if(font == NULL || cache_level < 0 || cache_level > font->glyph_cache_count) 1112 | return NULL; 1113 | 1114 | return font->glyph_cache[cache_level]; 1115 | } 1116 | 1117 | Uint8 FC_SetGlyphCacheLevel(FC_Font* font, int cache_level, FC_Image* cache_texture) 1118 | { 1119 | if(font == NULL || cache_level < 0) 1120 | return 0; 1121 | 1122 | // Must be sequentially added 1123 | if(cache_level > font->glyph_cache_count + 1) 1124 | return 0; 1125 | 1126 | if(cache_level == font->glyph_cache_count) 1127 | { 1128 | font->glyph_cache_count++; 1129 | 1130 | // Grow cache? 1131 | if(font->glyph_cache_count > font->glyph_cache_size) 1132 | { 1133 | // Copy old cache to new one 1134 | int i; 1135 | FC_Image** new_cache; 1136 | new_cache = (FC_Image**)malloc(font->glyph_cache_count * sizeof(FC_Image*)); 1137 | for(i = 0; i < font->glyph_cache_size; ++i) 1138 | new_cache[i] = font->glyph_cache[i]; 1139 | 1140 | // Save new cache 1141 | free(font->glyph_cache); 1142 | font->glyph_cache_size = font->glyph_cache_count; 1143 | font->glyph_cache = new_cache; 1144 | } 1145 | } 1146 | 1147 | font->glyph_cache[cache_level] = cache_texture; 1148 | return 1; 1149 | } 1150 | 1151 | 1152 | FC_Font* FC_CreateFont(void) 1153 | { 1154 | FC_Font* font; 1155 | 1156 | font = (FC_Font*)malloc(sizeof(FC_Font)); 1157 | memset(font, 0, sizeof(FC_Font)); 1158 | 1159 | FC_Init(font); 1160 | ++NUM_EXISTING_FONTS; 1161 | 1162 | return font; 1163 | } 1164 | 1165 | 1166 | // Assume this many will be enough... 1167 | #define FC_LOAD_MAX_SURFACES 10 1168 | 1169 | #ifdef FC_USE_SDL_GPU 1170 | Uint8 FC_LoadFontFromTTF(FC_Font* font, TTF_Font* ttf, SDL_Color color) 1171 | #else 1172 | Uint8 FC_LoadFontFromTTF(FC_Font* font, SDL_Renderer* renderer, TTF_Font* ttf, SDL_Color color) 1173 | #endif 1174 | { 1175 | if(font == NULL || ttf == NULL) 1176 | return 0; 1177 | #ifndef FC_USE_SDL_GPU 1178 | if(renderer == NULL) 1179 | return 0; 1180 | #endif 1181 | 1182 | FC_ClearFont(font); 1183 | 1184 | 1185 | // Might as well check render target support here 1186 | #ifdef FC_USE_SDL_GPU 1187 | fc_has_render_target_support = GPU_IsFeatureEnabled(GPU_FEATURE_RENDER_TARGETS); 1188 | #else 1189 | SDL_RendererInfo info; 1190 | SDL_GetRendererInfo(renderer, &info); 1191 | fc_has_render_target_support = (info.flags & SDL_RENDERER_TARGETTEXTURE); 1192 | 1193 | font->renderer = renderer; 1194 | #endif 1195 | 1196 | font->ttf_source = ttf; 1197 | 1198 | //font->line_height = TTF_FontLineSkip(ttf); 1199 | font->height = TTF_FontHeight(ttf); 1200 | font->ascent = TTF_FontAscent(ttf); 1201 | font->descent = -TTF_FontDescent(ttf); 1202 | 1203 | // Some bug for certain fonts can result in an incorrect height. 1204 | if(font->height < font->ascent - font->descent) 1205 | font->height = font->ascent - font->descent; 1206 | 1207 | font->baseline = font->height - font->descent; 1208 | 1209 | font->default_color = color; 1210 | 1211 | { 1212 | SDL_Color white = {255, 255, 255, 255}; 1213 | SDL_Surface* glyph_surf; 1214 | char buff[5]; 1215 | const char* buff_ptr = buff; 1216 | const char* source_string; 1217 | Uint8 packed = 0; 1218 | 1219 | // Copy glyphs from the surface to the font texture and store the position data 1220 | // Pack row by row into a square texture 1221 | // Try figuring out dimensions that make sense for the font size. 1222 | unsigned int w = font->height*12; 1223 | unsigned int h = font->height*12; 1224 | SDL_Surface* surfaces[FC_LOAD_MAX_SURFACES]; 1225 | int num_surfaces = 1; 1226 | surfaces[0] = FC_CreateSurface32(w, h); 1227 | font->last_glyph.rect.x = FC_CACHE_PADDING; 1228 | font->last_glyph.rect.y = FC_CACHE_PADDING; 1229 | font->last_glyph.rect.w = 0; 1230 | font->last_glyph.rect.h = font->height; 1231 | 1232 | source_string = font->loading_string; 1233 | for(; *source_string != '\0'; source_string = U8_next(source_string)) 1234 | { 1235 | memset(buff, 0, 5); 1236 | if(!U8_charcpy(buff, source_string, 5)) 1237 | continue; 1238 | glyph_surf = TTF_RenderUTF8_Blended(ttf, buff, white); 1239 | if(glyph_surf == NULL) 1240 | continue; 1241 | 1242 | // Try packing. If it fails, create a new surface for the next cache level. 1243 | packed = (FC_PackGlyphData(font, FC_GetCodepointFromUTF8(&buff_ptr, 0), glyph_surf->w, surfaces[num_surfaces-1]->w, surfaces[num_surfaces-1]->h) != NULL); 1244 | if(!packed) 1245 | { 1246 | int i = num_surfaces-1; 1247 | if(num_surfaces >= FC_LOAD_MAX_SURFACES) 1248 | { 1249 | // Can't do any more! 1250 | FC_Log("SDL_FontCache error: Could not create enough cache surfaces to fit all of the loading string!\n"); 1251 | SDL_FreeSurface(glyph_surf); 1252 | break; 1253 | } 1254 | 1255 | // Upload the current surface to the glyph cache now so we can keep the cache level packing cursor up to date as we go. 1256 | FC_UploadGlyphCache(font, i, surfaces[i]); 1257 | SDL_FreeSurface(surfaces[i]); 1258 | #ifndef FC_USE_SDL_GPU 1259 | SDL_SetTextureBlendMode(font->glyph_cache[i], SDL_BLENDMODE_BLEND); 1260 | #endif 1261 | // Update the glyph cursor to the new cache level. We need to do this here because the actual cache lags behind our use of the packing above. 1262 | font->last_glyph.cache_level = num_surfaces; 1263 | 1264 | 1265 | surfaces[num_surfaces] = FC_CreateSurface32(w, h); 1266 | num_surfaces++; 1267 | } 1268 | 1269 | // Try packing for the new surface, then blit onto it. 1270 | if(packed || FC_PackGlyphData(font, FC_GetCodepointFromUTF8(&buff_ptr, 0), glyph_surf->w, surfaces[num_surfaces-1]->w, surfaces[num_surfaces-1]->h) != NULL) 1271 | { 1272 | SDL_SetSurfaceBlendMode(glyph_surf, SDL_BLENDMODE_NONE); 1273 | SDL_Rect srcRect = {0, 0, glyph_surf->w, glyph_surf->h}; 1274 | SDL_Rect destrect = font->last_glyph.rect; 1275 | SDL_BlitSurface(glyph_surf, &srcRect, surfaces[num_surfaces-1], &destrect); 1276 | } 1277 | 1278 | SDL_FreeSurface(glyph_surf); 1279 | } 1280 | 1281 | { 1282 | int i = num_surfaces-1; 1283 | FC_UploadGlyphCache(font, i, surfaces[i]); 1284 | SDL_FreeSurface(surfaces[i]); 1285 | #ifndef FC_USE_SDL_GPU 1286 | SDL_SetTextureBlendMode(font->glyph_cache[i], SDL_BLENDMODE_BLEND); 1287 | #endif 1288 | } 1289 | } 1290 | 1291 | return 1; 1292 | } 1293 | 1294 | 1295 | #ifdef FC_USE_SDL_GPU 1296 | Uint8 FC_LoadFont(FC_Font* font, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style) 1297 | #else 1298 | Uint8 FC_LoadFont(FC_Font* font, FC_Target* renderer, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style) 1299 | #endif 1300 | { 1301 | SDL_RWops* rwops; 1302 | 1303 | if(font == NULL) 1304 | return 0; 1305 | 1306 | rwops = SDL_RWFromFile(filename_ttf, "rb"); 1307 | 1308 | if(rwops == NULL) 1309 | { 1310 | FC_Log("Unable to open file for reading: %s \n", SDL_GetError()); 1311 | return 0; 1312 | } 1313 | 1314 | #ifdef FC_USE_SDL_GPU 1315 | return FC_LoadFont_RW(font, rwops, 1, pointSize, color, style); 1316 | #else 1317 | return FC_LoadFont_RW(font, renderer, rwops, 1, pointSize, color, style); 1318 | #endif 1319 | } 1320 | 1321 | #ifdef FC_USE_SDL_GPU 1322 | Uint8 FC_LoadFont_RW(FC_Font* font, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style) 1323 | #else 1324 | Uint8 FC_LoadFont_RW(FC_Font* font, FC_Target* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style) 1325 | #endif 1326 | { 1327 | Uint8 result; 1328 | TTF_Font* ttf; 1329 | Uint8 outline; 1330 | 1331 | if(font == NULL) 1332 | return 0; 1333 | 1334 | if(!TTF_WasInit() && TTF_Init() < 0) 1335 | { 1336 | FC_Log("Unable to initialize SDL_ttf: %s \n", TTF_GetError()); 1337 | if(own_rwops) 1338 | SDL_RWclose(file_rwops_ttf); 1339 | return 0; 1340 | } 1341 | 1342 | ttf = TTF_OpenFontRW(file_rwops_ttf, own_rwops, pointSize); 1343 | 1344 | if(ttf == NULL) 1345 | { 1346 | FC_Log("Unable to load TrueType font: %s \n", TTF_GetError()); 1347 | if(own_rwops) 1348 | SDL_RWclose(file_rwops_ttf); 1349 | return 0; 1350 | } 1351 | 1352 | outline = (style & TTF_STYLE_OUTLINE); 1353 | if(outline) 1354 | { 1355 | style &= ~TTF_STYLE_OUTLINE; 1356 | TTF_SetFontOutline(ttf, 1); 1357 | } 1358 | TTF_SetFontStyle(ttf, style); 1359 | 1360 | #ifdef FC_USE_SDL_GPU 1361 | result = FC_LoadFontFromTTF(font, ttf, color); 1362 | #else 1363 | result = FC_LoadFontFromTTF(font, renderer, ttf, color); 1364 | #endif 1365 | 1366 | // Can only load new (uncached) glyphs if we can keep the SDL_RWops open. 1367 | font->owns_ttf_source = own_rwops; 1368 | if(!own_rwops) 1369 | { 1370 | TTF_CloseFont(font->ttf_source); 1371 | font->ttf_source = NULL; 1372 | } 1373 | 1374 | return result; 1375 | } 1376 | 1377 | 1378 | #ifndef FC_USE_SDL_GPU 1379 | void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType) 1380 | { 1381 | TTF_Font* ttf; 1382 | SDL_Color col; 1383 | Uint8 owns_ttf; 1384 | if (font == NULL) 1385 | return; 1386 | 1387 | // Destroy glyph cache 1388 | if (evType == SDL_RENDER_TARGETS_RESET) { 1389 | int i; 1390 | for (i = 0; i < font->glyph_cache_count; ++i) 1391 | SDL_DestroyTexture(font->glyph_cache[i]); 1392 | } 1393 | free(font->glyph_cache); 1394 | 1395 | ttf = font->ttf_source; 1396 | col = font->default_color; 1397 | owns_ttf = font->owns_ttf_source; 1398 | FC_Init(font); 1399 | 1400 | // Can only reload glyphs if we own the SDL_RWops. 1401 | if (owns_ttf) 1402 | FC_LoadFontFromTTF(font, renderer, ttf, col); 1403 | font->owns_ttf_source = owns_ttf; 1404 | } 1405 | #endif 1406 | 1407 | void FC_ClearFont(FC_Font* font) 1408 | { 1409 | int i; 1410 | if(font == NULL) 1411 | return; 1412 | 1413 | // Release resources 1414 | if(font->owns_ttf_source) 1415 | TTF_CloseFont(font->ttf_source); 1416 | 1417 | font->owns_ttf_source = 0; 1418 | font->ttf_source = NULL; 1419 | 1420 | // Delete glyph map 1421 | FC_MapFree(font->glyphs); 1422 | font->glyphs = NULL; 1423 | 1424 | // Delete glyph cache 1425 | for(i = 0; i < font->glyph_cache_count; ++i) 1426 | { 1427 | #ifdef FC_USE_SDL_GPU 1428 | GPU_FreeImage(font->glyph_cache[i]); 1429 | #else 1430 | SDL_DestroyTexture(font->glyph_cache[i]); 1431 | #endif 1432 | } 1433 | free(font->glyph_cache); 1434 | font->glyph_cache = NULL; 1435 | 1436 | // Reset font 1437 | FC_Init(font); 1438 | } 1439 | 1440 | 1441 | void FC_FreeFont(FC_Font* font) 1442 | { 1443 | int i; 1444 | if(font == NULL) 1445 | return; 1446 | 1447 | // Release resources 1448 | if(font->owns_ttf_source) 1449 | TTF_CloseFont(font->ttf_source); 1450 | 1451 | // Delete glyph map 1452 | FC_MapFree(font->glyphs); 1453 | 1454 | // Delete glyph cache 1455 | for(i = 0; i < font->glyph_cache_count; ++i) 1456 | { 1457 | #ifdef FC_USE_SDL_GPU 1458 | GPU_FreeImage(font->glyph_cache[i]); 1459 | #else 1460 | SDL_DestroyTexture(font->glyph_cache[i]); 1461 | #endif 1462 | } 1463 | free(font->glyph_cache); 1464 | 1465 | free(font->loading_string); 1466 | 1467 | free(font); 1468 | 1469 | // If the last font has been freed; assume shutdown and free the global variables 1470 | if (--NUM_EXISTING_FONTS <= 0) 1471 | { 1472 | free(ASCII_STRING); 1473 | ASCII_STRING = NULL; 1474 | 1475 | free(LATIN_1_STRING); 1476 | LATIN_1_STRING = NULL; 1477 | 1478 | free(ASCII_LATIN_1_STRING); 1479 | ASCII_LATIN_1_STRING = NULL; 1480 | 1481 | free(fc_buffer); 1482 | fc_buffer = NULL; 1483 | } 1484 | } 1485 | 1486 | int FC_GetNumCacheLevels(FC_Font* font) 1487 | { 1488 | return font->glyph_cache_count; 1489 | } 1490 | 1491 | Uint8 FC_AddGlyphToCache(FC_Font* font, SDL_Surface* glyph_surface) 1492 | { 1493 | if(font == NULL || glyph_surface == NULL) 1494 | return 0; 1495 | 1496 | SDL_SetSurfaceBlendMode(glyph_surface, SDL_BLENDMODE_NONE); 1497 | FC_Image* dest = FC_GetGlyphCacheLevel(font, font->last_glyph.cache_level); 1498 | if(dest == NULL) 1499 | return 0; 1500 | 1501 | #ifdef FC_USE_SDL_GPU 1502 | { 1503 | GPU_Target* target = GPU_LoadTarget(dest); 1504 | if(target == NULL) 1505 | return 0; 1506 | GPU_Image* img = GPU_CopyImageFromSurface(glyph_surface); 1507 | GPU_SetAnchor(img, 0.5f, 0.5f); // Just in case the default is different 1508 | GPU_SetImageFilter(img, GPU_FILTER_NEAREST); 1509 | GPU_SetBlendMode(img, GPU_BLEND_SET); 1510 | 1511 | SDL_Rect destrect = font->last_glyph.rect; 1512 | GPU_Blit(img, NULL, target, destrect.x + destrect.w/2, destrect.y + destrect.h/2); 1513 | 1514 | GPU_FreeImage(img); 1515 | GPU_FreeTarget(target); 1516 | } 1517 | #else 1518 | { 1519 | SDL_Renderer* renderer = font->renderer; 1520 | SDL_Texture* img; 1521 | SDL_Rect destrect; 1522 | SDL_Texture* prev_target = SDL_GetRenderTarget(renderer); 1523 | SDL_Rect prev_clip, prev_viewport; 1524 | int prev_logicalw, prev_logicalh; 1525 | Uint8 prev_clip_enabled; 1526 | float prev_scalex, prev_scaley; 1527 | // only backup if previous target existed (SDL will preserve them for the default target) 1528 | if (prev_target) { 1529 | prev_clip_enabled = has_clip(renderer); 1530 | if (prev_clip_enabled) 1531 | prev_clip = get_clip(renderer); 1532 | SDL_RenderGetViewport(renderer, &prev_viewport); 1533 | SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley); 1534 | SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh); 1535 | } 1536 | 1537 | img = SDL_CreateTextureFromSurface(renderer, glyph_surface); 1538 | 1539 | destrect = font->last_glyph.rect; 1540 | SDL_SetRenderTarget(renderer, dest); 1541 | SDL_RenderCopy(renderer, img, NULL, &destrect); 1542 | SDL_SetRenderTarget(renderer, prev_target); 1543 | if (prev_target) { 1544 | if (prev_clip_enabled) 1545 | set_clip(renderer, &prev_clip); 1546 | if (prev_logicalw && prev_logicalh) 1547 | SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh); 1548 | else { 1549 | SDL_RenderSetViewport(renderer, &prev_viewport); 1550 | SDL_RenderSetScale(renderer, prev_scalex, prev_scaley); 1551 | } 1552 | } 1553 | 1554 | SDL_DestroyTexture(img); 1555 | } 1556 | #endif 1557 | 1558 | return 1; 1559 | } 1560 | 1561 | 1562 | unsigned int FC_GetNumCodepoints(FC_Font* font) 1563 | { 1564 | FC_Map* glyphs; 1565 | int i; 1566 | unsigned int result = 0; 1567 | if(font == NULL || font->glyphs == NULL) 1568 | return 0; 1569 | 1570 | glyphs = font->glyphs; 1571 | 1572 | for(i = 0; i < glyphs->num_buckets; ++i) 1573 | { 1574 | FC_MapNode* node; 1575 | for(node = glyphs->buckets[i]; node != NULL; node = node->next) 1576 | { 1577 | result++; 1578 | } 1579 | } 1580 | 1581 | return result; 1582 | } 1583 | 1584 | void FC_GetCodepoints(FC_Font* font, Uint32* result) 1585 | { 1586 | FC_Map* glyphs; 1587 | int i; 1588 | unsigned int count = 0; 1589 | if(font == NULL || font->glyphs == NULL) 1590 | return; 1591 | 1592 | glyphs = font->glyphs; 1593 | 1594 | for(i = 0; i < glyphs->num_buckets; ++i) 1595 | { 1596 | FC_MapNode* node; 1597 | for(node = glyphs->buckets[i]; node != NULL; node = node->next) 1598 | { 1599 | result[count] = node->key; 1600 | count++; 1601 | } 1602 | } 1603 | } 1604 | 1605 | Uint8 FC_GetGlyphData(FC_Font* font, FC_GlyphData* result, Uint32 codepoint) 1606 | { 1607 | FC_GlyphData* e = FC_MapFind(font->glyphs, codepoint); 1608 | if(e == NULL) 1609 | { 1610 | char buff[5]; 1611 | int w, h; 1612 | SDL_Color white = {255, 255, 255, 255}; 1613 | SDL_Surface* surf; 1614 | FC_Image* cache_image; 1615 | 1616 | if(font->ttf_source == NULL) 1617 | return 0; 1618 | 1619 | FC_GetUTF8FromCodepoint(buff, codepoint); 1620 | 1621 | cache_image = FC_GetGlyphCacheLevel(font, font->last_glyph.cache_level); 1622 | if(cache_image == NULL) 1623 | { 1624 | FC_Log("SDL_FontCache: Failed to load cache image, so cannot add new glyphs!\n"); 1625 | return 0; 1626 | } 1627 | 1628 | #ifdef FC_USE_SDL_GPU 1629 | w = cache_image->w; 1630 | h = cache_image->h; 1631 | #else 1632 | SDL_QueryTexture(cache_image, NULL, NULL, &w, &h); 1633 | #endif 1634 | 1635 | surf = TTF_RenderUTF8_Blended(font->ttf_source, buff, white); 1636 | if(surf == NULL) 1637 | { 1638 | return 0; 1639 | } 1640 | 1641 | e = FC_PackGlyphData(font, codepoint, surf->w, w, h); 1642 | if(e == NULL) 1643 | { 1644 | // Grow the cache 1645 | FC_GrowGlyphCache(font); 1646 | 1647 | // Try packing again 1648 | e = FC_PackGlyphData(font, codepoint, surf->w, w, h); 1649 | if(e == NULL) 1650 | { 1651 | SDL_FreeSurface(surf); 1652 | return 0; 1653 | } 1654 | } 1655 | 1656 | // Render onto the cache texture 1657 | FC_AddGlyphToCache(font, surf); 1658 | 1659 | SDL_FreeSurface(surf); 1660 | } 1661 | 1662 | if(result != NULL && e != NULL) 1663 | *result = *e; 1664 | 1665 | return 1; 1666 | } 1667 | 1668 | 1669 | FC_GlyphData* FC_SetGlyphData(FC_Font* font, Uint32 codepoint, FC_GlyphData glyph_data) 1670 | { 1671 | return FC_MapInsert(font->glyphs, codepoint, glyph_data); 1672 | } 1673 | 1674 | 1675 | 1676 | // Drawing 1677 | static FC_Rect FC_RenderLeft(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) 1678 | { 1679 | const char* c = text; 1680 | FC_Rect srcRect; 1681 | FC_Rect dstRect; 1682 | FC_Rect dirtyRect = FC_MakeRect(x, y, 0, 0); 1683 | 1684 | FC_GlyphData glyph; 1685 | Uint32 codepoint; 1686 | 1687 | float destX = x; 1688 | float destY = y; 1689 | float destH; 1690 | float destLineSpacing; 1691 | float destLetterSpacing; 1692 | 1693 | if(font == NULL) 1694 | return dirtyRect; 1695 | 1696 | destH = font->height * scale.y; 1697 | destLineSpacing = font->lineSpacing*scale.y; 1698 | destLetterSpacing = font->letterSpacing*scale.x; 1699 | 1700 | if(c == NULL || font->glyph_cache_count == 0 || dest == NULL) 1701 | return dirtyRect; 1702 | 1703 | int newlineX = x; 1704 | 1705 | for(; *c != '\0'; c++) 1706 | { 1707 | if(*c == '\n') 1708 | { 1709 | destX = newlineX; 1710 | destY += destH + destLineSpacing; 1711 | continue; 1712 | } 1713 | 1714 | codepoint = FC_GetCodepointFromUTF8(&c, 1); // Increments 'c' to skip the extra UTF-8 bytes 1715 | if(!FC_GetGlyphData(font, &glyph, codepoint)) 1716 | { 1717 | codepoint = ' '; 1718 | if(!FC_GetGlyphData(font, &glyph, codepoint)) 1719 | continue; // Skip bad characters 1720 | } 1721 | 1722 | if (codepoint == ' ') 1723 | { 1724 | destX += glyph.rect.w*scale.x + destLetterSpacing; 1725 | continue; 1726 | } 1727 | /*if(destX >= dest->w) 1728 | continue; 1729 | if(destY >= dest->h) 1730 | continue;*/ 1731 | 1732 | #ifdef FC_USE_SDL_GPU 1733 | srcRect.x = glyph.rect.x; 1734 | srcRect.y = glyph.rect.y; 1735 | srcRect.w = glyph.rect.w; 1736 | srcRect.h = glyph.rect.h; 1737 | #else 1738 | srcRect = glyph.rect; 1739 | #endif 1740 | dstRect = fc_render_callback(FC_GetGlyphCacheLevel(font, glyph.cache_level), &srcRect, dest, destX, destY, scale.x, scale.y); 1741 | if(dirtyRect.w == 0 || dirtyRect.h == 0) 1742 | dirtyRect = dstRect; 1743 | else 1744 | dirtyRect = FC_RectUnion(dirtyRect, dstRect); 1745 | 1746 | destX += glyph.rect.w*scale.x + destLetterSpacing; 1747 | } 1748 | 1749 | return dirtyRect; 1750 | } 1751 | 1752 | static void set_color_for_all_caches(FC_Font* font, SDL_Color color) 1753 | { 1754 | // TODO: How can I predict which glyph caches are to be used? 1755 | FC_Image* img; 1756 | int i; 1757 | int num_levels = FC_GetNumCacheLevels(font); 1758 | for(i = 0; i < num_levels; ++i) 1759 | { 1760 | img = FC_GetGlyphCacheLevel(font, i); 1761 | set_color(img, color.r, color.g, color.b, FC_GET_ALPHA(color)); 1762 | } 1763 | } 1764 | 1765 | FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...) 1766 | { 1767 | if(formatted_text == NULL || font == NULL) 1768 | return FC_MakeRect(x, y, 0, 0); 1769 | 1770 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 1771 | 1772 | set_color_for_all_caches(font, font->default_color); 1773 | 1774 | return FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); 1775 | } 1776 | 1777 | 1778 | 1779 | typedef struct FC_StringList 1780 | { 1781 | char* value; 1782 | struct FC_StringList* next; 1783 | } FC_StringList; 1784 | 1785 | void FC_StringListFree(FC_StringList* node) 1786 | { 1787 | // Delete the nodes in order 1788 | while(node != NULL) 1789 | { 1790 | FC_StringList* last = node; 1791 | node = node->next; 1792 | 1793 | free(last->value); 1794 | free(last); 1795 | } 1796 | } 1797 | 1798 | FC_StringList** FC_StringListPushBack(FC_StringList** node, char* value, Uint8 copy) 1799 | { 1800 | if(node == NULL) 1801 | { 1802 | return NULL; 1803 | } 1804 | 1805 | // Get to the last node 1806 | while(*node != NULL) 1807 | { 1808 | node = &(*node)->next; 1809 | } 1810 | 1811 | *node = (FC_StringList*)malloc(sizeof(FC_StringList)); 1812 | 1813 | (*node)->value = (copy? U8_strdup(value) : value); 1814 | (*node)->next = NULL; 1815 | 1816 | return node; 1817 | } 1818 | 1819 | FC_StringList** FC_StringListPushBackBytes(FC_StringList** node, const char* data, int num_bytes) 1820 | { 1821 | if(node == NULL) 1822 | { 1823 | return node; 1824 | } 1825 | 1826 | // Get to the last node 1827 | while(*node != NULL) 1828 | { 1829 | node = &(*node)->next; 1830 | } 1831 | 1832 | *node = (FC_StringList*)malloc(sizeof(FC_StringList)); 1833 | 1834 | (*node)->value = (char*)malloc(num_bytes + 1); 1835 | memcpy((*node)->value, data, num_bytes); 1836 | (*node)->value[num_bytes] = '\0'; 1837 | (*node)->next = NULL; 1838 | 1839 | return node; 1840 | } 1841 | 1842 | static FC_StringList* FC_Explode(const char* text, char delimiter) 1843 | { 1844 | FC_StringList* head; 1845 | FC_StringList* new_node; 1846 | FC_StringList** node; 1847 | const char* start; 1848 | const char* end; 1849 | unsigned int size; 1850 | if(text == NULL) 1851 | return NULL; 1852 | 1853 | head = NULL; 1854 | node = &head; 1855 | 1856 | // Doesn't technically support UTF-8, but it's probably fine, right? 1857 | size = 0; 1858 | start = end = text; 1859 | while(1) 1860 | { 1861 | if(*end == delimiter || *end == '\0') 1862 | { 1863 | *node = (FC_StringList*)malloc(sizeof(FC_StringList)); 1864 | new_node = *node; 1865 | 1866 | new_node->value = (char*)malloc(size + 1); 1867 | memcpy(new_node->value, start, size); 1868 | new_node->value[size] = '\0'; 1869 | 1870 | new_node->next = NULL; 1871 | 1872 | if(*end == '\0') 1873 | break; 1874 | 1875 | node = &((*node)->next); 1876 | start = end+1; 1877 | size = 0; 1878 | } 1879 | else 1880 | ++size; 1881 | 1882 | ++end; 1883 | } 1884 | 1885 | return head; 1886 | } 1887 | 1888 | static FC_StringList* FC_ExplodeBreakingSpace(const char* text, FC_StringList** spaces) 1889 | { 1890 | FC_StringList* head; 1891 | FC_StringList** node; 1892 | const char* start; 1893 | const char* end; 1894 | unsigned int size; 1895 | if(text == NULL) 1896 | return NULL; 1897 | 1898 | head = NULL; 1899 | node = &head; 1900 | 1901 | // Warning: spaces must not be initialized before this function 1902 | *spaces = NULL; 1903 | 1904 | // Doesn't technically support UTF-8, but it's probably fine, right? 1905 | size = 0; 1906 | start = end = text; 1907 | while(1) 1908 | { 1909 | // Add any characters here that should make separate words (except for \n?) 1910 | if(*end == ' ' || *end == '\t' || *end == '\0') 1911 | { 1912 | FC_StringListPushBackBytes(node, start, size); 1913 | FC_StringListPushBackBytes(spaces, end, 1); 1914 | 1915 | if(*end == '\0') 1916 | break; 1917 | 1918 | node = &((*node)->next); 1919 | start = end+1; 1920 | size = 0; 1921 | } 1922 | else 1923 | ++size; 1924 | 1925 | ++end; 1926 | } 1927 | 1928 | return head; 1929 | } 1930 | 1931 | static FC_StringList* FC_ExplodeAndKeep(const char* text, char delimiter) 1932 | { 1933 | FC_StringList* head; 1934 | FC_StringList** node; 1935 | const char* start; 1936 | const char* end; 1937 | unsigned int size; 1938 | if(text == NULL) 1939 | return NULL; 1940 | 1941 | head = NULL; 1942 | node = &head; 1943 | 1944 | // Doesn't technically support UTF-8, but it's probably fine, right? 1945 | size = 0; 1946 | start = end = text; 1947 | while(1) 1948 | { 1949 | if(*end == delimiter || *end == '\0') 1950 | { 1951 | FC_StringListPushBackBytes(node, start, size); 1952 | 1953 | if(*end == '\0') 1954 | break; 1955 | 1956 | node = &((*node)->next); 1957 | start = end; 1958 | size = 1; 1959 | } 1960 | else 1961 | ++size; 1962 | 1963 | ++end; 1964 | } 1965 | 1966 | return head; 1967 | } 1968 | 1969 | static void FC_RenderAlign(FC_Font* font, FC_Target* dest, float x, float y, int width, FC_Scale scale, FC_AlignEnum align, const char* text) 1970 | { 1971 | switch(align) 1972 | { 1973 | case FC_ALIGN_LEFT: 1974 | FC_RenderLeft(font, dest, x, y, scale, text); 1975 | break; 1976 | case FC_ALIGN_CENTER: 1977 | FC_RenderCenter(font, dest, x + width/2, y, scale, text); 1978 | break; 1979 | case FC_ALIGN_RIGHT: 1980 | FC_RenderRight(font, dest, x + width, y, scale, text); 1981 | break; 1982 | } 1983 | } 1984 | 1985 | static FC_StringList* FC_GetBufferFitToColumn(FC_Font* font, int width, FC_Scale scale, Uint8 keep_newlines) 1986 | { 1987 | FC_StringList* result = NULL; 1988 | FC_StringList** current = &result; 1989 | 1990 | FC_StringList *ls, *iter; 1991 | 1992 | ls = (keep_newlines? FC_ExplodeAndKeep(fc_buffer, '\n') : FC_Explode(fc_buffer, '\n')); 1993 | for(iter = ls; iter != NULL; iter = iter->next) 1994 | { 1995 | char* line = iter->value; 1996 | 1997 | // If line is too long, then add words one at a time until we go over. 1998 | if(width > 0 && FC_GetWidth(font, "%s", line) > width) 1999 | { 2000 | FC_StringList *words, *word_iter, *spaces, *spaces_iter; 2001 | 2002 | words = FC_ExplodeBreakingSpace(line, &spaces); 2003 | // Skip the first word for the iterator, so there will always be at least one word per line 2004 | line = new_concat(words->value, spaces->value); 2005 | for(word_iter = words->next, spaces_iter = spaces->next; word_iter != NULL && spaces_iter != NULL; word_iter = word_iter->next, spaces_iter = spaces_iter->next) 2006 | { 2007 | char* line_plus_word = new_concat(line, word_iter->value); 2008 | char* word_plus_space = new_concat(word_iter->value, spaces_iter->value); 2009 | if(FC_GetWidth(font, "%s", line_plus_word) > width) 2010 | { 2011 | current = FC_StringListPushBack(current, line, 0); 2012 | 2013 | line = word_plus_space; 2014 | } 2015 | else 2016 | { 2017 | replace_concat(&line, word_plus_space); 2018 | free(word_plus_space); 2019 | } 2020 | free(line_plus_word); 2021 | } 2022 | current = FC_StringListPushBack(current, line, 0); 2023 | FC_StringListFree(words); 2024 | FC_StringListFree(spaces); 2025 | } 2026 | else 2027 | { 2028 | current = FC_StringListPushBack(current, line, 0); 2029 | iter->value = NULL; 2030 | } 2031 | } 2032 | FC_StringListFree(ls); 2033 | 2034 | return result; 2035 | } 2036 | 2037 | static void FC_DrawColumnFromBuffer(FC_Font* font, FC_Target* dest, FC_Rect box, int* total_height, FC_Scale scale, FC_AlignEnum align) 2038 | { 2039 | int y = box.y; 2040 | FC_StringList *ls, *iter; 2041 | 2042 | ls = FC_GetBufferFitToColumn(font, box.w, scale, 0); 2043 | for(iter = ls; iter != NULL; iter = iter->next) 2044 | { 2045 | FC_RenderAlign(font, dest, box.x, y, box.w, scale, align, iter->value); 2046 | y += FC_GetLineHeight(font); 2047 | } 2048 | FC_StringListFree(ls); 2049 | 2050 | if(total_height != NULL) 2051 | *total_height = y - box.y; 2052 | } 2053 | 2054 | FC_Rect FC_DrawBox(FC_Font* font, FC_Target* dest, FC_Rect box, const char* formatted_text, ...) 2055 | { 2056 | Uint8 useClip; 2057 | if(formatted_text == NULL || font == NULL) 2058 | return FC_MakeRect(box.x, box.y, 0, 0); 2059 | 2060 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2061 | 2062 | useClip = has_clip(dest); 2063 | FC_Rect oldclip, newclip; 2064 | if(useClip) 2065 | { 2066 | oldclip = get_clip(dest); 2067 | newclip = FC_RectIntersect(oldclip, box); 2068 | } 2069 | else 2070 | newclip = box; 2071 | 2072 | set_clip(dest, &newclip); 2073 | 2074 | set_color_for_all_caches(font, font->default_color); 2075 | 2076 | FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), FC_ALIGN_LEFT); 2077 | 2078 | if(useClip) 2079 | set_clip(dest, &oldclip); 2080 | else 2081 | set_clip(dest, NULL); 2082 | 2083 | return box; 2084 | } 2085 | 2086 | FC_Rect FC_DrawBoxAlign(FC_Font* font, FC_Target* dest, FC_Rect box, FC_AlignEnum align, const char* formatted_text, ...) 2087 | { 2088 | Uint8 useClip; 2089 | if(formatted_text == NULL || font == NULL) 2090 | return FC_MakeRect(box.x, box.y, 0, 0); 2091 | 2092 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2093 | 2094 | useClip = has_clip(dest); 2095 | FC_Rect oldclip, newclip; 2096 | if(useClip) 2097 | { 2098 | oldclip = get_clip(dest); 2099 | newclip = FC_RectIntersect(oldclip, box); 2100 | } 2101 | else 2102 | newclip = box; 2103 | set_clip(dest, &newclip); 2104 | 2105 | set_color_for_all_caches(font, font->default_color); 2106 | 2107 | FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), align); 2108 | 2109 | if(useClip) 2110 | set_clip(dest, &oldclip); 2111 | else 2112 | set_clip(dest, NULL); 2113 | 2114 | return box; 2115 | } 2116 | 2117 | FC_Rect FC_DrawBoxScale(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Scale scale, const char* formatted_text, ...) 2118 | { 2119 | Uint8 useClip; 2120 | if(formatted_text == NULL || font == NULL) 2121 | return FC_MakeRect(box.x, box.y, 0, 0); 2122 | 2123 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2124 | 2125 | useClip = has_clip(dest); 2126 | FC_Rect oldclip, newclip; 2127 | if(useClip) 2128 | { 2129 | oldclip = get_clip(dest); 2130 | newclip = FC_RectIntersect(oldclip, box); 2131 | } 2132 | else 2133 | newclip = box; 2134 | set_clip(dest, &newclip); 2135 | 2136 | set_color_for_all_caches(font, font->default_color); 2137 | 2138 | FC_DrawColumnFromBuffer(font, dest, box, NULL, scale, FC_ALIGN_LEFT); 2139 | 2140 | if(useClip) 2141 | set_clip(dest, &oldclip); 2142 | else 2143 | set_clip(dest, NULL); 2144 | 2145 | return box; 2146 | } 2147 | 2148 | FC_Rect FC_DrawBoxColor(FC_Font* font, FC_Target* dest, FC_Rect box, SDL_Color color, const char* formatted_text, ...) 2149 | { 2150 | Uint8 useClip; 2151 | if(formatted_text == NULL || font == NULL) 2152 | return FC_MakeRect(box.x, box.y, 0, 0); 2153 | 2154 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2155 | 2156 | useClip = has_clip(dest); 2157 | FC_Rect oldclip, newclip; 2158 | if(useClip) 2159 | { 2160 | oldclip = get_clip(dest); 2161 | newclip = FC_RectIntersect(oldclip, box); 2162 | } 2163 | else 2164 | newclip = box; 2165 | set_clip(dest, &newclip); 2166 | 2167 | set_color_for_all_caches(font, color); 2168 | 2169 | FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), FC_ALIGN_LEFT); 2170 | 2171 | if(useClip) 2172 | set_clip(dest, &oldclip); 2173 | else 2174 | set_clip(dest, NULL); 2175 | 2176 | return box; 2177 | } 2178 | 2179 | FC_Rect FC_DrawBoxEffect(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Effect effect, const char* formatted_text, ...) 2180 | { 2181 | Uint8 useClip; 2182 | if(formatted_text == NULL || font == NULL) 2183 | return FC_MakeRect(box.x, box.y, 0, 0); 2184 | 2185 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2186 | 2187 | useClip = has_clip(dest); 2188 | FC_Rect oldclip, newclip; 2189 | if(useClip) 2190 | { 2191 | oldclip = get_clip(dest); 2192 | newclip = FC_RectIntersect(oldclip, box); 2193 | } 2194 | else 2195 | newclip = box; 2196 | set_clip(dest, &newclip); 2197 | 2198 | set_color_for_all_caches(font, effect.color); 2199 | 2200 | FC_DrawColumnFromBuffer(font, dest, box, NULL, effect.scale, effect.alignment); 2201 | 2202 | if(useClip) 2203 | set_clip(dest, &oldclip); 2204 | else 2205 | set_clip(dest, NULL); 2206 | 2207 | return box; 2208 | } 2209 | 2210 | FC_Rect FC_DrawColumn(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...) 2211 | { 2212 | FC_Rect box = {x, y, width, 0}; 2213 | int total_height; 2214 | 2215 | if(formatted_text == NULL || font == NULL) 2216 | return FC_MakeRect(x, y, 0, 0); 2217 | 2218 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2219 | 2220 | set_color_for_all_caches(font, font->default_color); 2221 | 2222 | FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), FC_ALIGN_LEFT); 2223 | 2224 | return FC_MakeRect(box.x, box.y, width, total_height); 2225 | } 2226 | 2227 | FC_Rect FC_DrawColumnAlign(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_AlignEnum align, const char* formatted_text, ...) 2228 | { 2229 | FC_Rect box = {x, y, width, 0}; 2230 | int total_height; 2231 | 2232 | if(formatted_text == NULL || font == NULL) 2233 | return FC_MakeRect(x, y, 0, 0); 2234 | 2235 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2236 | 2237 | set_color_for_all_caches(font, font->default_color); 2238 | 2239 | switch(align) 2240 | { 2241 | case FC_ALIGN_CENTER: 2242 | box.x -= width/2; 2243 | break; 2244 | case FC_ALIGN_RIGHT: 2245 | box.x -= width; 2246 | break; 2247 | default: 2248 | break; 2249 | } 2250 | 2251 | FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), align); 2252 | 2253 | return FC_MakeRect(box.x, box.y, width, total_height); 2254 | } 2255 | 2256 | FC_Rect FC_DrawColumnScale(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Scale scale, const char* formatted_text, ...) 2257 | { 2258 | FC_Rect box = {x, y, width, 0}; 2259 | int total_height; 2260 | 2261 | if(formatted_text == NULL || font == NULL) 2262 | return FC_MakeRect(x, y, 0, 0); 2263 | 2264 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2265 | 2266 | set_color_for_all_caches(font, font->default_color); 2267 | 2268 | FC_DrawColumnFromBuffer(font, dest, box, &total_height, scale, FC_ALIGN_LEFT); 2269 | 2270 | return FC_MakeRect(box.x, box.y, width, total_height); 2271 | } 2272 | 2273 | FC_Rect FC_DrawColumnColor(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, SDL_Color color, const char* formatted_text, ...) 2274 | { 2275 | FC_Rect box = {x, y, width, 0}; 2276 | int total_height; 2277 | 2278 | if(formatted_text == NULL || font == NULL) 2279 | return FC_MakeRect(x, y, 0, 0); 2280 | 2281 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2282 | 2283 | set_color_for_all_caches(font, color); 2284 | 2285 | FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), FC_ALIGN_LEFT); 2286 | 2287 | return FC_MakeRect(box.x, box.y, width, total_height); 2288 | } 2289 | 2290 | FC_Rect FC_DrawColumnEffect(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Effect effect, const char* formatted_text, ...) 2291 | { 2292 | FC_Rect box = {x, y, width, 0}; 2293 | int total_height; 2294 | 2295 | if(formatted_text == NULL || font == NULL) 2296 | return FC_MakeRect(x, y, 0, 0); 2297 | 2298 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2299 | 2300 | set_color_for_all_caches(font, effect.color); 2301 | 2302 | switch(effect.alignment) 2303 | { 2304 | case FC_ALIGN_CENTER: 2305 | box.x -= width/2; 2306 | break; 2307 | case FC_ALIGN_RIGHT: 2308 | box.x -= width; 2309 | break; 2310 | default: 2311 | break; 2312 | } 2313 | 2314 | FC_DrawColumnFromBuffer(font, dest, box, &total_height, effect.scale, effect.alignment); 2315 | 2316 | return FC_MakeRect(box.x, box.y, width, total_height); 2317 | } 2318 | 2319 | static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) 2320 | { 2321 | FC_Rect result = {x, y, 0, 0}; 2322 | if(text == NULL || font == NULL) 2323 | return result; 2324 | 2325 | char* str = U8_strdup(text); 2326 | char* del = str; 2327 | char* c; 2328 | 2329 | // Go through str, when you find a \n, replace it with \0 and print it 2330 | // then move down, back, and continue. 2331 | for(c = str; *c != '\0';) 2332 | { 2333 | if(*c == '\n') 2334 | { 2335 | *c = '\0'; 2336 | result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str)/2.0f, y, scale, str), result); 2337 | *c = '\n'; 2338 | c++; 2339 | str = c; 2340 | y += scale.y*font->height; 2341 | } 2342 | else 2343 | c++; 2344 | } 2345 | 2346 | result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str)/2.0f, y, scale, str), result); 2347 | 2348 | free(del); 2349 | return result; 2350 | } 2351 | 2352 | static FC_Rect FC_RenderRight(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) 2353 | { 2354 | FC_Rect result = {x, y, 0, 0}; 2355 | if(text == NULL || font == NULL) 2356 | return result; 2357 | 2358 | char* str = U8_strdup(text); 2359 | char* del = str; 2360 | char* c; 2361 | 2362 | for(c = str; *c != '\0';) 2363 | { 2364 | if(*c == '\n') 2365 | { 2366 | *c = '\0'; 2367 | result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str), y, scale, str), result); 2368 | *c = '\n'; 2369 | c++; 2370 | str = c; 2371 | y += scale.y*font->height; 2372 | } 2373 | else 2374 | c++; 2375 | } 2376 | 2377 | result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str), y, scale, str), result); 2378 | 2379 | free(del); 2380 | return result; 2381 | } 2382 | 2383 | 2384 | 2385 | FC_Rect FC_DrawScale(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* formatted_text, ...) 2386 | { 2387 | if(formatted_text == NULL || font == NULL) 2388 | return FC_MakeRect(x, y, 0, 0); 2389 | 2390 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2391 | 2392 | set_color_for_all_caches(font, font->default_color); 2393 | 2394 | return FC_RenderLeft(font, dest, x, y, scale, fc_buffer); 2395 | } 2396 | 2397 | FC_Rect FC_DrawAlign(FC_Font* font, FC_Target* dest, float x, float y, FC_AlignEnum align, const char* formatted_text, ...) 2398 | { 2399 | if(formatted_text == NULL || font == NULL) 2400 | return FC_MakeRect(x, y, 0, 0); 2401 | 2402 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2403 | 2404 | set_color_for_all_caches(font, font->default_color); 2405 | 2406 | FC_Rect result; 2407 | switch(align) 2408 | { 2409 | case FC_ALIGN_LEFT: 2410 | result = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); 2411 | break; 2412 | case FC_ALIGN_CENTER: 2413 | result = FC_RenderCenter(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); 2414 | break; 2415 | case FC_ALIGN_RIGHT: 2416 | result = FC_RenderRight(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); 2417 | break; 2418 | default: 2419 | result = FC_MakeRect(x, y, 0, 0); 2420 | break; 2421 | } 2422 | 2423 | return result; 2424 | } 2425 | 2426 | FC_Rect FC_DrawColor(FC_Font* font, FC_Target* dest, float x, float y, SDL_Color color, const char* formatted_text, ...) 2427 | { 2428 | if(formatted_text == NULL || font == NULL) 2429 | return FC_MakeRect(x, y, 0, 0); 2430 | 2431 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2432 | 2433 | set_color_for_all_caches(font, color); 2434 | 2435 | return FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); 2436 | } 2437 | 2438 | 2439 | FC_Rect FC_DrawEffect(FC_Font* font, FC_Target* dest, float x, float y, FC_Effect effect, const char* formatted_text, ...) 2440 | { 2441 | if(formatted_text == NULL || font == NULL) 2442 | return FC_MakeRect(x, y, 0, 0); 2443 | 2444 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2445 | 2446 | set_color_for_all_caches(font, effect.color); 2447 | 2448 | FC_Rect result; 2449 | switch(effect.alignment) 2450 | { 2451 | case FC_ALIGN_LEFT: 2452 | result = FC_RenderLeft(font, dest, x, y, effect.scale, fc_buffer); 2453 | break; 2454 | case FC_ALIGN_CENTER: 2455 | result = FC_RenderCenter(font, dest, x, y, effect.scale, fc_buffer); 2456 | break; 2457 | case FC_ALIGN_RIGHT: 2458 | result = FC_RenderRight(font, dest, x, y, effect.scale, fc_buffer); 2459 | break; 2460 | default: 2461 | result = FC_MakeRect(x, y, 0, 0); 2462 | break; 2463 | } 2464 | 2465 | return result; 2466 | } 2467 | 2468 | 2469 | 2470 | 2471 | // Getters 2472 | 2473 | 2474 | FC_FilterEnum FC_GetFilterMode(FC_Font* font) 2475 | { 2476 | if(font == NULL) 2477 | return FC_FILTER_NEAREST; 2478 | 2479 | return font->filter; 2480 | } 2481 | 2482 | Uint16 FC_GetLineHeight(FC_Font* font) 2483 | { 2484 | if(font == NULL) 2485 | return 0; 2486 | 2487 | return font->height; 2488 | } 2489 | 2490 | Uint16 FC_GetHeight(FC_Font* font, const char* formatted_text, ...) 2491 | { 2492 | if(formatted_text == NULL || font == NULL) 2493 | return 0; 2494 | 2495 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2496 | 2497 | Uint16 numLines = 1; 2498 | const char* c; 2499 | 2500 | for (c = fc_buffer; *c != '\0'; c++) 2501 | { 2502 | if(*c == '\n') 2503 | numLines++; 2504 | } 2505 | 2506 | // Actual height of letter region + line spacing 2507 | return font->height*numLines + font->lineSpacing*(numLines - 1); //height*numLines; 2508 | } 2509 | 2510 | Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...) 2511 | { 2512 | if(formatted_text == NULL || font == NULL) 2513 | return 0; 2514 | 2515 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2516 | 2517 | const char* c; 2518 | Uint16 width = 0; 2519 | Uint16 bigWidth = 0; // Allows for multi-line strings 2520 | 2521 | for (c = fc_buffer; *c != '\0'; c++) 2522 | { 2523 | if(*c == '\n') 2524 | { 2525 | bigWidth = bigWidth >= width? bigWidth : width; 2526 | width = 0; 2527 | continue; 2528 | } 2529 | 2530 | FC_GlyphData glyph; 2531 | Uint32 codepoint = FC_GetCodepointFromUTF8(&c, 1); 2532 | if(FC_GetGlyphData(font, &glyph, codepoint) || FC_GetGlyphData(font, &glyph, ' ')) 2533 | width += glyph.rect.w; 2534 | } 2535 | bigWidth = bigWidth >= width? bigWidth : width; 2536 | 2537 | return bigWidth; 2538 | } 2539 | 2540 | // If width == -1, use no width limit 2541 | FC_Rect FC_GetCharacterOffset(FC_Font* font, Uint16 position_index, int column_width, const char* formatted_text, ...) 2542 | { 2543 | FC_Rect result = {0, 0, 1, FC_GetLineHeight(font)}; 2544 | FC_StringList *ls, *iter; 2545 | int num_lines = 0; 2546 | Uint8 done = 0; 2547 | 2548 | if(formatted_text == NULL || column_width == 0 || position_index == 0 || font == NULL) 2549 | return result; 2550 | 2551 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2552 | 2553 | ls = FC_GetBufferFitToColumn(font, column_width, FC_MakeScale(1,1), 1); 2554 | for(iter = ls; iter != NULL;) 2555 | { 2556 | char* line; 2557 | int i = 0; 2558 | FC_StringList* next_iter = iter->next; 2559 | 2560 | ++num_lines; 2561 | for(line = iter->value; line != NULL && *line != '\0'; line = (char*)U8_next(line)) 2562 | { 2563 | ++i; 2564 | --position_index; 2565 | if(position_index == 0) 2566 | { 2567 | // FIXME: Doesn't handle box-wrapped newlines correctly 2568 | line = (char*)U8_next(line); 2569 | line[0] = '\0'; 2570 | result.x = FC_GetWidth(font, "%s", iter->value); 2571 | done = 1; 2572 | break; 2573 | } 2574 | } 2575 | if(done) 2576 | break; 2577 | 2578 | // Prevent line wrapping if there are no more lines 2579 | if(next_iter == NULL && !done) 2580 | result.x = FC_GetWidth(font, "%s", iter->value); 2581 | iter = next_iter; 2582 | } 2583 | FC_StringListFree(ls); 2584 | 2585 | if(num_lines > 1) 2586 | { 2587 | result.y = (num_lines - 1) * FC_GetLineHeight(font); 2588 | } 2589 | 2590 | return result; 2591 | } 2592 | 2593 | 2594 | Uint16 FC_GetColumnHeight(FC_Font* font, Uint16 width, const char* formatted_text, ...) 2595 | { 2596 | int y = 0; 2597 | 2598 | FC_StringList *ls, *iter; 2599 | 2600 | if(font == NULL) 2601 | return 0; 2602 | 2603 | if(formatted_text == NULL || width == 0) 2604 | return font->height; 2605 | 2606 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2607 | 2608 | ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0); 2609 | for(iter = ls; iter != NULL; iter = iter->next) 2610 | { 2611 | y += FC_GetLineHeight(font); 2612 | } 2613 | FC_StringListFree(ls); 2614 | 2615 | return y; 2616 | } 2617 | 2618 | static int FC_GetAscentFromCodepoint(FC_Font* font, Uint32 codepoint) 2619 | { 2620 | FC_GlyphData glyph; 2621 | 2622 | if(font == NULL) 2623 | return 0; 2624 | 2625 | // FIXME: Store ascent so we can return it here 2626 | FC_GetGlyphData(font, &glyph, codepoint); 2627 | return glyph.rect.h; 2628 | } 2629 | 2630 | static int FC_GetDescentFromCodepoint(FC_Font* font, Uint32 codepoint) 2631 | { 2632 | FC_GlyphData glyph; 2633 | 2634 | if(font == NULL) 2635 | return 0; 2636 | 2637 | // FIXME: Store descent so we can return it here 2638 | FC_GetGlyphData(font, &glyph, codepoint); 2639 | return glyph.rect.h; 2640 | } 2641 | 2642 | int FC_GetAscent(FC_Font* font, const char* formatted_text, ...) 2643 | { 2644 | Uint32 codepoint; 2645 | int max, ascent; 2646 | const char* c; 2647 | 2648 | if(font == NULL) 2649 | return 0; 2650 | 2651 | if(formatted_text == NULL) 2652 | return font->ascent; 2653 | 2654 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2655 | 2656 | max = 0; 2657 | c = fc_buffer; 2658 | 2659 | while(*c != '\0') 2660 | { 2661 | codepoint = FC_GetCodepointFromUTF8(&c, 1); 2662 | if(codepoint != 0) 2663 | { 2664 | ascent = FC_GetAscentFromCodepoint(font, codepoint); 2665 | if(ascent > max) 2666 | max = ascent; 2667 | } 2668 | ++c; 2669 | } 2670 | return max; 2671 | } 2672 | 2673 | int FC_GetDescent(FC_Font* font, const char* formatted_text, ...) 2674 | { 2675 | Uint32 codepoint; 2676 | int max, descent; 2677 | const char* c; 2678 | 2679 | if(font == NULL) 2680 | return 0; 2681 | 2682 | if(formatted_text == NULL) 2683 | return font->descent; 2684 | 2685 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2686 | 2687 | max = 0; 2688 | c = fc_buffer; 2689 | 2690 | while(*c != '\0') 2691 | { 2692 | codepoint = FC_GetCodepointFromUTF8(&c, 1); 2693 | if(codepoint != 0) 2694 | { 2695 | descent = FC_GetDescentFromCodepoint(font, codepoint); 2696 | if(descent > max) 2697 | max = descent; 2698 | } 2699 | ++c; 2700 | } 2701 | return max; 2702 | } 2703 | 2704 | int FC_GetBaseline(FC_Font* font) 2705 | { 2706 | if(font == NULL) 2707 | return 0; 2708 | 2709 | return font->baseline; 2710 | } 2711 | 2712 | int FC_GetSpacing(FC_Font* font) 2713 | { 2714 | if(font == NULL) 2715 | return 0; 2716 | 2717 | return font->letterSpacing; 2718 | } 2719 | 2720 | int FC_GetLineSpacing(FC_Font* font) 2721 | { 2722 | if(font == NULL) 2723 | return 0; 2724 | 2725 | return font->lineSpacing; 2726 | } 2727 | 2728 | Uint16 FC_GetMaxWidth(FC_Font* font) 2729 | { 2730 | if(font == NULL) 2731 | return 0; 2732 | 2733 | return font->maxWidth; 2734 | } 2735 | 2736 | SDL_Color FC_GetDefaultColor(FC_Font* font) 2737 | { 2738 | if(font == NULL) 2739 | { 2740 | SDL_Color c = {0,0,0,255}; 2741 | return c; 2742 | } 2743 | 2744 | return font->default_color; 2745 | } 2746 | 2747 | FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...) 2748 | { 2749 | FC_Rect result = {x, y, 0, 0}; 2750 | 2751 | if(formatted_text == NULL) 2752 | return result; 2753 | 2754 | // Create a temp buffer while GetWidth and GetHeight use fc_buffer. 2755 | char* temp = (char*)malloc(fc_buffer_size); 2756 | FC_EXTRACT_VARARGS(temp, formatted_text); 2757 | 2758 | result.w = FC_GetWidth(font, "%s", temp) * scale.x; 2759 | result.h = FC_GetHeight(font, "%s", temp) * scale.y; 2760 | 2761 | switch(align) 2762 | { 2763 | case FC_ALIGN_LEFT: 2764 | break; 2765 | case FC_ALIGN_CENTER: 2766 | result.x -= result.w/2; 2767 | break; 2768 | case FC_ALIGN_RIGHT: 2769 | result.x -= result.w; 2770 | break; 2771 | default: 2772 | break; 2773 | } 2774 | 2775 | free(temp); 2776 | 2777 | return result; 2778 | } 2779 | 2780 | Uint8 FC_InRect(float x, float y, FC_Rect input_rect) 2781 | { 2782 | return (input_rect.x <= x && x <= input_rect.x + input_rect.w && input_rect.y <= y && y <= input_rect.y + input_rect.h); 2783 | } 2784 | 2785 | // TODO: Make it work with alignment 2786 | Uint16 FC_GetPositionFromOffset(FC_Font* font, float x, float y, int column_width, FC_AlignEnum align, const char* formatted_text, ...) 2787 | { 2788 | FC_StringList *ls, *iter; 2789 | Uint8 done = 0; 2790 | int height = FC_GetLineHeight(font); 2791 | Uint16 position = 0; 2792 | int current_x = 0; 2793 | int current_y = 0; 2794 | FC_GlyphData glyph_data; 2795 | 2796 | if(formatted_text == NULL || column_width == 0 || font == NULL) 2797 | return 0; 2798 | 2799 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2800 | 2801 | ls = FC_GetBufferFitToColumn(font, column_width, FC_MakeScale(1,1), 1); 2802 | for(iter = ls; iter != NULL; iter = iter->next) 2803 | { 2804 | char* line; 2805 | 2806 | for(line = iter->value; line != NULL && *line != '\0'; line = (char*)U8_next(line)) 2807 | { 2808 | if(FC_GetGlyphData(font, &glyph_data, FC_GetCodepointFromUTF8((const char**)&line, 0))) 2809 | { 2810 | if(FC_InRect(x, y, FC_MakeRect(current_x, current_y, glyph_data.rect.w, glyph_data.rect.h))) 2811 | { 2812 | done = 1; 2813 | break; 2814 | } 2815 | 2816 | current_x += glyph_data.rect.w; 2817 | } 2818 | position++; 2819 | } 2820 | if(done) 2821 | break; 2822 | 2823 | current_x = 0; 2824 | current_y += height; 2825 | if(y < current_y) 2826 | break; 2827 | } 2828 | FC_StringListFree(ls); 2829 | 2830 | return position; 2831 | } 2832 | 2833 | int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, const char* formatted_text, ...) 2834 | { 2835 | FC_StringList *ls, *iter; 2836 | 2837 | if(font == NULL) 2838 | return 0; 2839 | 2840 | if(formatted_text == NULL || width == 0) 2841 | return 0; 2842 | 2843 | FC_EXTRACT_VARARGS(fc_buffer, formatted_text); 2844 | 2845 | ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0); 2846 | int size_so_far = 0; 2847 | int size_remaining = max_result_size-1; // reserve for \0 2848 | for(iter = ls; iter != NULL && size_remaining > 0; iter = iter->next) 2849 | { 2850 | // Copy as much of this line as we can 2851 | int len = strlen(iter->value); 2852 | int num_bytes = FC_MIN(len, size_remaining); 2853 | memcpy(&result[size_so_far], iter->value, num_bytes); 2854 | size_so_far += num_bytes; 2855 | 2856 | // If there's another line, add newline character 2857 | if(size_remaining > 0 && iter->next != NULL) 2858 | { 2859 | --size_remaining; 2860 | result[size_so_far] = '\n'; 2861 | ++size_so_far; 2862 | } 2863 | } 2864 | FC_StringListFree(ls); 2865 | 2866 | result[size_so_far] = '\0'; 2867 | 2868 | return size_so_far; 2869 | } 2870 | 2871 | 2872 | 2873 | // Setters 2874 | 2875 | 2876 | void FC_SetFilterMode(FC_Font* font, FC_FilterEnum filter) 2877 | { 2878 | if(font == NULL) 2879 | return; 2880 | 2881 | if(font->filter != filter) 2882 | { 2883 | font->filter = filter; 2884 | 2885 | #ifdef FC_USE_SDL_GPU 2886 | // Update each texture to use this filter mode 2887 | { 2888 | int i; 2889 | GPU_FilterEnum gpu_filter = GPU_FILTER_NEAREST; 2890 | if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) 2891 | gpu_filter = GPU_FILTER_LINEAR; 2892 | 2893 | for(i = 0; i < font->glyph_cache_count; ++i) 2894 | { 2895 | GPU_SetImageFilter(font->glyph_cache[i], gpu_filter); 2896 | } 2897 | } 2898 | #endif 2899 | } 2900 | } 2901 | 2902 | 2903 | void FC_SetSpacing(FC_Font* font, int LetterSpacing) 2904 | { 2905 | if(font == NULL) 2906 | return; 2907 | 2908 | font->letterSpacing = LetterSpacing; 2909 | } 2910 | 2911 | void FC_SetLineSpacing(FC_Font* font, int LineSpacing) 2912 | { 2913 | if(font == NULL) 2914 | return; 2915 | 2916 | font->lineSpacing = LineSpacing; 2917 | } 2918 | 2919 | void FC_SetDefaultColor(FC_Font* font, SDL_Color color) 2920 | { 2921 | if(font == NULL) 2922 | return; 2923 | 2924 | font->default_color = color; 2925 | } 2926 | -------------------------------------------------------------------------------- /source/SDL_FontCache.h: -------------------------------------------------------------------------------- 1 | /* 2 | SDL_FontCache v0.10.0: A font cache for SDL and SDL_ttf 3 | by Jonathan Dearborn 4 | Dedicated to the memory of Florian Hufsky 5 | 6 | License: 7 | The short: 8 | Use it however you'd like, but keep the copyright and license notice 9 | whenever these files or parts of them are distributed in uncompiled form. 10 | 11 | The long: 12 | Copyright (c) 2019 Jonathan Dearborn 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | THE SOFTWARE. 31 | */ 32 | 33 | #ifndef _SDL_FONTCACHE_H__ 34 | #define _SDL_FONTCACHE_H__ 35 | 36 | #include "SDL.h" 37 | #include "SDL_ttf.h" 38 | 39 | #ifdef FC_USE_SDL_GPU 40 | #include "SDL_gpu.h" 41 | #endif 42 | 43 | 44 | #include 45 | 46 | #ifdef __cplusplus 47 | extern "C" { 48 | #endif 49 | 50 | 51 | // Let's pretend this exists... 52 | #define TTF_STYLE_OUTLINE 16 53 | 54 | 55 | 56 | // Differences between SDL_Renderer and SDL_gpu 57 | #ifdef FC_USE_SDL_GPU 58 | #define FC_Rect GPU_Rect 59 | #define FC_Target GPU_Target 60 | #define FC_Image GPU_Image 61 | #define FC_Log GPU_LogError 62 | #else 63 | #define FC_Rect SDL_Rect 64 | #define FC_Target SDL_Renderer 65 | #define FC_Image SDL_Texture 66 | #define FC_Log SDL_Log 67 | #endif 68 | 69 | 70 | // SDL_FontCache types 71 | 72 | typedef enum 73 | { 74 | FC_ALIGN_LEFT, 75 | FC_ALIGN_CENTER, 76 | FC_ALIGN_RIGHT 77 | } FC_AlignEnum; 78 | 79 | typedef enum 80 | { 81 | FC_FILTER_NEAREST, 82 | FC_FILTER_LINEAR 83 | } FC_FilterEnum; 84 | 85 | typedef struct FC_Scale 86 | { 87 | float x; 88 | float y; 89 | 90 | } FC_Scale; 91 | 92 | typedef struct FC_Effect 93 | { 94 | FC_AlignEnum alignment; 95 | FC_Scale scale; 96 | SDL_Color color; 97 | 98 | } FC_Effect; 99 | 100 | // Opaque type 101 | typedef struct FC_Font FC_Font; 102 | 103 | 104 | typedef struct FC_GlyphData 105 | { 106 | SDL_Rect rect; 107 | int cache_level; 108 | 109 | } FC_GlyphData; 110 | 111 | 112 | 113 | 114 | // Object creation 115 | 116 | FC_Rect FC_MakeRect(float x, float y, float w, float h); 117 | 118 | FC_Scale FC_MakeScale(float x, float y); 119 | 120 | SDL_Color FC_MakeColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a); 121 | 122 | FC_Effect FC_MakeEffect(FC_AlignEnum alignment, FC_Scale scale, SDL_Color color); 123 | 124 | FC_GlyphData FC_MakeGlyphData(int cache_level, Sint16 x, Sint16 y, Uint16 w, Uint16 h); 125 | 126 | 127 | 128 | // Font object 129 | 130 | FC_Font* FC_CreateFont(void); 131 | 132 | #ifdef FC_USE_SDL_GPU 133 | Uint8 FC_LoadFont(FC_Font* font, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style); 134 | 135 | Uint8 FC_LoadFontFromTTF(FC_Font* font, TTF_Font* ttf, SDL_Color color); 136 | 137 | Uint8 FC_LoadFont_RW(FC_Font* font, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style); 138 | #else 139 | Uint8 FC_LoadFont(FC_Font* font, SDL_Renderer* renderer, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style); 140 | 141 | Uint8 FC_LoadFontFromTTF(FC_Font* font, SDL_Renderer* renderer, TTF_Font* ttf, SDL_Color color); 142 | 143 | Uint8 FC_LoadFont_RW(FC_Font* font, SDL_Renderer* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style); 144 | #endif 145 | 146 | #ifndef FC_USE_SDL_GPU 147 | // note: handle SDL event types SDL_RENDER_TARGETS_RESET(>= SDL 2.0.2) and SDL_RENDER_DEVICE_RESET(>= SDL 2.0.4) 148 | void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType); 149 | #endif 150 | 151 | void FC_ClearFont(FC_Font* font); 152 | 153 | void FC_FreeFont(FC_Font* font); 154 | 155 | 156 | 157 | // Built-in loading strings 158 | 159 | char* FC_GetStringASCII(void); 160 | 161 | char* FC_GetStringLatin1(void); 162 | 163 | char* FC_GetStringASCII_Latin1(void); 164 | 165 | 166 | // UTF-8 to SDL_FontCache codepoint conversion 167 | 168 | /*! 169 | Returns the Uint32 codepoint (not UTF-32) parsed from the given UTF-8 string. 170 | \param c A pointer to a string of proper UTF-8 character values. 171 | \param advance_pointer If true, the source pointer will be incremented to skip the extra bytes from multibyte codepoints. 172 | */ 173 | Uint32 FC_GetCodepointFromUTF8(const char** c, Uint8 advance_pointer); 174 | 175 | /*! 176 | Parses the given codepoint and stores the UTF-8 bytes in 'result'. The result is NULL terminated. 177 | \param result A memory buffer for the UTF-8 values. Must be at least 5 bytes long. 178 | \param codepoint The Uint32 codepoint to parse (not UTF-32). 179 | */ 180 | void FC_GetUTF8FromCodepoint(char* result, Uint32 codepoint); 181 | 182 | 183 | // UTF-8 string operations 184 | 185 | /*! Allocates a new string of 'size' bytes that is already NULL-terminated. The NULL byte counts toward the size limit, as usual. Returns NULL if size is 0. */ 186 | char* U8_alloc(unsigned int size); 187 | 188 | /*! Deallocates the given string. */ 189 | void U8_free(char* string); 190 | 191 | /*! Allocates a copy of the given string. */ 192 | char* U8_strdup(const char* string); 193 | 194 | /*! Returns the number of UTF-8 characters in the given string. */ 195 | int U8_strlen(const char* string); 196 | 197 | /*! Returns the number of bytes in the UTF-8 multibyte character pointed at by 'character'. */ 198 | int U8_charsize(const char* character); 199 | 200 | /*! Copies the source multibyte character into the given buffer without overrunning it. Returns 0 on failure. */ 201 | int U8_charcpy(char* buffer, const char* source, int buffer_size); 202 | 203 | /*! Returns a pointer to the next UTF-8 character. */ 204 | const char* U8_next(const char* string); 205 | 206 | /*! Inserts a UTF-8 string into 'string' at the given position. Use a position of -1 to append. Returns 0 when unable to insert the string. */ 207 | int U8_strinsert(char* string, int position, const char* source, int max_bytes); 208 | 209 | /*! Erases the UTF-8 character at the given position, moving the subsequent characters down. */ 210 | void U8_strdel(char* string, int position); 211 | 212 | 213 | // Internal settings 214 | 215 | /*! Sets the string from which to load the initial glyphs. Use this if you need upfront loading for any reason (such as lack of render-target support). */ 216 | void FC_SetLoadingString(FC_Font* font, const char* string); 217 | 218 | /*! Returns the size of the internal buffer which is used for unpacking variadic text data. This buffer is shared by all FC_Fonts. */ 219 | unsigned int FC_GetBufferSize(void); 220 | 221 | /*! Changes the size of the internal buffer which is used for unpacking variadic text data. This buffer is shared by all FC_Fonts. */ 222 | void FC_SetBufferSize(unsigned int size); 223 | 224 | /*! Returns the width of a single horizontal tab in multiples of the width of a space (default: 4) */ 225 | unsigned int FC_GetTabWidth(void); 226 | 227 | /*! Changes the width of a horizontal tab in multiples of the width of a space (default: 4) */ 228 | void FC_SetTabWidth(unsigned int width_in_spaces); 229 | 230 | void FC_SetRenderCallback(FC_Rect (*callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale)); 231 | 232 | FC_Rect FC_DefaultRenderCallback(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale); 233 | 234 | 235 | // Custom caching 236 | 237 | /*! Returns the number of cache levels that are active. */ 238 | int FC_GetNumCacheLevels(FC_Font* font); 239 | 240 | /*! Returns the cache source texture at the given cache level. */ 241 | FC_Image* FC_GetGlyphCacheLevel(FC_Font* font, int cache_level); 242 | 243 | // TODO: Specify ownership of the texture (should be shareable) 244 | /*! Sets a cache source texture for rendering. New cache levels must be sequential. */ 245 | Uint8 FC_SetGlyphCacheLevel(FC_Font* font, int cache_level, FC_Image* cache_texture); 246 | 247 | /*! Copies the given surface to the given cache level as a texture. New cache levels must be sequential. */ 248 | Uint8 FC_UploadGlyphCache(FC_Font* font, int cache_level, SDL_Surface* data_surface); 249 | 250 | 251 | /*! Returns the number of codepoints that are stored in the font's glyph data map. */ 252 | unsigned int FC_GetNumCodepoints(FC_Font* font); 253 | 254 | /*! Copies the stored codepoints into the given array. */ 255 | void FC_GetCodepoints(FC_Font* font, Uint32* result); 256 | 257 | /*! Stores the glyph data for the given codepoint in 'result'. Returns 0 if the codepoint was not found in the cache. */ 258 | Uint8 FC_GetGlyphData(FC_Font* font, FC_GlyphData* result, Uint32 codepoint); 259 | 260 | /*! Sets the glyph data for the given codepoint. Duplicates are not checked. Returns a pointer to the stored data. */ 261 | FC_GlyphData* FC_SetGlyphData(FC_Font* font, Uint32 codepoint, FC_GlyphData glyph_data); 262 | 263 | 264 | // Rendering 265 | 266 | FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...); 267 | FC_Rect FC_DrawAlign(FC_Font* font, FC_Target* dest, float x, float y, FC_AlignEnum align, const char* formatted_text, ...); 268 | FC_Rect FC_DrawScale(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* formatted_text, ...); 269 | FC_Rect FC_DrawColor(FC_Font* font, FC_Target* dest, float x, float y, SDL_Color color, const char* formatted_text, ...); 270 | FC_Rect FC_DrawEffect(FC_Font* font, FC_Target* dest, float x, float y, FC_Effect effect, const char* formatted_text, ...); 271 | 272 | FC_Rect FC_DrawBox(FC_Font* font, FC_Target* dest, FC_Rect box, const char* formatted_text, ...); 273 | FC_Rect FC_DrawBoxAlign(FC_Font* font, FC_Target* dest, FC_Rect box, FC_AlignEnum align, const char* formatted_text, ...); 274 | FC_Rect FC_DrawBoxScale(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Scale scale, const char* formatted_text, ...); 275 | FC_Rect FC_DrawBoxColor(FC_Font* font, FC_Target* dest, FC_Rect box, SDL_Color color, const char* formatted_text, ...); 276 | FC_Rect FC_DrawBoxEffect(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Effect effect, const char* formatted_text, ...); 277 | 278 | FC_Rect FC_DrawColumn(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...); 279 | FC_Rect FC_DrawColumnAlign(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_AlignEnum align, const char* formatted_text, ...); 280 | FC_Rect FC_DrawColumnScale(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Scale scale, const char* formatted_text, ...); 281 | FC_Rect FC_DrawColumnColor(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, SDL_Color color, const char* formatted_text, ...); 282 | FC_Rect FC_DrawColumnEffect(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Effect effect, const char* formatted_text, ...); 283 | 284 | 285 | // Getters 286 | 287 | FC_FilterEnum FC_GetFilterMode(FC_Font* font); 288 | Uint16 FC_GetLineHeight(FC_Font* font); 289 | Uint16 FC_GetHeight(FC_Font* font, const char* formatted_text, ...); 290 | Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...); 291 | 292 | // Returns a 1-pixel wide box in front of the character in the given position (index) 293 | FC_Rect FC_GetCharacterOffset(FC_Font* font, Uint16 position_index, int column_width, const char* formatted_text, ...); 294 | Uint16 FC_GetColumnHeight(FC_Font* font, Uint16 width, const char* formatted_text, ...); 295 | 296 | int FC_GetAscent(FC_Font* font, const char* formatted_text, ...); 297 | int FC_GetDescent(FC_Font* font, const char* formatted_text, ...); 298 | int FC_GetBaseline(FC_Font* font); 299 | int FC_GetSpacing(FC_Font* font); 300 | int FC_GetLineSpacing(FC_Font* font); 301 | Uint16 FC_GetMaxWidth(FC_Font* font); 302 | SDL_Color FC_GetDefaultColor(FC_Font* font); 303 | 304 | FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...); 305 | 306 | Uint8 FC_InRect(float x, float y, FC_Rect input_rect); 307 | // Given an offset (x,y) from the text draw position (the upper-left corner), returns the character position (UTF-8 index) 308 | Uint16 FC_GetPositionFromOffset(FC_Font* font, float x, float y, int column_width, FC_AlignEnum align, const char* formatted_text, ...); 309 | 310 | // Returns the number of characters in the new wrapped text written into `result`. 311 | int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, const char* formatted_text, ...); 312 | 313 | // Setters 314 | 315 | void FC_SetFilterMode(FC_Font* font, FC_FilterEnum filter); 316 | void FC_SetSpacing(FC_Font* font, int LetterSpacing); 317 | void FC_SetLineSpacing(FC_Font* font, int LineSpacing); 318 | void FC_SetDefaultColor(FC_Font* font, SDL_Color color); 319 | 320 | 321 | #ifdef __cplusplus 322 | } 323 | #endif 324 | 325 | 326 | 327 | #endif 328 | -------------------------------------------------------------------------------- /source/Screen.cpp: -------------------------------------------------------------------------------- 1 | #include "Screen.hpp" 2 | #include "Gfx.hpp" 3 | 4 | void Screen::DrawTopBar(const char* name) 5 | { 6 | // draw top bar 7 | Gfx::DrawRectFilled(0, 0, Gfx::SCREEN_WIDTH, 75, Gfx::COLOR_BARS); 8 | 9 | // draw top bar content 10 | Gfx::DrawIcon(32, 75 / 2, 60, Gfx::COLOR_TEXT, 0xf002, Gfx::ALIGN_VERTICAL); 11 | Gfx::Print(128, 75 / 2, 60, Gfx::COLOR_TEXT, "WiiUIdent", Gfx::ALIGN_VERTICAL); 12 | Gfx::Print(Gfx::GetTextWidth(60, "WiiUIdent") + 128 + 16, 75 / 2 + 5, 50, Gfx::COLOR_ALT_TEXT, "v" APP_VERSION, Gfx::ALIGN_VERTICAL); 13 | if (name) 14 | Gfx::Print(Gfx::SCREEN_WIDTH - 32, 75 / 2, 50, Gfx::COLOR_ALT_TEXT, name, Gfx::ALIGN_VERTICAL | Gfx::ALIGN_RIGHT); 15 | } 16 | 17 | void Screen::DrawBottomBar(const char* leftHint, const char* centerHint, const char* rightHint) 18 | { 19 | // draw bottom bar 20 | Gfx::DrawRectFilled(0, Gfx::SCREEN_HEIGHT - 75, Gfx::SCREEN_WIDTH, 75, Gfx::COLOR_BARS); 21 | 22 | // draw bottom bar content 23 | if (leftHint) 24 | Gfx::Print(32, Gfx::SCREEN_HEIGHT - 75 / 2, 50, Gfx::COLOR_TEXT, leftHint, Gfx::ALIGN_VERTICAL); 25 | if (centerHint) 26 | Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT - 75 / 2, 50, Gfx::COLOR_TEXT, centerHint, Gfx::ALIGN_CENTER); 27 | if (rightHint) 28 | Gfx::Print(Gfx::SCREEN_WIDTH - 32, Gfx::SCREEN_HEIGHT - 78 / 2, 50, Gfx::COLOR_TEXT, rightHint, Gfx::ALIGN_VERTICAL | Gfx::ALIGN_RIGHT); 29 | } 30 | 31 | int Screen::DrawHeader(int x, int y, int w, uint16_t icon, const char* text) 32 | { 33 | const int iconWidth = Gfx::GetIconWidth(50, icon); 34 | const int width = iconWidth + 32 + Gfx::GetTextWidth(50, text); 35 | const int xStart = x + (w / 2) - (width / 2); 36 | 37 | Gfx::DrawIcon(xStart, y, 50, Gfx::COLOR_TEXT, icon, Gfx::ALIGN_VERTICAL); 38 | Gfx::Print(xStart + iconWidth + 32, y, 50, Gfx::COLOR_TEXT, text, Gfx::ALIGN_VERTICAL); 39 | Gfx::DrawRectFilled(x, y + 32, w, 4, Gfx::COLOR_ACCENT); 40 | 41 | return y + 64; 42 | } 43 | 44 | int Screen::DrawList(int x, int y, int w, ScreenList items) 45 | { 46 | int yOff = y; 47 | for (const auto& item : items) { 48 | Gfx::Print(x + 16, yOff, 40, Gfx::COLOR_TEXT, item.first, Gfx::ALIGN_VERTICAL); 49 | Gfx::Print(x + w - 16, yOff, 40, Gfx::COLOR_TEXT, item.second.string, Gfx::ALIGN_VERTICAL | Gfx::ALIGN_RIGHT, item.second.monospace); 50 | yOff += std::max(Gfx::GetTextHeight(40, item.first), Gfx::GetTextHeight(40, item.second.string, item.second.monospace)); 51 | } 52 | 53 | return yOff + 32; 54 | } 55 | -------------------------------------------------------------------------------- /source/Screen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class Screen 9 | { 10 | public: 11 | Screen() = default; 12 | virtual ~Screen() = default; 13 | 14 | virtual void Draw() = 0; 15 | 16 | virtual bool Update(VPADStatus& input) = 0; 17 | 18 | protected: 19 | void DrawTopBar(const char* name); 20 | 21 | void DrawBottomBar(const char* leftHint, const char* centerHint, const char* rightHint); 22 | 23 | int DrawHeader(int x, int y, int w, uint16_t icon, const char* text); 24 | 25 | struct ScreenListElement 26 | { 27 | ScreenListElement(std::string string, bool monospace = false) 28 | : string(string), monospace(monospace) {} 29 | ScreenListElement(const char* string, bool monospace = false) 30 | : string(string), monospace(monospace) {} 31 | 32 | std::string string; 33 | bool monospace; 34 | }; 35 | using ScreenList = std::vector>; 36 | 37 | int DrawList(int x, int y, int w, ScreenList items); 38 | 39 | private: 40 | }; 41 | -------------------------------------------------------------------------------- /source/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "Utils.hpp" 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | namespace Utils 8 | { 9 | 10 | std::string ToHexString(const void* data, size_t size) 11 | { 12 | std::string str; 13 | for (size_t i = 0; i < size; ++i) 14 | str += Utils::sprintf("%02x", ((const uint8_t*) data)[i]); 15 | 16 | return str; 17 | } 18 | 19 | int AESDecrypt(const void* key, unsigned keybits, const void* iv, const void* in, void* out, size_t dataSize) 20 | { 21 | int res; 22 | mbedtls_aes_context ctx; 23 | mbedtls_aes_init(&ctx); 24 | 25 | if ((res = mbedtls_aes_setkey_dec(&ctx, (const unsigned char*)key, keybits)) != 0) { 26 | mbedtls_aes_free(&ctx); 27 | return res; 28 | } 29 | 30 | unsigned char tmpiv[16]; 31 | if (iv) { 32 | memcpy(tmpiv, iv, sizeof(tmpiv)); 33 | } else { 34 | memset(tmpiv, 0, sizeof(tmpiv)); 35 | } 36 | 37 | res = mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_DECRYPT, dataSize, tmpiv, (const unsigned char*)in, (unsigned char*)out); 38 | mbedtls_aes_free(&ctx); 39 | return res; 40 | } 41 | 42 | int SHA256(const void* in, size_t inSize, void* out, size_t outSize) 43 | { 44 | if (outSize != 32) { 45 | return -1; 46 | } 47 | 48 | int res; 49 | mbedtls_sha256_context ctx; 50 | mbedtls_sha256_init(&ctx); 51 | 52 | if ((res = mbedtls_sha256_starts_ret(&ctx, 0)) != 0) { 53 | mbedtls_sha256_free(&ctx); 54 | return res; 55 | } 56 | 57 | if ((res = mbedtls_sha256_update_ret(&ctx, (const unsigned char*)in, inSize)) != 0) { 58 | mbedtls_sha256_free(&ctx); 59 | return res; 60 | } 61 | 62 | res = mbedtls_sha256_finish_ret(&ctx, (unsigned char*)out); 63 | mbedtls_sha256_free(&ctx); 64 | return res; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /source/Utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Utils 7 | { 8 | 9 | template 10 | std::string sprintf(const std::string& format, Args ...args) 11 | { 12 | int size = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1; 13 | 14 | std::unique_ptr buf(new char[size]); 15 | std::snprintf(buf.get(), size, format.c_str(), args ...); 16 | 17 | return std::string(buf.get(), buf.get() + size - 1); 18 | } 19 | 20 | std::string ToHexString(const void* data, size_t size); 21 | 22 | int AESDecrypt(const void* key, uint32_t keybits, const void* iv, const void* in, void* out, size_t dataSize); 23 | 24 | int SHA256(const void* in, size_t inSize, void* out, size_t outSize); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Gfx.hpp" 2 | #include "screens/MainScreen.hpp" 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace 13 | { 14 | 15 | inline bool RunningFromMiiMaker() 16 | { 17 | return (OSGetTitleID() & 0xFFFFFFFFFFFFF0FFull) == 0x000500101004A000ull; 18 | } 19 | 20 | 21 | uint32_t RemapWiiMoteButtons(uint32_t buttons) 22 | { 23 | uint32_t conv_buttons = 0; 24 | 25 | if (buttons & WPAD_BUTTON_LEFT) 26 | conv_buttons |= VPAD_BUTTON_LEFT; 27 | 28 | if (buttons & WPAD_BUTTON_RIGHT) 29 | conv_buttons |= VPAD_BUTTON_RIGHT; 30 | 31 | if (buttons & WPAD_BUTTON_DOWN) 32 | conv_buttons |= VPAD_BUTTON_DOWN; 33 | 34 | if (buttons & WPAD_BUTTON_UP) 35 | conv_buttons |= VPAD_BUTTON_UP; 36 | 37 | if (buttons & WPAD_BUTTON_PLUS) 38 | conv_buttons |= VPAD_BUTTON_PLUS; 39 | 40 | if (buttons & WPAD_BUTTON_B) 41 | conv_buttons |= VPAD_BUTTON_B; 42 | 43 | if (buttons & WPAD_BUTTON_A) 44 | conv_buttons |= VPAD_BUTTON_A; 45 | 46 | if (buttons & WPAD_BUTTON_MINUS) 47 | conv_buttons |= VPAD_BUTTON_MINUS; 48 | 49 | if (buttons & WPAD_BUTTON_HOME) 50 | conv_buttons |= VPAD_BUTTON_HOME; 51 | 52 | return conv_buttons; 53 | } 54 | 55 | uint32_t RemapClassicButtons(uint32_t buttons) 56 | { 57 | uint32_t conv_buttons = 0; 58 | 59 | if (buttons & WPAD_CLASSIC_BUTTON_LEFT) 60 | conv_buttons |= VPAD_BUTTON_LEFT; 61 | 62 | if (buttons & WPAD_CLASSIC_BUTTON_RIGHT) 63 | conv_buttons |= VPAD_BUTTON_RIGHT; 64 | 65 | if (buttons & WPAD_CLASSIC_BUTTON_DOWN) 66 | conv_buttons |= VPAD_BUTTON_DOWN; 67 | 68 | if (buttons & WPAD_CLASSIC_BUTTON_UP) 69 | conv_buttons |= VPAD_BUTTON_UP; 70 | 71 | if (buttons & WPAD_CLASSIC_BUTTON_PLUS) 72 | conv_buttons |= VPAD_BUTTON_PLUS; 73 | 74 | if (buttons & WPAD_CLASSIC_BUTTON_X) 75 | conv_buttons |= VPAD_BUTTON_X; 76 | 77 | if (buttons & WPAD_CLASSIC_BUTTON_Y) 78 | conv_buttons |= VPAD_BUTTON_Y; 79 | 80 | if (buttons & WPAD_CLASSIC_BUTTON_B) 81 | conv_buttons |= VPAD_BUTTON_B; 82 | 83 | if (buttons & WPAD_CLASSIC_BUTTON_A) 84 | conv_buttons |= VPAD_BUTTON_A; 85 | 86 | if (buttons & WPAD_CLASSIC_BUTTON_MINUS) 87 | conv_buttons |= VPAD_BUTTON_MINUS; 88 | 89 | if (buttons & WPAD_CLASSIC_BUTTON_HOME) 90 | conv_buttons |= VPAD_BUTTON_HOME; 91 | 92 | if (buttons & WPAD_CLASSIC_BUTTON_ZR) 93 | conv_buttons |= VPAD_BUTTON_ZR; 94 | 95 | if (buttons & WPAD_CLASSIC_BUTTON_ZL) 96 | conv_buttons |= VPAD_BUTTON_ZL; 97 | 98 | if (buttons & WPAD_CLASSIC_BUTTON_R) 99 | conv_buttons |= VPAD_BUTTON_R; 100 | 101 | if (buttons & WPAD_CLASSIC_BUTTON_L) 102 | conv_buttons |= VPAD_BUTTON_L; 103 | 104 | return conv_buttons; 105 | } 106 | 107 | void UpdatePads(VPADStatus* status) 108 | { 109 | KPADStatus kpad_data{}; 110 | KPADError kpad_error; 111 | for (int i = 0; i < 4; i++) { 112 | if (KPADReadEx((KPADChan) i, &kpad_data, 1, &kpad_error) > 0) { 113 | if (kpad_error == KPAD_ERROR_OK && kpad_data.extensionType != 0xFF) { 114 | if (kpad_data.extensionType == WPAD_EXT_CORE || kpad_data.extensionType == WPAD_EXT_NUNCHUK) { 115 | status->trigger |= RemapWiiMoteButtons(kpad_data.trigger); 116 | status->release |= RemapWiiMoteButtons(kpad_data.release); 117 | status->hold |= RemapWiiMoteButtons(kpad_data.hold); 118 | } else { 119 | status->trigger |= RemapClassicButtons(kpad_data.classic.trigger); 120 | status->release |= RemapClassicButtons(kpad_data.classic.release); 121 | status->hold |= RemapClassicButtons(kpad_data.classic.hold); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | } 129 | 130 | int main(int argc, char const* argv[]) 131 | { 132 | WHBProcInit(); 133 | 134 | // call AXInit to stop already playing sounds 135 | AXInit(); 136 | 137 | KPADInit(); 138 | WPADEnableURCC(TRUE); 139 | 140 | Gfx::Init(); 141 | 142 | std::unique_ptr mainScreen = std::make_unique(); 143 | 144 | while (WHBProcIsRunning()) { 145 | VPADStatus input{}; 146 | VPADRead(VPAD_CHAN_0, &input, 1, nullptr); 147 | UpdatePads(&input); 148 | 149 | if (!mainScreen->Update(input)) { 150 | // screen requested quit 151 | if (RunningFromMiiMaker()) { 152 | // legacy way, just quit 153 | break; 154 | } else { 155 | // launch menu otherwise 156 | SYSLaunchMenu(); 157 | } 158 | } 159 | 160 | mainScreen->Draw(); 161 | Gfx::Render(); 162 | } 163 | 164 | mainScreen.reset(); 165 | 166 | Gfx::Shutdown(); 167 | 168 | AXQuit(); 169 | 170 | WHBProcShutdown(); 171 | return 0; 172 | } 173 | -------------------------------------------------------------------------------- /source/screens/AboutScreen.cpp: -------------------------------------------------------------------------------- 1 | #include "AboutScreen.hpp" 2 | 3 | AboutScreen::AboutScreen() 4 | { 5 | creditList.push_back({"Developers:", "GaryOderNichts"}); 6 | creditList.push_back({"", "GerbilSoft"}); 7 | 8 | fontList.push_back({"Main Font:", "Wii U System Font"}); 9 | fontList.push_back({"Icon Font:", "FontAwesome"}); 10 | fontList.push_back({"Monospace Font:", "Terminus Font"}); 11 | 12 | linkList.push_back({"GitHub:", ""}); 13 | linkList.push_back({"", {"github.com/GaryOderNichts/WiiUIdent", true}}); 14 | linkList.push_back({"System Database:", {DATABASE_URL, true}}); 15 | } 16 | 17 | AboutScreen::~AboutScreen() 18 | { 19 | } 20 | 21 | void AboutScreen::Draw() 22 | { 23 | DrawTopBar("About"); 24 | 25 | int yOff = 128; 26 | yOff = DrawHeader(32, yOff, 896, 0xf121, "Credits"); 27 | yOff = DrawList(32, yOff, 896, creditList); 28 | yOff = DrawHeader(32, yOff, 896, 0xf031, "Fonts"); 29 | yOff = DrawList(32, yOff, 896, fontList); 30 | 31 | yOff = 128; 32 | yOff = DrawHeader(992, yOff, 896, 0xf08e, "Links"); 33 | yOff = DrawList(992, yOff, 896, linkList); 34 | 35 | DrawBottomBar(nullptr, "\ue044 Exit", "\ue001 Back"); 36 | } 37 | 38 | bool AboutScreen::Update(VPADStatus& input) 39 | { 40 | if (input.trigger & VPAD_BUTTON_B) { 41 | return false; 42 | } 43 | 44 | return true; 45 | } 46 | -------------------------------------------------------------------------------- /source/screens/AboutScreen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Screen.hpp" 4 | 5 | class AboutScreen : public Screen 6 | { 7 | public: 8 | AboutScreen(); 9 | virtual ~AboutScreen(); 10 | 11 | void Draw(); 12 | 13 | bool Update(VPADStatus& input); 14 | 15 | private: 16 | ScreenList creditList; 17 | ScreenList fontList; 18 | ScreenList linkList; 19 | }; 20 | -------------------------------------------------------------------------------- /source/screens/DRXInfoScreen.cpp: -------------------------------------------------------------------------------- 1 | #include "DRXInfoScreen.hpp" 2 | #include "Utils.hpp" 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace { 10 | 11 | bool GetEepromValue(uint32_t offset, std::span value) 12 | { 13 | uint8_t data[value.size() + 2]; 14 | if (CCRCFGGetCachedEeprom(0, offset, data, value.size() + 2) != 0) { 15 | return false; 16 | } 17 | 18 | uint16_t crc = (uint16_t) data[value.size() + 1] << 8 | data[value.size()]; 19 | if (CCRCDCCalcCRC16(data, value.size()) != crc) { 20 | return false; 21 | } 22 | 23 | std::copy(data, data + value.size(), value.begin()); 24 | return true; 25 | } 26 | 27 | // from gamepad firmare @0x000b2990 28 | const char* kBoardMainVersions[] = { 29 | "DK1", 30 | "DK1 / EP / DK2", 31 | "DP1", 32 | "DP2", 33 | "DK3", 34 | "DK4", 35 | "PreDP3 / DP3", 36 | "DK5", 37 | "DP4", 38 | "DKMP", 39 | "DP5", 40 | "MASS", 41 | "DKMP2", 42 | "DRC-I", 43 | "DKTVMP", 44 | }; 45 | 46 | // from gamepad firmare @0x000b29cc 47 | const char* kBoardSubVersions[] = { 48 | "DK1 / EP / DK2", 49 | "DP1 / DK3", 50 | "DK4", 51 | "DP3", 52 | "DK5", 53 | "DP4", 54 | "DKMP", 55 | "DP5", 56 | "MASS", 57 | "DKMP2", 58 | "DRC-I", 59 | "DKTVMP" 60 | }; 61 | 62 | // from gamepad firmware @0x00020bc0 63 | const std::map kChipVersions = { 64 | { 0x10, "TS" }, 65 | { 0x20, "ES1" }, 66 | { 0x30, "ES2" }, 67 | { 0x40, "ES3" }, 68 | { 0x41, "MS01" }, 69 | }; 70 | 71 | // from gamepad firmare @0x000b29fc 72 | const char* kRegionStrings[] = { 73 | "JAPAN", 74 | "AMERICA", 75 | "EUROPE", 76 | "CHINA", 77 | "SOUTH KOREA", 78 | "TAIWAN", 79 | "AUSTRALIA", 80 | }; 81 | 82 | } 83 | 84 | DRXInfoScreen::DRXInfoScreen() 85 | { 86 | CCRCDCSoftwareVersion softwareVersion; 87 | if (CCRCDCSoftwareGetVersion(CCR_CDC_DESTINATION_DRC0, &softwareVersion) == 0) { 88 | uint32_t v = softwareVersion.runningVersion; 89 | mDRCList.push_back({"Running Version:", Utils::sprintf("%d.%d.%d", v >> 24 & 0xff, v >> 16 & 0xff, v & 0xffff)}); 90 | mDRCList.push_back({"", {Utils::sprintf("(0x%08x)", v), true}}); 91 | v = softwareVersion.activeVersion; 92 | mDRCList.push_back({"Active Version:", Utils::sprintf("%d.%d.%d", v >> 24 & 0xff, v >> 16 & 0xff, v & 0xffff)}); 93 | mDRCList.push_back({"", {Utils::sprintf("(0x%08x)", v), true}}); 94 | } else { 95 | mDRCList.push_back({"CCRCDCSoftwareGetVersion failed", ""}); 96 | } 97 | 98 | uint8_t boardInfo; 99 | if (GetEepromValue(0x100, std::span(std::addressof(boardInfo), 1))) { 100 | uint8_t mainVersion = boardInfo & 0xf; 101 | uint8_t subVersion = boardInfo >> 4; 102 | mDRCList.push_back({"Board Version:", Utils::sprintf("%d.%d (0x%02x)", mainVersion, subVersion, boardInfo)}); 103 | mDRCList.push_back({"", Utils::sprintf("(%s / %s)", 104 | mainVersion < 0xf ? kBoardMainVersions[mainVersion] : "UNKNOWN", 105 | subVersion < 0xc ? kBoardSubVersions[subVersion] : "UNKNOWN")}); 106 | } else { 107 | mDRCList.push_back({"GetEepromValue failed", ""}); 108 | } 109 | 110 | uint8_t region; 111 | if (GetEepromValue(0x103, std::span(std::addressof(region), 1))) { 112 | mDRCList.push_back({"Region:", Utils::sprintf("%s (0x%02x)", 113 | region < 0x7 ? kRegionStrings[region] : "UNKNOWN", region)}); 114 | } else { 115 | mDRCList.push_back({"GetRegion failed", ""}); 116 | } 117 | 118 | CCRCDCSysInfo sysInfo; 119 | if (CCRCDCSysGetInfo(CCR_CDC_DESTINATION_DRC0, &sysInfo) == 0) { 120 | mDRCList.push_back({"Chip Version:", Utils::sprintf("%s (0x%02x)", 121 | kChipVersions.contains(sysInfo.chipVersion) ? kChipVersions.at(sysInfo.chipVersion) : "UNKNOWN", sysInfo.chipVersion)}); 122 | mDRCList.push_back({"UMI Version:", {Utils::sprintf("0x%08x", sysInfo.umiVersion), true}}); 123 | } else { 124 | mDRCList.push_back({"SysGetInfo failed", ""}); 125 | } 126 | 127 | if (CCRCDCSoftwareGetVersion(CCR_CDC_DESTINATION_DRH, &softwareVersion) == 0) { 128 | uint32_t v = softwareVersion.runningVersion; 129 | mDRHList.push_back({"Running Version:", Utils::sprintf("%d.%d.%d", v >> 24 & 0xff, v >> 16 & 0xff, v & 0xffff)}); 130 | mDRHList.push_back({"", {Utils::sprintf("(0x%08x)", v), true}}); 131 | v = softwareVersion.activeVersion; 132 | mDRHList.push_back({"Active Version:", Utils::sprintf("%d.%d.%d", v >> 24 & 0xff, v >> 16 & 0xff, v & 0xffff)}); 133 | mDRHList.push_back({"", {Utils::sprintf("(0x%08x)", v), true}}); 134 | } else { 135 | mDRHList.push_back({"CCRCDCSoftwareGetVersion failed", ""}); 136 | } 137 | 138 | uint32_t extId; 139 | if (CCRCDCSoftwareGetExtId(CCR_CDC_DESTINATION_DRC0, CCR_CDC_EXT_LANGUAGE, &extId) == 0) { 140 | mExtIdList.push_back({"Language:", {Utils::sprintf("0x%08x", extId), true}}); 141 | mExtIdList.push_back({"", Utils::sprintf("(version: %04d bank: %02d)", extId >> 8 & 0xffff, extId >> 24)}); 142 | } else { 143 | mExtIdList.push_back({"CCRCDCSoftwareGetExtId(CCR_CDC_EXT_LANGUAGE) failed", ""}); 144 | } 145 | 146 | if (CCRCDCSoftwareGetExtId(CCR_CDC_DESTINATION_DRC0, CCR_CDC_EXT_RC_DATABASE, &extId) == 0) { 147 | mExtIdList.push_back({"RC Database:", {Utils::sprintf("0x%08x", extId), true}}); 148 | } else { 149 | mExtIdList.push_back({"CCRCDCSoftwareGetExtId(CCR_CDC_EXT_RC_DATABASE) failed", ""}); 150 | } 151 | 152 | for (int i = CCR_CDC_EXT_UNK2; i <= CCR_CDC_EXT_UNK4; i++) { 153 | if (CCRCDCSoftwareGetExtId(CCR_CDC_DESTINATION_DRC0, (CCRCDCExt) i, &extId) == 0) { 154 | mExtIdList.push_back({Utils::sprintf("ID %d:", i), {Utils::sprintf("0x%08x", extId), true}}); 155 | } else { 156 | mExtIdList.push_back({Utils::sprintf("CCRCDCSoftwareGetExtId(%d) failed", i), ""}); 157 | } 158 | } 159 | } 160 | 161 | DRXInfoScreen::~DRXInfoScreen() 162 | { 163 | } 164 | 165 | void DRXInfoScreen::Draw() 166 | { 167 | DrawTopBar("DRC/DRH Information"); 168 | 169 | int yOff = 128; 170 | yOff = DrawHeader(32, yOff, 896, 0xf11b, "DRC Info"); 171 | yOff = DrawList(32, yOff, 896, mDRCList); 172 | yOff = DrawHeader(32, yOff, 896, 0xf2db, "DRH Info"); 173 | yOff = DrawList(32, yOff, 896, mDRHList); 174 | 175 | yOff = 128; 176 | yOff = DrawHeader(992, yOff, 896, 0xf0cb, "DRC Ext IDs"); 177 | yOff = DrawList(992, yOff, 896, mExtIdList); 178 | 179 | DrawBottomBar(nullptr, "\ue044 Exit", "\ue001 Back"); 180 | } 181 | 182 | bool DRXInfoScreen::Update(VPADStatus& input) 183 | { 184 | if (input.trigger & VPAD_BUTTON_B) { 185 | return false; 186 | } 187 | 188 | return true; 189 | } 190 | -------------------------------------------------------------------------------- /source/screens/DRXInfoScreen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Screen.hpp" 4 | 5 | class DRXInfoScreen : public Screen 6 | { 7 | public: 8 | DRXInfoScreen(); 9 | virtual ~DRXInfoScreen(); 10 | 11 | void Draw(); 12 | 13 | bool Update(VPADStatus& input); 14 | 15 | private: 16 | ScreenList mDRCList; 17 | ScreenList mExtIdList; 18 | 19 | ScreenList mDRHList; 20 | }; 21 | -------------------------------------------------------------------------------- /source/screens/GeneralScreen.cpp: -------------------------------------------------------------------------------- 1 | #include "GeneralScreen.hpp" 2 | #include "Gfx.hpp" 3 | #include "Utils.hpp" 4 | #include "system/OTP.hpp" 5 | #include "system/SEEPROM.hpp" 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace 12 | { 13 | 14 | static const char* const consoleTypeLookup[] = { 15 | "Invalid", "WUP", "CAT-R", "CAT-DEV", 16 | "EV board", "Kiosk", "OrchestraX", "WUIH", 17 | "WUIH_DEV", "CAT_DEV_WUIH", 18 | }; 19 | 20 | const char* const keysetLookup[] = { 21 | "Factory", 22 | "Debug", 23 | "Retail", 24 | "Invalid" 25 | }; 26 | 27 | const std::map hardwareVersionMap = { 28 | { 0x21100010, "LATTE_A11_EV" }, 29 | { 0x21100020, "LATTE_A11_CAT" }, 30 | { 0x21200010, "LATTE_A12_EV" }, 31 | { 0x21200020, "LATTE_A12_CAT" }, 32 | { 0x22100010, "LATTE_A2X_EV" }, 33 | { 0x22100020, "LATTE_A2X_CAT" }, 34 | { 0x23100010, "LATTE_A3X_EV" }, 35 | { 0x23100020, "LATTE_A3X_CAT" }, 36 | { 0x23100028, "LATTE_A3X_CAFE" }, 37 | { 0x24100010, "LATTE_A4X_EV" }, 38 | { 0x24100020, "LATTE_A4X_CAT" }, 39 | { 0x24100028, "LATTE_A4X_CAFE" }, 40 | { 0x25100010, "LATTE_A5X_EV" }, 41 | { 0x25100011, "LATTE_A5X_EV_Y" }, 42 | { 0x25100020, "LATTE_A5X_CAT" }, 43 | { 0x25100028, "LATTE_A5X_CAFE" }, 44 | { 0x26100010, "LATTE_B1X_EV" }, 45 | { 0x26100011, "LATTE_B1X_EV_Y" }, 46 | { 0x26100020, "LATTE_B1X_CAT" }, 47 | { 0x26100028, "LATTE_B1X_CAFE" }, 48 | }; 49 | 50 | const char* const sataDeviceLookup[] = { 51 | "Invalid", "Default", "No device", "ROM drive", 52 | "R drive", "MION", "SES", "GEN2-HDD", 53 | "GEN1-HDD", 54 | }; 55 | 56 | const char* const regionLookup[] = { 57 | "JPN", "USA", "EUR", "AUS", 58 | "CHN", "KOR", "TWN", 59 | }; 60 | 61 | } 62 | 63 | GeneralScreen::GeneralScreen() 64 | { 65 | const auto& otp = OTP::Get(); 66 | const auto& seeprom = SEEPROM::Get(); 67 | 68 | WUT_ALIGNAS(0x40) MCPSysProdSettings sysProd{}; 69 | WUT_ALIGNAS(0x40) MCPSystemVersion sysVer{}; 70 | int32_t mcpHandle = MCP_Open(); 71 | if (mcpHandle >= 0) { 72 | // Don't bother checking res here, if it fails sysProd is zeroed 73 | MCP_GetSysProdSettings(mcpHandle, &sysProd); 74 | MCP_GetSystemVersion(mcpHandle, &sysVer); 75 | MCP_Close(mcpHandle); 76 | } 77 | 78 | sysIdentList.push_back({"Model:", Utils::sprintf("%.*s", sizeof(sysProd.model_number), sysProd.model_number)}); 79 | sysIdentList.push_back({"Serial:", Utils::sprintf("%.*s%.*s", sizeof(sysProd.code_id), sysProd.code_id, sizeof(sysProd.serial_id), sysProd.serial_id)}); 80 | sysIdentList.push_back({"Production Date:", 81 | Utils::sprintf("%04x/%02x/%02x %02x:%02x", 82 | seeprom.prod_info.prod_year, 83 | seeprom.prod_info.prod_month_day >> 8, 84 | seeprom.prod_info.prod_month_day & 0xff, 85 | seeprom.prod_info.prod_hour_minute >> 8, 86 | seeprom.prod_info.prod_hour_minute & 0xff 87 | ) 88 | }); 89 | if (seeprom.bc.consoleType < 10) { 90 | hardwareList.push_back({"Type:", Utils::sprintf("%s (0x%02x)", consoleTypeLookup[seeprom.bc.consoleType], seeprom.bc.consoleType)}); 91 | } 92 | sysIdentList.push_back({"Keyset:", keysetLookup[(otp.wiiUBank.securityLevel & 0x18000000) >> 27]}); 93 | 94 | BSPHardwareVersion hardwareVersion = 0; 95 | bspGetHardwareVersion(&hardwareVersion); 96 | const char* hardwareVersionString = 97 | hardwareVersionMap.contains(hardwareVersion) ? hardwareVersionMap.at(hardwareVersion) 98 | : "Unknown"; 99 | hardwareList.push_back({"Version:", Utils::sprintf("%s", hardwareVersionString)}); 100 | hardwareList.push_back({"", Utils::sprintf("(0x%08x)", hardwareVersion)}); 101 | hardwareList.push_back({"Board Type:", Utils::sprintf("%c%c", seeprom.bc.boardType >> 8, seeprom.bc.boardType & 0xff)}); 102 | if (seeprom.bc.sataDevice < 9) { 103 | hardwareList.push_back({"SATA Device:", Utils::sprintf("%s (0x%02x)", sataDeviceLookup[seeprom.bc.sataDevice], seeprom.bc.sataDevice)}); 104 | } 105 | hardwareList.push_back({"DDR3 Size:", Utils::sprintf("%d MiB", seeprom.bc.ddr3Size)}); 106 | hardwareList.push_back({"DDR3 Speed:", Utils::sprintf("%d", seeprom.bc.ddr3Speed)}); 107 | hardwareList.push_back({"DDR3 Vendor:", Utils::sprintf("0x%02X", seeprom.bc.ddr3Vendor)}); 108 | 109 | const int productAreaId = sysProd.product_area ? __builtin_ctz(sysProd.product_area) : 7; 110 | if (productAreaId < 7) { 111 | regionList.push_back({"Product Area:", regionLookup[productAreaId]}); 112 | } 113 | regionList.push_back({"Game Region:", ""}); 114 | regionList.push_back({"", 115 | Utils::sprintf("%s %s %s %s %s %s (%u)", 116 | (sysProd.game_region & MCP_REGION_JAPAN) ? regionLookup[0] : "---", 117 | (sysProd.game_region & MCP_REGION_USA) ? regionLookup[1] : "---", 118 | (sysProd.game_region & MCP_REGION_EUROPE) ? regionLookup[2] : "---", 119 | (sysProd.game_region & MCP_REGION_CHINA) ? regionLookup[4] : "---", 120 | (sysProd.game_region & MCP_REGION_KOREA) ? regionLookup[5] : "---", 121 | (sysProd.game_region & MCP_REGION_TAIWAN) ? regionLookup[6] : "---", sysProd.game_region) 122 | }); 123 | 124 | versionList.push_back({"System Version:", Utils::sprintf("%u.%u.%u%c", sysVer.major, sysVer.minor, sysVer.patch, sysVer.region)}); 125 | 126 | // Need to decrypt boot1 info before using 127 | SEEPROM::Boot1Info boot1_info[2]{}; 128 | if (Utils::AESDecrypt(otp.wiiUBank.seepromKey, 128, nullptr, &seeprom.boot1_info[0], &boot1_info[0], sizeof(SEEPROM::Boot1Info)) == 0 && 129 | Utils::AESDecrypt(otp.wiiUBank.seepromKey, 128, nullptr, &seeprom.boot1_info[1], &boot1_info[1], sizeof(SEEPROM::Boot1Info)) == 0) { 130 | versionList.push_back({"Boot1 Version (0/1):", Utils::sprintf("0x%04x (%u)", boot1_info[0].version, boot1_info[0].version)}); 131 | versionList.push_back({"Boot1 Version (1/1):", Utils::sprintf("0x%04x (%u)", boot1_info[1].version, boot1_info[1].version)}); 132 | } 133 | } 134 | 135 | GeneralScreen::~GeneralScreen() 136 | { 137 | 138 | } 139 | 140 | void GeneralScreen::Draw() 141 | { 142 | DrawTopBar("General System Information"); 143 | 144 | int yOff = 128; 145 | yOff = DrawHeader(32, yOff, 896, 0xf02a, "Identification"); 146 | yOff = DrawList(32, yOff, 896, sysIdentList); 147 | 148 | yOff = DrawHeader(32, yOff, 896, 0xf538, "Hardware"); 149 | yOff = DrawList(32, yOff, 896, hardwareList); 150 | 151 | yOff = 128; 152 | yOff = DrawHeader(992, yOff, 896, 0xf0ac, "Region"); 153 | yOff = DrawList(992, yOff, 896, regionList); 154 | 155 | yOff = DrawHeader(992, yOff, 896, 0xf886, "Versions"); 156 | yOff = DrawList(992, yOff, 896, versionList); 157 | 158 | DrawBottomBar(nullptr, "\ue044 Exit", "\ue001 Back"); 159 | } 160 | 161 | bool GeneralScreen::Update(VPADStatus& input) 162 | { 163 | if (input.trigger & VPAD_BUTTON_B) { 164 | return false; 165 | } 166 | 167 | return true; 168 | } 169 | -------------------------------------------------------------------------------- /source/screens/GeneralScreen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Screen.hpp" 4 | 5 | class GeneralScreen : public Screen 6 | { 7 | public: 8 | GeneralScreen(); 9 | virtual ~GeneralScreen(); 10 | 11 | void Draw(); 12 | 13 | bool Update(VPADStatus& input); 14 | 15 | private: 16 | ScreenList sysIdentList; 17 | ScreenList hardwareList; 18 | ScreenList regionList; 19 | ScreenList versionList; 20 | }; 21 | -------------------------------------------------------------------------------- /source/screens/MainScreen.cpp: -------------------------------------------------------------------------------- 1 | #include "MainScreen.hpp" 2 | #include "MenuScreen.hpp" 3 | #include "Gfx.hpp" 4 | #include "system/OTP.hpp" 5 | #include "system/SEEPROM.hpp" 6 | #include "system/MemoryDevice.hpp" 7 | 8 | #include 9 | 10 | #include 11 | 12 | MainScreen::~MainScreen() 13 | { 14 | if (state > STATE_INIT_MOCHA) { 15 | Mocha_DeInitLibrary(); 16 | } 17 | } 18 | 19 | void MainScreen::Draw() 20 | { 21 | Gfx::Clear(Gfx::COLOR_BACKGROUND); 22 | 23 | if (menuScreen) { 24 | menuScreen->Draw(); 25 | return; 26 | } 27 | 28 | DrawTopBar(nullptr); 29 | 30 | switch (state) { 31 | case STATE_INIT: 32 | break; 33 | case STATE_INIT_MOCHA: 34 | if (stateFailure) { 35 | DrawStatus("Failed to initialize mocha!\nMake sure to update or install Tiramisu/Aroma.", Gfx::COLOR_ERROR); 36 | break; 37 | } 38 | 39 | DrawStatus("Initializing mocha..."); 40 | break; 41 | case STATE_INIT_OTP: 42 | if (stateFailure) { 43 | DrawStatus("Failed to initialize OTP!", Gfx::COLOR_ERROR); 44 | break; 45 | } 46 | 47 | DrawStatus("Initializing OTP..."); 48 | break; 49 | case STATE_INIT_SEEPROM: 50 | if (stateFailure) { 51 | DrawStatus("Failed to initialize SEEPROM!", Gfx::COLOR_ERROR); 52 | break; 53 | } 54 | 55 | DrawStatus("Initializing SEEPROM..."); 56 | break; 57 | case STATE_INIT_MEMORY_DEVICE: 58 | if (stateFailure) { 59 | DrawStatus("Failed to initialize Memory Devices!", Gfx::COLOR_ERROR); 60 | break; 61 | } 62 | 63 | DrawStatus("Initializing Memory Devices..."); 64 | break; 65 | case STATE_LOAD_MENU: 66 | DrawStatus("Loading menu..."); 67 | break; 68 | case STATE_IN_MENU: 69 | break; 70 | } 71 | 72 | DrawBottomBar(stateFailure ? nullptr : "Please wait...", stateFailure ? "\ue044 Exit" : nullptr, nullptr); 73 | } 74 | 75 | bool MainScreen::Update(VPADStatus& input) 76 | { 77 | if (menuScreen) { 78 | if (!menuScreen->Update(input)) { 79 | // menu wants to exit 80 | return false; 81 | } 82 | return true; 83 | } 84 | 85 | MochaUtilsStatus status; 86 | switch (state) { 87 | case STATE_INIT: 88 | state = STATE_INIT_MOCHA; 89 | break; 90 | case STATE_INIT_MOCHA: 91 | status = Mocha_InitLibrary(); 92 | if (status == MOCHA_RESULT_SUCCESS) { 93 | state = STATE_INIT_OTP; 94 | break; 95 | } 96 | 97 | stateFailure = true; 98 | break; 99 | case STATE_INIT_OTP: 100 | if (OTP::Init()) { 101 | state = STATE_INIT_SEEPROM; 102 | break; 103 | } 104 | 105 | stateFailure = true; 106 | break; 107 | case STATE_INIT_SEEPROM: 108 | if (SEEPROM::Init()) { 109 | state = STATE_INIT_MEMORY_DEVICE; 110 | break; 111 | } 112 | 113 | stateFailure = true; 114 | break; 115 | case STATE_INIT_MEMORY_DEVICE: 116 | if (MemoryDevice::Init()) { 117 | state = STATE_LOAD_MENU; 118 | break; 119 | } 120 | 121 | stateFailure = true; 122 | break; 123 | case STATE_LOAD_MENU: 124 | menuScreen = std::make_unique(); 125 | break; 126 | case STATE_IN_MENU: 127 | break; 128 | }; 129 | 130 | return true; 131 | } 132 | 133 | void MainScreen::DrawStatus(std::string status, SDL_Color color) 134 | { 135 | Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT / 2, 64, color, status, Gfx::ALIGN_CENTER); 136 | } 137 | -------------------------------------------------------------------------------- /source/screens/MainScreen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Screen.hpp" 4 | #include "Gfx.hpp" 5 | #include 6 | 7 | class MainScreen : public Screen 8 | { 9 | public: 10 | MainScreen() = default; 11 | virtual ~MainScreen(); 12 | 13 | void Draw(); 14 | 15 | bool Update(VPADStatus& input); 16 | 17 | protected: 18 | void DrawStatus(std::string status, SDL_Color color = Gfx::COLOR_TEXT); 19 | 20 | private: 21 | enum { 22 | STATE_INIT, 23 | STATE_INIT_MOCHA, 24 | STATE_INIT_OTP, 25 | STATE_INIT_SEEPROM, 26 | STATE_INIT_MEMORY_DEVICE, 27 | STATE_LOAD_MENU, 28 | STATE_IN_MENU, 29 | } state = STATE_INIT; 30 | bool stateFailure = false; 31 | 32 | std::unique_ptr menuScreen; 33 | }; 34 | -------------------------------------------------------------------------------- /source/screens/MenuScreen.cpp: -------------------------------------------------------------------------------- 1 | #include "MenuScreen.hpp" 2 | #include "Gfx.hpp" 3 | #include "AboutScreen.hpp" 4 | #include "DRXInfoScreen.hpp" 5 | #include "GeneralScreen.hpp" 6 | #include "StorageScreen.hpp" 7 | #include "SubmitScreen.hpp" 8 | 9 | #include 10 | 11 | MenuScreen::MenuScreen() 12 | : entries({ 13 | { MENU_ID_GENERAL, { 0xf085, "General System Information" }}, 14 | { MENU_ID_STORAGE, { 0xf7c2, "Storage Information" }}, 15 | { MENU_ID_DRX, { 0xf11b, "DRC/DRH Information" }}, 16 | { MENU_ID_SUBMIT, { 0xf0ee, "Submit System Information" }}, 17 | // { MENU_ID_TITLE, { 0xf022, "Title Information" }}, 18 | { MENU_ID_ABOUT, { 0xf05a, "About WiiUIdent" }}, 19 | // { MENU_ID_EXIT, { 0xf057, "Exit" }}, 20 | }) 21 | { 22 | 23 | } 24 | 25 | MenuScreen::~MenuScreen() 26 | { 27 | } 28 | 29 | void MenuScreen::Draw() 30 | { 31 | if (subscreen) { 32 | subscreen->Draw(); 33 | return; 34 | } 35 | 36 | DrawTopBar(nullptr); 37 | 38 | // draw entries 39 | for (MenuID id = MENU_ID_MIN; id <= MENU_ID_MAX; id = static_cast(id + 1)) { 40 | int yOff = 75 + static_cast(id) * 150; 41 | Gfx::DrawRectFilled(0, yOff, Gfx::SCREEN_WIDTH, 150, Gfx::COLOR_ALT_BACKGROUND); 42 | Gfx::DrawIcon(68, yOff + 150 / 2, 60, Gfx::COLOR_TEXT, entries[id].icon); 43 | Gfx::Print(128 + 8, yOff + 150 / 2, 60, Gfx::COLOR_TEXT, entries[id].name, Gfx::ALIGN_VERTICAL); 44 | 45 | if (id == selected) { 46 | Gfx::DrawRect(0, yOff, Gfx::SCREEN_WIDTH, 150, 8, Gfx::COLOR_HIGHLIGHTED); 47 | } 48 | } 49 | 50 | DrawBottomBar("\ue07d Navigate", "\ue044 Exit", "\ue000 Select"); 51 | } 52 | 53 | bool MenuScreen::Update(VPADStatus& input) 54 | { 55 | if (subscreen) { 56 | if (!subscreen->Update(input)) { 57 | // subscreen wants to exit 58 | subscreen.reset(); 59 | } 60 | return true; 61 | } 62 | 63 | if (input.trigger & VPAD_BUTTON_DOWN) { 64 | if (selected < MENU_ID_MAX) { 65 | selected = static_cast(selected + 1); 66 | } 67 | } else if (input.trigger & VPAD_BUTTON_UP) { 68 | if (selected > MENU_ID_MIN) { 69 | selected = static_cast(selected - 1); 70 | } 71 | } 72 | 73 | if (input.trigger & VPAD_BUTTON_A) { 74 | switch (selected) { 75 | case MENU_ID_GENERAL: 76 | subscreen = std::make_unique(); 77 | break; 78 | case MENU_ID_STORAGE: 79 | subscreen = std::make_unique(); 80 | break; 81 | case MENU_ID_DRX: 82 | subscreen = std::make_unique(); 83 | break; 84 | case MENU_ID_SUBMIT: 85 | subscreen = std::make_unique(); 86 | break; 87 | case MENU_ID_ABOUT: 88 | subscreen = std::make_unique(); 89 | break; 90 | } 91 | } 92 | 93 | return true; 94 | } 95 | -------------------------------------------------------------------------------- /source/screens/MenuScreen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Screen.hpp" 4 | #include 5 | #include 6 | 7 | class MenuScreen : public Screen 8 | { 9 | public: 10 | MenuScreen(); 11 | virtual ~MenuScreen(); 12 | 13 | void Draw(); 14 | 15 | bool Update(VPADStatus& input); 16 | 17 | private: 18 | std::unique_ptr subscreen; 19 | 20 | enum MenuID { 21 | MENU_ID_GENERAL, 22 | MENU_ID_STORAGE, 23 | MENU_ID_DRX, 24 | MENU_ID_SUBMIT, 25 | MENU_ID_ABOUT, 26 | 27 | MENU_ID_MIN = MENU_ID_GENERAL, 28 | MENU_ID_MAX = MENU_ID_ABOUT, 29 | }; 30 | 31 | struct MenuEntry { 32 | uint16_t icon; 33 | const char* name; 34 | }; 35 | std::map entries; 36 | MenuID selected = MENU_ID_MIN; 37 | }; 38 | -------------------------------------------------------------------------------- /source/screens/StorageScreen.cpp: -------------------------------------------------------------------------------- 1 | #include "StorageScreen.hpp" 2 | #include "system/MemoryDevice.hpp" 3 | #include "Utils.hpp" 4 | #include 5 | 6 | StorageScreen::StorageScreen() 7 | { 8 | for (const MemoryDevice& dev : MemoryDevice::GetDevices()) { 9 | ScreenList* list = nullptr; 10 | if (dev.GetType() == MemoryDevice::TYPE_MLC) { 11 | list = &mlcList; 12 | } else if (dev.GetType() == MemoryDevice::TYPE_SD_CARD) { 13 | list = &sdList; 14 | } else { 15 | continue; 16 | } 17 | 18 | list->push_back({"Type:", dev.GetCardType() == MemoryDevice::CARD_TYPE_SD ? "SD" : "MMC"}); 19 | list->push_back({"Manufacturer:", Utils::sprintf("%s (0x%02x)", dev.GetManufacturerName().c_str(), dev.GetMID())}); 20 | list->push_back({"Product Name:", {dev.GetName(), true}}); 21 | list->push_back({"Product Revision:", Utils::sprintf("%d.%d (0x%02x)", dev.GetPRV() >> 4, dev.GetPRV() & 0xf, dev.GetPRV())}); 22 | if (dev.GetType() == MemoryDevice::TYPE_MLC) 23 | list->push_back({"Production Date:", dev.GetProductionDate()}); 24 | list->push_back({"Size:", Utils::sprintf("%llu MiB", dev.GetTotalSize() / 1024ull / 1024ull)}); 25 | list->push_back({"CID:", {Utils::ToHexString(dev.GetCID().data(), dev.GetCID().size()), true}}); 26 | list->push_back({"CSD:", {Utils::ToHexString(dev.GetCSD().data(), dev.GetCSD().size()), true}}); 27 | } 28 | 29 | if (mlcList.empty()) { 30 | mlcList.push_back({"Not Attached", ""}); 31 | } 32 | 33 | if (sdList.empty()) { 34 | sdList.push_back({"Not Attached", ""}); 35 | } 36 | } 37 | 38 | StorageScreen::~StorageScreen() 39 | { 40 | } 41 | 42 | void StorageScreen::Draw() 43 | { 44 | DrawTopBar("Storage Information"); 45 | 46 | int yOff = 128; 47 | yOff = DrawHeader(32, yOff, 896, 0xf2db, "MLC"); 48 | yOff = DrawList(32, yOff, 896, mlcList); 49 | 50 | yOff = 128; 51 | yOff = DrawHeader(992, yOff, 896, 0xf7c2, "SD Card"); 52 | yOff = DrawList(992, yOff, 896, sdList); 53 | 54 | DrawBottomBar(nullptr, "\ue044 Exit", "\ue001 Back"); 55 | } 56 | 57 | bool StorageScreen::Update(VPADStatus& input) 58 | { 59 | if (input.trigger & VPAD_BUTTON_B) { 60 | return false; 61 | } 62 | 63 | return true; 64 | } 65 | -------------------------------------------------------------------------------- /source/screens/StorageScreen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Screen.hpp" 4 | 5 | class StorageScreen : public Screen 6 | { 7 | public: 8 | StorageScreen(); 9 | virtual ~StorageScreen(); 10 | 11 | void Draw(); 12 | 13 | bool Update(VPADStatus& input); 14 | 15 | private: 16 | ScreenList mlcList; 17 | ScreenList sdList; 18 | }; 19 | -------------------------------------------------------------------------------- /source/screens/SubmitScreen.cpp: -------------------------------------------------------------------------------- 1 | #include "SubmitScreen.hpp" 2 | #include "Gfx.hpp" 3 | #include "Utils.hpp" 4 | #include "system/OTP.hpp" 5 | #include "system/SEEPROM.hpp" 6 | #include "system/MemoryDevice.hpp" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | struct IOSCEccSignedCert { 17 | uint32_t signature_type; // [0x000] 0x00010002 or 0x00010005 18 | uint8_t signature[0x3C]; // [0x004] ECC signature 19 | uint8_t reserved1[0x40]; // [0x040] All zeroes 20 | char issuer[0x40]; // [0x080] Issuer 21 | uint32_t key_type; // [0x0C0] Key type (2) 22 | char console_id[0x40]; // [0x0C4] Console ID ("NGxxxxxxxx") 23 | uint32_t ng_id; // [0x104] NG ID 24 | uint8_t pub_key[0x3C]; // [0x108] ECC public key 25 | uint8_t reserved2[0x3C]; // [0x10C] All zeroes 26 | }; 27 | WUT_CHECK_SIZE(IOSCEccSignedCert, 0x180); 28 | 29 | // POST data 30 | struct post_data { 31 | char system_model[16]; // [0x000] seeprom[0xB8] 32 | char system_serial[16]; // [0x010] seeprom[0xAC] + seeprom[0xB0] - need to mask the last 3 digits 33 | uint8_t mfg_date[6]; // [0x020] seeprom[0xC4] 34 | uint8_t productArea; // [0x026] 35 | uint8_t gameRegion; // [0x027] 36 | uint32_t sec_level; // [0x028] otp[0x080] 37 | uint16_t boardType; // [0x02C] seeprom[0x21] 38 | uint16_t boardRevision; // [0x02E] seeprom[0x22] 39 | uint16_t bootSource; // [0x030] seeprom[0x23] 40 | uint16_t ddr3Size; // [0x032] seeprom[0x24] 41 | uint16_t ddr3Speed; // [0x034] seeprom[0x25] 42 | uint16_t ddr3Vendor; // [0x036] seeprom[0x29] 43 | uint16_t sataDevice; // [0x038] seeprom[0x2C] 44 | uint16_t consoleType; // [0x03A] seeprom[0x2D] 45 | uint32_t bsp_rev; // [0x03C] bspGetHardwareVersion(); 46 | uint32_t wiiu_root_ms_id; // [0x040] otp[0x280] 47 | uint32_t wiiu_root_ca_id; // [0x044] otp[0x284] 48 | uint32_t wiiu_ng_id; // [0x048] otp[0x21C] 49 | uint32_t wiiu_ng_key_id; // [0x04C] otp[0x288] 50 | uint8_t reserved2[48]; // [0x050] 51 | 52 | // [0x080] 53 | struct { 54 | uint32_t cid[4]; // [0x080] CID 55 | uint32_t mid_prv; // [0x090] Manufacturer and product revision 56 | uint32_t blockSize; // [0x094] Block size 57 | uint64_t numBlocks; // [0x098] Number of blocks 58 | } mlc; 59 | 60 | // [0x0A0] 61 | uint8_t otp_sha256[32]; // [0x0A0] OTP SHA-256 hash (to prevent duplicates) 62 | 63 | // [0x0C0] 64 | IOSCEccSignedCert device_cert; // [0x0C0] Device client certificate 65 | }; // size == 0x240 (576) 66 | WUT_CHECK_SIZE(post_data, 0x240); 67 | 68 | struct post_data_hashed { 69 | struct post_data data; 70 | uint8_t post_sha256[32]; // [0x140] SHA-256 hash of post_data, with adjustments 71 | }; // size == 0x260 (608) 72 | WUT_CHECK_SIZE(post_data_hashed, 0x260); 73 | 74 | namespace 75 | { 76 | 77 | const char desc[] = 78 | "This will submit statistical data to the developers of WiiUIdent,\n" 79 | "which will help to determine various statistics about Wii U consoles,\n" 80 | "e.g. eMMC manufacturers. The submitted data may be publicly accessible\n" 81 | "but personally identifying information will be kept confidential.\n" 82 | "\n" 83 | "Information that will be submitted:\n" 84 | "\uff65 System model and serial number (excluding the last 3 digits)\n" 85 | "\uff65 Manufacturing date\n" 86 | "\uff65 Region information\n" 87 | "\uff65 Security level (keyset), sataDevice, consoleType, BSP revision\n" 88 | "\uff65 boardType, boardRevision, bootSource, ddr3Size, ddr3Speed, ddr3Vendor\n" 89 | "\uff65 MLC manufacturer, revision, name, size, and CID\n" 90 | "\uff65 Device certificate and SHA-256 hash of OTP (to prevent duplicates)\n" 91 | "\uff64 MS, CA, NG, and NG key IDs\n" 92 | "\n" 93 | "Do you want to submit your console's system data?\n"; 94 | } 95 | 96 | SubmitScreen::SubmitScreen() 97 | { 98 | } 99 | 100 | SubmitScreen::~SubmitScreen() 101 | { 102 | } 103 | 104 | void SubmitScreen::Draw() 105 | { 106 | DrawTopBar("Submit System Information"); 107 | 108 | if (state == STATE_INFO) { 109 | Gfx::Print(32, 75 + 32, 40, Gfx::COLOR_TEXT, desc); 110 | 111 | DrawBottomBar(nullptr, "\ue044 Exit", "\ue001 Back / \ue000 Submit"); 112 | } else if (state == STATE_SUBMITTING) { 113 | Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT / 2, 64, Gfx::COLOR_TEXT, "Submitting info...", Gfx::ALIGN_CENTER); 114 | 115 | DrawBottomBar("Please wait...", nullptr, nullptr); 116 | } else if (state == STATE_SUBMITTED) { 117 | if (!error.empty() && response.empty()) { 118 | Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT / 2, 40, Gfx::COLOR_ERROR, Utils::sprintf("Error!\n%s", error.c_str()), Gfx::ALIGN_CENTER); 119 | 120 | Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT - 75 - 32, 40, Gfx::COLOR_TEXT, 121 | "Failed to submit system data. Please report a bug on GitHub:\n" 122 | "https://github.com/GaryOderNichts/WiiUIdent/issues", Gfx::ALIGN_HORIZONTAL | Gfx::ALIGN_BOTTOM); 123 | } else if (!response.empty()) { 124 | Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT / 2, 40, Gfx::COLOR_TEXT, response, Gfx::ALIGN_CENTER); 125 | 126 | if (!error.empty()) { 127 | Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT - 75 - 32, 40, Gfx::COLOR_TEXT, 128 | "Failed to submit system data. Please report a bug on GitHub:\n" 129 | "https://github.com/GaryOderNichts/WiiUIdent/issues", Gfx::ALIGN_HORIZONTAL | Gfx::ALIGN_BOTTOM); 130 | } else { 131 | Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT - 75 - 32, 40, Gfx::COLOR_TEXT, 132 | "System data submitted successfully. Check out the Wii U console database at:\n" 133 | "https://" DATABASE_URL "/", Gfx::ALIGN_HORIZONTAL | Gfx::ALIGN_BOTTOM); 134 | } 135 | } else { 136 | Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT / 2, 64, Gfx::COLOR_TEXT, "No response.", Gfx::ALIGN_CENTER); 137 | } 138 | 139 | DrawBottomBar(nullptr, "\ue044 Exit", "\ue001 Back"); 140 | } 141 | } 142 | 143 | bool SubmitScreen::Update(VPADStatus& input) 144 | { 145 | if (state == STATE_INFO) { 146 | if (input.trigger & VPAD_BUTTON_A) { 147 | state = STATE_SUBMITTING; 148 | } else if (input.trigger & VPAD_BUTTON_B) { 149 | return false; 150 | } 151 | } else if (state == STATE_SUBMITTING) { 152 | SubmitSystemData(); 153 | state = STATE_SUBMITTED; 154 | } else if (state == STATE_SUBMITTED) { 155 | if (input.trigger & VPAD_BUTTON_B) { 156 | return false; 157 | } 158 | } 159 | 160 | return true; 161 | } 162 | 163 | size_t SubmitScreen::CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp) 164 | { 165 | size_t realsize = size * nmemb; 166 | SubmitScreen* screen = (SubmitScreen*)userp; 167 | 168 | if (screen->response.size() < 4096) { // let's add a limit here to be safe 169 | screen->response += std::string((char*) contents, (char*) contents + realsize); 170 | } 171 | 172 | return realsize; 173 | } 174 | 175 | void SubmitScreen::SubmitSystemData() 176 | { 177 | auto otp = OTP::Get(); 178 | const auto& seeprom = SEEPROM::Get(); 179 | WUT_ALIGNAS(0x40) MCPSysProdSettings sysProd{}; 180 | int32_t mcpHandle = MCP_Open(); 181 | if (mcpHandle >= 0) { 182 | // Don't bother checking res here, if it fails sysProd is zeroed 183 | MCP_GetSysProdSettings(mcpHandle, &sysProd); 184 | MCP_Close(mcpHandle); 185 | } 186 | const MemoryDevice* mlcDev = nullptr; 187 | for (const MemoryDevice& dev : MemoryDevice::GetDevices()) { 188 | if (dev.GetType() == MemoryDevice::TYPE_MLC) { 189 | mlcDev = &dev; 190 | } 191 | } 192 | 193 | post_data_hashed pdh{}; 194 | post_data* pd = &pdh.data; 195 | memcpy(pd->system_model, seeprom.sys_prod.model_numer, sizeof(pd->system_model)); 196 | memcpy(pd->mfg_date, &seeprom.prod_info.prod_year, sizeof(pd->mfg_date)); 197 | pd->sec_level = otp.wiiUBank.securityLevel; 198 | pd->boardType = seeprom.bc.boardType; 199 | pd->boardRevision = seeprom.bc.boardRevision; 200 | pd->bootSource = seeprom.bc.bootSource; 201 | pd->ddr3Size = seeprom.bc.ddr3Size; 202 | pd->ddr3Speed = seeprom.bc.ddr3Speed; 203 | pd->ddr3Vendor = seeprom.bc.ddr3Vendor; 204 | pd->sataDevice = seeprom.bc.sataDevice; 205 | pd->consoleType = seeprom.bc.consoleType; 206 | pd->wiiu_root_ms_id = otp.wiiUCertBank.rootCertMSId; 207 | pd->wiiu_root_ca_id = otp.wiiUCertBank.rootCertCAId; 208 | pd->wiiu_ng_id = otp.wiiUNGBank.ngId; 209 | pd->wiiu_ng_key_id = otp.wiiUCertBank.rootCertNGKeyId; 210 | 211 | if (bspGetHardwareVersion(&pd->bsp_rev) != 0) { 212 | error = "Failed to get BSP revision"; 213 | return; 214 | } 215 | 216 | // Device certificate 217 | // IOSC_GetDeviceCertificate() isn't directly accessible from PPC, 218 | // so we'll cheat by reading a known buffer in the kernel. (2.13.01) 219 | for (uint32_t i = 0; i < sizeof(pd->device_cert) / 4; i++) { 220 | if (Mocha_IOSUKernelRead32(0x04024d40 + (i * 4), ((uint32_t*)&pd->device_cert) + i) != MOCHA_RESULT_SUCCESS) { 221 | error = "Failed to read device certificate"; 222 | return; 223 | } 224 | } 225 | 226 | // System serial number 227 | // NOTE: Assuming code+serial doesn't exceed 15 chars (plus NULL). 228 | #pragma GCC diagnostic push 229 | #pragma GCC diagnostic ignored "-Wformat-truncation" 230 | snprintf(pd->system_serial, sizeof(pd->system_serial), "%s%s", 231 | seeprom.sys_prod.code_id, seeprom.sys_prod.serial_id); 232 | #pragma GCC diagnostic pop 233 | 234 | // Mask the last 3 digits of the system serial number. 235 | for (unsigned int i = sizeof(pd->system_serial)-1; i > 3; i--) { 236 | if (pd->system_serial[i] <= 0x20) { 237 | pd->system_serial[i] = 0; 238 | continue; 239 | } 240 | // Found printable text. 241 | // Mask the last three digits. 242 | pd->system_serial[i-0] = '*'; 243 | pd->system_serial[i-1] = '*'; 244 | pd->system_serial[i-2] = '*'; 245 | break; 246 | } 247 | 248 | if (sysProd.product_area) { 249 | pd->productArea = __builtin_ctz(sysProd.product_area); 250 | } 251 | pd->gameRegion = sysProd.game_region; 252 | 253 | if (mlcDev) { 254 | memcpy(pd->mlc.cid, mlcDev->GetCID().data(), sizeof(pd->mlc.cid)); 255 | pd->mlc.mid_prv = mlcDev->GetMID() << 16 | mlcDev->GetPRV(); 256 | pd->mlc.numBlocks = mlcDev->GetNumBlocks(); 257 | pd->mlc.blockSize = mlcDev->GetBlockSize(); 258 | // NOTE: Not copying pd->mlc.name1 because the eMMC device name 259 | // is fully contained within the MLC CID. 260 | } 261 | 262 | memset(&otp.miscBank, 0, 0x40); 263 | if (Utils::SHA256(&otp, sizeof(otp), pd->otp_sha256, sizeof(pd->otp_sha256)) != 0) { 264 | error = "Failed to hash otp data"; 265 | return; 266 | } 267 | 268 | char hashbuf[sizeof(*pd) + 64]; 269 | memcpy(hashbuf, pd, sizeof(*pd)); 270 | memcpy(hashbuf + sizeof(*pd), "This will submit statistical data to the developers of recovery_menu", 64); 271 | if (Utils::SHA256(hashbuf, sizeof(hashbuf), pdh.post_sha256, sizeof(pdh.post_sha256)) != 0) { 272 | error = "Failed to hash data"; 273 | return; 274 | } 275 | 276 | CURL* curl = curl_easy_init(); 277 | if (!curl) { 278 | error = "Failed to init curl!"; 279 | return; 280 | } 281 | 282 | // setup post 283 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "WiiUIdent/" APP_VERSION); 284 | curl_easy_setopt(curl, CURLOPT_URL, "https://" DATABASE_URL "/add-system.php"); 285 | curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, sizeof(pdh)); 286 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, &pdh); 287 | 288 | // set content type to octet stream 289 | struct curl_slist* list = nullptr; 290 | list = curl_slist_append(list, "Content-Type: application/octet-stream"); 291 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); 292 | 293 | // set write callback for response 294 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback); 295 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); 296 | 297 | // load certs 298 | struct curl_blob blob{}; 299 | blob.data = (void *) cacert_pem; 300 | blob.len = cacert_pem_size; 301 | blob.flags = CURL_BLOB_COPY; 302 | curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob); 303 | 304 | CURLcode res = curl_easy_perform(curl); 305 | if (res != CURLE_OK) { 306 | error = Utils::sprintf("Failed to upload data:\n%s (%d)", curl_easy_strerror(res), res); 307 | curl_easy_cleanup(curl); 308 | return; 309 | } 310 | 311 | long code; 312 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); 313 | if (code >= 400) { 314 | error = Utils::sprintf("Server responded with %d!", code); 315 | } 316 | 317 | curl_easy_cleanup(curl); 318 | return; 319 | } 320 | -------------------------------------------------------------------------------- /source/screens/SubmitScreen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Screen.hpp" 4 | 5 | class SubmitScreen : public Screen 6 | { 7 | public: 8 | SubmitScreen(); 9 | virtual ~SubmitScreen(); 10 | 11 | void Draw(); 12 | 13 | bool Update(VPADStatus& input); 14 | 15 | private: 16 | enum State { 17 | STATE_INFO, 18 | STATE_SUBMITTING, 19 | STATE_SUBMITTED, 20 | }; 21 | State state = STATE_INFO; 22 | 23 | static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp); 24 | 25 | void SubmitSystemData(); 26 | 27 | std::string error; 28 | std::string response; 29 | }; 30 | -------------------------------------------------------------------------------- /source/system/MemoryDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "MemoryDevice.hpp" 2 | #include "Utils.hpp" 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | struct WUT_PACKED SALDeviceParams { 9 | uint32_t usrptr; 10 | uint32_t mid_prv; 11 | uint32_t device_type; 12 | WUT_UNKNOWN_BYTES(0x1c); 13 | uint64_t numBlocks; 14 | uint32_t blockSize; 15 | WUT_UNKNOWN_BYTES(0x18); 16 | char name0[128]; 17 | char name1[128]; 18 | char name2[128]; 19 | uint32_t functions[12]; 20 | }; 21 | 22 | struct WUT_PACKED MDBlkDrv { 23 | int32_t registered; 24 | WUT_UNKNOWN_BYTES(0x8); 25 | SALDeviceParams params; 26 | int sal_handle; 27 | int deviceId; 28 | WUT_UNKNOWN_BYTES(0xc4); 29 | }; 30 | WUT_CHECK_SIZE(MDBlkDrv, 0x2d4); 31 | 32 | namespace 33 | { 34 | 35 | constexpr uint32_t MDBLK_DRIVER_ADDRESS = 0x11c39e78; 36 | constexpr uint32_t MD_DEVICE_POINTERS_ADDRESS = 0x10899308; 37 | 38 | // TODO turn this into a struct eventually 39 | constexpr uint32_t MD_DEVICE_CID_OFFSET = 0x58; 40 | constexpr uint32_t MD_DEVICE_CSD_OFFSET = 0x68; 41 | 42 | const std::map sdMidMap = { 43 | // from https://kernel.googlesource.com/pub/scm/utils/mmc/mmc-utils/+/refs/heads/master/lsmmc.c 44 | { 0x01, "Panasonic" }, 45 | { 0x02, "Toshiba/Kingston/Viking" }, 46 | { 0x03, "SanDisk" }, 47 | { 0x08, "Silicon Power" }, 48 | { 0x18, "Infineon" }, 49 | { 0x1b, "Transcend/Samsung" }, 50 | { 0x1c, "Transcend" }, 51 | { 0x1d, "Corsair/AData" }, 52 | { 0x1e, "Transcend" }, 53 | { 0x1f, "Kingston" }, 54 | { 0x27, "Delkin/Phison" }, 55 | { 0x28, "Lexar" }, 56 | { 0x30, "SanDisk" }, 57 | { 0x31, "Silicon Power" }, 58 | { 0x33, "STMicroelectronics" }, 59 | { 0x41, "Kingston" }, 60 | { 0x5d, "Swissbit"}, 61 | { 0x6f, "STMicroelectronics" }, 62 | { 0x74, "Transcend" }, 63 | { 0x76, "Patriot" }, 64 | { 0x82, "Gobe/Sony" }, 65 | { 0x89, "Unknown" }, 66 | { 0x9e, "PNY" }, 67 | }; 68 | 69 | const std::map mmcMidMap = { 70 | { 0x11, "Toshiba" }, 71 | // technically shared with Samsung/SanDisk/LG but Nintendo only used Samsung in Wii Us 72 | { 0x15, "Samsung" }, 73 | { 0x90, "Hynix" } 74 | }; 75 | 76 | } 77 | 78 | bool MemoryDevice::Init() 79 | { 80 | memoryDevices.clear(); 81 | 82 | // read driver structs from IOS-FS 83 | MDBlkDrv blkDrvs[2]{}; 84 | for (uint32_t i = 0; i < sizeof(blkDrvs) / 4; i++) { 85 | if (Mocha_IOSUKernelRead32(MDBLK_DRIVER_ADDRESS + (i * 4), ((uint32_t*) &blkDrvs) + i) != MOCHA_RESULT_SUCCESS) { 86 | return false; 87 | } 88 | } 89 | 90 | // read all SAL device pointers 91 | uint32_t devicePointers[8]{}; 92 | for (uint32_t i = 0; i < sizeof(devicePointers) / 4; i++) { 93 | if (Mocha_IOSUKernelRead32(MD_DEVICE_POINTERS_ADDRESS + (i * 4), devicePointers + i) != MOCHA_RESULT_SUCCESS) { 94 | return false; 95 | } 96 | } 97 | 98 | // Create a MemoryDevice instance for each struct 99 | for (MDBlkDrv& drv : blkDrvs) { 100 | // don't bother adding it if not attached 101 | if (!drv.registered) { 102 | continue; 103 | } 104 | 105 | MemoryDevice dev{}; 106 | dev.type = (Type) drv.params.device_type; 107 | dev.mid = drv.params.mid_prv >> 16; 108 | dev.prv = drv.params.mid_prv & 0xffff; 109 | dev.name = drv.params.name1; 110 | dev.numBlocks = drv.params.numBlocks; 111 | dev.blockSize = drv.params.blockSize; 112 | 113 | // some manufacturers place weird characters into their names? 114 | dev.name.erase(std::remove_if(dev.name.begin(), dev.name.end(), [](char c){return !(c >= 0 && c < 128);}), dev.name.end()); 115 | 116 | // read cid and csd 117 | int idx = drv.deviceId - 0x42; 118 | if (idx >= 0 && idx < 8 && devicePointers[idx]) { 119 | for (uint32_t i = 0; i < dev.cid.size() / 4; i++) { 120 | if (Mocha_IOSUKernelRead32(devicePointers[idx] + MD_DEVICE_CID_OFFSET + (i * 4), (uint32_t*) dev.cid.data() + i) != MOCHA_RESULT_SUCCESS) { 121 | return false; 122 | } 123 | } 124 | 125 | for (uint32_t i = 0; i < dev.csd.size() / 4; i++) { 126 | if (Mocha_IOSUKernelRead32(devicePointers[idx] + MD_DEVICE_CSD_OFFSET + (i * 4), (uint32_t*) dev.csd.data() + i) != MOCHA_RESULT_SUCCESS) { 127 | return false; 128 | } 129 | } 130 | } 131 | 132 | memoryDevices.push_back(dev); 133 | } 134 | 135 | return true; 136 | } 137 | 138 | MemoryDevice::CardType MemoryDevice::GetCardType() const 139 | { 140 | // if we have a mmc manufacturer for this mid it's most likely a mmc 141 | if (mmcMidMap.contains(GetMID())) { 142 | return CARD_TYPE_MMC; 143 | } 144 | 145 | // otherwise if we have a sd manufacturer it's probably an sd card 146 | if (sdMidMap.contains(GetMID())) { 147 | return CARD_TYPE_SD; 148 | } 149 | 150 | return CARD_TYPE_UNKNOWN; 151 | } 152 | 153 | std::string MemoryDevice::GetManufacturerName() const 154 | { 155 | CardType cardType = GetCardType(); 156 | if (cardType == CARD_TYPE_MMC) { 157 | return mmcMidMap.at(GetMID()); 158 | } else if (cardType == CARD_TYPE_SD) { 159 | return sdMidMap.at(GetMID()); 160 | } 161 | 162 | return "Unknown"; 163 | } 164 | 165 | std::string MemoryDevice::GetProductionDate() const 166 | { 167 | if (GetCardType() != CARD_TYPE_MMC) { 168 | // we currently don't read the production data for non mmc types 169 | return ""; 170 | } 171 | 172 | uint8_t month = (cid[14] >> 4) & 0xf; 173 | uint16_t year = cid[14] & 0xf; 174 | year += 1997; 175 | if(year < 2005) 176 | year += 0x10; 177 | 178 | return Utils::sprintf("%u/%02u", year, month); 179 | } 180 | -------------------------------------------------------------------------------- /source/system/MemoryDevice.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class MemoryDevice 9 | { 10 | public: 11 | virtual ~MemoryDevice() = default; 12 | MemoryDevice(const MemoryDevice&) = default; 13 | 14 | static bool Init(); 15 | static const std::vector& GetDevices() { return memoryDevices; } 16 | 17 | public: 18 | enum Type { 19 | TYPE_MLC = 0x05, 20 | TYPE_SD_CARD = 0x06, 21 | // used instead of MLC if bsp_variant == 0x29 || bsp_variant == 0x21 22 | TYPE_UNKNOWN = 0x12, 23 | }; 24 | 25 | Type GetType() const { return type; } 26 | uint16_t GetMID() const { return mid; } 27 | uint16_t GetPRV() const { return prv; } 28 | std::string GetName() const { return name; } 29 | 30 | uint64_t GetNumBlocks() const { return numBlocks; } 31 | uint32_t GetBlockSize() const { return blockSize; } 32 | uint64_t GetTotalSize() const { return GetNumBlocks() * GetBlockSize(); } 33 | 34 | const std::array& GetCID() const { return cid; } 35 | const std::array& GetCSD() const { return csd; } 36 | 37 | enum CardType { 38 | CARD_TYPE_MMC, 39 | CARD_TYPE_SD, 40 | CARD_TYPE_UNKNOWN, 41 | }; 42 | 43 | CardType GetCardType() const; 44 | std::string GetManufacturerName() const; 45 | std::string GetProductionDate() const; 46 | 47 | private: 48 | MemoryDevice() = default; 49 | 50 | static inline std::vector memoryDevices = {}; 51 | 52 | private: 53 | Type type; 54 | uint16_t mid; 55 | uint16_t prv; 56 | std::string name; 57 | uint64_t numBlocks; 58 | uint32_t blockSize; 59 | std::array cid; 60 | std::array csd; 61 | }; 62 | -------------------------------------------------------------------------------- /source/system/OTP.cpp: -------------------------------------------------------------------------------- 1 | #include "OTP.hpp" 2 | 3 | #include 4 | 5 | bool OTP::Init() 6 | { 7 | if (Mocha_ReadOTP(&cachedOTP) != MOCHA_RESULT_SUCCESS) { 8 | return false; 9 | } 10 | 11 | return true; 12 | } 13 | -------------------------------------------------------------------------------- /source/system/OTP.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class OTP 6 | { 7 | public: 8 | static bool Init(); 9 | static const WiiUConsoleOTP& Get() { return cachedOTP; } 10 | 11 | private: 12 | OTP() = default; 13 | virtual ~OTP() = default; 14 | 15 | static inline WiiUConsoleOTP cachedOTP; 16 | }; 17 | -------------------------------------------------------------------------------- /source/system/SEEPROM.cpp: -------------------------------------------------------------------------------- 1 | #include "SEEPROM.hpp" 2 | 3 | #include 4 | 5 | bool SEEPROM::Init() 6 | { 7 | if (Mocha_SEEPROMRead((uint8_t*) &cachedSEEPROMData, 0, sizeof(Data)) != sizeof(Data)) { 8 | return false; 9 | } 10 | 11 | return true; 12 | } 13 | -------------------------------------------------------------------------------- /source/system/SEEPROM.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class SEEPROM 6 | { 7 | public: 8 | struct WUT_PACKED BoardConfig { 9 | uint32_t crc32; 10 | uint16_t size; 11 | uint16_t version; 12 | uint16_t author; 13 | uint16_t boardType; 14 | uint16_t boardRevision; 15 | uint16_t bootSource; 16 | uint16_t ddr3Size; 17 | uint16_t ddr3Speed; 18 | uint16_t ppcClockMultiplier; 19 | uint16_t iopClockMultiplier; 20 | uint16_t video1080p; 21 | uint16_t ddr3Vendor; 22 | uint16_t movPassiveReset; 23 | uint16_t sysPllSpeed; 24 | uint16_t sataDevice; 25 | uint16_t consoleType; 26 | uint32_t devicePresence; 27 | WUT_UNKNOWN_BYTES(0x20); 28 | }; 29 | WUT_CHECK_SIZE(BoardConfig, 0x48); 30 | 31 | struct WUT_PACKED SysProd { 32 | uint32_t version; 33 | uint32_t eeprom_version; 34 | uint32_t product_area; 35 | uint32_t game_region; 36 | uint32_t ntsc_pal; 37 | uint16_t wifi_5ghz_country_code; 38 | uint16_t wifi_5ghz_country_code_revision; 39 | char code_id[0x08]; 40 | char serial_id[0x10]; 41 | char model_numer[0x10]; 42 | }; 43 | WUT_CHECK_SIZE(SysProd, 0x40); 44 | 45 | struct WUT_PACKED ProdInfo { 46 | WUT_UNKNOWN_BYTES(0x8); 47 | uint16_t prod_year; 48 | uint16_t prod_month_day; 49 | uint16_t prod_hour_minute; 50 | uint32_t crc32; 51 | }; 52 | WUT_CHECK_SIZE(ProdInfo, 0x12); 53 | 54 | struct WUT_PACKED BootParams { 55 | uint16_t cpu_flags; 56 | uint16_t nand_sd_flags; 57 | uint32_t nand_config; 58 | uint32_t nand_bank; 59 | uint32_t crc32; 60 | }; 61 | WUT_CHECK_SIZE(BootParams, 0x10); 62 | 63 | struct WUT_PACKED Boot1Info { 64 | uint16_t version; 65 | uint16_t sector; 66 | WUT_UNKNOWN_BYTES(0x8); 67 | uint32_t crc32; 68 | }; 69 | WUT_CHECK_SIZE(Boot1Info, 0x10); 70 | 71 | struct WUT_PACKED Data { 72 | WUT_UNKNOWN_BYTES(0x12); 73 | uint8_t rng_seed[0x08]; 74 | WUT_UNKNOWN_BYTES(0x6); 75 | uint32_t ppc_pvr; 76 | char seeprom_version_name[0x06]; 77 | uint16_t seeprom_version_code; 78 | uint16_t otp_version_code; 79 | uint16_t otp_revision_code; 80 | char otp_version_name[0x08]; 81 | BoardConfig bc; 82 | uint8_t drive_key[0x10]; 83 | uint8_t factory_key[0x10]; 84 | uint8_t shdd_key[0x10]; 85 | uint8_t usb_key_seed[0x10]; 86 | uint16_t drive_key_status; 87 | uint16_t usk_key_status; 88 | uint16_t shdd_key_status; 89 | WUT_UNKNOWN_BYTES(0x7A); 90 | SysProd sys_prod; 91 | ProdInfo prod_info; 92 | uint16_t marker0; 93 | WUT_UNKNOWN_BYTES(0xe); 94 | uint16_t marker1; 95 | WUT_UNKNOWN_BYTES(0x1c); 96 | BootParams boot_params; 97 | Boot1Info boot1_info[2]; 98 | WUT_UNKNOWN_BYTES(0x10); 99 | }; 100 | WUT_CHECK_SIZE(Data, 0x200); 101 | 102 | static bool Init(); 103 | static const SEEPROM::Data& Get() { return cachedSEEPROMData; } 104 | 105 | private: 106 | static inline SEEPROM::Data cachedSEEPROMData; 107 | }; 108 | --------------------------------------------------------------------------------