From e2708863036066c2cc8bad1fc142e90fb8a0464d Mon Sep 17 00:00:00 2001 From: FIX94 Date: Mon, 16 Oct 2017 11:55:29 +0200 Subject: [PATCH] first version --- Makefile | 147 ++++ README.md | 9 +- README_YAWMM.txt | 172 +++++ apps/some-yawmm-mod/icon.png | Bin 0 -> 4423 bytes apps/some-yawmm-mod/meta.xml | 19 + data/background | Bin 0 -> 32537 bytes data/ehcmodule.elf | Bin 0 -> 25614 bytes include/wiilight.h | 7 + lib/libwiilight.a | Bin 0 -> 8420 bytes source/fat.c | 58 ++ source/fat.h | 46 ++ source/globals.h | 58 ++ source/gui.c | 90 +++ source/gui.h | 8 + source/iospatch.c | 116 +++ source/iospatch.h | 32 + source/libpng/pngu/pngu.c | 1132 ++++++++++++++++++++++++++++ source/libpng/pngu/pngu.h | 171 +++++ source/menu.c | 1382 ++++++++++++++++++++++++++++++++++ source/menu.h | 8 + source/mload.c | 381 ++++++++++ source/mload.h | 194 +++++ source/nand.c | 86 +++ source/nand.h | 24 + source/restart.c | 36 + 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 | 426 +++++++++++ source/wad.c | 677 +++++++++++++++++ source/wad.h | 8 + source/wkb.c | 47 ++ source/wkb.h | 19 + source/wpad.c | 105 +++ source/wpad.h | 13 + wm_config.txt | 25 + 46 files changed, 6839 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100644 README_YAWMM.txt create mode 100644 apps/some-yawmm-mod/icon.png create mode 100644 apps/some-yawmm-mod/meta.xml create mode 100644 data/background create mode 100644 data/ehcmodule.elf create mode 100644 include/wiilight.h create mode 100644 lib/libwiilight.a create mode 100644 source/fat.c create mode 100644 source/fat.h create mode 100644 source/globals.h create mode 100644 source/gui.c create mode 100644 source/gui.h create mode 100644 source/iospatch.c create mode 100644 source/iospatch.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 wm_config.txt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..abf1940 --- /dev/null +++ b/Makefile @@ -0,0 +1,147 @@ +#--------------------------------------------------------------------------------- +# 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 include source/libtinysmb source/libpng source/libpng/pngu +DATA := data +INCLUDES := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- + +CFLAGS = -Os -Wall $(MACHDEP) $(INCLUDE) +CXXFLAGS = $(CFLAGS) + +LDFLAGS = $(MACHDEP) -Wl,-Map,$(notdir $@).map + +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -ltinysmb -lpng -lfat -lwiidrc -lwiiuse -lbte -logc -lm -lz -lwiilight + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CURDIR) $(PORTLIBS) + +#--------------------------------------------------------------------------------- +# 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) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + diff --git a/README.md b/README.md index 1d2e4cc..3677af9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # Some-YAWMM-Mod -yet another mod of yet another mod manager mod +Based on YAWMM which itself is based on WAD Manager, modded up by various people. +Changes from the last YAWMM googlecode version: +-updated to be compiled in the latest devkitppc, libogc versions +-added on-the-fly IOS patches when AHBPROT is disabled, so no cIOS is required in those cases +-support for classic controller, wiiu pro controller (on both wii and vwii) and wiiu gamepad (in wii vc mode) +-small corrections in how the root path is selected (having no "wad" folder now correctly displays on device root) + +For more info on YAWMM itself, check its [original readme](README_YAWMM.txt). \ No newline at end of file diff --git a/README_YAWMM.txt b/README_YAWMM.txt new file mode 100644 index 0000000..dfcbc9c --- /dev/null +++ b/README_YAWMM.txt @@ -0,0 +1,172 @@ + + =YET ANOTHER WAD MANAGER MOD (YAWMM)= + +==[ DISCLAIMER ]:== + + THIS APPLICATION COMES WITH NO WARRANTY AT ALL, NEITHER EXPRESSED NOR IMPLIED. + NO ONE BUT YOURSELF IS RESPONSIBLE FOR ANY DAMAGE TO YOUR WII CONSOLE + BECAUSE OF A IMPROPER USAGE OF THIS SOFTWARE. + + +==[ DESCRIPTION ]:== + + This is an application that allows you to (un)install WADs. + + It lists all the available WADs 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). + + +==[ USAGE ]:== + + * Copy the WAD(s) you wish to insall to your storage device. + * Run the application with any method to load homebrew. + + * The following 3 options can be skipped/pre-set, see the [ CONFIG FILE USAGE ] Section for details + # Select an IOS to use (must have proper patches), 2 commonly used IOSs are 249 and 36. + # Select the device where you have saved the WADs. + # Choose Nand Emulation Device (leave "disabled" to install to WADs to the Wii's real Nand) + + * Browse device to locate WADs ("A" to open folders, and "B" to go back) + + * Press the "A" Button on an individual WAD to (un)install. + * If no file is marked, the normal single file (un)install menu will appear. + * If at least one file is marked, the batch (un)install menu will appear. + + * Press the "+" Button to (un)mark the selected WAD for batch installation + * Press the "-" Button to (un)mark the selected WAD for batch uninstallation + * Hold +/- for 2 seconds to (un)mark all items in a directory. + * A "+" will appear in front of the name of marked WADs for installation + * A "-" will appear in front of the name of marked WADs for uninstallation + + * Press the "1" Button to go to the operations menu (currently can only delete single WADs) + + +==[ NOTES ]:== + + To use the NAND emulation is necessary to have a COMPLETE copy + of the NAND filesystem in the root of the FAT device. + + +==;[ CONFIG FILE USAGE ]:== + +; wm_config.txt resides in sd:/wad, and it is optional. You will get all the prompts if you don't have this file. If you are missing this file, copy and paste the entire [ CONFIG FILE USAGE ] Section to a new text file, rename it wm_config.txt, and Save it to sd:/wad. + +*; To bypass any of the params, just comment out the line using a ";" at the beginning of the line* + +; If you don't have any of the other parameters, it will prompt you for it + +; The param keywords are case-sensitive at this point. + +; No spaces precede the keyword on a line + +; If you don't have a password in the config file, the default is no password + +; If you don't have a startupPath, the default is /wad + +; Blank lines are ignored. + +; Password=your_password ("LRUD" only, where L=left, R=right, U=up, D=down on the WiiMote or GC Controller, max 10 characters) + +; StartupPath=startupPath (starting at the root dir "/"). Be sure that the path exists, else you will get an error. + + + +*;Password=UDLR* + +*;StartupPath=/myWAD* + +; Example of StartupPath at the root of the SD card + +;StartupPath=/ + +; 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* + + + + +==[ CHANGELOG ]:== + + * YAWMM (cwstjdenobs) + * Hold +/- for 2 seconds to select all items in a directory. + * Supports Hermes v4/v5 cIOS. Mainly useful if 202 works best for your HDD/SDHC card. + * More detailed failed report after batch un/installs. + * Will not uninstall The System Menu, the SM's region EULA or rgsel, or their IOSs. + * Will not install the wrong regions SM. + * Will not load stub IOS. + * Will not install titles if they rely on a stub IOS. + * Will not install stub SM, EULA and rgsel IOS. + * Will not install a SM lower than 4.0 on a boot2v4 Wii. + * Gives a warning when uninstalling the HBCs IOS. + * Read config file from usb. + * Can load an alternative background from /wad/background.png. + * Won't load incompatible cIOS in SNEEK. + + * Wad Manager Multi-Mod (Leathl) v3: + * Reassigned the buttons again (Read Usage section) + * Shortened on-screen instructions down to 2 lines + * Fixed bug when changing directory with marked WADs + + * Wad Manager Multi-Mod (Leathl) v2: + * Reassigned the buttons (Read the Usage section) + * Batch un- and installation is now possible in one turn + * Added confirmation screen before (un-)installation + * Added file operations (can yet only delete single files) + + * Wad Manager Multi-Mod (Leathl) v1: + * Added batch (un)installation + + * Wad Manager Folder Mod (WiiNinja) v3: + * Config file contains additional params to automate the selection of IOS, Storage Device, and NAND Emulation + * WiiLight mod by mariomaniac33 + + * Wad Manager Folder Mod (WiiNinja) v2: + * Config file in sd:/wad/wm_config.txt + * Optional startup password. Very simple password using the D-Pads + * Optional startup path + + * Wad Manager Folder Mod (WiiNinja) v1: + * Folder support (10 levels deep) + * GC Controller support + * Removed disclaimer prompt + * Sorg's enhancements are included + + * Wad Manager (Waninkoko) v1.5: + * Allows NAND Emulation. + + * Wad Manager (Waninkoko) v1.4: + * Allows user to choose which IOS to install WAD Manager. + + +==[ SOURCE ]:== + * http://code.google.com/p/yawmm/source/checkout + + +==[ THANKS ]:== + * X-Flak + * Pepxl + +==[ KUDOS ]:== + * Leathl + * WiiNinja + * Sorg + * Waninkoko + * Team Twiizers/devkitPRO \ No newline at end of file diff --git a/apps/some-yawmm-mod/icon.png b/apps/some-yawmm-mod/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6f05bfbde20e7404826eb2b0b8307cec8ae8a135 GIT binary patch literal 4423 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 N002ovPDHLkV1f>@Y~ug` literal 0 HcmV?d00001 diff --git a/apps/some-yawmm-mod/meta.xml b/apps/some-yawmm-mod/meta.xml new file mode 100644 index 0000000..c4c8da3 --- /dev/null +++ b/apps/some-yawmm-mod/meta.xml @@ -0,0 +1,19 @@ + + + Some YAWMM Mod + 1.0 + various + Install\Uninstall WADs + Press the "A" key to (un)install WADs. +If no files are selected for batch (un)installation, the normal individual (un)installation menu appears. +If at least one file is selected, the batch (un)installation menu appears. + +Press the "+" button to add\remove the selected WAD to the batch installer list +Press the "-" button to add\remove the selected WAD to batch uninstaller list +Press the "1" key to enter the extension menu + +A "+" before the name means the WAD will be installed +A "-" before the name means the WAD will be uninstalled + + + diff --git a/data/background b/data/background new file mode 100644 index 0000000000000000000000000000000000000000..7e60c1d87cef5b53ea886ae68e59ad6865206dad GIT binary patch literal 32537 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 literal 0 HcmV?d00001 diff --git a/data/ehcmodule.elf b/data/ehcmodule.elf new file mode 100644 index 0000000000000000000000000000000000000000..ea10c88b764371ff368c826842751d2c258b07d2 GIT binary patch literal 25614 zcmcJ%3w#sDwLd<)dRp>BmUzh0+FogGFqQ#hIY2OJgak&`nn!>%B+#(NcD%+0gbk@| zL*j(AAt7mPizK#08WPf5T6%lkCj4%1n)cG9FPh$73zGkjN763Pl&19ICQY%ivHss# z$vo2Z-rxOw{(r-2X6Je4%$YOioH?@(FRQI)Sf-94=|7f~Aja6j>U(7>Rj((NgeSUm z8A9}krSsIZL-;*JhSj(hzw~)qO{3|lvXwXG)9!ArPs_fY%J(5kkk1$?3@JD^ARo`L z3IS~!`5WK0g(c)mcr{cI_L(Edr*P4H`bWn>-_3YvT1I+Idk~M{d5Dm51ms~`+eb$( z9wOx8Aq#o`g?Z%s+@$UO7mkm=fABatkMv6ie`uy5(m6F$%MRZ8`(*Oto8EnR{|jyB zPllYSC|fX@N5hMDVlfe7z4)_k%f&;y<^9Kbwe9nV_$-?L{tH7rH2h%prU@EeCM4%~ z-%rKQ9~xpWS_p&stlHKgqD8(AZPGO6{TGJUr`i}^mkw8_LcSJoSZVxEwj9gNF#Sn$ z>-(}`U{j!aIk8q!+)}*f4|jswOc|mb$Ys7O*-734wt9P{UH6m zEFEr5hn{peHyzGPhpGO^)Z+AeVLH6$SeVms*__r*BCbk?GcGLGYqFi5jMEy%#yJA) zrH?psRh{06G~Hw4?e$s zvDSbiaxq}@=o8brgs#ncrxqz)hgsxyT?jZzJBQfPeF-D&(=}CLf;!g}&ONLhA0A}5 zlGn>qFxbMHCBRcAr$c`Nr{@{J4>41=r(MYOwAY(F?T2@7-YsaYdS}3~`*8dFwEf)| z^s@sFj+dsEXS%es{BB`)8iL)2cPq3^z)o`}u5sn~y}*++&V~oS++*xDa7He-Pv4h; zydTv*EDIizAj%((WDPT;-QsS^4vcDr?pPM%^v8y@p$>uZZ;))txfP$6OH0YQdhtP| zOQCkz#;s3m!`hL}z#&n75D`3qgeg+*sYw_j zhdpZ(+Q>!Esstl8CHzZX|*)Z@)`QmSxw zGghv|MTT`(Pm611hq%@sOE~2DJgI(lTa3q0+A zerS<+yEGMjEks`h;zF)O=4Ki=*RXDMeQ3V%C*+m&1ESYw;2AfGGS!bMhJNOAd#j}- zHos znkU+kp=GyBRnGWH15i$s06~8JM%pnP3)ytu;%2id2KYBX9_TY-c;9G)R z5_-vnQWM{oW}tkA&@C?_anPYw_PD!Qt357xYWl^PY?85BPceYZEcVd867hax%@csw zJ%hRQY($8pc|=6zQjrVXv_Qceb|^B3J@cR%5D7uZgrGYxa63UlWL&JwW4&HlX!78GGtH0bSP zJRcO3^OqkI9!i&fNG<)4in*Kla*8Vl9DVwmd#0%=Y;;i$%f`)sj+sf6%$xh~O!I5> z_Bv;X>A7sYvlH;?BiK3HvxeoD}w=tE*xWWJYxxdxgWuA`t{9^;TUFNP( zdiU9{&d<*S4iS`qS^TR%b~;e1KRvHhcZw*#EM<`PW#F#4lpP{UQu%Ru9X6BxXS;1- zwgc<_IMOl^2J!gtwA$Lq;Fb zKVO=Owi8N|_#NyK?Oq1IL^+|nDE^BI)8l~2TGge-k0GwDV$~AgRsKmlqSgqj?}N%w z@vF4dc|A%Q#KTD4F21DZy`&r#UsPN8W4iqTIv1hv_2$Tx*t0h!oY=FMChXX=Kbx3}J$q@wDh6t6+>GRqT=GXAZ9*@9`$&#k zJFK1fMLb6mu+EDFMwz1KX|Lz~U?2yuYm$9ESy=n5n*o-JL**$fUFrt@G^)H+C@>+f zDs4DIvuG~HF;5+t<4SVug-gBhEUY47XRDf{2J z=~y}C@{TLXt7{f8^XbZY;oRR89eU1Gm~=_4Y{SZAW)MXSUO5eApPIpduanz~S|d%6`K2j%EOdQ|Y)C_AT(T_> zV=YN|)*l;QmG831RV%o_eyK|KtOT!J?RS=iT!GzWd=idHv~owd1gT#BJjUczjQz9wVG9lfH; zY=Z0oCN-j2c1j)vwJ5BkqarKYQ?e)eXh5I#RKF58+4nJvn8Rsh2L~y=CJS{=iCHcS z)<7nxrQKx-Qfa7OH1k@C6S2q9u}e{udkZo^N)EB>K0Z&-E=R|Z8XFy#RC%m?MQ$ip z_6$Davq>JFQrRDb2`u%LhFzBgnj zhVto%s%wTVI@^wJiUa5~5dto}q=G#C&ApGbQ`oFBXsHC<}E`+|#=^)-uwcc9(Q z+Bre5SoMkWJA&onf={G11!s%iPo#ZLuk#>7@7gij#^Z5C7_F7y0nT6uoM6ub}OL3;j9ghzP({>M=F0MFlvwp`_SYEk~L2urX= zG6x?Kh+JA@#tcrWHPx80>Up^YZ&R_tCz3y*>wH9WV?9tBOUVpU5ol08MmP=Qa~vn# z_Tz((kmJD3+bFHl%dz3Xy-`wqKyS`A%}53u?0nAb^Rmuj#I>^#_n5HedKl@A7-ZMY zHnEg5`5Zzwv%%57?6H$fb%QdOzS(~+7IV3rmYd_t>t|*%*e~t0&45ElvQD3wu3XwO zZLN-lmbk|L^5$jB8cvbw2DSc)1x&eiS;I+YuJ*kP%NmBjGY3G0V+Kb7x+(Z3)s45_Q*8_NysRVsC;zr;aHsPKD*j5^-?Z1#6eYM5~-fqV_mBXFsuhKlYfZ zTxO-T&#kqZY}wX;P3z2b`i~||`!?T_@=Ahf+NY@6;^h8A`6a~}BxjalcC#kcYxQwa zUt@?kWTV=jPI9FB^RBlar+am(9v#;)3+BSgO^GiQML7a_FgllaI>G*Z(5E>jrod7A5@Fhh?xAx}GNX`MzSMH} zAts^J>#0(eOyF3VG4W?{MQDYTF9!Scu5NcyF4Ub6U|otDd)YoFu6Gfs&@DmJ(T*;{ zuV(a~hRA?EZj?=w_Xyour?hrJkG;SoMP+l0brsgdxGL|%&`JvfUzV-h!af6PEe2K6 z+6%#rX;_K%O2hI2Mo7AoS}W^}>U^2Lse z;A!334Ry8wy=qUd+NnX|T3%c&G zu78H-N06g<)%L4J%Wt^|rvs)SAB}uf@JWX$q=QU-qa;Z6DX9>)C&%DkIUhC&jGA@M zOwQB*ViN9qx&fA7jL8OH&%!>wQ{L!UM1E=5sE8Ab*JS0p^#M7 z$H;FOqW4GjeZ(Bzs%$l|7uPxnJG&u#;lP zGn<^$x}9yt&Z_wG1U8VT&j^?#R`$50=2(hKQmsj}HKgdy0S+}r=M(+))vZnU-m2C* zjs8|4{Ztx`b1EF~s&GsN91B$(d`4a*dm2tbcOl4Mtmf*k=Pr*kE=jhdrP4SfJ7~!m zBim?MymKhcCjZ?FXnkSp2zTX9~-U}L}Bm|Xsnu%dDo@v1)#It zkbWK&^IcT?L`=5%&53c>ILhf}cF*Xpomxz^4>J$Sf=k|8Q!B7hAc5dc480QK<=}j+@gVWbC44oAd!G3Ugr48{Y0hbFlgmz^-STt9wRs? zQU1C9#{#Kqd8nniG-`Or(?{1kd(b$eTbUskqeM6)H@l6nGD8m-n+f>Aoz;51E&lo+ z|6{o*W|VU4d*Wyb zby1D>&X}3=_7RScnW#1T>gBj8mf>(lO>u+WsJz#H zxnx5qTe`jeyT0~-Ee7t{N2pEs>a+4;cQ?*^Y|`O=Bjx>rSy-u@&>do}rh!xPjCZMrD-`Udnja2#0%d~V? zDauxX$FQ&)&bU!Jpvil;>u}eFRN37EExUU&wPM1e%ua0WtnYj&-Ofd|9ryLTO)++X z6&#lB@N_m;AB?k|-{|~9SIM&abN_i$`*ySvdY*}CZeyNdDzbLBKhN}Oc7J>KhkGjb zgkghx=@Vru2X!fK^=b~~RMlIA?teB`=$ZI`Sh{U1^ObV0$x}RJsNPa7?}(P`xy4r| zMt`Xk!@ATy#Ch6JYdsA^22XRssc1dB5Q`+HDJHojzYG-FresiUWbC#4GOirwMXA)k z;B1dk;}fG_=Zd-Auvd}9Tk5Je4r-|_gUT`B(-nhXmYq1gh>7L$I@wvVl*H{PWovLw zAM6duuW9+W*_yImlL88hnzRz$N_x$Bn-w){58+3dgr+GZly7+PZAP4;5Ro$|Z!! zLHH7$uj6?I&%fdMcRWAB^A?^VJn!H+i|5z2aGmy8xoC!U$x+P9eH1$9WUA z=8TM~{K(-i9r_47Nws{S9&9LoSLYko>ztpgleo1Gm1VQRLF_B-E9|TCiiqr$Ll6&*uN0dX9`UN2e3E zn@a!DQ-9)FoG8(~7Ash_jak5ZZRHm5+ip2-=j6>)qw+fv#CM-CClHf1^SUb&6JtWR zyqTsC-J1SqQOE0~dX)HmO0rDN8zFWEv^Z<<3$Pt&FrBOb$=!1-B}t$kHI*3S;Gl3kU# z*lT+s<(Bue!tSm?$GC_>ToyoTv%lp#&1q_O8BV{WY2NSd4%ie{4Wo7Y#c<7o zp!To$Om#76+vSjt*s-6q{4bmmj;v_zXSrF>0@(3nXq&+F!KN&34Q`e5u1s88Uwglc zmjy_E?AWuAOqx@&ooZA0ucbS|#jCYoTfKJq%Eb6sob+g<0<2<<7*wsyIpT6^e-+nN zEXCTOJk;jafo^p}Y{*A#_5B)u^^$79$dv06tI*E*+Nu;ZSFTO`6!KE42Z#6>;PqZv z_&=6B)tYEyE82Wsr97g%0Ev0Zmd^o`Tne+$dkJq5XUlo1cQ@YMmW;PtjL)2B^+oeo zcmb3Kp6rM9b8|p>7;W{6(^?P0(jrm(UY%%p8MglsHy^YNQ0n!mbGPIl^kZlqQ2QxD zKLTCjI4eu>XDVZMWAEuk2tM5eWxi>j&*vjlET8b8VKoYK>U6BdJw;^+~#FuRfewvvR~`#9KGc zznSKTpVRbf)SX`CmFe+5M#l=>>51yC56;Rs@nEy7n>^8LC?eI(2S}eb3O;{L8O%s| zy{J4|+Y73#JOn?Hq&rK7HZuAl^voXF-gP@9XSyq&E+OYumShbv!4f4T3q?sePrhG% zzRQ_y!p;lbge~ICe9|Xq3Dz7FaF{a0!hi|80~0Jz=EVqBU8dfX4f%=l&=szRSi=Da zG%!aDR*xa3x;|{vd$TLjHbiQvOX)y3{WCea-17cFo~-c?piL9}h)f)8X`u|C36h@% zQs4DG1K|E~t$*#^2>*cRGNFd?B$fXLzYt}sTfB24PEHqPsu=3q)T`m9dvZ?S&*|qM zlFK27-V1J52poiQGB>*@DcPXY_d~;(r?*u#=B&1^_KQLc(HglKG}XBT0IzX%JV4i6*3haG*ta*M$jun zuyAGV`zsI0{D3LOl;=Rx`7KJ*`Dn&`6kgZX4=AUnuP44fRGI@pYWrZ zb24Em+o8#x&3XeKt#gMz+mY$q@#Z&KfrObIk3J{}zPgLz)Ql57?1Oc6GyE@{fz@f4 z9o2Ca-0jjK!22c6)emYO|EByRtPvH|9x?Kmd}x>*xuoc%uk|z3OJ@9ePB&%U$L+Ldc!`M_Br+L`vSUO(#r$OKBh2-AHx)?X?<;gGJTn;k5 zn#Y;l8R{B*|Ewoszw;1&nquyt@+ak`}dO#P}eILms?qrm>T-2_iVqz++ zJg(q;=si}*9~0vlior#wJf3Wza?m(aM8uh)64=TpEHlyOOzt+xc3V#wIcwr8u1t&# z%dlFZd@U_M_JK0HYo1zvDR4qllwcv9ozV+r?uYr?~>G;Z1$85m#9vPQ5j%$NSodbDA}(t8VGjdgp>F zjA4g3&<9yMG$0NX!G3fgqfZlMxx*gjG&ygi9)#3-2Rsy{=9kxDzXC?bB=Jk?zWSx> z`>GYV%ZR~mtS_UKeUB)Y)sfdKzs9U&#I-RUJ5B{#$#%ZNOZZCyM`FGenvXt*muJgD-EyHnpIW6)73X6Sw zSgAp&<;vk(`e;NSji2bFJKe_@#d~QVsHG2nj?Uc3mEcCq(Bc#xl*ceKhq5Q#>nyG! zZos-cPcecTYt$OQ3f_ep0fqJ}nAH#RkFi%-Kl z3(Lqf&~w%edRAo($POx9r_Nli({|l=LC4Vl>Y)48{m~ZQ44?GP7RVPbgGa;F`t%2s zPjfl0nOr^xYwyx#aP0iBStt!_Tt=oVU=9beZQ;Etm$vQDxQNNK!@uK^9dEYj+Lm;^ z;{V>6qWmfnyEh8h3Tv&flezAVx=nqId*;$Dur#BFTi-)^wSBrkOU#1vf!l%W4LDVF zmyq|2u9-`lBlN7~#y&!AvRG@AFdiG`IIfU04?gbI_UK3)Eos*0@!gy`P|x~s-cF^n zIA@51r+T&UhVw;Ph2x6-ub&~@9a_&oW@bSz>DLWtL8V@-xW~ayvtt_P;k-3{ngKSZ z!>JLse*Zvbpv8WoHxp8yGH6J_pyEcy*@c-i375UJ1>7b-Tqu-;%}je(1YY(7FK?M_ zJIp4{j?j)T?|7@t*tV+csQ;BS?tCw>vKAxDueD-cg?radVx>>x)&~OEIdss}=EW?} zvCl!rzm||@QjAc%sq0r0)TMXUG)7dpQk}Q|Bb?;^4{);oe}I!PC`IEv#7&c(I9avY z4-W1F&RAZHJ#l9BmWl#K#EDBWPF`48!(pw8YGCU#^^vHKa|^I4UfYP?tVi@Xp@*Gx z{9k8dUBva0C*O5Mucmy9%9lv-Las92*y|{rVg}rTwa0)N=yH>=C=`XC4F4RsH^<>W z>pg_CMLudlodlmbr(^4@c2&!_tKUqs8KM@04D@AxAz+WQPH!ovOG&?z``+r z%ouSCg`CA_FVPL@OAPSahTUs6JEXsKCZwY)6c!)oLXaJdH)MyV1E7^JbDADQubx8T z>aN~i-O6MvSWrwVXWH)%-vQ4GXdr-NFL)PQto3hI?i2LkV&DjSs{mi)0lk~_8wB!< z_5gVf{vqQ(gY+^hI0M#$4cwNYK%|P=R%es5p8=L<3YsuZOSp0s+twZM3)-<3Jy_Ah znvml*7B*4`xS>O`frHG1WTC%#HfV1ceT9G>bNqnXj|KhAoK4Q5Sq-DIb8%i;=bgd@116g zG>~u>ttf;K1rJ{m0`2rKVj)*9c_=?f)~UPTOwkSr&O?1nl16nupN?|xfg?H~$sD3* zM2vG|LMZg*!5TJkZHTMNUxZV)J`(3{w|~d}k?P_8FKSIQ@{fl!qLz9)-Qbm}eIGFH zVdl*y2>bDT6VDIffBYCX1(Ijej1lE z%#5Arp?3qw`G*8+l=N!`;8mtdZvR`|dC2)jq~Np+I=H`Ihw@*IycQY5sR~Z3Q)j|d z2F|vIU+Y{H9_#G-`<%8#ov*cZb$&{nw%58AwT*TCU7hx>-CfyMf-`ICh0(qUy*`+& z+4;Rp&CW5CW>?j&hzfDZ?k*RhX9%RveiHD_hsalbtap3zqWfUbq8Y4BF;t6iS~5CcAaJa3RmvVfS&kstUG3GcH}#ek3KP~ zsTl8f!XHDTWyWTu@TdGL@=?DgN`Ni&fc6)p@TdH^!yBn2r!f{EN?WrvZS9$wwpUD= zwvXDOG2ukxM{%8VcAhh;f5y<^#fZ*gL~GFhFEO4B<%!O=&R0@!c78aZr*L+80cWQD zdH4)dI3MirM9Ro%$MIq2>V<4g`_W8I`(=}6r)Q_Ewtpb5j~ViuPHUebs_y{xLTmxP zu6`wE=+`U8NT5oqt`6pmHllHyv>%6#@YlHB@uT)uNP1H#7Ows;z}*74j{@$??IqCk znmPA?9<3~h>EnhxtJB%1j~W26Ntw}gwCne2Yrbc9D>PACfQy^m2K#T|r$X!GM}(Lj z7DIi9yQBUSxCJfkaPO#xoM6ToqN&2rXYTOCOt7_{if6L78??7rX7GWPn*`*j23(MQY*fZb-X*(g+ zVJ`hu{rv-7AJr|KKJ-(bco&52f+cJdf6P@>^n)91<-E7KyggFh&Lca2x2t5Aj2kFa z{<>RTx8FyrzX`q$U1AnwB65CMd`*?1Dr*0&!_xtu--OU{p`!(1VW+FJv{UMAVKaaM zW55Ovm8qRC;sis%dC845bC(-Cax7u!+T3*+p{DDl-5%HmG9c-IyN!8^nBZZAU-vhh z{~1l|(A0co$NrAMj<-75&Oi#gR3~jSw}m<%?R+zpLbcMiC)?iY+8cVN-Q4wNySQ7^ zzF+93{;Hu~7wOgX8gQyJE_j!=d_61Th?rfs>8(qf)jLBb*Zif;Xa}jm$3csR0gXK# z)pa_^8bH!evlrY)i*o>08H(!$be;Eg#?uu1b6Ts*j5>Pz!H#;5G4XK6VUK<&0~TWg ze2H{8Y1VkOLySk4XzRG>S)JJ4AwWuP1+Ql+4u);PtsO7A8T2wwAnon#-kAj6Q)OK* zbzMyLu-i?0*j*p6<=q>3%flwP$dlBzb=KVF1sxcnW$4>0I?Nq4pv$~|Rxx$8BRvD@ zhW55pI;?82KaP1i1Mp{ zc=pAN{260B+Vp9AXr}p|;~CQK-pt#3ge~8`oDbUMnnG`T<=`+hJDMQb}AhVDK{YvZ9!McLkN? z24FM7DjEw;1^k~+(cI{0ZLYGH%5&1IWL;v{X8|x(+)u$5jf(%0un1s5i(A{38sw^+=UaSY8{k(?|~rRg!`3aE2vJ7yay4lMffFzB5-sG@vq|fDeNT?_~B4ERzXiA-kD(s?z!=y zc9lc5huYJYi#AA3Z7bVBjkeoxzlU3}^XSgYyF9yE7WBvqDfNv_)VBVA%#Vzqjod2} zBbS5sVC0)?KdVHmTe*_TnMga2Ua2H@9-1*zdzbP>u_m-ya$r^kbyioZvuYHpshtkx zs6NlshLpA%zx*U-)y$dGvwDKg>O@@44=9^KA@kE?z71oBttNOImChzkswJwFyTpo) z4OrhA5q2TN*>~J4W@Akqt(_&7&wy5w(%tu6cD;ymCyzHf%DN{6@+|nvwWR2h1hk@W z|CVa&n3yB0Tsd?7;levfgO zBu8tXg#CkuT)i8kC(3xu<2=)TC}NJJ?|ww~Q~ndd-H)MQhp6KAVXRKgQ2vT86}NlD zJ^u}E?-CzWam%!SOZoRSMz;prI`ToA6C+DkHgmJ+3cY%VinpyuTcp}t7@V0eru(Y$ za_DmubK?D>I*eg6*7kkW=Aax1ZQQY9CNqN+Gtf^fsBdFmMobs6xnVmT($f2ER0m1S z46f(qq227zO8va-49?OA+s##-+TUGBXVv~J>ixH@P>|Xr`wgJ0&*F5bL)|0qfIZiy zYy~tvXn+e>EUUd!yz?1-2%Jm|%p|gX-zjNs`*WZxn>V`{I+E)}fw(800d0*Z#QC`O zLa9p^njTJkka=UF}ZfJU#6RqJq zaXRdzIn*P6;tI~0vf&ZiNk{RsGt=h$J*fYKY8ahPj2oHFB$~O>k z@O#R`pqunR+nwfNqqcD4bnq;C*9!Np-sr(QY&(sg0}b9a_=H?Y_S)eC#5pR~+sEub zz%2q@lLHpkYom7-5n&p=dn$Cpqoy3be|oavSr%Bs{gJbG@C`U&zcyM{rb%3eWwHcw z^%~!R6yYD#a-x>Il0o_3rOQ94mWS;ebq}g>-KWcaFXlKs`tT@eSuY?CsF_EUm4NX-@bHFRF z9Ahd`YyMc6M#noj+GhwI@7NEpcct!2FhN7p9c9}0-onY#P4@m|@=8-OsbF8Z@)?}- zo8U`G@02jOQ!+6!m)551I9V-f)O6fDyy05~8;m}<6uSuv&n3OwtDfPXAg|=XJNckq zotKyTjP{Fe(&Ugep(;6^pCfOOMfGf-RnPPrxOT16O){N*8Bv|+h3`cbu&_z2;2YG~ zePX%0TdESSl5-IjYv3(Qj0-t$T(%!#qRr=7x21O3WZp8zu{> zAN~jN45~NahSyP~8ObX#Jsqe06h?QJYin}c;J~0`)n5QU4jMx8@K_i^c%4e$u(!k4 zE+2j;xZy584@@5ge($5c&S#xCU0NUy@Ft2QNkoZCIbKOUFJ8Vfo_t%hz=U(| z571L+60cZFPqPhY-xhG^#tLkXC!ax#QDZ%b8P(W+dgo7#eG#ztx|BcHb++>Aazh!88tpk3P@RyVZFsA#& z=`l?tFU|(1VW;qdBV6Ki!70dDfimhyLzz^*1lMO%Uw;sB7XrK38|dvg&rF_M0`>zG zewE)+UOf9JR6YmKo#fND@6)kiEaKqfm?uLY)r$>je+W%zxft}ZI9G#T1jeWjRm0z# zc@MV>u&dUIcZTkWYP9A|XEAP+Lu+7Tsr!boN~?b1NQDl3jM^KDi8x~S9Bg5{{ymCCgQl4G|96lpL!sK zG_3yzB+RR;#uahk&gSYRk4h$PDtVT(yO>q^N(nhCFH$9!lTCNaRrwX_h_S=?%|mwT z=|A3^9_bz#`#rD`ms4ZR!5DKeMwXTu`#Im}_bH~^7;ahwH@&Wp>iKiOyg`AJaMCdl za=Xf3VON7a-SEcSbj>XUcc9dlK|PkPjE!AWJ(tMHj+U1%eN;P_>fu9<*7dkSIyUxa z>L=8?9(Ra0%WmokU1t+#%5I!f@GZDi2H&vtDD?+f2no$`to#CKk>lertQ4JC$m>!* zcYdFt{hgwav$%Ve6aG90n18O`3|f3CeMhVccf<}RGF=Qj470^4ZeGf(!M!8d?DwVa ziHW(8Sm-^mEJ%df1ki~u%UJydSU-BdZ*t{f9`@mR3J-i&n;`e9bBKK=b%zYHgl2gd zUlziH`(%^fA{iU|I$B_sL&uYRNWbwdlCiM^plJFnlCiP)Fd3c2{|xK%n~2%f*w=3P z7Ksluu!o-eqx8;Hi?kp0z4a6C4cFf-oEGJA&Xkf5aW{;<;~v5-?^CY76PGghNYKDi zm~^6E0#5=T#STbs_o7wx{PR$1EW_}j(1Wrc@5MO-R>|14&#V$cZW;O$<<^PmSYiKG z%Y(;_U;7%)RNy&@aisL=6l#sCtw|F!pzY%lAJV5dE^w~W0fnJyh>&g`uvl=vK8Guit2o>l z#3^)M;4V6+S3?{R4G|s@<2EXVU|s1^hUPaTxBaH|i1AT6;2)v*#0k&X)s5HZpQd+T zPhWjKy`0N}Mr^~XE#+Li1$F$$D_4C6| z=th5gBRvftNx?UI206etc+=B8IGc~MvW_!t*;eq1?`%rji)%Yekl-?S=*?oE9e_)3Z!5#Bww_J|tP$wRv z(oAjZ2eKW0Q4UD(d*T@+Cbu|nWnAyIj&n&mZ zSXi8Yf|DkCGUJLHUNCL>!1scE2kj^CL@OjQ^yE@|vTnH?GTT4OI|nS7RV)}O7RIk) z)VSG)*vDAY%*{R}QZ3ewg6G}kJoujlTCR4ht%_;1$v#lsHE^4;t5;Ji4f(_H2ngTyVz^(WzV~=sXGggoaT*Ete-fFH z`Nu|%s=r5IJ02VPDkP_|t4EY6H+pO-i&3mNwtBowqen5uNsL**;N*pT`6x~)zBvPa zoLu>&jze+q0Az*-AU`}1OyOax1|Ct^bAwsvVJ9p;V9xWg4c%}K9ZauGO zYv2jL*MIPoj-!%2ZVH#06CdyK*iW^!EBY-Tz~`Rfya&ORM}8jhM%q&6mXTLf8|R1k z{?K~JJ=);X+)UpK()Lbo^Q<;chqogQZO1De!xY|tUHTpNp`G0AfhBcc?`+%S z*}TPfz;UwqvM=E~MOVId(HB}Wt={f0%pw~K?y{fouYpH#t~c17x4A5)sV|MQ^(`^x zX;1w(jAuJNj+cG!Hk8rtUnQ7^7R6i;v3L8se4yJ=`RURI*?7{0F9wWtQi|S}*ILr) zS;#EO0|Z{Vg?^E!D)aFp{{A!M@uRYgIxBJS|96d^LHG@&Xq0Fr$VW}eJ^t#oX`GnwE}sE>NlI_nl$TQ+?;RJMlHEAx#H8k0i=rt%=oO?y9)L4zJ!FfIoGj z*?rLki&?L&&zN_jSJ!U>jjCfA`Hni4FV(C)XxO;rbDjaC)=8JC${y@{BIbt<_Eu05b{{%+%z|GL6U9W3jCb@KqLEX0XnuD=)7bn8JK>}u8nxdYh;b6B(1J&+Z!9mF?!^lnHX zxkIVdR!t!*uIvs}7VNW_7Oov11a5xn* zmULL24p*nca60TshlA;GC>;)`Le`QF%hTcNbQn&DJ?U^T9S)_#;Z&%xq{H%bxH=t% z(_v3K988Bp>2Np|YAxxoJRPo1hv9VClMV;d;ZQmpPK7#4IxJ6zt5c!AClwloQ=!q4 z3QcNwp~pfl4iYWq<0wr(e+b{<#RI-tDKKvGs>Q$WR9O*YDDuB}QtN-@B+Fzl&PyZY zHMmb7jPJB{fADmPGZ~`a2uaW#}6X@i*)?m0K7YgG#SnzCL`n0Mb7fN zWqJ4*B#n{#aa&Kn+ya?_`Vjs5>)8Rn;|Y2{6R<3K6?&7ew)$lX(=751@x$X7zrO#! zp{vF&Nc*`Cr8xzq3gas`U{rO^;p%&;t6x3N6^PTSd#I%9^Hx8K8@1~*Gr641dDV{v zpi>0abH#xb)phnqGtKss{+=@|_VNw(KZ+UEb=oPJ7N6N(YCm7?3q-2d2HNe%?cJ|u zR`*oLu-dfCE%eR^zT1=yT}4}}IfK*lfW{CTvf{gqMtqmhS+GUXS2FV$`i}2i>`hp) zbN&r5hp9|2v(UNmO~^ZN?_!>Iw&wKTI4$%<<Yd=KKSg%;`5JWGY8BbiK3WoHdM7RCrOFyG< zR6oU8xZ4e{4dH(1P)+q_y=@_r?R0At7Vk?IGGTlV#GjkMw;ecBiRR24Az8iI=U{E{ zopW$gxaRTi*V_e!q|?DA(QwsyY2<%<(MHg)X9rlsN2)jlQQTrhh2o(#OEDagvJQJN3s! z^y@ExI7LO1u+^+u%WvJ@u&cF^a&$2M|Kjic!eK2fq<*^qwsH_MD4E`VO8wC|-7olU z+jsJf+qUm&lKHKTTer7$xJ+bjL*s*UceQV>xDlJXs=E57H^Squb$ipM_MLS*ceV3; zNpbru6XBP%cWkRC{OZQG=Iss5h^=X?ZzX)Nu5~BjYnCh_eD#ubOKK6+u0gTDQo=7= zgILYVb!wBT<`(hA5x%(n@Bg6({H6O0Z*SX4OPUBkZQr@Qt*)t&Z)%Lm3IEO#kTL-`CpX7RjWVN^ruxmFzuv<26tZa(t?O@XS<&9Ku6b)C?T3Q=Dd|XSYpg>34dS$gmF4k_N$0&AAtbvG@ucG=3c%l^CUG^QzzX#;p| z+leXP9_H(}Hr!IVX4{Tk&21gGq%CcHu(`gGU)K?COwAK*V+}?Ss0-KCH}AYr8Yozs zn!rt~!j0QXW-a2=7+u=fUfmK*hBGdbw^}Q)7e61$cG)`9Slw2EJ}v2gozs?>~aS9yF&e7!Y&K zWZ@LPZ$Z{AwE3w$Q#>_qYvO5(`~z*N;Si(*OCN=_b_GY-`?GGVA}U zm0M7jYKLFd@@c6z4GdlLOIlmE*Dw0iE#vc>A81d{8mXhjzyd)^sV#+o0_fY$?R2WM$+ZRNLP?UT)Q4VafEY#wq0-`-e{rO&rFccSF>#`bM9 bcOr9Vy-YK=?b!1i>qaptqrZ`o|93_kQQ&o_p@OpU(+(CUVKqtqUW;Qd-}-{?_&l8{6A927@h9Wj}+#*0y$4 zA-<4^Du_hqrz!jU`l5Y1dj|%0x`n8F#7&ct%whLwgTbSzR5~>>S{N*(#@sy_nj<)L zsOY0FlPXwTzSMdmmnkX-!0oZ?NLGs$n7FM^DyOsN7y^OljXs-t{!d0!3D{2Jk_DlcbNfDKd{T|35cfnOAL3!9;9 zs3aU=lDT8%fjd^2X3O7$I&c@A6*evb;W4d&kaSEJHO&4+249j zD|fu1sNPn=_8=!m4!`|8e8=$+e@F4Lh{^HK2fNSbpYI5a2fgSx5IEg_zaw}QH#$xk z3~np)m!&cVasuG!c_+X|uH$FjJJJWeA{~f0Y6;gFgV{kC(T)HjAo>oRi$|S2VBT3n z+*HIV;CawlFQt6~Kh zrY5m>3H!QL^ze%bBA84ebP*Lj5cG+CwMPM%r15;fIeh?}NDIzB zxH#Pa_PoFvbgHLMOuaja&Nxf!If1`yz+(@OF+Cv44{-Cf;iyciVW_uBgu%?1Am;}+HRBF%fhj7 zHyb)l7OR)_lF!Ebr^18jGdI{hn&u7)Q3 z>zZg>68{!VYH7W{tCw}OB~El|0r?r9-bMaXMSs+Fe)cC@y67By zQsR!Mr`w5W*X-FTW9AE$4{a zz&LR{;`m>#dOEZ(*~e6hqg z7K>cz*msXW`L?-9n!6>WSB=zMC^1BxMk;TNknFZ@m3&X+u{B2et_b1RONG+#nW9Wx z5y`2y{CU?_gt=~x5Z6ygx0&jFd|`!TuaS6{Vu+GfRqtL3L#CLQgyL9uV)@iV%F~2N z52=3hbYO~lFXCZmn(#(|+7mf^O@@~?;p>r--n;$QgdpR-v zRF`kVd;Q+I`}4t`pgd`U&FXCH?+NDIw42Dg!Sx&0(xF5;?@}Q*;Zi!A841bzY=Z}pQ5<_?wa^8DXqk?;Hfx^RyLW06W`8=RIG%+%m7#hmCM<|R@N+?k( zb0}LPNT)Kc60n!BiQB9NuX?G{jMcT2a1@!@0K#PxN2d*h9BX< zW5RijiCBX}7k-k?Abj~5szQt=6n zMPb&g9K~ZAh5d1VSjYZ$;%)nT9sGSLnv_o527%>oRWtZ$6!wSjYvXS}u;p*T9bkE# zY101gguwRqE_lQv{WT~Z$Cn1S{4LxAK8~VE`x}A4_Q&(!UKEaRmC~`lF9CBM&JSoI z=B+d7Vfkboiukhk)|$%C0Ik)WrCSW_E6OV|r^KU0-o(y+)t#8Nwa555 zqwt!y{4M(s{C`k1v7c*^k^5=(AGQIU86C^PTX9Iy`cU{4@lf=a(VyJUH{?N1i zeT|hM)^onjOAfgL&-r>@@lo*jPd`Jk>;FdSb#ni#_&OMK{{B{?x9*5@t|TA5TN#P> zC16-{zbbl=7v7Q|?)l>aH)&Jp|M6#m=>Gn}9{hoz;0|2-?*y4dCYw*Y?zqYU&ge3| zMlkQ>kCi+gSlf*Gw_{qWFs1{o#1hcUV8E@k5aCzQ=Qz}UB?%`3oJC65Ca6lQzGDs6TEe^tpJ1^%XzM}S{Y_$lBYD|`|7 zw+izd`n|%u=e(&f-h?=$e?Y?C63~7Bx5B(u|LfTZ{4&EosV`7A0y>G)kYk@?KjJJ( z2-8;E+@$2_lUT3Xx(uxHCSC>Jj=fsj_bQCs{wp)R5_LOUzFDlG7hm`*)@Pi(9 z27n*Na@O{bC|nKsW6Eb7_*+<>+UA17><4+2@ni20$fL{;_8swkELUy+LxoxXQx(G- zz%OG7YnvH`@fNQspId;jUZwvS@D=618TftWCyluUA2OfV8=MAZvj=!BwmjwEQFBr} z3;AYjV_M#&>Ry8UbC}Xvey8#?4*4Li3R*s_O~9;Jb+#cZ`b}FPzP0 zTrfXO&M`+#Zog~g4KeBb@L5%d^(hJOI={l3&_|Xt-+_Kh6Gzmg7!h z{Gi@SuFJF#m~EJ-mH}q}zgj{)3?;_%|^u zgN=>>{;M@ce@$%dM2)&FdC5}Rfb6;Z2eLk}#MxJ<{=y^_! z3I`y^I+b$9>l9``SpTN2ZiUeff5%BVW7tVN101vL;|g;OSa;Iq3E;yDUj)WHnWxro n39iQ4)^dAXf>wW(~DP(g|%A literal 0 HcmV?d00001 diff --git a/source/fat.c b/source/fat.c new file mode 100644 index 0000000..5c8081b --- /dev/null +++ b/source/fat.c @@ -0,0 +1,58 @@ +#include +#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..34e2ead --- /dev/null +++ b/source/fat.h @@ -0,0 +1,46 @@ +#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; + + /* Filestat */ + bool isdir; + size_t fsize; +} 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..1db1364 --- /dev/null +++ b/source/globals.h @@ -0,0 +1,58 @@ +#ifndef _GLOBALS_H_ +#define _GLOBALS_H_ + +// Constants +#define CIOS_VERSION 249 +#define ENTRIES_PER_PAGE 14 +#define MAX_FILE_PATH_LEN 1024 +#define MAX_DIR_LEVELS 10 +#define WAD_DIRECTORY "/" +#define WAD_ROOT_DIRECTORY "/wad" + +#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/gui.c b/source/gui.c new file mode 100644 index 0000000..c24b51e --- /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 70 +#define CONSOLE_YCOORD 118 +#define CONSOLE_WIDTH 502 +#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/iospatch.c b/source/iospatch.c new file mode 100644 index 0000000..abb1f3a --- /dev/null +++ b/source/iospatch.c @@ -0,0 +1,116 @@ +// 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.0. + +// 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 2.0 for more details. + +// Copyright 2010 Joseph Jordan +// Wii U vWii patches Copyright 2012/2013 damysteryman + + +#include +#include +#include +#include + +#include "iospatch.h" + +#define MEM_REG_BASE 0xd8b4000 +#define MEM_PROT (MEM_REG_BASE + 0x20a) + +static void disable_memory_protection() { + write32(MEM_PROT, read32(MEM_PROT) & 0x0000FFFF); +} + +static u32 apply_patch(char *name, const u8 *old, u32 old_size, const u8 *patch, u32 patch_size, u32 patch_offset) { + u8 *ptr_start = (u8*)*((u32*)0x80003134), *ptr_end = (u8*)0x94000000; + u32 found = 0; + printf(" Patching %-30s", name); + u8 *location = NULL; + while (ptr_start < (ptr_end - patch_size)) { + if (!memcmp(ptr_start, old, old_size)) { + found++; + location = ptr_start + patch_offset; + u8 *start = location; + u32 i; + for (i = 0; i < patch_size; i++) { + *location++ = patch[i]; + } + DCFlushRange((u8 *)(((u32)start) >> 5 << 5), (patch_size >> 5 << 5) + 64); + ICInvalidateRange((u8 *)(((u32)start) >> 5 << 5), (patch_size >> 5 << 5) + 64); + } + ptr_start++; + } + if (found) + printf(" patched\n"); + else + printf(" not patched\n"); + return found; +} +/* +static const u8 di_readlimit_old[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0A, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x7E, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 +}; +static const u8 di_readlimit_patch[] = { 0x7e, 0xd4 }; + +const u8 isfs_permissions_old[] = { 0x42, 0x8B, 0xD0, 0x01, 0x25, 0x66 }; +const u8 isfs_permissions_patch[] = { 0x42, 0x8B, 0xE0, 0x01, 0x25, 0x66 }; +static const u8 setuid_old[] = { 0xD1, 0x2A, 0x1C, 0x39 }; +static const u8 setuid_patch[] = { 0x46, 0xC0 }; +const u8 es_identify_old[] = { 0x28, 0x03, 0xD1, 0x23 }; +const u8 es_identify_patch[] = { 0x00, 0x00 };*/ +const u8 hash_old[] = { 0x20, 0x07, 0x23, 0xA2 }; +const u8 hash_patch[] = { 0x00 }; +const u8 new_hash_old[] = { 0x20, 0x07, 0x4B, 0x0B }; +const u8 es_set_ahbprot_old[] = { 0x68, 0x5B, 0x22, 0xEC, 0x00, 0x52, 0x18, 0x9B, 0x68, 0x1B, 0x46, 0x98, 0x07, 0xDB }; +const u8 es_set_ahbprot_patch[] = { 0x01 }; +const u8 ES_TitleVersionCheck_old[] = { 0xD2, 0x01, 0x4E, 0x56 }; +const u8 ES_TitleVersionCheck_patch[] = { 0xE0, 0x01, 0x4E, 0x56 }; +const u8 ES_TitleDeleteCheck_old[] = { 0xD8, 0x00, 0x4A, 0x04 }; +const u8 ES_TitleDeleteCheck_patch[] = { 0xE0, 0x00, 0x4A, 0x04 }; + +//Following patches made my damysteryman for use with Wii U's vWii +const u8 Kill_AntiSysTitleInstallv3_pt1_old[] = { 0x68, 0x1A, 0x2A, 0x01, 0xD0, 0x05 }; // Make sure that the pt1 +const u8 Kill_AntiSysTitleInstallv3_pt1_patch[] = { 0x68, 0x1A, 0x2A, 0x01, 0x46, 0xC0 }; // patch is applied twice. -dmm +const u8 Kill_AntiSysTitleInstallv3_pt2_old[] = { 0xD0, 0x02, 0x33, 0x06, 0x42, 0x9A, 0xD1, 0x01 }; // Make sure that the pt2 patch +const u8 Kill_AntiSysTitleInstallv3_pt2_patch[] = { 0x46, 0xC0, 0x33, 0x06, 0x42, 0x9A, 0xE0, 0x01 }; // is also applied twice. -dmm +const u8 Kill_AntiSysTitleInstallv3_pt3_old[] = { 0x68, 0xFB, 0x2B, 0x00, 0xDB, 0x01 }; +const u8 Kill_AntiSysTitleInstallv3_pt3_patch[] = { 0x68, 0xFB, 0x2B, 0x00, 0xDB, 0x10 }; + +u32 IOSPATCH_AHBPROT() { + if (AHBPROT_DISABLED) { + write32(MEM_PROT, read32(MEM_PROT) & 0x0000FFFF); + //return apply_patch("set_ahbprot", check_tmd_old, sizeof(check_tmd_old), check_tmd_patch, sizeof(check_tmd_patch), 6); + return apply_patch("es_set_ahbprot", es_set_ahbprot_old, sizeof(es_set_ahbprot_old), es_set_ahbprot_patch, sizeof(es_set_ahbprot_patch), 25); + } + return 0; +} + +u32 IOSPATCH_Apply() { + u32 count = 0; + if (AHBPROT_DISABLED) { + disable_memory_protection(); + //count += apply_patch("di_readlimit", di_readlimit_old, sizeof(di_readlimit_old), di_readlimit_patch, sizeof(di_readlimit_patch), 12); + //count += apply_patch("isfs_permissions", isfs_permissions_old, sizeof(isfs_permissions_old), isfs_permissions_patch, sizeof(isfs_permissions_patch), 0); + //count += apply_patch("es_setuid", setuid_old, sizeof(setuid_old), setuid_patch, sizeof(setuid_patch), 0); + //count += apply_patch("es_identify", es_identify_old, sizeof(es_identify_old), es_identify_patch, sizeof(es_identify_patch), 2); + count += apply_patch("hash_check", hash_old, sizeof(hash_old), hash_patch, sizeof(hash_patch), 1); + count += apply_patch("new_hash_check", new_hash_old, sizeof(new_hash_old), hash_patch, sizeof(hash_patch), 1); + count += apply_patch("ES_TitleVersionCheck", ES_TitleVersionCheck_old, sizeof(ES_TitleVersionCheck_old), ES_TitleVersionCheck_patch, sizeof(ES_TitleVersionCheck_patch), 0); + count += apply_patch("ES_TitleDeleteCheck", ES_TitleDeleteCheck_old, sizeof(ES_TitleDeleteCheck_old), ES_TitleDeleteCheck_patch, sizeof(ES_TitleDeleteCheck_patch), 0); + + if((*(vu16*)0xCD8005A0 == 0xCAFE)) + { + count += apply_patch("Kill_AntiSysTitleInstallv3_pt1", Kill_AntiSysTitleInstallv3_pt1_old, sizeof(Kill_AntiSysTitleInstallv3_pt1_old), Kill_AntiSysTitleInstallv3_pt1_patch, sizeof(Kill_AntiSysTitleInstallv3_pt1_patch), 0); + count += apply_patch("Kill_AntiSysTitleInstallv3_pt2", Kill_AntiSysTitleInstallv3_pt2_old, sizeof(Kill_AntiSysTitleInstallv3_pt2_old), Kill_AntiSysTitleInstallv3_pt2_patch, sizeof(Kill_AntiSysTitleInstallv3_pt2_patch), 0); + count += apply_patch("Kill_AntiSysTitleInstallv3_pt3", Kill_AntiSysTitleInstallv3_pt3_old, sizeof(Kill_AntiSysTitleInstallv3_pt3_old), Kill_AntiSysTitleInstallv3_pt3_patch, sizeof(Kill_AntiSysTitleInstallv3_pt3_patch), 0); + } + } + return count; +} diff --git a/source/iospatch.h b/source/iospatch.h new file mode 100644 index 0000000..9adfdbc --- /dev/null +++ b/source/iospatch.h @@ -0,0 +1,32 @@ +// 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.0. + +// 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 2.0 for more details. + +// Copyright (C) 2012 damysteryman + + +#ifndef _IOSPATCH_H +#define _IOSPATCH_H + +#ifdef __cplusplus +extern "C" { +#endif +/* __cplusplus */ + +#include + +#define AHBPROT_DISABLED ((*(vu32*)0xcd800064 == 0xFFFFFFFF) ? 1 : 0) + +u32 IOSPATCH_AHBPROT(); +u32 IOSPATCH_Apply(); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _IOSPATCH_H */ diff --git a/source/libpng/pngu/pngu.c b/source/libpng/pngu/pngu.c new file mode 100644 index 0000000..2f6a3b2 --- /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..12c3ad4 --- /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..8eee9dc --- /dev/null +++ b/source/menu.c @@ -0,0 +1,1382 @@ +#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" +#include "iospatch.h" + +/* FAT device list */ +//static fatDevice fdevList[] = { +fatDevice fdevList[] = { + { "sd", "Wii SD Slot", &__io_wiisd }, + { "usb", "USB Mass Storage Device", &__io_usbstorage }, + { "usb2", "USB 2.0 Mass Storage Device", &__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[] = { + { "Disable", 0, 0x00, 0x00 }, + { "SD/SDHC Card", 1, 0xF0, 0xF1 }, + { "USB 2.0 Mass Storage Device", 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, int Selected, int Start); +char *PopCurrentDir(int *Selected, int *Start); +bool IsListFull (void); +char *PeekCurrentDir (void); +u32 WaitButtons(void); +u32 Pad_GetButtons(void); +void WiiLightControl (int state); + +int __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; +} + + +int __Menu_EntryCmp(const void *p1, const void *p2) +{ + fatFile *f1 = (fatFile *)p1; + fatFile *f2 = (fatFile *)p2; + + /* Compare entries */ // wiiNinja: Include directory + if ((f1->isdir) && !(f2->isdir)) + return (-1); + else if (!(f1->isdir) && (f2->isdir)) + return (1); + else + return strcasecmp(f1->filename, f2->filename); +} + +static bool __FolderExists(const char *path) +{ + DIR *dir; + dir = opendir(path); + if(dir) + { + closedir(dir); + return true; + } + return false; +} + +static size_t __GetFileSizeBytes(const char *path) +{ + FILE *f; + size_t size = 0; + + f = fopen(path, "rb"); + if(!f) return 0; + + //Get file size + fseek(f, 0, SEEK_END); + size = ftell(f); + fclose(f); + + return size; +} + +char gFileName[MAX_FILE_PATH_LEN]; +s32 __Menu_RetrieveList(char *inPath, fatFile **outbuf, u32 *outlen) +{ + fatFile *buffer = NULL; + DIR *dir = NULL; + struct dirent *ent = NULL; + + //char dirpath[256], filename[768]; + u32 cnt; + + /* Generate dirpath */ + //sprintf(dirpath, "%s:" WAD_DIRECTORY, fdev->mount); + + /* Open directory */ + dir = opendir(inPath); + if (!dir) + return -1; + + /* Count entries */ + for (cnt = 0; ((ent = readdir(dir)) != NULL);) { + cnt++; + } + + if (cnt > 0) { + /* Allocate memory */ + buffer = malloc(sizeof(fatFile) * cnt); + if (!buffer) { + closedir(dir); + return -2; + } + + /* Reset directory */ + rewinddir(dir); + + /* Get entries */ + for (cnt = 0; ((ent = readdir(dir)) != NULL);) + { + bool addFlag = false; + bool isdir = false; + size_t fsize = 0; + + snprintf(gFileName, MAX_FILE_PATH_LEN, "%s/%s", inPath, ent->d_name); + if (__FolderExists(gFileName)) // wiiNinja + { + isdir = true; + // Add only the item ".." which is the previous directory + // AND if we're not at the root directory + if ((strcmp (ent->d_name, "..") == 0) && (gDirLevel > 1)) + addFlag = true; + else if (strcmp (ent->d_name, ".") != 0) + addFlag = true; + } + else + { + if(strlen(ent->d_name)>4) + { + if(!strcasecmp(ent->d_name+strlen(ent->d_name)-4, ".wad")) + { + fsize = __GetFileSizeBytes(gFileName); + addFlag = true; + } + } + } + + if (addFlag == true) + { + fatFile *file = &buffer[cnt++]; + + /* File name */ + strcpy(file->filename, ent->d_name); + + /* File stats */ + file->isdir = isdir; + file->fsize = fsize; + + } + } + + /* 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>> Select IOS version to use: < IOS%d >\n\n", iosVersion[selected]); + + printf("\t Press LEFT/RIGHT to change IOS version.\n\n"); + + printf("\t Press A button to continue.\n"); + printf("\t Press HOME button to restart.\n\n"); + + 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) +{ + int 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>> Select source device: < %s >\n\n", fdev->name); + + printf("\t Press LEFT/RIGHT to change the selected device.\n\n"); + + printf("\t Press A button to continue.\n"); + printf("\t Press HOME button to restart.\n\n"); + + 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("[+] Mounting %s, please wait...", fdev->name ); + fflush(stdout); + + /* Mount FAT device */ + + ret = Fat_Mount(fdev); + if (ret < 0) { + printf(" ERROR! (ret = %d)\n", ret); + goto err; + } else + printf(" OK!\n"); + + return; + +err: + + if(gConfig.fatDeviceIndex >= 0) gConfig.fatDeviceIndex = -1; + WiiLightControl (WII_LIGHT_OFF); + printf("\n"); + printf(" Press any button to continue...\n"); + + WaitButtons(); + + /* Prompt menu again */ + Menu_FatDevice(); +} + +void Menu_NandDevice(void) +{ + int 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>> Select NAND emulator device: < %s >\n\n", ndev->name); + + printf("\t Press LEFT/RIGHT to change the selected device.\n\n"); + + printf("\t Press A button to continue.\n"); + printf("\t Press HOME button to restart.\n\n"); + + 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("[+] Enabling NAND emulator..."); + fflush(stdout); + + /* Mount NAND device */ + ret = Nand_Mount(ndev); + if (ret < 0) { + printf(" ERROR! (ret = %d)\n", ret); + goto err; + } + + /* Enable NAND emulator */ + ret = Nand_Enable(ndev); + if (ret < 0) { + printf(" ERROR! (ret = %d)\n", ret); + goto err; + } else + printf(" OK!\n"); + + return; + +err: + printf("\n"); + printf(" Press any button to continue...\n"); + + 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 file%s marked for installation.\n", installCnt, (installCnt == 1) ? "" : "s"); + printf(" Do you want to proceed?\n"); + } + else if ((installCnt == 0) & (uninstallCnt > 0)) { + printf("[+] %d file%s marked for uninstallation.\n", uninstallCnt, (uninstallCnt == 1) ? "" : "s"); + printf(" Do you want to proceed?\n"); + } + else { + printf("[+] %d file%s marked for installation and %d file%s for uninstallation.\n", installCnt, (installCnt == 1) ? "" : "s", uninstallCnt, (uninstallCnt == 1) ? "" : "s"); + printf(" Do you want to proceed?\n"); + } + + printf("\n\n Press A to continue.\n"); + printf(" Press B to go back to the menu.\n\n"); + + 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("[+] Opening \"%s\", please wait...\n\n", thisFile->filename); + + sprintf(gTmpFilePath, "%s/%s", inFilePath, thisFile->filename); + + FILE *fp = fopen(gTmpFilePath, "rb"); + if (!fp) { + printf(" ERROR!\n"); + errors += 1; + continue; + } + + printf("[+] %s WAD, please wait...\n", (mode == 2) ? "Uninstalling" : "Installing"); + if (mode == 2) { + ret = Wad_Uninstall(fp); + } + else { + ret = Wad_Install(fp); + } + + if (ret < 0) errors += 1; + else success += 1; + + thisFile->installstate = ret; + + if (fp) + fclose(fp); + } + } + + WiiLightControl (WII_LIGHT_OFF); + + printf("\n"); + printf(" %d titles succeeded and %d failed...\n", success, errors); + + if (errors > 0) + { + printf("\n Some operations failed"); + printf("\n Press A to list.\n"); + printf(" Press B skip.\n"); + + 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 Skipped\n", str); + else if(thisFile->installstate == -106) printf(" %s Not installed?\n", str); + else if(thisFile->installstate == -1036 ) printf(" %s Needed IOS missing\n", str); + else if(thisFile->installstate == -4100 ) printf(" %s No trucha bug?\n", str); + else printf(" %s error %d\n", str, thisFile->installstate); + if( i == 17 ) + { + printf("\n Press any button to continue\n"); + WaitButtons(); + i = 0; + } + } + } + } + } + printf("\n Press any button to continue...\n"); + WaitButtons(); + + return 1; +} + +/* File Operations - Leathl */ +int Menu_FileOperations(fatFile *file, char *inFilePath) +{ + f32 filesize = (file->fsize / MB_SIZE); + + for (;;) + { + Con_Clear(); + + printf("[+] WAD Filename : %s\n", file->filename); + printf(" WAD Filesize : %.2f MB\n\n\n", filesize); + + + printf("[+] Select action: < %s WAD >\n\n", "Delete"); //There's yet nothing else than delete + + printf(" Press LEFT/RIGHT to change selected action.\n\n"); + + printf(" Press A to continue.\n"); + printf(" Press B to go back to the menu.\n\n"); + + u32 buttons = WaitButtons(); + + /* A button */ + if (buttons & WPAD_BUTTON_A) + break; + + /* B button */ + if (buttons & WPAD_BUTTON_B) + return 0; + } + + Con_Clear(); + + printf("[+] Deleting \"%s\", please wait...\n", file->filename); + + sprintf(gTmpFilePath, "%s/%s", inFilePath, file->filename); + int error = remove(gTmpFilePath); + if (error != 0) + printf(" ERROR!"); + else + printf(" Successfully deleted!"); + + printf("\n"); + printf(" Press any button to continue...\n"); + + WaitButtons(); + + return !error; +} + +void Menu_WadManage(fatFile *file, char *inFilePath) +{ + FILE *fp = NULL; + + //char filepath[128]; + f32 filesize; + + u32 mode = 0; + + /* File size in megabytes */ + filesize = (file->fsize / MB_SIZE); + + for (;;) { + /* Clear console */ + Con_Clear(); + + printf("[+] WAD Filename : %s\n", file->filename); + printf(" WAD Filesize : %.2f MB\n\n\n", filesize); + + + printf("[+] Select action: < %s WAD >\n\n", (!mode) ? "Install" : "Uninstall"); + + printf(" Press LEFT/RIGHT to change selected action.\n\n"); + + printf(" Press A to continue.\n"); + printf(" Press B to go back to the menu.\n\n"); + + 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; + } + + /* Clear console */ + Con_Clear(); + + printf("[+] Opening \"%s\", please wait...", 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(" ERROR!\n"); + goto out; + } else + printf(" OK!\n\n"); + + printf("[+] %s WAD, please wait...\n", (!mode) ? "Installing" : "Uninstalling"); + + /* Do install/uninstall */ + WiiLightControl (WII_LIGHT_ON); + if (!mode) + Wad_Install(fp); + else + Wad_Uninstall(fp); + WiiLightControl (WII_LIGHT_OFF); + +out: + /* Close file */ + if (fp) + fclose(fp); + + printf("\n"); + printf(" Press any button to continue...\n"); + + /* Wait for button */ + WaitButtons(); +} + +void Menu_WadList(void) +{ + char str [100]; + fatFile *fileList = NULL; + u32 fileCnt; + int 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) + { + ret = -997; // What am I gonna use here? + printf(" ERROR! Out of memory (ret = %d)\n", ret); + return; + } + + printf("[+] Retrieving file list..."); + fflush(stdout); + + gDirLevel = 0; + + // push root dir as base folder + sprintf(tmpPath, "%s:%s", fdev->mount, WAD_DIRECTORY); + PushCurrentDir(tmpPath,0,0); + // if user provides startup directory, try it out first + if (strcmp (WAD_DIRECTORY, gConfig.startupPath) != 0) + { + // replace root dir with provided startup directory + sprintf(tmpPath, "%s:%s", fdev->mount, gConfig.startupPath); + // If the directory can be successfully opened, it must exists + DIR *tmpDirPtr = opendir(tmpPath); + if (tmpDirPtr) + { + closedir (tmpDirPtr); + PushCurrentDir(tmpPath,0,0); + } + else // unable to open provided dir, stick with root dir + sprintf(tmpPath, "%s:%s", fdev->mount, WAD_DIRECTORY); + } + + /* Retrieve filelist */ +getList: + if (fileList) + { + free (fileList); + fileList = NULL; + } + + ret = __Menu_RetrieveList(tmpPath, &fileList, &fileCnt); + if (ret < 0) { + printf(" ERROR! (ret = %d)\n", ret); + goto err; + } + + /* No files */ + if (!fileCnt) { + printf(" No files found!\n"); + goto err; + } + + /* Set install-values to 0 - Leathl */ + int counter; + for (counter = 0; counter < fileCnt; counter++) { + fatFile *file = &fileList[counter]; + file->install = 0; + } + + for (;;) + { + u32 cnt; + s32 index; + + /* Clear console */ + Con_Clear(); + + /** Print entries **/ + cnt = strlen(tmpPath); + if(cnt>30) + index = cnt-30; + else + index = 0; + + printf("[+] WAD files on [%s]:\n\n", tmpPath+index); + + /* Print entries */ + for (cnt = start; cnt < fileCnt; cnt++) + { + fatFile *file = &fileList[cnt]; + f32 filesize = file->fsize / MB_SIZE; + + /* 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 */ + //printf("\t%2s %s (%.2f MB)\n", (cnt == selected) ? ">>" : " ", file->filename, filesize); + if (file->isdir) // wiiNinja + printf("\t%2s [%s]\n", (cnt == selected) ? ">>" : " ", str); + else + printf("\t%2s%s%s (%.2f MB)\n", (cnt == selected) ? ">>" : " ", (file->install == 1) ? "+" : ((file->install == 2) ? "-" : " "), str, filesize); + + } + + printf("\n"); + + printf("[+] Press A to (un)install."); + if(gDirLevel>1) + printf(" Press B to go up-level DIR.\n"); + else + printf(" Press B to select a device.\n"); + printf(" Use +/X and -/Y to (un)mark. Press 1/Z/ZR for delete menu."); + + /** Controls **/ + u32 buttons = WaitButtons(); + + /* DPAD buttons */ + if (buttons & WPAD_BUTTON_UP) { + selected--; + + if (selected <= -1) + selected = (fileCnt - 1); + } + if (buttons & WPAD_BUTTON_LEFT) { + selected = selected + ENTRIES_PER_PAGE; + + if (selected >= fileCnt) + selected = 0; + } + if (buttons & WPAD_BUTTON_DOWN) { + selected ++; + + if (selected >= fileCnt) + selected = 0; + } + if (buttons & WPAD_BUTTON_RIGHT) { + selected = selected - ENTRIES_PER_PAGE; + + if (selected <= -1) + selected = (fileCnt - 1); + } + + /* 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->isdir) == false) & (file->install == 0)) { + file->install = 1; + + installCnt += 1; + } + else if (((file->isdir) == false) & (file->install == 1)) { + file->install = 0; + + installCnt -= 1; + } + else if (((file->isdir) == false) & (file->install == 2)) { + file->install = 1; + + installCnt += 1; + uninstallCnt -= 1; + } + i++; + } + + } + else + { + fatFile *file = &fileList[selected]; + if (((file->isdir) == false) & (file->install == 0)) { + file->install = 1; + + installCnt += 1; + } + else if (((file->isdir) == false) & (file->install == 1)) { + file->install = 0; + + installCnt -= 1; + } + else if (((file->isdir) == 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->isdir) == false) & (file->install == 0)) { + file->install = 2; + + uninstallCnt += 1; + } + else if (((file->isdir) == false) & (file->install == 1)) { + file->install = 2; + + uninstallCnt += 1; + installCnt -= 1; + } + else if (((file->isdir) == false) & (file->install == 2)) { + file->install = 0; + + uninstallCnt -= 1; + } + i++; + } + + } + else + { + fatFile *file = &fileList[selected]; + if (((file->isdir) == false) & (file->install == 0)) { + file->install = 2; + + uninstallCnt += 1; + } + else if (((file->isdir) == false) & (file->install == 1)) { + file->install = 2; + + uninstallCnt += 1; + installCnt -= 1; + } + else if (((file->isdir) == 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]; + char *tmpCurPath = PeekCurrentDir (); + if (tmpCurPath != NULL) { + 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->isdir) // 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 ("Maximum number of directory levels is reached.\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(" Press any button to continue...\n"); + + free (tmpPath); + + /* Wait for button */ + WaitButtons(); +} + + +void Menu_Loop(void) +{ + u8 iosVersion; + if(AHBPROT_DISABLED) + IOSPATCH_Apply(); + else + { + /* Select IOS menu */ + 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, int Selected, int 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(int *Selected, int *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(" Press any button to continue...\n"); + + /* 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; +} + +u32 WiiDRC_GetButtons(void) +{ + if(!WiiDRC_Inited() || !WiiDRC_Connected()) + return 0; + + /* Scan pads */ + WiiDRC_ScanPads(); + + /* Get pressed buttons */ + return WiiDRC_ButtonsDown(); +} + +// 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; + u32 buttonsDRC = 0; + + /* Wait for button pressing */ + while (!buttons && !buttonsGC && !buttonsDRC) + { + // Wii buttons + buttons = Wpad_GetButtons(); + + // GC buttons + buttonsGC = Pad_GetButtons(); + + // DRC buttons + buttonsDRC = WiiDRC_GetButtons(); + + VIDEO_WaitVSync(); + } + + if(buttons & WPAD_CLASSIC_BUTTON_A) + buttons |= WPAD_BUTTON_A; + else if(buttons & WPAD_CLASSIC_BUTTON_B) + buttons |= WPAD_BUTTON_B; + else if(buttons & WPAD_CLASSIC_BUTTON_LEFT) + buttons |= WPAD_BUTTON_LEFT; + else if(buttons & WPAD_CLASSIC_BUTTON_RIGHT) + buttons |= WPAD_BUTTON_RIGHT; + else if(buttons & WPAD_CLASSIC_BUTTON_DOWN) + buttons |= WPAD_BUTTON_DOWN; + else if(buttons & WPAD_CLASSIC_BUTTON_UP) + buttons |= WPAD_BUTTON_UP; + else if(buttons & WPAD_CLASSIC_BUTTON_HOME) + buttons |= WPAD_BUTTON_HOME; + else if(buttons & (WPAD_CLASSIC_BUTTON_X | WPAD_CLASSIC_BUTTON_PLUS)) + buttons |= WPAD_BUTTON_PLUS; + else if(buttons & (WPAD_CLASSIC_BUTTON_Y | WPAD_CLASSIC_BUTTON_MINUS)) + buttons |= WPAD_BUTTON_MINUS; + else if(buttons & WPAD_CLASSIC_BUTTON_ZR) + buttons |= WPAD_BUTTON_1; + + if (buttonsGC) + { + if(buttonsGC & PAD_BUTTON_A) + buttons |= WPAD_BUTTON_A; + else if(buttonsGC & PAD_BUTTON_B) + buttons |= WPAD_BUTTON_B; + else if(buttonsGC & PAD_BUTTON_LEFT) + buttons |= WPAD_BUTTON_LEFT; + else if(buttonsGC & PAD_BUTTON_RIGHT) + buttons |= WPAD_BUTTON_RIGHT; + else if(buttonsGC & PAD_BUTTON_DOWN) + buttons |= WPAD_BUTTON_DOWN; + else if(buttonsGC & PAD_BUTTON_UP) + buttons |= WPAD_BUTTON_UP; + else if(buttonsGC & PAD_BUTTON_START) + buttons |= WPAD_BUTTON_HOME; + else if(buttonsGC & PAD_BUTTON_X) + buttons |= WPAD_BUTTON_PLUS; + else if(buttonsGC & PAD_BUTTON_Y) + buttons |= WPAD_BUTTON_MINUS; + else if(buttonsGC & PAD_TRIGGER_Z) + buttons |= WPAD_BUTTON_1; + } + + if (buttonsDRC) + { + if(buttonsDRC & WIIDRC_BUTTON_A) + buttons |= WPAD_BUTTON_A; + else if(buttonsDRC & WIIDRC_BUTTON_B) + buttons |= WPAD_BUTTON_B; + else if(buttonsDRC & WIIDRC_BUTTON_LEFT) + buttons |= WPAD_BUTTON_LEFT; + else if(buttonsDRC & WIIDRC_BUTTON_RIGHT) + buttons |= WPAD_BUTTON_RIGHT; + else if(buttonsDRC & WIIDRC_BUTTON_DOWN) + buttons |= WPAD_BUTTON_DOWN; + else if(buttonsDRC & WIIDRC_BUTTON_UP) + buttons |= WPAD_BUTTON_UP; + else if(buttonsDRC & WIIDRC_BUTTON_HOME) + buttons |= WPAD_BUTTON_HOME; + else if(buttonsDRC & (WIIDRC_BUTTON_X | WIIDRC_BUTTON_PLUS)) + buttons |= WPAD_BUTTON_PLUS; + else if(buttonsDRC & (WIIDRC_BUTTON_Y | WIIDRC_BUTTON_MINUS)) + buttons |= WPAD_BUTTON_MINUS; + else if(buttonsDRC & WIIDRC_BUTTON_ZR) + buttons |= WPAD_BUTTON_1; + } + + 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..2f774d0 --- /dev/null +++ b/source/mload.c @@ -0,0 +1,381 @@ +/* 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, ":"); + + +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); + + +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: + +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); + + +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); + + +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); + + +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); + + +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; + +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); + + +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..d979817 --- /dev/null +++ b/source/restart.c @@ -0,0 +1,36 @@ +#include +#include + +#include "nand.h" +#include "sys.h" +#include "wpad.h" +#include "video.h" + + +void Restart(void) +{ + Con_Clear (); + printf("\n Restarting Wii..."); + fflush(stdout); + + /* Disable NAND emulator */ + Nand_Disable(); + + /* Load system menu */ + Sys_LoadMenu(); +} + +void Restart_Wait(void) +{ + printf("\n"); + + printf(" Press any button to restart..."); + 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..ebb8fb4 --- /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..d0d3432 --- /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..6c6cec5 --- /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..5fd492a --- /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) +{ + int 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[%lu;%um", color + 30, bold); + fflush(stdout); +} + +void Con_BgColor(u32 color, u8 bold) +{ + /* Set background color */ + printf("\x1b[%lu;%um", color + 40, bold); + fflush(stdout); +} + +void Con_FillRow(u32 row, u32 color, u8 bold) +{ + int cols, rows; + u32 cnt; + + /* Set color */ + printf("\x1b[%lu;%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[%lu;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..f3f768a --- /dev/null +++ b/source/wad-manager.c @@ -0,0 +1,426 @@ +#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 "iospatch.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("[+] [Enter Password to Continue]:\n\n"); + + printf(">> Press A to continue.\n"); + printf(">> Press B button to restart your Wii.\n"); + + /* 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(">> Password Accepted...\n"); + break; + } + else + { + printf ("\n"); + printf(">> Incorrect Password. Try again...\n"); + printf("[+] [Enter Password to Continue]:\n\n"); + printf(">> Press A to continue.\n"); + printf(">> Press B button to restart your Wii.\n"); + 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(">> If you agree, press A button to continue.\n"); + printf(">> Otherwise, press B button to restart your Wii.\n"); + + /* 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(); + } +} + +int main(int argc, char **argv) +{ + ES_GetBoot2Version(&boot2version); + if(!AHBPROT_DISABLED) + { + 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(); + + /* Initialize Wiimote and GC Controller */ + Wpad_Init(); + PAD_Init (); + WiiDRC_Init(); + WIILIGHT_Init(); + + /* Print disclaimer */ + //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]; + int 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(" ERROR! (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 ("Password longer than 10 characters; will be ignored. Press a button...\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_ROOT_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..36aee55 --- /dev/null +++ b/source/wad.c @@ -0,0 +1,677 @@ +#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)) +// Turn upper and lower into a full title ID +#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)) + +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/%08lx/%08lx/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; + + 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) +{ + 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; + int ret; + u64 tid; + + printf("\t\t>> Reading WAD data..."); + 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 I can't let you do that Dave\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) != 0 && isIOSstub(TITLE_LOWER(tmd_data->sys_version))) + { + printf("\n This Title wants IOS%li but the installed version\n is a 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 I won't install a stub System Menu IOS\n"); + 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 I won't install a stub EULA IOS\n"); + 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 I won't install a stub rgsel IOS\n"); + 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 Are you sure you wan't to install a stub HBC IOS?\n"); + printf("\n Press A to continue.\n"); + printf(" Press B skip."); + + 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 Can't get the SM region\n Please check the site for updates\n"); + 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 Can't get the SM region\n Please check the site for updates\n"); + ret = -999; + goto err; + } + if( get_sm_region_basic() != regionlist[i].region) + { + printf("\n I won't install the wrong regions SM\n"); + ret = -999; + goto err; + } + if(tmd_data->title_version < 416) + { + if(boot2version == 4) + { + printf("\n This version of the System Menu\n is not compatible with your Wii\n"); + ret = -999; + goto err; + } + } + } + + /* Fix ticket */ + __Wad_FixTicket(p_tik); + + printf("\t\t>> Installing 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>> Installing 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>> Installing content #%02ld...", 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>> Finishing installation..."); + fflush(stdout); + + /* Finish title install */ + ret = ES_AddTitleFinish(); + if (ret >= 0) { + printf(" OK!\n"); + goto out; + } + +err: + printf(" ERROR! (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; + int 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; +} diff --git a/source/wad.h b/source/wad.h new file mode 100644 index 0000000..f5b2586 --- /dev/null +++ b/source/wad.h @@ -0,0 +1,8 @@ +#ifndef _WAD_H_ +#define _WAD_H_ + +/* Prototypes */ +s32 Wad_Install(FILE *); +s32 Wad_Uninstall(FILE *); + +#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/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