From 81897a0881dcad328a2837f5dc9d0bb5d4eabf3d Mon Sep 17 00:00:00 2001 From: Nanolx Date: Sat, 22 Dec 2012 18:35:15 +0100 Subject: [PATCH] initial import --- COPYING | 339 +++++++++ Makefile | 151 ++++ README.md | 52 +- data/background | Bin 0 -> 59700 bytes data/background.png | Bin 0 -> 59700 bytes data/ehcmodule.elf | Bin 0 -> 25614 bytes rvl.ld | 295 ++++++++ source/fat.c | 58 ++ source/fat.h | 55 ++ source/globals.h | 57 ++ source/gpio.h | 58 ++ source/gui.c | 90 +++ source/gui.h | 8 + source/hollywood.h | 179 +++++ source/libpng/pngu/pngu.c | 1132 ++++++++++++++++++++++++++++ source/libpng/pngu/pngu.h | 171 +++++ source/menu.c | 1478 +++++++++++++++++++++++++++++++++++++ source/menu.h | 8 + source/mload.c | 399 ++++++++++ source/mload.h | 194 +++++ source/nand.c | 86 +++ source/nand.h | 24 + source/restart.c | 33 + source/restart.h | 8 + source/sha1.c | 177 +++++ source/sha1.h | 6 + source/stub.S | 6 + source/sys.c | 188 +++++ source/sys.h | 14 + source/title.c | 324 ++++++++ source/title.h | 19 + source/usbstorage.c | 400 ++++++++++ source/usbstorage.h | 27 + source/utils.h | 15 + source/video.c | 141 ++++ source/video.h | 19 + source/wad-manager.c | 494 +++++++++++++ source/wad.c | 797 ++++++++++++++++++++ source/wad.h | 9 + source/wkb.c | 47 ++ source/wkb.h | 19 + source/wpad.c | 105 +++ source/wpad.h | 13 + source/xyzzy.c | 173 +++++ source/xyzzy.h | 74 ++ wm_config.txt | 25 + 46 files changed, 7965 insertions(+), 2 deletions(-) create mode 100644 COPYING create mode 100644 Makefile create mode 100644 data/background create mode 100644 data/background.png create mode 100644 data/ehcmodule.elf create mode 100644 rvl.ld create mode 100644 source/fat.c create mode 100644 source/fat.h create mode 100644 source/globals.h create mode 100644 source/gpio.h create mode 100644 source/gui.c create mode 100644 source/gui.h create mode 100644 source/hollywood.h create mode 100644 source/libpng/pngu/pngu.c create mode 100644 source/libpng/pngu/pngu.h create mode 100644 source/menu.c create mode 100644 source/menu.h create mode 100644 source/mload.c create mode 100644 source/mload.h create mode 100644 source/nand.c create mode 100644 source/nand.h create mode 100644 source/restart.c create mode 100644 source/restart.h create mode 100644 source/sha1.c create mode 100644 source/sha1.h create mode 100644 source/stub.S create mode 100644 source/sys.c create mode 100644 source/sys.h create mode 100644 source/title.c create mode 100644 source/title.h create mode 100644 source/usbstorage.c create mode 100644 source/usbstorage.h create mode 100644 source/utils.h create mode 100644 source/video.c create mode 100644 source/video.h create mode 100644 source/wad-manager.c create mode 100644 source/wad.c create mode 100644 source/wad.h create mode 100644 source/wkb.c create mode 100644 source/wkb.h create mode 100644 source/wpad.c create mode 100644 source/wpad.h create mode 100644 source/xyzzy.c create mode 100644 source/xyzzy.h create mode 100644 wm_config.txt diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..7e14f38 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d1f25dc --- /dev/null +++ b/Makefile @@ -0,0 +1,151 @@ +#--------------------------------------------------------------------------------- +# Clear the implicit built in rules +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +#DEVKITPRO = /opt/devkitPPC +#DEVKITPPC = /opt/devkitPPC + +ifeq ($(strip $(DEVKITPPC)),) +$(error "Please set DEVKITPPC in your environment. export DEVKITPPC=devkitPPC") +endif + +include $(DEVKITPPC)/wii_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source source/libtinysmb source/libpng source/libpng/pngu +DATA := data +INCLUDES := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- + +CFLAGS = -Os -Wall -mrvl $(MACHDEP) $(INCLUDE) +CXXFLAGS = $(CFLAGS) + +LDFLAGS = $(MACHDEP) -Wl,-Map,$(notdir $@).map -T../rvl.ld + +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -ltinysmb -lpng -lfat -lwiiuse -lbte -logc -lm -lz -lwiilight -lruntimeiospatch + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +#--------------------------------------------------------------------------------- +# automatically build a list of object files for our project +#--------------------------------------------------------------------------------- +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) + export LD := $(CC) +else + export LD := $(CXX) +endif + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \ + $(sFILES:.s=.o) $(SFILES:.S=.o) + +#--------------------------------------------------------------------------------- +# build a list of include paths +#--------------------------------------------------------------------------------- +export INCLUDE := $(foreach dir,$(INCLUDES), -iquote $(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) \ + -I$(LIBOGC_INC) + +#--------------------------------------------------------------------------------- +# build a list of library paths +#--------------------------------------------------------------------------------- +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \ + -L$(LIBOGC_LIB) + +export OUTPUT := $(CURDIR)/$(TARGET) +.PHONY: $(BUILD) clean + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).dol + +#--------------------------------------------------------------------------------- +run: + wiiload $(TARGET).dol + + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT).dol: $(OUTPUT).elf +$(OUTPUT).elf: $(OFILES) + +#--------------------------------------------------------------------------------- +# This rule links in binary data with the .jpg extension +#--------------------------------------------------------------------------------- +%.jpg.o : %.jpg +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + $(bin2o) + +%.elf.o : %.elf + @echo $(notdir $<) + $(bin2o) + +%.dat.o : %.dat + @echo $(notdir $<) + $(bin2o) + +%.png.o : %.png + @echo $(notdir $<) + $(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + diff --git a/README.md b/README.md index 7b4856f..36a7724 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,50 @@ -yawmmde -======= \ No newline at end of file ++----------------------------+ +| WAD Manager v1.5 | +| developed by Waninkoko | ++----------------------------+ +| www.teknoconsolas.es | ++----------------------------+ + + +[ DISCLAIMER ]: + +- THIS APPLICATION COMES WITH NO WARRANTY AT ALL, NEITHER EXPRESS NOR IMPLIED. + I DO NOT TAKE ANY RESPONSIBILITY FOR ANY DAMAGE IN YOUR WII CONSOLE + BECAUSE OF A IMPROPER USAGE OF THIS SOFTWARE. + + +[ DESCRIPTION ]: + +- WAD Manager is an application that allows you to (un)install + WAD packages. + + It lists all the available WAD packages in a storage device + so you can select which one to (un)install. + + +[ SUPPORTED DEVICES ]: + +- SDGecko. +- Internal SD slot (with SDHC support). +- USB device (1.1 and 2.0). + + +[ HOW TO USE ]: + +1. Create a folder called "wad" in the root of the storage device. +2. Copy all the WAD packages in the folder created in the step 1. +3. Run the application with any method to load homebrew. + + +[ NOTES ]: + +- To use the NAND emulation is necessary to have a COMPLETE copy + of the NAND filesystem in the root of the FAT device. + + +[ KUDOS ]: + +- Team Twiizers/devkitPRO +- svpe +- kwiirk +- All my betatesters. diff --git a/data/background b/data/background new file mode 100644 index 0000000000000000000000000000000000000000..2d39ecbed2f07bbbe1f6c80f3260c4fe0aa822ab GIT binary patch literal 59700 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 bcOr9Vy-YK=?b!1i>qapt +#include +#include +//#include + +#include "fat.h" + + +s32 Fat_Mount(fatDevice *dev) +{ + s32 ret; + + /* Initialize interface */ + ret = dev->interface->startup(); + if (!ret) + return -1; + + /* Mount device */ + ret = fatMountSimple(dev->mount, dev->interface); + if (!ret) + return -1; + + return 0; +} + +void Fat_Unmount(fatDevice *dev) +{ + /* Unmount device */ + fatUnmount(dev->mount); + + /* Shutdown interface */ + dev->interface->shutdown(); +} + +char *Fat_ToFilename(const char *filename) +{ + static char buffer[128]; + + u32 cnt, idx, len; + + /* Clear buffer */ + memset(buffer, 0, sizeof(buffer)); + + /* Get filename length */ + len = strlen(filename); + + for (cnt = idx = 0; idx < len; idx++) { + char c = filename[idx]; + + /* Valid characters */ + if ( (c >= '#' && c <= ')') || (c >= '-' && c <= '.') || + (c >= '0' && c <= '9') || (c >= 'A' && c <= 'z') || + (c >= 'a' && c <= 'z') || (c == '!') ) + buffer[cnt++] = c; + } + + return buffer; +} diff --git a/source/fat.h b/source/fat.h new file mode 100644 index 0000000..43033fd --- /dev/null +++ b/source/fat.h @@ -0,0 +1,55 @@ +#ifndef _FAT_H_ +#define _FAT_H_ + +/* libfat header */ +#include +#include + +/* SD headers */ +#include +#include + + +/* 'FAT Device' structure */ +typedef struct { + /* Device mount point */ + char *mount; + + /* Device name */ + char *name; + + /* Device interface */ + const DISC_INTERFACE *interface; +} fatDevice; + +/* 'FAT File' structure */ +typedef struct { + /* Filename */ + char filename[128]; + /* 1 = Batch Install, 2 = Batch Uninstall - Leathl */ + int install; + + int installstate; + + int old_ios; + int new_ios; + bool old_ahbprot; + bool new_ahbprot; + bool old_pass; + bool new_pass; + + u32 high_id; + u32 low_id; + + /* Filestat */ + struct dirent entry; +} fatFile; + + +/* Prototypes */ +s32 Fat_Mount(fatDevice *); +void Fat_Unmount(fatDevice *); +char *Fat_ToFilename(const char *); + +#endif + diff --git a/source/globals.h b/source/globals.h new file mode 100644 index 0000000..e4d7f25 --- /dev/null +++ b/source/globals.h @@ -0,0 +1,57 @@ +#ifndef _GLOBALS_H_ +#define _GLOBALS_H_ + +// Constants +#define CIOS_VERSION 249 +#define ENTRIES_PER_PAGE 12 +#define MAX_FILE_PATH_LEN 1024 +#define MAX_DIR_LEVELS 10 +#define WAD_DIRECTORY "/" + +#define MAX_PASSWORD_LENGTH 10 +#define MAX_FAT_DEVICE_LENGTH 10 +#define MAX_NAND_DEVICE_LENGTH 10 + +#define WM_CONFIG_FILE_PATH ":/wad/wm_config.txt" +#define WM_BACKGROUND_PATH ":/wad/background.png" + +// These are indices into the fatDevice fdevList +#define FAT_DEVICE_INDEX_WII_SD 0 +#define FAT_DEVICE_INDXE_USB 1 +#define FAT_DEVICE_INDEX_USB2 2 +#define FAT_DEVICE_INDEX_GC_SDA 3 +#define FAT_DEVICE_INDEX_GC_SDB 4 +#define FAT_DEVICE_INDEX_INVALID -1 + +// These are the indices into the nandDevice ndevList +#define NAND_DEVICE_INDEX_DISABLE 0 +#define NAND_DEVICE_INDEX_SD 1 +#define NAND_DEVICE_INDEX_USB 2 +#define NAND_DEVICE_INDEX_INVALID -1 + +#define CIOS_VERSION_INVALID -1 + +// For the WiiLight +#define WII_LIGHT_OFF 0 +#define WII_LIGHT_ON 1 + +typedef struct +{ + char password[MAX_PASSWORD_LENGTH]; + char startupPath [256]; + int cIOSVersion; + int fatDeviceIndex; + int nandDeviceIndex; + const char *smbuser; + const char *smbpassword; + const char *share; + const char *ip; +} CONFIG; + + +extern CONFIG gConfig; +extern nandDevice ndevList[]; +extern fatDevice fdevList[]; + + +#endif diff --git a/source/gpio.h b/source/gpio.h new file mode 100644 index 0000000..ec80fc2 --- /dev/null +++ b/source/gpio.h @@ -0,0 +1,58 @@ +/* + mini - a Free Software replacement for the Nintendo/BroadOn IOS. + GPIO pin-out constants + +Copyright (C) 2008, 2009 Hector Martin "marcan" + +# This code is licensed to you under the terms of the GNU GPL, version 2; +# see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +*/ + +#ifndef __GPIO_H__ +#define __GPIO_H__ + +enum { + GP_POWER = 0x000001, + GP_SHUTDOWN = 0x000002, + GP_FAN = 0x000004, + GP_DCDC = 0x000008, + GP_DISPIN = 0x000010, + GP_SLOTLED = 0x000020, + GP_EJECTBTN = 0x000040, + GP_SLOTIN = 0x000080, + GP_SENSORBAR = 0x000100, + GP_DOEJECT = 0x000200, + GP_EEP_CS = 0x000400, + GP_EEP_CLK = 0x000800, + GP_EEP_MOSI = 0x001000, + GP_EEP_MISO = 0x002000, + GP_AVE_SCL = 0x004000, + GP_AVE_SDA = 0x008000, + GP_DEBUG0 = 0x010000, + GP_DEBUG1 = 0x020000, + GP_DEBUG2 = 0x040000, + GP_DEBUG3 = 0x080000, + GP_DEBUG4 = 0x100000, + GP_DEBUG5 = 0x200000, + GP_DEBUG6 = 0x400000, + GP_DEBUG7 = 0x800000, +}; + +#define GP_DEBUG_SHIFT 16 +#define GP_DEBUG_MASK 0xFF0000 + +#define GP_ALL 0xFFFFFF +#define GP_OWNER_PPC (GP_AVE_SDA | GP_AVE_SCL | GP_DOEJECT | GP_SENSORBAR | GP_SLOTIN | GP_SLOTLED) +#define GP_OWNER_ARM (GP_ALL ^ GP_OWNER_PPC) +#define GP_INPUTS (GP_POWER | GP_EJECTBTN | GP_SLOTIN | GP_EEP_MISO | GP_AVE_SDA) +#define GP_OUTPUTS (GP_ALL ^ GP_INPUTS) +#define GP_ARM_INPUTS (GP_INPUTS & GP_OWNER_ARM) +#define GP_PPC_INPUTS (GP_INPUTS & GP_OWNER_PPC) +#define GP_ARM_OUTPUTS (GP_OUTPUTS & GP_OWNER_ARM) +#define GP_PPC_OUTPUTS (GP_OUTPUTS & GP_OWNER_PPC) +#define GP_DEFAULT_ON (GP_AVE_SCL | GP_DCDC | GP_FAN) +#define GP_ARM_DEFAULT_ON (GP_DEFAULT_ON & GP_OWNER_ARM) +#define GP_PPC_DEFAULT_ON (GP_DEFAULT_ON & GP_OWNER_PPC) + +#endif + diff --git a/source/gui.c b/source/gui.c new file mode 100644 index 0000000..ba8a245 --- /dev/null +++ b/source/gui.c @@ -0,0 +1,90 @@ +#include +#include +#include + +#include "video.h" +#include "fat.h" +#include "menu.h" +#include "nand.h" +#include "globals.h" + +/* Constants */ +#define CONSOLE_XCOORD 50 +#define CONSOLE_YCOORD 100 +#define CONSOLE_WIDTH 540 +#define CONSOLE_HEIGHT 320 + +bool file_exists(const char * filename) +{ + FILE * file; + if (file = fopen(filename, "r")) + { + fclose(file); + return true; + } + return false; +} + + +s32 __Gui_DrawPng(void *img, u32 x, u32 y) +{ + IMGCTX ctx = NULL; + PNGUPROP imgProp; + + s32 ret; + + fatDevice *fdev = &fdevList[0]; + ret = Fat_Mount(fdev); + if (file_exists("sd:/wad/background.png")) ctx = PNGU_SelectImageFromDevice ("sd:/wad/background.png"); + + if (ret < 0) + { + fdev = &fdevList[2]; + Fat_Mount(fdev); + if (file_exists("usb2:/wad/background.png")) ctx = PNGU_SelectImageFromDevice ("usb2:/wad/background.png"); + } + + if(!ctx) + { + /* Select PNG data */ + ctx = PNGU_SelectImageFromBuffer(img); + if (!ctx) { + ret = -1; + goto out; + } + } + /* Get image properties */ + ret = PNGU_GetImageProperties(ctx, &imgProp); + if (ret != PNGU_OK) { + ret = -1; + goto out; + } + + /* Draw image */ + Video_DrawPng(ctx, imgProp, x, y); + + /* Success */ + ret = 0; + +out: + /* Free memory */ + if (ctx) + PNGU_ReleaseImageContext(ctx); + + return ret; +} + + +void Gui_InitConsole(void) +{ + /* Initialize console */ + Con_Init(CONSOLE_XCOORD, CONSOLE_YCOORD, CONSOLE_WIDTH, CONSOLE_HEIGHT); +} + +void Gui_DrawBackground(void) +{ + extern char bgData[]; + + /* Draw background */ + __Gui_DrawPng(bgData, 0, 0); +} diff --git a/source/gui.h b/source/gui.h new file mode 100644 index 0000000..80f174c --- /dev/null +++ b/source/gui.h @@ -0,0 +1,8 @@ +#ifndef _GUI_H_ +#define _GUI_H_ + +/* Prototypes */ +void Gui_InitConsole(void); +void Gui_DrawBackground(void); + +#endif diff --git a/source/hollywood.h b/source/hollywood.h new file mode 100644 index 0000000..3543861 --- /dev/null +++ b/source/hollywood.h @@ -0,0 +1,179 @@ +/* + mini - a Free Software replacement for the Nintendo/BroadOn IOS. + Hollywood register definitions + +Copyright (C) 2008, 2009 Haxx Enterprises +Copyright (C) 2008, 2009 Sven Peter +Copyright (C) 2008, 2009 Hector Martin "marcan" +Copyright (C) 2008, 2009 John Kelley + +# This code is licensed to you under the terms of the GNU GPL, version 2; +# see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +*/ + +#ifndef __HOLLYWOOD_H__ +#define __HOLLYWOOD_H__ + +/* Hollywood Registers */ + +#define HW_PPC_REG_BASE 0xd000000 +#define HW_REG_BASE 0xd800000 + +// The PPC can only see the first three IPC registers +#define HW_IPC_PPCMSG (HW_REG_BASE + 0x000) +#define HW_IPC_PPCCTRL (HW_REG_BASE + 0x004) +#define HW_IPC_ARMMSG (HW_REG_BASE + 0x008) +#define HW_IPC_ARMCTRL (HW_REG_BASE + 0x00c) + +#define HW_TIMER (HW_REG_BASE + 0x010) +#define HW_ALARM (HW_REG_BASE + 0x014) + +#define HW_PPCIRQFLAG (HW_REG_BASE + 0x030) +#define HW_PPCIRQMASK (HW_REG_BASE + 0x034) + +#define HW_ARMIRQFLAG (HW_REG_BASE + 0x038) +#define HW_ARMIRQMASK (HW_REG_BASE + 0x03c) + +#define HW_MEMMIRR (HW_REG_BASE + 0x060) +#define HW_AHBPROT (HW_REG_BASE + 0x064) + +// something to do with PPCBOOT +// and legacy DI it seems ?!? +#define HW_EXICTRL (HW_REG_BASE + 0x070) +#define EXICTRL_ENABLE_EXI 1 + +// PPC side of GPIO1 (Starlet can access this too) +// Output state +#define HW_GPIO1BOUT (HW_REG_BASE + 0x0c0) +// Direction (1=output) +#define HW_GPIO1BDIR (HW_REG_BASE + 0x0c4) +// Input state +#define HW_GPIO1BIN (HW_REG_BASE + 0x0c8) +// Interrupt level +#define HW_GPIO1BINTLVL (HW_REG_BASE + 0x0cc) +// Interrupt flags (write 1 to clear) +#define HW_GPIO1BINTFLAG (HW_REG_BASE + 0x0d0) +// Interrupt propagation enable +// Do these interrupts go anywhere??? +#define HW_GPIO1BINTENABLE (HW_REG_BASE + 0x0d4) +//??? seems to be a mirror of inputs at some point... power-up state? +#define HW_GPIO1BINMIR (HW_REG_BASE + 0x0d8) +// 0xFFFFFF by default, if cleared disables respective outputs. Top bits non-settable. +#define HW_GPIO1ENABLE (HW_REG_BASE + 0x0dc) + +#define HW_GPIO1_SLOT 0x000020 +#define HW_GPIO1_DEBUG 0xFF0000 +#define HW_GPIO1_DEBUG_SH 16 + +// Starlet side of GPIO1 +// Output state +#define HW_GPIO1OUT (HW_REG_BASE + 0x0e0) +// Direction (1=output) +#define HW_GPIO1DIR (HW_REG_BASE + 0x0e4) +// Input state +#define HW_GPIO1IN (HW_REG_BASE + 0x0e8) +// Interrupt level +#define HW_GPIO1INTLVL (HW_REG_BASE + 0x0ec) +// Interrupt flags (write 1 to clear) +#define HW_GPIO1INTFLAG (HW_REG_BASE + 0x0f0) +// Interrupt propagation enable (interrupts go to main interrupt 0x800) +#define HW_GPIO1INTENABLE (HW_REG_BASE + 0x0f4) +//??? seems to be a mirror of inputs at some point... power-up state? +#define HW_GPIO1INMIR (HW_REG_BASE + 0x0f8) +// Owner of each GPIO bit. If 1, GPIO1B registers assume control. If 0, GPIO1 registers assume control. +#define HW_GPIO1OWNER (HW_REG_BASE + 0x0fc) + +// ???? +#define HW_DIFLAGS (HW_REG_BASE + 0x180) +#define DIFLAGS_BOOT_CODE 0x100000 + +// maybe a GPIO??? +#define HW_RESETS (HW_REG_BASE + 0x194) + +#define HW_CLOCKS (HW_REG_BASE + 0x1b4) + +#define HW_GPIO2OUT (HW_REG_BASE + 0x1c8) +#define HW_GPIO2DIR (HW_REG_BASE + 0x1cc) +#define HW_GPIO2IN (HW_REG_BASE + 0x1d0) + +#define HW_OTPCMD (HW_REG_BASE + 0x1ec) +#define HW_OTPDATA (HW_REG_BASE + 0x1f0) +#define HW_VERSION (HW_REG_BASE + 0x214) + +/* NAND Registers */ + +#define NAND_REG_BASE 0xd010000 + +#define NAND_CMD (NAND_REG_BASE + 0x000) +#define NAND_STATUS NAND_CMD +#define NAND_CONF (NAND_REG_BASE + 0x004) +#define NAND_ADDR0 (NAND_REG_BASE + 0x008) +#define NAND_ADDR1 (NAND_REG_BASE + 0x00c) +#define NAND_DATA (NAND_REG_BASE + 0x010) +#define NAND_ECC (NAND_REG_BASE + 0x014) +#define NAND_UNK1 (NAND_REG_BASE + 0x018) +#define NAND_UNK2 (NAND_REG_BASE + 0x01c) + +/* AES Registers */ + +#define AES_REG_BASE 0xd020000 + +#define AES_CMD (AES_REG_BASE + 0x000) +#define AES_SRC (AES_REG_BASE + 0x004) +#define AES_DEST (AES_REG_BASE + 0x008) +#define AES_KEY (AES_REG_BASE + 0x00c) +#define AES_IV (AES_REG_BASE + 0x010) + +/* SHA-1 Registers */ + +#define SHA_REG_BASE 0xd030000 + +#define SHA_CMD (SHA_REG_BASE + 0x000) +#define SHA_SRC (SHA_REG_BASE + 0x004) +#define SHA_H0 (SHA_REG_BASE + 0x008) +#define SHA_H1 (SHA_REG_BASE + 0x00c) +#define SHA_H2 (SHA_REG_BASE + 0x010) +#define SHA_H3 (SHA_REG_BASE + 0x014) +#define SHA_H4 (SHA_REG_BASE + 0x018) + +/* SD Host Controller Registers */ + +#define SDHC_REG_BASE 0xd070000 + +/* EXI Registers */ + +#define EXI_REG_BASE 0xd806800 +#define EXI0_REG_BASE (EXI_REG_BASE+0x000) +#define EXI1_REG_BASE (EXI_REG_BASE+0x014) +#define EXI2_REG_BASE (EXI_REG_BASE+0x028) + +#define EXI0_CSR (EXI0_REG_BASE+0x000) +#define EXI0_MAR (EXI0_REG_BASE+0x004) +#define EXI0_LENGTH (EXI0_REG_BASE+0x008) +#define EXI0_CR (EXI0_REG_BASE+0x00c) +#define EXI0_DATA (EXI0_REG_BASE+0x010) + +#define EXI1_CSR (EXI1_REG_BASE+0x000) +#define EXI1_MAR (EXI1_REG_BASE+0x004) +#define EXI1_LENGTH (EXI1_REG_BASE+0x008) +#define EXI1_CR (EXI1_REG_BASE+0x00c) +#define EXI1_DATA (EXI1_REG_BASE+0x010) + +#define EXI2_CSR (EXI2_REG_BASE+0x000) +#define EXI2_MAR (EXI2_REG_BASE+0x004) +#define EXI2_LENGTH (EXI2_REG_BASE+0x008) +#define EXI2_CR (EXI2_REG_BASE+0x00c) +#define EXI2_DATA (EXI2_REG_BASE+0x010) + +#define EXI_BOOT_BASE (EXI_REG_BASE+0x040) + +/* MEMORY CONTROLLER Registers */ + +#define MEM_REG_BASE 0xd8b4000 +#define MEM_PROT (MEM_REG_BASE+0x20a) +#define MEM_PROT_START (MEM_REG_BASE+0x20c) +#define MEM_PROT_END (MEM_REG_BASE+0x20e) +#define MEM_FLUSHREQ (MEM_REG_BASE+0x228) +#define MEM_FLUSHACK (MEM_REG_BASE+0x22a) + +#endif diff --git a/source/libpng/pngu/pngu.c b/source/libpng/pngu/pngu.c new file mode 100644 index 0000000..f98003b --- /dev/null +++ b/source/libpng/pngu/pngu.c @@ -0,0 +1,1132 @@ +/******************************************************************************************** + +PNGU Version : 0.2a + +Coder : frontier + +More info : http://frontier-dev.net + +********************************************************************************************/ +#include +#include +#include "pngu.h" +#include + + +// Constants +#define PNGU_SOURCE_BUFFER 1 +#define PNGU_SOURCE_DEVICE 2 + + +// Prototypes of helper functions +int pngu_info (IMGCTX ctx); +int pngu_decode (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, PNGU_u32 stripAlpha); +void pngu_free_info (IMGCTX ctx); +void pngu_read_data_from_buffer (png_structp png_ptr, png_bytep data, png_size_t length); +void pngu_write_data_to_buffer (png_structp png_ptr, png_bytep data, png_size_t length); +void pngu_flush_data_to_buffer (png_structp png_ptr); +int pngu_clamp (int value, int min, int max); + + +// PNGU Image context struct +struct _IMGCTX +{ + int source; + void *buffer; + char *filename; + PNGU_u32 cursor; + + PNGU_u32 propRead; + PNGUPROP prop; + + PNGU_u32 infoRead; + png_structp png_ptr; + png_infop info_ptr; + FILE *fd; + + png_bytep *row_pointers; + png_bytep img_data; +}; + + +// PNGU Implementation // + +IMGCTX PNGU_SelectImageFromBuffer (const void *buffer) +{ + IMGCTX ctx = NULL; + + if (!buffer) + return NULL; + + ctx = malloc (sizeof (struct _IMGCTX)); + if (!ctx) + return NULL; + + ctx->buffer = (void *) buffer; + ctx->source = PNGU_SOURCE_BUFFER; + ctx->cursor = 0; + ctx->filename = NULL; + ctx->propRead = 0; + ctx->infoRead = 0; + + return ctx; +} + + +IMGCTX PNGU_SelectImageFromDevice (const char *filename) +{ + IMGCTX ctx = NULL; + + if (!filename) + return NULL; + + ctx = malloc (sizeof (struct _IMGCTX)); + if (!ctx) + return NULL; + + ctx->buffer = NULL; + ctx->source = PNGU_SOURCE_DEVICE; + ctx->cursor = 0; + + ctx->filename = malloc (strlen (filename) + 1); + if (!ctx->filename) + { + free (ctx); + return NULL; + } + strcpy(ctx->filename, filename); + + ctx->propRead = 0; + ctx->infoRead = 0; + + return ctx; +} + + +void PNGU_ReleaseImageContext (IMGCTX ctx) +{ + if (!ctx) + return; + + if (ctx->filename) + free (ctx->filename); + + if ((ctx->propRead) && (ctx->prop.trans)) + free (ctx->prop.trans); + + pngu_free_info (ctx); + + free (ctx); +} + + +int PNGU_GetImageProperties (IMGCTX ctx, PNGUPROP *imgprop) +{ + int res; + + if (!ctx->propRead) + { + res = pngu_info (ctx); + if (res != PNGU_OK) + return res; + } + + *imgprop = ctx->prop; + + return PNGU_OK; +} + + +int PNGU_DecodeToYCbYCr (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride) +{ + int result; + PNGU_u32 x, y, buffWidth; + + // width needs to be divisible by two + if (width % 2) + return PNGU_ODD_WIDTH; + + // stride needs to be divisible by two + if (stride % 2) + return PNGU_ODD_STRIDE; + + result = pngu_decode (ctx, width, height, 1); + if (result != PNGU_OK) + return result; + + // Copy image to the output buffer + buffWidth = (width + stride) / 2; + for (y = 0; y < height; y++) + for (x = 0; x < (width / 2); x++) + ((PNGU_u32 *)buffer)[y*buffWidth+x] = PNGU_RGB8_TO_YCbYCr (*(ctx->row_pointers[y]+x*6), *(ctx->row_pointers[y]+x*6+1), *(ctx->row_pointers[y]+x*6+2), + *(ctx->row_pointers[y]+x*6+3), *(ctx->row_pointers[y]+x*6+4), *(ctx->row_pointers[y]+x*6+5)); + + // Free resources + free (ctx->img_data); + free (ctx->row_pointers); + + // Success + return PNGU_OK; +} + + +int PNGU_DecodeToRGB565 (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride) +{ + int result; + PNGU_u32 x, y, buffWidth; + + result = pngu_decode (ctx, width, height, 1); + if (result != PNGU_OK) + return result; + + buffWidth = width + stride; + + // Copy image to the output buffer + for (y = 0; y < height; y++) + for (x = 0; x < width; x++) + ((PNGU_u16 *)buffer)[y*buffWidth+x] = + (((PNGU_u16) (ctx->row_pointers[y][x*3] & 0xF8)) << 8) | + (((PNGU_u16) (ctx->row_pointers[y][x*3+1] & 0xFC)) << 3) | + (((PNGU_u16) (ctx->row_pointers[y][x*3+2] & 0xF8)) >> 3); + + // Free resources + free (ctx->img_data); + free (ctx->row_pointers); + + // Success + return PNGU_OK; +} + + +int PNGU_DecodeToRGBA8 (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride, PNGU_u8 default_alpha) +{ + int result; + PNGU_u32 x, y, buffWidth; + + result = pngu_decode (ctx, width, height, 0); + if (result != PNGU_OK) + return result; + + buffWidth = width + stride; + + // Check is source image has an alpha channel + if ( (ctx->prop.imgColorType == PNGU_COLOR_TYPE_GRAY_ALPHA) || (ctx->prop.imgColorType == PNGU_COLOR_TYPE_RGB_ALPHA) ) + { + // Alpha channel present, copy image to the output buffer + for (y = 0; y < height; y++) + memcpy (buffer + (y * buffWidth * 4), ctx->row_pointers[y], width * 4); + } + else + { + // No alpha channel present, copy image to the output buffer + for (y = 0; y < height; y++) + for (x = 0; x < width; x++) + ((PNGU_u32 *)buffer)[y*buffWidth+x] = + (((PNGU_u32) ctx->row_pointers[y][x*3]) << 24) | + (((PNGU_u32) ctx->row_pointers[y][x*3+1]) << 16) | + (((PNGU_u32) ctx->row_pointers[y][x*3+2]) << 8) | + ((PNGU_u32) default_alpha); + } + + // Free resources + free (ctx->img_data); + free (ctx->row_pointers); + + // Success + return PNGU_OK; +} + + +int PNGU_DecodeTo4x4RGB565 (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer) +{ + int result; + PNGU_u32 x, y, qwidth, qheight; + + // width and height need to be divisible by four + if ((width % 4) || (height % 4)) + return PNGU_INVALID_WIDTH_OR_HEIGHT; + + result = pngu_decode (ctx, width, height, 1); + if (result != PNGU_OK) + return result; + + // Copy image to the output buffer + qwidth = width / 4; + qheight = height / 4; + + for (y = 0; y < qheight; y++) + for (x = 0; x < qwidth; x++) + { + int blockbase = (y * qwidth + x) * 4; + + PNGU_u64 field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4]+x*12)); + PNGU_u64 field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase] = + (((field64 & 0xF800000000000000ULL) | ((field64 & 0xFC000000000000ULL) << 3) | ((field64 & 0xF80000000000ULL) << 5)) | + (((field64 & 0xF800000000ULL) << 8) | ((field64 & 0xFC000000ULL) << 11) | ((field64 & 0xF80000ULL) << 13)) | + (((field64 & 0xF800ULL) << 16) | ((field64 & 0xFCULL) << 19) | ((field32 & 0xF8000000ULL) >> 11)) | + (((field32 & 0xF80000ULL) >> 8) | ((field32 & 0xFC00ULL) >> 5) | ((field32 & 0xF8ULL) >> 3))); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+1]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+1]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+1] = + (((field64 & 0xF800000000000000ULL) | ((field64 & 0xFC000000000000ULL) << 3) | ((field64 & 0xF80000000000ULL) << 5)) | + (((field64 & 0xF800000000ULL) << 8) | ((field64 & 0xFC000000ULL) << 11) | ((field64 & 0xF80000ULL) << 13)) | + (((field64 & 0xF800ULL) << 16) | ((field64 & 0xFCULL) << 19) | ((field32 & 0xF8000000ULL) >> 11)) | + (((field32 & 0xF80000ULL) >> 8) | ((field32 & 0xFC00ULL) >> 5) | ((field32 & 0xF8ULL) >> 3))); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+2]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+2]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+2] = + (((field64 & 0xF800000000000000ULL) | ((field64 & 0xFC000000000000ULL) << 3) | ((field64 & 0xF80000000000ULL) << 5)) | + (((field64 & 0xF800000000ULL) << 8) | ((field64 & 0xFC000000ULL) << 11) | ((field64 & 0xF80000ULL) << 13)) | + (((field64 & 0xF800ULL) << 16) | ((field64 & 0xFCULL) << 19) | ((field32 & 0xF8000000ULL) >> 11)) | + (((field32 & 0xF80000ULL) >> 8) | ((field32 & 0xFC00ULL) >> 5) | ((field32 & 0xF8ULL) >> 3))); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+3]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+3]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+3] = + (((field64 & 0xF800000000000000ULL) | ((field64 & 0xFC000000000000ULL) << 3) | ((field64 & 0xF80000000000ULL) << 5)) | + (((field64 & 0xF800000000ULL) << 8) | ((field64 & 0xFC000000ULL) << 11) | ((field64 & 0xF80000ULL) << 13)) | + (((field64 & 0xF800ULL) << 16) | ((field64 & 0xFCULL) << 19) | ((field32 & 0xF8000000ULL) >> 11)) | + (((field32 & 0xF80000ULL) >> 8) | ((field32 & 0xFC00ULL) >> 5) | ((field32 & 0xF8ULL) >> 3))); + } + + // Free resources + free (ctx->img_data); + free (ctx->row_pointers); + + // Success + return PNGU_OK; +} + + +int PNGU_DecodeTo4x4RGB5A3 (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u8 default_alpha) +{ + int result; + PNGU_u32 x, y, qwidth, qheight; + PNGU_u64 alphaMask; + + // width and height need to be divisible by four + if ((width % 4) || (height % 4)) + return PNGU_INVALID_WIDTH_OR_HEIGHT; + + result = pngu_decode (ctx, width, height, 0); + if (result != PNGU_OK) + return result; + + // Init some vars + qwidth = width / 4; + qheight = height / 4; + + // Check is source image has an alpha channel + if ( (ctx->prop.imgColorType == PNGU_COLOR_TYPE_GRAY_ALPHA) || (ctx->prop.imgColorType == PNGU_COLOR_TYPE_RGB_ALPHA) ) + { + // Alpha channel present, copy image to the output buffer + for (y = 0; y < qheight; y++) + for (x = 0; x < qwidth; x++) + { + int blockbase = (y * qwidth + x) * 4; + PNGU_u64 tmp; + + PNGU_u64 fieldA = *((PNGU_u64 *)(ctx->row_pointers[y*4]+x*16)); + PNGU_u64 fieldB = *((PNGU_u64 *)(ctx->row_pointers[y*4]+x*16+8)); + // If first pixel is opaque set MSB to 1 and encode colors in RGB555, else set MSB to 0 and encode colors in ARGB3444 + if ((fieldA & 0xE000000000ULL) == 0xE000000000ULL) + tmp = 0x8000000000000000ULL | ((fieldA & 0xF800000000000000ULL) >> 1) | ((fieldA & 0xF8000000000000ULL) << 2) | ((fieldA & 0xF80000000000ULL) << 5); + else + tmp = ((fieldA & 0xE000000000ULL) << 23) | ((fieldA & 0xF000000000000000ULL) >> 4) | (fieldA & 0xF0000000000000ULL) | ((fieldA & 0xF00000000000ULL) << 4); + + // If second pixel is opaque set MSB to 1 and encode colors in RGB555, else set MSB to 0 and encode colors in ARGB3444 + if ((fieldA & 0xE0ULL) == 0xE0ULL) + tmp = tmp | 0x800000000000ULL | ((fieldA & 0xF8000000ULL) << 15) | ((fieldA & 0xF80000ULL) << 18) | ((fieldA & 0xF800ULL) << 21); + else + tmp = tmp | ((fieldA & 0xE0ULL) << 39) | ((fieldA & 0xF0000000ULL) << 12) | ((fieldA & 0xF00000ULL) << 16) | ((fieldA & 0xF000ULL) << 20); + + // If third pixel is opaque set MSB to 1 and encode colors in RGB555, else set MSB to 0 and encode colors in ARGB3444 + if ((fieldB & 0xE000000000ULL) == 0xE000000000ULL) + tmp = tmp | 0x80000000ULL | ((fieldB & 0xF800000000000000ULL) >> 33) | ((fieldB & 0xF8000000000000ULL) >> 30) | ((fieldB & 0xF80000000000ULL) >> 27); + else + tmp = tmp | ((fieldB & 0xE000000000ULL) >> 9) | ((fieldB & 0xF000000000000000ULL) >> 36) | ((fieldB & 0xF0000000000000ULL) >> 32) | ((fieldB & 0xF00000000000ULL) >> 28); + + // If fourth pixel is opaque set MSB to 1 and encode colors in RGB555, else set MSB to 0 and encode colors in ARGB3444 + if ((fieldB & 0xE0ULL) == 0xE0ULL) + tmp = tmp | 0x8000ULL | ((fieldB & 0xF8000000ULL) >> 17) | ((fieldB & 0xF80000ULL) >> 14) | ((fieldB & 0xF800ULL) >> 11); + else + tmp = tmp | ((fieldB & 0xE0ULL) << 7) | ((fieldB & 0xF0000000ULL) >> 20) | ((fieldB & 0xF00000ULL) >> 16) | ((fieldB & 0xF000ULL) >> 12); + ((PNGU_u64 *) buffer)[blockbase] = tmp; + + fieldA = *((PNGU_u64 *)(ctx->row_pointers[y*4+1]+x*16)); + fieldB = *((PNGU_u64 *)(ctx->row_pointers[y*4+1]+x*16+8)); + if ((fieldA & 0xE000000000ULL) == 0xE000000000ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = 0x8000000000000000ULL | ((fieldA & 0xF800000000000000ULL) >> 1) | ((fieldA & 0xF8000000000000ULL) << 2) | ((fieldA & 0xF80000000000ULL) << 5); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = ((fieldA & 0xE000000000ULL) << 23) | ((fieldA & 0xF000000000000000ULL) >> 4) | (fieldA & 0xF0000000000000ULL) | ((fieldA & 0xF00000000000ULL) << 4); + + if ((fieldA & 0xE0ULL) == 0xE0ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = tmp | 0x800000000000ULL | ((fieldA & 0xF8000000ULL) << 15) | ((fieldA & 0xF80000ULL) << 18) | ((fieldA & 0xF800ULL) << 21); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = tmp | ((fieldA & 0xE0ULL) << 39) | ((fieldA & 0xF0000000ULL) << 12) | ((fieldA & 0xF00000ULL) << 16) | ((fieldA & 0xF000ULL) << 20); + + if ((fieldB & 0xE000000000ULL) == 0xE000000000ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = tmp | 0x80000000ULL | ((fieldB & 0xF800000000000000ULL) >> 33) | ((fieldB & 0xF8000000000000ULL) >> 30) | ((fieldB & 0xF80000000000ULL) >> 27); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = tmp | ((fieldB & 0xE000000000ULL) >> 9) | ((fieldB & 0xF000000000000000ULL) >> 36) | ((fieldB & 0xF0000000000000ULL) >> 32) | ((fieldB & 0xF00000000000ULL) >> 28); + + if ((fieldB & 0xE0ULL) == 0xE0ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = tmp | 0x8000ULL | ((fieldB & 0xF8000000ULL) >> 17) | ((fieldB & 0xF80000ULL) >> 14) | ((fieldB & 0xF800ULL) >> 11); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = tmp | ((fieldB & 0xE0ULL) << 7) | ((fieldB & 0xF0000000ULL) >> 20) | ((fieldB & 0xF00000ULL) >> 16) | ((fieldB & 0xF000ULL) >> 12); + ((PNGU_u64 *) buffer)[blockbase+1] = tmp; + + fieldA = *((PNGU_u64 *)(ctx->row_pointers[y*4+2]+x*16)); + fieldB = *((PNGU_u64 *)(ctx->row_pointers[y*4+2]+x*16+8)); + if ((fieldA & 0xE000000000ULL) == 0xE000000000ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = 0x8000000000000000ULL | ((fieldA & 0xF800000000000000ULL) >> 1) | ((fieldA & 0xF8000000000000ULL) << 2) | ((fieldA & 0xF80000000000ULL) << 5); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = ((fieldA & 0xE000000000ULL) << 23) | ((fieldA & 0xF000000000000000ULL) >> 4) | (fieldA & 0xF0000000000000ULL) | ((fieldA & 0xF00000000000ULL) << 4); + + if ((fieldA & 0xE0ULL) == 0xE0ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = tmp | 0x800000000000ULL | ((fieldA & 0xF8000000ULL) << 15) | ((fieldA & 0xF80000ULL) << 18) | ((fieldA & 0xF800ULL) << 21); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = tmp | ((fieldA & 0xE0ULL) << 39) | ((fieldA & 0xF0000000ULL) << 12) | ((fieldA & 0xF00000ULL) << 16) | ((fieldA & 0xF000ULL) << 20); + + if ((fieldB & 0xE000000000ULL) == 0xE000000000ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = tmp | 0x80000000ULL | ((fieldB & 0xF800000000000000ULL) >> 33) | ((fieldB & 0xF8000000000000ULL) >> 30) | ((fieldB & 0xF80000000000ULL) >> 27); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = tmp | ((fieldB & 0xE000000000ULL) >> 9) | ((fieldB & 0xF000000000000000ULL) >> 36) | ((fieldB & 0xF0000000000000ULL) >> 32) | ((fieldB & 0xF00000000000ULL) >> 28); + + if ((fieldB & 0xE0ULL) == 0xE0ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = tmp | 0x8000ULL | ((fieldB & 0xF8000000ULL) >> 17) | ((fieldB & 0xF80000ULL) >> 14) | ((fieldB & 0xF800ULL) >> 11); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = tmp | ((fieldB & 0xE0ULL) << 7) | ((fieldB & 0xF0000000ULL) >> 20) | ((fieldB & 0xF00000ULL) >> 16) | ((fieldB & 0xF000ULL) >> 12); + ((PNGU_u64 *) buffer)[blockbase+2] = tmp; + + fieldA = *((PNGU_u64 *)(ctx->row_pointers[y*4+3]+x*16)); + fieldB = *((PNGU_u64 *)(ctx->row_pointers[y*4+3]+x*16+8)); + if ((fieldA & 0xE000000000ULL) == 0xE000000000ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = 0x8000000000000000ULL | ((fieldA & 0xF800000000000000ULL) >> 1) | ((fieldA & 0xF8000000000000ULL) << 2) | ((fieldA & 0xF80000000000ULL) << 5); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = ((fieldA & 0xE000000000ULL) << 23) | ((fieldA & 0xF000000000000000ULL) >> 4) | (fieldA & 0xF0000000000000ULL) | ((fieldA & 0xF00000000000ULL) << 4); + + if ((fieldA & 0xE0ULL) == 0xE0ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = tmp | 0x800000000000ULL | ((fieldA & 0xF8000000ULL) << 15) | ((fieldA & 0xF80000ULL) << 18) | ((fieldA & 0xF800ULL) << 21); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = tmp | ((fieldA & 0xE0ULL) << 39) | ((fieldA & 0xF0000000ULL) << 12) | ((fieldA & 0xF00000ULL) << 16) | ((fieldA & 0xF000ULL) << 20); + + if ((fieldB & 0xE000000000ULL) == 0xE000000000ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = tmp | 0x80000000ULL | ((fieldB & 0xF800000000000000ULL) >> 33) | ((fieldB & 0xF8000000000000ULL) >> 30) | ((fieldB & 0xF80000000000ULL) >> 27); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = tmp | ((fieldB & 0xE000000000ULL) >> 9) | ((fieldB & 0xF000000000000000ULL) >> 36) | ((fieldB & 0xF0000000000000ULL) >> 32) | ((fieldB & 0xF00000000000ULL) >> 28); + + if ((fieldB & 0xE0ULL) == 0xE0ULL) + // Opaque pixel, so set MSB to 1 and encode colors in RGB555 + tmp = tmp | 0x8000ULL | ((fieldB & 0xF8000000ULL) >> 17) | ((fieldB & 0xF80000ULL) >> 14) | ((fieldB & 0xF800ULL) >> 11); + else + // Tranlucid pixel, so set MSB to 0 and encode colors in ARGB3444 + tmp = tmp | ((fieldB & 0xE0ULL) << 7) | ((fieldB & 0xF0000000ULL) >> 20) | ((fieldB & 0xF00000ULL) >> 16) | ((fieldB & 0xF000ULL) >> 12); + ((PNGU_u64 *) buffer)[blockbase+3] = tmp; + } + } + else + { + // No alpha channel present, copy image to the output buffer + default_alpha = (default_alpha >> 5); + if (default_alpha == 7) + { + // The user wants an opaque texture, so set MSB to 1 and encode colors in RGB555 + alphaMask = 0x8000800080008000ULL; + + for (y = 0; y < qheight; y++) + for (x = 0; x < qwidth; x++) + { + int blockbase = (y * qwidth + x) * 4; + + PNGU_u64 field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4]+x*12)); + PNGU_u64 field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase] = + alphaMask | ((field64 & 0xF800000000000000ULL) >> 1) | ((field64 & 0xF8000000000000ULL) << 2) | + ((field64 & 0xF80000000000ULL) << 5) | ((field64 & 0xF800000000ULL) << 7) | ((field64 & 0xF8000000ULL) << 10) | + ((field64 & 0xF80000ULL) << 13) | ((field64 & 0xF800ULL) << 15) | ((field64 & 0xF8ULL) << 18) | + ((field32 & 0xF8000000ULL) >> 11) | ((field32 & 0xF80000ULL) >> 9) | ((field32 & 0xF800ULL) >> 6) | ((field32 & 0xF8ULL) >> 3); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+1]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+1]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+1] = + alphaMask | ((field64 & 0xF800000000000000ULL) >> 1) | ((field64 & 0xF8000000000000ULL) << 2) | + ((field64 & 0xF80000000000ULL) << 5) | ((field64 & 0xF800000000ULL) << 7) | ((field64 & 0xF8000000ULL) << 10) | + ((field64 & 0xF80000ULL) << 13) | ((field64 & 0xF800ULL) << 15) | ((field64 & 0xF8ULL) << 18) | + ((field32 & 0xF8000000ULL) >> 11) | ((field32 & 0xF80000ULL) >> 9) | ((field32 & 0xF800ULL) >> 6) | ((field32 & 0xF8ULL) >> 3); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+2]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+2]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+2] = + alphaMask | ((field64 & 0xF800000000000000ULL) >> 1) | ((field64 & 0xF8000000000000ULL) << 2) | + ((field64 & 0xF80000000000ULL) << 5) | ((field64 & 0xF800000000ULL) << 7) | ((field64 & 0xF8000000ULL) << 10) | + ((field64 & 0xF80000ULL) << 13) | ((field64 & 0xF800ULL) << 15) | ((field64 & 0xF8ULL) << 18) | + ((field32 & 0xF8000000ULL) >> 11) | ((field32 & 0xF80000ULL) >> 9) | ((field32 & 0xF800ULL) >> 6) | ((field32 & 0xF8ULL) >> 3); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+3]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+3]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+3] = + alphaMask | ((field64 & 0xF800000000000000ULL) >> 1) | ((field64 & 0xF8000000000000ULL) << 2) | + ((field64 & 0xF80000000000ULL) << 5) | ((field64 & 0xF800000000ULL) << 7) | ((field64 & 0xF8000000ULL) << 10) | + ((field64 & 0xF80000ULL) << 13) | ((field64 & 0xF800ULL) << 15) | ((field64 & 0xF8ULL) << 18) | + ((field32 & 0xF8000000ULL) >> 11) | ((field32 & 0xF80000ULL) >> 9) | ((field32 & 0xF800ULL) >> 6) | ((field32 & 0xF8ULL) >> 3); + } + } + else + { + // The user wants a translucid texture, so set MSB to 0 and encode colors in ARGB3444 + default_alpha = (default_alpha << 4); + alphaMask = (((PNGU_u64) default_alpha) << 56) | (((PNGU_u64) default_alpha) << 40) | + (((PNGU_u64) default_alpha) << 24) | (((PNGU_u64) default_alpha) << 8); + + for (y = 0; y < qheight; y++) + for (x = 0; x < qwidth; x++) + { + int blockbase = (y * qwidth + x) * 4; + + PNGU_u64 field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4]+x*12)); + PNGU_u64 field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase] = + alphaMask | ((field64 & 0xF000000000000000ULL) >> 4) | (field64 & 0xF0000000000000ULL) | ((field64 & 0xF00000000000ULL) << 4) | + ((field64 & 0xF000000000ULL) << 4) | ((field64 & 0xF0000000ULL) << 8) | ((field64 & 0xF00000ULL) << 12) | + ((field64 & 0xF000ULL) << 12) | ((field64 & 0xF0ULL) << 16) | ((field32 & 0xF0000000ULL) >> 12) | + ((field32 & 0xF00000ULL) >> 12) | ((field32 & 0xF000ULL) >> 8) | ((field32 & 0xF0ULL) >> 4); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+1]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+1]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+1] = + alphaMask | ((field64 & 0xF000000000000000ULL) >> 4) | (field64 & 0xF0000000000000ULL) | ((field64 & 0xF00000000000ULL) << 4) | + ((field64 & 0xF000000000ULL) << 4) | ((field64 & 0xF0000000ULL) << 8) | ((field64 & 0xF00000ULL) << 12) | + ((field64 & 0xF000ULL) << 12) | ((field64 & 0xF0ULL) << 16) | ((field32 & 0xF0000000ULL) >> 12) | + ((field32 & 0xF00000ULL) >> 12) | ((field32 & 0xF000ULL) >> 8) | ((field32 & 0xF0ULL) >> 4); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+2]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+2]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+2] = + alphaMask | ((field64 & 0xF000000000000000ULL) >> 4) | (field64 & 0xF0000000000000ULL) | ((field64 & 0xF00000000000ULL) << 4) | + ((field64 & 0xF000000000ULL) << 4) | ((field64 & 0xF0000000ULL) << 8) | ((field64 & 0xF00000ULL) << 12) | + ((field64 & 0xF000ULL) << 12) | ((field64 & 0xF0ULL) << 16) | ((field32 & 0xF0000000ULL) >> 12) | + ((field32 & 0xF00000ULL) >> 12) | ((field32 & 0xF000ULL) >> 8) | ((field32 & 0xF0ULL) >> 4); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+3]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+3]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+3] = + alphaMask | ((field64 & 0xF000000000000000ULL) >> 4) | (field64 & 0xF0000000000000ULL) | ((field64 & 0xF00000000000ULL) << 4) | + ((field64 & 0xF000000000ULL) << 4) | ((field64 & 0xF0000000ULL) << 8) | ((field64 & 0xF00000ULL) << 12) | + ((field64 & 0xF000ULL) << 12) | ((field64 & 0xF0ULL) << 16) | ((field32 & 0xF0000000ULL) >> 12) | + ((field32 & 0xF00000ULL) >> 12) | ((field32 & 0xF000ULL) >> 8) | ((field32 & 0xF0ULL) >> 4); + } + } + } + + // Free resources + free (ctx->img_data); + free (ctx->row_pointers); + + // Success + return PNGU_OK; +} + + +int PNGU_DecodeTo4x4RGBA8 (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u8 default_alpha) +{ + int result; + PNGU_u32 x, y, qwidth, qheight; + PNGU_u64 alphaMask; + + // width and height need to be divisible by four + if ((width % 4) || (height % 4)) + return PNGU_INVALID_WIDTH_OR_HEIGHT; + + result = pngu_decode (ctx, width, height, 0); + if (result != PNGU_OK) + return result; + + // Init some variables + qwidth = width / 4; + qheight = height / 4; + + // Check is source image has an alpha channel + if ( (ctx->prop.imgColorType == PNGU_COLOR_TYPE_GRAY_ALPHA) || (ctx->prop.imgColorType == PNGU_COLOR_TYPE_RGB_ALPHA) ) + { + // Alpha channel present, copy image to the output buffer + for (y = 0; y < qheight; y++) + for (x = 0; x < qwidth; x++) + { + int blockbase = (y * qwidth + x) * 8; + + PNGU_u64 fieldA = *((PNGU_u64 *)(ctx->row_pointers[y*4]+x*16)); + PNGU_u64 fieldB = *((PNGU_u64 *)(ctx->row_pointers[y*4]+x*16+8)); + ((PNGU_u64 *) buffer)[blockbase] = + ((fieldA & 0xFF00000000ULL) << 24) | ((fieldA & 0xFF00000000000000ULL) >> 8) | + ((fieldA & 0xFFULL) << 40) | ((fieldA & 0xFF000000ULL) << 8) | + ((fieldB & 0xFF00000000ULL) >> 8) | ((fieldB & 0xFF00000000000000ULL) >> 40) | + ((fieldB & 0xFFULL) << 8) | ((fieldB & 0xFF000000ULL) >> 24); + ((PNGU_u64 *) buffer)[blockbase+4] = + ((fieldA & 0xFFFF0000000000ULL) << 8) | ((fieldA & 0xFFFF00ULL) << 24) | + ((fieldB & 0xFFFF0000000000ULL) >> 24) | ((fieldB & 0xFFFF00ULL) >> 8); + + fieldA = *((PNGU_u64 *)(ctx->row_pointers[y*4+1]+x*16)); + fieldB = *((PNGU_u64 *)(ctx->row_pointers[y*4+1]+x*16+8)); + ((PNGU_u64 *) buffer)[blockbase+1] = + ((fieldA & 0xFF00000000ULL) << 24) | ((fieldA & 0xFF00000000000000ULL) >> 8) | + ((fieldA & 0xFFULL) << 40) | ((fieldA & 0xFF000000ULL) << 8) | + ((fieldB & 0xFF00000000ULL) >> 8) | ((fieldB & 0xFF00000000000000ULL) >> 40) | + ((fieldB & 0xFFULL) << 8) | ((fieldB & 0xFF000000ULL) >> 24); + ((PNGU_u64 *) buffer)[blockbase+5] = + ((fieldA & 0xFFFF0000000000ULL) << 8) | ((fieldA & 0xFFFF00ULL) << 24) | + ((fieldB & 0xFFFF0000000000ULL) >> 24) | ((fieldB & 0xFFFF00ULL) >> 8); + + fieldA = *((PNGU_u64 *)(ctx->row_pointers[y*4+2]+x*16)); + fieldB = *((PNGU_u64 *)(ctx->row_pointers[y*4+2]+x*16+8)); + ((PNGU_u64 *) buffer)[blockbase+2] = + ((fieldA & 0xFF00000000ULL) << 24) | ((fieldA & 0xFF00000000000000ULL) >> 8) | + ((fieldA & 0xFFULL) << 40) | ((fieldA & 0xFF000000ULL) << 8) | + ((fieldB & 0xFF00000000ULL) >> 8) | ((fieldB & 0xFF00000000000000ULL) >> 40) | + ((fieldB & 0xFFULL) << 8) | ((fieldB & 0xFF000000ULL) >> 24); + ((PNGU_u64 *) buffer)[blockbase+6] = + ((fieldA & 0xFFFF0000000000ULL) << 8) | ((fieldA & 0xFFFF00ULL) << 24) | + ((fieldB & 0xFFFF0000000000ULL) >> 24) | ((fieldB & 0xFFFF00ULL) >> 8); + + fieldA = *((PNGU_u64 *)(ctx->row_pointers[y*4+3]+x*16)); + fieldB = *((PNGU_u64 *)(ctx->row_pointers[y*4+3]+x*16+8)); + ((PNGU_u64 *) buffer)[blockbase+3] = + ((fieldA & 0xFF00000000ULL) << 24) | ((fieldA & 0xFF00000000000000ULL) >> 8) | + ((fieldA & 0xFFULL) << 40) | ((fieldA & 0xFF000000ULL) << 8) | + ((fieldB & 0xFF00000000ULL) >> 8) | ((fieldB & 0xFF00000000000000ULL) >> 40) | + ((fieldB & 0xFFULL) << 8) | ((fieldB & 0xFF000000ULL) >> 24); + ((PNGU_u64 *) buffer)[blockbase+7] = + ((fieldA & 0xFFFF0000000000ULL) << 8) | ((fieldA & 0xFFFF00ULL) << 24) | + ((fieldB & 0xFFFF0000000000ULL) >> 24) | ((fieldB & 0xFFFF00ULL) >> 8); + } + } + else + { + // No alpha channel present, copy image to the output buffer + alphaMask = (((PNGU_u64)default_alpha) << 56) | (((PNGU_u64)default_alpha) << 40) | + (((PNGU_u64)default_alpha) << 24) | (((PNGU_u64)default_alpha) << 8); + + for (y = 0; y < qheight; y++) + for (x = 0; x < qwidth; x++) + { + int blockbase = (y * qwidth + x) * 8; + + PNGU_u64 field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4]+x*12)); + PNGU_u64 field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase] = + (((field64 & 0xFF00000000000000ULL) >> 8) | (field64 & 0xFF00000000ULL) | + ((field64 & 0xFF00ULL) << 8) | ((field32 & 0xFF0000ULL) >> 16) | alphaMask); + ((PNGU_u64 *) buffer)[blockbase+4] = + (((field64 & 0xFFFF0000000000ULL) << 8) | ((field64 & 0xFFFF0000ULL) << 16) | + ((field64 & 0xFFULL) << 24) | ((field32 & 0xFF000000ULL) >> 8) | (field32 & 0xFFFFULL)); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+1]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+1]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+1] = + (((field64 & 0xFF00000000000000ULL) >> 8) | (field64 & 0xFF00000000ULL) | + ((field64 & 0xFF00ULL) << 8) | ((field32 & 0xFF0000ULL) >> 16) | alphaMask); + ((PNGU_u64 *) buffer)[blockbase+5] = + (((field64 & 0xFFFF0000000000ULL) << 8) | ((field64 & 0xFFFF0000ULL) << 16) | + ((field64 & 0xFFULL) << 24) | ((field32 & 0xFF000000ULL) >> 8) | (field32 & 0xFFFFULL)); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+2]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+2]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+2] = + (((field64 & 0xFF00000000000000ULL) >> 8) | (field64 & 0xFF00000000ULL) | + ((field64 & 0xFF00ULL) << 8) | ((field32 & 0xFF0000ULL) >> 16) | alphaMask); + ((PNGU_u64 *) buffer)[blockbase+6] = + (((field64 & 0xFFFF0000000000ULL) << 8) | ((field64 & 0xFFFF0000ULL) << 16) | + ((field64 & 0xFFULL) << 24) | ((field32 & 0xFF000000ULL) >> 8) | (field32 & 0xFFFFULL)); + + field64 = *((PNGU_u64 *)(ctx->row_pointers[y*4+3]+x*12)); + field32 = (PNGU_u64) *((PNGU_u32 *)(ctx->row_pointers[y*4+3]+x*12+8)); + ((PNGU_u64 *) buffer)[blockbase+3] = + (((field64 & 0xFF00000000000000ULL) >> 8) | (field64 & 0xFF00000000ULL) | + ((field64 & 0xFF00ULL) << 8) | ((field32 & 0xFF0000ULL) >> 16) | alphaMask); + ((PNGU_u64 *) buffer)[blockbase+7] = + (((field64 & 0xFFFF0000000000ULL) << 8) | ((field64 & 0xFFFF0000ULL) << 16) | + ((field64 & 0xFFULL) << 24) | ((field32 & 0xFF000000ULL) >> 8) | (field32 & 0xFFFFULL)); + } + } + + // Free resources + free (ctx->img_data); + free (ctx->row_pointers); + + // Success + return PNGU_OK; +} + + +int PNGU_EncodeFromYCbYCr (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride) +{ + png_uint_32 rowbytes; + PNGU_u32 x, y, buffWidth; + + // Erase from the context any readed info + pngu_free_info (ctx); + ctx->propRead = 0; + + // Check if the user has selected a file to write the image + if (ctx->source == PNGU_SOURCE_BUFFER); + + else if (ctx->source == PNGU_SOURCE_DEVICE) + { + // Open file + if (!(ctx->fd = fopen (ctx->filename, "wb"))) + return PNGU_CANT_OPEN_FILE; + } + + else + return PNGU_NO_FILE_SELECTED; + + // Allocation of libpng structs + ctx->png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!(ctx->png_ptr)) + { + if (ctx->source == PNGU_SOURCE_DEVICE) + fclose (ctx->fd); + return PNGU_LIB_ERROR; + } + + ctx->info_ptr = png_create_info_struct (ctx->png_ptr); + if (!(ctx->info_ptr)) + { + png_destroy_write_struct (&(ctx->png_ptr), (png_infopp)NULL); + if (ctx->source == PNGU_SOURCE_DEVICE) + fclose (ctx->fd); + return PNGU_LIB_ERROR; + } + + if (ctx->source == PNGU_SOURCE_BUFFER) + { + // Installation of our custom data writer function + ctx->cursor = 0; + png_set_write_fn (ctx->png_ptr, ctx, pngu_write_data_to_buffer, pngu_flush_data_to_buffer); + } + else if (ctx->source == PNGU_SOURCE_DEVICE) + { + // Default data writer uses function fwrite, so it needs to use our FILE* + png_init_io (ctx->png_ptr, ctx->fd); + } + + // Setup output file properties + png_set_IHDR (ctx->png_ptr, ctx->info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + // Allocate memory to store the image in RGB format + rowbytes = width * 3; + if (rowbytes % 4) + rowbytes = ((rowbytes / 4) + 1) * 4; // Add extra padding so each row starts in a 4 byte boundary + + ctx->img_data = malloc (rowbytes * height); + if (!ctx->img_data) + { + png_destroy_write_struct (&(ctx->png_ptr), (png_infopp)NULL); + if (ctx->source == PNGU_SOURCE_DEVICE) + fclose (ctx->fd); + return PNGU_LIB_ERROR; + } + + ctx->row_pointers = malloc (sizeof (png_bytep) * height); + if (!ctx->row_pointers) + { + png_destroy_write_struct (&(ctx->png_ptr), (png_infopp)NULL); + if (ctx->source == PNGU_SOURCE_DEVICE) + fclose (ctx->fd); + return PNGU_LIB_ERROR; + } + + // Encode YCbYCr image into RGB8 format + buffWidth = (width + stride) / 2; + for (y = 0; y < height; y++) + { + ctx->row_pointers[y] = ctx->img_data + (y * rowbytes); + + for (x = 0; x < (width / 2); x++) + PNGU_YCbYCr_TO_RGB8 ( ((PNGU_u32 *)buffer)[y*buffWidth+x], + ((PNGU_u8 *) ctx->row_pointers[y]+x*6), ((PNGU_u8 *) ctx->row_pointers[y]+x*6+1), + ((PNGU_u8 *) ctx->row_pointers[y]+x*6+2), ((PNGU_u8 *) ctx->row_pointers[y]+x*6+3), + ((PNGU_u8 *) ctx->row_pointers[y]+x*6+4), ((PNGU_u8 *) ctx->row_pointers[y]+x*6+5) ); + } + + // Tell libpng where is our image data + png_set_rows (ctx->png_ptr, ctx->info_ptr, ctx->row_pointers); + + // Write file header and image data + png_write_png (ctx->png_ptr, ctx->info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + // Tell libpng we have no more data to write + png_write_end (ctx->png_ptr, (png_infop) NULL); + + // Free resources + free (ctx->img_data); + free (ctx->row_pointers); + png_destroy_write_struct (&(ctx->png_ptr), &(ctx->info_ptr)); + if (ctx->source == PNGU_SOURCE_DEVICE) + fclose (ctx->fd); + + // Success + return PNGU_OK; +} + + +// This function is taken from a libogc example +PNGU_u32 PNGU_RGB8_TO_YCbYCr (PNGU_u8 r1, PNGU_u8 g1, PNGU_u8 b1, PNGU_u8 r2, PNGU_u8 g2, PNGU_u8 b2) +{ + int y1, cb1, cr1, y2, cb2, cr2, cb, cr; + + y1 = (299 * r1 + 587 * g1 + 114 * b1) / 1000; + cb1 = (-16874 * r1 - 33126 * g1 + 50000 * b1 + 12800000) / 100000; + cr1 = (50000 * r1 - 41869 * g1 - 8131 * b1 + 12800000) / 100000; + + y2 = (299 * r2 + 587 * g2 + 114 * b2) / 1000; + cb2 = (-16874 * r2 - 33126 * g2 + 50000 * b2 + 12800000) / 100000; + cr2 = (50000 * r2 - 41869 * g2 - 8131 * b2 + 12800000) / 100000; + + cb = (cb1 + cb2) >> 1; + cr = (cr1 + cr2) >> 1; + + return (PNGU_u32) ((y1 << 24) | (cb << 16) | (y2 << 8) | cr); +} + + +void PNGU_YCbYCr_TO_RGB8 (PNGU_u32 ycbycr, PNGU_u8 *r1, PNGU_u8 *g1, PNGU_u8 *b1, PNGU_u8 *r2, PNGU_u8 *g2, PNGU_u8 *b2) +{ + PNGU_u8 *val = (PNGU_u8 *) &ycbycr; + int r, g, b; + + r = 1.371f * (val[3] - 128); + g = - 0.698f * (val[3] - 128) - 0.336f * (val[1] - 128); + b = 1.732f * (val[1] - 128); + + *r1 = pngu_clamp (val[0] + r, 0, 255); + *g1 = pngu_clamp (val[0] + g, 0, 255); + *b1 = pngu_clamp (val[0] + b, 0, 255); + + *r2 = pngu_clamp (val[2] + r, 0, 255); + *g2 = pngu_clamp (val[2] + g, 0, 255); + *b2 = pngu_clamp (val[2] + b, 0, 255); +} + + +int pngu_info (IMGCTX ctx) +{ + png_byte magic[8]; + png_uint_32 width; + png_uint_32 height; + png_color_16p background; + png_bytep trans; + png_color_16p trans_values; + int scale, i; + + // Check if there is a file selected and if it is a valid .png + if (ctx->source == PNGU_SOURCE_BUFFER) + memcpy (magic, ctx->buffer, 8); + + else if (ctx->source == PNGU_SOURCE_DEVICE) + { + // Open file + if (!(ctx->fd = fopen (ctx->filename, "rb"))) + return PNGU_CANT_OPEN_FILE; + + // Load first 8 bytes into magic buffer + if (fread (magic, 1, 8, ctx->fd) != 8) + { + fclose (ctx->fd); + return PNGU_CANT_READ_FILE; + } + } + + else + return PNGU_NO_FILE_SELECTED;; + + if (png_sig_cmp(magic, 0, 8) != 0) + { + if (ctx->source == PNGU_SOURCE_DEVICE) + fclose (ctx->fd); + return PNGU_FILE_IS_NOT_PNG; + } + + // Allocation of libpng structs + ctx->png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!(ctx->png_ptr)) + { + if (ctx->source == PNGU_SOURCE_DEVICE) + fclose (ctx->fd); + return PNGU_LIB_ERROR; + } + + ctx->info_ptr = png_create_info_struct (ctx->png_ptr); + if (!(ctx->info_ptr)) + { + if (ctx->source == PNGU_SOURCE_DEVICE) + fclose (ctx->fd); + png_destroy_read_struct (&(ctx->png_ptr), (png_infopp)NULL, (png_infopp)NULL); + return PNGU_LIB_ERROR; + } + + if (ctx->source == PNGU_SOURCE_BUFFER) + { + // Installation of our custom data provider function + ctx->cursor = 0; + png_set_read_fn (ctx->png_ptr, ctx, pngu_read_data_from_buffer); + } + else if (ctx->source == PNGU_SOURCE_DEVICE) + { + // Default data provider uses function fread, so it needs to use our FILE* + png_init_io (ctx->png_ptr, ctx->fd); + png_set_sig_bytes (ctx->png_ptr, 8); // We have read 8 bytes already to check PNG authenticity + } + + // Read png header + png_read_info (ctx->png_ptr, ctx->info_ptr); + + // Query image properties if they have not been queried before + if (!ctx->propRead) + { + png_get_IHDR(ctx->png_ptr, ctx->info_ptr, &width, &height, + (int *) &(ctx->prop.imgBitDepth), + (int *) &(ctx->prop.imgColorType), + NULL, NULL, NULL); + + ctx->prop.imgWidth = width; + ctx->prop.imgHeight = height; + switch (ctx->prop.imgColorType) + { + case PNG_COLOR_TYPE_GRAY: + ctx->prop.imgColorType = PNGU_COLOR_TYPE_GRAY; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + ctx->prop.imgColorType = PNGU_COLOR_TYPE_GRAY_ALPHA; + break; + case PNG_COLOR_TYPE_PALETTE: + ctx->prop.imgColorType = PNGU_COLOR_TYPE_PALETTE; + break; + case PNG_COLOR_TYPE_RGB: + ctx->prop.imgColorType = PNGU_COLOR_TYPE_RGB; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + ctx->prop.imgColorType = PNGU_COLOR_TYPE_RGB_ALPHA; + break; + default: + ctx->prop.imgColorType = PNGU_COLOR_TYPE_UNKNOWN; + break; + } + + // Constant used to scale 16 bit values to 8 bit values + scale = 1; + if (ctx->prop.imgBitDepth == 16) + scale = 256; + + // Query background color, if any. + ctx->prop.validBckgrnd = 0; + if (((ctx->prop.imgColorType == PNGU_COLOR_TYPE_RGB) || (ctx->prop.imgColorType == PNGU_COLOR_TYPE_RGB_ALPHA)) && + (png_get_bKGD (ctx->png_ptr, ctx->info_ptr, &background))) + { + ctx->prop.validBckgrnd = 1; + ctx->prop.bckgrnd.r = background->red / scale; + ctx->prop.bckgrnd.g = background->green / scale; + ctx->prop.bckgrnd.b = background->blue / scale; + } + else if (((ctx->prop.imgColorType == PNGU_COLOR_TYPE_GRAY) || (ctx->prop.imgColorType == PNGU_COLOR_TYPE_GRAY_ALPHA)) && + (png_get_bKGD (ctx->png_ptr, ctx->info_ptr, &background))) + { + ctx->prop.validBckgrnd = 1; + ctx->prop.bckgrnd.r = ctx->prop.bckgrnd.g = ctx->prop.bckgrnd.b = background->gray / scale; + } + + // Query list of transparent colors, if any. + ctx->prop.numTrans = 0; + ctx->prop.trans = NULL; + if (((ctx->prop.imgColorType == PNGU_COLOR_TYPE_RGB) || (ctx->prop.imgColorType == PNGU_COLOR_TYPE_RGB_ALPHA)) && + (png_get_tRNS (ctx->png_ptr, ctx->info_ptr, &trans, (int *) &(ctx->prop.numTrans), &trans_values))) + { + if (ctx->prop.numTrans) + { + ctx->prop.trans = malloc (sizeof (PNGUCOLOR) * ctx->prop.numTrans); + if (ctx->prop.trans) + for (i = 0; i < ctx->prop.numTrans; i++) + { + ctx->prop.trans[i].r = trans_values[i].red / scale; + ctx->prop.trans[i].g = trans_values[i].green / scale; + ctx->prop.trans[i].b = trans_values[i].blue / scale; + } + else + ctx->prop.numTrans = 0; + } + } + else if (((ctx->prop.imgColorType == PNGU_COLOR_TYPE_GRAY) || (ctx->prop.imgColorType == PNGU_COLOR_TYPE_GRAY_ALPHA)) && + (png_get_tRNS (ctx->png_ptr, ctx->info_ptr, &trans, (int *) &(ctx->prop.numTrans), &trans_values))) + { + if (ctx->prop.numTrans) + { + ctx->prop.trans = malloc (sizeof (PNGUCOLOR) * ctx->prop.numTrans); + if (ctx->prop.trans) + for (i = 0; i < ctx->prop.numTrans; i++) + ctx->prop.trans[i].r = ctx->prop.trans[i].g = ctx->prop.trans[i].b = + trans_values[i].gray / scale; + else + ctx->prop.numTrans = 0; + } + } + + ctx->propRead = 1; + } + + // Success + ctx->infoRead = 1; + + return PNGU_OK; +} + + +int pngu_decode (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, PNGU_u32 stripAlpha) +{ + png_uint_32 rowbytes; + int i; + + // Read info if it hasn't been read before + if (!ctx->infoRead) + { + i = pngu_info (ctx); + if (i != PNGU_OK) + return i; + } + + // Check if the user has specified the real width and height of the image + if ( (ctx->prop.imgWidth != width) || (ctx->prop.imgHeight != height) ) + return PNGU_INVALID_WIDTH_OR_HEIGHT; + + // Check if color type is supported by PNGU + if ( (ctx->prop.imgColorType == PNGU_COLOR_TYPE_PALETTE) || (ctx->prop.imgColorType == PNGU_COLOR_TYPE_UNKNOWN) ) + return PNGU_UNSUPPORTED_COLOR_TYPE; + + // Scale 16 bit samples to 8 bit + if (ctx->prop.imgBitDepth == 16) + png_set_strip_16 (ctx->png_ptr); + + // Remove alpha channel if we don't need it + if (stripAlpha && ((ctx->prop.imgColorType == PNGU_COLOR_TYPE_RGB_ALPHA) || (ctx->prop.imgColorType == PNGU_COLOR_TYPE_GRAY_ALPHA))) + png_set_strip_alpha (ctx->png_ptr); + + // Expand 1, 2 and 4 bit samples to 8 bit + if (ctx->prop.imgBitDepth < 8) + png_set_packing (ctx->png_ptr); + + // Transform grayscale images to RGB + if ( (ctx->prop.imgColorType == PNGU_COLOR_TYPE_GRAY) || (ctx->prop.imgColorType == PNGU_COLOR_TYPE_GRAY_ALPHA) ) + png_set_gray_to_rgb (ctx->png_ptr); + + // Flush transformations + png_read_update_info (ctx->png_ptr, ctx->info_ptr); + + // Allocate memory to store the image + rowbytes = png_get_rowbytes (ctx->png_ptr, ctx->info_ptr); + if (rowbytes % 4) + rowbytes = ((rowbytes / 4) + 1) * 4; // Add extra padding so each row starts in a 4 byte boundary + + ctx->img_data = malloc (rowbytes * ctx->prop.imgHeight); + if (!ctx->img_data) + { + pngu_free_info (ctx); + return PNGU_LIB_ERROR; + } + + ctx->row_pointers = malloc (sizeof (png_bytep) * ctx->prop.imgHeight); + if (!ctx->row_pointers) + { + free (ctx->img_data); + pngu_free_info (ctx); + return PNGU_LIB_ERROR; + } + + for (i = 0; i < ctx->prop.imgHeight; i++) + ctx->row_pointers[i] = ctx->img_data + (i * rowbytes); + + // Transform the image and copy it to our allocated memory + png_read_image (ctx->png_ptr, ctx->row_pointers); + + // Free resources + pngu_free_info (ctx); + + // Success + return PNGU_OK; +} + + +void pngu_free_info (IMGCTX ctx) +{ + if (ctx->infoRead) + { + if (ctx->source == PNGU_SOURCE_DEVICE) + fclose (ctx->fd); + + png_destroy_read_struct (&(ctx->png_ptr), &(ctx->info_ptr), (png_infopp)NULL); + + ctx->infoRead = 0; + } +} + + +// Custom data provider function used for reading from memory buffers. +void pngu_read_data_from_buffer (png_structp png_ptr, png_bytep data, png_size_t length) +{ + IMGCTX ctx = (IMGCTX) png_get_io_ptr (png_ptr); + memcpy (data, ctx->buffer + ctx->cursor, length); + ctx->cursor += length; +} + + +// Custom data writer function used for writing to memory buffers. +void pngu_write_data_to_buffer (png_structp png_ptr, png_bytep data, png_size_t length) +{ + IMGCTX ctx = (IMGCTX) png_get_io_ptr (png_ptr); + memcpy (ctx->buffer + ctx->cursor, data, length); + ctx->cursor += length; +} + + +// Custom data flusher function used for writing to memory buffers. +void pngu_flush_data_to_buffer (png_structp png_ptr) +{ + // Nothing to do here +} + + +// Function used in YCbYCr to RGB decoding +int pngu_clamp (int value, int min, int max) +{ + if (value < min) + value = min; + else if (value > max) + value = max; + + return value; +} + diff --git a/source/libpng/pngu/pngu.h b/source/libpng/pngu/pngu.h new file mode 100644 index 0000000..b5e172b --- /dev/null +++ b/source/libpng/pngu/pngu.h @@ -0,0 +1,171 @@ +/******************************************************************************************** + +PNGU Version : 0.2a + +Coder : frontier + +More info : http://frontier-dev.net + +********************************************************************************************/ +#ifndef __PNGU__ +#define __PNGU__ + +// Return codes +#define PNGU_OK 0 +#define PNGU_ODD_WIDTH 1 +#define PNGU_ODD_STRIDE 2 +#define PNGU_INVALID_WIDTH_OR_HEIGHT 3 +#define PNGU_FILE_IS_NOT_PNG 4 +#define PNGU_UNSUPPORTED_COLOR_TYPE 5 +#define PNGU_NO_FILE_SELECTED 6 +#define PNGU_CANT_OPEN_FILE 7 +#define PNGU_CANT_READ_FILE 8 +#define PNGU_LIB_ERROR 9 + +// Color types +#define PNGU_COLOR_TYPE_GRAY 1 +#define PNGU_COLOR_TYPE_GRAY_ALPHA 2 +#define PNGU_COLOR_TYPE_PALETTE 3 +#define PNGU_COLOR_TYPE_RGB 4 +#define PNGU_COLOR_TYPE_RGB_ALPHA 5 +#define PNGU_COLOR_TYPE_UNKNOWN 6 + + +#ifdef __cplusplus + extern "C" { +#endif + +// Types +typedef unsigned char PNGU_u8; +typedef unsigned short PNGU_u16; +typedef unsigned int PNGU_u32; +typedef unsigned long long PNGU_u64; + +typedef struct +{ + PNGU_u8 r; + PNGU_u8 g; + PNGU_u8 b; +} PNGUCOLOR; + +typedef struct +{ + PNGU_u32 imgWidth; // In pixels + PNGU_u32 imgHeight; // In pixels + PNGU_u32 imgBitDepth; // In bitx + PNGU_u32 imgColorType; // PNGU_COLOR_TYPE_* + PNGU_u32 validBckgrnd; // Non zero if there is a background color + PNGUCOLOR bckgrnd; // Backgroun color + PNGU_u32 numTrans; // Number of transparent colors + PNGUCOLOR *trans; // Transparent colors +} PNGUPROP; + +// Image context, always initialize with SelectImageFrom* and free with ReleaseImageContext +struct _IMGCTX; +typedef struct _IMGCTX *IMGCTX; + + +/**************************************************************************** +* Pixel conversion * +****************************************************************************/ + +// Macro to convert RGB8 values to RGB565 +#define PNGU_RGB8_TO_RGB565(r,g,b) ( ((((PNGU_u16) r) & 0xF8U) << 8) | ((((PNGU_u16) g) & 0xFCU) << 3) | (((PNGU_u16) b) >> 3) ) + +// Macro to convert RGBA8 values to RGB5A3 +#define PNGU_RGB8_TO_RGB5A3(r,g,b,a) (PNGU_u16) (((a & 0xE0U) == 0xE0U) ? \ + (0x8000U | ((((PNGU_u16) r) & 0xF8U) << 7) | ((((PNGU_u16) g) & 0xF8U) << 2) | (((PNGU_u16) b) >> 3)) : \ + (((((PNGU_u16) a) & 0xE0U) << 7) | ((((PNGU_u16) r) & 0xF0U) << 4) | (((PNGU_u16) g) & 0xF0U) | ((((PNGU_u16) b) & 0xF0U) >> 4))) + +// Function to convert two RGB8 values to YCbYCr +PNGU_u32 PNGU_RGB8_TO_YCbYCr (PNGU_u8 r1, PNGU_u8 g1, PNGU_u8 b1, PNGU_u8 r2, PNGU_u8 g2, PNGU_u8 b2); + +// Function to convert an YCbYCr to two RGB8 values. +void PNGU_YCbYCr_TO_RGB8 (PNGU_u32 ycbycr, PNGU_u8 *r1, PNGU_u8 *g1, PNGU_u8 *b1, PNGU_u8 *r2, PNGU_u8 *g2, PNGU_u8 *b2); + + +/**************************************************************************** +* Image context handling * +****************************************************************************/ + +// Selects a PNG file, previosly loaded into a buffer, and creates an image context for subsequent procesing. +IMGCTX PNGU_SelectImageFromBuffer (const void *buffer); + +// Selects a PNG file, from any devoptab device, and creates an image context for subsequent procesing. +IMGCTX PNGU_SelectImageFromDevice (const char *filename); + +// Frees resources associated with an image context. Always call this function when you no longer need the IMGCTX. +void PNGU_ReleaseImageContext (IMGCTX ctx); + + +/**************************************************************************** +* Miscelaneous * +****************************************************************************/ + +// Retrieves info from selected PNG file, including image dimensions, color format, background and transparency colors. +int PNGU_GetImageProperties (IMGCTX ctx, PNGUPROP *fileproperties); + + +/**************************************************************************** +* Image conversion * +****************************************************************************/ + +// Expands selected image into an YCbYCr buffer. You need to specify context, image dimensions, +// destination address and stride in pixels (stride = buffer width - image width). +int PNGU_DecodeToYCbYCr (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride); + +// Macro for decoding an image inside a buffer at given coordinates. +#define PNGU_DECODE_TO_COORDS_YCbYCr(ctx,coordX,coordY,imgWidth,imgHeight,bufferWidth,bufferHeight,buffer) \ + \ + PNGU_DecodeToYCbYCr (ctx, imgWidth, imgHeight, ((void *) buffer) + (coordY) * (bufferWidth) * 2 + \ + (coordX) * 2, (bufferWidth) - (imgWidth)) + +// Expands selected image into a linear RGB565 buffer. You need to specify context, image dimensions, +// destination address and stride in pixels (stride = buffer width - image width). +int PNGU_DecodeToRGB565 (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride); + +// Macro for decoding an image inside a buffer at given coordinates. +#define PNGU_DECODE_TO_COORDS_RGB565(ctx,coordX,coordY,imgWidth,imgHeight,bufferWidth,bufferHeight,buffer) \ + \ + PNGU_DecodeToRGB565 (ctx, imgWidth, imgHeight, ((void *) buffer) + (coordY) * (bufferWidth) * 2 + \ + (coordX) * 2, (bufferWidth) - (imgWidth)) + +// Expands selected image into a linear RGBA8 buffer. You need to specify context, image dimensions, +// destination address, stride in pixels and default alpha value, which is used if the source image +// doesn't have an alpha channel. +int PNGU_DecodeToRGBA8 (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride, PNGU_u8 default_alpha); + +// Macro for decoding an image inside a buffer at given coordinates. +#define PNGU_DECODE_TO_COORDS_RGBA8(ctx,coordX,coordY,imgWidth,imgHeight,default_alpha,bufferWidth,bufferHeight,buffer) \ + \ + PNGU_DecodeToRGBA8 (ctx, imgWidth, imgHeight, ((void *) buffer) + (coordY) * (bufferWidth) * 2 + \ + (coordX) * 2, (bufferWidth) - (imgWidth), default_alpha) + +// Expands selected image into a 4x4 tiled RGB565 buffer. You need to specify context, image dimensions +// and destination address. +int PNGU_DecodeTo4x4RGB565 (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer); + +// Expands selected image into a 4x4 tiled RGB5A3 buffer. You need to specify context, image dimensions, +// destination address and default alpha value, which is used if the source image doesn't have an alpha channel. +int PNGU_DecodeTo4x4RGB5A3 (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u8 default_alpha); + +// Expands selected image into a 4x4 tiled RGBA8 buffer. You need to specify context, image dimensions, +// destination address and default alpha value, which is used if the source image doesn't have an alpha channel. +int PNGU_DecodeTo4x4RGBA8 (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u8 default_alpha); + +// Encodes an YCbYCr image in PNG format and stores it in the selected device or memory buffer. You need to +// specify context, image dimensions, destination address and stride in pixels (stride = buffer width - image width). +int PNGU_EncodeFromYCbYCr (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride); + +// Macro for encoding an image stored into an YCbYCr buffer at given coordinates. +#define PNGU_ENCODE_TO_COORDS_YCbYCr(ctx,coordX,coordY,imgWidth,imgHeight,bufferWidth,bufferHeight,buffer) \ + \ + PNGU_EncodeFromYCbYCr (ctx, imgWidth, imgHeight, ((void *) buffer) + (coordY) * (bufferWidth) * 2 + \ + (coordX) * 2, (bufferWidth) - (imgWidth)) + +#ifdef __cplusplus + } +#endif + +#endif + diff --git a/source/menu.c b/source/menu.c new file mode 100644 index 0000000..ec4babc --- /dev/null +++ b/source/menu.c @@ -0,0 +1,1478 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys.h" +#include "fat.h" +#include "nand.h" +#include "restart.h" +#include "title.h" +#include "usbstorage.h" +#include "utils.h" +#include "video.h" +#include "wad.h" +#include "wpad.h" +#include +#include "globals.h" + + + +/* FAT device list */ +//static fatDevice fdevList[] = { +fatDevice fdevList[] = { + { "sd", "SD-Karte", &__io_wiisd }, + { "usb", "USB-Speichermedium", &__io_usbstorage }, + { "usb2", "USB2.0-Speichermedium", &__io_wiiums }, + { "gcsda", "SD Gecko (Slot A)", &__io_gcsda }, + { "gcsdb", "SD Gecko (Slot B)", &__io_gcsdb }, + //{ "smb", "SMB share", NULL }, +}; + +/* NAND device list */ +//static nandDevice ndevList[] = { +nandDevice ndevList[] = { + { "Deaktiviert", 0, 0x00, 0x00 }, + { "SD/SDHC Karte", 1, 0xF0, 0xF1 }, + { "USB2.0-Speichermedium", 2, 0xF2, 0xF3 }, +}; + +/* FAT device */ +static fatDevice *fdev = NULL; +static nandDevice *ndev = NULL; + +// wiiNinja: Define a buffer holding the previous path names as user +// traverses the directory tree. Max of 10 levels is define at this point +static u8 gDirLevel = 0; +static char gDirList [MAX_DIR_LEVELS][MAX_FILE_PATH_LEN]; +static s32 gSeleted[MAX_DIR_LEVELS]; +static s32 gStart[MAX_DIR_LEVELS]; + +/* Macros */ +#define NB_FAT_DEVICES (sizeof(fdevList) / sizeof(fatDevice)) +#define NB_NAND_DEVICES (sizeof(ndevList) / sizeof(nandDevice)) + +// Local prototypes: wiiNinja +void WaitPrompt (char *prompt); +int PushCurrentDir(char *dirStr, s32 Selected, s32 Start); +char *PopCurrentDir(s32 *Selected, s32 *Start); +bool IsListFull (void); +char *PeekCurrentDir (void); +u32 WaitButtons(void); +u32 Pad_GetButtons(void); +void WiiLightControl (int state); + +s32 __Menu_IsGreater(const void *p1, const void *p2) +{ + u32 n1 = *(u32 *)p1; + u32 n2 = *(u32 *)p2; + + /* Equal */ + if (n1 == n2) + return 0; + + return (n1 > n2) ? 1 : -1; +} + + +s32 __Menu_EntryCmp(const void *p1, const void *p2) +{ + fatFile *f1 = (fatFile *)p1; + fatFile *f2 = (fatFile *)p2; + + /* Compare entries */ // wiiNinja: Include directory + if ((f1->entry.d_type==DT_DIR) && !(f2->entry.d_type==DT_DIR)) + return (-1); + else if (!(f1->entry.d_type==DT_DIR) && (f2->entry.d_type==DT_DIR)) + return (1); + else + return strcmp(f1->filename, f2->filename); +} + +char gFileName[MAX_FILE_PATH_LEN]; +s32 __Menu_RetrieveList(char *inPath, fatFile **outbuf, u32 *outlen) +{ + fatFile *buffer = NULL; + DIR *dir = NULL; + + struct dirent *entry; + + //struct stat filestat; + + //char dirpath[256], filename[768]; + u32 cnt = 0; + + /* Generate dirpath */ + //sprintf(dirpath, "%s:" WAD_DIRECTORY, fdev->mount); + + /* Open directory */ + dir = opendir(inPath); + if (!dir) + return -1; + + while ((entry = readdir(dir))) { + cnt++; + } + + /* Count entries */ + /*for (cnt = 0; !dirnext(dir, gFileName, &filestat);) { + // if (!(filestat.st_mode & S_IFDIR)) // wiiNinja + cnt++; + }*/ + + if (cnt > 0) { + /* Allocate memory */ + buffer = malloc(sizeof(fatFile) * cnt); + if (!buffer) { + closedir(dir); + return -2; + } + + /* Reset directory */ + //dirreset(dir); + closedir(dir); + dir = opendir(inPath); + + /* Get entries */ + for (cnt = 0; (entry = readdir(dir));) + { + bool addFlag = false; + + if (entry->d_type==DT_DIR) + { + // Add only the item ".." which is the previous directory + // AND if we're not at the root directory + if ((strcmp (entry->d_name, "..") == 0) && (gDirLevel > 1)) + addFlag = true; + else if (strcmp (entry->d_name, ".") != 0) + addFlag = true; + } + else + { + if(strlen(entry->d_name)>4) + { + if(!stricmp(entry->d_name+strlen(entry->d_name)-4, ".wad")) + addFlag = true; + } + } + + if (addFlag == true) + { + fatFile *file = &buffer[cnt++]; + + /* File name */ + strcpy(file->filename, entry->d_name); + + /* File stats */ + file->entry = *entry; + } + } + + /* Sort list */ + qsort(buffer, cnt, sizeof(fatFile), __Menu_EntryCmp); + } + + /* Close directory */ + closedir(dir); + + /* Set values */ + *outbuf = buffer; + *outlen = cnt; + + return 0; +} + + +void Menu_SelectIOS(void) +{ + u8 *iosVersion = NULL; + u32 iosCnt; + u8 tmpVersion; + + u32 cnt; + s32 ret, selected = 0; + bool found = false; + + /* Get IOS versions */ + ret = Title_GetIOSVersions(&iosVersion, &iosCnt); + if (ret < 0) + return; + + /* Sort list */ + qsort(iosVersion, iosCnt, sizeof(u8), __Menu_IsGreater); + + if (gConfig.cIOSVersion < 0) + tmpVersion = CIOS_VERSION; + else + { + tmpVersion = (u8)gConfig.cIOSVersion; + // For debugging only + //printf ("User pre-selected cIOS: %i\n", tmpVersion); + //WaitButtons(); + } + + /* Set default version */ + for (cnt = 0; cnt < iosCnt; cnt++) { + u8 version = iosVersion[cnt]; + + /* Custom IOS available */ + //if (version == CIOS_VERSION) + if (version == tmpVersion) + { + selected = cnt; + found = true; + break; + } + + /* Current IOS */ + if (version == IOS_GetVersion()) + selected = cnt; + } + + /* Ask user for IOS version */ + if ((gConfig.cIOSVersion < 0) || (found == false)) + { + for (;;) + { + /* Clear console */ + Con_Clear(); + + printf("\t>> W%chle das zu benutzende IOS: < IOS%d >\n\n", 132, iosVersion[selected]); + + printf("\t Dr%ccke LINKS/RECHTS um die IOS Version zu wechseln.\n\n",129); + + printf("\t Dr%ccke (A) zum Fortfahren.\n",129); + printf("\t Dr%ccke (HOME) zum Beenden.\n",129); + + u32 buttons = WaitButtons(); + + /* LEFT/RIGHT buttons */ + if (buttons & WPAD_BUTTON_LEFT) { + if ((--selected) <= -1) + selected = (iosCnt - 1); + } + if (buttons & WPAD_BUTTON_RIGHT) { + if ((++selected) >= iosCnt) + selected = 0; + } + + /* HOME button */ + if (buttons & WPAD_BUTTON_HOME) + Restart(); + + /* A button */ + if (buttons & WPAD_BUTTON_A) + break; + } + } + + + u8 version = iosVersion[selected]; + + if (IOS_GetVersion() != version) { + /* Shutdown subsystems */ + Wpad_Disconnect(); + //mload_close(); + + /* Load IOS */ + + if(!loadIOS(version)) Wpad_Init(), Menu_SelectIOS(); + + /* Initialize subsystems */ + Wpad_Init(); + } +} + +void Menu_FatDevice(void) +{ + s32 ret, selected = 0; + + /* Unmount FAT device */ + //if (fdev) + //Fat_Unmount(fdev); + //if (((fdevList[selected].mount[0] == 's') && (ndev->name[0] == 'S'))) + //selected++; + + /* Select source device */ + if (gConfig.fatDeviceIndex < 0) + { + for (;;) { + /* Clear console */ + Con_Clear(); + + /* Selected device */ + fdev = &fdevList[selected]; + + printf("\t>> W%chle den WAD-Speicherort: < %s >\n\n", 132, fdev->name); + + printf("\t Dr%ccke LINKS/RECHTS um das Ger%ct zu wechseln.\n\n",129,132); + + printf("\t Dr%ccke (A) zum Fortfahren.\n",129); + printf("\t Dr%ccke (HOME) zum Beenden.\n\n",129); + + u32 buttons = WaitButtons(); + + /* LEFT/RIGHT buttons */ + if (buttons & WPAD_BUTTON_LEFT) { + if ((--selected) <= -1) + selected = (NB_FAT_DEVICES - 1); + if ((fdevList[selected].mount[0] == 's') && (ndev->name[0] == 'S')) + selected--; + if ((fdevList[selected].mount[0] == 'u' && fdevList[selected].mount[3] == '2') && (ndev->name[0] == 'U')) + selected--; + if ((selected) <= -1) + selected = (NB_FAT_DEVICES - 1); + } + if (buttons & WPAD_BUTTON_RIGHT) { + if ((++selected) >= NB_FAT_DEVICES) + selected = 0; + if ((fdevList[selected].mount[0] == 's') && (ndev->name[0] == 'S')) + selected++; + if ((fdevList[selected].mount[0] == 'u' && fdevList[selected].mount[3] == '2') && (ndev->name[0] == 'U')) + selected++; + } + + /* HOME button */ + if (buttons & WPAD_BUTTON_HOME) + Restart(); + + /* A button */ + if (buttons & WPAD_BUTTON_A) + break; + } + } + else + { + sleep(5); + fdev = &fdevList[gConfig.fatDeviceIndex]; + } + + printf("[+] Lade Ger%ct %s, bitte warten...",132, fdev->name); + fflush(stdout); + + /* Mount FAT device */ + + ret = Fat_Mount(fdev); + if (ret < 0) { + printf("\n FEHLER! (ret = %d)\n", ret); + goto err; + } else + printf("\n OK!\n"); + + return; + +err: + + if(gConfig.fatDeviceIndex >= 0) gConfig.fatDeviceIndex = -1; + WiiLightControl (WII_LIGHT_OFF); + printf("\n"); + printf(" Dr%ccke eine Taste zum Fortfahren...\n",129); + + WaitButtons(); + + /* Prompt menu again */ + Menu_FatDevice(); +} + +void Menu_NandDevice(void) +{ + s32 ret, selected = 0; + + /* Disable NAND emulator */ + if (ndev) { + Nand_Unmount(ndev); + Nand_Disable(); + } + + /* Select source device */ + if (gConfig.nandDeviceIndex < 0) + { + for (;;) { + /* Clear console */ + Con_Clear(); + + /* Selected device */ + ndev = &ndevList[selected]; + + printf("\t>> W%chle NAND-Emulation: < %s >\n\n",132, ndev->name); + + printf("\t Dr%ccke LINKS/RECHTS um das Ger%ct zu wechseln.\n\n",129,132); + + printf("\t Dr%ccke (A) zum Fortfahren.\n",129); + printf("\t Dr%ccke (HOME) zum Beenden.\n\n",129); + + u32 buttons = WaitButtons(); + + /* LEFT/RIGHT buttons */ + if (buttons & WPAD_BUTTON_LEFT) { + if ((--selected) <= -1) + selected = (NB_NAND_DEVICES - 1); + } + if (buttons & WPAD_BUTTON_RIGHT) { + if ((++selected) >= NB_NAND_DEVICES) + selected = 0; + } + + /* HOME button */ + if (buttons & WPAD_BUTTON_HOME) + Restart(); + + /* A button */ + if (buttons & WPAD_BUTTON_A) + break; + } + } + else + { + ndev = &ndevList[gConfig.nandDeviceIndex]; + } + + /* No NAND device */ + if (!ndev->mode) + return; + + printf("[+] Aktiviere NAND-Emulation..."); + fflush(stdout); + + /* Mount NAND device */ + ret = Nand_Mount(ndev); + if (ret < 0) { + printf("\n FEHLER! (ret = %d)\n", ret); + goto err; + } + + /* Enable NAND emulator */ + ret = Nand_Enable(ndev); + if (ret < 0) { + printf("\n FEHLER! (ret = %d)\n", ret); + goto err; + } else + printf("\n OK!\n"); + + return; + +err: + printf("\n"); + printf(" Dr%ccke eine Taste zum Fortfahren...\n",129); + + WaitButtons(); + + /* Prompt menu again */ + Menu_NandDevice(); +} + +char gTmpFilePath[MAX_FILE_PATH_LEN]; +/* Install and/or Uninstall multiple WADs - Leathl */ +int Menu_BatchProcessWads(fatFile *files, int fileCount, char *inFilePath, int installCnt, int uninstallCnt) +{ + int count; + + for (;;) + { + Con_Clear(); + + if ((installCnt > 0) & (uninstallCnt == 0)) { + printf("[+] %d Datei%s ausgew%chlt zum Installieren.\n\n", installCnt, (installCnt == 1) ? "" : "en", 132); + printf(" Wollen Sie Fortfahren?\n"); + } + else if ((installCnt == 0) & (uninstallCnt > 0)) { + printf("[+] %d Datei%s ausgew%chlt zum Deinstallieren.\n\n", uninstallCnt, (uninstallCnt == 1) ? "" : "en", 132); + printf(" Wollen Sie Fortfahren?\n"); + } + else { + printf("[+] %d Datei%s ausgew%chlt zum Installieren.\n", installCnt, (installCnt == 1) ? "" : "en", 132); + printf(" %d Datei%s ausgew%chlt zum Deinstallieren.\n\n", uninstallCnt, (uninstallCnt == 1) ? "" : "en", 132); + printf(" Wollen Sie Fortfahren?\n"); + } + + printf("\n Dr%ccke (A) zum Fortfahren.\n",129); + printf(" Dr%ccke [B] f%cr den Dateibrowser.\n",129,129); + + u32 buttons = WaitButtons(); + + if (buttons & WPAD_BUTTON_A) + break; + + if (buttons & WPAD_BUTTON_B) + return 0; + } + + WiiLightControl (WII_LIGHT_ON); + int errors = 0; + int success = 0; + s32 ret; + + for (count = 0; count < fileCount; count++) + { + fatFile *thisFile = &files[count]; + + if ((thisFile->install == 1) | (thisFile->install == 2)) { + int mode = thisFile->install; + Con_Clear(); + printf("[+] %cffne \"%s\", bitte warten...",153, thisFile->filename); + + sprintf(gTmpFilePath, "%s/%s", inFilePath, thisFile->filename); + + FILE *fp = fopen(gTmpFilePath, "rb"); + if (!fp) + { + printf("\n FEHLER!\n"); + errors += 1; + continue; + } + + printf("[+] %s WAD, bitte warten...\n", (mode == 2) ? "Deinstalliere" : "Installiere"); + if (mode == 2) + ret = Wad_Uninstall(fp); + else + ret = Wad_Install(fp, thisFile); + + if (ret < 0) errors += 1; + else success += 1; + + thisFile->installstate = ret; + + if (fp) + fclose(fp); + } + } + + WiiLightControl (WII_LIGHT_OFF); + + printf("\n"); + printf(" %d Dateien installiert und %d fehlgeschlagen...\n", success, errors); + + if (errors > 0) + { + printf("\n Einige Installationen fehlgeschlagen"); + printf("\n Dr%ccke (A) um diese aufzulisten.\n", 129); + printf(" Dr%ccke [B] zum %cberspringen.\n", 129, 154); + + u32 buttons = WaitButtons(); + + if ((buttons & WPAD_BUTTON_A)) + { + Con_Clear(); + + int i=0; + for (count = 0; count < fileCount; count++) + { + fatFile *thisFile = &files[count]; + + if (thisFile->installstate <0) + { + char str[41]; + strncpy(str, thisFile->filename, 40); //Only 40 chars to fit the screen + str[40]=0; + i++; + if(thisFile->installstate == -999) printf(" %s BRICK BLOCKED\n", str); + else if(thisFile->installstate == -998) printf(" %s %cbersprungen\n", str, 129); + else if(thisFile->installstate == -106) printf(" %s nicht installiert?\n", str); + else if(thisFile->installstate == -1036 ) printf(" %s benötigtes IOS fehlt\n", str); + else if(thisFile->installstate == -4100 ) printf(" %s kein trucha bug?\n", str); + else printf(" %s Fehler %d\n", str, thisFile->installstate); + if( i == 17 ) + { + printf("\n Dr%ccke eine Taste zum Fortfahren...\n",129); + WaitButtons(); + i = 0; + } + } + } + } + } + printf("\n Dr%ccke eine Taste zum Fortfahren...\n",129); + WaitButtons(); + + return 1; +} + +/* File Operations - Leathl */ +int Menu_FileOperations(fatFile *file, char *inFilePath) +{ + //f32 filesize = (file->filestat.st_size / MB_SIZE); + + for (;;) + { + Con_Clear(); + + printf("[+] WAD-Dateiname : %s\n", file->filename); + //printf(" WAD-Dateigr%csse : %.2f MB\n\n",148, filesize); + + + printf("[+] W%chle Aktion: < %s WAD >\n\n", 132, "L%csche"); //There's yet nothing else than delete + + printf(" Dr%ccke LINKS/RECHTS um die Aktion zu wechseln.\n\n",129); + + printf(" Dr%ccke (A) zum Fortfahren.\n",129); + printf(" Dr%ccke [B] f%cr den Dateibrowser.\n",129,129); + + u32 buttons = WaitButtons(); + + /* A button */ + if (buttons & WPAD_BUTTON_A) + break; + + /* B button */ + if (buttons & WPAD_BUTTON_B) + return 0; + } + + Con_Clear(); + + printf("[+] L%cschen \"%s\", bitte warten...\n", 148, file->filename); + + sprintf(gTmpFilePath, "%s/%s", inFilePath, file->filename); + int error = remove(gTmpFilePath); + if (error != 0) + printf(" FEHLER!"); + else + printf(" Erfolgreich gel%cscht!", 148); + + printf("\n"); + printf(" Dr%ccke eine Taste zum Fortfahren...\n", 129); + + WaitButtons(); + + return !error; +} + +void Menu_WadEdit(fatFile *file) +{ + /******************************************************************************************/ + // ios liste + /******************************************************************************************/ + u8 *iosVersion = NULL; + u32 iosCnt; + + u32 cnt; + s32 ret, ios_selected = 0; + + /* Get IOS versions */ + ret = Title_GetIOSVersions(&iosVersion, &iosCnt); + if (ret < 0) + return; + + /* Sort list */ + qsort(iosVersion, iosCnt, sizeof(u8), __Menu_IsGreater); + + /* Set default version */ + for (cnt = 0; cnt < iosCnt; cnt++) + { + u8 version = iosVersion[cnt]; + + /* Channel IOS */ + if (version == file->new_ios) + { + ios_selected = cnt; + break; + } + + /* Current IOS */ + if (version == IOS_GetVersion()) + ios_selected = cnt; + } + /******************************************************************************************/ + + int selected = 0; + bool ahbprot = file->new_ahbprot; + bool pass = file->new_pass; + char ios_text[30]; + + while(1) + { + /* Clear console */ + Con_Clear(); + + if(iosVersion[ios_selected] == 58) + sprintf(ios_text, "(USB2, empfohlen)"); + else + ios_text[0] = '\0'; + + printf("[+] Erweiterte Einstellungen\n\n"); + + printf("%2s Start-IOS des Kanals: %i %s\n", (selected == 0) ? ">>" : " ", iosVersion[ios_selected], ios_text); + printf("%2s AHBPROT aktivieren: %s\n", (selected == 1) ? ">>" : " ", (!ahbprot) ? "Nein" : "Ja"); + printf("%2s Altersbeschr%cnkung: %s\n\n", (selected == 2) ? ">>" : " ", 132, (!pass) ? "Nein" : "Ja"); + + printf(" Dr%ccke LINKS/RECHTS um die Aktion zu wechseln.\n\n",129); + + Con_FgColor(1, 1); + printf(" ACHTUNG: Bei Kan%clen bzw Anwendungen mit IOS-RELOAD,\n geht der AHBPROT-Flag verloren.\n\n", 132); + + Con_FgColor(7, 1); + printf(" Dr%ccke (A) zum Speichern.\n",129); + printf(" Dr%ccke [B] zum Abbrechen.\n\n",129); + + u32 buttons = Wpad_WaitButtons(); + + /* UP button */ + if (buttons & WPAD_BUTTON_UP && selected > 0) + selected--; + + /* DOWN button */ + if (buttons & WPAD_BUTTON_DOWN && selected < 2) + selected++; + + /* LEFT/RIGHT buttons */ + if (buttons & (WPAD_BUTTON_LEFT | WPAD_BUTTON_RIGHT)) + { + if(selected == 0) + { + if(buttons & WPAD_BUTTON_LEFT && --ios_selected <= -1) + ios_selected = (iosCnt - 1); + + else if(buttons & WPAD_BUTTON_RIGHT && ++ios_selected >= iosCnt) + ios_selected = 0; + } + + else if(selected == 1) + ahbprot ^= 1; + + else if(selected == 2) + pass ^= 1; + } + + /* A button */ + if (buttons & WPAD_BUTTON_A) + break; + + /* B button */ + if (buttons & WPAD_BUTTON_B) + return; + } + + printf(" gespeichert\n\n"); + file->new_ios = iosVersion[ios_selected]; + file->new_ahbprot = ahbprot; + file->new_pass = pass; + + /* Wait for button */ + printf(" Dr%ccke eine Taste zum Fortfahren...\n",129); + Wpad_WaitButtons(); +} + +void Menu_WadManage(fatFile *file, char *inFilePath) +{ + FILE *fp = NULL; + + //char filepath[128]; + //f32 filesize = 0; + u32 mode = 0; + + /* File size in megabytes */ + //filesize = (file->filestat.st_size / MB_SIZE); + + for (;;) { + /* Clear console */ + Con_Clear(); + + printf("[+] WAD-Dateiname : %s\n", file->filename); + //printf(" WAD-Dateigr%csse : %.2f MB\n\n",148, filesize); + + + printf("[+] W%chle Aktion: < %s WAD >\n\n",132, (!mode) ? "Installiere" : "Deinstalliere"); + + printf(" Dr%ccke LINKS/RECHTS um die Aktion zu wechseln.\n\n",129); + + printf(" Dr%ccke (A) zum Fortfahren.\n",129); + printf(" Dr%ccke [B] f%cr den Dateibrowser.\n",129,129); + if(file->new_ios) + printf(" Dr%ccke (1) f%cr erweiterte Einstellungen.",129,129); + + u32 buttons = WaitButtons(); + + /* LEFT/RIGHT buttons */ + if (buttons & (WPAD_BUTTON_LEFT | WPAD_BUTTON_RIGHT)) + mode ^= 1; + + /* A button */ + if (buttons & WPAD_BUTTON_A) + break; + + /* B button */ + if (buttons & WPAD_BUTTON_B) + return; + + /* 1 button */ + if (buttons & WPAD_BUTTON_1 && file->new_ios) + Menu_WadEdit(file); + } + + /* Clear console */ + Con_Clear(); + + printf("[+] %cffne \"%s\", bitte warten...",153, file->filename); + fflush(stdout); + + /* Generate filepath */ + // sprintf(filepath, "%s:" WAD_DIRECTORY "/%s", fdev->mount, file->filename); + sprintf(gTmpFilePath, "%s/%s", inFilePath, file->filename); // wiiNinja + + /* Open WAD */ + fp = fopen(gTmpFilePath, "rb"); + if (!fp) + { + printf(" FEHLER!\n"); + goto out; + } + else + printf(" OK!\n\n"); + + printf("[+] %s WAD, bitte warten...\n", (!mode) ? "Installiere" : "Deinstalliere"); + + /* Do install/uninstall */ + WiiLightControl (WII_LIGHT_ON); + if (!mode) + Wad_Install(fp, file); + else + Wad_Uninstall(fp); + WiiLightControl (WII_LIGHT_OFF); + +out: + /* Close file */ + if (fp) + fclose(fp); + + printf("\n"); + printf(" Dr%ccke eine Taste zum Fortfahren...\n",129); + + /* Wait for button */ + WaitButtons(); +} + +void Menu_WadList(void) +{ + char str [100]; + fatFile *fileList = NULL; + u32 fileCnt; + s32 ret, selected = 0, start = 0; + char *tmpPath = malloc (MAX_FILE_PATH_LEN); + int installCnt = 0; + int uninstallCnt = 0; + + //fatFile *installFiles = malloc(sizeof(fatFile) * 50); + //int installCount = 0; + + // wiiNinja: check for malloc error + if (tmpPath == NULL) + { + printf(" Fehler beim lesen des Verzeichnisses"); + return; + } + + printf("[+] Lade Dateiliste..."); + fflush(stdout); + + gDirLevel = 0; + + // wiiNinja: The root is always the primary folder + // But if the user has a /wad directory, just go there. This makes + // both sides of the argument win + sprintf(tmpPath, "%s:" WAD_DIRECTORY, fdev->mount); + PushCurrentDir(tmpPath,0,0); + //if (strcmp (WAD_DIRECTORY, WAD_ROOT_DIRECTORY) != 0) + if (strcmp (WAD_DIRECTORY, gConfig.startupPath) != 0) + { + // If the directory can be successfully opened, it must exists + //DIR_ITER *tmpDirPtr = NULL; + //tmpDirPtr = diropen(WAD_ROOT_DIRECTORY); + //if (tmpDirPtr) + //{ + // dirclose (tmpDirPtr); + + // Now push the /wad directory as the current operating folder + //sprintf(tmpPath, "%s:" WAD_ROOT_DIRECTORY, fdev->mount); + sprintf(tmpPath, "%s:%s", fdev->mount, gConfig.startupPath); + //printf ("\nThe final startupPath is: %s\n", tmpPath); + //WaitButtons (); + PushCurrentDir(tmpPath,0,0); // wiiNinja + //} + } + + /* Retrieve filelist */ +getList: + if (fileList) + { + free (fileList); + fileList = NULL; + } + + ret = __Menu_RetrieveList(tmpPath, &fileList, &fileCnt); + if (ret < 0) { + printf(" FEHLER! (ret = %d)\n", ret); + goto err; + } + + /* No files */ + if (!fileCnt) { + printf(" Keine Dateien/Verzeichnisse gefunden!\n"); + goto err; + } + + /* Set install-values to 0 - Leathl */ + int counter; + for (counter = 0; counter < fileCnt; counter++) { + fatFile *file = &fileList[counter]; + file->install = 0; + + sprintf(gTmpFilePath, "%s/%s", tmpPath, file->filename); + if (file->entry.d_type==DT_REG) + Wad_Read(fopen(gTmpFilePath, "r"), &file->old_ios, &file->old_ahbprot, &file->old_pass, &file->high_id, &file->low_id); + + file->new_ios = file->old_ios; + file->new_ahbprot = file->old_ahbprot; + file->new_pass = file->old_pass; + } + + for (;;) + { + u32 cnt; + s32 index; + + /* Clear console */ + Con_Clear(); + + /** Print entries **/ + cnt = strlen(tmpPath); + if(cnt>26) + index = cnt-26; + else + index = 0; + + printf("[+] WAD Datein in [%s]:", tmpPath+index); + Con_FgColor(2, 1); + printf("\x1b[0;48H"); + printf("IOS HW Pass\n\n"); + Con_FgColor(7, 1); + + /* Print entries */ + for (cnt = start; cnt < fileCnt; cnt++) + { + fatFile *file = &fileList[cnt]; + + // zeile markieren + if(cnt == selected) + Con_BgColor(6, 0); + else + Con_BgColor(0, 0); + + /* Entries per page limit */ + if ((cnt - start) >= ENTRIES_PER_PAGE) + break; + + strncpy(str, file->filename, 40); //Only 40 chars to fit the screen + str[40]=0; + + /* Print filename */ + if (file->entry.d_type==DT_DIR) // wiiNinja + printf(" %2s %-42s (VERZEICHNIS) \n", (cnt == selected) ? ">>" : " ", str); + else if(file->new_ios) + printf(" %2s%s %-42s %-4i %-6s %-6s\n", (cnt == selected) ? ">>" : " ", (file->install == 1) ? "+" : ((file->install == 2) ? "-" : " "), str, file->new_ios, (file->new_ahbprot) ? "Ja" : "Nein", (file->new_pass) ? "Ja" : "Nein"); + else + printf(" %2s%s %-61s\n", (cnt == selected) ? ">>" : " ", (file->install == 1) ? "+" : ((file->install == 2) ? "-" : " "), str); + } + + Con_BgColor(0, 0); + Con_FgColor(2, 1); + printf("\n IOS = Kanal-IOS | HW = HW_AHBPROT | Pass = Altersbeschr%cnkung\n", 132); + Con_FgColor(7, 1); + printf(" Dr%ccke (A) um das Verzeichnis oder die WAD-Datei zu %cffnen.\n", 129, 148); + if(gDirLevel>1) + printf(" Dr%ccke [B] um das Verzeichniss zu verlassen.\n", 129); + else + printf(" Dr%ccke [B] um ein anderes Speichermedium zu w%chlen.\n", 129, 132); + printf(" Dr%ccke (1) f%cr erweiterte Einstellungen.\n", 129, 129); + printf(" Dr%ccke + und - zum markieren bzw. die Markierung zu entfernen.", 129); + + /** Controls **/ + u32 buttons = WaitButtons(); + + /* DPAD buttons */ + if (buttons & (WPAD_BUTTON_UP | WPAD_BUTTON_LEFT)) + { + selected -= (buttons & WPAD_BUTTON_LEFT) ? ENTRIES_PER_PAGE : 1; + + if (selected <= -1) + selected = (fileCnt - 1); + } + if (buttons & (WPAD_BUTTON_DOWN | WPAD_BUTTON_RIGHT)) + { + selected += (buttons & WPAD_BUTTON_RIGHT) ? ENTRIES_PER_PAGE : 1; + + if (selected >= fileCnt) + selected = 0; + } + + /* HOME button */ + if (buttons & WPAD_BUTTON_HOME) + Restart(); + + /* Plus Button - Leathl */ + if (buttons & WPAD_BUTTON_PLUS) + { + if(Wpad_TimeButton()) + { + installCnt = 0; + int i = 0; + while( i < fileCnt) + { + fatFile *file = &fileList[i]; + if (((file->entry.d_type==DT_DIR) == false) & (file->install == 0)) { + file->install = 1; + + installCnt += 1; + } + else if (((file->entry.d_type==DT_DIR) == false) & (file->install == 1)) { + file->install = 0; + + installCnt -= 1; + } + else if (((file->entry.d_type==DT_DIR) == false) & (file->install == 2)) { + file->install = 1; + + installCnt += 1; + uninstallCnt -= 1; + } + i++; + } + + } + else + { + fatFile *file = &fileList[selected]; + if (((file->entry.d_type==DT_DIR) == false) & (file->install == 0)) { + file->install = 1; + + installCnt += 1; + } + else if (((file->entry.d_type==DT_DIR) == false) & (file->install == 1)) { + file->install = 0; + + installCnt -= 1; + } + else if (((file->entry.d_type==DT_DIR) == false) & (file->install == 2)) { + file->install = 1; + + installCnt += 1; + uninstallCnt -= 1; + } + selected++; + + if (selected >= fileCnt) + selected = 0; + } + } + + /* Minus Button - Leathl */ + if (buttons & WPAD_BUTTON_MINUS) + { + + if(Wpad_TimeButton()) + { + installCnt = 0; + int i = 0; + while( i < fileCnt) + { + fatFile *file = &fileList[i]; + if (((file->entry.d_type==DT_DIR) == false) & (file->install == 0)) { + file->install = 2; + + uninstallCnt += 1; + } + else if (((file->entry.d_type==DT_DIR) == false) & (file->install == 1)) { + file->install = 2; + + uninstallCnt += 1; + installCnt -= 1; + } + else if (((file->entry.d_type==DT_DIR) == false) & (file->install == 2)) { + file->install = 0; + + uninstallCnt -= 1; + } + i++; + } + + } + else + { + fatFile *file = &fileList[selected]; + if (((file->entry.d_type==DT_DIR) == false) & (file->install == 0)) { + file->install = 2; + + uninstallCnt += 1; + } + else if (((file->entry.d_type==DT_DIR) == false) & (file->install == 1)) { + file->install = 2; + + uninstallCnt += 1; + installCnt -= 1; + } + else if (((file->entry.d_type==DT_DIR) == false) & (file->install == 2)) { + file->install = 0; + + uninstallCnt -= 1; + } + selected++; + + if (selected >= fileCnt) + selected = 0; + } + } + + /* 1 Button - Leathl */ + if (buttons & WPAD_BUTTON_1) + { + fatFile *tmpFile = &fileList[selected]; + if(tmpFile->new_ios) + { + char *tmpCurPath = PeekCurrentDir (); + if (tmpCurPath != NULL) { + Menu_WadEdit(tmpFile); + // int res = Menu_FileOperations(tmpFile, tmpCurPath); + // if (res != 0) + // goto getList; + } + } + } + + + /* A button */ + if (buttons & WPAD_BUTTON_A) + { + fatFile *tmpFile = &fileList[selected]; + char *tmpCurPath; + if (tmpFile->entry.d_type==DT_DIR) // wiiNinja + { + if (strcmp (tmpFile->filename, "..") == 0) + { + selected = 0; + start = 0; + + // Previous dir + tmpCurPath = PopCurrentDir(&selected, &start); + if (tmpCurPath != NULL) + sprintf(tmpPath, "%s", tmpCurPath); + + installCnt = 0; + uninstallCnt = 0; + + goto getList; + } + else if (IsListFull () == true) + { + WaitPrompt ("Maximale Anzahl an Unterordnern erreicht.\n"); + } + else + { + tmpCurPath = PeekCurrentDir (); + if (tmpCurPath != NULL) + { + if(gDirLevel>1) + sprintf(tmpPath, "%s/%s", tmpCurPath, tmpFile->filename); + else + sprintf(tmpPath, "%s%s", tmpCurPath, tmpFile->filename); + } + // wiiNinja: Need to PopCurrentDir + PushCurrentDir (tmpPath, selected, start); + selected = 0; + start = 0; + + installCnt = 0; + uninstallCnt = 0; + + goto getList; + } + } + else + { + //If at least one WAD is marked, goto batch screen - Leathl + if ((installCnt > 0) | (uninstallCnt > 0)) { + char *thisCurPath = PeekCurrentDir (); + if (thisCurPath != NULL) { + int res = Menu_BatchProcessWads(fileList, fileCnt, thisCurPath, installCnt, uninstallCnt); + + if (res == 1) { + int counter; + for (counter = 0; counter < fileCnt; counter++) { + fatFile *temp = &fileList[counter]; + temp->install = 0; + } + + installCnt = 0; + uninstallCnt = 0; + } + } + } + //else use standard wadmanage menu - Leathl + else { + tmpCurPath = PeekCurrentDir (); + if (tmpCurPath != NULL) + Menu_WadManage(tmpFile, tmpCurPath); + } + } + } + + /* B button */ + if (buttons & WPAD_BUTTON_B) + { + if(gDirLevel<=1) + { + return; + } + + char *tmpCurPath; + selected = 0; + start = 0; + // Previous dir + tmpCurPath = PopCurrentDir(&selected, &start); + if (tmpCurPath != NULL) + sprintf(tmpPath, "%s", tmpCurPath); + goto getList; + //return; + } + + /** Scrolling **/ + /* List scrolling */ + index = (selected - start); + + if (index >= ENTRIES_PER_PAGE) + start += index - (ENTRIES_PER_PAGE - 1); + if (index <= -1) + start += index; + } + +err: + printf("\n"); + printf(" Dr%ccke eine Taste zum Fortfahren...\n",129); + + free (tmpPath); + + /* Wait for button */ + WaitButtons(); +} + + +void Menu_Loop(void) +{ + u8 iosVersion; + + /* Select IOS menu */ + if (have_ahbprot()) + { + + u32 mode = 0; + while(1) + { + /* Clear console */ + Con_Clear(); + printf(" HW_AHBPROT Flag vorhanden\n"); + printf(" trotzdem ein anderes IOS verwenden?: < %s >\n\n", (!mode) ? "Nein" : "Ja"); + printf(" Dr%ccke (A) zum Fortfahren.\n", 129); + printf(" Dr%ccke (HOME) zum Beenden.\n", 129); + u32 buttons = WaitButtons(); + /* LEFT/RIGHT buttons */ + if (buttons & (WPAD_BUTTON_LEFT | WPAD_BUTTON_RIGHT)) + mode ^= 1; + + /* HOME button */ + if (buttons & WPAD_BUTTON_HOME) + Restart(); + + /* A button */ + if (buttons & WPAD_BUTTON_A) + break; + } + + if(mode) + Menu_SelectIOS(); + } + else + Menu_SelectIOS(); + + /* Retrieve IOS version */ + iosVersion = IOS_GetVersion(); + + ndev = &ndevList[0]; + + /* NAND device menu */ + if ((iosVersion == CIOS_VERSION || iosVersion == 250) && IOS_GetRevision() >13) + { + Menu_NandDevice(); + } + for (;;) { + /* FAT device menu */ + Menu_FatDevice(); + + /* WAD list menu */ + Menu_WadList(); + } +} + +// Start of wiiNinja's added routines + +int PushCurrentDir (char *dirStr, s32 Selected, s32 Start) +{ + int retval = 0; + + // Store dirStr into the list and increment the gDirLevel + // WARNING: Make sure dirStr is no larger than MAX_FILE_PATH_LEN + if (gDirLevel < MAX_DIR_LEVELS) + { + strcpy (gDirList [gDirLevel], dirStr); + gSeleted[gDirLevel]=Selected; + gStart[gDirLevel]=Start; + gDirLevel++; + //if (gDirLevel >= MAX_DIR_LEVELS) + // gDirLevel = 0; + } + else + retval = -1; + + return (retval); +} + +char *PopCurrentDir(s32 *Selected, s32 *Start) +{ + if (gDirLevel > 1) + gDirLevel--; + else + gDirLevel = 0; + + *Selected = gSeleted[gDirLevel]; + *Start = gStart[gDirLevel]; + return PeekCurrentDir(); +} + +bool IsListFull (void) +{ + if (gDirLevel < MAX_DIR_LEVELS) + return (false); + else + return (true); +} + +char *PeekCurrentDir (void) +{ + // Return the current path + if (gDirLevel > 0) + return (gDirList [gDirLevel-1]); + else + return (NULL); +} + +void WaitPrompt (char *prompt) +{ + printf("\n%s", prompt); + printf(" Dr%ccke eine Taste zum Fortfahren...\n",129); + + /* Wait for button */ + WaitButtons(); +} + +u32 Pad_GetButtons(void) +{ + u32 buttons = 0, cnt; + + /* Scan pads */ + PAD_ScanPads(); + + /* Get pressed buttons */ + //for (cnt = 0; cnt < MAX_WIIMOTES; cnt++) + for (cnt = 0; cnt < 4; cnt++) + buttons |= PAD_ButtonsDown(cnt); + + return buttons; +} + + +// Routine to wait for a button from either the Wiimote or a gamecube +// controller. The return value will mimic the WPAD buttons to minimize +// the amount of changes to the original code, that is expecting only +// Wiimote button presses. Note that the "HOME" button on the Wiimote +// is mapped to the "SELECT" button on the Gamecube Ctrl. (wiiNinja 5/15/2009) +u32 WaitButtons(void) +{ + u32 buttons = 0; + u32 buttonsGC = 0; + + /* Wait for button pressing */ + while (!buttons && !buttonsGC) + { + // GC buttons + buttonsGC = Pad_GetButtons (); + + // Wii buttons + buttons = Wpad_GetButtons(); + + VIDEO_WaitVSync(); + } + + if (buttonsGC) + { + if(buttonsGC & PAD_BUTTON_A) + { + //printf ("Button A on the GC controller\n"); + buttons |= WPAD_BUTTON_A; + } + else if(buttonsGC & PAD_BUTTON_B) + { + //printf ("Button B on the GC controller\n"); + buttons |= WPAD_BUTTON_B; + } + else if(buttonsGC & PAD_BUTTON_LEFT) + { + //printf ("Button LEFT on the GC controller\n"); + buttons |= WPAD_BUTTON_LEFT; + } + else if(buttonsGC & PAD_BUTTON_RIGHT) + { + //printf ("Button RIGHT on the GC controller\n"); + buttons |= WPAD_BUTTON_RIGHT; + } + else if(buttonsGC & PAD_BUTTON_DOWN) + { + //printf ("Button DOWN on the GC controller\n"); + buttons |= WPAD_BUTTON_DOWN; + } + else if(buttonsGC & PAD_BUTTON_UP) + { + //printf ("Button UP on the GC controller\n"); + buttons |= WPAD_BUTTON_UP; + } + else if(buttonsGC & PAD_BUTTON_START) + { + //printf ("Button START on the GC controller\n"); + buttons |= WPAD_BUTTON_HOME; + } + } + + return buttons; +} // WaitButtons + + +void WiiLightControl (int state) +{ + switch (state) + { + case WII_LIGHT_ON: + /* Turn on Wii Light */ + WIILIGHT_SetLevel(255); + WIILIGHT_TurnOn(); + break; + + case WII_LIGHT_OFF: + default: + /* Turn off Wii Light */ + WIILIGHT_SetLevel(0); + WIILIGHT_TurnOn(); + WIILIGHT_Toggle(); + break; + } +} // WiiLightControl + diff --git a/source/menu.h b/source/menu.h new file mode 100644 index 0000000..92cd259 --- /dev/null +++ b/source/menu.h @@ -0,0 +1,8 @@ +#ifndef _MENU_H_ +#define _MENU_H_ + +/* Prototypes */ +void Menu_Loop(void); + +#endif + diff --git a/source/mload.c b/source/mload.c new file mode 100644 index 0000000..8b1bab4 --- /dev/null +++ b/source/mload.c @@ -0,0 +1,399 @@ +/* mload.c (for PPC) (c) 2009, Hermes + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "mload.h" + +static const char mload_fs[] ATTRIBUTE_ALIGN(32) = "/dev/mload"; + +static s32 mload_fd = -1; + +/*--------------------------------------------------------------------------------------------------------------*/ + +// to init/test if the device is running + +int mload_init() +{ +int n; + + if(mload_fd>=0) return 0; + + for(n=0;n<10;n++) // try 2.5 seconds + { + mload_fd=IOS_Open(mload_fs, 0); + + if(mload_fd>=0) break; + + usleep(250*1000); + } + +return mload_fd; +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// to close the device (remember call it when rebooting the IOS!) + +int mload_close() +{ +int ret; + + if(mload_fd<0) return -1; + + ret=IOS_Close(mload_fd); + + mload_fd=-1; + +return ret; +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// to get the thread id of mload + +int mload_get_thread_id() +{ +int ret; +s32 hid = -1; + + + if(mload_init()<0) return -1; + + hid = iosCreateHeap(0x800); + + if(hid<0) return hid; + + ret= IOS_IoctlvFormat(hid, mload_fd, MLOAD_MLOAD_THREAD_ID, ":"); + + + //iosDestroyHeap(hid); + +return ret; + +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// get the base and the size of the memory readable/writable to load modules + +int mload_get_load_base(u32 *starlet_base, int *size) +{ +int ret; +s32 hid = -1; + + + if(mload_init()<0) return -1; + + hid = iosCreateHeap(0x800); + + if(hid<0) return hid; + + ret= IOS_IoctlvFormat(hid, mload_fd, MLOAD_GET_LOAD_BASE, ":ii",starlet_base, size); + + + //iosDestroyHeap(hid); + +return ret; + +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// load and run a module from starlet (it need to allocate MEM2 to send the elf file) +// the module must be a elf made with stripios + +int mload_module(void *addr, int len) +{ +int ret; +void *buf=NULL; +s32 hid = -1; + + if(mload_init()<0) return -1; + + hid = iosCreateHeap(len+0x800); + + if(hid<0) return hid; + + buf= iosAlloc(hid, len); + + if(!buf) {ret= -1;goto out;} + + + memcpy(buf, addr,len); + + ret = IOS_IoctlvFormat(hid, mload_fd, MLOAD_LOAD_MODULE, ":d", buf, len); + + if(ret<0) goto out; + + ret=IOS_IoctlvFormat(hid, mload_fd, MLOAD_RUN_MODULE, ":"); + + if(ret<0) {ret= -666;goto out;} + +out: + + //iosDestroyHeap(hid); + +return ret; + +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// load a module from the PPC +// the module must be a elf made with stripios + +int mload_elf(void *my_elf, data_elf *data_elf) +{ +int n,m; +int p; +u8 *adr; +u32 elf=(u32) my_elf; + +if(elf & 3) return -1; // aligned to 4 please! + +elfheader *head=(void *) elf; +elfphentry *entries; + +if(head->ident0!=0x7F454C46) return -1; +if(head->ident1!=0x01020161) return -1; +if(head->ident2!=0x01000000) return -1; + +p=head->phoff; + +data_elf->start=(void *) head->entry; + +for(n=0; nphnum; n++) + { + entries=(void *) (elf+p); + p+=sizeof(elfphentry); + + if(entries->type == 4) + { + adr=(void *) (elf + entries->offset); + + if(getbe32(0)!=0) return -2; // bad info (sure) + + for(m=4; m < entries->memsz; m+=8) + { + switch(getbe32(m)) + { + case 0x9: + data_elf->start= (void *) getbe32(m+4); + break; + case 0x7D: + data_elf->prio= getbe32(m+4); + break; + case 0x7E: + data_elf->size_stack= getbe32(m+4); + break; + case 0x7F: + data_elf->stack= (void *) (getbe32(m+4)); + break; + + } + + } + + } + else + if(entries->type == 1 && entries->memsz != 0 && entries->vaddr!=0) + { + + if(mload_memset((void *) entries->vaddr, 0, entries->memsz)<0) return -1; + if(mload_seek(entries->vaddr, SEEK_SET)<0) return -1; + if(mload_write((void *) (elf + entries->offset), entries->filesz)<0) return -1; + + } + } + +return 0; +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// run one thread (you can use to load modules or binary files) + +int mload_run_thread(void *starlet_addr, void *starlet_top_stack, int stack_size, int priority) +{ +int ret; +s32 hid = -1; + + + if(mload_init()<0) return -1; + + hid = iosCreateHeap(0x800); + + if(hid<0) return hid; + + ret= IOS_IoctlvFormat(hid, mload_fd, MLOAD_RUN_THREAD, "iiii:", starlet_addr,starlet_top_stack, stack_size, priority); + + + //iosDestroyHeap(hid); + +return ret; +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// stops one starlet thread + +int mload_stop_thread(int id) +{ +int ret; +s32 hid = -1; + + + if(mload_init()<0) return -1; + + hid = iosCreateHeap(0x800); + + if(hid<0) return hid; + + ret= IOS_IoctlvFormat(hid, mload_fd, MLOAD_STOP_THREAD, "i:", id); + + + //iosDestroyHeap(hid); + +return ret; + +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// continue one stopped starlet thread + +int mload_continue_thread(int id) +{ +int ret; +s32 hid = -1; + + + if(mload_init()<0) return -1; + + hid = iosCreateHeap(0x800); + + if(hid<0) return hid; + + ret= IOS_IoctlvFormat(hid, mload_fd, MLOAD_CONTINUE_THREAD, "i:", id); + + + //iosDestroyHeap(hid); + +return ret; + +} +/*--------------------------------------------------------------------------------------------------------------*/ + +// fix starlet address to read/write (uses SEEK_SET, etc as mode) + +int mload_seek(int offset, int mode) +{ + if(mload_init()<0) return -1; + return IOS_Seek(mload_fd, offset, mode); +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// read bytes from starlet (it update the offset) + +int mload_read(void* buf, u32 size) +{ + if(mload_init()<0) return -1; + return IOS_Read(mload_fd, buf, size); +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// write bytes from starlet (it update the offset) + +int mload_write(const void * buf, u32 size) +{ + if(mload_init()<0) return -1; + return IOS_Write(mload_fd, buf, size); +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// fill a block (similar to memset) + +int mload_memset(void *starlet_addr, int set, int len) +{ +int ret; +s32 hid = -1; + + + if(mload_init()<0) return -1; + + hid = iosCreateHeap(0x800); + + if(hid<0) return hid; + + ret= IOS_IoctlvFormat(hid, mload_fd, MLOAD_MEMSET, "iii:", starlet_addr, set, len); + + + //iosDestroyHeap(hid); + +return ret; +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// get the ehci datas ( ehcmodule.elf uses this address) + +void * mload_get_ehci_data() +{ +int ret; +s32 hid = -1; + + + if(mload_init()<0) return NULL; + + hid = iosCreateHeap(0x800); + + if(hid<0) return NULL; + + ret= IOS_IoctlvFormat(hid, mload_fd, MLOAD_GET_EHCI_DATA, ":"); + if(ret<0) return NULL; + + //iosDestroyHeap(hid); + +return (void *) ret; +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +// set the dev/es ioctlv in routine + +int mload_set_ES_ioctlv_vector(void *starlet_addr) +{ +int ret; +s32 hid = -1; + + + if(mload_init()<0) return -1; + + hid = iosCreateHeap(0x800); + + if(hid<0) return hid; + + ret= IOS_IoctlvFormat(hid, mload_fd, MLOAD_SET_ES_IOCTLV, "i:", starlet_addr); + + + //iosDestroyHeap(hid); + +return ret; +} + diff --git a/source/mload.h b/source/mload.h new file mode 100644 index 0000000..b50eeb6 --- /dev/null +++ b/source/mload.h @@ -0,0 +1,194 @@ +/* mload.c (for PPC) (c) 2009, Hermes + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __MLOAD_H__ +#define __MLOAD_H__ + + +#include +#include +#include +#include +#include +#include "unistd.h" + +#define MLOAD_MLOAD_THREAD_ID 0x4D4C4400 +#define MLOAD_LOAD_MODULE 0x4D4C4480 +#define MLOAD_RUN_MODULE 0x4D4C4481 +#define MLOAD_RUN_THREAD 0x4D4C4482 + +#define MLOAD_STOP_THREAD 0x4D4C4484 +#define MLOAD_CONTINUE_THREAD 0x4D4C4485 + +#define MLOAD_GET_LOAD_BASE 0x4D4C4490 +#define MLOAD_MEMSET 0x4D4C4491 + +#define MLOAD_GET_EHCI_DATA 0x4D4C44A0 + +#define MLOAD_SET_ES_IOCTLV 0x4D4C44B0 + +#ifdef __cplusplus +extern "C" { +#endif + + +// from IOS ELF stripper of neimod + +#define getbe32(x) ((adr[x]<<24) | (adr[x+1]<<16) | (adr[x+2]<<8) | (adr[x+3])) + +typedef struct +{ + u32 ident0; + u32 ident1; + u32 ident2; + u32 ident3; + u32 machinetype; + u32 version; + u32 entry; + u32 phoff; + u32 shoff; + u32 flags; + u16 ehsize; + u16 phentsize; + u16 phnum; + u16 shentsize; + u16 shnum; + u16 shtrndx; +} elfheader; + +typedef struct +{ + u32 type; + u32 offset; + u32 vaddr; + u32 paddr; + u32 filesz; + u32 memsz; + u32 flags; + u32 align; +} elfphentry; + +typedef struct +{ + void *start; + int prio; + void *stack; + int size_stack; +} data_elf; + +/*--------------------------------------------------------------------------------------------------------------*/ + +// to init/test if the device is running + +int mload_init(); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// to close the device (remember call it when rebooting the IOS!) + +int mload_close(); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// to get the thread id of mload + +int mload_get_thread_id(); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// get the base and the size of the memory readable/writable to load modules + +int mload_get_load_base(u32 *starlet_base, int *size); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// load and run a module from starlet (it need to allocate MEM2 to send the elf file) +// the module must be a elf made with stripios + +int mload_module(void *addr, int len); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// load a module from the PPC +// the module must be a elf made with stripios + +int mload_elf(void *my_elf, data_elf *data_elf); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// run one thread (you can use to load modules or binary files) + +int mload_run_thread(void *starlet_addr, void *starlet_top_stack, int stack_size, int priority); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// stops one starlet thread + +int mload_stop_thread(int id); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// continue one stopped starlet thread + +int mload_continue_thread(int id); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// fix starlet address to read/write (uses SEEK_SET, etc as mode) + +int mload_seek(int offset, int mode); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// read bytes from starlet (it update the offset) + +int mload_read(void* buf, u32 size); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// write bytes from starlet (it update the offset) + +int mload_write(const void * buf, u32 size); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// fill a block (similar to memset) + +int mload_memset(void *starlet_addr, int set, int len); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// get the ehci datas ( ehcmodule.elf uses this address) + +void * mload_get_ehci_data(); + +/*--------------------------------------------------------------------------------------------------------------*/ + +// set the dev/es ioctlv in routine + +int mload_set_ES_ioctlv_vector(void *starlet_addr); + +/*--------------------------------------------------------------------------------------------------------------*/ + + + +#ifdef __cplusplus + } +#endif + + +#endif diff --git a/source/nand.c b/source/nand.c new file mode 100644 index 0000000..a5c99be --- /dev/null +++ b/source/nand.c @@ -0,0 +1,86 @@ +#include +#include + +#include "nand.h" + +/* Buffer */ +static u32 inbuf[8] ATTRIBUTE_ALIGN(32); + + +s32 Nand_Mount(nandDevice *dev) +{ + s32 fd, ret; + + /* Open FAT module */ + fd = IOS_Open("fat", 0); + if (fd < 0) + return fd; + + /* Mount device */ + ret = IOS_Ioctlv(fd, dev->mountCmd, 0, 0, NULL); + + /* Close FAT module */ + IOS_Close(fd); + + return ret; +} + +s32 Nand_Unmount(nandDevice *dev) +{ + s32 fd, ret; + + /* Open FAT module */ + fd = IOS_Open("fat", 0); + if (fd < 0) + return fd; + + /* Unmount device */ + ret = IOS_Ioctlv(fd, dev->umountCmd, 0, 0, NULL); + + /* Close FAT module */ + IOS_Close(fd); + + return ret; +} + +s32 Nand_Enable(nandDevice *dev) +{ + s32 fd, ret; + + /* Open /dev/fs */ + fd = IOS_Open("/dev/fs", 0); + if (fd < 0) + return fd; + + /* Set input buffer */ + inbuf[0] = dev->mode; + + /* Enable NAND emulator */ + ret = IOS_Ioctl(fd, 100, inbuf, sizeof(inbuf), NULL, 0); + + /* Close /dev/fs */ + IOS_Close(fd); + + return ret; +} + +s32 Nand_Disable(void) +{ + s32 fd, ret; + + /* Open /dev/fs */ + fd = IOS_Open("/dev/fs", 0); + if (fd < 0) + return fd; + + /* Set input buffer */ + inbuf[0] = 0; + + /* Disable NAND emulator */ + ret = IOS_Ioctl(fd, 100, inbuf, sizeof(inbuf), NULL, 0); + + /* Close /dev/fs */ + IOS_Close(fd); + + return ret; +} diff --git a/source/nand.h b/source/nand.h new file mode 100644 index 0000000..0b769d8 --- /dev/null +++ b/source/nand.h @@ -0,0 +1,24 @@ +#ifndef _NAND_H_ +#define _NAND_H_ + +/* 'NAND Device' structure */ +typedef struct { + /* Device name */ + char *name; + + /* Mode value */ + u32 mode; + + /* Un/mount command */ + u32 mountCmd; + u32 umountCmd; +} nandDevice; + + +/* Prototypes */ +s32 Nand_Mount(nandDevice *); +s32 Nand_Unmount(nandDevice *); +s32 Nand_Enable(nandDevice *); +s32 Nand_Disable(void); + +#endif diff --git a/source/restart.c b/source/restart.c new file mode 100644 index 0000000..db3104c --- /dev/null +++ b/source/restart.c @@ -0,0 +1,33 @@ +#include +#include + +#include "nand.h" +#include "sys.h" +#include "wpad.h" +//#include "video.h" + + +void Restart(void) +{ + printf("\n Beende den WAD-Manager..."); + fflush(stdout); + + /* Disable NAND emulator */ + Nand_Disable(); + + /* Load system menu */ + Sys_LoadMenu(); +} + +void Restart_Wait(void) +{ + printf("\n Dr%ccke eine Taste f%cr den Neustart...",129,129); + fflush(stdout); + + /* Wait for button */ + Wpad_WaitButtons(); + + /* Restart */ + Restart(); +} + diff --git a/source/restart.h b/source/restart.h new file mode 100644 index 0000000..3df94d7 --- /dev/null +++ b/source/restart.h @@ -0,0 +1,8 @@ +#ifndef _RESTART_H_ +#define _RESTART_H_ + +/* Prototypes */ +void Restart(void); +void Restart_Wait(void); + +#endif diff --git a/source/sha1.c b/source/sha1.c new file mode 100644 index 0000000..7ce9e6d --- /dev/null +++ b/source/sha1.c @@ -0,0 +1,177 @@ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd if true. */ +#define SHA1HANDSOFF + +#include +#include +#include "sha1.h" + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifdef LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +typedef struct { + unsigned long state[5]; + unsigned long count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(unsigned long state[5], unsigned char buffer[64]) +{ +unsigned long a, b, c, d, e; +typedef union { + unsigned char c[64]; + unsigned long l[16]; +} CHAR64LONG16; +CHAR64LONG16* block; +#ifdef SHA1HANDSOFF +static unsigned char workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); +#else + block = (CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int len) +{ +unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(unsigned char digest[20], SHA1_CTX* context) +{ +unsigned long i, j; +unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1Update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + SHA1Update(context, (unsigned char *)"\0", 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite it's own static vars */ + SHA1Transform(context->state, context->buffer); +#endif +} + +void SHA1(unsigned char *ptr, unsigned int size, unsigned char *outbuf) { + SHA1_CTX ctx; + + SHA1Init(&ctx); + SHA1Update(&ctx, ptr, size); + SHA1Final(outbuf, &ctx); +} diff --git a/source/sha1.h b/source/sha1.h new file mode 100644 index 0000000..35b3388 --- /dev/null +++ b/source/sha1.h @@ -0,0 +1,6 @@ +#ifndef _SHA1_H_ +#define _SHA1_H_ + +void SHA1(unsigned char *, unsigned int, unsigned char *); + +#endif diff --git a/source/stub.S b/source/stub.S new file mode 100644 index 0000000..418c838 --- /dev/null +++ b/source/stub.S @@ -0,0 +1,6 @@ + .rodata + + .globl bgData + .balign 32 +bgData: + .incbin "../data/background" diff --git a/source/sys.c b/source/sys.c new file mode 100644 index 0000000..4829eba --- /dev/null +++ b/source/sys.c @@ -0,0 +1,188 @@ +#include +#include +#include +#include + +#include "sys.h" +#include "mload.h" +#include "ehcmodule_elf.h" + +/* Constants */ +#define CERTS_LEN 0x280 + +/* Variables */ +static const char certs_fs[] ATTRIBUTE_ALIGN(32) = "/sys/cert.sys"; + +void __Sys_ResetCallback(void) +{ + /* Reboot console */ + Sys_Reboot(); +} + +void __Sys_PowerCallback(void) +{ + /* Poweroff console */ + Sys_Shutdown(); +} + +bool isIOSstub(u8 ios_number) +{ + u32 tmd_size; + tmd_view *ios_tmd; + + + if((boot2version >= 5) && ( ios_number == 202 || ios_number == 222 || ios_number == 223 || ios_number == 224)) return true; + + ES_GetTMDViewSize(0x0000000100000000ULL | ios_number, &tmd_size); + if (!tmd_size) + { + //getting size failed. invalid or fake tmd for sure! + //gprintf("failed to get tmd for ios %d\n",ios_number); + return true; + } + ios_tmd = (tmd_view *)memalign( 32, (tmd_size+31)&(~31) ); + if(!ios_tmd) + { + //gprintf("failed to mem align the TMD struct!\n"); + return true; + } + memset(ios_tmd , 0, tmd_size); + ES_GetTMDView(0x0000000100000000ULL | ios_number, (u8*)ios_tmd , tmd_size); + //gprintf("IOS %d is rev %d(0x%x) with tmd size of %u and %u contents\n",ios_number,ios_tmd->title_version,ios_tmd->title_version,tmd_size,ios_tmd->num_contents); + /*Stubs have a few things in common: + - title version : it is mostly 65280 , or even better : in hex the last 2 digits are 0. + example : IOS 60 rev 6400 = 0x1900 = 00 = stub + - exception for IOS21 which is active, the tmd size is 592 bytes (or 140 with the views) + - the stub ios' have 1 app of their own (type 0x1) and 2 shared apps (type 0x8001). + eventho the 00 check seems to work fine , we'll only use other knowledge as well cause some + people/applications install an ios with a stub rev >_> ...*/ + u8 Version = ios_tmd->title_version; + + if((boot2version >= 5) && (ios_number == 249 || ios_number == 250) && (Version < 18)) return true; + if(( ios_number == 202 || ios_number == 222 || ios_number == 223 || ios_number == 224) && (Version < 4)) return true; + //version now contains the last 2 bytes. as said above, if this is 00, its a stub + if ( Version == 0 ) + { + if ( ( ios_tmd->num_contents == 3) && (ios_tmd->contents[0].type == 1 && ios_tmd->contents[1].type == 0x8001 && ios_tmd->contents[2].type == 0x8001) ) + { + //gprintf("IOS %d is a stub\n",ios_number); + free(ios_tmd); + return true; + } + else + { + //gprintf("IOS %d is active\n",ios_number); + free(ios_tmd); + return false; + } + } + //gprintf("IOS %d is active\n",ios_number); + free(ios_tmd); + return false; +} + + +bool loadIOS(int ios) +{ + if(isIOSstub(ios)) return false; + mload_close(); + if(IOS_ReloadIOS(ios)>=0) + { + if (IOS_GetVersion() != 249 && IOS_GetVersion() != 250) + { + if (mload_init() >= 0) + { + data_elf my_data_elf; + mload_elf((void *) ehcmodule_elf, &my_data_elf); + mload_run_thread(my_data_elf.start, my_data_elf.stack, my_data_elf.size_stack, 0x47); + } + } + return true; + } + return false; +} + +void Sys_Init(void) +{ + /* Initialize video subsytem */ + VIDEO_Init(); + + /* Set RESET/POWER button callback */ + SYS_SetResetCallback(__Sys_ResetCallback); + SYS_SetPowerCallback(__Sys_PowerCallback); +} + +void Sys_Reboot(void) +{ + /* Restart console */ + STM_RebootSystem(); +} + +void Sys_Shutdown(void) +{ + /* Poweroff console */ + if(CONF_GetShutdownMode() == CONF_SHUTDOWN_IDLE) { + s32 ret; + + /* Set LED mode */ + ret = CONF_GetIdleLedMode(); + if(ret >= 0 && ret <= 2) + STM_SetLedMode(ret); + + /* Shutdown to idle */ + STM_ShutdownToIdle(); + } else { + /* Shutdown to standby */ + STM_ShutdownToStandby(); + } +} + +void Sys_LoadMenu(void) +{ + int HBC = 0; + char * sig = (char *)0x80001804; + if( + sig[0] == 'S' && + sig[1] == 'T' && + sig[2] == 'U' && + sig[3] == 'B' && + sig[4] == 'H' && + sig[5] == 'A' && + sig[6] == 'X' && + sig[7] == 'X') + HBC=1; // Exit to HBC + + + /* Homebrew Channel stub */ + if (HBC == 1) + exit(0); + + /* Return to the Wii system menu */ + SYS_ResetSystem(SYS_RETURNTOMENU, 0, 0); +} + +s32 Sys_GetCerts(signed_blob **certs, u32 *len) +{ + static signed_blob certificates[CERTS_LEN] ATTRIBUTE_ALIGN(32); + + s32 fd, ret; + + /* Open certificates file */ + fd = IOS_Open(certs_fs, 1); + if (fd < 0) + return fd; + + /* Read certificates */ + ret = IOS_Read(fd, certificates, sizeof(certificates)); + + /* Close file */ + IOS_Close(fd); + + /* Set values */ + if (ret > 0) { + *certs = certificates; + *len = sizeof(certificates); + } + + return ret; +} diff --git a/source/sys.h b/source/sys.h new file mode 100644 index 0000000..419ed68 --- /dev/null +++ b/source/sys.h @@ -0,0 +1,14 @@ +#ifndef _SYS_H_ +#define _SYS_H_ + +u32 boot2version; +/* Prototypes */ +bool isIOSstub(u8 ios_number); +bool loadIOS(int ios); +void Sys_Init(void); +void Sys_Reboot(void); +void Sys_Shutdown(void); +void Sys_LoadMenu(void); +s32 Sys_GetCerts(signed_blob **, u32 *); + +#endif diff --git a/source/title.c b/source/title.c new file mode 100644 index 0000000..76fe3a6 --- /dev/null +++ b/source/title.c @@ -0,0 +1,324 @@ +#include +#include +#include +#include +#include +#include + +#include "sha1.h" +#include "utils.h" + + +s32 Title_ZeroSignature(signed_blob *p_sig) +{ + u8 *ptr = (u8 *)p_sig; + + /* Fill signature with zeroes */ + memset(ptr + 4, 0, SIGNATURE_SIZE(p_sig) - 4); + + return 0; +} + +s32 Title_FakesignTik(signed_blob *p_tik) +{ + tik *tik_data = NULL; + u16 fill; + + /* Zero signature */ + Title_ZeroSignature(p_tik); + + /* Ticket data */ + tik_data = (tik *)SIGNATURE_PAYLOAD(p_tik); + + for (fill = 0; fill < USHRT_MAX; fill++) { + sha1 hash; + + /* Modify ticket padding field */ + tik_data->padding = fill; + + /* Calculate hash */ + SHA1((u8 *)tik_data, sizeof(tik), hash); + + /* Found valid hash */ + if (!hash[0]) + return 0; + } + + return -1; +} + +s32 Title_FakesignTMD(signed_blob *p_tmd) +{ + tmd *tmd_data = NULL; + u16 fill; + + /* Zero signature */ + Title_ZeroSignature(p_tmd); + + /* TMD data */ + tmd_data = (tmd *)SIGNATURE_PAYLOAD(p_tmd); + + for (fill = 0; fill < USHRT_MAX; fill++) { + sha1 hash; + + /* Modify TMD fill field */ + tmd_data->fill3 = fill; + + /* Calculate hash */ + SHA1((u8 *)tmd_data, TMD_SIZE(tmd_data), hash); + + /* Found valid hash */ + if (!hash[0]) + return 0; + } + + return -1; +} + +s32 Title_GetList(u64 **outbuf, u32 *outlen) +{ + u64 *titles = NULL; + + u32 len, nb_titles; + s32 ret; + + /* Get number of titles */ + ret = ES_GetNumTitles(&nb_titles); + if (ret < 0) + return ret; + + /* Calculate buffer lenght */ + len = round_up(sizeof(u64) * nb_titles, 32); + + /* Allocate memory */ + titles = memalign(32, len); + if (!titles) + return -1; + + /* Get titles */ + ret = ES_GetTitles(titles, nb_titles); + if (ret < 0) + goto err; + + /* Set values */ + *outbuf = titles; + *outlen = nb_titles; + + return 0; + +err: + /* Free memory */ + if (titles) + free(titles); + + return ret; +} + +s32 Title_GetTicketViews(u64 tid, tikview **outbuf, u32 *outlen) +{ + tikview *views = NULL; + + u32 nb_views; + s32 ret; + + /* Get number of ticket views */ + ret = ES_GetNumTicketViews(tid, &nb_views); + if (ret < 0) + return ret; + + /* Allocate memory */ + views = (tikview *)memalign(32, sizeof(tikview) * nb_views); + if (!views) + return -1; + + /* Get ticket views */ + ret = ES_GetTicketViews(tid, views, nb_views); + if (ret < 0) + goto err; + + /* Set values */ + *outbuf = views; + *outlen = nb_views; + + return 0; + +err: + /* Free memory */ + if (views) + free(views); + + return ret; +} + +s32 Title_GetTMD(u64 tid, signed_blob **outbuf, u32 *outlen) +{ + void *p_tmd = NULL; + + u32 len; + s32 ret; + + /* Get TMD size */ + ret = ES_GetStoredTMDSize(tid, &len); + if (ret < 0) + return ret; + + /* Allocate memory */ + p_tmd = memalign(32, round_up(len, 32)); + if (!p_tmd) + return -1; + + /* Read TMD */ + ret = ES_GetStoredTMD(tid, p_tmd, len); + if (ret < 0) + goto err; + + /* Set values */ + *outbuf = p_tmd; + *outlen = len; + + return 0; + +err: + /* Free memory */ + if (p_tmd) + free(p_tmd); + + return ret; +} + +s32 Title_GetVersion(u64 tid, u16 *outbuf) +{ + signed_blob *p_tmd = NULL; + tmd *tmd_data = NULL; + + u32 len; + s32 ret; + + /* Get title TMD */ + ret = Title_GetTMD(tid, &p_tmd, &len); + if (ret < 0) + return ret; + + /* Retrieve TMD info */ + tmd_data = (tmd *)SIGNATURE_PAYLOAD(p_tmd); + + /* Set values */ + *outbuf = tmd_data->title_version; + + /* Free memory */ + free(p_tmd); + + return 0; +} + +s32 Title_GetSysVersion(u64 tid, u64 *outbuf) +{ + signed_blob *p_tmd = NULL; + tmd *tmd_data = NULL; + + u32 len; + s32 ret; + + /* Get title TMD */ + ret = Title_GetTMD(tid, &p_tmd, &len); + if (ret < 0) + return ret; + + /* Retrieve TMD info */ + tmd_data = (tmd *)SIGNATURE_PAYLOAD(p_tmd); + + /* Set values */ + *outbuf = tmd_data->sys_version; + + /* Free memory */ + free(p_tmd); + + return 0; +} + +s32 Title_GetSize(u64 tid, u32 *outbuf) +{ + signed_blob *p_tmd = NULL; + tmd *tmd_data = NULL; + + u32 cnt, len, size = 0; + s32 ret; + + /* Get title TMD */ + ret = Title_GetTMD(tid, &p_tmd, &len); + if (ret < 0) + return ret; + + /* Retrieve TMD info */ + tmd_data = (tmd *)SIGNATURE_PAYLOAD(p_tmd); + + /* Calculate title size */ + for (cnt = 0; cnt < tmd_data->num_contents; cnt++) { + tmd_content *content = &tmd_data->contents[cnt]; + + /* Add content size */ + size += content->size; + } + + /* Set values */ + *outbuf = size; + + /* Free memory */ + free(p_tmd); + + return 0; +} + +s32 Title_GetIOSVersions(u8 **outbuf, u32 *outlen) +{ + u8 *buffer = NULL; + u64 *list = NULL; + + u32 count, cnt, idx; + s32 ret; + + /* Get title list */ + ret = Title_GetList(&list, &count); + if (ret < 0) + return ret; + + /* Count IOS */ + for (cnt = idx = 0; idx < count; idx++) { + u32 tidh = (list[idx] >> 32); + u32 tidl = (list[idx] & 0xFFFFFFFF); + + /* Title is IOS */ + if ((tidh == 0x1) && (tidl >= 3) && (tidl <= 255)) + cnt++; + } + + /* Allocate memory */ + buffer = (u8 *)memalign(32, cnt); + if (!buffer) { + ret = -1; + goto out; + } + + /* Copy IOS */ + for (cnt = idx = 0; idx < count; idx++) { + u32 tidh = (list[idx] >> 32); + u32 tidl = (list[idx] & 0xFFFFFFFF); + + /* Title is IOS */ + if ((tidh == 0x1) && (tidl >= 3) && (tidl <= 255)) + buffer[cnt++] = (u8)(tidl & 0xFF); + } + + /* Set values */ + *outbuf = buffer; + *outlen = cnt; + + goto out; + +out: + /* Free memory */ + if (list) + free(list); + + return ret; +} diff --git a/source/title.h b/source/title.h new file mode 100644 index 0000000..4faba41 --- /dev/null +++ b/source/title.h @@ -0,0 +1,19 @@ +#ifndef _TITLE_H_ +#define _TITLE_H_ + +/* Constants */ +#define BLOCK_SIZE 1024 + +/* Prototypes */ +s32 Title_ZeroSignature(signed_blob *); +s32 Title_FakesignTik(signed_blob *); +s32 Title_FakesignTMD(signed_blob *); +s32 Title_GetList(u64 **, u32 *); +s32 Title_GetTicketViews(u64, tikview **, u32 *); +s32 Title_GetTMD(u64, signed_blob **, u32 *); +s32 Title_GetVersion(u64, u16 *); +s32 Title_GetSysVersion(u64, u64 *); +s32 Title_GetSize(u64, u32 *); +s32 Title_GetIOSVersions(u8 **, u32 *); + +#endif diff --git a/source/usbstorage.c b/source/usbstorage.c new file mode 100644 index 0000000..56e7f60 --- /dev/null +++ b/source/usbstorage.c @@ -0,0 +1,400 @@ +/*------------------------------------------------------------- + +usbstorage_starlet.c -- USB mass storage support, inside starlet +Copyright (C) 2009 Kwiirk + +If this driver is linked before libogc, this will replace the original +usbstorage driver by svpe from libogc +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you +must not claim that you wrote the original software. If you use +this software in a product, an acknowledgment in the product +documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. + +-------------------------------------------------------------*/ + +#include +#include +#include +#include + +/* IOCTL commands */ +#define UMS_BASE (('U'<<24)|('M'<<16)|('S'<<8)) +#define USB_IOCTL_UMS_INIT (UMS_BASE+0x1) +#define USB_IOCTL_UMS_GET_CAPACITY (UMS_BASE+0x2) +#define USB_IOCTL_UMS_READ_SECTORS (UMS_BASE+0x3) +#define USB_IOCTL_UMS_WRITE_SECTORS (UMS_BASE+0x4) +#define USB_IOCTL_UMS_READ_STRESS (UMS_BASE+0x5) +#define USB_IOCTL_UMS_SET_VERBOSE (UMS_BASE+0x6) +#define USB_IOCTL_UMS_UNMOUNT (UMS_BASE+0x10) +#define USB_IOCTL_UMS_WATCHDOG (UMS_BASE+0x80) + +#define WBFS_BASE (('W'<<24)|('F'<<16)|('S'<<8)) +#define USB_IOCTL_WBFS_OPEN_DISC (WBFS_BASE+0x1) +#define USB_IOCTL_WBFS_READ_DISC (WBFS_BASE+0x2) +#define USB_IOCTL_WBFS_READ_DEBUG (WBFS_BASE+0x3) +#define USB_IOCTL_WBFS_SET_DEVICE (WBFS_BASE+0x4) +#define USB_IOCTL_WBFS_SET_FRAGLIST (WBFS_BASE+0x5) + +#define UMS_HEAPSIZE 0x1000 + +/* Variables */ +static char fs[] ATTRIBUTE_ALIGN(32) = "/dev/usb2"; +static char fs2[] ATTRIBUTE_ALIGN(32) = "/dev/usb/ehc"; +static char fs3[] ATTRIBUTE_ALIGN(32) = "/dev/usb/usb123"; + +static s32 hid = -1, fd = -1; +static u32 sector_size; + +s32 USBStorage_GetCapacity(u32 *_sector_size) { + if (fd > 0) { + s32 ret; + + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_UMS_GET_CAPACITY, ":i", §or_size); + + if (ret && _sector_size) + *_sector_size = sector_size; + + return ret; + } + + return IPC_ENOENT; +} + +s32 USBStorage_Init(void) { + s32 ret; + + /* Already open */ + if (fd > 0) + return 0; + + /* Create heap */ + if (hid < 0) { + hid = iosCreateHeap(UMS_HEAPSIZE); + if (hid < 0) + return IPC_ENOMEM; + } + + /* Open USB device */ + fd = IOS_Open(fs, 0); + if (fd < 0) + fd = IOS_Open(fs2, 0); + if (fd < 0) + fd = IOS_Open(fs3, 0); + if (fd < 0) + return fd; + + /* Initialize USB storage */ + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_UMS_INIT, ":"); + if (ret<0) goto err; + + /* Get device capacity */ + ret = USBStorage_GetCapacity(NULL); + if (!ret) + goto err; + + return 0; + +err: + /* Close USB device */ + if (fd > 0) { + IOS_Close(fd); + fd = -1; + } + + return -1; +} + +/** Hermes **/ +s32 USBStorage_Watchdog(u32 on_off) { + if (fd >= 0) { + s32 ret; + + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_UMS_WATCHDOG, "i:", on_off); + + return ret; + } + + return IPC_ENOENT; +} + +s32 USBStorage_Umount(void) { + if (fd >= 0) { + s32 ret; + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_UMS_UNMOUNT, ":"); + return ret; + } + + return IPC_ENOENT; +} + +void USBStorage_Deinit(void) { + /* Close USB device */ + if (fd > 0) { + IOS_Close(fd); + fd = -1; + } +} + +s32 USBStorage_ReadSectors(u32 sector, u32 numSectors, void *buffer) { + +// void *buf = (void *)buffer; + u32 len = (sector_size * numSectors); + + s32 ret; + + /* Device not opened */ + if (fd < 0) + return fd; + + + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_UMS_READ_SECTORS, "ii:d", sector, numSectors, buffer, len); + return ret; +} + +s32 USBStorage_WriteSectors(u32 sector, u32 numSectors, const void *buffer) { + u32 len = (sector_size * numSectors); + + s32 ret; + + /* Device not opened */ + if (fd < 0) + return fd; + + /* Write data */ + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_UMS_WRITE_SECTORS, "ii:d", sector, numSectors, buffer, len); + + return ret; +} + +static bool __io_usb_Startup(void) +{ + return USBStorage_Init() >= 0; +} + +static bool __io_usb_IsInserted(void) +{ + s32 ret; + if (fd < 0) return false; + ret = USBStorage_GetCapacity(NULL); + if (ret == 0) return false; + return true; +} + +bool __io_usb_ReadSectors(u32 sector, u32 count, void *buffer) +{ + s32 ret = USBStorage_ReadSectors(sector, count, buffer); + return ret > 0; +} + +bool __io_usb_WriteSectors(u32 sector, u32 count, void *buffer) +{ + s32 ret = USBStorage_WriteSectors(sector, count, buffer); + return ret > 0; +} + +static bool __io_usb_ClearStatus(void) +{ + return true; +} + +static bool __io_usb_Shutdown(void) +{ + // do nothing + return true; +} + +static bool __io_usb_NOP(void) +{ + // do nothing + return true; +} + +const DISC_INTERFACE __io_usbstorage_ro = { + DEVICE_TYPE_WII_USB, + FEATURE_MEDIUM_CANREAD | FEATURE_WII_USB, + (FN_MEDIUM_STARTUP) &__io_usb_Startup, + (FN_MEDIUM_ISINSERTED) &__io_usb_IsInserted, + (FN_MEDIUM_READSECTORS) &__io_usb_ReadSectors, + (FN_MEDIUM_WRITESECTORS) &__io_usb_NOP, //&__io_usb_WriteSectors, + (FN_MEDIUM_CLEARSTATUS) &__io_usb_ClearStatus, + (FN_MEDIUM_SHUTDOWN) &__io_usb_Shutdown +}; + +s32 USBStorage_WBFS_Open(char *buffer) +{ + u32 len = 8; + + s32 ret; + + /* Device not opened */ + if (fd < 0) + return fd; + + extern u32 wbfs_part_lba; + u32 part = wbfs_part_lba; + + /* Read data */ + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_WBFS_OPEN_DISC, "dd:", buffer, len, &part, 4); + + return ret; +} + +// woffset is in 32bit words, len is in bytes +s32 USBStorage_WBFS_Read(u32 woffset, u32 len, void *buffer) +{ + s32 ret; + + USBStorage_Init(); + /* Device not opened */ + if (fd < 0) + return fd; + + /* Read data */ + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_WBFS_READ_DISC, "ii:d", woffset, len, buffer, len); + + return ret; +} + + +s32 USBStorage_WBFS_ReadDebug(u32 off, u32 size, void *buffer) +{ + s32 ret; + + USBStorage_Init(); + /* Device not opened */ + if (fd < 0) + return fd; + + /* Read data */ + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_WBFS_READ_DEBUG, "ii:d", off, size, buffer, size); + + return ret; +} + + +s32 USBStorage_WBFS_SetDevice(int dev) +{ + s32 ret; + static s32 retval = 0; + retval = 0; + USBStorage_Init(); + // Device not opened + if (fd < 0) return fd; + // ioctl + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_WBFS_SET_DEVICE, "i:i", dev, &retval); + if (retval) return retval; + return ret; +} + +s32 USBStorage_WBFS_SetFragList(void *p, int size) +{ + s32 ret; + USBStorage_Init(); + // Device not opened + if (fd < 0) return fd; + // ioctl + ret = IOS_IoctlvFormat(hid, fd, USB_IOCTL_WBFS_SET_FRAGLIST, "d:", p, size); + return ret; +} + +#define DEVICE_TYPE_WII_UMS (('W'<<24)|('U'<<16)|('M'<<8)|'S') + +bool umsio_Startup() { + return USBStorage_Init() == 0; +} + +bool umsio_IsInserted() { + return true; // allways true +} + +bool umsio_ReadSectors(sec_t sector, sec_t numSectors, u8 *buffer) { + u32 cnt = 0; + s32 ret; + /* Do reads */ + while (cnt < numSectors) { + u32 sectors = (numSectors - cnt); + + /* Read sectors is too big */ + if (sectors > 32) + sectors = 32; + + /* USB read */ + ret = USBStorage_ReadSectors(sector + cnt, sectors, &buffer[cnt*512]); + if (ret < 0) + return false; + + /* Increment counter */ + cnt += sectors; + } + + return true; +} + +bool umsio_WriteSectors(sec_t sector, sec_t numSectors, const u8* buffer) { + u32 cnt = 0; + s32 ret; + + /* Do writes */ + while (cnt < numSectors) { + u32 sectors = (numSectors - cnt); + + /* Write sectors is too big */ + if (sectors > 32) + sectors = 32; + + /* USB write */ + ret = USBStorage_WriteSectors(sector + cnt, sectors, &buffer[cnt * 512]); + if (ret < 0) + return false; + + /* Increment counter */ + cnt += sectors; + } + + return true; +} + +bool umsio_ClearStatus(void) { + return true; +} + +bool umsio_Shutdown() { + USBStorage_Deinit(); + return true; +} + +const DISC_INTERFACE __io_wiiums = { + DEVICE_TYPE_WII_UMS, + FEATURE_MEDIUM_CANREAD | FEATURE_MEDIUM_CANWRITE | FEATURE_WII_USB, + (FN_MEDIUM_STARTUP) &umsio_Startup, + (FN_MEDIUM_ISINSERTED) &umsio_IsInserted, + (FN_MEDIUM_READSECTORS) &umsio_ReadSectors, + (FN_MEDIUM_WRITESECTORS) &umsio_WriteSectors, + (FN_MEDIUM_CLEARSTATUS) &umsio_ClearStatus, + (FN_MEDIUM_SHUTDOWN) &umsio_Shutdown +}; + +const DISC_INTERFACE __io_wiiums_ro = { + DEVICE_TYPE_WII_UMS, + FEATURE_MEDIUM_CANREAD | FEATURE_MEDIUM_CANWRITE | FEATURE_WII_USB, + (FN_MEDIUM_STARTUP) &umsio_Startup, + (FN_MEDIUM_ISINSERTED) &umsio_IsInserted, + (FN_MEDIUM_READSECTORS) &umsio_ReadSectors, + (FN_MEDIUM_WRITESECTORS) &__io_usb_NOP, + (FN_MEDIUM_CLEARSTATUS) &umsio_ClearStatus, + (FN_MEDIUM_SHUTDOWN) &umsio_Shutdown +}; diff --git a/source/usbstorage.h b/source/usbstorage.h new file mode 100644 index 0000000..47df60a --- /dev/null +++ b/source/usbstorage.h @@ -0,0 +1,27 @@ +#ifndef _USBSTORAGE_H_ +#define _USBSTORAGE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + /* Prototypes */ + s32 USBStorage_GetCapacity(u32 *); + s32 USBStorage_Init(void); + void USBStorage_Deinit(void); + s32 USBStorage_Watchdog(u32 on_off); + s32 USBStorage_ReadSectors(u32, u32, void *); + s32 USBStorage_WriteSectors(u32, u32, const void *); + + s32 USBStorage_WBFS_Open(char *buf_id); + s32 USBStorage_WBFS_Read(u32 woffset, u32 len, void *buffer); + s32 USBStorage_WBFS_ReadDebug(u32 off, u32 size, void *buffer); + s32 USBStorage_WBFS_SetDevice(int dev); + s32 USBStorage_WBFS_SetFragList(void *p, int size); + + extern const DISC_INTERFACE __io_wiiums; + extern const DISC_INTERFACE __io_wiiums_ro; +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/utils.h b/source/utils.h new file mode 100644 index 0000000..3a4862b --- /dev/null +++ b/source/utils.h @@ -0,0 +1,15 @@ +#ifndef _UTILS_H_ +#define _UTILS_H_ + +/* Constants */ +#define KB_SIZE 1024.0 +#define MB_SIZE 1048576.0 +#define GB_SIZE 1073741824.0 + +/* Macros */ +#define round_up(x,n) (-(-(x) & -(n))) + +/* Prototypes */ +u32 swap32(u32); + +#endif diff --git a/source/video.c b/source/video.c new file mode 100644 index 0000000..0182671 --- /dev/null +++ b/source/video.c @@ -0,0 +1,141 @@ +#include +#include + +#include "sys.h" +#include "video.h" + +/* Video variables */ +static void *framebuffer = NULL; +static GXRModeObj *vmode = NULL; + + +void Con_Init(u32 x, u32 y, u32 w, u32 h) +{ + /* Create console in the framebuffer */ + CON_InitEx(vmode, x, y, w, h); +} + +void Con_Clear(void) +{ + /* Clear console */ + printf("\x1b[2J"); + fflush(stdout); +} + +void Con_ClearLine(void) +{ + s32 cols, rows; + u32 cnt; + + printf("\r"); + fflush(stdout); + + /* Get console metrics */ + CON_GetMetrics(&cols, &rows); + + /* Erase line */ + for (cnt = 1; cnt < cols; cnt++) { + printf(" "); + fflush(stdout); + } + + printf("\r"); + fflush(stdout); +} + +void Con_FgColor(u32 color, u8 bold) +{ + /* Set foreground color */ + printf("\x1b[%u;%um", color + 30, bold); + fflush(stdout); +} + +void Con_BgColor(u32 color, u8 bold) +{ + /* Set background color */ + printf("\x1b[%u;%um", color + 40, bold); + fflush(stdout); +} + +void Con_FillRow(u32 row, u32 color, u8 bold) +{ + s32 cols, rows; + u32 cnt; + + /* Set color */ + printf("\x1b[%u;%um", color + 40, bold); + fflush(stdout); + + /* Get console metrics */ + CON_GetMetrics(&cols, &rows); + + /* Save current row and col */ + printf("\x1b[s"); + fflush(stdout); + + /* Move to specified row */ + printf("\x1b[%u;0H", row); + fflush(stdout); + + /* Fill row */ + for (cnt = 0; cnt < cols; cnt++) { + printf(" "); + fflush(stdout); + } + + /* Load saved row and col */ + printf("\x1b[u"); + fflush(stdout); + + /* Set default color */ + Con_BgColor(0, 0); + Con_FgColor(7, 1); +} + +void Video_Configure(GXRModeObj *rmode) +{ + /* Configure the video subsystem */ + VIDEO_Configure(rmode); + + /* Setup video */ + VIDEO_SetBlack(FALSE); + VIDEO_Flush(); + VIDEO_WaitVSync(); + + if (rmode->viTVMode & VI_NON_INTERLACE) + VIDEO_WaitVSync(); +} + +void Video_SetMode(void) +{ + /* Select preferred video mode */ + vmode = VIDEO_GetPreferredMode(NULL); + + /* Allocate memory for the framebuffer */ + framebuffer = MEM_K0_TO_K1(SYS_AllocateFramebuffer(vmode)); + + /* Configure the video subsystem */ + VIDEO_Configure(vmode); + + /* Setup video */ + VIDEO_SetNextFramebuffer(framebuffer); + VIDEO_SetBlack(FALSE); + VIDEO_Flush(); + VIDEO_WaitVSync(); + + if (vmode->viTVMode & VI_NON_INTERLACE) + VIDEO_WaitVSync(); + + /* Clear the screen */ + Video_Clear(COLOR_BLACK); +} + +void Video_Clear(s32 color) +{ + VIDEO_ClearFrameBuffer(vmode, framebuffer, color); +} + +void Video_DrawPng(IMGCTX ctx, PNGUPROP imgProp, u16 x, u16 y) +{ + PNGU_DECODE_TO_COORDS_YCbYCr(ctx, x, y, imgProp.imgWidth, imgProp.imgHeight, vmode->fbWidth, vmode->xfbHeight, framebuffer); +} diff --git a/source/video.h b/source/video.h new file mode 100644 index 0000000..e0fb7fd --- /dev/null +++ b/source/video.h @@ -0,0 +1,19 @@ +#ifndef _VIDEO_H_ +#define _VIDEO_H_ + +#include "libpng/pngu/pngu.h" + +/* Prototypes */ +void Con_Init(u32, u32, u32, u32); +void Con_Clear(void); +void Con_ClearLine(void); +void Con_FgColor(u32, u8); +void Con_BgColor(u32, u8); +void Con_FillRow(u32, u32, u8); + +void Video_Configure(GXRModeObj *); +void Video_SetMode(void); +void Video_Clear(s32); +void Video_DrawPng(IMGCTX, PNGUPROP, u16, u16); + +#endif diff --git a/source/wad-manager.c b/source/wad-manager.c new file mode 100644 index 0000000..e155786 --- /dev/null +++ b/source/wad-manager.c @@ -0,0 +1,494 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys.h" +#include "gui.h" +#include "menu.h" +#include "restart.h" +#include "sys.h" +#include "video.h" +#include "wpad.h" +#include "fat.h" +#include "nand.h" +#include "globals.h" +#include "xyzzy.h" + + +// Globals +CONFIG gConfig; + +// Prototypes +extern u32 WaitButtons (void); +void CheckPassword (void); +void SetDefaultConfig (void); +int ReadConfigFile (char *configFilePath); +int GetIntParam (char *inputStr); +int GetStartupPath (char *startupPath, char *inputStr); +int GetStringParam (char *outParam, char *inputStr, int maxChars); + +// Default password Up-Down-Left-Right-Up-Down +//#define PASSWORD "UDLRUD" +void CheckPassword (void) +{ + char curPassword [11]; // Max 10 characters password, NULL terminated + int count = 0; + + if (strlen (gConfig.password) == 0) + return; + + // Ask user for a password. Press "B" to restart Wii + printf("[+] [Kennwort eingeben, um fortzufahren]:\n\n"); + + printf(">> Dr%ccke (A) zum Fortfahren.\n",129); + printf(">> Dr%ccke [B] zum Beenden.\n", 129); + + /* Wait for user answer */ + for (;;) + { + u32 buttons = WaitButtons(); + + if (buttons & WPAD_BUTTON_A) + { + // A button, validate the pw + curPassword [count] = 0; + //if (strcmp (curPassword, PASSWORD) == 0) + if (strcmp (curPassword, gConfig.password) == 0) + { + printf(">> Kennwort akzeptiert...\n"); + break; + } + else + { + printf ("\n"); + printf(">> Falsches Kennwort. Versuchen Sie es erneut...\n"); + printf("[+] [Kennwort eingeben, um fortzufahren]:\n\n"); + printf(">> Dr%ccke (A) zum Fortfahren.\n",129); + printf(">> Dr%ccke [B] zum Beenden.\n", 129); + count = 0; + } + } + else if (buttons & WPAD_BUTTON_B) + // B button, restart + Restart(); + else + { + if (count < 10) + { + // Other buttons, build the password + if (buttons & WPAD_BUTTON_LEFT) + { + curPassword [count++] = 'L'; + printf ("*"); + } + else if (buttons & WPAD_BUTTON_RIGHT) + { + curPassword [count++] = 'R'; + printf ("*"); + } + else if (buttons & WPAD_BUTTON_UP) + { + curPassword [count++] = 'U'; + printf ("*"); + } + else if (buttons & WPAD_BUTTON_DOWN) + { + curPassword [count++] = 'D'; + printf ("*"); + } + else if (buttons & WPAD_BUTTON_1) + { + curPassword [count++] = '1'; + printf ("*"); + } + else if (buttons & WPAD_BUTTON_2) + { + curPassword [count++] = '2'; + printf ("*"); + } + } + } + } +} + +void Disclaimer(void) +{ + /* Print disclaimer */ + printf("[+] [DISCLAIMER]:\n\n"); + + printf(" THIS APPLICATION COMES WITH NO WARRANTY AT ALL,\n"); + printf(" NEITHER EXPRESS NOR IMPLIED.\n"); + printf(" I DO NOT TAKE ANY RESPONSIBILITY FOR ANY DAMAGE IN YOUR\n"); + printf(" WII CONSOLE BECAUSE OF A IMPROPER USAGE OF THIS SOFTWARE.\n\n"); + printf(" Die Autoren dieses Programms %cbernehmen keine Haftung\n",129); + printf(" f%cr Sch%cden, die durch die Benutzung hervorgerufen werden.\n",129,132); + printf(" Wenn deine Konsole brickt, tr%cgst du alleine die Verantwortung\n",132); + printf(" daf%cr.\n\n",129); + + printf(">> Dr%ccke (A) zum Fortfahren .\n", 129); + printf(">> Andernfalls, dr%ccke [B] f%cr einen Neustart.\n",129,129); + + /* Wait for user answer */ + for (;;) { + // u32 buttons = Wpad_WaitButtons(); + u32 buttons = WaitButtons(); + + /* A button */ + if (buttons & WPAD_BUTTON_A) + break; + + /* B button */ + if (buttons & WPAD_BUTTON_B) + Restart(); + } +} + +void farbe(void) +{ + /* Print disclaimer */ + Con_BgColor(0, 1); + printf(" \n"); + Con_BgColor(0, 0); + printf(" \n"); + Con_BgColor(1, 1); + printf(" \n"); + Con_BgColor(1, 0); + printf(" \n"); + Con_BgColor(2, 1); + printf(" \n"); + Con_BgColor(2, 0); + printf(" \n"); + Con_BgColor(3, 1); + printf(" \n"); + Con_BgColor(3, 0); + printf(" \n"); + Con_BgColor(4, 1); + printf(" \n"); + Con_BgColor(4, 0); + printf(" \n"); + Con_BgColor(5, 1); + printf(" \n"); + Con_BgColor(5, 0); + printf(" \n"); + Con_BgColor(6, 1); + printf(" \n"); + Con_BgColor(6, 0); + printf(" \n"); + Con_BgColor(7, 1); + printf(" \n"); + Con_BgColor(7, 0); + printf(" \n"); + + + + /* Wait for user answer */ + for (;;) { + // u32 buttons = Wpad_WaitButtons(); + u32 buttons = WaitButtons(); + + /* A button */ + if (buttons & WPAD_BUTTON_HOME) + exit(0); + + } +} + +int main(int argc, char **argv) +{ + ES_GetBoot2Version(&boot2version); +/* + if(boot2version < 5) + { + if(!loadIOS(202)) if(!loadIOS(222)) if(!loadIOS(223)) if(!loadIOS(224)) if(!loadIOS(249)) loadIOS(36); + }else{ + if(!loadIOS(249)) loadIOS(36); + } +*/ /* Initialize subsystems */ + Sys_Init(); + + /* Set video mode */ + Video_SetMode(); + + /* Initialize console */ + Gui_InitConsole(); + + /* Draw background */ + Gui_DrawBackground(); + + if (have_ahbprot()) { + fatDevice *fdev = &fdevList[0]; + Fat_Unmount(fdev); + + IosPatch_RUNTIME(true, true, true, true); + + Fat_Mount(fdev); + keydump(); + Fat_Unmount(fdev); + + sleep(2); + Con_Clear(); + } + + /* Initialize Wiimote and GC Controller */ + Wpad_Init(); + PAD_Init (); + WIILIGHT_Init(); + + /* Print disclaimer */ +// farbe(); + Disclaimer(); + + // Set the defaults + SetDefaultConfig (); + + // Read the config file + ReadConfigFile (WM_CONFIG_FILE_PATH); + + // Check password + CheckPassword (); + + /* Menu loop */ + Menu_Loop(); + + /* Restart Wii */ + Restart_Wait(); + + return 0; +} + + +int ReadConfigFile (char *configFilePath) +{ + int retval = 0; + FILE *fptr; + char *tmpStr = malloc (MAX_FILE_PATH_LEN); + char tmpOutStr [40], path[128]; + int i; + + if (tmpStr == NULL) + return (-1); + + fatDevice *fdev = &fdevList[0]; + s32 ret = Fat_Mount(fdev); + snprintf(path, sizeof(path), "%s%s", fdev->mount, configFilePath); + + if (ret < 0) + { + fdev = &fdevList[2]; + ret = Fat_Mount(fdev); + snprintf(path, sizeof(path), "%s%s", fdev->mount, configFilePath); + snprintf(path, sizeof(path), "%s%s", fdev->mount, configFilePath); + } + + if (ret < 0) + { + printf(" FEHLER! (ret = %d)\n", ret); + // goto err; + retval = -1; + } + else + { + // Read the file + fptr = fopen (path, "rb"); + if (fptr != NULL) + { + // Read the options + char done = 0; + + while (!done) + { + if (fgets (tmpStr, MAX_FILE_PATH_LEN, fptr) == NULL) + done = 1; + else if (isalpha(tmpStr[0])) + { + // Get the password + if (strncmp (tmpStr, "Password", 8) == 0) + { + // Get password + // GetPassword (gConfig.password, tmpStr); + GetStringParam (gConfig.password, tmpStr, MAX_PASSWORD_LENGTH); + + // If password is too long, ignore it + if (strlen (gConfig.password) > 10) + { + gConfig.password [0] = 0; + printf ("Kennwort länger als 10 Zeichen werden ignoriert. Drücken Sie eine Taste...\n"); + WaitButtons (); + } + } + + // Get startup path + else if (strncmp (tmpStr, "StartupPath", 11) == 0) + { + // Get startup Path + GetStartupPath (gConfig.startupPath, tmpStr); + } + + // cIOS + else if (strncmp (tmpStr, "cIOSVersion", 11) == 0) + { + // Get cIOSVersion + gConfig.cIOSVersion = (u8)GetIntParam (tmpStr); + } + + // FatDevice + else if (strncmp (tmpStr, "FatDevice", 9) == 0) + { + // Get fatDevice + GetStringParam (tmpOutStr, tmpStr, MAX_FAT_DEVICE_LENGTH); + for (i = 0; i < 5; i++) + { + if (strncmp (fdevList[i].mount, tmpOutStr, 4) == 0) + { + gConfig.fatDeviceIndex = i; + } + } + } + + // NandDevice + else if (strncmp (tmpStr, "NANDDevice", 10) == 0) + { + // Get fatDevice + GetStringParam (tmpOutStr, tmpStr, MAX_NAND_DEVICE_LENGTH); + for (i = 0; i < 3; i++) + { + if (strncmp (ndevList[i].name, tmpOutStr, 2) == 0) + { + gConfig.nandDeviceIndex = i; + } + } + } + } + } // EndWhile + + // Close the config file + fclose (fptr); + } + else + { + // If the wm_config.txt file is not found, just take the default config params + //printf ("Config file is not found\n"); // This is for testing only + //WaitButtons(); + } + Fat_Unmount(fdev); + } + + // Free memory + free (tmpStr); + + return (retval); +} // ReadConfig + + +void SetDefaultConfig (void) +{ + // Default password is NULL or no password + gConfig.password [0] = 0; + + // Default startup folder + strcpy (gConfig.startupPath, WAD_DIRECTORY); + + gConfig.cIOSVersion = CIOS_VERSION_INVALID; // Means that user has to select later + gConfig.fatDeviceIndex = FAT_DEVICE_INDEX_INVALID; // Means that user has to select + gConfig.nandDeviceIndex = NAND_DEVICE_INDEX_INVALID; // Means that user has to select + +} // SetDefaultConfig + + +int GetStartupPath (char *startupPath, char *inputStr) +{ + int i = 0; + int len = strlen (inputStr); + + // Find the "=" + while ((inputStr [i] != '=') && (i < len)) + { + i++; + } + i++; + + // Get to the "/" + while ((inputStr [i] != '/') && (i < len)) + { + i++; + } + + // Get the startup Path + int count = 0; + while (isascii(inputStr [i]) && (i < len) && (inputStr [i] != '\n') && + (inputStr [i] != '\r') && (inputStr [i] != ' ')) + { + startupPath [count++] = inputStr [i++]; + } + startupPath [count] = 0; // NULL terminate + + return (0); +} // GetStartupPath + +int GetIntParam (char *inputStr) +{ + int retval = 0; + int i = 0; + int len = strlen (inputStr); + char outParam [40]; + + // Find the "=" + while ((inputStr [i] != '=') && (i < len)) + { + i++; + } + i++; + + // Get to the first alpha numeric character + while ((isdigit(inputStr [i]) == 0) && (i < len)) + { + i++; + } + + // Get the string param + int outCount = 0; + while ((isdigit(inputStr [i])) && (i < len) && (outCount < 40)) + { + outParam [outCount++] = inputStr [i++]; + } + outParam [outCount] = 0; // NULL terminate + retval = atoi (outParam); + + return (retval); +} // GetIntParam + + +int GetStringParam (char *outParam, char *inputStr, int maxChars) +{ + int i = 0; + int len = strlen (inputStr); + + // Find the "=" + while ((inputStr [i] != '=') && (i < len)) + { + i++; + } + i++; + + // Get to the first alpha character + while ((isalpha(inputStr [i]) == 0) && (i < len)) + { + i++; + } + + // Get the string param + int outCount = 0; + while ((isalnum(inputStr [i])) && (i < len) && (outCount < maxChars)) + { + outParam [outCount++] = inputStr [i++]; + } + outParam [outCount] = 0; // NULL terminate + + return (0); +} // GetStringParam diff --git a/source/wad.c b/source/wad.c new file mode 100644 index 0000000..2728e40 --- /dev/null +++ b/source/wad.c @@ -0,0 +1,797 @@ +#include +#include +#include +#include +#include + +#include "sys.h" +#include "title.h" +#include "utils.h" +#include "video.h" +#include "wad.h" +#include "wpad.h" + +// Turn upper and lower into a full title ID +#define TITLE_ID(x,y) (((u64)(x) << 32) | (y)) +// Get upper or lower half of a title ID +#define TITLE_UPPER(x) ((u32)((x) >> 32)) +#define TITLE_LOWER(x) ((u32)(x)) + +typedef struct { + int version; + int region; + +} SMRegion; + +SMRegion regionlist[] = { + {33, 'X'}, + {128, 'J'}, {97, 'E'}, {130, 'P'}, + {162, 'P'}, + {192, 'J'}, {193, 'E'}, {194, 'P'}, + {224, 'J'}, {225, 'E'}, {226, 'P'}, + {256, 'J'}, {257, 'E'}, {258, 'P'}, + {288, 'J'}, {289, 'E'}, {290, 'P'}, + {352, 'J'}, {353, 'E'}, {354, 'P'}, {326, 'K'}, + {384, 'J'}, {385, 'E'}, {386, 'P'}, + {390, 'K'}, + {416, 'J'}, {417, 'E'}, {418, 'P'}, + {448, 'J'}, {449, 'E'}, {450, 'P'}, {454, 'K'}, + {480, 'J'}, {481, 'E'}, {482, 'P'}, {486, 'K'}, + {512, 'E'}, {513, 'E'}, {514, 'P'}, {518, 'K'}, +}; + +#define NB_SM (sizeof(regionlist) / sizeof(SMRegion)) + +static u8 wadBuffer[BLOCK_SIZE] ATTRIBUTE_ALIGN(32); + +u32 WaitButtons(void); + +u32 be32(const u8 *p) +{ + return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; +} + +u64 be64(const u8 *p) +{ + return ((u64)be32(p) << 32) | be32(p + 4); +} + +u64 get_title_ios(u64 title) { + s32 ret, fd; + static char filepath[256] ATTRIBUTE_ALIGN(32); + + // Check to see if title exists + if (ES_GetDataDir(title, filepath) >= 0 ) { + u32 tmd_size; + static u8 tmd_buf[MAX_SIGNED_TMD_SIZE] ATTRIBUTE_ALIGN(32); + + ret = ES_GetStoredTMDSize(title, &tmd_size); + if (ret < 0){ + // If we fail to use the ES function, try reading manually + // This is a workaround added since some IOS (like 21) don't like our + // call to ES_GetStoredTMDSize + + //printf("Error! ES_GetStoredTMDSize: %d\n", ret); + + sprintf(filepath, "/title/%08x/%08x/content/title.tmd", TITLE_UPPER(title), TITLE_LOWER(title)); + + ret = ISFS_Open(filepath, ISFS_OPEN_READ); + if (ret <= 0) + { + //printf("Error! ISFS_Open (ret = %d)\n", ret); + return 0; + } + + fd = ret; + + ret = ISFS_Seek(fd, 0x184, 0); + if (ret < 0) + { + //printf("Error! ISFS_Seek (ret = %d)\n", ret); + return 0; + } + + ret = ISFS_Read(fd,tmd_buf,8); + if (ret < 0) + { + //printf("Error! ISFS_Read (ret = %d)\n", ret); + return 0; + } + + ret = ISFS_Close(fd); + if (ret < 0) + { + //printf("Error! ISFS_Close (ret = %d)\n", ret); + return 0; + } + + return be64(tmd_buf); + + } else { + // Normal versions of IOS won't have a problem, so we do things the "right" way. + + // Some of this code adapted from bushing's title_lister.c + signed_blob *s_tmd = (signed_blob *)tmd_buf; + ret = ES_GetStoredTMD(title, s_tmd, tmd_size); + if (ret < 0){ + //printf("Error! ES_GetStoredTMD: %d\n", ret); + return -1; + } + tmd *t = SIGNATURE_PAYLOAD(s_tmd); + return t->sys_version; + } + + + } + return 0; +} + +int get_sm_region_basic() +{ + u32 tmd_size; + + u64 title = TITLE_ID(1, 2); + static u8 tmd_buf[MAX_SIGNED_TMD_SIZE] ATTRIBUTE_ALIGN(32); + + int ret = ES_GetStoredTMDSize(title, &tmd_size); + + // Some of this code adapted from bushing's title_lister.c + signed_blob *s_tmd = (signed_blob *)tmd_buf; + ret = ES_GetStoredTMD(title, s_tmd, tmd_size); + if (ret < 0){ + //printf("Error! ES_GetStoredTMD: %d\n", ret); + return -1; + } + tmd *t = SIGNATURE_PAYLOAD(s_tmd); + ret = t->title_version; + int i = 0; + while( i <= NB_SM) + { + if( regionlist[i].version == ret) return regionlist[i].region; + i++; + } + return 0; +} + +/* 'WAD Header' structure */ +typedef struct { + /* Header length */ + u32 header_len; + + /* WAD type */ + u16 type; + + /* Padding */ + u16 padding; + + /* Data length */ + u32 certs_len; + u32 crl_len; + u32 tik_len; + u32 tmd_len; + u32 data_len; + u32 footer_len; +} ATTRIBUTE_PACKED wadHeader; + +/* Variables */ +static u8 wadBuffer[BLOCK_SIZE] ATTRIBUTE_ALIGN(32); + + +s32 __Wad_ReadFile(FILE *fp, void *outbuf, u32 offset, u32 len) +{ + s32 ret; + + /* Seek to offset */ + fseek(fp, offset, SEEK_SET); + + /* Read data */ + ret = fread(outbuf, len, 1, fp); + if (ret < 0) + return ret; + + return 0; +} + +s32 __Wad_ReadAlloc(FILE *fp, void **outbuf, u32 offset, u32 len) +{ + void *buffer = NULL; + s32 ret; + + /* Allocate memory */ + buffer = memalign(32, len); + if (!buffer) + return -1; + + /* Read file */ + ret = __Wad_ReadFile(fp, buffer, offset, len); + if (ret < 0) { + free(buffer); + return ret; + } + + /* Set pointer */ + *outbuf = buffer; + + return 0; +} + +s32 __Wad_GetTitleID(FILE *fp, wadHeader *header, u64 *tid) +{ + signed_blob *p_tik = NULL; + tik *tik_data = NULL; + + u32 offset = 0; + s32 ret; + + /* Ticket offset */ + offset += round_up(header->header_len, 64); + offset += round_up(header->certs_len, 64); + offset += round_up(header->crl_len, 64); + + /* Read ticket */ + ret = __Wad_ReadAlloc(fp, (void *)&p_tik, offset, header->tik_len); + if (ret < 0) + goto out; + + /* Ticket data */ + tik_data = (tik *)SIGNATURE_PAYLOAD(p_tik); + + /* Copy title ID */ + *tid = tik_data->titleid; + +out: + /* Free memory */ + if (p_tik) + free(p_tik); + + return ret; +} + +void __Wad_FixTicket(signed_blob *p_tik) +{ + u8 *data = (u8 *)p_tik; + u8 *ckey = data + 0x1F1; + + /* Check common key */ + if (*ckey > 1) + *ckey = 0; + + /* Fakesign ticket */ + Title_FakesignTik(p_tik); +} + +s32 Wad_Install(FILE *fp, fatFile *file) +{ + wadHeader *header = NULL; + signed_blob *p_certs = NULL, *p_crl = NULL, *p_tik = NULL, *p_tmd = NULL; + + tmd *tmd_data = NULL; + + u32 cnt, offset = 0; + s32 ret; + u64 tid; + + printf("\t\t>> Lese WAD-Daten..."); + fflush(stdout); + + ret = __Wad_ReadAlloc(fp, (void *)&header, offset, sizeof(wadHeader)); + if (ret >= 0) + offset += round_up(header->header_len, 64); + else + goto err; + + //Don't try to install boot2 + __Wad_GetTitleID(fp, header, &tid); + + if (tid == TITLE_ID(1, 1)) + { + printf("\n Das kann ich nicht zulassen\n"); + ret = -999; + goto out; + } + + /* WAD certificates */ + ret = __Wad_ReadAlloc(fp, (void *)&p_certs, offset, header->certs_len); + if (ret >= 0) + offset += round_up(header->certs_len, 64); + else + goto err; + + /* WAD crl */ + if (header->crl_len) { + ret = __Wad_ReadAlloc(fp, (void *)&p_crl, offset, header->crl_len); + if (ret < 0) + goto err; + else + offset += round_up(header->crl_len, 64); + } + + /* WAD ticket */ + ret = __Wad_ReadAlloc(fp, (void *)&p_tik, offset, header->tik_len); + if (ret < 0) + goto err; + else + offset += round_up(header->tik_len, 64); + + /* WAD TMD */ + ret = __Wad_ReadAlloc(fp, (void *)&p_tmd, offset, header->tmd_len); + if (ret < 0) + goto err; + else + offset += round_up(header->tmd_len, 64); + + Con_ClearLine(); + + /* Get TMD info */ + + tmd_data = (tmd *)SIGNATURE_PAYLOAD(p_tmd); + + if(TITLE_LOWER(tmd_data->sys_version) != NULL && isIOSstub(TITLE_LOWER(tmd_data->sys_version))) + { + printf("\n Diese Wad braucht IOS%i aber die installierte version\n ist ein stub.\n", TITLE_LOWER(tmd_data->sys_version)); + ret = -999; + goto err; + } + + if(get_title_ios(TITLE_ID(1, 2)) == tid) + { + if ( ( tmd_data->num_contents == 3) && (tmd_data->contents[0].type == 1 && tmd_data->contents[1].type == 0x8001 && tmd_data->contents[2].type == 0x8001) ) + { + printf("\n Ich installiere kein Stub f%cr das Systemmen%c\n", 129, 129); + ret = -999; + goto err; + } + } + + if(tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | 'E')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | 'P')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | 'J')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | 'K'))) + { + if ( ( tmd_data->num_contents == 3) && (tmd_data->contents[0].type == 1 && tmd_data->contents[1].type == 0x8001 && tmd_data->contents[2].type == 0x8001) ) + { + printf("\n Ich installiere kein Stub f%cr die EULA\n", 129); + ret = -999; + goto err; + } + } + + if(tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | 'E')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | 'P')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | 'J')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | 'K'))) + { + if ( ( tmd_data->num_contents == 3) && (tmd_data->contents[0].type == 1 && tmd_data->contents[1].type == 0x8001 && tmd_data->contents[2].type == 0x8001) ) + { + printf("\n Ich installiere kein Stub f%cr rgsel IOS\n", 129); + ret = -999; + goto err; + } + } + if (tid == get_title_ios(TITLE_ID(0x10001, 0x48415858)) || tid == get_title_ios(TITLE_ID(0x10001, 0x4A4F4449))) + { + if ( ( tmd_data->num_contents == 3) && (tmd_data->contents[0].type == 1 && tmd_data->contents[1].type == 0x8001 && tmd_data->contents[2].type == 0x8001) ) + { + printf("\n Sind Sie sicher, ein Stub vom HBC zu installieren?\n"); + printf("\n Dr%ccke (A) zum Fortfahren.\n",129); + printf(" Dr%ccke [B] zum %cberspringen.", 129, 154); + + u32 buttons = WaitButtons(); + + if (!(buttons & WPAD_BUTTON_A)) + { + ret = -998; + goto err; + } + } + } + + if (tid == TITLE_ID(1, 2)) + { + if(get_sm_region_basic() == 0) + { + printf("\n Kann die Region vom Systemmen%c nicht ermitteln\n", 129); + printf("\n Dr%ccke (A) zum Fortfahren.\n",129); + printf(" Dr%ccke [B] zum %cberspringen.", 129, 154); + + u32 buttons = WaitButtons(); + + if (!(buttons & WPAD_BUTTON_A)) + { + ret = -999; + goto err; + } + } + int i, ret = -1; + for(i = 0; i <= NB_SM; i++) + { + if( regionlist[i].version == tmd_data->title_version) + { + ret = 1; + break; + } + } + if(ret -1) + { + printf("\n Kann die Region vom Systemmen%c nicht ermitteln\n", 129); + printf("\n Dr%ccke (A) zum Fortfahren.\n",129); + printf(" Dr%ccke [B] zum %cberspringen.", 129, 154); + + u32 buttons = WaitButtons(); + + if (!(buttons & WPAD_BUTTON_A)) + { + ret = -999; + goto err; + } + } + if( get_sm_region_basic() != regionlist[i].region) + { + printf("\n Falsche Regionen f%crs Systemmen%c\n", 129, 129); + printf("\n Dr%ccke (A) zum Fortfahren.\n",129); + printf(" Dr%ccke [B] zum %cberspringen.", 129, 154); + + u32 buttons = WaitButtons(); + + if (!(buttons & WPAD_BUTTON_A)) + { + ret = -999; + goto err; + } + } + if(tmd_data->title_version < 416) + { + if(boot2version == 4) + { + printf("\n Diese Version des Systemmen%cs\n ist nicht kompatibel mit Ihrer Wii\n", 129); + ret = -999; + goto err; + } + } + } + + /* Fix ticket */ + __Wad_FixTicket(p_tik); + + printf("\t\t>> Installiere Ticket..."); + fflush(stdout); + + /* Install ticket */ + ret = ES_AddTicket(p_tik, header->tik_len, p_certs, header->certs_len, p_crl, header->crl_len); + if (ret < 0) + goto err; + + Con_ClearLine(); + + printf("\r\t\t>> Installiere Title..."); + fflush(stdout); + + /* Install title */ + ret = ES_AddTitleStart(p_tmd, header->tmd_len, p_certs, header->certs_len, p_crl, header->crl_len); + if (ret < 0) + goto err; + + /* Install contents */ + for (cnt = 0; cnt < tmd_data->num_contents; cnt++) { + tmd_content *content = &tmd_data->contents[cnt]; + + u32 idx = 0, len; + s32 cfd; + + Con_ClearLine(); + + printf("\r\t\t>> Installiere Inhalt #%02d...", content->cid); + fflush(stdout); + + /* Encrypted content size */ + len = round_up(content->size, 64); + + /* Install content */ + cfd = ES_AddContentStart(tmd_data->title_id, content->cid); + if (cfd < 0) { + ret = cfd; + goto err; + } + + /* Install content data */ + while (idx < len) { + u32 size; + + /* Data length */ + size = (len - idx); + if (size > BLOCK_SIZE) + size = BLOCK_SIZE; + + /* Read data */ + ret = __Wad_ReadFile(fp, &wadBuffer, offset, size); + if (ret < 0) + goto err; + + /* Install data */ + ret = ES_AddContentData(cfd, wadBuffer, size); + if (ret < 0) + goto err; + + /* Increase variables */ + idx += size; + offset += size; + } + + /* Finish content installation */ + ret = ES_AddContentFinish(cfd); + if (ret < 0) + goto err; + } + + Con_ClearLine(); + + printf("\r\t\t>> Beende Installation..."); + fflush(stdout); + + /* Finish title install */ + ret = ES_AddTitleFinish(); + if (ret >= 0) + { + if(file->old_ios != file->new_ios || file->old_ahbprot != file->new_ahbprot || file->old_pass != file->new_pass) + { + Con_ClearLine(); + + printf("\r\t\t>> Patche TMD"); + fflush(stdout); + + ISFS_Initialize(); + + s32 fd; + + char filepath[ISFS_MAXPATH]; + sprintf(filepath, "/title/%08x/%08x/content/title.tmd", file->high_id, file->low_id); + + static fstats filestats ATTRIBUTE_ALIGN(32); + static u8 filearray[1024] ATTRIBUTE_ALIGN(32); + + fd = ISFS_Open(filepath, ISFS_OPEN_READ); + if (fd <= 0) + ISFS_Close(fd); + + ISFS_GetFileStats(fd, &filestats); + ISFS_Read(fd, filearray, filestats.file_length); + ISFS_Close(fd); + + if(filestats.file_length >= 0) + { + fd = ISFS_Open(filepath, ISFS_OPEN_RW); + + filearray[395] = file->new_ios; + if(file->new_ahbprot) + { + filearray[472] = 0; + filearray[473] = 0; + filearray[474] = 0; + filearray[475] = 3; + } + else + { + filearray[472] = 0; + filearray[473] = 0; + filearray[474] = 0; + filearray[475] = 0; + } + if(!file->new_pass) + filearray[417] = 0; + else + filearray[417] = 16; + + ISFS_Write(fd, filearray, sizeof( filearray )); + ISFS_Close(fd); + } + + ISFS_Deinitialize(); + } + + printf(" OK!\n"); + goto out; + } + +err: + printf(" FEHLER! (ret = %d)\n", ret); + + /* Cancel install */ + ES_AddTitleCancel(); + +out: + /* Free memory */ + if (header) + free(header); + if (p_certs) + free(p_certs); + if (p_crl) + free(p_crl); + if (p_tik) + free(p_tik); + if (p_tmd) + free(p_tmd); + + return ret; +} + +s32 Wad_Uninstall(FILE *fp) +{ + wadHeader *header = NULL; + tikview *viewData = NULL; + + u64 tid; + u32 viewCnt; + s32 ret; + + printf("\t\t>> Reading WAD data..."); + fflush(stdout); + + /* WAD header */ + ret = __Wad_ReadAlloc(fp, (void *)&header, 0, sizeof(wadHeader)); + if (ret < 0) { + printf(" ERROR! (ret = %d)\n", ret); + goto out; + } + + /* Get title ID */ + ret = __Wad_GetTitleID(fp, header, &tid); + if (ret < 0) { + printf(" ERROR! (ret = %d)\n", ret); + goto out; + } + //Assorted Checks + if (TITLE_UPPER(tid) == 1 && get_title_ios(TITLE_ID(1, 2)) == 0) + { + printf("\n I can't determine the System Menus IOS\nDeleting system titles is disabled\n"); + ret = -999; + goto out; + } + if (tid == TITLE_ID(1, 1)) + { + printf("\n I won't try to uninstall boot2\n"); + ret = -999; + goto out; + } + if (tid == TITLE_ID(1, 2)) + { + printf("\n I won't uninstall the System Menu\n"); + ret = -999; + goto out; + } + if(get_title_ios(TITLE_ID(1, 2)) == tid) + { + printf("\n I won't uninstall the System Menus IOS\n"); + ret = -999; + goto out; + } + if (tid == get_title_ios(TITLE_ID(0x10001, 0x48415858)) || tid == get_title_ios(TITLE_ID(0x10001, 0x4A4F4449))) + { + printf("\n This is the HBCs IOS, uninstalling will break the HBC!\n"); + printf("\n Press A to continue.\n"); + printf(" Press B skip."); + + u32 buttons = WaitButtons(); + + if (!(buttons & WPAD_BUTTON_A)) + { + ret = -998; + goto out; + } + } + if((tid == TITLE_ID(0x10008, 0x48414B00 | 'E') || tid == TITLE_ID(0x10008, 0x48414B00 | 'P') || tid == TITLE_ID(0x10008, 0x48414B00 | 'J') || tid == TITLE_ID(0x10008, 0x48414B00 | 'K') + || (tid == TITLE_ID(0x10008, 0x48414C00 | 'E') || tid == TITLE_ID(0x10008, 0x48414C00 | 'P') || tid == TITLE_ID(0x10008, 0x48414C00 | 'J') || tid == TITLE_ID(0x10008, 0x48414C00 | 'K'))) && get_sm_region_basic() == 0) + { + printf("\n Can't get the SM region\n Please check the site for updates\n"); + ret = -999; + goto out; + } + if(tid == TITLE_ID(0x10008, 0x48414B00 | get_sm_region_basic())) + { + printf("\n I won't uninstall the EULA\n"); + ret = -999; + goto out; + } + if(tid == TITLE_ID(0x10008, 0x48414C00 | get_sm_region_basic())) + { + printf("\n I won't uninstall rgsel\n"); + ret = -999; + goto out; + } + if(tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | get_sm_region_basic()))) + { + printf("\n I won't uninstall the EULAs IOS\n"); + ret = -999; + goto out; + } + if(tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | get_sm_region_basic()))) + { + printf("\n I won't uninstall the rgsel IOS\n"); + ret = -999; + goto out; + } + + Con_ClearLine(); + + printf("\t\t>> Deleting tickets..."); + fflush(stdout); + + /* Get ticket views */ + ret = Title_GetTicketViews(tid, &viewData, &viewCnt); + if (ret < 0) + printf(" ERROR! (ret = %d)\n", ret); + + /* Delete tickets */ + if (ret >= 0) { + u32 cnt; + + /* Delete all tickets */ + for (cnt = 0; cnt < viewCnt; cnt++) { + ret = ES_DeleteTicket(&viewData[cnt]); + if (ret < 0) + break; + } + + if (ret < 0) + printf(" ERROR! (ret = %d\n", ret); + else + printf(" OK!\n"); + } + + printf("\t\t>> Deleting title contents..."); + fflush(stdout); + + /* Delete title contents */ + ret = ES_DeleteTitleContent(tid); + if (ret < 0) + printf(" ERROR! (ret = %d)\n", ret); + else + printf(" OK!\n"); + + + printf("\t\t>> Deleting title..."); + fflush(stdout); + + /* Delete title */ + ret = ES_DeleteTitle(tid); + if (ret < 0) + printf(" ERROR! (ret = %d)\n", ret); + else + printf(" OK!\n"); + +out: + /* Free memory */ + if (header) + free(header); + return ret; +} + +void Wad_Read(FILE *fp, int *ios, bool *ahbprot, bool *pass, u32 *high_id, u32 *low_id) +{ + wadHeader *header = NULL; + + /* WAD header */ + __Wad_ReadAlloc(fp, (void *)&header, 0, sizeof(wadHeader)); + __Wad_ReadFile(fp, &wadBuffer, 3328, header->tmd_len); + + + *ios = wadBuffer[395]; + if(ios > 0) + { + if(wadBuffer[472] == 0 && wadBuffer[473] == 0 && wadBuffer[474] == 0 && wadBuffer[475] == 3) + *ahbprot = true; + else + *ahbprot = false; + + if(wadBuffer[417] < 16) + *pass = false; + else + *pass = true; + + signed_blob *p_tmd = NULL; + + __Wad_ReadAlloc(fp, (void *)&p_tmd, 3328, header->tmd_len); + tmd *tmd_data = (tmd *)SIGNATURE_PAYLOAD(p_tmd); + + *high_id = TITLE_UPPER(tmd_data->title_id); + *low_id = TITLE_LOWER(tmd_data->title_id); + + if (p_tmd) + free(p_tmd); + } + if (header) + free(header); +} diff --git a/source/wad.h b/source/wad.h new file mode 100644 index 0000000..4f4ca74 --- /dev/null +++ b/source/wad.h @@ -0,0 +1,9 @@ +#ifndef _WAD_H_ +#define _WAD_H_ +#include "fat.h" + +/* Prototypes */ +s32 Wad_Install(FILE *, fatFile *file); +s32 Wad_Uninstall(FILE *); +void Wad_Read(FILE *, int *ios, bool *ahbprot, bool *pass, u32 *high_id, u32 *low_id); +#endif diff --git a/source/wkb.c b/source/wkb.c new file mode 100644 index 0000000..f04ed68 --- /dev/null +++ b/source/wkb.c @@ -0,0 +1,47 @@ +#include "wkb.h" + +/* + +s32 USBKeyboard_Open(const eventcallback cb); +void USBKeyboard_Close(void); + +bool USBKeyboard_IsConnected(void); +s32 USBKeyboard_Scan(void); + +s32 USBKeyboard_SetLed(const USBKeyboard_led led, bool on); +s32 USBKeyboard_ToggleLed(const USBKeyboard_led led); +*/ + +s32 WkbInit(void) +{ + s32 retval = 0; + + retval = USBKeyboard_Initialize(); + + return (retval); + +} // WkbInit + +s32 WkbDeInit(void) +{ + s32 retval = 0; + + retval = USBKeyboard_Deinitialize(); + + return (retval); + +} // WkbDeInit + +u32 WkbWaitKey (void) +{ + u32 retval = 0; + + // Stub + return (retval); + +} // WkbWaitKey + +//void Wpad_Disconnect(void); +//u32 Wpad_GetButtons(void); + + diff --git a/source/wkb.h b/source/wkb.h new file mode 100644 index 0000000..568572f --- /dev/null +++ b/source/wkb.h @@ -0,0 +1,19 @@ +#ifndef _WKB_H_ +#define _WKB_H_ + +//#include +//#include +//#include +//#include +#include // u8, u16, etc... + +#include +#include + +/* Prototypes */ +s32 WkbInit(void); +u32 WkbWaitKey (void); +//void Wpad_Disconnect(void); +//u32 Wpad_GetButtons(void); + +#endif diff --git a/source/wpad.c b/source/wpad.c new file mode 100644 index 0000000..2d60dfe --- /dev/null +++ b/source/wpad.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include + +#include "sys.h" +#include "wpad.h" +//#include "wkb.h" + +/* Constants */ +#define MAX_WIIMOTES 4 + +int start; + +void __Wpad_PowerCallback(s32 chan) +{ + /* Poweroff console */ + Sys_Shutdown(); +} + + +s32 Wpad_Init(void) +{ + s32 ret; + + /* Initialize Wiimote subsystem */ + ret = WPAD_Init(); + if (ret < 0) + return ret; + + /* Set POWER button callback */ + WPAD_SetPowerButtonCallback(__Wpad_PowerCallback); + + return ret; +} + +void Wpad_Disconnect(void) +{ + u32 cnt; + + /* Disconnect Wiimotes */ + for (cnt = 0; cnt < MAX_WIIMOTES; cnt++) + WPAD_Disconnect(cnt); + + /* Shutdown Wiimote subsystem */ + WPAD_Shutdown(); +} + +u32 Wpad_GetButtons(void) +{ + u32 buttons = 0, cnt; + + /* Scan pads */ + WPAD_ScanPads(); + + /* Get pressed buttons */ + for (cnt = 0; cnt < MAX_WIIMOTES; cnt++) + buttons |= WPAD_ButtonsDown(cnt); + + return buttons; +} + +u32 Wpad_WaitButtons(void) +{ + u32 buttons = 0; + /* Wait for button pressing */ + while (!buttons) { + buttons = Wpad_GetButtons(); + VIDEO_WaitVSync(); + } + + return buttons; +} + +u32 Wpad_HeldButtons(void) +{ + u32 buttons = 0, cnt; + + /* Scan pads */ + WPAD_ScanPads(); + + /* Get pressed buttons */ + for (cnt = 0; cnt < MAX_WIIMOTES; cnt++) + buttons |= WPAD_ButtonsHeld(cnt); + + return buttons; +} + +bool Wpad_TimeButton(void) +{ + u32 buttons = 1; + + time_t start,end; + time (&start); + int dif; + /* Wait for button pressing */ + while (buttons) { + buttons = Wpad_HeldButtons(); + VIDEO_WaitVSync(); + time (&end); + dif = difftime (end,start); + if(dif>=2) return true; + } + return false; +} \ No newline at end of file diff --git a/source/wpad.h b/source/wpad.h new file mode 100644 index 0000000..913973d --- /dev/null +++ b/source/wpad.h @@ -0,0 +1,13 @@ +#ifndef _WPAD_H_ +#define _WPAD_H_ + +#include + +/* Prototypes */ +s32 Wpad_Init(void); +void Wpad_Disconnect(void); +u32 Wpad_GetButtons(void); +u32 Wpad_WaitButtons(void); +bool Wpad_TimeButton(void); + +#endif diff --git a/source/xyzzy.c b/source/xyzzy.c new file mode 100644 index 0000000..5ff3d09 --- /dev/null +++ b/source/xyzzy.c @@ -0,0 +1,173 @@ +/* xyzzy -- keydumper for Wii + + Copyright (C) 2008 bushing + Copyright (C) 2011 R2-D2199 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, version 2. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hollywood.h" +#include "gpio.h" +#include "xyzzy.h" + +#define eeprom_delay() udelay(5) +#define HAVE_AHBPROT ((*(vu32*)0xcd800064 == 0xFFFFFFFF) ? 1 : 0) + +keys *keys_bin; +void udelay(int us); + + + +static inline void set32(u32 addr, u32 set) +{ + write32(addr, read32(addr) | set); +} + +static inline void clear32(u32 addr, u32 clear) +{ + write32(addr, read32(addr)&(~clear)); +} + +void send_bits(int b, int bits) +{ + while(bits--) + { + if(b & (1 << bits)) + set32(HW_GPIO1OUT, GP_EEP_MOSI); + else + clear32(HW_GPIO1OUT, GP_EEP_MOSI); + eeprom_delay(); + set32(HW_GPIO1OUT, GP_EEP_CLK); + eeprom_delay(); + clear32(HW_GPIO1OUT, GP_EEP_CLK); + eeprom_delay(); + } +} + +int recv_bits(int bits) +{ + int res = 0; + while(bits--) + { + res <<= 1; + set32(HW_GPIO1OUT, GP_EEP_CLK); + eeprom_delay(); + clear32(HW_GPIO1OUT, GP_EEP_CLK); + eeprom_delay(); + res |= !!(read32(HW_GPIO1IN) & GP_EEP_MISO); + } + return res; +} + +int seeprom_read(void *dst, int offset, int size) +{ + int i; + u16 *ptr = (u16 *)dst; + u16 recv; + + if(size & 1) + return -1; + + clear32(HW_GPIO1OUT, GP_EEP_CLK); + clear32(HW_GPIO1OUT, GP_EEP_CS); + eeprom_delay(); + + for(i = 0; i < size; ++i) + { + set32(HW_GPIO1OUT, GP_EEP_CS); + send_bits((0x600 | (offset + i)), 11); + recv = recv_bits(16); + *ptr++ = recv; + clear32(HW_GPIO1OUT, GP_EEP_CS); + eeprom_delay(); + } + + return size; +} + +void crypto_read_otp(void) +{ + otp_t *otpd = memalign(32, sizeof(otp_t)); + u32 buffer[0x20]; + int i; + for (i=0; i< 0x20; i++) { + write32(HW_OTPCMD,0x80000000|i); + buffer[i] = read32(HW_OTPDATA); + } + memcpy(otpd, buffer, sizeof(otp_t)); + memcpy(keys_bin->boot1Hash, otpd->boot1_hash, sizeof(otpd->boot1_hash)); + memcpy(keys_bin->commonKey, otpd->common_key, sizeof(otpd->common_key)); + keys_bin->consoleID = otpd->ng_id; + snprintf(keys_bin->text, sizeof(keys_bin->text), "YAWMM_DE, ConsoleID: %08x", otpd->ng_id); + memset(keys_bin->text+29, 0, sizeof(keys_bin->text)-30); + memcpy(keys_bin->privateKey, otpd->ng_priv, sizeof(otpd->ng_priv)); + memcpy(keys_bin->nandHmac, otpd->nand_hmac, sizeof(otpd->nand_hmac)); + memcpy(keys_bin->nandKey, otpd->nand_key, sizeof(otpd->nand_key)); + memcpy(keys_bin->rngKey, otpd->rng_key, sizeof(otpd->rng_key)); + memcpy(keys_bin->nandKey, otpd->nand_key, sizeof(otpd->nand_key)); + keys_bin->unk1 = otpd->unk1; + keys_bin->unk2 = otpd->unk2; + free(otpd); +} + +void seeprom_read_seeprom(void) { + u32 buffer[sizeof(seeprom_t) / 4]; + seeprom_t *seeprom = memalign(32, sizeof(seeprom_t)); + seeprom_read(&buffer, 0, sizeof(seeprom_t) / 2); + memcpy(seeprom, buffer, sizeof(seeprom_t)); + + memset(keys_bin->blank, 0, 136); + keys_bin->ngKeyID = seeprom->ng_key_id; + memcpy(keys_bin->ngSig, seeprom->ng_sig, sizeof(seeprom->ng_sig)); + memset(keys_bin->blank2, 0, 440); + free(seeprom); +} + +int keydump() { + static char buf[128]; + snprintf(buf, 128, "sd:/keys.bin"); + FILE *fp = NULL; + fp = fopen(buf, "r"); + if (fp) { + fclose(fp); + return 0; + } + printf(" Dumpe keys.bin"); + keys_bin = memalign(32, sizeof(keys)); + printf("."); + crypto_read_otp(); + printf("."); + seeprom_read_seeprom(); + printf("."); + + fp = fopen(buf, "wb"); + printf("."); + if (fp) + { + fwrite((u8 *)keys_bin, 1, sizeof(keys), fp); + fclose(fp); + } + free(keys_bin); + printf("OK\n"); + return 0; +} diff --git a/source/xyzzy.h b/source/xyzzy.h new file mode 100644 index 0000000..23b7839 --- /dev/null +++ b/source/xyzzy.h @@ -0,0 +1,74 @@ +typedef struct +{ + u8 boot1_hash[20]; + u8 common_key[16]; + u32 ng_id; + union { + struct { + u8 ng_priv[30]; + u8 _wtf1[18]; + }; + struct { + u8 _wtf2[28]; + u8 nand_hmac[20]; + }; + }; + u8 nand_key[16]; + u8 rng_key[16]; + u32 unk1; + u32 unk2; // 0x00000007 +} __attribute__((packed)) otp_t; + +typedef struct +{ + u8 boot2version; + u8 unknown1; + u8 unknown2; + u8 pad; + u32 update_tag; + u16 checksum; +} __attribute__((packed)) eep_ctr_t; + +typedef struct +{ + union { + struct { + u32 ms_id; + u32 ca_id; + u32 ng_key_id; + u8 ng_sig[60]; + eep_ctr_t counters[2]; + u8 fill[0x18]; + u8 korean_key[16]; + }; + u8 data[256]; + }; +} __attribute__((packed)) seeprom_t; + +typedef struct { + char text[0x100]; + u8 boot1Hash[20]; + u8 commonKey[16]; + u32 consoleID; + union { + struct { + u8 privateKey[30]; + u8 _wtf1[18]; + }; + struct { + u8 _wtf2[28]; + u8 nandHmac[20]; + }; + }; + u8 nandKey[16]; + u8 rngKey[16]; + u32 unk1; + u32 unk2; // 0x00000007 + char blank[136]; + u32 ngKeyID; + u8 ngSig[60]; + char blank2[440]; + +} keys; + +int keydump(); \ No newline at end of file diff --git a/wm_config.txt b/wm_config.txt new file mode 100644 index 0000000..f169aa0 --- /dev/null +++ b/wm_config.txt @@ -0,0 +1,25 @@ +;Config file format +; +;No spaces precedes the keyword on a line +; +Password= + +; StartupPath: +StartupPath=/wad + +; cIOS: 249, 222, whatever +:cIOSVersion=249 + +; FatDevice: sd usb usb2 gcsda gcsdb +:FatDevice=sd + +; NANDDevice: Disable SD USB +; Note that WM will prompt for NAND device only if you selected cIOS=249 +:NANDDevice=Disable + +: Settings for SMB shares + +:SMBUser= +:SMBPassword= +:SMBShare= +:SMBhostIP= \ No newline at end of file