From 676a53462f678aab4eeefbca6270644ac4916303 Mon Sep 17 00:00:00 2001 From: dborth Date: Fri, 9 Oct 2009 23:13:17 +0000 Subject: [PATCH] finalize 1.0 --- Makefile.wii | 38 +- README | 71 + src/gui/sdlmain.cpp | 2 +- src/platform/wii/FreeTypeGX.cpp | 718 ++++++++++ src/platform/wii/FreeTypeGX.h | 298 ++++ src/platform/wii/Metaphrasis.cpp | 383 +++++ src/platform/wii/Metaphrasis.h | 116 ++ src/platform/wii/config.h | 2 +- src/platform/wii/filelist.h | 106 ++ src/platform/wii/fonts/font.ttf | Bin 0 -> 29244 bytes src/platform/wii/images/battery.png | Bin 0 -> 216 bytes src/platform/wii/images/battery_bar.png | Bin 0 -> 145 bytes src/platform/wii/images/battery_red.png | Bin 0 -> 230 bytes src/platform/wii/images/bg_bottom.png | Bin 0 -> 20424 bytes src/platform/wii/images/bg_top.png | Bin 0 -> 16161 bytes src/platform/wii/images/button.png | Bin 0 -> 3977 bytes src/platform/wii/images/button_large.png | Bin 0 -> 6488 bytes src/platform/wii/images/button_large_over.png | Bin 0 -> 6447 bytes src/platform/wii/images/button_over.png | Bin 0 -> 3944 bytes src/platform/wii/images/button_small.png | Bin 0 -> 2008 bytes src/platform/wii/images/button_small_over.png | Bin 0 -> 2002 bytes src/platform/wii/images/credits_box.png | Bin 0 -> 60892 bytes src/platform/wii/images/keyboard_key.png | Bin 0 -> 981 bytes src/platform/wii/images/keyboard_key_over.png | Bin 0 -> 929 bytes src/platform/wii/images/keyboard_largekey.png | Bin 0 -> 2795 bytes .../wii/images/keyboard_largekey_over.png | Bin 0 -> 2733 bytes .../wii/images/keyboard_mediumkey.png | Bin 0 -> 1434 bytes .../wii/images/keyboard_mediumkey_over.png | Bin 0 -> 1427 bytes src/platform/wii/images/keyboard_textbox.png | Bin 0 -> 4719 bytes src/platform/wii/images/logo.png | Bin 0 -> 6697 bytes src/platform/wii/images/logo_over.png | Bin 0 -> 9519 bytes src/platform/wii/images/player1_point.png | Bin 0 -> 2339 bytes src/platform/wii/images/player2_point.png | Bin 0 -> 2500 bytes src/platform/wii/images/player3_point.png | Bin 0 -> 2527 bytes src/platform/wii/images/player4_point.png | Bin 0 -> 2474 bytes src/platform/wii/input.cpp | 100 ++ src/platform/wii/input.h | 26 + src/platform/wii/libwiigui/gui.h | 783 ++++++++++ src/platform/wii/libwiigui/gui_button.cpp | 306 ++++ src/platform/wii/libwiigui/gui_element.cpp | 570 ++++++++ src/platform/wii/libwiigui/gui_image.cpp | 242 ++++ src/platform/wii/libwiigui/gui_imagedata.cpp | 84 ++ src/platform/wii/libwiigui/gui_keyboard.cpp | 362 +++++ src/platform/wii/libwiigui/gui_sound.cpp | 143 ++ src/platform/wii/libwiigui/gui_text.cpp | 348 +++++ src/platform/wii/libwiigui/gui_trigger.cpp | 249 ++++ src/platform/wii/libwiigui/gui_window.cpp | 404 ++++++ src/platform/wii/menu.cpp | 591 ++++++++ src/platform/wii/oggplayer.c | 539 +++++++ src/platform/wii/oggplayer.h | 177 +++ src/platform/wii/pngu.c | 1266 +++++++++++++++++ src/platform/wii/pngu.h | 176 +++ src/platform/wii/sounds/button_click.pcm | Bin 0 -> 7204 bytes src/platform/wii/sounds/button_over.pcm | Bin 0 -> 2876 bytes src/platform/wii/sounds/enter.ogg | Bin 0 -> 59779 bytes src/platform/wii/sounds/exit.ogg | Bin 0 -> 51898 bytes src/platform/wii/wiihardware.cpp | 210 +++ src/platform/wii/wiihardware.h | 7 + src/platform/wii/wiivideo.cpp | 197 +++ src/platform/wii/wiivideo.h | 26 + 60 files changed, 8533 insertions(+), 7 deletions(-) create mode 100644 src/platform/wii/FreeTypeGX.cpp create mode 100644 src/platform/wii/FreeTypeGX.h create mode 100644 src/platform/wii/Metaphrasis.cpp create mode 100644 src/platform/wii/Metaphrasis.h create mode 100644 src/platform/wii/filelist.h create mode 100644 src/platform/wii/fonts/font.ttf create mode 100644 src/platform/wii/images/battery.png create mode 100644 src/platform/wii/images/battery_bar.png create mode 100644 src/platform/wii/images/battery_red.png create mode 100644 src/platform/wii/images/bg_bottom.png create mode 100644 src/platform/wii/images/bg_top.png create mode 100644 src/platform/wii/images/button.png create mode 100644 src/platform/wii/images/button_large.png create mode 100644 src/platform/wii/images/button_large_over.png create mode 100644 src/platform/wii/images/button_over.png create mode 100644 src/platform/wii/images/button_small.png create mode 100644 src/platform/wii/images/button_small_over.png create mode 100644 src/platform/wii/images/credits_box.png create mode 100644 src/platform/wii/images/keyboard_key.png create mode 100644 src/platform/wii/images/keyboard_key_over.png create mode 100644 src/platform/wii/images/keyboard_largekey.png create mode 100644 src/platform/wii/images/keyboard_largekey_over.png create mode 100644 src/platform/wii/images/keyboard_mediumkey.png create mode 100644 src/platform/wii/images/keyboard_mediumkey_over.png create mode 100644 src/platform/wii/images/keyboard_textbox.png create mode 100644 src/platform/wii/images/logo.png create mode 100644 src/platform/wii/images/logo_over.png create mode 100644 src/platform/wii/images/player1_point.png create mode 100644 src/platform/wii/images/player2_point.png create mode 100644 src/platform/wii/images/player3_point.png create mode 100644 src/platform/wii/images/player4_point.png create mode 100644 src/platform/wii/input.cpp create mode 100644 src/platform/wii/input.h create mode 100644 src/platform/wii/libwiigui/gui.h create mode 100644 src/platform/wii/libwiigui/gui_button.cpp create mode 100644 src/platform/wii/libwiigui/gui_element.cpp create mode 100644 src/platform/wii/libwiigui/gui_image.cpp create mode 100644 src/platform/wii/libwiigui/gui_imagedata.cpp create mode 100644 src/platform/wii/libwiigui/gui_keyboard.cpp create mode 100644 src/platform/wii/libwiigui/gui_sound.cpp create mode 100644 src/platform/wii/libwiigui/gui_text.cpp create mode 100644 src/platform/wii/libwiigui/gui_trigger.cpp create mode 100644 src/platform/wii/libwiigui/gui_window.cpp create mode 100644 src/platform/wii/menu.cpp create mode 100644 src/platform/wii/oggplayer.c create mode 100644 src/platform/wii/oggplayer.h create mode 100644 src/platform/wii/pngu.c create mode 100644 src/platform/wii/pngu.h create mode 100644 src/platform/wii/sounds/button_click.pcm create mode 100644 src/platform/wii/sounds/button_over.pcm create mode 100644 src/platform/wii/sounds/enter.ogg create mode 100644 src/platform/wii/sounds/exit.ogg create mode 100644 src/platform/wii/wiivideo.cpp create mode 100644 src/platform/wii/wiivideo.h diff --git a/Makefile.wii b/Makefile.wii index 4b75c8c..c7ece3c 100644 --- a/Makefile.wii +++ b/Makefile.wii @@ -20,7 +20,9 @@ TARGETDIR := executables BUILD := build_wii SOURCES := src src/cpu src/debug src/dos src/fpu src/gui \ src/hardware src/hardware/serialport src/ints src/libs \ - src/misc src/platform/wii src/shell + src/misc src/platform/wii src/platform/wii/fonts \ + src/platform/wii/images src/platform/wii/libwiigui \ + src/platform/wii/sounds src/shell INCLUDES := include src/platform/wii #--------------------------------------------------------------------------------- @@ -35,13 +37,14 @@ LDFLAGS = -g $(MACHDEP) -Wl,-Map,$(notdir $@).map -Wl,--cref #--------------------------------------------------------------------------------- # any extra libraries we wish to link with #--------------------------------------------------------------------------------- -LIBS := -lSDL -lfat -lwiiuse -lbte -logc -lwiikeyboard +LIBS := -lSDL -lfat -lwiiuse -lbte -lasnd -logc -lwiikeyboard \ + -lpng -lvorbisidec -lfreetype -lz #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing # include and lib #--------------------------------------------------------------------------------- -LIBDIRS := $(CURDIR) +LIBDIRS := $(PORTLIBS) #--------------------------------------------------------------------------------- # no real need to edit anything past this point unless you need to add additional @@ -64,6 +67,10 @@ 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))) +TTFFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.ttf))) +PNGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.png))) +OGGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.ogg))) +PCMFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.pcm))) #--------------------------------------------------------------------------------- # use CXX for linking C++ projects, CC for standard C @@ -75,7 +82,9 @@ else endif export OFILES := $(addsuffix .o,$(BINFILES)) \ - $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \ + $(TTFFILES:.ttf=.ttf.o) $(PNGFILES:.png=.png.o) \ + $(OGGFILES:.ogg=.ogg.o) $(PCMFILES:.pcm=.pcm.o) #--------------------------------------------------------------------------------- # build a list of include paths @@ -83,7 +92,7 @@ export OFILES := $(addsuffix .o,$(BINFILES)) \ export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ -I$(CURDIR)/$(BUILD) \ - -I$(LIBOGC_INC) -I$(LIBOGC_INC)/SDL + -I$(LIBOGC_INC) -I$(LIBOGC_INC)/SDL -I$(PORTLIBS)/include/freetype2 #--------------------------------------------------------------------------------- # build a list of library paths @@ -124,6 +133,25 @@ DEPENDS := $(OFILES:.o=.d) $(OUTPUT).dol: $(OUTPUT).elf $(OUTPUT).elf: $(OFILES) +#--------------------------------------------------------------------------------- +# This rule links in binary data with .ttf, .png, and .mp3 extensions +#--------------------------------------------------------------------------------- +%.ttf.o : %.ttf + @echo $(notdir $<) + $(bin2o) + +%.png.o : %.png + @echo $(notdir $<) + $(bin2o) + +%.ogg.o : %.ogg + @echo $(notdir $<) + $(bin2o) + +%.pcm.o : %.pcm + @echo $(notdir $<) + $(bin2o) + -include $(DEPENDS) #--------------------------------------------------------------------------------- diff --git a/README b/README index 2897067..c63bcc2 100644 --- a/README +++ b/README @@ -1,3 +1,74 @@ +¸,ø¤°`°¤ø,¸¸,ø¤°`°¤ø,¸,ø¤°`°¤ø,¸¸,ø¤°`°¤ø,¸,ø¤°`°¤ø,¸¸,ø¤°`°¤ø,¸,ø¤°`°¤ø,¸¸,ø¤ + + - DOSBox Wii - + Version 1.0 + http://code.google.com/p/dosbox-wii + (Under GPL License) + +¸,ø¤°`°¤ø,¸¸,ø¤°`°¤ø,¸,ø¤°`°¤ø,¸¸,ø¤°`°¤ø,¸,ø¤°`°¤ø,¸¸,ø¤°`°¤ø,¸,ø¤°`°¤ø,¸¸,ø¤ + +A port of DOSBox to the Wii using SDL Wii. + +-=[ Features ]=- + +* USB Keyboard and mouse support +* Wiimote pointer support +* SD/USB mounting +* Most DOS games are playable + +-=[ Update History ]=- + +[1.0 - October 9, 2009] + +* Home menu and on-screen keyboard (a proof of concept showing SDL + libwiigui) +* Small speed increases via SDL Wii improvements +* Compiled with devkitPPC r18 and libogc 1.8.0 +* Updated to latest DOSBox SVN + +[0.73.02 - July 1, 2009] + +* Upgraded DOSBox engine to latest SVN +* Fixed lockup when only using the Wiimote (no attachments connected) +* Compiled with latest libogc and SDL Wii + +[0.73.01 - June 3, 2009] + +* Upgraded DOSBox engine to 0.73 +* Sound issues fixed +* Compiled with latest libogc and SDL Wii + +[0.72.01 - May 14, 2009] + +* Initial release + +-=[ Instructions ]=- + +You need a Wii Remote and a USB keyboard. The C: drive will automatically be +mounted to sd:/DOSBox/ if loaded from apps/dosbox-wii/. Otherwise, the +directory the dol is loaded from will be mounted as C, and sd:/DOSBox as D +(if present). The Z: driver is a virtual drive that is part of DOSBox. + +Other drives can be mounted using the MOUNT command. Prefix sd: for an SD card, +and usb: for a USB drive. The Wii's DVD drive and network folders can't be +mounted at this time. + +The configuration file is loaded from the directory the dol is located in +(if present) and sd:/DOSBox/dosbox.conf otherwise. This file will be created +automatically after you start DOSBox. Please edit it with a text editor +to choose settings appropriate for each game. + +Press the Home button, type "exit", press Ctrl+F9, or press Reset (on the +console) to exit. + +-=[ Compatibility ]=- + +Most games work properly now; however, many games will require some sort +of adjustment to the dosbox.conf file. It is recommended to try the game +you want to play on the PC version of DOSBox first to discover the optimum +settings. + +------------------------------------------------------------------------------- + DOSBox v0.73 diff --git a/src/gui/sdlmain.cpp b/src/gui/sdlmain.cpp index da1e0c2..e54964d 100644 --- a/src/gui/sdlmain.cpp +++ b/src/gui/sdlmain.cpp @@ -1330,7 +1330,7 @@ void GFX_Events() { { btns = WPAD_ButtonsHeld(i); if((btns & WPAD_BUTTON_HOME) || (btns & WPAD_CLASSIC_BUTTON_HOME)) - throw(0); + WiiMenu (); } #endif diff --git a/src/platform/wii/FreeTypeGX.cpp b/src/platform/wii/FreeTypeGX.cpp new file mode 100644 index 0000000..5a004ed --- /dev/null +++ b/src/platform/wii/FreeTypeGX.cpp @@ -0,0 +1,718 @@ +/* + * FreeTypeGX is a wrapper class for libFreeType which renders a compiled + * FreeType parsable font into a GX texture for Wii homebrew development. + * Copyright (C) 2008 Armin Tamzarian + * Modified by Tantric, 2009 + * + * This file is part of FreeTypeGX. + * + * FreeTypeGX is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * FreeTypeGX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FreeTypeGX. If not, see . + */ + +#include "FreeTypeGX.h" + +static FT_Library ftLibrary; /**< FreeType FT_Library instance. */ +static FT_Face ftFace; /**< FreeType reusable FT_Face typographic object. */ +static FT_GlyphSlot ftSlot; /**< FreeType reusable FT_GlyphSlot glyph container object. */ + +FreeTypeGX *fontSystem[MAX_FONT_SIZE+1]; + +void InitFreeType(uint8_t* fontBuffer, FT_Long bufferSize) +{ + FT_Init_FreeType(&ftLibrary); + FT_New_Memory_Face(ftLibrary, (FT_Byte *)fontBuffer, bufferSize, 0, &ftFace); + ftSlot = ftFace->glyph; + + for(int i=0; i<50; i++) + fontSystem[i] = NULL; +} + +void ChangeFontSize(FT_UInt pixelSize) +{ + FT_Set_Pixel_Sizes(ftFace, 0, pixelSize); +} + +void ClearFontData() +{ + for(int i=0; i<50; i++) + { + if(fontSystem[i]) + delete fontSystem[i]; + fontSystem[i] = NULL; + } +} + +/** + * Convert a short char sctring to a wide char string. + * + * This routine converts a supplied shot character string into a wide character string. + * Note that it is the user's responsibility to clear the returned buffer once it is no longer needed. + * + * @param strChar Character string to be converted. + * @return Wide character representation of supplied character string. + */ + +wchar_t* charToWideChar(const char* strChar) +{ + wchar_t *strWChar; + strWChar = new wchar_t[strlen(strChar) + 1]; + + char *tempSrc = (char *)strChar; + wchar_t *tempDest = strWChar; + while((*tempDest++ = *tempSrc++)); + + return strWChar; +} + +/** + * Default constructor for the FreeTypeGX class. + * + * @param textureFormat Optional format (GX_TF_*) of the texture as defined by the libogc gx.h header file. If not specified default value is GX_TF_RGBA8. + * @param vertexIndex Optional vertex format index (GX_VTXFMT*) of the glyph textures as defined by the libogc gx.h header file. If not specified default value is GX_VTXFMT1. + */ +FreeTypeGX::FreeTypeGX(FT_UInt pixelSize, uint8_t textureFormat, uint8_t vertexIndex) +{ + this->textureFormat = textureFormat; + this->setVertexFormat(vertexIndex); + this->setCompatibilityMode(FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_PASSCLR | FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_NONE); + this->ftPointSize = pixelSize; + this->ftKerningEnabled = FT_HAS_KERNING(ftFace); +} + +/** + * Default destructor for the FreeTypeGX class. + */ +FreeTypeGX::~FreeTypeGX() +{ + this->unloadFont(); +} + +/** + * Setup the vertex attribute formats for the glyph textures. + * + * This function sets up the vertex format for the glyph texture on the specified vertex format index. + * Note that this function should not need to be called except if the vertex formats are cleared or the specified + * vertex format index is modified. + * + * @param vertexIndex Vertex format index (GX_VTXFMT*) of the glyph textures as defined by the libogc gx.h header file. +*/ +void FreeTypeGX::setVertexFormat(uint8_t vertexIndex) +{ + this->vertexIndex = vertexIndex; + GX_SetVtxAttrFmt(this->vertexIndex, GX_VA_POS, GX_POS_XY, GX_S16, 0); + GX_SetVtxAttrFmt(this->vertexIndex, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); + GX_SetVtxAttrFmt(this->vertexIndex, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); +} + +/** + * Sets the TEV and VTX rendering compatibility requirements for the class. + * + * This sets up the default TEV opertion and VTX descriptions rendering values for the class. This ensures that FreeTypeGX + * can remain compatible with external liraries or project code. Certain external libraries or code by design or lack of + * foresight assume that the TEV opertion and VTX descriptions values will remain constant or are always returned to a + * certain value. This will enable compatibility with those libraries and any other code which cannot or will not be changed. + * + * @param compatibilityMode Compatibility descritor (FTGX_COMPATIBILITY_*) as defined in FreeTypeGX.h +*/ +void FreeTypeGX::setCompatibilityMode(uint32_t compatibilityMode) +{ + this->compatibilityMode = compatibilityMode; +} + +/** + * Sets the TEV operation and VTX descriptor values after texture rendering it complete. + * + * This function calls the GX_SetTevOp and GX_SetVtxDesc functions with the compatibility parameters specified + * in setCompatibilityMode. + */ +void FreeTypeGX::setDefaultMode() +{ + if(this->compatibilityMode) + { + switch(this->compatibilityMode & 0x00FF) + { + case FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_MODULATE: + GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE); + break; + case FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_DECAL: + GX_SetTevOp(GX_TEVSTAGE0, GX_DECAL); + break; + case FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_BLEND: + GX_SetTevOp(GX_TEVSTAGE0, GX_BLEND); + break; + case FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_REPLACE: + GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE); + break; + case FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_PASSCLR: + GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR); + break; + default: + break; + } + + switch(this->compatibilityMode & 0xFF00) + { + case FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_NONE: + GX_SetVtxDesc(GX_VA_TEX0, GX_NONE); + break; + case FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_DIRECT: + GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); + break; + case FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_INDEX8: + GX_SetVtxDesc(GX_VA_TEX0, GX_INDEX8); + break; + case FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_INDEX16: + GX_SetVtxDesc(GX_VA_TEX0, GX_INDEX16); + break; + default: + break; + } + } +} + +/** + * Clears all loaded font glyph data. + * + * This routine clears all members of the font map structure and frees all allocated memory back to the system. + */ +void FreeTypeGX::unloadFont() +{ + if(this->fontData.size() == 0) + return; + for(std::map::iterator i = this->fontData.begin(); i != this->fontData.end(); i++) + free(i->second.glyphDataTexture); + this->fontData.clear(); +} + +/** + * Adjusts the texture data buffer to necessary width for a given texture format. + * + * This routine determines adjusts the given texture width into the required width to hold the necessary texture data for proper alignment. + * + * @param textureWidth The initial guess for the texture width. + * @param textureFormat The texture format to which the data is to be converted. + * @return The correctly adjusted texture width. + */ +uint16_t FreeTypeGX::adjustTextureWidth(uint16_t textureWidth, uint8_t textureFormat) +{ + uint16_t alignment; + + switch(textureFormat) + { + case GX_TF_I4: /* 8x8 Tiles - 4-bit Intensity */ + case GX_TF_I8: /* 8x4 Tiles - 8-bit Intensity */ + case GX_TF_IA4: /* 8x4 Tiles - 4-bit Intensity, , 4-bit Alpha */ + alignment = 8; + break; + + case GX_TF_IA8: /* 4x4 Tiles - 8-bit Intensity, 8-bit Alpha */ + case GX_TF_RGB565: /* 4x4 Tiles - RGB565 Format */ + case GX_TF_RGB5A3: /* 4x4 Tiles - RGB5A3 Format */ + case GX_TF_RGBA8: /* 4x4 Tiles - RGBA8 Dual Cache Line Format */ + default: + alignment = 4; + break; + } + return textureWidth % alignment == 0 ? textureWidth : alignment + textureWidth - (textureWidth % alignment); +} + +/** + * Adjusts the texture data buffer to necessary height for a given texture format. + * + * This routine determines adjusts the given texture height into the required height to hold the necessary texture data for proper alignment. + * + * @param textureHeight The initial guess for the texture height. + * @param textureFormat The texture format to which the data is to be converted. + * @return The correctly adjusted texture height. + */ +uint16_t FreeTypeGX::adjustTextureHeight(uint16_t textureHeight, uint8_t textureFormat) +{ + uint16_t alignment; + + switch(textureFormat) + { + case GX_TF_I4: /* 8x8 Tiles - 4-bit Intensity */ + alignment = 8; + break; + + case GX_TF_I8: /* 8x4 Tiles - 8-bit Intensity */ + case GX_TF_IA4: /* 8x4 Tiles - 4-bit Intensity, , 4-bit Alpha */ + case GX_TF_IA8: /* 4x4 Tiles - 8-bit Intensity, 8-bit Alpha */ + case GX_TF_RGB565: /* 4x4 Tiles - RGB565 Format */ + case GX_TF_RGB5A3: /* 4x4 Tiles - RGB5A3 Format */ + case GX_TF_RGBA8: /* 4x4 Tiles - RGBA8 Dual Cache Line Format */ + default: + alignment = 4; + break; + } + return textureHeight % alignment == 0 ? textureHeight : alignment + textureHeight - (textureHeight % alignment); +} + +/** + * Caches the given font glyph in the instance font texture buffer. + * + * This routine renders and stores the requested glyph's bitmap and relevant information into its own quickly addressible + * structure within an instance-specific map. + * + * @param charCode The requested glyph's character code. + * @return A pointer to the allocated font structure. + */ +ftgxCharData *FreeTypeGX::cacheGlyphData(wchar_t charCode) +{ + FT_UInt gIndex; + uint16_t textureWidth = 0, textureHeight = 0; + + gIndex = FT_Get_Char_Index( ftFace, charCode ); + if (!FT_Load_Glyph(ftFace, gIndex, FT_LOAD_DEFAULT )) { + FT_Render_Glyph( ftSlot, FT_RENDER_MODE_NORMAL ); + + if(ftSlot->format == FT_GLYPH_FORMAT_BITMAP) { + FT_Bitmap *glyphBitmap = &ftSlot->bitmap; + + textureWidth = adjustTextureWidth(glyphBitmap->width, this->textureFormat); + textureHeight = adjustTextureHeight(glyphBitmap->rows, this->textureFormat); + + this->fontData[charCode] = (ftgxCharData){ + ftSlot->bitmap_left, + ftSlot->advance.x >> 6, + gIndex, + textureWidth, + textureHeight, + ftSlot->bitmap_top, + ftSlot->bitmap_top, + glyphBitmap->rows - ftSlot->bitmap_top, + NULL + }; + this->loadGlyphData(glyphBitmap, &this->fontData[charCode]); + + return &this->fontData[charCode]; + } + } + return NULL; +} + +/** + * Locates each character in this wrapper's configured font face and proccess them. + * + * This routine locates each character in the configured font face and renders the glyph's bitmap. + * Each bitmap and relevant information is loaded into its own quickly addressible structure within an instance-specific map. + */ +uint16_t FreeTypeGX::cacheGlyphDataComplete() +{ + uint16_t i = 0; + FT_UInt gIndex; + FT_ULong charCode = FT_Get_First_Char( ftFace, &gIndex ); + while ( gIndex != 0 ) + { + if(this->cacheGlyphData(charCode) != NULL) + i++; + charCode = FT_Get_Next_Char( ftFace, charCode, &gIndex ); + } + return i; +} + +/** + * Loads the rendered bitmap into the relevant structure's data buffer. + * + * This routine does a simple byte-wise copy of the glyph's rendered 8-bit grayscale bitmap into the structure's buffer. + * Each byte is converted from the bitmap's intensity value into the a uint32_t RGBA value. + * + * @param bmp A pointer to the most recently rendered glyph's bitmap. + * @param charData A pointer to an allocated ftgxCharData structure whose data represent that of the last rendered glyph. + */ +void FreeTypeGX::loadGlyphData(FT_Bitmap *bmp, ftgxCharData *charData) +{ + uint32_t *glyphData = (uint32_t *)memalign(32, charData->textureWidth * charData->textureHeight * 4); + memset(glyphData, 0x00, charData->textureWidth * charData->textureHeight * 4); + + for (uint16_t imagePosY = 0; imagePosY < bmp->rows; imagePosY++) + { + for (uint16_t imagePosX = 0; imagePosX < bmp->width; imagePosX++) + { + uint32_t pixel = (uint32_t) bmp->buffer[imagePosY * bmp->width + imagePosX]; + glyphData[imagePosY * charData->textureWidth + imagePosX] = 0x00000000 | (pixel << 24) | (pixel << 16) | (pixel << 8) | pixel; + } + } + + switch(this->textureFormat) + { + case GX_TF_I4: + charData->glyphDataTexture = Metaphrasis::convertBufferToI4(glyphData, charData->textureWidth, charData->textureHeight); + break; + case GX_TF_I8: + charData->glyphDataTexture = Metaphrasis::convertBufferToI8(glyphData, charData->textureWidth, charData->textureHeight); + break; + case GX_TF_IA4: + charData->glyphDataTexture = Metaphrasis::convertBufferToIA4(glyphData, charData->textureWidth, charData->textureHeight); + break; + case GX_TF_IA8: + charData->glyphDataTexture = Metaphrasis::convertBufferToIA8(glyphData, charData->textureWidth, charData->textureHeight); + break; + case GX_TF_RGB565: + charData->glyphDataTexture = Metaphrasis::convertBufferToRGB565(glyphData, charData->textureWidth, charData->textureHeight); + break; + case GX_TF_RGB5A3: + charData->glyphDataTexture = Metaphrasis::convertBufferToRGB5A3(glyphData, charData->textureWidth, charData->textureHeight); + break; + case GX_TF_RGBA8: + default: + charData->glyphDataTexture = Metaphrasis::convertBufferToRGBA8(glyphData, charData->textureWidth, charData->textureHeight); + break; + } + free(glyphData); +} + +/** + * Determines the x offset of the rendered string. + * + * This routine calculates the x offset of the rendered string based off of a supplied positional format parameter. + * + * @param width Current pixel width of the string. + * @param format Positional format of the string. + */ +int16_t FreeTypeGX::getStyleOffsetWidth(uint16_t width, uint16_t format) +{ + if (format & FTGX_JUSTIFY_LEFT) + return 0; + else if (format & FTGX_JUSTIFY_CENTER) + return -(width >> 1); + else if (format & FTGX_JUSTIFY_RIGHT) + return -width; + return 0; +} + +/** + * Determines the y offset of the rendered string. + * + * This routine calculates the y offset of the rendered string based off of a supplied positional format parameter. + * + * @param offset Current pixel offset data of the string. + * @param format Positional format of the string. + */ +int16_t FreeTypeGX::getStyleOffsetHeight(ftgxDataOffset *offset, uint16_t format) +{ + switch(format & FTGX_ALIGN_MASK) + { + case FTGX_ALIGN_TOP: + return offset->ascender; + + default: + case FTGX_ALIGN_MIDDLE: + return (offset->ascender + offset->descender + 1) >> 1; + + case FTGX_ALIGN_BOTTOM: + return offset->descender; + + case FTGX_ALIGN_BASELINE: + return 0; + + case FTGX_ALIGN_GLYPH_TOP: + return offset->max; + + case FTGX_ALIGN_GLYPH_MIDDLE: + return (offset->max + offset->min + 1) >> 1; + + case FTGX_ALIGN_GLYPH_BOTTOM: + return offset->min; + } + return 0; +} + +/** + * Processes the supplied text string and prints the results at the specified coordinates. + * + * This routine processes each character of the supplied text string, loads the relevant preprocessed bitmap buffer, + * a texture from said buffer, and loads the resultant texture into the EFB. + * + * @param x Screen X coordinate at which to output the text. + * @param y Screen Y coordinate at which to output the text. Note that this value corresponds to the text string origin and not the top or bottom of the glyphs. + * @param text NULL terminated string to output. + * @param color Optional color to apply to the text characters. If not specified default value is ftgxWhite: (GXColor){0xff, 0xff, 0xff, 0xff} + * @param textStyle Flags which specify any styling which should be applied to the rendered string. + * @return The number of characters printed. + */ +uint16_t FreeTypeGX::drawText(int16_t x, int16_t y, wchar_t *text, GXColor color, uint16_t textStyle) +{ + uint16_t strLength = wcslen(text); + uint16_t x_pos = x, printed = 0; + uint16_t x_offset = 0, y_offset = 0; + GXTexObj glyphTexture; + FT_Vector pairDelta; + ftgxDataOffset offset; + + if(textStyle & FTGX_JUSTIFY_MASK) + { + x_offset = this->getStyleOffsetWidth(this->getWidth(text), textStyle); + } + if(textStyle & FTGX_ALIGN_MASK) + { + this->getOffset(text, &offset); + y_offset = this->getStyleOffsetHeight(&offset, textStyle); + } + + for (uint16_t i = 0; i < strLength; i++) + { + ftgxCharData* glyphData = NULL; + if( this->fontData.find(text[i]) != this->fontData.end() ) + { + glyphData = &this->fontData[text[i]]; + } + else + { + glyphData = this->cacheGlyphData(text[i]); + } + + if(glyphData != NULL) + { + if(this->ftKerningEnabled && i) + { + FT_Get_Kerning( ftFace, this->fontData[text[i - 1]].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta ); + x_pos += pairDelta.x >> 6; + } + + GX_InitTexObj(&glyphTexture, glyphData->glyphDataTexture, glyphData->textureWidth, glyphData->textureHeight, this->textureFormat, GX_CLAMP, GX_CLAMP, GX_FALSE); + this->copyTextureToFramebuffer(&glyphTexture, glyphData->textureWidth, glyphData->textureHeight, x_pos + glyphData->renderOffsetX + x_offset, y - glyphData->renderOffsetY + y_offset, color); + + x_pos += glyphData->glyphAdvanceX; + printed++; + } + } + + if(textStyle & FTGX_STYLE_MASK) + { + this->getOffset(text, &offset); + this->drawTextFeature(x + x_offset, y + y_offset, this->getWidth(text), &offset, textStyle, color); + } + + return printed; +} + +/** + * \overload + */ +uint16_t FreeTypeGX::drawText(int16_t x, int16_t y, wchar_t const *text, GXColor color, uint16_t textStyle) +{ + return this->drawText(x, y, (wchar_t *)text, color, textStyle); +} + +void FreeTypeGX::drawTextFeature(int16_t x, int16_t y, uint16_t width, ftgxDataOffset *offsetData, uint16_t format, GXColor color) +{ + uint16_t featureHeight = this->ftPointSize >> 4 > 0 ? this->ftPointSize >> 4 : 1; + + if (format & FTGX_STYLE_UNDERLINE) + this->copyFeatureToFramebuffer(width, featureHeight, x, y + 1, color); + + if (format & FTGX_STYLE_STRIKE) + this->copyFeatureToFramebuffer(width, featureHeight, x, y - ((offsetData->max) >> 1), color); +} + +/** + * Processes the supplied string and return the width of the string in pixels. + * + * This routine processes each character of the supplied text string and calculates the width of the entire string. + * Note that if precaching of the entire font set is not enabled any uncached glyph will be cached after the call to this function. + * + * @param text NULL terminated string to calculate. + * @return The width of the text string in pixels. + */ +uint16_t FreeTypeGX::getWidth(wchar_t *text) +{ + uint16_t strLength = wcslen(text); + uint16_t strWidth = 0; + FT_Vector pairDelta; + + for (uint16_t i = 0; i < strLength; i++) + { + + ftgxCharData* glyphData = NULL; + if( this->fontData.find(text[i]) != this->fontData.end() ) + { + glyphData = &this->fontData[text[i]]; + } + else + { + glyphData = this->cacheGlyphData(text[i]); + } + + if(glyphData != NULL) + { + if(this->ftKerningEnabled && (i > 0)) + { + FT_Get_Kerning( ftFace, this->fontData[text[i - 1]].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta ); + strWidth += pairDelta.x >> 6; + } + strWidth += glyphData->glyphAdvanceX; + } + } + return strWidth; +} + +/** + * + * \overload + */ +uint16_t FreeTypeGX::getWidth(wchar_t const *text) +{ + return this->getWidth((wchar_t *)text); +} + +/** + * Processes the supplied string and return the height of the string in pixels. + * + * This routine processes each character of the supplied text string and calculates the height of the entire string. + * Note that if precaching of the entire font set is not enabled any uncached glyph will be cached after the call to this function. + * + * @param text NULL terminated string to calculate. + * @return The height of the text string in pixels. + */ +uint16_t FreeTypeGX::getHeight(wchar_t *text) +{ + ftgxDataOffset offset; + this->getOffset(text, &offset); + return offset.max - offset.min; +} + +/** + * + * \overload + */ +uint16_t FreeTypeGX::getHeight(wchar_t const *text) +{ + return this->getHeight((wchar_t *)text); +} + +/** + * Get the maximum offset above and minimum offset below the font origin line. + * + * This function calculates the maximum pixel height above the font origin line and the minimum + * pixel height below the font origin line and returns the values in an addressible structure. + * + * @param text NULL terminated string to calculate. + * @param offset returns the max and min values above and below the font origin line + * + */ +void FreeTypeGX::getOffset(wchar_t *text, ftgxDataOffset* offset) +{ + uint16_t strLength = wcslen(text); + int16_t strMax = 0, strMin = 9999; + + for (uint16_t i = 0; i < strLength; i++) + { + + ftgxCharData* glyphData = NULL; + if( this->fontData.find(text[i]) != this->fontData.end() ) + { + glyphData = &this->fontData[text[i]]; + } + else + { + glyphData = this->cacheGlyphData(text[i]); + } + + if(glyphData != NULL) + { + strMax = glyphData->renderOffsetMax > strMax ? glyphData->renderOffsetMax : strMax; + strMin = glyphData->renderOffsetMin < strMin ? glyphData->renderOffsetMin : strMin; + } + } + offset->ascender = ftFace->size->metrics.ascender>>6; + offset->descender = ftFace->size->metrics.descender>>6; + offset->max = strMax; + offset->min = strMin; +} + +/** + * + * \overload + */ +void FreeTypeGX::getOffset(wchar_t const *text, ftgxDataOffset* offset) +{ + this->getOffset(text, offset); +} + +/** + * Copies the supplied texture quad to the EFB. + * + * This routine uses the in-built GX quad builder functions to define the texture bounds and location on the EFB target. + * + * @param texObj A pointer to the glyph's initialized texture object. + * @param texWidth The pixel width of the texture object. + * @param texHeight The pixel height of the texture object. + * @param screenX The screen X coordinate at which to output the rendered texture. + * @param screenY The screen Y coordinate at which to output the rendered texture. + * @param color Color to apply to the texture. + */ +void FreeTypeGX::copyTextureToFramebuffer(GXTexObj *texObj, f32 texWidth, f32 texHeight, int16_t screenX, int16_t screenY, GXColor color) +{ + GX_LoadTexObj(texObj, GX_TEXMAP0); + GX_InvalidateTexAll(); + + GX_SetTevOp (GX_TEVSTAGE0, GX_MODULATE); + GX_SetVtxDesc (GX_VA_TEX0, GX_DIRECT); + + GX_Begin(GX_QUADS, this->vertexIndex, 4); + GX_Position2s16(screenX, screenY); + GX_Color4u8(color.r, color.g, color.b, color.a); + GX_TexCoord2f32(0.0f, 0.0f); + + GX_Position2s16(texWidth + screenX, screenY); + GX_Color4u8(color.r, color.g, color.b, color.a); + GX_TexCoord2f32(1.0f, 0.0f); + + GX_Position2s16(texWidth + screenX, texHeight + screenY); + GX_Color4u8(color.r, color.g, color.b, color.a); + GX_TexCoord2f32(1.0f, 1.0f); + + GX_Position2s16(screenX, texHeight + screenY); + GX_Color4u8(color.r, color.g, color.b, color.a); + GX_TexCoord2f32(0.0f, 1.0f); + GX_End(); + GX_DrawDone(); + + this->setDefaultMode(); +} + +/** + * Creates a feature quad to the EFB. + * + * This function creates a simple quad for displaying underline or strikeout text styling. + * + * @param featureWidth The pixel width of the quad. + * @param featureHeight The pixel height of the quad. + * @param screenX The screen X coordinate at which to output the quad. + * @param screenY The screen Y coordinate at which to output the quad. + * @param color Color to apply to the texture. + */ +void FreeTypeGX::copyFeatureToFramebuffer(f32 featureWidth, f32 featureHeight, int16_t screenX, int16_t screenY, GXColor color) +{ + GX_SetTevOp (GX_TEVSTAGE0, GX_PASSCLR); + GX_SetVtxDesc (GX_VA_TEX0, GX_NONE); + + GX_Begin(GX_QUADS, this->vertexIndex, 4); + GX_Position2s16(screenX, screenY); + GX_Color4u8(color.r, color.g, color.b, color.a); + + GX_Position2s16(featureWidth + screenX, screenY); + GX_Color4u8(color.r, color.g, color.b, color.a); + + GX_Position2s16(featureWidth + screenX, featureHeight + screenY); + GX_Color4u8(color.r, color.g, color.b, color.a); + + GX_Position2s16(screenX, featureHeight + screenY); + GX_Color4u8(color.r, color.g, color.b, color.a); + GX_End(); + GX_DrawDone(); + + this->setDefaultMode(); +} diff --git a/src/platform/wii/FreeTypeGX.h b/src/platform/wii/FreeTypeGX.h new file mode 100644 index 0000000..2b428fc --- /dev/null +++ b/src/platform/wii/FreeTypeGX.h @@ -0,0 +1,298 @@ +/* + * FreeTypeGX is a wrapper class for libFreeType which renders a compiled + * FreeType parsable font into a GX texture for Wii homebrew development. + * Copyright (C) 2008 Armin Tamzarian + * Modified by Tantric, 2009 + * + * This file is part of FreeTypeGX. + * + * FreeTypeGX is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * FreeTypeGX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FreeTypeGX. If not, see . + */ + +/** \mainpage FreeTypeGX + * + * \section sec_intro Introduction + * + * FreeTypeGX is a wrapper class for libFreeType which renders a compiled FreeType parsable font into a GX texture for Wii homebrew development. + *
+ * FreeTypeGX is written in C++ and makes use of a selectable pre-buffered or buffer-on-demand methodology to allow fast and efficient printing of text to the EFB. + *

+ * This library was developed in-full by Armin Tamzarian with the support of developers in \#wiibrew on EFnet. + * + * \section sec_installation_source Installation (Source Code) + * + * -# Ensure that you have the libFreeType Wii library installed in your development environment with the library added to your Makefile where appropriate. + * -# Ensure that you have the Metaphrasis library installed in your development environment with the library added to your Makefile where appropriate. + * -# Extract the FreeTypeGX archive. + * -# Copy the contents of the src directory into your project's development path. + * -# Include the FreeTypeGX header file in your code using syntax such as the following: + * \code + * #include "FreeTypeGX.h" + * \endcode + * + * \section sec_installation_library Installation (Library) + * + * -# Ensure that you have the libFreeType Wii library installed in your development environment with the library added to your Makefile where appropriate. + * -# Ensure that you have the Metaphrasis library installed in your development environment with the library added to your Makefile where appropriate. + * -# Extract the FreeTypeGX archive. + * -# Copy the contents of the lib directory into your devKitPro/libogc directory. + * -# Include the FreeTypeGX header file in your code using syntax such as the following: + * \code + * #include "FreeTypeGX.h" + * \endcode + * + * \section sec_freetypegx_prerequisites FreeTypeGX Prerequisites + * + * Before you begin using FreeTypeGX in your project you must ensure that the desired font in compiled into your project. For this example I will assume you are building your project with a Makefile using devKitPro evironment and are attempting to include a font whose filename is rursus_compact_mono.ttf. + * + * -# Copy the font into a directory which will be processed by the project's Makefile. If you are unsure about where you should place your font just copy the it into your project's source directory. + * \n\n + * -# Modify the Makefile to convert the font into an object file: + * \code + * %.ttf.o : %.ttf + * @echo $(notdir $<) + * $(bin2o) + * \endcode + * \n + * -# Include the font object's generated header file in your source code: + * \code + * #include "rursus_compact_mono_ttf.h" + * \endcode + * This header file defines the two variables that you will need for use within your project: + * \code + * extern const u8 rursus_compact_mono_ttf[]; A pointer to the font buffer within the compiled project. + * extern const u32 rursus_compact_mono_ttf_size; The size of the font's buffer in bytes. + * \endcode + * + * \section sec_freetypegx_usage FreeTypeGX Usage + * + * -# Within the file you included the FreeTypeGX.h header create an instance object of the FreeTypeGX class: + * \code + * FreeTypeGX *freeTypeGX = new FreeTypeGX(); + * \endcode + * Alternately you can specify a texture format to which you would like to render the font characters. Note that the default value for this parameter is GX_TF_RGBA8. + * \code + * FreeTypeGX *freeTypeGX = new FreeTypeGX(GX_TF_RGB565); + * \endcode + * Furthermore, you can also specify a vertex format index to avoid conflicts with concurrent libraries or other systems. Note that the default value for this parameter is GX_VTXFMT1. + * \code + * FreeTypeGX *freeTypeGX = new FreeTypeGX(GX_TF_RGB565, GX_VTXFMT1); + * \endcode + * \n + * Currently supported textures are: + * \li GX_TF_I4 + * \li GX_TF_I8 + * \li GX_TF_IA4 + * \li GX_TF_IA8 + * \li GX_TF_RGB565 + * \li GX_TF_RGB5A3 + * \li GX_TF_RGBA8 + * + * \n + * -# Using the allocated FreeTypeGX instance object call the loadFont function to load the font from the compiled buffer and specify the desired point size. Note that this function can be called multiple times to load a new: + * \code + * freeTypeGX->loadFont(rursus_compact_mono_ttf, rursus_compact_mono_ttf_size, 64); + * \endcode + * Alternately you can specify a flag which will load and cache all available font glyphs immidiately. Note that on large font sets enabling this feature could take a significant amount of time. + * \code + * freeTypeGX->loadFont(rursus_compact_mono_ttf, rursus_compact_mono_ttf_size, 64, true); + * \endcode + * \n + * -# If necessary you can enable compatibility modes with concurrent libraries or systems. For more information on this feature see the documentation for setCompatibilityMode: + * \code + * freeTypeGX->setCompatibilityMode(FTGX_COMPATIBILITY_GRRLIB); + * \endcode + * -# Using the allocated FreeTypeGX instance object call the drawText function to print a string at the specified screen X and Y coordinates to the current EFB: + * \code + * freeTypeGX->drawText(10, 25, _TEXT("FreeTypeGX Rocks!")); + * \endcode + * Alternately you can specify a GXColor object you would like to apply to the printed characters: + * \code + * freeTypeGX->drawText(10, 25, _TEXT("FreeTypeGX Rocks!"), + * (GXColor){0xff, 0xee, 0xaa, 0xff}); + * \endcode + * Furthermore you can also specify a group of styling parameters which will modify the positioning or style of the text: + * \code + * freeTypeGX->drawText(10, 25, _TEXT("FreeTypeGX Rocks!"), + * (GXColor){0xff, 0xee, 0xaa, 0xff}, + * FTGX_JUSTIFY_CENTER | FTGX_ALIGN_BOTTOM | FTGX_STYLE_UNDERLINE); + * \endcode + * \n + * Currently style parameters are: + * \li FTGX_JUSTIFY_LEFT + * \li FTGX_JUSTIFY_CENTER + * \li FTGX_JUSTIFY_RIGHT + * \li FTGX_ALIGN_TOP + * \li FTGX_ALIGN_MIDDLE + * \li FTGX_ALIGN_BOTTOM + * \li FTGX_STYLE_UNDERLINE + * \li FTGX_STYLE_STRIKE + * + * \section sec_license License + * + * FreeTypeGX is distributed under the GNU Lesser General Public License. + * + * \section sec_contact Contact + * + * If you have any suggestions, questions, or comments regarding this library feel free to e-mail me at tamzarian1989 [at] gmail [dawt] com. + */ + +#ifndef FREETYPEGX_H_ +#define FREETYPEGX_H_ + +#include +#include +#include FT_FREETYPE_H +#include FT_BITMAP_H +#include "Metaphrasis.h" + +#include +#include +#include +#include + +#define MAX_FONT_SIZE 100 + +/*! \struct ftgxCharData_ + * + * Font face character glyph relevant data structure. + */ +typedef struct ftgxCharData_ { + int16_t renderOffsetX; /**< Texture X axis bearing offset. */ + uint16_t glyphAdvanceX; /**< Character glyph X coordinate advance in pixels. */ + uint16_t glyphIndex; /**< Charachter glyph index in the font face. */ + + uint16_t textureWidth; /**< Texture width in pixels/bytes. */ + uint16_t textureHeight; /**< Texture glyph height in pixels/bytes. */ + + int16_t renderOffsetY; /**< Texture Y axis bearing offset. */ + int16_t renderOffsetMax; /**< Texture Y axis bearing maximum value. */ + int16_t renderOffsetMin; /**< Texture Y axis bearing minimum value. */ + + uint32_t* glyphDataTexture; /**< Glyph texture bitmap data buffer. */ +} ftgxCharData; + +/*! \struct ftgxDataOffset_ + * + * Offset structure which hold both a maximum and minimum value. + */ +typedef struct ftgxDataOffset_ { + int16_t ascender; /**< Maximum data offset. */ + int16_t descender; /**< Minimum data offset. */ + int16_t max; /**< Maximum data offset. */ + int16_t min; /**< Minimum data offset. */ +} ftgxDataOffset; + +typedef struct ftgxCharData_ ftgxCharData; +typedef struct ftgxDataOffset_ ftgxDataOffset; + +#define _TEXT(t) L ## t /**< Unicode helper macro. */ + +#define FTGX_NULL 0x0000 +#define FTGX_JUSTIFY_LEFT 0x0001 +#define FTGX_JUSTIFY_CENTER 0x0002 +#define FTGX_JUSTIFY_RIGHT 0x0004 +#define FTGX_JUSTIFY_MASK 0x000f + +#define FTGX_ALIGN_TOP 0x0010 +#define FTGX_ALIGN_MIDDLE 0x0020 +#define FTGX_ALIGN_BOTTOM 0x0040 +#define FTGX_ALIGN_BASELINE 0x0080 +#define FTGX_ALIGN_GLYPH_TOP 0x0100 +#define FTGX_ALIGN_GLYPH_MIDDLE 0x0200 +#define FTGX_ALIGN_GLYPH_BOTTOM 0x0400 +#define FTGX_ALIGN_MASK 0x0ff0 + +#define FTGX_STYLE_UNDERLINE 0x1000 +#define FTGX_STYLE_STRIKE 0x2000 +#define FTGX_STYLE_MASK 0xf000 + +#define FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_MODULATE 0X0001 +#define FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_DECAL 0X0002 +#define FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_BLEND 0X0004 +#define FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_REPLACE 0X0008 +#define FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_PASSCLR 0X0010 + +#define FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_NONE 0X0100 +#define FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_DIRECT 0X0200 +#define FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_INDEX8 0X0400 +#define FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_INDEX16 0X0800 + +#define FTGX_COMPATIBILITY_NONE 0x0000 +#define FTGX_COMPATIBILITY_GRRLIB FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_PASSCLR | FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_NONE +#define FTGX_COMPATIBILITY_LIBWIISPRITE FTGX_COMPATIBILITY_DEFAULT_TEVOP_GX_MODULATE | FTGX_COMPATIBILITY_DEFAULT_VTXDESC_GX_DIRECT + +const GXColor ftgxWhite = (GXColor){0xff, 0xff, 0xff, 0xff}; /**< Constant color value used only to sanitize Doxygen documentation. */ + +void InitFreeType(uint8_t* fontBuffer, FT_Long bufferSize); +void ChangeFontSize(FT_UInt pixelSize); +wchar_t* charToWideChar(const char* p); +void ClearFontData(); + +/*! \class FreeTypeGX + * \brief Wrapper class for the libFreeType library with GX rendering. + * \author Armin Tamzarian + * \version 0.2.4 + * + * FreeTypeGX acts as a wrapper class for the libFreeType library. It supports precaching of transformed glyph data into + * a specified texture format. Rendering of the data to the EFB is accomplished through the application of high performance + * GX texture functions resulting in high throughput of string rendering. + */ +class FreeTypeGX { + + private: + FT_UInt ftPointSize; /**< Requested size of the rendered font. */ + bool ftKerningEnabled; /**< Flag indicating the availability of font kerning data. */ + + uint8_t textureFormat; /**< Defined texture format of the target EFB. */ + uint8_t vertexIndex; /**< Vertex format descriptor index. */ + uint32_t compatibilityMode; /**< Compatibility mode for default tev operations and vertex descriptors. */ + std::map fontData; /**< Map which holds the glyph data structures for the corresponding characters. */ + + static uint16_t adjustTextureWidth(uint16_t textureWidth, uint8_t textureFormat); + static uint16_t adjustTextureHeight(uint16_t textureHeight, uint8_t textureFormat); + + static int16_t getStyleOffsetWidth(uint16_t width, uint16_t format); + static int16_t getStyleOffsetHeight(ftgxDataOffset *offset, uint16_t format); + + void unloadFont(); + ftgxCharData *cacheGlyphData(wchar_t charCode); + uint16_t cacheGlyphDataComplete(); + void loadGlyphData(FT_Bitmap *bmp, ftgxCharData *charData); + + void setDefaultMode(); + + void drawTextFeature(int16_t x, int16_t y, uint16_t width, ftgxDataOffset *offsetData, uint16_t format, GXColor color); + void copyTextureToFramebuffer(GXTexObj *texObj, f32 texWidth, f32 texHeight, int16_t screenX, int16_t screenY, GXColor color); + void copyFeatureToFramebuffer(f32 featureWidth, f32 featureHeight, int16_t screenX, int16_t screenY, GXColor color); + + public: + FreeTypeGX(FT_UInt pixelSize, uint8_t textureFormat = GX_TF_RGBA8, uint8_t vertexIndex = GX_VTXFMT1); + ~FreeTypeGX(); + + void setVertexFormat(uint8_t vertexIndex); + void setCompatibilityMode(uint32_t compatibilityMode); + + uint16_t drawText(int16_t x, int16_t y, wchar_t *text, GXColor color = ftgxWhite, uint16_t textStyling = FTGX_NULL); + uint16_t drawText(int16_t x, int16_t y, wchar_t const *text, GXColor color = ftgxWhite, uint16_t textStyling = FTGX_NULL); + + uint16_t getWidth(wchar_t *text); + uint16_t getWidth(wchar_t const *text); + uint16_t getHeight(wchar_t *text); + uint16_t getHeight(wchar_t const *text); + void getOffset(wchar_t *text, ftgxDataOffset* offset); + void getOffset(wchar_t const *text, ftgxDataOffset* offset); +}; + +#endif /* FREETYPEGX_H_ */ diff --git a/src/platform/wii/Metaphrasis.cpp b/src/platform/wii/Metaphrasis.cpp new file mode 100644 index 0000000..cc83cfc --- /dev/null +++ b/src/platform/wii/Metaphrasis.cpp @@ -0,0 +1,383 @@ +/* + * Metaphrasis is a static conversion class for transforming RGBA image + * buffers into verious GX texture formats for Wii homebrew development. + * Copyright (C) 2008 Armin Tamzarian + * + * This file is part of Metaphrasis. + * + * Metaphrasis is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Metaphrasis is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Metaphrasis. If not, see . + */ + +#include "Metaphrasis.h" + +/** + * Default constructor for the Metaphrasis class. + */ + +Metaphrasis::Metaphrasis() { +} + +/** + * Default destructor for the Metaphrasis class. + */ + +Metaphrasis::~Metaphrasis() { +} + +/** + * Convert the specified RGBA data buffer into the I4 texture format + * + * This routine converts the RGBA data buffer into the I4 texture format and returns a pointer to the converted buffer. + * + * @param rgbaBuffer Buffer containing the temporarily rendered RGBA data. + * @param bufferWidth Pixel width of the data buffer. + * @param bufferHeight Pixel height of the data buffer. + * @return A pointer to the allocated buffer. + */ + +uint32_t* Metaphrasis::convertBufferToI4(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight) { + uint32_t bufferSize = bufferWidth * bufferHeight >> 1; + uint32_t* dataBufferI4 = (uint32_t *)memalign(32, bufferSize); + memset(dataBufferI4, 0x00, bufferSize); + + uint32_t *src = (uint32_t *)rgbaBuffer; + uint8_t *dst = (uint8_t *)dataBufferI4; + + for(uint16_t y = 0; y < bufferHeight; y += 8) { + for(uint16_t x = 0; x < bufferWidth; x += 8) { + for(uint16_t rows = 0; rows < 8; rows++) { + *dst++ = (src[((y + rows) * bufferWidth) + (x + 0)] & 0xf0) | ((src[((y + rows) * bufferWidth) + (x + 1)] & 0xf0) >> 4); + *dst++ = (src[((y + rows) * bufferWidth) + (x + 2)] & 0xf0) | ((src[((y + rows) * bufferWidth) + (x + 3)] & 0xf0) >> 4); + *dst++ = (src[((y + rows) * bufferWidth) + (x + 4)] & 0xf0) | ((src[((y + rows) * bufferWidth) + (x + 5)] & 0xf0) >> 4); + *dst++ = (src[((y + rows) * bufferWidth) + (x + 5)] & 0xf0) | ((src[((y + rows) * bufferWidth) + (x + 7)] & 0xf0) >> 4); + } + } + } + DCFlushRange(dataBufferI4, bufferSize); + + return dataBufferI4; +} + +/** + * Convert the specified RGBA data buffer into the I8 texture format + * + * This routine converts the RGBA data buffer into the I8 texture format and returns a pointer to the converted buffer. + * + * @param rgbaBuffer Buffer containing the temporarily rendered RGBA data. + * @param bufferWidth Pixel width of the data buffer. + * @param bufferHeight Pixel height of the data buffer. + * @return A pointer to the allocated buffer. + */ + +uint32_t* Metaphrasis::convertBufferToI8(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight) { + uint32_t bufferSize = bufferWidth * bufferHeight; + uint32_t* dataBufferI8 = (uint32_t *)memalign(32, bufferSize); + memset(dataBufferI8, 0x00, bufferSize); + + uint32_t *src = (uint32_t *)rgbaBuffer; + uint8_t *dst = (uint8_t *)dataBufferI8; + + for(uint16_t y = 0; y < bufferHeight; y += 4) { + for(uint16_t x = 0; x < bufferWidth; x += 8) { + for(uint16_t rows = 0; rows < 4; rows++) { + *dst++ = src[((y + rows) * bufferWidth) + (x + 0)] & 0xff; + *dst++ = src[((y + rows) * bufferWidth) + (x + 1)] & 0xff; + *dst++ = src[((y + rows) * bufferWidth) + (x + 2)] & 0xff; + *dst++ = src[((y + rows) * bufferWidth) + (x + 3)] & 0xff; + *dst++ = src[((y + rows) * bufferWidth) + (x + 4)] & 0xff; + *dst++ = src[((y + rows) * bufferWidth) + (x + 5)] & 0xff; + *dst++ = src[((y + rows) * bufferWidth) + (x + 6)] & 0xff; + *dst++ = src[((y + rows) * bufferWidth) + (x + 7)] & 0xff; + } + } + } + DCFlushRange(dataBufferI8, bufferSize); + + return dataBufferI8; +} + +/** + * Downsample the specified RGBA value data buffer to an IA4 value. + * + * This routine downsamples the given RGBA data value into the IA4 texture data format. + * + * @param rgba A 32-bit RGBA value to convert to the IA4 format. + * @return The IA4 value of the given RGBA value. + */ + +uint8_t Metaphrasis::convertRGBAToIA4(uint32_t rgba) { + uint8_t i, a; + + i = (rgba >> 8) & 0xf0; + a = (rgba ) & 0xff; + + return i | (a >> 4); +} + +/** + * Convert the specified RGBA data buffer into the IA4 texture format + * + * This routine converts the RGBA data buffer into the IA4 texture format and returns a pointer to the converted buffer. + * + * @param rgbaBuffer Buffer containing the temporarily rendered RGBA data. + * @param bufferWidth Pixel width of the data buffer. + * @param bufferHeight Pixel height of the data buffer. + * @return A pointer to the allocated buffer. + */ + +uint32_t* Metaphrasis::convertBufferToIA4(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight) { + uint32_t bufferSize = bufferWidth * bufferHeight; + uint32_t* dataBufferIA4 = (uint32_t *)memalign(32, bufferSize); + memset(dataBufferIA4, 0x00, bufferSize); + + uint32_t *src = (uint32_t *)rgbaBuffer; + uint8_t *dst = (uint8_t *)dataBufferIA4; + + for(uint16_t y = 0; y < bufferHeight; y += 4) { + for(uint16_t x = 0; x < bufferWidth; x += 8) { + for(uint16_t rows = 0; rows < 4; rows++) { + *dst++ = Metaphrasis::convertRGBAToIA4(src[((y + rows) * bufferWidth) + (x + 0)]); + *dst++ = Metaphrasis::convertRGBAToIA4(src[((y + rows) * bufferWidth) + (x + 1)]); + *dst++ = Metaphrasis::convertRGBAToIA4(src[((y + rows) * bufferWidth) + (x + 2)]); + *dst++ = Metaphrasis::convertRGBAToIA4(src[((y + rows) * bufferWidth) + (x + 3)]); + *dst++ = Metaphrasis::convertRGBAToIA4(src[((y + rows) * bufferWidth) + (x + 4)]); + *dst++ = Metaphrasis::convertRGBAToIA4(src[((y + rows) * bufferWidth) + (x + 5)]); + *dst++ = Metaphrasis::convertRGBAToIA4(src[((y + rows) * bufferWidth) + (x + 6)]); + *dst++ = Metaphrasis::convertRGBAToIA4(src[((y + rows) * bufferWidth) + (x + 7)]); + } + } + } + DCFlushRange(dataBufferIA4, bufferSize); + + return dataBufferIA4; +} + +/** + * Downsample the specified RGBA value data buffer to an IA8 value. + * + * This routine downsamples the given RGBA data value into the IA8 texture data format. + * + * @param rgba A 32-bit RGBA value to convert to the IA8 format. + * @return The IA8 value of the given RGBA value. + */ + +uint16_t Metaphrasis::convertRGBAToIA8(uint32_t rgba) { + uint8_t i, a; + + i = (rgba >> 8) & 0xff; + a = (rgba ) & 0xff; + + return (i << 8) | a; +} + +/** + * Convert the specified RGBA data buffer into the IA8 texture format + * + * This routine converts the RGBA data buffer into the IA8 texture format and returns a pointer to the converted buffer. + * + * @param rgbaBuffer Buffer containing the temporarily rendered RGBA data. + * @param bufferWidth Pixel width of the data buffer. + * @param bufferHeight Pixel height of the data buffer. + * @return A pointer to the allocated buffer. + */ + +uint32_t* Metaphrasis::convertBufferToIA8(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight) { + uint32_t bufferSize = (bufferWidth * bufferHeight) << 1; + uint32_t* dataBufferIA8 = (uint32_t *)memalign(32, bufferSize); + memset(dataBufferIA8, 0x00, bufferSize); + + uint32_t *src = (uint32_t *)rgbaBuffer; + uint16_t *dst = (uint16_t *)dataBufferIA8; + + for(uint16_t y = 0; y < bufferHeight; y += 4) { + for(uint16_t x = 0; x < bufferWidth; x += 4) { + for(uint16_t rows = 0; rows < 4; rows++) { + *dst++ = Metaphrasis::convertRGBAToIA8(src[((y + rows) * bufferWidth) + (x + 0)]); + *dst++ = Metaphrasis::convertRGBAToIA8(src[((y + rows) * bufferWidth) + (x + 1)]); + *dst++ = Metaphrasis::convertRGBAToIA8(src[((y + rows) * bufferWidth) + (x + 2)]); + *dst++ = Metaphrasis::convertRGBAToIA8(src[((y + rows) * bufferWidth) + (x + 3)]); + } + } + } + DCFlushRange(dataBufferIA8, bufferSize); + + return dataBufferIA8; +} + +/** + * Convert the specified RGBA data buffer into the RGBA8 texture format + * + * This routine converts the RGBA data buffer into the RGBA8 texture format and returns a pointer to the converted buffer. + * + * @param rgbaBuffer Buffer containing the temporarily rendered RGBA data. + * @param bufferWidth Pixel width of the data buffer. + * @param bufferHeight Pixel height of the data buffer. + * @return A pointer to the allocated buffer. + */ + +uint32_t* Metaphrasis::convertBufferToRGBA8(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight) { + uint32_t bufferSize = (bufferWidth * bufferHeight) << 2; + uint32_t* dataBufferRGBA8 = (uint32_t *)memalign(32, bufferSize); + memset(dataBufferRGBA8, 0x00, bufferSize); + + uint8_t *src = (uint8_t *)rgbaBuffer; + uint8_t *dst = (uint8_t *)dataBufferRGBA8; + + for(uint16_t block = 0; block < bufferHeight; block += 4) { + for(uint16_t i = 0; i < bufferWidth; i += 4) { + for (uint16_t c = 0; c < 4; c++) { + for (uint16_t ar = 0; ar < 4; ar++) { + *dst++ = src[(((i + ar) + ((block + c) * bufferWidth)) * 4) + 3]; + *dst++ = src[((i + ar) + ((block + c) * bufferWidth)) * 4]; + } + } + for (uint16_t c = 0; c < 4; c++) { + for (uint16_t gb = 0; gb < 4; gb++) { + *dst++ = src[(((i + gb) + ((block + c) * bufferWidth)) * 4) + 1]; + *dst++ = src[(((i + gb) + ((block + c) * bufferWidth)) * 4) + 2]; + } + } + } + } + DCFlushRange(dataBufferRGBA8, bufferSize); + + return dataBufferRGBA8; +} + +/** + * Downsample the specified RGBA value data buffer to an RGB565 value. + * + * This routine downsamples the given RGBA data value into the RGB565 texture data format. + * Attribution for this routine is given fully to NoNameNo of GRRLIB Wii library. + * + * @param rgba A 32-bit RGBA value to convert to the RGB565 format. + * @return The RGB565 value of the given RGBA value. + */ + +uint16_t Metaphrasis::convertRGBAToRGB565(uint32_t rgba) { + uint8_t r, g, b; + + r = (((rgba >> 24) & 0xff) * 31) / 255; + g = (((rgba >> 16) & 0xff) * 63) / 255; + b = (((rgba >> 8) & 0xff) * 31) / 255; + + return (((r << 6) | g ) << 5 ) | b; +} + +/** + * Convert the specified RGBA data buffer into the RGB565 texture format + * + * This routine converts the RGBA data buffer into the RGB565 texture format and returns a pointer to the converted buffer. + * + * @param rgbaBuffer Buffer containing the temporarily rendered RGBA data. + * @param bufferWidth Pixel width of the data buffer. + * @param bufferHeight Pixel height of the data buffer. + * @return A pointer to the allocated buffer. + */ + +uint32_t* Metaphrasis::convertBufferToRGB565(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight) { + uint32_t bufferSize = (bufferWidth * bufferHeight) << 1; + uint32_t* dataBufferRGB565 = (uint32_t *)memalign(32, bufferSize); + memset(dataBufferRGB565, 0x00, bufferSize); + + uint32_t *src = (uint32_t *)rgbaBuffer; + uint16_t *dst = (uint16_t *)dataBufferRGB565; + + for(uint16_t y = 0; y < bufferHeight; y += 4) { + for(uint16_t x = 0; x < bufferWidth; x += 4) { + for(uint16_t rows = 0; rows < 4; rows++) { + *dst++ = Metaphrasis::convertRGBAToRGB565(src[((y + rows) * bufferWidth) + (x + 0)]); + *dst++ = Metaphrasis::convertRGBAToRGB565(src[((y + rows) * bufferWidth) + (x + 1)]); + *dst++ = Metaphrasis::convertRGBAToRGB565(src[((y + rows) * bufferWidth) + (x + 2)]); + *dst++ = Metaphrasis::convertRGBAToRGB565(src[((y + rows) * bufferWidth) + (x + 3)]); + } + } + } + DCFlushRange(dataBufferRGB565, bufferSize); + + return dataBufferRGB565; +} + +/** + * Downsample the specified RGBA value data buffer to an RGB5A3 value. + * + * This routine downsamples the given RGBA data value into the RGB5A3 texture data format. + * Attribution for this routine is given fully to WiiGator via the TehSkeen forum. + * + * @param rgba A 32-bit RGBA value to convert to the RGB5A3 format. + * @return The RGB5A3 value of the given RGBA value. + */ + +uint16_t Metaphrasis::convertRGBAToRGB5A3(uint32_t rgba) { + uint32_t r, g, b, a; + uint16_t color; + + r = (rgba >> 24) & 0xff; + g = (rgba >> 16) & 0xff; + b = (rgba >> 8) & 0xff; + a = (rgba ) & 0xff; + + if (a > 0xe0) { + r = r >> 3; + g = g >> 3; + b = b >> 3; + + color = (r << 10) | (g << 5) | b; + color |= 0x8000; + } + else { + r = r >> 4; + g = g >> 4; + b = b >> 4; + a = a >> 5; + + color = (a << 12) | (r << 8) | (g << 4) | b; + } + + return color; +} + +/** + * Convert the specified RGBA data buffer into the RGB5A3 texture format + * + * This routine converts the RGBA data buffer into the RGB5A3 texture format and returns a pointer to the converted buffer. + * + * @param rgbaBuffer Buffer containing the temporarily rendered RGBA data. + * @param bufferWidth Pixel width of the data buffer. + * @param bufferHeight Pixel height of the data buffer. + * @return A pointer to the allocated buffer. + */ + +uint32_t* Metaphrasis::convertBufferToRGB5A3(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight) { + uint32_t bufferSize = (bufferWidth * bufferHeight) << 1; + uint32_t* dataBufferRGB5A3 = (uint32_t *)memalign(32, bufferSize); + memset(dataBufferRGB5A3, 0x00, bufferSize); + + uint32_t *src = (uint32_t *)rgbaBuffer; + uint16_t *dst = (uint16_t *)dataBufferRGB5A3; + + for(uint16_t y = 0; y < bufferHeight; y += 4) { + for(uint16_t x = 0; x < bufferWidth; x += 4) { + for(uint16_t rows = 0; rows < 4; rows++) { + *dst++ = Metaphrasis::convertRGBAToRGB5A3(src[((y + rows) * bufferWidth) + (x + 0)]); + *dst++ = Metaphrasis::convertRGBAToRGB5A3(src[((y + rows) * bufferWidth) + (x + 1)]); + *dst++ = Metaphrasis::convertRGBAToRGB5A3(src[((y + rows) * bufferWidth) + (x + 2)]); + *dst++ = Metaphrasis::convertRGBAToRGB5A3(src[((y + rows) * bufferWidth) + (x + 3)]); + } + } + } + DCFlushRange(dataBufferRGB5A3, bufferSize); + + return dataBufferRGB5A3; +} diff --git a/src/platform/wii/Metaphrasis.h b/src/platform/wii/Metaphrasis.h new file mode 100644 index 0000000..4ca8871 --- /dev/null +++ b/src/platform/wii/Metaphrasis.h @@ -0,0 +1,116 @@ +/* + * Metaphrasis is a static conversion class for transforming RGBA image + * buffers into verious GX texture formats for Wii homebrew development. + * Copyright (C) 2008 Armin Tamzarian + * + * This file is part of Metaphrasis. + * + * Metaphrasis is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Metaphrasis is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Metaphrasis. If not, see . + */ + +/** \mainpage Metaphrasis + * + * \section sec_intro Introduction + * + * Metaphrasis is a static conversion class for transforming RGBA image buffers into verious GX texture formats for Wii homebrew development. + *
+ * Metaphrasis is written in C++ and makes use of a community standard and newly developed algorithms for conversion of 32-bit RGBA data buffers into various GX texture formats common to both the Gamecube and Wii platforms. + *

+ * This library was developed in-full by Armin Tamzarian with the support of developers in \#wiibrew on EFnet, Chaosteil of libwiisprite, and DrTwox of GRRLIB. + * + * \section sec_installation_source Installation (Source Code) + * + * -# Extract the Metaphrasis archive. + * -# Copy the contents of the src directory into your project's development path. + * -# Include the Metaphrasis header file in your code using syntax such as the following: + * \code + * #include "Metaphrasis.h" + * \endcode + * + * \section sec_installation_library Installation (Library) + * + * -# Extract the Metaphrasis archive. + * -# Copy the contents of the lib directory into your devKitPro/libogc directory. + * -# Include the Metaphrasis header file in your code using syntax such as the following: + * \code + * #include "Metaphrasis.h" + * \endcode + * + * \section sec_usage Usage + * + * -# Create a buffer full of 32-bit RGBA values noting both the pixel height and width of the buffer. + * -# Call one of the many conversion routines from within your code. (Note: All methods within the Metaphrasis class are static and thus no class instance need be allocated) + * \code + * uint32_t* rgba8Buffer = Metaphrasis::convertBufferToRGBA8(rgbaBuffer, bufferWidth, bufferHeight); + * \endcode + * -# Free your temporary RGBA value buffer if you no longer need said values. + * + * Currently supported conversion routines are as follows: + * \li convertBufferToI4 + * \li convertBufferToI8 + * \li convertBufferToIA4 + * \li convertBufferToIA8 + * \li convertBufferToRGBA8 + * \li convertBufferToRGB565 + * \li convertBufferToRGB5A3 + * + * \section sec_license License + * + * Metaphrasis is distributed under the GNU Lesser General Public License. + * + * \section sec_contact Contact + * + * If you have any suggestions, questions, or comments regarding this library feel free to e-mail me at tamzarian1989 [at] gmail [dawt] com. + */ + +#ifndef METAPHRASIS_H_ +#define METAPHRASIS_H_ + +#include +#include +#include +#include + +/*! \class Metaphrasis + * \brief A static conversion class for transforming RGBA image buffers into verious GX texture formats for + * Wii homebrew development. + * \author Armin Tamzarian + * \version 0.1.0 + * + * Metaphrasis is a static conversion class for transforming RGBA image buffers into verious GX texture formats for + * Wii homebrew development. Metaphrasis is written in C++ and makes use of a community standard and newly developed + * algorithms for conversion of 32-bit RGBA data buffers into various GX texture formats common to both the Gamecube + * and Wii platforms. + */ +class Metaphrasis { + public: + Metaphrasis(); + virtual ~Metaphrasis(); + + static uint32_t* convertBufferToI4(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight); + static uint32_t* convertBufferToI8(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight); + static uint32_t* convertBufferToIA4(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight); + static uint32_t* convertBufferToIA8(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight); + static uint32_t* convertBufferToRGBA8(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight); + static uint32_t* convertBufferToRGB565(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight); + static uint32_t* convertBufferToRGB5A3(uint32_t* rgbaBuffer, uint16_t bufferWidth, uint16_t bufferHeight); + + static uint8_t convertRGBAToIA4(uint32_t rgba); + static uint16_t convertRGBAToIA8(uint32_t rgba); + static uint16_t convertRGBAToRGB565(uint32_t rgba); + static uint16_t convertRGBAToRGB5A3(uint32_t rgba); + +}; + +#endif /*METAPHRASIS_H_*/ diff --git a/src/platform/wii/config.h b/src/platform/wii/config.h index b3a5ece..2d0ca7a 100644 --- a/src/platform/wii/config.h +++ b/src/platform/wii/config.h @@ -1,4 +1,4 @@ -#define VERSION "0.73.02" +#define VERSION "0.73" /* Define to 1 to enable internal debugger, requires libcurses */ #define C_DEBUG 0 diff --git a/src/platform/wii/filelist.h b/src/platform/wii/filelist.h new file mode 100644 index 0000000..2e51274 --- /dev/null +++ b/src/platform/wii/filelist.h @@ -0,0 +1,106 @@ +/**************************************************************************** + * DOSBox Wii Home Menu + * Tantric 2009 + * + * filelist.h + * + * Contains a list of all of the files stored in the images/, fonts/, and + * sounds/ folders + ***************************************************************************/ + +#ifndef _FILELIST_H_ +#define _FILELIST_H_ + +#include + +extern const u8 font_ttf[]; +extern const u32 font_ttf_size; + +extern const u8 enter_ogg[]; +extern const u32 enter_ogg_size; + +extern const u8 exit_ogg[]; +extern const u32 exit_ogg_size; + +extern const u8 button_over_pcm[]; +extern const u32 button_over_pcm_size; + +extern const u8 button_click_pcm[]; +extern const u32 button_click_pcm_size; + +extern const u8 logo_png[]; +extern const u32 logo_png_size; + +extern const u8 logo_over_png[]; +extern const u32 logo_over_png_size; + +extern const u8 bg_top_png[]; +extern const u32 bg_top_png_size; + +extern const u8 bg_bottom_png[]; +extern const u32 bg_bottom_png_size; + +extern const u8 button_png[]; +extern const u32 button_png_size; + +extern const u8 button_over_png[]; +extern const u32 button_over_png_size; + +extern const u8 button_small_png[]; +extern const u32 button_small_png_size; + +extern const u8 button_small_over_png[]; +extern const u32 button_small_over_png_size; + +extern const u8 button_large_png[]; +extern const u32 button_large_png_size; + +extern const u8 button_large_over_png[]; +extern const u32 button_large_over_png_size; + +extern const u8 battery_png[]; +extern const u32 battery_png_size; + +extern const u8 battery_red_png[]; +extern const u32 battery_red_png_size; + +extern const u8 battery_bar_png[]; +extern const u32 battery_bar_png_size; + +extern const u8 credits_box_png[]; +extern const u32 credits_box_png_size; + +extern const u8 keyboard_textbox_png[]; +extern const u32 keyboard_textbox_png_size; + +extern const u8 keyboard_key_png[]; +extern const u32 keyboard_key_png_size; + +extern const u8 keyboard_key_over_png[]; +extern const u32 keyboard_key_over_png_size; + +extern const u8 keyboard_mediumkey_png[]; +extern const u32 keyboard_mediumkey_png_size; + +extern const u8 keyboard_mediumkey_over_png[]; +extern const u32 keyboard_mediumkey_over_png_size; + +extern const u8 keyboard_largekey_png[]; +extern const u32 keyboard_largekey_png_size; + +extern const u8 keyboard_largekey_over_png[]; +extern const u32 keyboard_largekey_over_png_size; + +extern const u8 player1_point_png[]; +extern const u32 player1_point_png_size; + +extern const u8 player2_point_png[]; +extern const u32 player2_point_png_size; + +extern const u8 player3_point_png[]; +extern const u32 player3_point_png_size; + +extern const u8 player4_point_png[]; +extern const u32 player4_point_png_size; + +#endif diff --git a/src/platform/wii/fonts/font.ttf b/src/platform/wii/fonts/font.ttf new file mode 100644 index 0000000000000000000000000000000000000000..934f65ee6e0d9dbfe4919f3a67374ed4a17b1778 GIT binary patch literal 29244 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 G$ob#i=3n6e literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/battery.png b/src/platform/wii/images/battery.png new file mode 100644 index 0000000000000000000000000000000000000000..1733f41e5bb42a4bbcf3015192bcd19dba5ba6f4 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^IzTMK!3HG1t-XH`NJ*BsMwA5SrCo^$0gxr*q*kiz1GCmZV0!3HEZNY`WoDajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_d9MaG^kjv*DdlK%YvZ_jLZ;D`gm1xH5iDSn3)?YjkEamU0Z r%JA%y@KLnnp3Zd4ae^EJ6A#0QAnx7&&bh|`wKI6S`njxgN@xNA&{8V0 literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/battery_red.png b/src/platform/wii/images/battery_red.png new file mode 100644 index 0000000000000000000000000000000000000000..fd7d995c7808bc605a2a3bd2eb51496c59e95ad9 GIT binary patch literal 230 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 literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/bg_top.png b/src/platform/wii/images/bg_top.png new file mode 100644 index 0000000000000000000000000000000000000000..059c891b165296a1ac06b7e730b6d93d3f9c9e4b GIT binary patch literal 16161 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 literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/button.png b/src/platform/wii/images/button.png new file mode 100644 index 0000000000000000000000000000000000000000..e3988203065e0958fc61d611a99b68d833a812be GIT binary patch literal 3977 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 literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/button_large.png b/src/platform/wii/images/button_large.png new file mode 100644 index 0000000000000000000000000000000000000000..eaf2db7247112b0919cc2f31cf5bbe76d2c8dde1 GIT binary patch literal 6488 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 JPDHLkV1m9~lMw&_ literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/button_over.png b/src/platform/wii/images/button_over.png new file mode 100644 index 0000000000000000000000000000000000000000..6d31690cb4573e176bc76a787046f5e0893f9021 GIT binary patch literal 3944 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ξ@erUf+6G^nUYR*8-T}F%O|XpmQpHIe1~Znq8i>&()7m~Vek@7% z#c&!LI!S%v(-y_aSffdD+w8q{v~6mMsY?IWC{t>cr7>}X^0q{MSL{5PQEb>hBR;PF0raGm$XSvXBkgvSr@wP&Tv81T`7% z2(^n^;JSjy$jHX_+)v@wcX3Km1$HEjSq`~`j`Dj&*W}W zx;Ttl!Kmgm+6a9O#WE8^Pwg3#5H#-=vj>~lVA`W(az<5tE0nDG;nh1fD8*{bmRb>~ zhyt~oQcP|rE8X+UdZiN|f?zLGeN$#^sxZ>Pymm zOQN4OefiXzq3VXdhu|f;9wQ5#$Z$<>q9t?56f9K>Y4fdSH%*4za!TF-!``VvjU;wE zKpVo-^dv}sz$PBQj>Dx9whY4166ud?`keww%gn!VTWnQrn*e+Fbj{M9c;KG?`L~wcr>HNPWQIeEsJ2;F|Ass^2a@!s# zi};fnb)2ocP=LX`yh-%Z-+ep(XI)}SR@ zS~T!Za?}of3t`i{Y1LknJf?)pLe{50V~o=7@O}*W-A=i+pCwZ?D`aTChfKJvbiR?& zrdC8Nl!?bh-*22ae@q~#H*_+Mk=Wb~GD`=nol}#V zY^|FFVO<5Y=7P5h&zx44CbKrW1dFRH84k6dp(M>hj!%49t%3&ITjG^@wyh8QE*SrW zht(?J=FZqAt|+Np#(ke&7ftxidKfLOq*Nf<1kRZ4wa-k&Yx*~nkhSThhKjc2^mNpp zifAg9Q<8M`^`ec}AoW(E5=@pVlGG113X)gCJ47Hce`l(WtAn@w{Hg>;FShmeOr=5$ zXFDfP@BM@TdFN;E%-AoQ81`P9Ou1QqPgmvkplS!=;-6rrolfpCtzpZWH|;F5#+rNM z$(Zb&w$%wbMb#^bn=M2^<1ewSH-kpo=S#*?Mv$)+u{36C`A}93`l@9{zl-A9i;{x` zF;sZ-iMXEW8Zhd6B(W1mYE3O&!XkZDAxC1pN3cWac_O9x?bhWO~OKjO!_RtlOlQMClDC=ITM>(1$SAni&OeF4+zPGhE z%qS%Fk(U11l{9xkNNhsYgh>)dBW=#s%T3il)0QBw_;iS^6A`D$#5(eJ2SG}$p%ie} zgrObe>5zJ&0<|uJMuQ}^85i!ri8cJ`LAnT4nU=#cJ#}@@t3CG#KfddxrPVd_g*A@XF5+JgPUqWM>xR!yfPZMj?hBJ^h6(A|$GRmVg?rIAp zg9y^}T(cEV$>gf+c;b^`N7K{uH@r5JoGKC}J^Yj*k+djPbE)d51y1T@R|}ac_{DxU zL6asqG5thSIof}0}9iZHf+f3?7B%$jJrV68gaMqFoJ)6dgkuvtl=$M zNnEIQ=rn2J2LGptEGywn$;Z042qiMfJaIrsm&Pgz(+Hw*<+bP1(()$rJ!)wfB#5{- zi6+p9yyp2g{^HL_l(rJ1MZS)*Q3Y?*9Ladg_Us$M$7XdT2W|RN4fb6|ixPBdkZF99`CbdwEZF}R*rO{vG3~bIMTK7#S zsZAuk8m!u2f71tTmEPi5*A@kRAIapNJkYBJWl$Qr4+q$7~Y0wOz3U z&c)qwIdGa#S0Y6|z_Ekh^wFNfcoA$$cC>n_S1Oc;Xn?dW$E z&Q|Eq;cO0VdcTcSx)MmAaQ|(J$rQiHyX0pr3=;9U3ZkTU--7T$;!D@FBr*ieQ6+Ch zK~i$CK!h#Okx%xfU<+fR?!mQ$>Igi$Y z%xb_iWX}4bEYkW5YWJdzm`G0~^=n2C+y7qmU`QU+j1;XmUoKuyGuJZ|j%vTDm#qo#>)k;Qh?!&qE|NhioE zcz*ewSE@ct;3BuRlfHi&*)e_PW@~MPTAH6m)4V7Dh^u=yef9)^ui-ND& zSoUBf9q+5nXVv4{;YetRtrDo$XQi&M{;nl#rs|<*pOQ6G4UvQBDdMmMIRl5T>6sxnOy)lH>uAxt&ctBu*E!=5x78o88AW=@j{n~b!cT~lE@ zf#pq4JdJbGvuc$A8P~m|Wkl^Ni@062=1jtp_Qli4mO&njBW)|N604rnN!M}JcL|C$ zbwqnz z$W!md`r}h3;{K zE#yFADx2AQ1OKLfyTcJUK_yz-k`A;as6~hIsld|7eNzmh1Lk8}(2$skq-a?FMw0lc z=VaO?#*LF&fR<>V3NS+mP5MCX^P4A*)uLIvJ=&RPP4`;s+p2-u_Vte(2;&RTGIQz+ zryfo^lHU9Fx^)RyoRLN(Cy;G^%Ie))VolQ%#Qrc}*f$CP8gbxE+CAT3mO~C$mQkQkaT5 z;?o#IJu(+NL0a1cLz?zWDAMBI+R#Q@L@zOjcsuR-Q!IY9fY%^?1F4hokX2B$S>`It z(*j_+o+Vo|E}$k+SUn6<7E3CFwz?)~(UxqS9u7-VLP^?sAf@{=391n)GX3Cpei0g$ z_I2YdB@L9cHcpi%-{Bq6lvuA03(J#85cny=wD=5}%C{9KW`8b;hn28yNe;!khW@s5 zfs%LoyXjyjSL>E2rNnF*uc!0F3?ozy1ZCjuB`p2_G%=7Wly$b%R#k1|H2AGmV|G#? z-K!ZbySkplq+NX#7TEfQ%;LO%pcwTIZMUt5h5@zKKCX2i`RCRTghkdO?s*7m?! zTf}Y>Yw}{+CM^jw-%CbM3v~O0(e{QI7J>#3W24NYRK^7_d*7IMyc~^4H=|6#R=0^qgc~g-PT;q5|mS1YzTz$+(>b^ z5yrp#-OFS??PWJ157QQ)&2i%PctW6Sqhu|JH^kjj%$@WX+~HNi^QR^iSmkmU7<5Cf`l4jq>1CS zgDnN1MK3*#(zHc##k8j6!04K{jFpU^+E`R$KDR!hK|3hmn?P=@kXIK+y3R(@I$^0N z=yI`dZU-Z2(3|S*i9>WfP;A>TL8t_sHaEa0`5$YLq{*LF2`{qbPb2|%*8wGR^u0zQ zaw+0W+vsZjEfU(q_iEyVO}wwI3%YL-`J;A^9bIpTSZfg{4&BT2mC_<=eI{#Ry7;MI z_4d<$pQu0@hHqPpB;T4A1HnWlk)pqE?}mbXYZ;l@bVKYpg`V5BE+|%lAdQslVhm>x zfp<+`ShR0BglPK6CQo-Q%+2+l9BL%?bCvLuE=XDrCj{R+Yoj3rlt?uHAd9?_Fy4B9 z7KJqkJE81mOqp8LSH-N24`c{t+G%Zy(r5?$gaeTlh#}>q?2vArGSNofZ1dL*l5ONJ zBA~PY*W5>~YGGq}w<(gwqFt&=lknM~OfIZzh13uOdv8k;li*64!g+GE?rHr8b7-1z zWJ{2|+PjHY^^U~hDseYSB&idULg|U0kVz7rZ2z^k(#L0E4bliAH*4Bx-z@uKL0lSP zd-XA)*6JcCc=ZejEj37fTf;Op^5xIVROHkvY7-c3;iUQS)`ul?D|(1jWiD!lYjw6t zFt>hOgY?bp^;*r?k;J9fx>Ut9&SYd^sXAIlYbhWb2)3;Y%ap%+xRNJD3+!~*k$rY5 z7T)2+X{%XV`I)trP)ABWR&-=(52pCK>I#^|gbJdz_Kgk?jS8oBY>T6d4o*vWTC2~t zT3TX9B74;8ZqKfvzaM|ks|8dmS8O47BBfE*mf$4nqZ+8HjcFP=ZF3Oa>3g+aWde>TOs=4+# z8Q8lWQf951mcSjY6+4O0?5S-ljFN|TcvFIFg`7;Hm=YLz<_1L~gFicJZr@evFwoQn zwMSlU6Pb!_6%|@1J{nSyf?R^So6Fc$@^RHKGkgyl6pc>25kIjUko3Gv&&+BdnBaGk z2++nlMLFo_R&kiqu~y@~@h*)7d%#-c##EuHE%|Ou2&4FCCda72SjLK1WaO@#^_|va z^NC0>H^mKV^EPCm9sXC(cu^5OX)`o(^`gLR&r_425mI6$(yt1lSF5ZN#M2b$mP)tr zEX+Yjhk_aFv6Bn7Vld8Y2}Vp+a0`0Iq=sO+G(y!lAd8vSl5EgsHlN=THX~eQ0@~8v zX-ERe`8K%!&Oy;2QZr-T)*MYH*ka*He+&}ku`R}QSR^M`qY+_I%cl_<HnoS(0mc+hnWnwD+mi45}s=@0mh_+1a zDv4No_^nE8ikSvBqXlY5ImsKo+1pz9d0`lnYTexmd4UEO0kiaTt+z@}uhvYdC7sk# zda6xilh7szl>!>cr<((E5dd94qQ6J7d=jH`(V%$}sI#c!6KKm_gvxjtwZqPi?UELP zMFml?M!FV5+D`wDUKl2GwT3LM7nl+h()n&Z*#s{( zaim3twvCW3m6Q#xR_uim6H*^+uAr^Wa@QL64!5L@PBwVmjJ1+cltsko-LwkfF6Y4- zc&*9x6>3TSY6|`*2caFIw(D~dfg&`*qCu-2dP4(j;$gRe|b z#5=a)i|VE|$Z1y5_L;oHY$~(W+J=7*6?b>Xr#YXo?d106;Gbv%ws8UntON*)FqVbRhs} zH0s$%&JCSus3rZ3jiKw9Y`ce)jJ!<6-9`~j#nuQ^7Q!MOdgId8dW}tt$~d~wA|#Z> zZs0ZwOE$Tt>a`X8($8LDz9n&>0K5d;7A#j+Rdd}<-#(e6ogN03O`j52TZvJ7hFt%i zXop!;i=^Qx77`NMG*gnm-Bay}(*wqc02;^IhH6tRH3cB8YlBR-NwP-hZwa;;876mR za`HTJgeJqXtIsz@;)~$DW@Tz&UsHuNhq0FFHG?dw=v%v`p(aUXjUYzT!Z2i@lS;cm zehuwu_UF__vK88TAf|V2545ERnUIMl7G_1Y7M0C!K`ylIx+ZM3J`>aXe(Eqa9M5ZlJ-ke zyiAq`KPC`nj?`u`YtnJ@!bzvx%(R=nbX%CVtzyWVCO}X>iy-PGie!Uq3MTJP90*Zq z8}{VzF{oG4I-qtQwcCuw#S>yd%i(1Rp^cxjjG-Fm-@D1@Ivr-+3{4x%m;<&X=jrEQ zlHEIPnzpCRGc&~sAr z`%X@T=n|?;vg77C7@l3)FO&Fu6*iEiu}#xIr$e?8vS?JN zRj*BjjVccs>q$^I1(mEkRx?GMfyZvg(tC@yA$^xG9V9QX1Q&xb-drHM4$6YsD%U5{lZ8oi@$X zpmjSb*DSHANHn~;lVmWpI~HklLe-y&B`@>MMqhwb6^#lW`JXQtZh|J;f^O{C2Axx% zv_aWdo6&kLG?+YP-zFHhUO3J6FTL+&;o7mH)?!HLf=z_12$z9!>5!qoau?a51i?P< z)A2qFS)2Q@NGDA_STm$ly!1d)bTXq=ouvprH-jb)eK}3RMZwNwSXGg zb4kQVPs6OCyTP%oL;79|-6!!blYrUYI91JytLamf=GD7@0wpn|=_@4gPU2+O*HQpn z6(C9O*am!y>^+S{(6o!4tTVBg4$6kMe*8P@1JRNNi!e6n`W6%DoFpPDxsNU=!Xi&@ zjMGy0;AJ*bBb~GjYY)g)F&G7~#Yo90jitk*`Ej&fDjh&Xn4y*sN$grTQbH`Qs?+6A zTiXIuF<9u`2IM9zMhU8ceNC(9WUTKrIkV5ej93~MPILbYx%Bj1w?w4{>XmeClA^1m zhp=pKIj!UsE#r9Sdw&a0GkS4}lon@xQ+gd8=xtjN3+95wk zS+@_cY+%~(G=)IKv!;?|`aD*b+%SC2{ieR1)thf-EA&E5Vv-;%GNW4he1s&OAP^Rv zSo}tafSJT!5;@LDSX<)i-GgBHJ_?wv;#{tkh`vu(+$WWJiUrnSsG&7P&b`~pup~tb zVYkYhltxePZQl4KP-}{1k>Kv}IL|yc^(ufXrSm)h@7ytAVnO#W5YC zrpHUw_kv0+%7$q?_s3c9*Q+MSEm2?J@12R0Q;rB#yjMlQX^*>1PI;Z>{3%F?$Y#c&l&Tu83cw!kHnqMdbD_w%Ads<~DsbRj`L z37%=97prc;Rkhzd3tH7jitAfv08qB*ARP zV4Hd!RaA{A`wqk=1+UgFTg<^IVM~(;7S(!!NlyEnOqNK+*7BjALS7baofG0|DYv((&YBj zxsb+em%_jx#0G)13b+PwYCb;3qoeVyn(TMlmv+rwt&NwFvrSbVjSm`_s%@%fW@H$! zHaVhA(GCi7CtMHEMvS3KXJznPb+soBu&0S}ZH|6PDwqB?U3WShP1|UFC-3;8pwgb% zGD*-RjGs`Tw4ORw_r36l=v_Boo`f8}`)lzfBGulda&A@e)qW(hd{aR+^80DhgDQ6u z8_ct!Hnv%Vl)M?lv_1QloJ0h|5FDQ}ULvCsYsO@J*-iF*b-ztRz$7s=@0+q&gA%0P ztVsqEdSOQ{S84LQ0g}jSC!cQ+omNR3(0i8D_OffTvp(3rdmRUrU;Z@3T&)7#sVBXtg~`IwVaPt0}0a^T7-zq=bpk ziYufck?tueNG+tsCHeXpybNS$RQpbPSH_2;9rP_JFoHkE@P;g1ui##rjJ2LzQ@~3L zsX35RkB^|U6X7NE?{@;DHc48O*&79l1&)8GP4fvMjanbA(!UC7jr?VW_VJ>^yCtc2J*4@-8r{31D;}E;Hn0 zj?*@EntnMM^}K2eoK)kssy(&Xw)}ys^+Aevrh>h5KTi>YTaa)2Z(+^9D{)8%oh9ZO zWY@s&4Z2UUEAcXn&JMM@4dP^D`!b5({=13RwZEstx3z$1#>M)`k;NL@yIYE=V=dr-+G0#V663V)6=$742kg4UhJ9$ z(lr=JkJ=ChE;m18t9XJ4zrN0|1%pgbvW|TA+D#y`RmMplw3(L?%br>r1bIL8Uf0&p z`hClI9clchYRD?Kt}P7vUX}xs&^fE2@`MWI$@ew#ZX%Q#-JKJp);{OfIwmA_MI;Pb zOh_^%0(NZ>_r&bKtIuhoUo=hfY7aK`NZaNyW+(}=O1cW^#_2o+0}vNf);Bt8>VpMbcRWNaB>UD`(0 z+WIbJZ`V_Ti3!>^R?n$zbB7agSMJ=92Wb>Fi3l1A{j{eOf|DxybS|Wk&Zb0Z5{jn1 zvm~U}l*@9X%r#hj4C05p2$rUPr>O`HB;15*Om>=}_Y@GC>*UAlu^D+|uoA3EeOALk zpkAO+`Lr9&u(6-04k_@i0kplFMUpqIqmv|pDxs%cIF|~gkvg0DsR?+E#AjMy%l8^Y z(TvjCwoF?SPru8PG0+Bwlk7I_k+ue!S(Z8Q+B96_(V2Z}OxB2SZsKD@x{dJILjE@> zdMjROy}it+#{!jF-h?sgphF}jJjSqxq@I(LWg4GM&yCd1Od`_)E)mX>A&#kH?o0+e zF-fo>Bv;9e8MSM{gM?TJ9kM-Yb4cAyBJ7y$U4IpFXKc^kxW*E+YrelF%_%uDg@F;w zyJM2JB&jF+>&eks8hvj3AQ!uBY&!OLgVB8egF9b zfAy$67-M4ACUB^AW+Y)LF?`~nXq6kaP3Yh4b&?s?Q*8>f-mEI0QKW4MCqdqp z+tU!E6ZGM0L%LSXtZg|u@Xd3imRS=XGq&l(SEJTliVIB`?G%A+>%*8N+EG|S1KZcD z{qWU-PdzVV!%B*8%OPwfc3aj<5SQ^iNDS|Fg2bRRt)+8C03^0HnL;Jjb5nIQu=~?J zZksq#pd#K0B{r>P)yA%x5S!7Sc;eYv5?n(Ajf$oDw-eqDC9rGjfDlxA9~KsW8l5w1 z-jKIf6w8VhQ9GDE*P0q$#YMW$cBa`;`b0LRR!o!LruNCn8X$gSO9+2Lyl(;J>c+?s zkw}IO^066gS{MY~TX^2gwLeuEr9*<$H&_a&?+(% zvK#uzgyGahm7sb%SRP$9N#D{lOpe{J6jlL z9zCtU1v+hQ#CIi^lyo)|!S>#k1Gcq2pCVVD@)(G}VZNuW58J+=gutSR%}vwlE~Fwk z{e3gSoRDHwTPOtiwv5miP8kT1Dmz+yji}SCRa3%{ zUYH>P+3NZ#NlBGa>ercydJSZEDknRdxr{w65dCs(t>r{5L)uU8Msk|D4k1v@CDFFF z3^cvGj}s1zA%rjRdTZC%QBI4nJ&{ng_}C`U(x|tsY#T?*pd)4?kK&^xLE7n^7FSyv zx*-NMhIv(JTLSo^Z}VzZLaU_K%$zt1Ex~9R*&WC&y_iLww{129M>m2bC3mJ}K|UjQ zB!1V5>|G;lCP`c>Rq7F%M7Om%S|zX|6Hi0eSNqNA__y)r_Pd@ok$Td}^fY;HRc%U2 zmV^I^HqJu&ZM-m}>C@1=6Co{{vNNNWw!pnY!CR5i+MWvv)QImCuNz0LxtSUrkB`6O zMS*!8w=@ed-2?@612-CzmSnYSHceX;cD8tRks}uHRq$`3T&r!jVYXXB(_Qip9YRY> ziVhwtRlc@G+PJZ?V!x9ZnYItfRvQAcd44Ow*gRij0c{Q=s>)3e8_ckX%oEDnz{wG>*=iKp8^p z@~#N|v1VH4+0pu?k?od`g9CeI}qM(-F%RNaV%VE-)Et3W`acG9A1r zlQV&cOl?Sl`Kkrg+Eccbl#;-AX%w_)wN$G%fSVCmN?h&pr~6$TV4pZl+8(wjgsB#r zcfqQ?@s9Oo+iui4Yy6TEnc#vvjz&2X^xqHwQfh2L#Hjc+a*9C#Oy!pr zbDJ%0WFL~!Cf)X@0v%1Iw93N9D=KUM*fjh_?Aq<90Xx6Ar1QwKnG)B@PX%st&f0PPGQh!Ww+7SL%V>J`& ztoh$*3+lDB185Q3WPnf(lMNDNNgnMf+cx4Jsm0J1gi*pYy<=Mq-U&9OICqB)e&VZQ z)?FJNJAF1ytG~6I7H81K2j(dPYD-415~EX&0*wxvz5W!6Wt2W`@?+Z^X6o!DS+ag5 zYa62xuTu2U|R{&XeYRyF%DoVcZHCT3>Ci8DYidlzoz9(|P{!S-l!O zLG!g&-%GQmDQKqb&(4I~%!$U$8ibSXrF~x$l`;Y!)}NvI&r>gu`i>Uren)kDS|QqG z!8+zGres!K@zfIhE==8?u^o*gZbO5NO|W_ggrDZ)4ZLiVp48%NF2wfQpC|$>GC`DJf$E%{0#x+TX`?Q78yfzl@)S_$B*2n8kzpiOCv?L@|0Ik18 zm3#B(paaNcfuta5VBYm`Yza+5IMU0eo;q}Uo@U)60eh%ky)oF?C-_m`q%Mg z$h_?tG;(oL30#8Pa$p+ay9R&TUb8I%u3{_|Oz%$8TQOtDH52-~{QT7+ZhTZ0O%)S2 z?09fXs+08pDvp)b8p#wGfk^U_qITcQ2LDEYvHhz?m~1`L7SPRDp%Dg356diKYF8NB zUs5S+n}9eaDIsiCRO>h1srpZn4MITdCOJht+9oIRWFt~q2Kcu~&?qPp_lD-(&B65U z>67NZ+y0*B{LQ@@v%PoniKr4@$bCjJm#)vW6sa|0HCb8`+_WU_jy$_OL`j2NN$uyH82tEX#8~{TLEX%!Gb=5*LED5-(PDJdjL`&Q7HpuD!Wh%a@TL^Sav z;=MIAPPX#JQGQqrukve^_*+xJx|YkCM*kjc_hOyrkE zajYdFHV{~6wk6Qg9C$iB$R%_kTb(eP6AWm4>`xE4rMGMluaiWmLA9-NJ~;$V;mW71 zy-mPah1*iFp_W-=uivS*TjI6^PJOVnxPai z$8GP9;J);`XhE31sbRRAIrubMO4qhjxu>Yb39P@s&L$x?FeN1l)PlJSRWFBY3Ysag zvicl$)XpY^X5Cu&*rHhmu}W=$CGk0N4ZWL07!vXdryJD6a3-$M2`e{EzCdlvsBO}5BE;>>_rItLO^~i}S6gPSn@)FXkI><|RHHPKb-_D_5V<51cHNJB=}07Gz{%}fb3 zu>pVC%*%miq|e*tv~4@m??`JZp=E}kdsj5-uAR9hKc;(5Q&y&}v9kB=drbVImM}D1 z^99u~oR1add{Q%n4i+MoHWa3<2Py)S%BMk)P5e12v|0j61u*+4@S&euSajMPpx6d(fX_Jgj_fMm)6lhl6^R?O_3+i8oW&u@? zt@qjxG~4TJMsX{wP2}ITU1Q&I8`HB~3^P-2r#Tj+%J`V1XKVE*QZmt=ixEId&;K+D ze}WTX*U?%Km?^weO{aEVn;2R|lWf96$qG3lx7cr#mCYWYce&``P+R;bBuawxS{9i` zB@H}gxD=_Ex_CldEy&hlF-nmO-(!WZts^Iqy(oijTJKE*;%P$H6IBx(7Hg$?CAUuZ zY9V{uLby|jFNp>b%_!k-lT#mqBzdqbBbOGjNfn*#!<%Y-IjF3b#!36}2}Hv9 zg7;z}+$4*MLvUg&wIXVbFlve^3fxzR>XO_n=@>u6{;b=n*z(J-^kq|tCU$hE+qlC{MJ@&6<5>~(D0uC2UT-gf5DZ$Ju&$KWLp z`Ata3fF~dzAt${7LOcX=dXaB<10%~IOO|cn|DR)S)t1lEK6OWL-`B<%t876@+sEhZ zs#R;vIcEQ9z4g}81DyB>X4JKM&>KUh1`@pmSGS4NoVkYJcG5jJNi}VNrTbw>)imce zE84C2+KH+w`8Ci+SYtVF3n}BdvRZXaTX|NTjF8GS3Qxs%GvZ8Qf@xE3(*7aN$PVxB z^Vj^CG{y;{Ta6Z`|3+Q6fpODo(QK(5t(eL$!PjPnNVC(o@8j`!EZ>XSkTJj85Gfmk zrG;j#27ui(`TTlK#p3k)NGQhymFYSeg47KaPg4*yXaXS#yCP7}RNHJ(iqtfvIAaq~ zGtM-l&K=V%Gj2%caVhqsI79K;bou5rR`;e=wQSC$<;J8$zACVdy|#%l)9*4?2!#Hs z5yn&yOawrhpH8#)9RierM^ndc*v01eQblKoLuSTHf+f46HD>?UY5&O zk_)0C(0RHJ8`mcx zr;TpHRDhWVfo!kMhLCM^(`!s;lB^Xh^4-`mP}9}g#U~6W6Y9ZXmV+5RTnLUWjoc0nQzuPcUL#g0rWwS_swwi4O)93r6vdIwYe*PpATQW~5+hU|aJZpVgsSWM-gR3{D^N}K*o<{j z#hYMdQ`is`c-lGH;-WP@GgY}Mwh{-&s+v+)*6?$7yUuBAXqxp}&QW7WezHkmpA$M; zlkN;9C6k0w!M1BAwtLOY{bwQXk~3$2TuXmlK9AM zFct327_3EA$lS^#<>`#1*EbwA8f&!c+tayk`E!O>mK2Fo95s3xso6Bebt8Oc_!kd? zSrTlXEV`RHu=SE4*fh-=Eqs?qja02Aeg=`rrXnN-Sfdozj2lcRu^X&Tivhb`W0H<_ z&YbSURF#w0hE>0`N%01ZZMx?~a%{BtO+j>W&f1ZURccM>Db2^zY@F6Wn=^eY}EAC1jC2bcmnUjCNW-n#c6RW>m0Z=O-xT^w77G zzor77{!G>MZgM2DDDez-Zf-kcn>q@bg|++6JJ;((d}tC1YZIAK4mA~hT5oGMlXhTi zqb_K!ztO8mS_-H9nYO^7CSZzxN$f3cCD1kojXK}s%SyASJdn=4X(GwZH9aE}3Yg-M zHSt;%K`XtZ)O1fHo|b23b&*mrX4Hr6w#nyTkLD03?+cqG6B{+TfSSZOnQkS1g5gCZ z0?-8Ok&0SkK~EiyJwS~coRP;io(QI3x)mI)taU1a8nlDXWZEDgb1rN2m5P_$Zr!lP z%`-$oHpcqfB($b5vST!>RBM}uR9GZaJ}bk1iq2XIrQBkS1=|g))!(Ph zD=V5sqJ@n;Ih|b^G17=`8mpzX?5~Y5cDtsjVxP7uj3`Dz56GB>;8-IqokkZ_(4rqe;@&A)=ubKQoAXI-7|Hm9|98 zIxCIDn-T1zAr4Y-n`XsnH-FWUq@a`LNkpKT*wx8K+yuIb2Q9KyJ-eh#u2HA_IvPpZA0<5>L{=8SiMX?pDoqc0ItU4}CurK3?HKf=g|qA~J?R|S%K5!!x6M|A zK}$?gw3>UGGfN{fv)MEiO}psI>3uQU{Owr4n6%v(Yc(#m(|ubdGIC-~b8jkIc0T_} z@i8H=tD-z{6wGLBl_Yo9Kc^8JaVD&Ey%}NI28s#`Guolgm!8WV?qL$odwX3T_CQT= zl691v0y8;3PF=u+cBIPRCO+E<*@9uZPLnjNozId$mW8Lu!rDOg&;BwDZIet4G{5ut ztU*`0hD1be6K!J{G@a#DBC*|`nFZg1NbW~29cuz(;B%Y@0w;kbTw*y(9IwyDeX+#4q^I`Cz~6_ zpv3HFr=nmYho#X$o2*F}-gHu?U2l2xI?i9)jkK823ZR+pcX6iIBgq#5*=w&>GJ zbNWms;C_BBS9bNMMTbEdj1R`tETjT-RY+NQ_}OTlF%C0l-*nmZeTiI|&a@#7Qld}S zgT|t%P&Sg#t9)VKLqY{@Y@*L(;{3>gutrh_+9$z~M1r$E7}TAf$Uz%Vd()*_!4^Xh z-O<@d#7W|hq)xy>>NmP*3!Rcy|LWYOF8u7IEL*5=lmn z6g#LOYno6pc}#`&#{?PM9q%OULDJYpfXE;ft?|uN7*Lm()>BC)+mh&v>6WP+X%*9m z;20w5t!~kd9}H?{6;Y?_M;5pi@t~Doi@M$Pz%a$5Ijd%TVT%EBy4=0i7TKfcHpNFm zB2t6W@O5tMfY8~XZ4t9&VD6R4GsIEZd9G8Fn2H*MklW|SdZ?rrZ=QX+UekM_ocP%3 z#!a`A$d-1llf2_;hDgj_nuAlfWas0FNJ$5cTtcbHX-P+>8*M~&ZteLuUFON_8>T$T z^)?T8Ds-pcb)ySUiOd)cO%A+Cg5A!cTSTk1ly60d(r9PbM6N+DPtYa0X135X$gDL5 zONd1)PDMyBZ7@xZpwW{^YoNx>n#AesSi&|3>7Jxwn9ljf)Ct)+e(L6J=a>qZBq7?s zaJJ&3d4JQnHl2GaxOUmf^m|fJut?1lG@gj#NPj1@-oGruXsyFsL3D(6bKr>fgLBnN$-AzAP_uf)cQ!b!m2o?Kt;vP zRA|xnCzGh@Atp%dX25R*u9~X+)G^esmMtP;i{KMOvbHcYCiR; zI;UM$1GP3=4vlqsqgZN^fR)95=9tJN!__m{dUsgjTieYVGPZqx#1~rexy-05CDv#A z$(UWSDhygM^;gGi((l zNj9DIHdZ+!IdvwdW;2$z=u&E0h-jS9G(w%oS$lP#SHJgRpuW@{CCzthB0?8G&F1ZE zlFaqA?LqV16r4zL&^#cP(K~(4)~R#lm63wsvxB4AgHG4Mz^a7wB;S+?DWk-i3alhs zdppTY?~$GlD%?{NS^bWaHO}VDQRhrTKBQjQ+BWZ4WACDS=HBn17zrJi5>K*BAjmyE zx1^>^_mGOAw7Fso_b87L;j?u{O?KThdNXMG!**QejasNGxm_B&U9*GTY+DdyL!6CC zsdM^0ll^(R!ITtJh26SU()%%7gInFQWnDM&*k**W(=|<=Emp5&O49ao?QXcFXVIVr zi2!EDBPV81?dNScA8D1~w2*Jg(Yl2Fn%{-kkti?~tln37!U5Nb!0rf4L0 zWE#UH)Y8W7llw-<$SO}qB}!6}Gc5F}x}pm;LDC7%F>tfFSyP2*6#FJ5#ujGN0Y4?* zOq`Xe8n@Lc!SlD6Ty;3DdcxhqqY+vtlkcy3&b<)DWn5d{I&YHTp2OMmX)r zjG*oGIVl<0&4!yK%ce;oU6Mu?+>8S3vu<^Z%snG9!j`|YDv+!(88vHG()*OBMytjT zs}Pwg!sJXh&3G({hPvzN0Zb&wc6WL51DUEdIta#VWCe3ht3pYDiyjgZLYc-=)-3Im z_%l5q%>$8C)(9e;U{KT1n=KJC**YdH15KU4$72g zA5?)FJL(j~(`?6*?~Go(fh*1HzMXe9frGk@)f!x?p2-olaYi-scVp}_ecl~NwIw+w z9gj3>Yjg*a<*=Q9w|)*Z55K|CZ=u!ZoS7ou+O6MQXQkrICdcW#8#It$b4n)97z?KJ znh+9W&_}U2t&dLEbsFyx$Bf8{X0y;DvKB=mG}73GPlaF_tx(KLqscTWc-ZPOLE#2b zNP#&Kt)>yiq+5}yP@8n9B2DilS#pyNxPjc8j&wqTD3H@is}06C&<8>i=yh#%Z}dhi z8q(N}8?^35QnhmP4L8AT;52*c&)h3hw%{i)fh?1tWCJ>7~7(IP2*^c0m*yGp4A;w zM{)_JdoXDurQAj8n6!2BFfz*thFW#f#!@tq7Rgl0J}6c{VRZvmEczrul9IA9uUWmm zor_tKlL=W^tt=YVwH*YY3HoHg-IBE|N-)h=Y5j@3Q^*tLhUR{A-ILH6t*Tk~&cr8Z z68tlu{RuWUJ|?T=Wefk+b+|2AzGKdnNbZE%q!AC9cGc`i4xLezGFHTGYVQgA5;Jtht<<-ZahP*pM{0Z+i^$pIj!df zZGo6kqb>GLXPd~$w~Ht{(y&thsjE&KL}@H;azrAl(|E|0#mgX&z=pDH2S#v$NkgwAo5CRbr{5Ozh%R43S8YT}`D*vf0N^NwP8bx*gb> zu9P+@O!C=5!$?Sq3dclZw7iq%UNiz{DMlm|!H`0YXpQkpu=kWc|0L}{b#CnjMY1}- zt@E*4MAFzMb^2B{Hlb}(Gieg%jv?EsX|fX16PjtHqf=hHp<>#QoQ(~{+n;+hFc)ng zOaXzo1WAUO)~XT=oMt#iHafX(Cf7GIB{4}a1u;6nwwkB0!352{S8$_(psm^s+-Kp- zRnVa^AAL3p_1}cCZShb3D<-%m(<)mm(&myWoQ-~mxo1Yv)D$uelJkr)4vnfT>X%k9 zli6Y;YGbl#pbdq5BS^?20UXf4^&7~-?o60e% zO;SKG;{mIfw_1C&>#g)N2>~*J$;|92@EgOO_cj4aU4!XDrg?P})DvPru%(gazQqA( z%zH?cwcVyjynuxA7((C8aZPf_rF%F9>M2I;_^RBz*Ha8k32K$>8*;fB5v}g8HA*8i zgbIX{W-G~C8-7OOb0wdSgnW>zwiSJ(ruQoy2-AU5H$XRVmCVTcH$i4QV+0G~8s$jZ z&owH7D`A=4*`1O9CRI_h_ueGyMppV(Az>j>QkYA{^A0iB>Tgg`dXMW!FsJGM+r)4x zjc(=6?a0zX;G`3fP}Ln~zrA<2P>57yHr@P$)TQFX#4+2EQ{kC%m05#txk)rCp;3F2 ze~qSvQAwV1;G=_Z{z$&D92RP zOb~vviSi!42d0oc-K94|Gz6Pk4F@Y{O|Ns3Wa*sK9F(>XXl=?zhpopaLnOC5BvSa^H0Qsewvok*>$v z?|m33)u07r3bo=Lq*bWo2VsOYsERf}J0;)5$WCLPBuPCTR+1z&Bc&ZF)kckM$CtxG z>$d`?O%yAF*2Mg_@1a>arBTW4&pHJl>$^da>J+R}!Tt;$KnmQ(JZVCgsK`tmU|J=- zIkqq{l8Q&W;bL8wO|ov3P6@e4xy`PoPKAzfIG&ox)w9%YS|yE|bPlKDz-)7T4S`5q z(e8&$Hl@=Bf_d0zJA{Jbw0e39!6(*j!{)b)UNgh8P@Abh6B3fT;}e%f3r*inH0^sd z>%EQPO|x=*E2+Lcmqc{7g%T;%u6P2ce2?k;8Y^{+o>AeIwgXnE-ZRk#i+ovy&Sd9K zae|O+V+AlJc&l(}5C-$yS<D_yCO!; zlPy}+&ef?9ni4lzHya}5lo*XNEkXA;MpR89Y)E~nqn*|*6N+G@|IMoG>SwIF=){vV z+caijlz$=G{#Vql2;{ zADhji)%f)D|NeM9uv^^mhu9ro7=^`Y43`p4Qe#YsxcS`n*|DggecdT{k&Y3a^>hx5 zv^u@71Pqw?Jza-K>U>Go)pRBa;xytr$)IY21_@4WAwRN0CQ%f#Ha2Z#n0aM_P3iEp zQr2X!)jYhmDz;sG8vqFEP6c0cpLf(1SDop5t#O(_M zUtL%u7-iVF6tt#?Je9W^~fq?ql^(*o1MTYbM*}6JMTr*{-i#6a@OthV_Xfb+&Qt_37MPT(@oT+J!57g z!o;|C|kQ-@FagghuLVVy>LT5 z(gmJ6N8)Li5yG@5og`X`H$e`ZbO;+V9vvQnX4AuaJJ8Ym*0}3Yp-ON(?Y*aBV-hYh z9pBDw{EdWi@_VMwYOjG!L?;EQZDN{&Z^!(|a4t@tlLlon?zW}?pNf+d$H;k?5D6NA zn7B&Df~)4WjgdPAIGrcrk2IW`WVl7^u8%g+P!*WmD~(#9S;sOEB|-V*gFzQM{l0W# z?2TF-SS!fX1SAp(vYj<4UG8+1iOgoIbIU580xGQxC#c#&>^Ze2nCpChpscq0^JDw$UlN6GT85K3rL@aKK zT^d!ih~QTv3@{?a&4bo8nsYTo}QU!qbw#!ruAGSr=Mb3GW@&! z`FEwb2_kO@jLDLo%obb3%$n!f=7GeflE7TzC=9&k(y5LWyNMrbND8(rO%SC8!?hs4>^slcJ$!wis0wJ^Oeco z5SgaHXa2u|VsDHanz7n5I`ecLlNgdoFlM#*_J&TASd$W8qc33`L|bd<=G-*n4O%=&-}0`!30fDFp$yJPGrn! z-ejwM`vBPOl0>L8L5J?q=K0aCKYEiH>3c*BkYtacry+&It>X{G~9`{ zuuW*$ZE>M?^w+@fX?$dhvs3*LYHp~AAqnSZ!^aw^?W`7CczKFd&Dv~&y@@Yiymk_* zkO<4k4`~vyN-N80TtF-Dq%mRgU%RzsVwfqA+9vMp>pm=+kki_N0)Y z4m%y}1Y_EbjOIyi#Al4%*u{w4&3in%t??Ia1KxnW?`Mv|O5o~eLLfg=&35<%-( z>zljDiKA zn51p20UGjVBeaz~l&0d$3Z_x9OY5Is(}A#EV%jp8)DdmqA|ajxYfoK5^2M-XDv7mB zqk@!3Ca;m{keber^!}URneIbl{9{N&wo4{E>2?R(NOz|MNd7BE#AQV+rv2?(g#&fd zRJc!PFNrWU^797vZwk%CQJM;CTM#x4(@oBYSzR|pnklMx5+m_@8d-YlLt>v7YocZq zD5v~5UCTxvr{zkmVrJuGWGxXM_F$&UiEai35ktgF9ZA~Vrxh792X3o=BM+R&ob4`n zg5FFLGKy!!Xg2Tn#QcXW=8aevjef{7eM)vl5!IN{(JuD09V<;INw9QUi)+qG;~lV5 zI2jYY-Pbn||Aaj5jGda|fxg%1*Gz?B!^xP)lq=-Ze1_zlY#YP3uQMS?(>9^eQX#7V zyB0s$GfXi$HK-2)anp=ES{e3)9vTPFr1+Z}4k|*Z`5}c@GX`4a64Pu)1R^DuwA*h; zEVe^89Wk+ATRYxqZj_3E6|_s5`zc^qp)I3RPsI;azEe_diEZtSt?gKCl9>Y01bdP% z1w9{0;+f{GMqKLF7kQ?%ULHiOHde|vio=wojBi8wJM(&Wlwo7sDe}}@uLO}dqau^E zlivfKE%IR~Y5@tyd z^fW4=#B2(crpq>BJ9Ge2A&|&KBot#hj&!(p%*vV|ZmiaKMj})wq~x9K(9KM?vG#6x zIXl73=)9P&+l(4+yN)(&dUIBjS|91Nn9-W`9kJdkx5iniD5TMz<$@WaHWiyIp2ChF zhZ!AMg1gmepywhPDmF3C6d$%hnDW!ZR4a5q%>12_QG!rNr=XcnC8CazCr+ft)n_(5 z1Or3aanf$bN~9Vim~}It+0Wi0B4+P-y2lA=u!*e=a?Kd0O~gi5gZ9`h(qj-YBmHgz zar-?@ac70f%(L7U!8Z`7W?X3spJsh}lCw?(*Ce`kvSPmzYfLe0noBe4-x2>yhH=)S zAT=0ozxUA^!q|svN}@)Ik=8_w*b{jcq{D7!niJ`VVtar1( zFiw{(8Lc_Ht0HYWyEGyJ<}4Z0qx;ZU2=7AE)FBcfl)6*5@rj7N zZiw6|ZnT>i&+@-nJmnf?>HX8Z)i}+XZq?Wn+X91%*qs^j6qu*^{j|ZA zwz*na=iO(_T0*C5-=HN)X}4*J5;>B1JSOob&7f5gV{G3OfluxOkPvrRq44`5x zDp48@g$Y4u2$juBRZ8T<^0vGAR&0iT-flRM*p-1Er{aVd)s(P^q-3qCZ+zKxy6L^8 zwXBvbYOUpM05_e`ZkK72WlM^saezgH8gZgssCt)dJ9!@@LN(3hCx_BD*q;>T6CbVd zh)5%1yDiWban>HZ`PtNH(ulz%YAcYK_;4gXm2{BOeK*e|jUXR302$(3LMA47)6Rs< zT)t6*n<{GBfnEiIq;D`?BvY-@sDrKt-Gu2n zG&bl|K~D#7g>39Fy{(P`t!kc*F-VMtP$KiXDS2Bo$0h^o9dld8+9rk_GV`j#ZpTli z;M^HCSww{1izzBBf^@PPy;GPbB-!d&kPzDrABtj(+1g3wQj^G&)tyKUA2mv?Ot^`8 z3E?M|(li;E2&^b1m4d#N(QK-B+nFU&NFvXfy=&T4PB3N)MhVU~hiRINuFwvGXN?ip zE@S;xz@x6N(GwwuI&0LFcq-E#daG`j0ugl%x372O1wg(pwsvu8D0o(Q6 zWL`B1M@{#|bh}NWypc$aZ2iPl+9`raG>9Nl`@GyZd#Sh}BKCCe6LWrd6X;n5r9tG{ znA?m)D6tt$k|_yJWPiJ9X2um$UYi=Rsh~C-z;?7VJ*Oo3o$~Q=gpo!CHo5HB*OI1! zaj7(#=E*FJZhCspv_a84R0g6?*NjN26U;mnTr^YL2~<0Yk~UJz+}V(6lltA%p(hnp zvwqf!B|W29GXzPa+S!O}?OITuTo+oSP6dT|Cf}YT<7Rt8G`@KrP73>ECqP?lX=H1p z_g5puRM1bT7d5e~qLD1V(|FDhvzuH(b`SXuXTrJ^S#*z^GG1bONTte%DWJ=09qLY$!NblDHtL!!~P_&$tF=uXkFSkdZrOTaxYGhyG8%( zI&f+Z(m61ztH~g&9cPiwhV_O@XvpdsJlc5_Rf7o zV`pof=Thf*D<~1k&776fjxlvCbVkft*6M+x^~uTpeR4TGL1<3YHFpXOtL}wd3JL$=+?zz$2Nh_@rLXi|cr-H>6zLs=2#gxQ> zn8qiRGmSiY8#5@U(5Pbc>2({1Idz^E-kxAG zn$^w^Q!JsmFq`t~V=jEmCx2opTqV zF-TC7zfJ{`vBy5Wr`EDOJ?o~3X}Kv$kvA1LMmMO9H+I9r6wy{)G_7Br2+~>d@r1Av zmuDK!Jq(gSo!(R>t~z^i=Cq1@37St*(J7?68uqsnFkUK>>1$@&in5~^vQb=d+ zCYR&%OeA}Pbk7Wn-xl-jCR{2wXcTTb>S-KDsP{CgGepH(_nw;ZspufGa8mSr>{M99 zcu&SGgdUg?#ZHG}-8T)=Va;e#!cFE&O=X_~xtY`2B$v9dn_s3Qz1@|PfmCZ@Z@f4R zY;23elWR>bu0*IL_f{HHn69nqifzL~Ba_ugYeEMnM02MQd@m)+cKn<+A&5+9{ZCRe zI`J#0OSW-^(3y$fKz_MWVBbD*5*jK!@@Nw zFr=GfR@0{7WM`Wx_!`H_6V5|IQ0V^dpb^guhN<`@;TTFFQzcJiFpKIn+>@PaeFAkG zS3@eS5>#ut{U)H@uymgar0MxHXM-5+4N3NV0(l!8%GYz_7&YBU2T6vH(uD3*% zOz3c9U$8nW6N#UsrcJ>|O~5Ii=0VlYs#0p$n`vvJF&t{mb|z9BSu>{<*436tszlRV z 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 ZU;yYZ;@0;NL#hA(002ovPDHLkV1gl78tMQ5 literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/keyboard_key.png b/src/platform/wii/images/keyboard_key.png new file mode 100644 index 0000000000000000000000000000000000000000..e08dca2822916a9ed2d86f6d977ef30fef59db37 GIT binary patch literal 981 zcmV;`11kK9P)pKne?T}p zxk|w%h-Iq$fOM7@k}73Fs-z91$^9+CQUtY9EDlk~iKy9mla;x7XJ%(ZAPcYV_s-5d z^RZJIjYba2<#Kr){_ZQ4%BxU!xP-nqr_>kwaT}L77S|f;lkh#j2Yvp}CSOA1W8AdW zYLyPcn_0)SHC)+lx9)g6-i0+k!rHsg^%e+x1H{2bgTcV{`+fI6%XB(*#!o&Xiz|N?64-c#17f4AMq)js^VE zT1*@^JD&5ioTwy`V8d<^9mjb|0#`eT57H**;EwKviQ^PUx`?M{faA>H+CU8^lz3^4 z*F=T6O2;Ink+5aOHOAmoi=)4!lcbUgqqOi#+81j%wvnD%dpxKGp%xBuTu;G76fsp6 z$@Qc}B)LuLB9dh&DM>WclEKW0jJ0(s_|T}yYnY*?$LgYTA|lQdm0TUR+!m~)6eMMQ zIW9|3@KSJ^a#2>Bm=jQNjPN+b&v94MJV~25txg3atf9v2);5-il!9FkWTP~Nsvs z&CL7otsvX~`1t7HOJ(&n4Yz1+!kq%sK83!YzXTWn$j%(#0h)-C00000NkvXXu0mjf D0$k40 literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/keyboard_key_over.png b/src/platform/wii/images/keyboard_key_over.png new file mode 100644 index 0000000000000000000000000000000000000000..bdcf6ceff0abe04b6f77f173fe952414048b28f3 GIT binary patch literal 929 zcmV;S177@zP)!7bN7Az6y^7&IDcvUQIxNtQmIs2qtPgPpH8Q9+iN~B$7Zu} zd7i&1nx2Xh-XDX4??JgtsB;SsZIj8w4TnQFo6W**&irLt zUBBOVolb|H1Pg?3@TT9nNxARbg2TJjYMtwag5mW&D}LVA>-AE(g~cs{ydEXV)vh#+m31ws@?R|;}8lN#Q~cn_L+ zMk&CwYQYqoNw`hrJ!zb`VB{Xp6%Sg&Q;G#B-AFqTYx9-?QuaadNht*Igz<>~Q#^81 zNSZwAN#L|qG zF2xcEji{x?bLpBG>a3y2n&(+~Q1f8XNnS!RvBwG>l?P*-rCC7gi_bJE)?*_H^-$?Q z^;*d(BmCvw393NuNqx2NRcY)vYZi=i}$;1XPpfVH+89;C`?nTmyOq#fZ- zfZ9>C++yi!BQ-9^LS#%H2LsPx=cV_7(w16p7sO;Cc5`Jt(P;-L&8xL@^I5_DexGC! z2@;l!MS=mvVV>vD7~F2RZn0R1_jmNp&k~zC8QkvRBPO&3<>vEwsow_h91d>1 zUc1$51)st#|4-n-&qetgZu>*0Q11K$qkRkL`zOEvl-fNLulU?^00000NkvXXu0mjf Ds*=0z literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/keyboard_largekey.png b/src/platform/wii/images/keyboard_largekey.png new file mode 100644 index 0000000000000000000000000000000000000000..4b67865130a1ffe375ebd38bcf82f4fe2f895169 GIT binary patch literal 2795 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 literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/keyboard_largekey_over.png b/src/platform/wii/images/keyboard_largekey_over.png new file mode 100644 index 0000000000000000000000000000000000000000..cfdd9f984a18070ea4418ba4878a8e6d11e27dd4 GIT binary patch literal 2733 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 literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/keyboard_mediumkey.png b/src/platform/wii/images/keyboard_mediumkey.png new file mode 100644 index 0000000000000000000000000000000000000000..931909f3b6838894ed989cdbe93a524a0ecfcea0 GIT binary patch literal 1434 zcmV;L1!ek)P)z;!p-i<q3`EVu|T#B`^{fE|dWPr7g)g8^jYHu}j)JV2Qvu8|N(F#finAyUVc$9^(fM1K{kU3xrEjb!d zg*Bwak`-v^NR$%IX;_?tB}J@tG>E~GUripqSky%eEhJNrj?cm!Y0_wca$3=1p-==N zO9+;e=JiYhhk=I}Lm7;u{S%Fcyngzvbv}%dtcZcM)u^+(ref)WS^jB-x{xii0IDT1 zZ>PeN2h}{X6vZQBo_l!)G_cr9S||hpSkgG;(5%2~m&(P5ySqD++KjRwl`kDa#otNhxzA zTCAyMDjKa~Cc@H|T8zz#7uKYZ)_0cul@fvIBrt|B5(9I|v+O@uH=wkvfNf#2G|__S zXla~f9#1k6N#slNM&#W}Aao0!hqeMx4r~QlwfL3IwL!?SHT|jGY+_Exl5H>?a#S$B zhQ@S(be@SY?}a9A4W^~|E%%z1bWS_%P@r7gpxMqa4c|tjf!`1U@n0*qKq;a z<`9tm)8a4*Y?V?u37O9GUj(_aeH;;yMo6^gEzy>yvpz%5K ztUY2B&bpnRp2F=x%>kgTO3fx}hmS1jn&og3T3Gad%`RSFU%Qi&lXkwnrKP2T|KN2v zEI2ti*`}=NocGgWzO}UAQ z3HQJJDYv(`?IH8Cv$NO~PU(M%Va!K={q&67K0iNq;X*J~$)=BGkJWy!WH>X!fm0N@WIX)&t|@&Et;07*qoM6N<$f}wJ&NB{r; literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/keyboard_mediumkey_over.png b/src/platform/wii/images/keyboard_mediumkey_over.png new file mode 100644 index 0000000000000000000000000000000000000000..fc6ed0761aba4abde8bf779fddb5c7a2e53824a8 GIT binary patch literal 1427 zcmV;E1#J3>P)Z|G=93LO2X?fLNZ`$i+`d?W2*TKkG>aKm>g4&rRNqAm?a#f4Ep!^`FT1yIZ5O3I9uzunVM}nJUmQ$dwX+}MuLzX zBjH;2bsZ!C`CH%fo>?zPx0UUgg6KZCx3|0BtpM}a=VANtVq;???e6ZThlhvmVg4>c zDvk5Ka$gQq?v=K(jn%#s*`KZ_@9F3Azg1TXOxLm2XUzfiU7lwl?(FQO83v9=(x4ypqM}R9UD>LEbN>Wp#;Jk@>cqjWsSXon0 zTMH)iJmh?=ek|Oz6sSRFXAO=9@*IfO@3Opbt$>ksGb~10oXgPjs1`nGfgt>08seFo z?w1Jz2`JjeV+6V{Ip;OB3dI1Sa+-| zqE6XFEbX!cWnmH0LS}BV4?T{xCZtge>n$xEMlb>`BC4tSzg$eMiCS~==2mMZ%=|Sx zH4Nkta7NgCgnF>?mJtr3c}lbPqAnD9nO>NwF;jC3SJ0$13|O$>@kBHS6nj=^sX7ip?@D>}z#0Z{7=M?3J(J{T} z!^oRO@n#?4F>tQoaVSPc+TwA_sG|x+A}ExRIKo&vGg-@|bX&v;k@;Da%A!l2wP~C~ ziY1dO4I{UVFl)!aIG^Qc7{`m?6Pi;5FTLo;CCbkGgmQufasd5vdohiFp@Ee%6rW+GjO% z#WGPQyz+k-^*p0UQE)J99*ZXm@#E8SEB{3uBfeG%U2Dzu&HVP~Hc4v{TQHy_A|rgsdYc6Tna zRp4caGzZkV$i80N)8bBmcXxL*SdZ*{Sz3TRm*o}NIX%x4Gl~^G)LFB=DrMFQ@b>mL zU0zl?|e|!ynM`eTE1ls{Mug6+Uwov>1lUxaBXcZ zt*)*vz literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/keyboard_textbox.png b/src/platform/wii/images/keyboard_textbox.png new file mode 100644 index 0000000000000000000000000000000000000000..e2ea573d754d14efb9e5f3f366e694a9092ec242 GIT binary patch literal 4719 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 literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/logo.png b/src/platform/wii/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..340b309db6476afe2f08eaa9cd651158b0ff6618 GIT binary patch literal 6697 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 literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/player3_point.png b/src/platform/wii/images/player3_point.png new file mode 100644 index 0000000000000000000000000000000000000000..e88b90f235047f736868b2c64848ce9ee5972718 GIT binary patch literal 2527 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 G3HHBKg}~AP literal 0 HcmV?d00001 diff --git a/src/platform/wii/images/player4_point.png b/src/platform/wii/images/player4_point.png new file mode 100644 index 0000000000000000000000000000000000000000..68bb3aa4a2dce8c43c2252742f2c7b94d2477b96 GIT binary patch literal 2474 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 BnMwcv literal 0 HcmV?d00001 diff --git a/src/platform/wii/input.cpp b/src/platform/wii/input.cpp new file mode 100644 index 0000000..c29230f --- /dev/null +++ b/src/platform/wii/input.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** + * DOSBox Wii Home Menu + * Tantric 2009 + * + * input.cpp + * + * Wii/Gamecube controller management + ***************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "input.h" +#include "libwiigui/gui.h" + +int rumbleRequest[4] = {0,0,0,0}; +GuiTrigger userInput[4]; +static int rumbleCount[4] = {0,0,0,0}; + +/**************************************************************************** + * UpdatePads + * + * called by postRetraceCallback in InitGCVideo - scans pad and wpad + ***************************************************************************/ +void UpdatePads() +{ + WPAD_ScanPads(); + PAD_ScanPads(); + + for(int i=3; i >= 0; i--) + { + memcpy(&userInput[i].wpad, WPAD_Data(i), sizeof(WPADData)); + + userInput[i].chan = i; + userInput[i].pad.btns_d = PAD_ButtonsDown(i); + userInput[i].pad.btns_u = PAD_ButtonsUp(i); + userInput[i].pad.btns_h = PAD_ButtonsHeld(i); + userInput[i].pad.stickX = PAD_StickX(i); + userInput[i].pad.stickY = PAD_StickY(i); + userInput[i].pad.substickX = PAD_SubStickX(i); + userInput[i].pad.substickY = PAD_SubStickY(i); + userInput[i].pad.triggerL = PAD_TriggerL(i); + userInput[i].pad.triggerR = PAD_TriggerR(i); + } +} + +/**************************************************************************** + * ShutoffRumble + ***************************************************************************/ +void ShutoffRumble() +{ + for(int i=0;i<4;i++) + { + WPAD_Rumble(i, 0); + rumbleCount[i] = 0; + } +} + +/**************************************************************************** + * DoRumble + ***************************************************************************/ +void DoRumble(int i) +{ + if(rumbleRequest[i] && rumbleCount[i] < 3) + { + WPAD_Rumble(i, 1); // rumble on + rumbleCount[i]++; + } + else if(rumbleRequest[i]) + { + rumbleCount[i] = 12; + rumbleRequest[i] = 0; + } + else + { + if(rumbleCount[i]) + rumbleCount[i]--; + WPAD_Rumble(i, 0); // rumble off + } +} + +bool MenuRequested() +{ + for(int i=0; i<4; i++) + { + if ((userInput[i].wpad.btns_h & WPAD_BUTTON_HOME) || + (userInput[i].wpad.btns_h & WPAD_CLASSIC_BUTTON_HOME)) + { + return true; + } + } + return false; +} diff --git a/src/platform/wii/input.h b/src/platform/wii/input.h new file mode 100644 index 0000000..446dc48 --- /dev/null +++ b/src/platform/wii/input.h @@ -0,0 +1,26 @@ +/**************************************************************************** + * DOSBox Wii Home Menu + * Tantric 2009 + * + * input.h + * + * Wii/Gamecube controller management + ***************************************************************************/ + +#ifndef _INPUT_H_ +#define _INPUT_H_ + +#include +#include + +#define PI 3.14159265f +#define PADCAL 50 + +extern int rumbleRequest[4]; + +void ShutoffRumble(); +void DoRumble(int i); +void UpdatePads(); +bool MenuRequested(); + +#endif diff --git a/src/platform/wii/libwiigui/gui.h b/src/platform/wii/libwiigui/gui.h new file mode 100644 index 0000000..7f04a9c --- /dev/null +++ b/src/platform/wii/libwiigui/gui.h @@ -0,0 +1,783 @@ +/*!\mainpage libwiigui Documentation + * + * \section Introduction + * libwiigui is a GUI library for the Wii, created to help structure the + * design of a complicated GUI interface, and to enable an author to create + * a sophisticated, feature-rich GUI. It was originally conceived and written + * after I started to design a GUI for Snes9x GX, and found libwiisprite and + * GRRLIB inadequate for the purpose. It uses GX for drawing, and makes use + * of PNGU for displaying images and FreeTypeGX for text. It was designed to + * be flexible and is easy to modify - don't be afraid to change the way it + * works or expand it to suit your GUI's purposes! If you do, and you think + * your changes might benefit others, please share them so they might be + * added to the project! + * + * \section Quickstart + * Start from the supplied template example. For more advanced uses, see the + * source code for Snes9x GX, FCE Ultra GX, and Visual Boy Advance GX. + + * \section Contact + * If you have any suggestions for the library or documentation, or want to + * contribute, please visit the libwiigui website: + * http://code.google.com/p/libwiigui/ + + * \section Credits + * This library was wholly designed and written by Tantric. Thanks to the + * authors of PNGU and FreeTypeGX, of which this library makes use. Thanks + * also to the authors of GRRLIB and libwiisprite for laying the foundations. + * +*/ + +#ifndef LIBWIIGUI_H +#define LIBWIIGUI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pngu.h" +#include "FreeTypeGX.h" +#include "wiivideo.h" +#include "filelist.h" +#include "input.h" +#include "oggplayer.h" + +extern FreeTypeGX *fontSystem[]; + +#define SCROLL_INITIAL_DELAY 20 +#define SCROLL_LOOP_DELAY 3 +#define MAX_KEYBOARD_DISPLAY 32 + +typedef void (*UpdateCallback)(void * e); + +enum +{ + ALIGN_LEFT, + ALIGN_RIGHT, + ALIGN_CENTRE, + ALIGN_TOP, + ALIGN_BOTTOM, + ALIGN_MIDDLE +}; + +enum +{ + STATE_DEFAULT, + STATE_SELECTED, + STATE_CLICKED, + STATE_HELD, + STATE_DISABLED +}; + +enum +{ + SOUND_PCM, + SOUND_OGG +}; + +enum +{ + IMAGE_TEXTURE, + IMAGE_COLOR, + IMAGE_DATA +}; + +enum +{ + TRIGGER_SIMPLE, + TRIGGER_HELD, + TRIGGER_BUTTON_ONLY, + TRIGGER_BUTTON_ONLY_IN_FOCUS +}; + +enum +{ + SCROLL_NONE, + SCROLL_HORIZONTAL +}; + +typedef struct _paddata { + u16 btns_d; + u16 btns_u; + u16 btns_h; + s8 stickX; + s8 stickY; + s8 substickX; + s8 substickY; + u8 triggerL; + u8 triggerR; +} PADData; + +#define EFFECT_SLIDE_TOP 1 +#define EFFECT_SLIDE_BOTTOM 2 +#define EFFECT_SLIDE_RIGHT 4 +#define EFFECT_SLIDE_LEFT 8 +#define EFFECT_SLIDE_IN 16 +#define EFFECT_SLIDE_OUT 32 +#define EFFECT_FADE 64 +#define EFFECT_SCALE 128 +#define EFFECT_COLOR_TRANSITION 256 + +//!Sound conversion and playback. A wrapper for other sound libraries - ASND, libmad, ltremor, etc +class GuiSound +{ + public: + //!Constructor + //!\param s Pointer to the sound data + //!\param l Length of sound data + //!\param t Sound format type (SOUND_PCM or SOUND_OGG) + GuiSound(const u8 * s, int l, int t); + //!Destructor + ~GuiSound(); + //!Start sound playback + void Play(); + //!Stop sound playback + void Stop(); + //!Pause sound playback + void Pause(); + //!Resume sound playback + void Resume(); + //!Checks if the sound is currently playing + //!\return true if sound is playing, false otherwise + bool IsPlaying(); + //!Set sound volume + //!\param v Sound volume (0-100) + void SetVolume(int v); + //!Set the sound to loop playback (only applies to OGG) + //!\param l Loop (true to loop) + void SetLoop(bool l); + protected: + const u8 * sound; //!< Pointer to the sound data + int type; //!< Sound format type (SOUND_PCM or SOUND_OGG) + s32 length; //!< Length of sound data + s32 voice; //!< Currently assigned ASND voice channel + s32 volume; //!< Sound volume (0-100) + bool loop; //!< Loop sound playback +}; + +//!Menu input trigger management. Determine if action is neccessary based on input data by comparing controller input data to a specific trigger element. +class GuiTrigger +{ + public: + //!Constructor + GuiTrigger(); + //!Destructor + ~GuiTrigger(); + //!Sets a simple trigger. Requires: element is selected, and trigger button is pressed + //!\param ch Controller channel number + //!\param wiibtns Wii controller trigger button(s) - classic controller buttons are considered separately + //!\param gcbtns GameCube controller trigger button(s) + void SetSimpleTrigger(s32 ch, u32 wiibtns, u16 gcbtns); + //!Sets a held trigger. Requires: element is selected, and trigger button is pressed + //!\param ch Controller channel number + //!\param wiibtns Wii controller trigger button(s) - classic controller buttons are considered separately + //!\param gcbtns GameCube controller trigger button(s) + void SetHeldTrigger(s32 ch, u32 wiibtns, u16 gcbtns); + //!Sets a button-only trigger. Requires: Trigger button is pressed + //!\param ch Controller channel number + //!\param wiibtns Wii controller trigger button(s) - classic controller buttons are considered separately + //!\param gcbtns GameCube controller trigger button(s) + void SetButtonOnlyTrigger(s32 ch, u32 wiibtns, u16 gcbtns); + //!Sets a button-only trigger. Requires: trigger button is pressed and parent window of element is in focus + //!\param ch Controller channel number + //!\param wiibtns Wii controller trigger button(s) - classic controller buttons are considered separately + //!\param gcbtns GameCube controller trigger button(s) + void SetButtonOnlyInFocusTrigger(s32 ch, u32 wiibtns, u16 gcbtns); + //!Get X/Y value from Wii Joystick (classic, nunchuk) input + //!\param right Controller stick (left = 0, right = 1) + //!\param axis Controller stick axis (x-axis = 0, y-axis = 1) + //!\return Stick value + s8 WPAD_Stick(u8 right, int axis); + //!Move menu selection left (via pad/joystick). Allows scroll delay and button overriding + //!\return true if selection should be moved left, false otherwise + bool Left(); + //!Move menu selection right (via pad/joystick). Allows scroll delay and button overriding + //!\return true if selection should be moved right, false otherwise + bool Right(); + //!Move menu selection up (via pad/joystick). Allows scroll delay and button overriding + //!\return true if selection should be moved up, false otherwise + bool Up(); + //!Move menu selection down (via pad/joystick). Allows scroll delay and button overriding + //!\return true if selection should be moved down, false otherwise + bool Down(); + + u8 type; //!< trigger type (TRIGGER_SIMPLE, TRIGGER_HELD, TRIGGER_BUTTON_ONLY, TRIGGER_BUTTON_ONLY_IN_FOCUS) + s32 chan; //!< Trigger controller channel (0-3, -1 for all) + WPADData wpad; //!< Wii controller trigger data + PADData pad; //!< GameCube controller trigger data +}; + +extern GuiTrigger userInput[4]; + +//!Primary GUI class. Most other classes inherit from this class. +class GuiElement +{ + public: + //!Constructor + GuiElement(); + //!Destructor + ~GuiElement(); + //!Set the element's parent + //!\param e Pointer to parent element + void SetParent(GuiElement * e); + //!Gets the element's parent + //!\return Pointer to parent element + GuiElement * GetParent(); + //!Gets the current leftmost coordinate of the element + //!Considers horizontal alignment, x offset, width, and parent element's GetLeft() / GetWidth() values + //!\return left coordinate + int GetLeft(); + //!Gets the current topmost coordinate of the element + //!Considers vertical alignment, y offset, height, and parent element's GetTop() / GetHeight() values + //!\return top coordinate + int GetTop(); + //!Sets the minimum y offset of the element + //!\param y Y offset + void SetMinY(int y); + //!Gets the minimum y offset of the element + //!\return Minimum Y offset + int GetMinY(); + //!Sets the maximum y offset of the element + //!\param y Y offset + void SetMaxY(int y); + //!Gets the maximum y offset of the element + //!\return Maximum Y offset + int GetMaxY(); + //!Sets the minimum x offset of the element + //!\param x X offset + void SetMinX(int x); + //!Gets the minimum x offset of the element + //!\return Minimum X offset + int GetMinX(); + //!Sets the maximum x offset of the element + //!\param x X offset + void SetMaxX(int x); + //!Gets the maximum x offset of the element + //!\return Maximum X offset + int GetMaxX(); + //!Gets the current width of the element. Does not currently consider the scale + //!\return width + int GetWidth(); + //!Gets the height of the element. Does not currently consider the scale + //!\return height + int GetHeight(); + //!Sets the size (width/height) of the element + //!\param w Width of element + //!\param h Height of element + void SetSize(int w, int h); + //!Checks whether or not the element is visible + //!\return true if visible, false otherwise + bool IsVisible(); + //!Checks whether or not the element is selectable + //!\return true if selectable, false otherwise + bool IsSelectable(); + //!Checks whether or not the element is clickable + //!\return true if clickable, false otherwise + bool IsClickable(); + //!Checks whether or not the element is holdable + //!\return true if holdable, false otherwise + bool IsHoldable(); + //!Sets whether or not the element is selectable + //!\param s Selectable + void SetSelectable(bool s); + //!Sets whether or not the element is clickable + //!\param c Clickable + void SetClickable(bool c); + //!Sets whether or not the element is holdable + //!\param c Holdable + void SetHoldable(bool d); + //!Gets the element's current state + //!\return state + int GetState(); + //!Gets the controller channel that last changed the element's state + //!\return Channel number (0-3, -1 = no channel) + int GetStateChan(); + //!Sets the element's alpha value + //!\param a alpha value + void SetAlpha(int a); + //!Gets the element's alpha value + //!Considers alpha, alphaDyn, and the parent element's GetAlpha() value + //!\return alpha + int GetAlpha(); + //!Sets the element's scale + //!\param s scale (1 is 100%) + void SetScale(float s); + //!Gets the element's current scale + //!Considers scale, scaleDyn, and the parent element's GetScale() value + float GetScale(); + //!Set a new GuiTrigger for the element + //!\param t Pointer to GuiTrigger + void SetTrigger(GuiTrigger * t); + //!\overload + //!\param i Index of trigger array to set + //!\param t Pointer to GuiTrigger + void SetTrigger(u8 i, GuiTrigger * t); + //!Checks whether rumble was requested by the element + //!\return true is rumble was requested, false otherwise + bool Rumble(); + //!Sets whether or not the element is requesting a rumble event + //!\param r true if requesting rumble, false if not + void SetRumble(bool r); + //!Set an effect for the element + //!\param e Effect to enable + //!\param a Amount of the effect (usage varies on effect) + //!\param t Target amount of the effect (usage varies on effect) + void SetEffect(int e, int a, int t=0); + //!Sets an effect to be enabled on wiimote cursor over + //!\param e Effect to enable + //!\param a Amount of the effect (usage varies on effect) + //!\param t Target amount of the effect (usage varies on effect) + void SetEffectOnOver(int e, int a, int t=0); + //!Shortcut to SetEffectOnOver(EFFECT_SCALE, 4, 110) + void SetEffectGrow(); + //!Gets the current element effects + //!\return element effects + int GetEffect(); + //!Checks whether the specified coordinates are within the element's boundaries + //!\param x X coordinate + //!\param y Y coordinate + //!\return true if contained within, false otherwise + bool IsInside(int x, int y); + //!Sets the element's position + //!\param x X coordinate + //!\param y Y coordinate + void SetPosition(int x, int y); + //!Updates the element's effects (dynamic values) + //!Called by Draw(), used for animation purposes + void UpdateEffects(); + //!Sets a function to called after after Update() + //!Callback function can be used to response to changes in the state of the element, and/or update the element's attributes + void SetUpdateCallback(UpdateCallback u); + //!Checks whether the element is in focus + //!\return true if element is in focus, false otherwise + int IsFocused(); + //!Sets the element's visibility + //!\param v Visibility (true = visible) + virtual void SetVisible(bool v); + //!Sets the element's focus + //!\param f Focus (true = in focus) + virtual void SetFocus(int f); + //!Sets the element's state + //!\param s State (STATE_DEFAULT, STATE_SELECTED, STATE_CLICKED, STATE_DISABLED) + //!\param c Controller channel (0-3, -1 = none) + virtual void SetState(int s, int c = -1); + //!Resets the element's state to STATE_DEFAULT + virtual void ResetState(); + //!Gets whether or not the element is in STATE_SELECTED + //!\return true if selected, false otherwise + virtual int GetSelected(); + //!Sets the element's alignment respective to its parent element + //!\param hor Horizontal alignment (ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTRE) + //!\param vert Vertical alignment (ALIGN_TOP, ALIGN_BOTTOM, ALIGN_MIDDLE) + virtual void SetAlignment(int hor, int vert); + //!Called constantly to allow the element to respond to the current input data + //!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD + virtual void Update(GuiTrigger * t); + //!Called constantly to redraw the element + virtual void Draw(); + protected: + bool visible; //!< Visibility of the element. If false, Draw() is skipped + int focus; //!< Element focus (-1 = focus disabled, 0 = not focused, 1 = focused) + int width; //!< Element width + int height; //!< Element height + int xoffset; //!< Element X offset + int yoffset; //!< Element Y offset + int ymin; //!< Element's min Y offset allowed + int ymax; //!< Element's max Y offset allowed + int xmin; //!< Element's min X offset allowed + int xmax; //!< Element's max X offset allowed + int xoffsetDyn; //!< Element X offset, dynamic (added to xoffset value for animation effects) + int yoffsetDyn; //!< Element Y offset, dynamic (added to yoffset value for animation effects) + int alpha; //!< Element alpha value (0-255) + f32 scale; //!< Element scale (1 = 100%) + int alphaDyn; //!< Element alpha, dynamic (multiplied by alpha value for blending/fading effects) + f32 scaleDyn; //!< Element scale, dynamic (multiplied by alpha value for blending/fading effects) + bool rumble; //!< Wiimote rumble (on/off) - set to on when this element requests a rumble event + int effects; //!< Currently enabled effect(s). 0 when no effects are enabled + int effectAmount; //!< Effect amount. Used by different effects for different purposes + int effectTarget; //!< Effect target amount. Used by different effects for different purposes + int effectsOver; //!< Effects to enable when wiimote cursor is over this element. Copied to effects variable on over event + int effectAmountOver; //!< EffectAmount to set when wiimote cursor is over this element + int effectTargetOver; //!< EffectTarget to set when wiimote cursor is over this element + int alignmentHor; //!< Horizontal element alignment, respective to parent element (LEFT, RIGHT, CENTRE) + int alignmentVert; //!< Horizontal element alignment, respective to parent element (TOP, BOTTOM, MIDDLE) + int state; //!< Element state (DEFAULT, SELECTED, CLICKED, DISABLED) + int stateChan; //!< Which controller channel is responsible for the last change in state + bool selectable; //!< Whether or not this element selectable (can change to SELECTED state) + bool clickable; //!< Whether or not this element is clickable (can change to CLICKED state) + bool holdable; //!< Whether or not this element is holdable (can change to HELD state) + GuiTrigger * trigger[2]; //!< GuiTriggers (input actions) that this element responds to + GuiElement * parentElement; //!< Parent element + UpdateCallback updateCB; //!< Callback function to call when this element is updated +}; + +//!Allows GuiElements to be grouped together into a "window" +class GuiWindow : public GuiElement +{ + public: + //!Constructor + GuiWindow(); + //!\overload + //!\param w Width of window + //!\param h Height of window + GuiWindow(int w, int h); + //!Destructor + ~GuiWindow(); + //!Appends a GuiElement to the GuiWindow + //!\param e The GuiElement to append. If it is already in the GuiWindow, it is removed first + void Append(GuiElement* e); + //!Inserts a GuiElement into the GuiWindow at the specified index + //!\param e The GuiElement to insert. If it is already in the GuiWindow, it is removed first + //!\param i Index in which to insert the element + void Insert(GuiElement* e, u32 i); + //!Removes the specified GuiElement from the GuiWindow + //!\param e GuiElement to be removed + void Remove(GuiElement* e); + //!Removes all GuiElements + void RemoveAll(); + //!Returns the GuiElement at the specified index + //!\param index The index of the element + //!\return A pointer to the element at the index, NULL on error (eg: out of bounds) + GuiElement* GetGuiElementAt(u32 index) const; + //!Returns the size of the list of elements + //!\return The size of the current element list + u32 GetSize(); + //!Sets the visibility of the window + //!\param v visibility (true = visible) + void SetVisible(bool v); + //!Resets the window's state to STATE_DEFAULT + void ResetState(); + //!Sets the window's state + //!\param s State + void SetState(int s); + //!Gets the index of the GuiElement inside the window that is currently selected + //!\return index of selected GuiElement + int GetSelected(); + //!Sets the window focus + //!\param f Focus + void SetFocus(int f); + //!Change the focus to the specified element + //!This is intended for the primary GuiWindow only + //!\param e GuiElement that should have focus + void ChangeFocus(GuiElement * e); + //!Changes window focus to the next focusable window or element + //!If no element is in focus, changes focus to the first available element + //!If B or 1 button is pressed, changes focus to the next available element + //!This is intended for the primary GuiWindow only + //!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD + void ToggleFocus(GuiTrigger * t); + //!Moves the selected element to the element to the left or right + //!\param d Direction to move (-1 = left, 1 = right) + void MoveSelectionHor(int d); + //!Moves the selected element to the element above or below + //!\param d Direction to move (-1 = up, 1 = down) + void MoveSelectionVert(int d); + //!Draws all the elements in this GuiWindow + void Draw(); + //!Updates the window and all elements contains within + //!Allows the GuiWindow and all elements to respond to the input data specified + //!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD + void Update(GuiTrigger * t); + protected: + std::vector _elements; //!< Contains all elements within the GuiWindow +}; + +//!Converts image data into GX-useable RGBA8. Currently designed for use only with PNG files +class GuiImageData +{ + public: + //!Constructor + //!Converts the image data to RGBA8 - expects PNG format + //!\param i Image data + GuiImageData(const u8 * i); + //!Destructor + ~GuiImageData(); + //!Gets a pointer to the image data + //!\return pointer to image data + u8 * GetImage(); + //!Gets the image width + //!\return image width + int GetWidth(); + //!Gets the image height + //!\return image height + int GetHeight(); + protected: + u8 * data; //!< Image data + int height; //!< Height of image + int width; //!< Width of image +}; + +//!Display, manage, and manipulate images in the GUI +class GuiImage : public GuiElement +{ + public: + //!Constructor + GuiImage(); + //!\overload + //!\param img Pointer to GuiImageData element + GuiImage(GuiImageData * img); + //!\overload + //!Sets up a new image from the image data specified + //!\param img + //!\param w Image width + //!\param h Image height + GuiImage(u8 * img, int w, int h); + //!\overload + //!Creates an image filled with the specified color + //!\param w Image width + //!\param h Image height + //!\param c Image color + GuiImage(int w, int h, GXColor c); + //!Destructor + ~GuiImage(); + //!Sets the image rotation angle for drawing + //!\param a Angle (in degrees) + void SetAngle(float a); + //!Sets the number of times to draw the image horizontally + //!\param t Number of times to draw the image + void SetTile(int t); + //!Constantly called to draw the image + void Draw(); + //!Gets the image data + //!\return pointer to image data + u8 * GetImage(); + //!Sets up a new image using the GuiImageData object specified + //!\param img Pointer to GuiImageData object + void SetImage(GuiImageData * img); + //!\overload + //!\param img Pointer to image data + //!\param w Width + //!\param h Height + void SetImage(u8 * img, int w, int h); + //!Gets the pixel color at the specified coordinates of the image + //!\param x X coordinate + //!\param y Y coordinate + GXColor GetPixel(int x, int y); + //!Sets the pixel color at the specified coordinates of the image + //!\param x X coordinate + //!\param y Y coordinate + //!\param color Pixel color + void SetPixel(int x, int y, GXColor color); + //!Directly modifies the image data to create a color-striped effect + //!Alters the RGB values by the specified amount + //!\param s Amount to increment/decrement the RGB values in the image + void ColorStripe(int s); + //!Sets a stripe effect on the image, overlaying alpha blended rectangles + //!Does not alter the image data + //!\param s Alpha amount to draw over the image + void SetStripe(int s); + protected: + int imgType; //!< Type of image data (IMAGE_TEXTURE, IMAGE_COLOR, IMAGE_DATA) + u8 * image; //!< Poiner to image data. May be shared with GuiImageData data + f32 imageangle; //!< Angle to draw the image + int tile; //!< Number of times to draw (tile) the image horizontally + int stripe; //!< Alpha value (0-255) to apply a stripe effect to the texture +}; + +//!Display, manage, and manipulate text in the GUI +class GuiText : public GuiElement +{ + public: + //!Constructor + //!\param t Text + //!\param s Font size + //!\param c Font color + GuiText(const char * t, int s, GXColor c); + //!\overload + //!\Assumes SetPresets() has been called to setup preferred text attributes + //!\param t Text + GuiText(const char * t); + //!Destructor + ~GuiText(); + //!Sets the text of the GuiText element + //!\param t Text + void SetText(const char * t); + //!Sets up preset values to be used by GuiText(t) + //!Useful when printing multiple text elements, all with the same attributes set + //!\param sz Font size + //!\param c Font color + //!\param w Maximum width of texture image (for text wrapping) + //!\param s Font size + //!\param h Text alignment (horizontal) + //!\param v Text alignment (vertical) + void SetPresets(int sz, GXColor c, int w, u16 s, int h, int v); + //!Sets the font size + //!\param s Font size + void SetFontSize(int s); + //!Sets the maximum width of the drawn texture image + //!\param w Maximum width + void SetMaxWidth(int width); + //!Enables/disables text scrolling + //!\param s Scrolling on/off + void SetScroll(int s); + //!Enables/disables text wrapping + //!\param w Wrapping on/off + //!\param width Maximum width (0 to disable) + void SetWrap(bool w, int width = 0); + //!Sets the font color + //!\param c Font color + void SetColor(GXColor c); + //!Sets the FreeTypeGX style attributes + //!\param s Style attributes + void SetStyle(u16 s); + //!Sets the text alignment + //!\param hor Horizontal alignment (ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTRE) + //!\param vert Vertical alignment (ALIGN_TOP, ALIGN_BOTTOM, ALIGN_MIDDLE) + void SetAlignment(int hor, int vert); + //!Constantly called to draw the text + void Draw(); + protected: + char * origText; //!< Original text data + wchar_t* text; //!< Unicode text value + int size; //!< Font size + int maxWidth; //!< Maximum width of the generated text object (for text wrapping) + bool wrap; //!< Wrapping toggle + wchar_t* textDyn; //!< Wrapped text value + int textScroll; //!< Scrolling toggle + int textScrollPos; //!< Current starting index of text string for scrolling + int textScrollInitialDelay; //!< Delay to wait before starting to scroll + int textScrollDelay; //!< Scrolling speed + u16 style; //!< FreeTypeGX style attributes + GXColor color; //!< Font color +}; + +//!Display, manage, and manipulate buttons in the GUI. Buttons can have images, icons, text, and sound set (all of which are optional) +class GuiButton : public GuiElement +{ + public: + //!Constructor + //!\param w Width + //!\param h Height + GuiButton(int w, int h); + //!Destructor + ~GuiButton(); + //!Sets the button's image + //!\param i Pointer to GuiImage object + void SetImage(GuiImage* i); + //!Sets the button's image on over + //!\param i Pointer to GuiImage object + void SetImageOver(GuiImage* i); + //!Sets the button's image on hold + //!\param i Pointer to GuiImage object + void SetImageHold(GuiImage* i); + //!Sets the button's image on click + //!\param i Pointer to GuiImage object + void SetImageClick(GuiImage* i); + //!Sets the button's icon + //!\param i Pointer to GuiImage object + void SetIcon(GuiImage* i); + //!Sets the button's icon on over + //!\param i Pointer to GuiImage object + void SetIconOver(GuiImage* i); + //!Sets the button's icon on hold + //!\param i Pointer to GuiImage object + void SetIconHold(GuiImage* i); + //!Sets the button's icon on click + //!\param i Pointer to GuiImage object + void SetIconClick(GuiImage* i); + //!Sets the button's label + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void SetLabel(GuiText* t, int n = 0); + //!Sets the button's label on over (eg: different colored text) + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void SetLabelOver(GuiText* t, int n = 0); + //!Sets the button's label on hold + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void SetLabelHold(GuiText* t, int n = 0); + //!Sets the button's label on click + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void SetLabelClick(GuiText* t, int n = 0); + //!Sets the sound to play on over + //!\param s Pointer to GuiSound object + void SetSoundOver(GuiSound * s); + //!Sets the sound to play on hold + //!\param s Pointer to GuiSound object + void SetSoundHold(GuiSound * s); + //!Sets the sound to play on click + //!\param s Pointer to GuiSound object + void SetSoundClick(GuiSound * s); + //!Constantly called to draw the GuiButton + void Draw(); + //!Constantly called to allow the GuiButton to respond to updated input data + //!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD + void Update(GuiTrigger * t); + protected: + GuiImage * image; //!< Button image (default) + GuiImage * imageOver; //!< Button image for STATE_SELECTED + GuiImage * imageHold; //!< Button image for STATE_HELD + GuiImage * imageClick; //!< Button image for STATE_CLICKED + GuiImage * icon; //!< Button icon (drawn after button image) + GuiImage * iconOver; //!< Button icon for STATE_SELECTED + GuiImage * iconHold; //!< Button icon for STATE_HELD + GuiImage * iconClick; //!< Button icon for STATE_CLICKED + GuiText * label[3]; //!< Label(s) to display (default) + GuiText * labelOver[3]; //!< Label(s) to display for STATE_SELECTED + GuiText * labelHold[3]; //!< Label(s) to display for STATE_HELD + GuiText * labelClick[3]; //!< Label(s) to display for STATE_CLICKED + GuiSound * soundOver; //!< Sound to play for STATE_SELECTED + GuiSound * soundHold; //!< Sound to play for STATE_HELD + GuiSound * soundClick; //!< Sound to play for STATE_CLICKED +}; + +typedef struct _keytype { + char ch, chShift; +} Key; + +//!On-screen keyboard +class GuiKeyboard : public GuiWindow +{ + public: + GuiKeyboard(char * t, u32 m); + ~GuiKeyboard(); + void Update(GuiTrigger * t); + char kbtextstr[256]; + protected: + u32 kbtextmaxlen; + Key keys[4][11]; + int shift; + int caps; + GuiText * kbText; + GuiImage * keyTextboxImg; + GuiText * keyCapsText; + GuiImage * keyCapsImg; + GuiImage * keyCapsOverImg; + GuiButton * keyCaps; + GuiText * keyShiftText; + GuiImage * keyShiftImg; + GuiImage * keyShiftOverImg; + GuiButton * keyShift; + GuiText * keyBackText; + GuiImage * keyBackImg; + GuiImage * keyBackOverImg; + GuiButton * keyBack; + GuiImage * keySpaceImg; + GuiImage * keySpaceOverImg; + GuiButton * keySpace; + GuiButton * keyBtn[4][11]; + GuiImage * keyImg[4][11]; + GuiImage * keyImgOver[4][11]; + GuiText * keyTxt[4][11]; + GuiImageData * keyTextbox; + GuiImageData * key; + GuiImageData * keyOver; + GuiImageData * keyMedium; + GuiImageData * keyMediumOver; + GuiImageData * keyLarge; + GuiImageData * keyLargeOver; + GuiSound * keySoundOver; + GuiSound * keySoundClick; + GuiTrigger * trigA; +}; + +#endif diff --git a/src/platform/wii/libwiigui/gui_button.cpp b/src/platform/wii/libwiigui/gui_button.cpp new file mode 100644 index 0000000..0b5a9a7 --- /dev/null +++ b/src/platform/wii/libwiigui/gui_button.cpp @@ -0,0 +1,306 @@ +/**************************************************************************** + * libwiigui + * + * Tantric 2009 + * + * gui_button.cpp + * + * GUI class definitions + ***************************************************************************/ + +#include "gui.h" +/** + * Constructor for the GuiButton class. + */ + +GuiButton::GuiButton(int w, int h) +{ + width = w; + height = h; + image = NULL; + imageOver = NULL; + imageHold = NULL; + imageClick = NULL; + icon = NULL; + iconOver = NULL; + iconHold = NULL; + iconClick = NULL; + + for(int i=0; i < 3; i++) + { + label[i] = NULL; + labelOver[i] = NULL; + labelHold[i] = NULL; + labelClick[i] = NULL; + } + + soundOver = NULL; + soundHold = NULL; + soundClick = NULL; + selectable = true; + holdable = false; + clickable = true; +} + +/** + * Destructor for the GuiButton class. + */ +GuiButton::~GuiButton() +{ +} + +void GuiButton::SetImage(GuiImage* img) +{ + image = img; + if(img) img->SetParent(this); +} +void GuiButton::SetImageOver(GuiImage* img) +{ + imageOver = img; + if(img) img->SetParent(this); +} +void GuiButton::SetImageHold(GuiImage* img) +{ + imageHold = img; + if(img) img->SetParent(this); +} +void GuiButton::SetImageClick(GuiImage* img) +{ + imageClick = img; + if(img) img->SetParent(this); +} +void GuiButton::SetIcon(GuiImage* img) +{ + icon = img; + if(img) img->SetParent(this); +} +void GuiButton::SetIconOver(GuiImage* img) +{ + iconOver = img; + if(img) img->SetParent(this); +} +void GuiButton::SetIconHold(GuiImage* img) +{ + iconHold = img; + if(img) img->SetParent(this); +} +void GuiButton::SetIconClick(GuiImage* img) +{ + iconClick = img; + if(img) img->SetParent(this); +} +void GuiButton::SetLabel(GuiText* txt, int n) +{ + label[n] = txt; + if(txt) txt->SetParent(this); +} +void GuiButton::SetLabelOver(GuiText* txt, int n) +{ + labelOver[n] = txt; + if(txt) txt->SetParent(this); +} +void GuiButton::SetLabelHold(GuiText* txt, int n) +{ + labelHold[n] = txt; + if(txt) txt->SetParent(this); +} +void GuiButton::SetLabelClick(GuiText* txt, int n) +{ + labelClick[n] = txt; + if(txt) txt->SetParent(this); +} +void GuiButton::SetSoundOver(GuiSound * snd) +{ + soundOver = snd; +} +void GuiButton::SetSoundHold(GuiSound * snd) +{ + soundHold = snd; +} +void GuiButton::SetSoundClick(GuiSound * snd) +{ + soundClick = snd; +} + +/** + * Draw the button on screen + */ +void GuiButton::Draw() +{ + if(!this->IsVisible()) + return; + + // draw image + if((state == STATE_SELECTED || state == STATE_HELD) && imageOver) + imageOver->Draw(); + else if(image) + image->Draw(); + // draw icon + if((state == STATE_SELECTED || state == STATE_HELD) && iconOver) + iconOver->Draw(); + else if(icon) + icon->Draw(); + // draw text + for(int i=0; i<3; i++) + { + if((state == STATE_SELECTED || state == STATE_HELD) && labelOver[i]) + labelOver[i]->Draw(); + else if(label[i]) + label[i]->Draw(); + } + + this->UpdateEffects(); +} + +void GuiButton::Update(GuiTrigger * t) +{ + if(state == STATE_CLICKED || state == STATE_DISABLED || !t) + return; + else if(parentElement && parentElement->GetState() == STATE_DISABLED) + return; + + #ifdef HW_RVL + // cursor + if(t->wpad.ir.valid && t->chan >= 0) + { + if(this->IsInside(t->wpad.ir.x, t->wpad.ir.y)) + { + if(state == STATE_DEFAULT) // we weren't on the button before! + { + this->SetState(STATE_SELECTED, t->chan); + + if(this->Rumble()) + rumbleRequest[t->chan] = 1; + + if(soundOver) + soundOver->Play(); + + if(effectsOver && !effects) + { + // initiate effects + effects = effectsOver; + effectAmount = effectAmountOver; + effectTarget = effectTargetOver; + } + } + } + else + { + if(state == STATE_SELECTED && (stateChan == t->chan || stateChan == -1)) + this->ResetState(); + + if(effectTarget == effectTargetOver && effectAmount == effectAmountOver) + { + // initiate effects (in reverse) + effects = effectsOver; + effectAmount = -effectAmountOver; + effectTarget = 100; + } + } + } + #endif + + // button triggers + if(this->IsClickable()) + { + s32 wm_btns, wm_btns_trig, cc_btns, cc_btns_trig; + for(int i=0; i<2; i++) + { + if(trigger[i] && (trigger[i]->chan == -1 || trigger[i]->chan == t->chan)) + { + // higher 16 bits only (wiimote) + wm_btns = t->wpad.btns_d << 16; + wm_btns_trig = trigger[i]->wpad.btns_d << 16; + + // lower 16 bits only (classic controller) + cc_btns = t->wpad.btns_d >> 16; + cc_btns_trig = trigger[i]->wpad.btns_d >> 16; + + if( + (t->wpad.btns_d > 0 && + (wm_btns == wm_btns_trig || + (cc_btns == cc_btns_trig && t->wpad.exp.type == EXP_CLASSIC))) || + (t->pad.btns_d == trigger[i]->pad.btns_d && t->pad.btns_d > 0)) + { + if(t->chan == stateChan || stateChan == -1) + { + if(state == STATE_SELECTED) + { + if(!t->wpad.ir.valid || this->IsInside(t->wpad.ir.x, t->wpad.ir.y)) + { + this->SetState(STATE_CLICKED, t->chan); + + if(soundClick) + soundClick->Play(); + } + } + else if(trigger[i]->type == TRIGGER_BUTTON_ONLY) + { + this->SetState(STATE_CLICKED, t->chan); + } + else if(trigger[i]->type == TRIGGER_BUTTON_ONLY_IN_FOCUS && + parentElement->IsFocused()) + { + this->SetState(STATE_CLICKED, t->chan); + } + } + } + } + } + } + + if(this->IsHoldable()) + { + bool held = false; + s32 wm_btns, wm_btns_h, wm_btns_trig, cc_btns, cc_btns_h, cc_btns_trig; + + for(int i=0; i<2; i++) + { + if(trigger[i] && (trigger[i]->chan == -1 || trigger[i]->chan == t->chan)) + { + // higher 16 bits only (wiimote) + wm_btns = t->wpad.btns_d << 16; + wm_btns_h = t->wpad.btns_h << 16; + wm_btns_trig = trigger[i]->wpad.btns_h << 16; + + // lower 16 bits only (classic controller) + cc_btns = t->wpad.btns_d >> 16; + cc_btns_h = t->wpad.btns_h >> 16; + cc_btns_trig = trigger[i]->wpad.btns_h >> 16; + + if( + (t->wpad.btns_d > 0 && + (wm_btns == wm_btns_trig || + (cc_btns == cc_btns_trig && t->wpad.exp.type == EXP_CLASSIC))) || + (t->pad.btns_d == trigger[i]->pad.btns_h && t->pad.btns_d > 0)) + { + if(trigger[i]->type == TRIGGER_HELD && state == STATE_SELECTED && + (t->chan == stateChan || stateChan == -1)) + this->SetState(STATE_CLICKED, t->chan); + } + + if( + (t->wpad.btns_h > 0 && + (wm_btns_h == wm_btns_trig || + (cc_btns_h == cc_btns_trig && t->wpad.exp.type == EXP_CLASSIC))) || + (t->pad.btns_h == trigger[i]->pad.btns_h && t->pad.btns_h > 0)) + { + if(trigger[i]->type == TRIGGER_HELD) + held = true; + } + + if(!held && state == STATE_HELD && stateChan == t->chan) + { + this->ResetState(); + } + else if(held && state == STATE_CLICKED && stateChan == t->chan) + { + this->SetState(STATE_HELD, t->chan); + } + } + } + } + + if(updateCB) + updateCB(this); +} diff --git a/src/platform/wii/libwiigui/gui_element.cpp b/src/platform/wii/libwiigui/gui_element.cpp new file mode 100644 index 0000000..6a40946 --- /dev/null +++ b/src/platform/wii/libwiigui/gui_element.cpp @@ -0,0 +1,570 @@ +/**************************************************************************** + * libwiigui + * + * Tantric 2009 + * + * gui_element.cpp + * + * GUI class definitions + ***************************************************************************/ + +#include "gui.h" + +/** + * Constructor for the Object class. + */ +GuiElement::GuiElement() +{ + xoffset = 0; + yoffset = 0; + xmin = 0; + xmax = 0; + ymin = 0; + ymax = 0; + width = 0; + height = 0; + alpha = 255; + scale = 1; + state = STATE_DEFAULT; + stateChan = -1; + trigger[0] = NULL; + trigger[1] = NULL; + parentElement = NULL; + rumble = true; + selectable = false; + clickable = false; + holdable = false; + visible = true; + focus = -1; // cannot be focused + updateCB = NULL; + yoffsetDyn = 0; + xoffsetDyn = 0; + alphaDyn = -1; + scaleDyn = 1; + effects = 0; + effectAmount = 0; + effectTarget = 0; + effectsOver = 0; + effectAmountOver = 0; + effectTargetOver = 0; + + // default alignment - align to top left + alignmentVert = ALIGN_TOP; + alignmentHor = ALIGN_LEFT; +} + +/** + * Destructor for the GuiElement class. + */ +GuiElement::~GuiElement() +{ +} + +void GuiElement::SetParent(GuiElement * e) +{ + parentElement = e; +} + +GuiElement * GuiElement::GetParent() +{ + return parentElement; +} +/** + * Get the left position of the GuiElement. + * @see SetLeft() + * @return Left position in pixel. + */ +int GuiElement::GetLeft() +{ + int x = 0; + int pWidth = 0; + int pLeft = 0; + + if(parentElement) + { + pWidth = parentElement->GetWidth(); + pLeft = parentElement->GetLeft(); + } + + if(effects & (EFFECT_SLIDE_IN | EFFECT_SLIDE_OUT)) + pLeft += xoffsetDyn; + + switch(alignmentHor) + { + case ALIGN_LEFT: + x = pLeft; + break; + case ALIGN_CENTRE: + x = pLeft + (pWidth/2) - (width/2); + break; + case ALIGN_RIGHT: + x = pLeft + pWidth - width; + break; + } + return x + xoffset; +} + +/** + * Get the top position of the GuiElement. + * @see SetTop() + * @return Top position in pixel. + */ +int GuiElement::GetTop() +{ + int y = 0; + int pHeight = 0; + int pTop = 0; + + if(parentElement) + { + pHeight = parentElement->GetHeight(); + pTop = parentElement->GetTop(); + } + + if(effects & (EFFECT_SLIDE_IN | EFFECT_SLIDE_OUT)) + pTop += yoffsetDyn; + + switch(alignmentVert) + { + case ALIGN_TOP: + y = pTop; + break; + case ALIGN_MIDDLE: + y = pTop + (pHeight/2) - (height/2); + break; + case ALIGN_BOTTOM: + y = pTop + pHeight - height; + break; + } + return y + yoffset; +} + +void GuiElement::SetMinX(int x) +{ + xmin = x; +} + +int GuiElement::GetMinX() +{ + return xmin; +} + +void GuiElement::SetMaxX(int x) +{ + xmax = x; +} + +int GuiElement::GetMaxX() +{ + return xmax; +} + +void GuiElement::SetMinY(int y) +{ + ymin = y; +} + +int GuiElement::GetMinY() +{ + return ymin; +} + +void GuiElement::SetMaxY(int y) +{ + ymax = y; +} + +int GuiElement::GetMaxY() +{ + return ymax; +} + +/** + * Get the width of the GuiElement. + * @see SetWidth() + * @return Width of the GuiElement. + */ +int GuiElement::GetWidth() +{ + return width; +} + +/** + * Get the height of the GuiElement. + * @see SetHeight() + * @return Height of the GuiElement. + */ +int GuiElement::GetHeight() +{ + return height; +} + +/** + * Set the width and height of the GuiElement. + * @param[in] Width Width in pixel. + * @param[in] Height Height in pixel. + * @see SetWidth() + * @see SetHeight() + */ +void GuiElement::SetSize(int w, int h) +{ + + width = w; + height = h; +} + +/** + * Get visible. + * @see SetVisible() + * @return true if visible, false otherwise. + */ +bool GuiElement::IsVisible() +{ + return visible; +} + +/** + * Set visible. + * @param[in] Visible Set to true to show GuiElement. + * @see IsVisible() + */ +void GuiElement::SetVisible(bool v) +{ + visible = v; +} + +void GuiElement::SetAlpha(int a) +{ + alpha = a; +} + +int GuiElement::GetAlpha() +{ + int a; + + if(alphaDyn >= 0) + a = alphaDyn; + else + a = alpha; + + if(parentElement) + a *= parentElement->GetAlpha()/255.0; + + return a; +} + +void GuiElement::SetScale(float s) +{ + scale = s; +} + +float GuiElement::GetScale() +{ + float s = scale * scaleDyn; + + if(parentElement) + s *= parentElement->GetScale(); + + return s; +} + +int GuiElement::GetState() +{ + return state; +} + +int GuiElement::GetStateChan() +{ + return stateChan; +} + +void GuiElement::SetState(int s, int c) +{ + state = s; + stateChan = c; +} + +void GuiElement::ResetState() +{ + if(state != STATE_DISABLED) + { + state = STATE_DEFAULT; + stateChan = -1; + } +} + +void GuiElement::SetClickable(bool c) +{ + clickable = c; +} + +void GuiElement::SetSelectable(bool s) +{ + selectable = s; +} + +void GuiElement::SetHoldable(bool d) +{ + holdable = d; +} + +bool GuiElement::IsSelectable() +{ + if(state == STATE_DISABLED || state == STATE_CLICKED) + return false; + else + return selectable; +} + +bool GuiElement::IsClickable() +{ + if(state == STATE_DISABLED || + state == STATE_CLICKED || + state == STATE_HELD) + return false; + else + return clickable; +} + +bool GuiElement::IsHoldable() +{ + if(state == STATE_DISABLED) + return false; + else + return holdable; +} + +void GuiElement::SetFocus(int f) +{ + focus = f; +} + +int GuiElement::IsFocused() +{ + return focus; +} + +void GuiElement::SetTrigger(GuiTrigger * t) +{ + if(!trigger[0]) + trigger[0] = t; + else if(!trigger[1]) + trigger[1] = t; + else // both were assigned, so we'll just overwrite the first one + trigger[0] = t; +} + +void GuiElement::SetTrigger(u8 i, GuiTrigger * t) +{ + trigger[i] = t; +} + +bool GuiElement::Rumble() +{ + return rumble; +} + +void GuiElement::SetRumble(bool r) +{ + rumble = r; +} + +int GuiElement::GetEffect() +{ + return effects; +} + +void GuiElement::SetEffect(int eff, int amount, int target) +{ + if(eff & EFFECT_SLIDE_IN) + { + // these calculations overcompensate a little + if(eff & EFFECT_SLIDE_TOP) + yoffsetDyn = -screenheight; + else if(eff & EFFECT_SLIDE_LEFT) + xoffsetDyn = -screenwidth; + else if(eff & EFFECT_SLIDE_BOTTOM) + yoffsetDyn = screenheight; + else if(eff & EFFECT_SLIDE_RIGHT) + xoffsetDyn = screenwidth; + } + if(eff & EFFECT_FADE && amount > 0) + { + alphaDyn = 0; + } + else if(eff & EFFECT_FADE && amount < 0) + { + alphaDyn = alpha; + } + + effects |= eff; + effectAmount = amount; + effectTarget = target; +} + +void GuiElement::SetEffectOnOver(int eff, int amount, int target) +{ + effectsOver |= eff; + effectAmountOver = amount; + effectTargetOver = target; +} + +void GuiElement::SetEffectGrow() +{ + SetEffectOnOver(EFFECT_SCALE, 4, 110); +} + +void GuiElement::UpdateEffects() +{ + if(effects & (EFFECT_SLIDE_IN | EFFECT_SLIDE_OUT)) + { + if(effects & EFFECT_SLIDE_IN) + { + if(effects & EFFECT_SLIDE_LEFT) + { + xoffsetDyn += effectAmount; + + if(xoffsetDyn >= 0) + { + xoffsetDyn = 0; + effects = 0; + } + } + else if(effects & EFFECT_SLIDE_RIGHT) + { + xoffsetDyn -= effectAmount; + + if(xoffsetDyn <= 0) + { + xoffsetDyn = 0; + effects = 0; + } + } + else if(effects & EFFECT_SLIDE_TOP) + { + yoffsetDyn += effectAmount; + + if(yoffsetDyn >= 0) + { + yoffsetDyn = 0; + effects = 0; + } + } + else if(effects & EFFECT_SLIDE_BOTTOM) + { + yoffsetDyn -= effectAmount; + + if(yoffsetDyn <= 0) + { + yoffsetDyn = 0; + effects = 0; + } + } + } + else + { + if(effects & EFFECT_SLIDE_LEFT) + { + xoffsetDyn -= effectAmount; + + if(xoffsetDyn <= -screenwidth) + effects = 0; // shut off effect + } + else if(effects & EFFECT_SLIDE_RIGHT) + { + xoffsetDyn += effectAmount; + + if(xoffsetDyn >= screenwidth) + effects = 0; // shut off effect + } + else if(effects & EFFECT_SLIDE_TOP) + { + yoffsetDyn -= effectAmount; + + if(yoffsetDyn <= -screenheight) + effects = 0; // shut off effect + } + else if(effects & EFFECT_SLIDE_BOTTOM) + { + yoffsetDyn += effectAmount; + + if(yoffsetDyn >= screenheight) + effects = 0; // shut off effect + } + } + } + if(effects & EFFECT_FADE) + { + alphaDyn += effectAmount; + + if(effectAmount < 0 && alphaDyn <= 0) + { + alphaDyn = 0; + effects = 0; // shut off effect + } + else if(effectAmount > 0 && alphaDyn >= alpha) + { + alphaDyn = alpha; + effects = 0; // shut off effect + } + } + if(effects & EFFECT_SCALE) + { + scaleDyn += effectAmount/100.0; + + if((effectAmount < 0 && scaleDyn <= effectTarget/100.0) + || (effectAmount > 0 && scaleDyn >= effectTarget/100.0)) + { + scaleDyn = effectTarget/100.0; + effects = 0; // shut off effect + } + } +} + +void GuiElement::Update(GuiTrigger * t) +{ + if(updateCB) + updateCB(this); +} + +void GuiElement::SetUpdateCallback(UpdateCallback u) +{ + updateCB = u; +} + +void GuiElement::SetPosition(int xoff, int yoff) +{ + xoffset = xoff; + yoffset = yoff; +} + +void GuiElement::SetAlignment(int hor, int vert) +{ + alignmentHor = hor; + alignmentVert = vert; +} + +int GuiElement::GetSelected() +{ + return -1; +} + +/** + * Draw an element on screen. + */ +void GuiElement::Draw() +{ +} + +/** + * Check if a position is inside the GuiElement. + * @param[in] x X position in pixel. + * @param[in] y Y position in pixel. + */ +bool GuiElement::IsInside(int x, int y) +{ + if(x > this->GetLeft() && x < (this->GetLeft()+width) + && y > this->GetTop() && y < (this->GetTop()+height)) + return true; + return false; +} diff --git a/src/platform/wii/libwiigui/gui_image.cpp b/src/platform/wii/libwiigui/gui_image.cpp new file mode 100644 index 0000000..6f15f48 --- /dev/null +++ b/src/platform/wii/libwiigui/gui_image.cpp @@ -0,0 +1,242 @@ +/**************************************************************************** + * libwiigui + * + * Tantric 2009 + * + * gui_image.cpp + * + * GUI class definitions + ***************************************************************************/ + +#include "gui.h" +/** + * Constructor for the GuiImage class. + */ +GuiImage::GuiImage() +{ + image = NULL; + width = 0; + height = 0; + imageangle = 0; + tile = -1; + stripe = 0; + imgType = IMAGE_DATA; +} + +GuiImage::GuiImage(GuiImageData * img) +{ + image = NULL; + width = 0; + height = 0; + if(img) + { + image = img->GetImage(); + width = img->GetWidth(); + height = img->GetHeight(); + } + imageangle = 0; + tile = -1; + stripe = 0; + imgType = IMAGE_DATA; +} + +GuiImage::GuiImage(u8 * img, int w, int h) +{ + image = img; + width = w; + height = h; + imageangle = 0; + tile = -1; + stripe = 0; + imgType = IMAGE_TEXTURE; +} + +GuiImage::GuiImage(int w, int h, GXColor c) +{ + image = (u8 *)memalign (32, w * h * 4); + width = w; + height = h; + imageangle = 0; + tile = -1; + stripe = 0; + imgType = IMAGE_COLOR; + + if(!image) + return; + + int x, y; + + for(y=0; y < h; y++) + { + for(x=0; x < w; x++) + { + this->SetPixel(x, y, c); + } + } + int len = w*h*4; + if(len%32) len += (32-len%32); + DCFlushRange(image, len); +} + +/** + * Destructor for the GuiImage class. + */ +GuiImage::~GuiImage() +{ + if(imgType == IMAGE_COLOR && image) + free(image); +} + +u8 * GuiImage::GetImage() +{ + return image; +} + +void GuiImage::SetImage(GuiImageData * img) +{ + image = NULL; + width = 0; + height = 0; + if(img) + { + image = img->GetImage(); + width = img->GetWidth(); + height = img->GetHeight(); + } + imgType = IMAGE_DATA; +} + +void GuiImage::SetImage(u8 * img, int w, int h) +{ + image = img; + width = w; + height = h; + imgType = IMAGE_TEXTURE; +} + +void GuiImage::SetAngle(float a) +{ + imageangle = a; +} + +void GuiImage::SetTile(int t) +{ + tile = t; +} + +GXColor GuiImage::GetPixel(int x, int y) +{ + if(!image || this->GetWidth() <= 0 || x < 0 || y < 0) + return (GXColor){0, 0, 0, 0}; + + u32 offset = (((y >> 2)<<4)*this->GetWidth()) + ((x >> 2)<<6) + (((y%4 << 2) + x%4 ) << 1); + GXColor color; + color.a = *(image+offset); + color.r = *(image+offset+1); + color.g = *(image+offset+32); + color.b = *(image+offset+33); + return color; +} + +void GuiImage::SetPixel(int x, int y, GXColor color) +{ + if(!image || this->GetWidth() <= 0 || x < 0 || y < 0) + return; + + u32 offset = (((y >> 2)<<4)*this->GetWidth()) + ((x >> 2)<<6) + (((y%4 << 2) + x%4 ) << 1); + *(image+offset) = color.a; + *(image+offset+1) = color.r; + *(image+offset+32) = color.g; + *(image+offset+33) = color.b; +} + +void GuiImage::SetStripe(int s) +{ + stripe = s; +} + +void GuiImage::ColorStripe(int shift) +{ + int x, y; + GXColor color; + int alt = 0; + + for(y=0; y < this->GetHeight(); y++) + { + if(y % 3 == 0) + alt ^= 1; + + for(x=0; x < this->GetWidth(); x++) + { + color = GetPixel(x, y); + + if(alt) + { + if(color.r < 255-shift) + color.r += shift; + else + color.r = 255; + if(color.g < 255-shift) + color.g += shift; + else + color.g = 255; + if(color.b < 255-shift) + color.b += shift; + else + color.b = 255; + + color.a = 255; + } + else + { + if(color.r > shift) + color.r -= shift; + else + color.r = 0; + if(color.g > shift) + color.g -= shift; + else + color.g = 0; + if(color.b > shift) + color.b -= shift; + else + color.b = 0; + + color.a = 255; + } + SetPixel(x, y, color); + } + } +} + +/** + * Draw the button on screen + */ +void GuiImage::Draw() +{ + if(!image || !this->IsVisible() || tile == 0) + return; + + float currScale = this->GetScale(); + int currLeft = this->GetLeft(); + + if(tile > 0) + { + for(int i=0; iGetTop(), width, height, image, imageangle, currScale, currScale, this->GetAlpha()); + } + else + { + // temporary (maybe), used to correct offset for scaled images + if(scale != 1) + currLeft = currLeft - width/2 + (width*scale)/2; + + Menu_DrawImg(currLeft, this->GetTop(), width, height, image, imageangle, currScale, currScale, this->GetAlpha()); + } + + if(stripe > 0) + for(int y=0; y < this->GetHeight(); y+=6) + Menu_DrawRectangle(currLeft,this->GetTop()+y,this->GetWidth(),3,(GXColor){0, 0, 0, stripe},1); + + this->UpdateEffects(); +} diff --git a/src/platform/wii/libwiigui/gui_imagedata.cpp b/src/platform/wii/libwiigui/gui_imagedata.cpp new file mode 100644 index 0000000..6781be8 --- /dev/null +++ b/src/platform/wii/libwiigui/gui_imagedata.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** + * libwiigui + * + * Tantric 2009 + * + * gui_imagedata.cpp + * + * GUI class definitions + ***************************************************************************/ + +#include "gui.h" + +/** + * Constructor for the GuiImageData class. + */ +GuiImageData::GuiImageData(const u8 * img) +{ + data = NULL; + width = 0; + height = 0; + + if(img) + { + PNGUPROP imgProp; + IMGCTX ctx = PNGU_SelectImageFromBuffer(img); + + if(!ctx) + return; + + int res = PNGU_GetImageProperties(ctx, &imgProp); + + if(res == PNGU_OK) + { + int len = imgProp.imgWidth * imgProp.imgHeight * 4; + if(len%32) len += (32-len%32); + data = (u8 *)memalign (32, len); + + if(data) + { + res = PNGU_DecodeTo4x4RGBA8 (ctx, imgProp.imgWidth, imgProp.imgHeight, data, 255); + + if(res == PNGU_OK) + { + width = imgProp.imgWidth; + height = imgProp.imgHeight; + DCFlushRange(data, len); + } + else + { + free(data); + data = NULL; + } + } + } + PNGU_ReleaseImageContext (ctx); + } +} + +/** + * Destructor for the GuiImageData class. + */ +GuiImageData::~GuiImageData() +{ + if(data) + { + free(data); + data = NULL; + } +} + +u8 * GuiImageData::GetImage() +{ + return data; +} + +int GuiImageData::GetWidth() +{ + return width; +} + +int GuiImageData::GetHeight() +{ + return height; +} diff --git a/src/platform/wii/libwiigui/gui_keyboard.cpp b/src/platform/wii/libwiigui/gui_keyboard.cpp new file mode 100644 index 0000000..6b30c96 --- /dev/null +++ b/src/platform/wii/libwiigui/gui_keyboard.cpp @@ -0,0 +1,362 @@ +/**************************************************************************** + * libwiigui + * + * Tantric 2009 + * + * gui_keyboard.cpp + * + * GUI class definitions + ***************************************************************************/ + +#include "gui.h" + +static char tmptxt[MAX_KEYBOARD_DISPLAY]; + +static char * GetDisplayText(char * t) +{ + if(!t) + return NULL; + + int len = strlen(t); + + if(len < MAX_KEYBOARD_DISPLAY) + return t; + + strncpy(tmptxt, &t[len-MAX_KEYBOARD_DISPLAY], MAX_KEYBOARD_DISPLAY); + tmptxt[MAX_KEYBOARD_DISPLAY-1] = 0; + return &tmptxt[0]; +} + +/** + * Constructor for the GuiKeyboard class. + */ + +GuiKeyboard::GuiKeyboard(char * t, u32 max) +{ + width = 540; + height = 400; + shift = 0; + caps = 0; + selectable = true; + focus = 0; // allow focus + alignmentHor = ALIGN_CENTRE; + alignmentVert = ALIGN_MIDDLE; + strncpy(kbtextstr, t, max); + kbtextstr[max] = 0; + kbtextmaxlen = max; + + Key thekeys[4][11] = { + { + {'1','!'}, + {'2','@'}, + {'3','#'}, + {'4','$'}, + {'5','%'}, + {'6','^'}, + {'7','&'}, + {'8','*'}, + {'9','('}, + {'0',')'}, + {'\0','\0'} + }, + { + {'q','Q'}, + {'w','W'}, + {'e','E'}, + {'r','R'}, + {'t','T'}, + {'y','Y'}, + {'u','U'}, + {'i','I'}, + {'o','O'}, + {'p','P'}, + {'-','_'} + }, + { + {'a','A'}, + {'s','S'}, + {'d','D'}, + {'f','F'}, + {'g','G'}, + {'h','H'}, + {'j','J'}, + {'k','K'}, + {'l','L'}, + {';',':'}, + {'\'','"'} + }, + + { + {'z','Z'}, + {'x','X'}, + {'c','C'}, + {'v','V'}, + {'b','B'}, + {'n','N'}, + {'m','M'}, + {',','<'}, + {'.','>'}, + {'/','?'}, + {'\0','\0'} + } + }; + memcpy(keys, thekeys, sizeof(thekeys)); + + keyTextbox = new GuiImageData(keyboard_textbox_png); + keyTextboxImg = new GuiImage(keyTextbox); + keyTextboxImg->SetAlignment(ALIGN_CENTRE, ALIGN_TOP); + keyTextboxImg->SetPosition(0, 0); + this->Append(keyTextboxImg); + + kbText = new GuiText(GetDisplayText(kbtextstr), 22, (GXColor){0, 0, 0, 0xff}); + kbText->SetAlignment(ALIGN_CENTRE, ALIGN_TOP); + kbText->SetPosition(0, 13); + this->Append(kbText); + + key = new GuiImageData(keyboard_key_png); + keyOver = new GuiImageData(keyboard_key_over_png); + keyMedium = new GuiImageData(keyboard_mediumkey_png); + keyMediumOver = new GuiImageData(keyboard_mediumkey_over_png); + keyLarge = new GuiImageData(keyboard_largekey_png); + keyLargeOver = new GuiImageData(keyboard_largekey_over_png); + + keySoundOver = new GuiSound(button_over_pcm, button_over_pcm_size, SOUND_PCM); + keySoundClick = new GuiSound(button_click_pcm, button_click_pcm_size, SOUND_PCM); + trigA = new GuiTrigger; + trigA->SetSimpleTrigger(-1, WPAD_BUTTON_A | WPAD_CLASSIC_BUTTON_A, PAD_BUTTON_A); + + keyBackImg = new GuiImage(keyMedium); + keyBackOverImg = new GuiImage(keyMediumOver); + keyBackText = new GuiText("Back", 22, (GXColor){0, 0, 0, 0xff}); + keyBack = new GuiButton(keyMedium->GetWidth(), keyMedium->GetHeight()); + keyBack->SetImage(keyBackImg); + keyBack->SetImageOver(keyBackOverImg); + keyBack->SetLabel(keyBackText); + keyBack->SetSoundOver(keySoundOver); + keyBack->SetSoundClick(keySoundClick); + keyBack->SetTrigger(trigA); + keyBack->SetPosition(10*42+40, 0*42+80); + keyBack->SetEffectGrow(); + this->Append(keyBack); + + keyCapsImg = new GuiImage(keyMedium); + keyCapsOverImg = new GuiImage(keyMediumOver); + keyCapsText = new GuiText("Caps", 22, (GXColor){0, 0, 0, 0xff}); + keyCaps = new GuiButton(keyMedium->GetWidth(), keyMedium->GetHeight()); + keyCaps->SetImage(keyCapsImg); + keyCaps->SetImageOver(keyCapsOverImg); + keyCaps->SetLabel(keyCapsText); + keyCaps->SetSoundOver(keySoundOver); + keyCaps->SetSoundClick(keySoundClick); + keyCaps->SetTrigger(trigA); + keyCaps->SetPosition(0, 2*42+80); + keyCaps->SetEffectGrow(); + this->Append(keyCaps); + + keyShiftImg = new GuiImage(keyMedium); + keyShiftOverImg = new GuiImage(keyMediumOver); + keyShiftText = new GuiText("Shift", 22, (GXColor){0, 0, 0, 0xff}); + keyShift = new GuiButton(keyMedium->GetWidth(), keyMedium->GetHeight()); + keyShift->SetImage(keyShiftImg); + keyShift->SetImageOver(keyShiftOverImg); + keyShift->SetLabel(keyShiftText); + keyShift->SetSoundOver(keySoundOver); + keyShift->SetSoundClick(keySoundClick); + keyShift->SetTrigger(trigA); + keyShift->SetPosition(21, 3*42+80); + keyShift->SetEffectGrow(); + this->Append(keyShift); + + keySpaceImg = new GuiImage(keyLarge); + keySpaceOverImg = new GuiImage(keyLargeOver); + keySpace = new GuiButton(keyLarge->GetWidth(), keyLarge->GetHeight()); + keySpace->SetImage(keySpaceImg); + keySpace->SetImageOver(keySpaceOverImg); + keySpace->SetSoundOver(keySoundOver); + keySpace->SetSoundClick(keySoundClick); + keySpace->SetTrigger(trigA); + keySpace->SetPosition(0, 4*42+80); + keySpace->SetAlignment(ALIGN_CENTRE, ALIGN_TOP); + keySpace->SetEffectGrow(); + this->Append(keySpace); + + char txt[2] = { 0, 0 }; + + for(int i=0; i<4; i++) + { + for(int j=0; j<11; j++) + { + if(keys[i][j].ch != '\0') + { + txt[0] = keys[i][j].ch; + keyImg[i][j] = new GuiImage(key); + keyImgOver[i][j] = new GuiImage(keyOver); + keyTxt[i][j] = new GuiText(txt, 22, (GXColor){0, 0, 0, 0xff}); + keyTxt[i][j]->SetAlignment(ALIGN_CENTRE, ALIGN_BOTTOM); + keyTxt[i][j]->SetPosition(0, -10); + keyBtn[i][j] = new GuiButton(key->GetWidth(), key->GetHeight()); + keyBtn[i][j]->SetImage(keyImg[i][j]); + keyBtn[i][j]->SetImageOver(keyImgOver[i][j]); + keyBtn[i][j]->SetSoundOver(keySoundOver); + keyBtn[i][j]->SetSoundClick(keySoundClick); + keyBtn[i][j]->SetTrigger(trigA); + keyBtn[i][j]->SetLabel(keyTxt[i][j]); + keyBtn[i][j]->SetPosition(j*42+21*i+40, i*42+80); + keyBtn[i][j]->SetEffectGrow(); + this->Append(keyBtn[i][j]); + } + } + } +} + +/** + * Destructor for the GuiKeyboard class. + */ +GuiKeyboard::~GuiKeyboard() +{ + delete kbText; + delete keyTextbox; + delete keyTextboxImg; + delete keyCapsText; + delete keyCapsImg; + delete keyCapsOverImg; + delete keyCaps; + delete keyShiftText; + delete keyShiftImg; + delete keyShiftOverImg; + delete keyShift; + delete keyBackText; + delete keyBackImg; + delete keyBackOverImg; + delete keyBack; + delete keySpaceImg; + delete keySpaceOverImg; + delete keySpace; + delete key; + delete keyOver; + delete keyMedium; + delete keyMediumOver; + delete keyLarge; + delete keyLargeOver; + delete keySoundOver; + delete keySoundClick; + delete trigA; + + for(int i=0; i<4; i++) + { + for(int j=0; j<11; j++) + { + if(keys[i][j].ch != '\0') + { + delete keyImg[i][j]; + delete keyImgOver[i][j]; + delete keyTxt[i][j]; + delete keyBtn[i][j]; + } + } + } +} + +void GuiKeyboard::Update(GuiTrigger * t) +{ + if(_elements.size() == 0 || (state == STATE_DISABLED && parentElement)) + return; + + for (u8 i = 0; i < _elements.size(); i++) + { + try { _elements.at(i)->Update(t); } + catch (const std::exception& e) { } + } + + bool update = false; + + if(keySpace->GetState() == STATE_CLICKED) + { + if(strlen(kbtextstr) < kbtextmaxlen) + { + kbtextstr[strlen(kbtextstr)] = ' '; + kbText->SetText(kbtextstr); + } + keySpace->SetState(STATE_SELECTED, t->chan); + } + else if(keyBack->GetState() == STATE_CLICKED) + { + kbtextstr[strlen(kbtextstr)-1] = 0; + kbText->SetText(GetDisplayText(kbtextstr)); + keyBack->SetState(STATE_SELECTED, t->chan); + } + else if(keyShift->GetState() == STATE_CLICKED) + { + shift ^= 1; + keyShift->SetState(STATE_SELECTED, t->chan); + update = true; + } + else if(keyCaps->GetState() == STATE_CLICKED) + { + caps ^= 1; + keyCaps->SetState(STATE_SELECTED, t->chan); + update = true; + } + + char txt[2] = { 0, 0 }; + + startloop: + + for(int i=0; i<4; i++) + { + for(int j=0; j<11; j++) + { + if(keys[i][j].ch != '\0') + { + if(update) + { + if(shift || caps) + txt[0] = keys[i][j].chShift; + else + txt[0] = keys[i][j].ch; + + keyTxt[i][j]->SetText(txt); + } + + if(keyBtn[i][j]->GetState() == STATE_CLICKED) + { + if(strlen(kbtextstr) < kbtextmaxlen) + { + if(shift || caps) + { + kbtextstr[strlen(kbtextstr)] = keys[i][j].chShift; + } + else + { + kbtextstr[strlen(kbtextstr)] = keys[i][j].ch; + } + } + kbText->SetText(GetDisplayText(kbtextstr)); + keyBtn[i][j]->SetState(STATE_SELECTED, t->chan); + + if(shift) + { + shift ^= 1; + update = true; + goto startloop; + } + } + } + } + } + + this->ToggleFocus(t); + + if(focus) // only send actions to this window if it's in focus + { + // pad/joystick navigation + if(t->Right()) + this->MoveSelectionHor(1); + else if(t->Left()) + this->MoveSelectionHor(-1); + else if(t->Down()) + this->MoveSelectionVert(1); + else if(t->Up()) + this->MoveSelectionVert(-1); + } +} diff --git a/src/platform/wii/libwiigui/gui_sound.cpp b/src/platform/wii/libwiigui/gui_sound.cpp new file mode 100644 index 0000000..b3213e7 --- /dev/null +++ b/src/platform/wii/libwiigui/gui_sound.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** + * libwiigui + * + * Tantric 2009 + * + * gui_sound.cpp + * + * GUI class definitions + ***************************************************************************/ + +#include "gui.h" + +/** + * Constructor for the GuiSound class. + */ +GuiSound::GuiSound(const u8 * snd, s32 len, int t) +{ + sound = snd; + length = len; + type = t; + voice = -1; + volume = 100; + loop = false; +} + +/** + * Destructor for the GuiSound class. + */ +GuiSound::~GuiSound() +{ + if(type == SOUND_OGG) + StopOgg(); +} + +void GuiSound::Play() +{ + int vol; + + switch(type) + { + case SOUND_PCM: + vol = 255*(volume/100.0); + voice = ASND_GetFirstUnusedVoice(); + if(voice >= 0) + ASND_SetVoice(voice, VOICE_STEREO_16BIT, 48000, 0, + (u8 *)sound, length, vol, vol, NULL); + break; + + case SOUND_OGG: + voice = 0; + if(loop) + PlayOgg((char *)sound, length, 0, OGG_INFINITE_TIME); + else + PlayOgg((char *)sound, length, 0, OGG_ONE_TIME); + SetVolumeOgg(255*(volume/100.0)); + break; + } +} + +void GuiSound::Stop() +{ + if(voice < 0) + return; + + switch(type) + { + case SOUND_PCM: + ASND_StopVoice(voice); + break; + + case SOUND_OGG: + StopOgg(); + break; + } +} + +void GuiSound::Pause() +{ + if(voice < 0) + return; + + switch(type) + { + case SOUND_PCM: + ASND_PauseVoice(voice, 1); + break; + + case SOUND_OGG: + PauseOgg(1); + break; + } +} + +void GuiSound::Resume() +{ + if(voice < 0) + return; + + switch(type) + { + case SOUND_PCM: + ASND_PauseVoice(voice, 0); + break; + + case SOUND_OGG: + PauseOgg(0); + break; + } +} + +bool GuiSound::IsPlaying() +{ + if(ASND_StatusVoice(voice) == SND_WORKING || ASND_StatusVoice(voice) == SND_WAITING) + return true; + else + return false; +} + +void GuiSound::SetVolume(int vol) +{ + volume = vol; + + if(voice < 0) + return; + + int newvol = 255*(volume/100.0); + + switch(type) + { + case SOUND_PCM: + ASND_ChangeVolumeVoice(voice, newvol, newvol); + break; + + case SOUND_OGG: + SetVolumeOgg(255*(volume/100.0)); + break; + } +} + +void GuiSound::SetLoop(bool l) +{ + loop = l; +} diff --git a/src/platform/wii/libwiigui/gui_text.cpp b/src/platform/wii/libwiigui/gui_text.cpp new file mode 100644 index 0000000..ac43d21 --- /dev/null +++ b/src/platform/wii/libwiigui/gui_text.cpp @@ -0,0 +1,348 @@ +/**************************************************************************** + * libwiigui + * + * Tantric 2009 + * + * gui_text.cpp + * + * GUI class definitions + ***************************************************************************/ + +#include "gui.h" + +static int currentSize = 0; +static int presetSize = 0; +static int presetMaxWidth = 0; +static int presetAlignmentHor = 0; +static int presetAlignmentVert = 0; +static u16 presetStyle = 0; +static GXColor presetColor = (GXColor){255, 255, 255, 255}; + +#define TEXT_SCROLL_DELAY 8 +#define TEXT_SCROLL_INITIAL_DELAY 6 + +/** + * Constructor for the GuiText class. + */ +GuiText::GuiText(const char * t, int s, GXColor c) +{ + origText = NULL; + text = NULL; + size = s; + color = c; + alpha = c.a; + style = FTGX_JUSTIFY_CENTER | FTGX_ALIGN_MIDDLE; + maxWidth = 0; + wrap = false; + textDyn = NULL; + textScroll = SCROLL_NONE; + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + textScrollDelay = TEXT_SCROLL_DELAY; + + alignmentHor = ALIGN_CENTRE; + alignmentVert = ALIGN_MIDDLE; + + if(t) + { + origText = strdup(t); + text = charToWideChar(t); + } +} + +/** + * Constructor for the GuiText class, uses presets + */ +GuiText::GuiText(const char * t) +{ + origText = NULL; + text = NULL; + size = presetSize; + color = presetColor; + alpha = presetColor.a; + style = presetStyle; + maxWidth = presetMaxWidth; + wrap = false; + textDyn = NULL; + textScroll = SCROLL_NONE; + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + textScrollDelay = TEXT_SCROLL_DELAY; + + alignmentHor = presetAlignmentHor; + alignmentVert = presetAlignmentVert; + + if(t) + { + origText = strdup(t); + text = charToWideChar(t); + } +} + +/** + * Destructor for the GuiText class. + */ +GuiText::~GuiText() +{ + if(origText) + free(origText); + if(text) + delete[] text; + if(textDyn) + delete[] textDyn; +} + +void GuiText::SetText(const char * t) +{ + if(origText) + free(origText); + if(text) + delete[] text; + if(textDyn) + delete[] textDyn; + + origText = NULL; + text = NULL; + textDyn = NULL; + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + + if(t) + { + origText = strdup(t); + text = charToWideChar(t); + } +} + +void GuiText::SetPresets(int sz, GXColor c, int w, u16 s, int h, int v) +{ + presetSize = sz; + presetColor = c; + presetStyle = s; + presetMaxWidth = w; + presetAlignmentHor = h; + presetAlignmentVert = v; +} + +void GuiText::SetFontSize(int s) +{ + size = s; +} + +void GuiText::SetMaxWidth(int width) +{ + maxWidth = width; +} + +void GuiText::SetWrap(bool w, int width) +{ + wrap = w; + maxWidth = width; +} + +void GuiText::SetScroll(int s) +{ + if(textScroll == s) + return; + + if(textDyn) + { + delete[] textDyn; + textDyn = NULL; + } + textScroll = s; + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + textScrollDelay = TEXT_SCROLL_DELAY; +} + +void GuiText::SetColor(GXColor c) +{ + color = c; + alpha = c.a; +} + +void GuiText::SetStyle(u16 s) +{ + style = s; +} + +void GuiText::SetAlignment(int hor, int vert) +{ + style = 0; + + switch(hor) + { + case ALIGN_LEFT: + style |= FTGX_JUSTIFY_LEFT; + break; + case ALIGN_RIGHT: + style |= FTGX_JUSTIFY_RIGHT; + break; + default: + style |= FTGX_JUSTIFY_CENTER; + break; + } + switch(vert) + { + case ALIGN_TOP: + style |= FTGX_ALIGN_TOP; + break; + case ALIGN_BOTTOM: + style |= FTGX_ALIGN_BOTTOM; + break; + default: + style |= FTGX_ALIGN_MIDDLE; + break; + } + + alignmentHor = hor; + alignmentVert = vert; +} + +/** + * Draw the text on screen + */ +void GuiText::Draw() +{ + if(!text) + return; + + if(!this->IsVisible()) + return; + + GXColor c = color; + c.a = this->GetAlpha(); + + int newSize = size*this->GetScale(); + + if(newSize > MAX_FONT_SIZE) + newSize = MAX_FONT_SIZE; + + if(newSize != currentSize) + { + ChangeFontSize(newSize); + if(!fontSystem[newSize]) + fontSystem[newSize] = new FreeTypeGX(newSize); + currentSize = newSize; + } + + if(maxWidth > 0) + { + char * tmpText = strdup(origText); + u8 maxChar = (maxWidth*2.0) / newSize; + + if(!textDyn) + { + if(strlen(tmpText) > maxChar) + tmpText[maxChar] = 0; + textDyn = charToWideChar(tmpText); + } + + if(textScroll == SCROLL_HORIZONTAL) + { + int textlen = strlen(origText); + + if(textlen > maxChar && (FrameTimer % textScrollDelay == 0)) + { + if(textScrollInitialDelay) + { + textScrollInitialDelay--; + } + else + { + textScrollPos++; + if(textScrollPos > textlen-1) + { + textScrollPos = 0; + textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY; + } + + strncpy(tmpText, &origText[textScrollPos], maxChar-1); + tmpText[maxChar-1] = 0; + + int dynlen = strlen(tmpText); + + if(dynlen+2 < maxChar) + { + tmpText[dynlen] = ' '; + tmpText[dynlen+1] = ' '; + strncat(&tmpText[dynlen+2], origText, maxChar - dynlen - 2); + } + if(textDyn) delete[] textDyn; + textDyn = charToWideChar(tmpText); + } + } + if(textDyn) + fontSystem[currentSize]->drawText(this->GetLeft(), this->GetTop(), textDyn, c, style); + } + else if(wrap) + { + int lineheight = newSize + 6; + int txtlen = wcslen(text); + int i = 0; + int ch = 0; + int linenum = 0; + int lastSpace = -1; + int lastSpaceIndex = -1; + wchar_t * textrow[20]; + + while(ch < txtlen) + { + if(i == 0) + textrow[linenum] = new wchar_t[txtlen + 1]; + + textrow[linenum][i] = text[ch]; + textrow[linenum][i+1] = 0; + + if(text[ch] == ' ' || ch == txtlen-1) + { + if(wcslen(textrow[linenum]) >= maxChar) + { + if(lastSpace >= 0) + { + textrow[linenum][lastSpaceIndex] = 0; // discard space, and everything after + ch = lastSpace; // go backwards to the last space + lastSpace = -1; // we have used this space + lastSpaceIndex = -1; + } + linenum++; + i = -1; + } + else if(ch == txtlen-1) + { + linenum++; + } + } + if(text[ch] == ' ' && i >= 0) + { + lastSpace = ch; + lastSpaceIndex = i; + } + ch++; + i++; + } + + int voffset = 0; + + if(alignmentVert == ALIGN_MIDDLE) + voffset = -(lineheight*linenum)/2 + lineheight/2; + + for(i=0; i < linenum; i++) + { + fontSystem[currentSize]->drawText(this->GetLeft(), this->GetTop()+voffset+i*lineheight, textrow[i], c, style); + delete[] textrow[i]; + } + } + else + { + fontSystem[currentSize]->drawText(this->GetLeft(), this->GetTop(), textDyn, c, style); + } + free(tmpText); + } + else + { + fontSystem[currentSize]->drawText(this->GetLeft(), this->GetTop(), text, c, style); + } + this->UpdateEffects(); +} diff --git a/src/platform/wii/libwiigui/gui_trigger.cpp b/src/platform/wii/libwiigui/gui_trigger.cpp new file mode 100644 index 0000000..d178954 --- /dev/null +++ b/src/platform/wii/libwiigui/gui_trigger.cpp @@ -0,0 +1,249 @@ +/**************************************************************************** + * libwiigui + * + * Tantric 2009 + * + * gui_trigger.cpp + * + * GUI class definitions + ***************************************************************************/ + +#include "gui.h" + +static int scrollDelay = 0; + +/** + * Constructor for the GuiTrigger class. + */ +GuiTrigger::GuiTrigger() +{ + chan = -1; + memset(&wpad, 0, sizeof(WPADData)); + memset(&pad, 0, sizeof(PADData)); +} + +/** + * Destructor for the GuiTrigger class. + */ +GuiTrigger::~GuiTrigger() +{ +} + +/** + * Sets a simple trigger. Requires: + * - Element is selected + * - Trigger button is pressed + */ +void GuiTrigger::SetSimpleTrigger(s32 ch, u32 wiibtns, u16 gcbtns) +{ + type = TRIGGER_SIMPLE; + chan = ch; + wpad.btns_d = wiibtns; + pad.btns_d = gcbtns; +} + +/** + * Sets a held trigger. Requires: + * - Element is selected + * - Trigger button is pressed and held + */ +void GuiTrigger::SetHeldTrigger(s32 ch, u32 wiibtns, u16 gcbtns) +{ + type = TRIGGER_HELD; + chan = ch; + wpad.btns_h = wiibtns; + pad.btns_h = gcbtns; +} + +/** + * Sets a button trigger. Requires: + * - Trigger button is pressed + */ +void GuiTrigger::SetButtonOnlyTrigger(s32 ch, u32 wiibtns, u16 gcbtns) +{ + type = TRIGGER_BUTTON_ONLY; + chan = ch; + wpad.btns_d = wiibtns; + pad.btns_d = gcbtns; +} + +/** + * Sets a button trigger. Requires: + * - Trigger button is pressed + * - Parent window is in focus + */ +void GuiTrigger::SetButtonOnlyInFocusTrigger(s32 ch, u32 wiibtns, u16 gcbtns) +{ + type = TRIGGER_BUTTON_ONLY_IN_FOCUS; + chan = ch; + wpad.btns_d = wiibtns; + pad.btns_d = gcbtns; +} + +/**************************************************************************** + * WPAD_Stick + * + * Get X/Y value from Wii Joystick (classic, nunchuk) input + ***************************************************************************/ + +s8 GuiTrigger::WPAD_Stick(u8 right, int axis) +{ + float mag = 0.0; + float ang = 0.0; + + switch (wpad.exp.type) + { + case WPAD_EXP_NUNCHUK: + case WPAD_EXP_GUITARHERO3: + if (right == 0) + { + mag = wpad.exp.nunchuk.js.mag; + ang = wpad.exp.nunchuk.js.ang; + } + break; + + case WPAD_EXP_CLASSIC: + if (right == 0) + { + mag = wpad.exp.classic.ljs.mag; + ang = wpad.exp.classic.ljs.ang; + } + else + { + mag = wpad.exp.classic.rjs.mag; + ang = wpad.exp.classic.rjs.ang; + } + break; + + default: + break; + } + + /* calculate x/y value (angle need to be converted into radian) */ + if (mag > 1.0) mag = 1.0; + else if (mag < -1.0) mag = -1.0; + double val; + + if(axis == 0) // x-axis + val = mag * sin((PI * ang)/180.0f); + else // y-axis + val = mag * cos((PI * ang)/180.0f); + + return (s8)(val * 128.0f); +} + +bool GuiTrigger::Left() +{ + u32 wiibtn = WPAD_BUTTON_LEFT; + + if((wpad.btns_d | wpad.btns_h) & (wiibtn | WPAD_CLASSIC_BUTTON_LEFT) + || (pad.btns_d | pad.btns_h) & PAD_BUTTON_LEFT + || pad.stickX < -PADCAL + || WPAD_Stick(0,0) < -PADCAL) + { + if(wpad.btns_d & (wiibtn | WPAD_CLASSIC_BUTTON_LEFT) + || pad.btns_d & PAD_BUTTON_LEFT) + { + scrollDelay = SCROLL_INITIAL_DELAY; // reset scroll delay. + return true; + } + else if(scrollDelay == 0) + { + scrollDelay = SCROLL_LOOP_DELAY; + return true; + } + else + { + if(scrollDelay > 0) + scrollDelay--; + } + } + return false; +} + +bool GuiTrigger::Right() +{ + u32 wiibtn = WPAD_BUTTON_RIGHT; + + if((wpad.btns_d | wpad.btns_h) & (wiibtn | WPAD_CLASSIC_BUTTON_RIGHT) + || (pad.btns_d | pad.btns_h) & PAD_BUTTON_RIGHT + || pad.stickX > PADCAL + || WPAD_Stick(0,0) > PADCAL) + { + if(wpad.btns_d & (wiibtn | WPAD_CLASSIC_BUTTON_RIGHT) + || pad.btns_d & PAD_BUTTON_RIGHT) + { + scrollDelay = SCROLL_INITIAL_DELAY; // reset scroll delay. + return true; + } + else if(scrollDelay == 0) + { + scrollDelay = SCROLL_LOOP_DELAY; + return true; + } + else + { + if(scrollDelay > 0) + scrollDelay--; + } + } + return false; +} + +bool GuiTrigger::Up() +{ + u32 wiibtn = WPAD_BUTTON_UP; + + if((wpad.btns_d | wpad.btns_h) & (wiibtn | WPAD_CLASSIC_BUTTON_UP) + || (pad.btns_d | pad.btns_h) & PAD_BUTTON_UP + || pad.stickY > PADCAL + || WPAD_Stick(0,1) > PADCAL) + { + if(wpad.btns_d & (wiibtn | WPAD_CLASSIC_BUTTON_UP) + || pad.btns_d & PAD_BUTTON_UP) + { + scrollDelay = SCROLL_INITIAL_DELAY; // reset scroll delay. + return true; + } + else if(scrollDelay == 0) + { + scrollDelay = SCROLL_LOOP_DELAY; + return true; + } + else + { + if(scrollDelay > 0) + scrollDelay--; + } + } + return false; +} + +bool GuiTrigger::Down() +{ + u32 wiibtn = WPAD_BUTTON_DOWN; + + if((wpad.btns_d | wpad.btns_h) & (wiibtn | WPAD_CLASSIC_BUTTON_DOWN) + || (pad.btns_d | pad.btns_h) & PAD_BUTTON_DOWN + || pad.stickY < -PADCAL + || WPAD_Stick(0,1) < -PADCAL) + { + if(wpad.btns_d & (wiibtn | WPAD_CLASSIC_BUTTON_DOWN) + || pad.btns_d & PAD_BUTTON_DOWN) + { + scrollDelay = SCROLL_INITIAL_DELAY; // reset scroll delay. + return true; + } + else if(scrollDelay == 0) + { + scrollDelay = SCROLL_LOOP_DELAY; + return true; + } + else + { + if(scrollDelay > 0) + scrollDelay--; + } + } + return false; +} diff --git a/src/platform/wii/libwiigui/gui_window.cpp b/src/platform/wii/libwiigui/gui_window.cpp new file mode 100644 index 0000000..6da58fd --- /dev/null +++ b/src/platform/wii/libwiigui/gui_window.cpp @@ -0,0 +1,404 @@ +/**************************************************************************** + * libwiigui + * + * Tantric 2009 + * + * gui_window.cpp + * + * GUI class definitions + ***************************************************************************/ + +#include "gui.h" + +GuiWindow::GuiWindow() +{ + width = 0; + height = 0; + focus = 0; // allow focus +} + +GuiWindow::GuiWindow(int w, int h) +{ + width = w; + height = h; + focus = 0; // allow focus +} + +GuiWindow::~GuiWindow() +{ +} + +void GuiWindow::Append(GuiElement* e) +{ + if (e == NULL) + return; + + Remove(e); + _elements.push_back(e); + e->SetParent(this); +} + +void GuiWindow::Insert(GuiElement* e, u32 index) +{ + if (e == NULL || index > (_elements.size() - 1)) + return; + + Remove(e); + _elements.insert(_elements.begin()+index, e); + e->SetParent(this); +} + +void GuiWindow::Remove(GuiElement* e) +{ + if (e == NULL) + return; + + for (u8 i = 0; i < _elements.size(); i++) + { + if(e == _elements.at(i)) + { + _elements.erase(_elements.begin()+i); + break; + } + } +} + +void GuiWindow::RemoveAll() +{ + _elements.clear(); +} + +GuiElement* GuiWindow::GetGuiElementAt(u32 index) const +{ + if (index >= _elements.size()) + return NULL; + return _elements.at(index); +} + +u32 GuiWindow::GetSize() +{ + return _elements.size(); +} + +void GuiWindow::Draw() +{ + if(_elements.size() == 0 || !this->IsVisible()) + return; + + for (u8 i = 0; i < _elements.size(); i++) + { + try { _elements.at(i)->Draw(); } + catch (const std::exception& e) { } + } + + this->UpdateEffects(); + + if(parentElement && state == STATE_DISABLED) + Menu_DrawRectangle(0,0,screenwidth,screenheight,(GXColor){0xbe, 0xca, 0xd5, 0x70},1); +} + +void GuiWindow::ResetState() +{ + if(state != STATE_DISABLED) + state = STATE_DEFAULT; + + for (u8 i = 0; i < _elements.size(); i++) + { + try { _elements.at(i)->ResetState(); } + catch (const std::exception& e) { } + } +} + +void GuiWindow::SetState(int s) +{ + state = s; + + for (u8 i = 0; i < _elements.size(); i++) + { + try { _elements.at(i)->SetState(s); } + catch (const std::exception& e) { } + } +} + +void GuiWindow::SetVisible(bool v) +{ + visible = v; + + for (u8 i = 0; i < _elements.size(); i++) + { + try { _elements.at(i)->SetVisible(v); } + catch (const std::exception& e) { } + } +} + +void GuiWindow::SetFocus(int f) +{ + focus = f; + + if(f == 1) + this->MoveSelectionVert(1); + else + this->ResetState(); +} + +void GuiWindow::ChangeFocus(GuiElement* e) +{ + if(parentElement) + return; // this is only intended for the main window + + for (u8 i = 0; i < _elements.size(); i++) + { + if(e == _elements.at(i)) + _elements.at(i)->SetFocus(1); + else if(_elements.at(i)->IsFocused() == 1) + _elements.at(i)->SetFocus(0); + } +} + +void GuiWindow::ToggleFocus(GuiTrigger * t) +{ + if(parentElement) + return; // this is only intended for the main window + + int found = -1; + int newfocus = -1; + u8 i; + + // look for currently in focus element + for (i = 0; i < _elements.size(); i++) + { + try + { + if(_elements.at(i)->IsFocused() == 1) + { + found = i; + break; + } + } + catch (const std::exception& e) { } + } + + // element with focus not found, try to give focus + if(found == -1) + { + for (i = 0; i < _elements.size(); i++) + { + try + { + if(_elements.at(i)->IsFocused() == 0 && _elements.at(i)->GetState() != STATE_DISABLED) // focus is possible (but not set) + { + _elements.at(i)->SetFocus(1); // give this element focus + break; + } + } + catch (const std::exception& e) { } + } + } + // change focus + else if(t->wpad.btns_d & (WPAD_BUTTON_1 | WPAD_BUTTON_B | WPAD_CLASSIC_BUTTON_B) + || t->pad.btns_d & PAD_BUTTON_B) + { + for (i = found; i < _elements.size(); i++) + { + try + { + if(_elements.at(i)->IsFocused() == 0 && _elements.at(i)->GetState() != STATE_DISABLED) // focus is possible (but not set) + { + newfocus = i; + _elements.at(i)->SetFocus(1); // give this element focus + _elements.at(found)->SetFocus(0); // disable focus on other element + break; + } + } + catch (const std::exception& e) { } + } + + if(newfocus == -1) + { + for (i = 0; i < found; i++) + { + try + { + if(_elements.at(i)->IsFocused() == 0 && _elements.at(i)->GetState() != STATE_DISABLED) // focus is possible (but not set) + { + _elements.at(i)->SetFocus(1); // give this element focus + _elements.at(found)->SetFocus(0); // disable focus on other element + break; + } + } + catch (const std::exception& e) { } + } + } + } +} + +int GuiWindow::GetSelected() +{ + // find selected element + int found = -1; + for (u8 i = 0; i < _elements.size(); i++) + { + try + { + if(_elements.at(i)->GetState() == STATE_SELECTED) + { + found = i; + break; + } + } + catch (const std::exception& e) { } + } + return found; +} + +// set element to left/right as selected +// there's probably a more clever way to do this, but this way works +void GuiWindow::MoveSelectionHor(int dir) +{ + int found = -1; + u16 left = 0; + u16 top = 0; + u8 i = 0; + + int selected = this->GetSelected(); + + if(selected >= 0) + { + left = _elements.at(selected)->GetLeft(); + top = _elements.at(selected)->GetTop(); + } + + // look for a button on the same row, to the left/right + for (i = 0; i < _elements.size(); i++) + { + try + { + if(_elements.at(i)->IsSelectable()) + { + if(_elements.at(i)->GetLeft()*dir > left*dir && _elements.at(i)->GetTop() == top) + { + if(found == -1) + found = i; + else if(_elements.at(i)->GetLeft()*dir < _elements.at(found)->GetLeft()*dir) + found = i; // this is a better match + } + } + } + catch (const std::exception& e) { } + } + if(found >= 0) + goto matchfound; + + // match still not found, let's try the first button in the next row + for (i = 0; i < _elements.size(); i++) + { + try + { + if(_elements.at(i)->IsSelectable()) + { + if(_elements.at(i)->GetTop()*dir > top*dir) + { + if(found == -1) + found = i; + else if(_elements.at(i)->GetTop()*dir < _elements.at(found)->GetTop()*dir) + found = i; // this is a better match + else if(_elements.at(i)->GetTop()*dir == _elements.at(found)->GetTop()*dir + && + _elements.at(i)->GetLeft()*dir < _elements.at(found)->GetLeft()*dir) + found = i; // this is a better match + } + } + } + catch (const std::exception& e) { } + } + + // match found + matchfound: + if(found >= 0) + { + _elements.at(found)->SetState(STATE_SELECTED); + if(selected >= 0) + _elements.at(selected)->ResetState(); + } +} + +void GuiWindow::MoveSelectionVert(int dir) +{ + int found = -1; + u16 left = 0; + u16 top = 0; + u8 i = 0; + + int selected = this->GetSelected(); + + if(selected >= 0) + { + left = _elements.at(selected)->GetLeft(); + top = _elements.at(selected)->GetTop(); + } + + // look for a button above/below, with the least horizontal difference + for (i = 0; i < _elements.size(); i++) + { + try + { + if(_elements.at(i)->IsSelectable()) + { + if(_elements.at(i)->GetTop()*dir > top*dir) + { + if(found == -1) + found = i; + else if(_elements.at(i)->GetTop()*dir < _elements.at(found)->GetTop()*dir) + found = i; // this is a better match + else if(_elements.at(i)->GetTop()*dir == _elements.at(found)->GetTop()*dir + && + abs(_elements.at(i)->GetLeft() - left) < + abs(_elements.at(found)->GetLeft() - left)) + found = i; + } + } + } + catch (const std::exception& e) { } + } + if(found >= 0) + goto matchfound; + + // match found + matchfound: + if(found >= 0) + { + _elements.at(found)->SetState(STATE_SELECTED); + if(selected >= 0) + _elements.at(selected)->ResetState(); + } +} + +void GuiWindow::Update(GuiTrigger * t) +{ + if(_elements.size() == 0 || (state == STATE_DISABLED && parentElement)) + return; + + for (u8 i = 0; i < _elements.size(); i++) + { + try { _elements.at(i)->Update(t); } + catch (const std::exception& e) { } + } + + this->ToggleFocus(t); + + if(focus) // only send actions to this window if it's in focus + { + // pad/joystick navigation + if(t->Right()) + this->MoveSelectionHor(1); + else if(t->Left()) + this->MoveSelectionHor(-1); + else if(t->Down()) + this->MoveSelectionVert(1); + else if(t->Up()) + this->MoveSelectionVert(-1); + } + + if(updateCB) + updateCB(this); +} diff --git a/src/platform/wii/menu.cpp b/src/platform/wii/menu.cpp new file mode 100644 index 0000000..2874b57 --- /dev/null +++ b/src/platform/wii/menu.cpp @@ -0,0 +1,591 @@ +/**************************************************************************** + * DOSBox Wii Home Menu + * Tantric 2009 + * + * menu.cpp + * + * Menu flow routines - handles all menu logic + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libwiigui/gui.h" +#include "wiihardware.h" + +#define THREAD_SLEEP 100 +#define APPVERSION "1.0" + +static GuiImageData * pointer[4]; +static GuiWindow * mainWindow = NULL; +static GuiButton * logoBtn = NULL; + +static lwp_t guithread = LWP_THREAD_NULL; +static lwp_t creditsthread = LWP_THREAD_NULL; +static bool guiHalt = true; +static bool ExitRequested = false; +static bool creditsOpen = false; + +/**************************************************************************** + * UpdateGUI + * + * Primary thread to allow GUI to respond to state changes, and draws GUI + ***************************************************************************/ +static void * UpdateGUI (void *arg) +{ + int i; + + while(1) + { + if(guiHalt) + break; + + UpdatePads(); + mainWindow->Draw(); + + for(i=3; i >= 0; i--) // so that player 1's cursor appears on top! + { + if(userInput[i].wpad.ir.valid) + Menu_DrawImg(userInput[i].wpad.ir.x-48, userInput[i].wpad.ir.y-48, + 96, 96, pointer[i]->GetImage(), userInput[i].wpad.ir.angle, 1, 1, 255); + DoRumble(i); + } + + Menu_Render(); + + for(i=3; i >= 0; i--) + mainWindow->Update(&userInput[i]); + + if(!creditsOpen && creditsthread != LWP_THREAD_NULL) + { + LWP_JoinThread(creditsthread, NULL); + creditsthread = LWP_THREAD_NULL; + } + + if(ExitRequested) + { + for(i = 0; i < 255; i += 15) + { + mainWindow->Draw(); + Menu_DrawRectangle(0,0,screenwidth,screenheight,(GXColor){0, 0, 0, i},1); + Menu_Render(); + } + ShutoffRumble(); + VIDEO_SetBlack(TRUE); + VIDEO_Flush(); + throw(0); + } + usleep(THREAD_SLEEP); + } + return NULL; +} + +/**************************************************************************** + * ResumeGui + * + * Signals the GUI thread to start, and resumes the thread. This is called + * after finishing the removal/insertion of new elements, and after initial + * GUI setup. + ***************************************************************************/ +static void +ResumeGui() +{ + guiHalt = false; + + if(guithread == LWP_THREAD_NULL) + LWP_CreateThread (&guithread, UpdateGUI, NULL, NULL, 0, 66); +} + +/**************************************************************************** + * HaltGui + * + * Signals the GUI thread to stop, and waits for GUI thread to stop + * This is necessary whenever removing/inserting new elements into the GUI. + * This eliminates the possibility that the GUI is in the middle of accessing + * an element that is being changed. + ***************************************************************************/ +static void +HaltGui() +{ + guiHalt = true; + + if(guithread == LWP_THREAD_NULL) + return; + + // wait for thread to finish + LWP_JoinThread(guithread, NULL); + guithread = LWP_THREAD_NULL; +} + +/**************************************************************************** + * OnScreenKeyboard + * + * Opens an on-screen keyboard window, with the data entered being stored + * into the specified variable. + ***************************************************************************/ +static void OnScreenKeyboard(char * var, u32 maxlen) +{ + int save = -1; + + GuiKeyboard keyboard(var, maxlen); + + GuiSound btnSoundOver(button_over_pcm, button_over_pcm_size, SOUND_PCM); + GuiSound btnSoundClick(button_click_pcm, button_click_pcm_size, SOUND_PCM); + GuiImageData btnOutline(button_png); + GuiImageData btnOutlineOver(button_over_png); + GuiTrigger trigA; + trigA.SetSimpleTrigger(-1, WPAD_BUTTON_A | WPAD_CLASSIC_BUTTON_A, PAD_BUTTON_A); + + GuiText okBtnTxt("OK", 24, (GXColor){0, 0, 0, 255}); + GuiImage okBtnImg(&btnOutline); + GuiImage okBtnImgOver(&btnOutlineOver); + GuiButton okBtn(btnOutline.GetWidth(), btnOutline.GetHeight()); + + okBtn.SetAlignment(ALIGN_LEFT, ALIGN_BOTTOM); + okBtn.SetPosition(25, -25); + + okBtn.SetLabel(&okBtnTxt); + okBtn.SetImage(&okBtnImg); + okBtn.SetImageOver(&okBtnImgOver); + okBtn.SetSoundOver(&btnSoundOver); + okBtn.SetSoundClick(&btnSoundClick); + okBtn.SetTrigger(&trigA); + okBtn.SetEffectGrow(); + + GuiText cancelBtnTxt("Cancel", 24, (GXColor){0, 0, 0, 255}); + GuiImage cancelBtnImg(&btnOutline); + GuiImage cancelBtnImgOver(&btnOutlineOver); + GuiButton cancelBtn(btnOutline.GetWidth(), btnOutline.GetHeight()); + cancelBtn.SetAlignment(ALIGN_RIGHT, ALIGN_BOTTOM); + cancelBtn.SetPosition(-25, -25); + cancelBtn.SetLabel(&cancelBtnTxt); + cancelBtn.SetImage(&cancelBtnImg); + cancelBtn.SetImageOver(&cancelBtnImgOver); + cancelBtn.SetSoundOver(&btnSoundOver); + cancelBtn.SetSoundClick(&btnSoundClick); + cancelBtn.SetTrigger(&trigA); + cancelBtn.SetEffectGrow(); + + keyboard.Append(&okBtn); + keyboard.Append(&cancelBtn); + + HaltGui(); + mainWindow->SetState(STATE_DISABLED); + mainWindow->Append(&keyboard); + mainWindow->ChangeFocus(&keyboard); + ResumeGui(); + + while(save == -1) + { + usleep(THREAD_SLEEP); + + if(okBtn.GetState() == STATE_CLICKED) + save = 1; + else if(cancelBtn.GetState() == STATE_CLICKED) + save = 0; + } + + if(save) + { + snprintf(var, maxlen, "%s", keyboard.kbtextstr); + } + + HaltGui(); + mainWindow->Remove(&keyboard); + mainWindow->SetState(STATE_DEFAULT); + ResumeGui(); +} + +/**************************************************************************** + * WindowCredits + * Display credits, legal copyright and licence + * + * THIS MUST NOT BE REMOVED OR DISABLED IN ANY DERIVATIVE WORK + ***************************************************************************/ +static void * WindowCredits(void *arg) +{ + bool exit = false; + int i = 0; + int y = 20; + + GuiWindow creditsWindow(528,408); + creditsWindow.SetAlignment(ALIGN_CENTRE, ALIGN_MIDDLE); + + GuiImageData creditsBox(credits_box_png); + GuiImage creditsBoxImg(&creditsBox); + creditsBoxImg.SetAlignment(ALIGN_CENTRE, ALIGN_MIDDLE); + creditsWindow.Append(&creditsBoxImg); + + int numEntries = 11; + GuiText * txt[numEntries]; + + txt[i] = new GuiText("Credits", 30, (GXColor){0, 0, 0, 255}); + txt[i]->SetAlignment(ALIGN_CENTRE, ALIGN_TOP); txt[i]->SetPosition(0,y); i++; y+=32; + + txt[i] = new GuiText("Official Site: http://code.google.com/p/dosbox-wii/", 20, (GXColor){0, 0, 0, 255}); + txt[i]->SetAlignment(ALIGN_CENTRE, ALIGN_TOP); txt[i]->SetPosition(0,y); i++; y+=40; + + txt[i]->SetPresets(20, (GXColor){0, 0, 0, 255}, 0, + FTGX_JUSTIFY_CENTER | FTGX_ALIGN_TOP, ALIGN_CENTRE, ALIGN_TOP); + + txt[i] = new GuiText("Porting & Menu Coding:"); + txt[i]->SetPosition(0,y); i++; y+=36; + txt[i] = new GuiText("Tantric"); + txt[i]->SetPosition(0,y); i++; y+=60; + + txt[i] = new GuiText("Thanks to:"); + txt[i]->SetPosition(0,y); i++; y+=36; + + txt[i] = new GuiText("DOSBox Team"); + txt[i]->SetPosition(0,y); i++; y+=22; + + txt[i] = new GuiText("shagkur & wintermute (libogc / devkitPPC)"); + txt[i]->SetPosition(0,y); i++; y+=22; + + txt[i] = new GuiText("Carl Kenner & Armin Tamzarian"); + txt[i]->SetPosition(0,y); i++; y+=60; + + txt[i]->SetPresets(18, (GXColor){0, 0, 0, 255}, 0, + FTGX_JUSTIFY_CENTER | FTGX_ALIGN_TOP, ALIGN_CENTRE, ALIGN_TOP); + + txt[i] = new GuiText("This software is open source and may be copied,"); + txt[i]->SetPosition(0,y); i++; y+=20; + txt[i] = new GuiText("distributed, or modified under the terms of the"); + txt[i]->SetPosition(0,y); i++; y+=20; + txt[i] = new GuiText("GNU General Public License (GPL) Version 2."); + txt[i]->SetPosition(0,y); i++; y+=20; + + for(i=0; i < numEntries; i++) + creditsWindow.Append(txt[i]); + + HaltGui(); + mainWindow->SetState(STATE_DISABLED); + mainWindow->Append(&creditsWindow); + mainWindow->ChangeFocus(&creditsWindow); + ResumeGui(); + + while(!exit) + { + for(i=0; i < 4; i++) + { + if(userInput[i].wpad.btns_d || userInput[i].pad.btns_d) + exit = true; + } + usleep(THREAD_SLEEP); + } + + HaltGui(); + mainWindow->Remove(&creditsWindow); + mainWindow->SetState(STATE_DEFAULT); + ResumeGui(); + + for(i=0; i < numEntries; i++) + delete txt[i]; + creditsOpen = false; + return NULL; +} + +static void DisplayCredits(void * ptr) +{ + if(logoBtn->GetState() != STATE_CLICKED) + return; + + logoBtn->ResetState(); + + // spawn a new thread to handle the Credits + creditsOpen = true; + if(creditsthread == LWP_THREAD_NULL) + LWP_CreateThread (&creditsthread, WindowCredits, NULL, NULL, 0, 60); +} + +/**************************************************************************** + * HomeMenu + ***************************************************************************/ +void HomeMenu () +{ + ResetVideo_Menu(); + + pointer[0] = new GuiImageData(player1_point_png); + pointer[1] = new GuiImageData(player2_point_png); + pointer[2] = new GuiImageData(player3_point_png); + pointer[3] = new GuiImageData(player4_point_png); + + mainWindow = new GuiWindow(screenwidth, screenheight); + + GuiImage screenImg(screenTex, screenwidth, screenheight); + screenImg.SetAlpha(192); + screenImg.ColorStripe(30); + + GuiTrigger trigA; + trigA.SetSimpleTrigger(-1, WPAD_BUTTON_A | WPAD_CLASSIC_BUTTON_A, PAD_BUTTON_A); + + GuiTrigger trigHome; + trigHome.SetButtonOnlyTrigger(-1, WPAD_BUTTON_HOME | WPAD_CLASSIC_BUTTON_HOME, 0); + + GuiSound btnSoundOver(button_over_pcm, button_over_pcm_size, SOUND_PCM); + GuiSound btnSoundClick(button_click_pcm, button_click_pcm_size, SOUND_PCM); + GuiSound enterSound(enter_ogg, enter_ogg_size, SOUND_OGG); + GuiSound exitSound(exit_ogg, exit_ogg_size, SOUND_OGG); + + GuiImageData btnLargeOutline(button_large_png); + GuiImageData btnLargeOutlineOver(button_large_over_png); + GuiImageData btnCloseOutline(button_small_png); + GuiImageData btnCloseOutlineOver(button_small_over_png); + + GuiImageData battery(battery_png); + GuiImageData batteryRed(battery_red_png); + GuiImageData batteryBar(battery_bar_png); + + GuiImageData bgTop(bg_top_png); + GuiImage bgTopImg(&bgTop); + GuiImageData bgBottom(bg_bottom_png); + GuiImage bgBottomImg(&bgBottom); + bgBottomImg.SetAlignment(ALIGN_LEFT, ALIGN_BOTTOM); + + GuiImageData logo(logo_png); + GuiImage logoImg(&logo); + GuiImageData logoOver(logo_over_png); + GuiImage logoImgOver(&logoOver); + GuiText logoTxt(APPVERSION, 18, (GXColor){255, 255, 255, 255}); + logoTxt.SetAlignment(ALIGN_RIGHT, ALIGN_TOP); + logoTxt.SetPosition(30, 31); + logoBtn = new GuiButton(logoImg.GetWidth(), logoImg.GetHeight()); + logoBtn->SetAlignment(ALIGN_RIGHT, ALIGN_BOTTOM); + logoBtn->SetPosition(-85, -40); + logoBtn->SetImage(&logoImg); + logoBtn->SetImageOver(&logoImgOver); + logoBtn->SetLabel(&logoTxt); + logoBtn->SetSoundOver(&btnSoundOver); + logoBtn->SetSoundClick(&btnSoundClick); + logoBtn->SetTrigger(&trigA); + logoBtn->SetUpdateCallback(DisplayCredits); + + GuiText exitBtnTxt("Exit", 24, (GXColor){0, 0, 0, 255}); + GuiImage exitBtnImg(&btnLargeOutline); + GuiImage exitBtnImgOver(&btnLargeOutlineOver); + GuiButton exitBtn(btnLargeOutline.GetWidth(), btnLargeOutline.GetHeight()); + exitBtn.SetAlignment(ALIGN_CENTRE, ALIGN_TOP); + exitBtn.SetPosition(-125, 120); + exitBtn.SetLabel(&exitBtnTxt); + exitBtn.SetImage(&exitBtnImg); + exitBtn.SetImageOver(&exitBtnImgOver); + exitBtn.SetSoundOver(&btnSoundOver); + exitBtn.SetSoundClick(&btnSoundClick); + exitBtn.SetTrigger(&trigA); + exitBtn.SetEffectGrow(); + + GuiText keyboardBtnTxt("Keyboard", 24, (GXColor){0, 0, 0, 255}); + GuiImage keyboardBtnImg(&btnLargeOutline); + GuiImage keyboardBtnImgOver(&btnLargeOutlineOver); + GuiButton keyboardBtn(btnLargeOutline.GetWidth(), btnLargeOutline.GetHeight()); + keyboardBtn.SetAlignment(ALIGN_CENTRE, ALIGN_TOP); + keyboardBtn.SetPosition(125, 120); + keyboardBtn.SetLabel(&keyboardBtnTxt); + keyboardBtn.SetImage(&keyboardBtnImg); + keyboardBtn.SetImageOver(&keyboardBtnImgOver); + keyboardBtn.SetSoundOver(&btnSoundOver); + keyboardBtn.SetSoundClick(&btnSoundClick); + keyboardBtn.SetTrigger(&trigA); + keyboardBtn.SetEffectGrow(); + + GuiText closeBtnTxt("Close", 22, (GXColor){0, 0, 0, 255}); + GuiImage closeBtnImg(&btnCloseOutline); + GuiImage closeBtnImgOver(&btnCloseOutlineOver); + GuiButton closeBtn(btnCloseOutline.GetWidth(), btnCloseOutline.GetHeight()); + closeBtn.SetAlignment(ALIGN_RIGHT, ALIGN_TOP); + closeBtn.SetPosition(-50, 35); + closeBtn.SetLabel(&closeBtnTxt); + closeBtn.SetImage(&closeBtnImg); + closeBtn.SetImageOver(&closeBtnImgOver); + closeBtn.SetSoundOver(&btnSoundOver); + closeBtn.SetSoundClick(&btnSoundClick); + closeBtn.SetTrigger(&trigA); + closeBtn.SetTrigger(&trigHome); + closeBtn.SetEffectGrow(); + + int i; + char txt[3]; + bool status[4] = { false, false, false, false }; + int level[4] = { 0, 0, 0, 0 }; + bool newStatus; + int newLevel; + GuiText * batteryTxt[4]; + GuiImage * batteryImg[4]; + GuiImage * batteryBarImg[4]; + GuiButton * batteryBtn[4]; + + for(i=0; i < 4; i++) + { + if(i == 0) + sprintf(txt, "P %d", i+1); + else + sprintf(txt, "P%d", i+1); + + batteryTxt[i] = new GuiText(txt, 22, (GXColor){255, 255, 255, 255}); + batteryTxt[i]->SetAlignment(ALIGN_LEFT, ALIGN_MIDDLE); + batteryImg[i] = new GuiImage(&battery); + batteryImg[i]->SetAlignment(ALIGN_LEFT, ALIGN_MIDDLE); + batteryImg[i]->SetPosition(30, 0); + batteryBarImg[i] = new GuiImage(&batteryBar); + batteryBarImg[i]->SetTile(0); + batteryBarImg[i]->SetAlignment(ALIGN_LEFT, ALIGN_MIDDLE); + batteryBarImg[i]->SetPosition(34, 0); + + batteryBtn[i] = new GuiButton(70, 20); + batteryBtn[i]->SetLabel(batteryTxt[i]); + batteryBtn[i]->SetImage(batteryImg[i]); + batteryBtn[i]->SetIcon(batteryBarImg[i]); + batteryBtn[i]->SetAlignment(ALIGN_LEFT, ALIGN_BOTTOM); + batteryBtn[i]->SetRumble(false); + batteryBtn[i]->SetSelectable(false); + batteryBtn[i]->SetAlpha(150); + } + + batteryBtn[0]->SetPosition(45, -65); + batteryBtn[1]->SetPosition(135, -65); + batteryBtn[2]->SetPosition(45, -40); + batteryBtn[3]->SetPosition(135, -40); + + GuiWindow w(screenwidth, screenheight); + + w.Append(&bgTopImg); + w.Append(&bgBottomImg); + w.Append(batteryBtn[0]); + w.Append(batteryBtn[1]); + w.Append(batteryBtn[2]); + w.Append(batteryBtn[3]); + w.Append(logoBtn); + w.Append(&closeBtn); + w.Append(&exitBtn); + w.Append(&keyboardBtn); + + mainWindow->Append(&screenImg); + mainWindow->Append(&w); + + enterSound.Play(); + bgTopImg.SetEffect(EFFECT_SLIDE_TOP | EFFECT_SLIDE_IN, 35); + closeBtn.SetEffect(EFFECT_SLIDE_TOP | EFFECT_SLIDE_IN, 35); + bgBottomImg.SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_IN, 35); + logoBtn->SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_IN, 35); + + batteryBtn[0]->SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_IN, 35); + batteryBtn[1]->SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_IN, 35); + batteryBtn[2]->SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_IN, 35); + batteryBtn[3]->SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_IN, 35); + + w.SetEffect(EFFECT_FADE, 15); + + ResumeGui(); + + while(1) + { + usleep(THREAD_SLEEP); + + for(i=0; i < 4; i++) + { + if(WPAD_Probe(i, NULL) == WPAD_ERR_NONE) + { + newStatus = true; + newLevel = (userInput[i].wpad.battery_level / 100.0) * 4; + if(newLevel > 4) newLevel = 4; + } + else + { + newStatus = false; + newLevel = 0; + } + + if(status[i] != newStatus || level[i] != newLevel) + { + if(newStatus == true) // controller connected + { + batteryBtn[i]->SetAlpha(255); + batteryBarImg[i]->SetTile(newLevel); + + if(newLevel == 0) + batteryImg[i]->SetImage(&batteryRed); + else + batteryImg[i]->SetImage(&battery); + } + else // controller not connected + { + batteryBtn[i]->SetAlpha(150); + batteryBarImg[i]->SetTile(0); + batteryImg[i]->SetImage(&battery); + } + status[i] = newStatus; + level[i] = newLevel; + } + } + + if(closeBtn.GetState() == STATE_CLICKED) + { + exitSound.Play(); + bgTopImg.SetEffect(EFFECT_SLIDE_TOP | EFFECT_SLIDE_OUT, 15); + closeBtn.SetEffect(EFFECT_SLIDE_TOP | EFFECT_SLIDE_OUT, 15); + bgBottomImg.SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_OUT, 15); + logoBtn->SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_OUT, 15); + + batteryBtn[0]->SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_OUT, 15); + batteryBtn[1]->SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_OUT, 15); + batteryBtn[2]->SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_OUT, 15); + batteryBtn[3]->SetEffect(EFFECT_SLIDE_BOTTOM | EFFECT_SLIDE_OUT, 15); + + w.SetEffect(EFFECT_FADE, -15); + usleep(450000); // wait for effects to finish + + break; + } + else if(exitBtn.GetState() == STATE_CLICKED) + { + ExitRequested = 1; + } + else if(keyboardBtn.GetState() == STATE_CLICKED) + { + keyboardBtn.ResetState(); + OnScreenKeyboard(dosboxCommand, MAXPATHLEN); + + if(dosboxCommand[0] != 0) + break; + } + } + + ShutoffRumble(); + + // wait for keys to be depressed + while(MenuRequested()) + usleep(THREAD_SLEEP); + + exitSound.Stop(); + + HaltGui(); + + for(i=0; i < 4; i++) + { + delete batteryTxt[i]; + delete batteryImg[i]; + delete batteryBarImg[i]; + delete batteryBtn[i]; + } + + delete mainWindow; + delete logoBtn; + delete pointer[0]; + delete pointer[1]; + delete pointer[2]; + delete pointer[3]; + + mainWindow = NULL; + + if(screenTex) + { + free(screenTex); + screenTex = NULL; + } +} diff --git a/src/platform/wii/oggplayer.c b/src/platform/wii/oggplayer.c new file mode 100644 index 0000000..689160d --- /dev/null +++ b/src/platform/wii/oggplayer.c @@ -0,0 +1,539 @@ +/* + Copyright (c) 2008 Francisco Muñoz 'Hermes' + All rights reserved. + + Proper (standard) vorbis usage by Tantric, 2009 + Threading modifications/corrections by Tantric, 2009 + + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other + materials provided with the distribution. + - The names of the contributors may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NO_SOUND + +#include "oggplayer.h" +#include +#include +#include + +/* functions to read the Ogg file from memory */ + +static struct +{ + char *mem; + int size; + int pos; +} file[4]; + +static int f_read(void * punt, int bytes, int blocks, int *f) +{ + int b; + int c; + int d; + + if (bytes * blocks <= 0) + return 0; + + blocks = bytes * blocks; + c = 0; + + while (blocks > 0) + { + b = blocks; + if (b > 4096) + b = 4096; + + if (*f >= 0x666 && *f <= 0x669) + { + d = (*f) - 0x666; + if (file[d].size == 0) + return -1; + if ((file[d].pos + b) > file[d].size) + b = file[d].size - file[d].pos; + if (b > 0) + { + memcpy(punt, file[d].mem + file[d].pos, b); + file[d].pos += b; + } + } + else + b = read(*f, ((char *) punt) + c, b); + + if (b <= 0) + { + return c / bytes; + } + c += b; + blocks -= b; + } + return c / bytes; +} + +static int f_seek(int *f, ogg_int64_t offset, int mode) +{ + if(f==NULL) return(-1); + + int k, d; + mode &= 3; + if (*f >= 0x666 && *f <= 0x669) + { + d = (*f) - 0x666; + k = 0; + + if (file[d].size == 0) + return -1; + + if (mode == 0) + { + if ((offset) >= file[d].size) + { + file[d].pos = file[d].size; + k = -1; + } + else if ((offset) < 0) + { + file[d].pos = 0; + k = -1; + } + else + file[d].pos = offset; + } + if (mode == 1) + { + if ((file[d].pos + offset) >= file[d].size) + { + file[d].pos = file[d].size; + k = -1; + } + else if ((file[d].pos + offset) < 0) + { + file[d].pos = 0; + k = -1; + } + else + file[d].pos += offset; + } + if (mode == 2) + { + + if ((file[d].size + offset) >= file[d].size) + { + file[d].pos = file[d].size; + k = -1; + } + else if ((file[d].size + offset) < 0) + { + file[d].pos = 0; + k = -1; + } + else + file[d].pos = file[d].size + offset; + } + + } + else + k = lseek(*f, (int) offset, mode); + + if (k < 0) + k = -1; + else + k = 0; + return k; +} + +static int f_close(int *f) +{ + int d; + if (*f >= 0x666 && *f <= 0x669) + { + d = (*f) - 0x666; + file[d].size = 0; + file[d].pos = 0; + if (file[d].mem) + { + file[d].mem = (void *) 0; + } + return 0; + } + else + return close(*f); + return 0; +} + +static long f_tell(int *f) +{ + int k, d; + + if (*f >= 0x666 && *f <= 0x669) + { + d = (*f) - 0x666; + k = file[d].pos; + } + else + k = lseek(*f, 0, 1); + + return (long) k; +} + +static int mem_open(char * ogg, int size) +{ + static int one = 1; + int n; + if (one) + { + one = 0; + for (n = 0; n < 4; n++) + file[n].size = 0; + } + + for (n = 0; n < 4; n++) + { + if (file[n].size == 0) + { + file[n].mem = ogg; + file[n].size = size; + file[n].pos = 0; + return (0x666 + n); + } + } + return -1; +} + +static int mem_close(int fd) +{ + if (fd >= 0x666 && fd <= 0x669) // it is a memory file descriptor? + { + fd -= 0x666; + file[fd].size = 0; + return 0; + } + else + return f_close(&fd); +} + +static ov_callbacks callbacks = { + (size_t (*)(void *, size_t, size_t, void *)) f_read, + (int (*)(void *, ogg_int64_t, int)) f_seek, + (int (*)(void *)) f_close, + (long (*)(void *)) f_tell +}; + +/* OGG control */ + +#define READ_SAMPLES 4096 // samples that it must read before to send +#define MAX_PCMOUT 4096 // minimum size to read ogg samples +typedef struct +{ + OggVorbis_File vf; + vorbis_info *vi; + int current_section; + + // OGG file operation + int fd; + int mode; + int eof; + int flag; + int volume; + int seek_time; + + /* OGG buffer control */ + short pcmout[2][READ_SAMPLES + MAX_PCMOUT * 2]; /* take 4k out of the data segment, not the stack */ + int pcmout_pos; + int pcm_indx; + +} private_data_ogg; + +static private_data_ogg private_ogg; + +// OGG thread control + +#define STACKSIZE 8192 + +static u8 oggplayer_stack[STACKSIZE]; +static lwpq_t oggplayer_queue = LWP_TQUEUE_NULL; +static lwp_t h_oggplayer = LWP_THREAD_NULL; +static int ogg_thread_running = 0; + +static void ogg_add_callback(int voice) +{ + if (!ogg_thread_running) + { + ASND_StopVoice(0); + return; + } + + if (private_ogg.flag & 128) + return; // Ogg is paused + + if (private_ogg.pcm_indx >= READ_SAMPLES) + { + if (ASND_AddVoice(0, + (void *) private_ogg.pcmout[private_ogg.pcmout_pos], + private_ogg.pcm_indx << 1) == 0) + { + private_ogg.pcmout_pos ^= 1; + private_ogg.pcm_indx = 0; + private_ogg.flag = 0; + LWP_ThreadSignal(oggplayer_queue); + } + } + else + { + if (private_ogg.flag & 64) + { + private_ogg.flag &= ~64; + LWP_ThreadSignal(oggplayer_queue); + } + } +} + +static void * ogg_player_thread(private_data_ogg * priv) +{ + int first_time = 1; + long ret; + + //init + LWP_InitQueue(&oggplayer_queue); + + priv[0].vi = ov_info(&priv[0].vf, -1); + + ASND_Pause(0); + + priv[0].pcm_indx = 0; + priv[0].pcmout_pos = 0; + priv[0].eof = 0; + priv[0].flag = 0; + priv[0].current_section = 0; + + ogg_thread_running = 1; + + while (!priv[0].eof && ogg_thread_running) + { + if (priv[0].flag) + LWP_ThreadSleep(oggplayer_queue); // wait only when i have samples to send + + if (priv[0].flag == 0) // wait to all samples are sent + { + if (ASND_TestPointer(0, priv[0].pcmout[priv[0].pcmout_pos]) + && ASND_StatusVoice(0) != SND_UNUSED) + { + priv[0].flag |= 64; + continue; + } + if (priv[0].pcm_indx < READ_SAMPLES) + { + priv[0].flag = 3; + + if (priv[0].seek_time >= 0) + { + ov_time_seek(&priv[0].vf, priv[0].seek_time); + priv[0].seek_time = -1; + } + + ret + = ov_read( + &priv[0].vf, + (void *) &priv[0].pcmout[priv[0].pcmout_pos][priv[0].pcm_indx], + MAX_PCMOUT,/*0,2,1,*/&priv[0].current_section); + priv[0].flag &= 192; + if (ret == 0) + { + /* EOF */ + if (priv[0].mode & 1) + ov_time_seek(&priv[0].vf, 0); // repeat + else + priv[0].eof = 1; // stops + } + else if (ret < 0) + { + /* error in the stream. Not a problem, just reporting it in + case we (the app) cares. In this case, we don't. */ + if (ret != OV_HOLE) + { + if (priv[0].mode & 1) + ov_time_seek(&priv[0].vf, 0); // repeat + else + priv[0].eof = 1; // stops + } + } + else + { + /* we don't bother dealing with sample rate changes, etc, but + you'll have to*/ + priv[0].pcm_indx += ret >> 1; //get 16 bits samples + } + } + else + priv[0].flag = 1; + } + + if (priv[0].flag == 1) + { + if (ASND_StatusVoice(0) == SND_UNUSED || first_time) + { + first_time = 0; + if (priv[0].vi->channels == 2) + { + ASND_SetVoice(0, VOICE_STEREO_16BIT, priv[0].vi->rate, 0, + (void *) priv[0].pcmout[priv[0].pcmout_pos], + priv[0].pcm_indx << 1, priv[0].volume, + priv[0].volume, ogg_add_callback); + priv[0].pcmout_pos ^= 1; + priv[0].pcm_indx = 0; + priv[0].flag = 0; + } + else + { + ASND_SetVoice(0, VOICE_MONO_16BIT, priv[0].vi->rate, 0, + (void *) priv[0].pcmout[priv[0].pcmout_pos], + priv[0].pcm_indx << 1, priv[0].volume, + priv[0].volume, ogg_add_callback); + priv[0].pcmout_pos ^= 1; + priv[0].pcm_indx = 0; + priv[0].flag = 0; + } + } + } + usleep(100); + } + ov_clear(&priv[0].vf); + priv[0].fd = -1; + priv[0].pcm_indx = 0; + + return 0; +} + +void StopOgg() +{ + ASND_StopVoice(0); + ogg_thread_running = 0; + + if(h_oggplayer != LWP_THREAD_NULL) + { + if(oggplayer_queue != LWP_TQUEUE_NULL) + LWP_ThreadSignal(oggplayer_queue); + LWP_JoinThread(h_oggplayer, NULL); + h_oggplayer = LWP_THREAD_NULL; + } + if(oggplayer_queue != LWP_TQUEUE_NULL) + { + LWP_CloseQueue(oggplayer_queue); + oggplayer_queue = LWP_TQUEUE_NULL; + } +} + +int PlayOgg(char * buf, int buflen, int time_pos, int mode) +{ + StopOgg(); + + private_ogg.fd = mem_open(buf, buflen); + + if (private_ogg.fd < 0) + { + private_ogg.fd = -1; + return -1; + } + + private_ogg.mode = mode; + private_ogg.eof = 0; + private_ogg.volume = 127; + private_ogg.flag = 0; + private_ogg.seek_time = -1; + + if (time_pos > 0) + private_ogg.seek_time = time_pos; + + if (ov_open_callbacks((void *) &private_ogg.fd, &private_ogg.vf, NULL, 0, callbacks) < 0) + { + mem_close(private_ogg.fd); // mem_close() can too close files from devices + private_ogg.fd = -1; + ogg_thread_running = 0; + return -1; + } + + if (LWP_CreateThread(&h_oggplayer, (void *) ogg_player_thread, + &private_ogg, oggplayer_stack, STACKSIZE, 80) == -1) + { + ogg_thread_running = 0; + ov_clear(&private_ogg.vf); + private_ogg.fd = -1; + return -1; + } + return 0; +} + +void PauseOgg(int pause) +{ + if (pause) + { + private_ogg.flag |= 128; + } + else + { + if (private_ogg.flag & 128) + { + private_ogg.flag |= 64; + private_ogg.flag &= ~128; + if (ogg_thread_running > 0) + { + LWP_ThreadSignal(oggplayer_queue); + } + } + } +} + +int StatusOgg() +{ + if (ogg_thread_running == 0) + return -1; // Error + else if (private_ogg.eof) + return 255; // EOF + else if (private_ogg.flag & 128) + return 2; // paused + else + return 1; // running +} + +void SetVolumeOgg(int volume) +{ + private_ogg.volume = volume; + ASND_ChangeVolumeVoice(0, volume, volume); +} + +s32 GetTimeOgg() +{ + int ret; + if (ogg_thread_running == 0 || private_ogg.fd < 0) + return 0; + ret = ((s32) ov_time_tell(&private_ogg.vf)); + if (ret < 0) + ret = 0; + + return ret; +} + +void SetTimeOgg(s32 time_pos) +{ + if (time_pos >= 0) + private_ogg.seek_time = time_pos; +} + +#endif diff --git a/src/platform/wii/oggplayer.h b/src/platform/wii/oggplayer.h new file mode 100644 index 0000000..58b29af --- /dev/null +++ b/src/platform/wii/oggplayer.h @@ -0,0 +1,177 @@ +/* + Copyright (c) 2008 Francisco Muñoz 'Hermes' + All rights reserved. + + Proper (standard) vorbis usage by Tantric, 2009 + Threading modifications/corrections by Tantric, 2009 + + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other + materials provided with the distribution. + - The names of the contributors may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NO_SOUND + +#ifndef __OGGPLAYER_H__ +#define __OGGPLAYER_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define OGG_ONE_TIME 0 +#define OGG_INFINITE_TIME 1 + +#define OGG_STATUS_RUNNING 1 +#define OGG_STATUS_ERR -1 +#define OGG_STATUS_PAUSED 2 +#define OGG_STATUS_EOF 255 + +/*------------------------------------------------------------------------------------------------------------------------------------------------------*/ +/* Player OGG functions */ +/*------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/* int PlayOgg(int fd, int time_pos, int mode); + + Play an Ogg file. This file can be loaded from memory (mem_open(void *ogg, int size_ogg)) or from device with open("device:file.ogg",O_RDONLY,0); + + NOTE: The file is closed by the player when you call PlayOgg(), StopOgg() or if it fail. + + -- Params --- + + fd: file descriptor from open() or mem_open() + + time_pos: initial time position in the file (in milliseconds). For example, use 30000 to advance 30 seconds + + mode: Use OGG_ONE_TIME or OGG_INFINITE_TIME. When you use OGG_ONE_TIME the sound stops and StatusOgg() return OGG_STATUS_EOF + + return: 0- Ok, -1 Error + + */ + +int PlayOgg(char * buf, int buflen, int time_pos, int mode); + +/*------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/* void StopOgg(); + + Stop an Ogg file. + + NOTE: The file is closed and the player thread is released + + -- Params --- + + + */ + +void StopOgg(); + +/*------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/* void PauseOgg(int pause); + + Pause an Ogg file. + + -- Params --- + + pause: 0 -> continue, 1-> pause + + */ + +void PauseOgg(int pause); + +/*------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/* int StatusOgg(); + + Return the Ogg status + + -- Params --- + + + return: OGG_STATUS_RUNNING + OGG_STATUS_ERR -> not initialized? + OGG_STATUS_PAUSED + OGG_STATUS_EOF -> player stopped by End Of File + + */ + +int StatusOgg(); + +/*------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/* void SetVolumeOgg(int volume); + + Set the Ogg playing volume. + NOTE: it change the volume of voice 0 (used for the Ogg player) + + -- Params --- + + volume: 0 to 255 (max) + + */ + +void SetVolumeOgg(int volume); + +/*------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/* s32 GetTimeOgg(); + + Return the Ogg time from the starts of the file + + -- Params --- + + return: 0 -> Ok or error condition (you must ignore this value) + >0 -> time in milliseconds from the starts + + */ + +s32 GetTimeOgg(); + +/*------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/* void SetTimeOgg(s32 time_pos); + + Set the time position + + NOTE: The file is closed by the player when you call PlayOgg(), StopOgg() or if it fail. + + -- Params --- + + time_pos: time position in the file (in milliseconds). For example, use 30000 to advance 30 seconds + + */ + +void SetTimeOgg(s32 time_pos); + +/*------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/src/platform/wii/pngu.c b/src/platform/wii/pngu.c new file mode 100644 index 0000000..8f554ed --- /dev/null +++ b/src/platform/wii/pngu.c @@ -0,0 +1,1266 @@ +/******************************************************************************************** + +PNGU Version : 0.2a + +Coder : frontier + +More info : http://frontier-dev.net + +Modified by Tantric, 2009 + +********************************************************************************************/ +#include +#include +#include "pngu.h" +#include "png.h" + + +// 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; +} + +int PNGU_EncodeFromRGB (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride) +{ + png_uint_32 rowbytes; + PNGU_u32 y; + + // 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); + memset(ctx->img_data, 0, 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); + memset(ctx->row_pointers, 0, 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; + } + + for (y = 0; y < height; y++) + { + ctx->row_pointers[y] = buffer + (y * rowbytes); + } + + // 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 ctx->cursor; +} + +int PNGU_EncodeFromGXTexture (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride) +{ + int x,y,res; + unsigned char * ptr = (unsigned char*)buffer; + unsigned char * tmpbuffer = (unsigned char *)malloc(width*height*3); + memset(tmpbuffer, 0, width*height*3); + png_uint_32 offset; + + for(y=0; y < height; y++) + { + for(x=0; x < width; x++) + { + offset = (((y >> 2)<<4)*width) + ((x >> 2)<<6) + (((y%4 << 2) + x%4 ) << 1); + + tmpbuffer[y*640*3+x*3] = ptr[offset+1]; // R + tmpbuffer[y*640*3+x*3+1] = ptr[offset+32]; // G + tmpbuffer[y*640*3+x*3+2] = ptr[offset+33]; // B + } + } + + res = PNGU_EncodeFromRGB (ctx, width, height, tmpbuffer, stride); + free(tmpbuffer); + return res; +} + +// 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/src/platform/wii/pngu.h b/src/platform/wii/pngu.h new file mode 100644 index 0000000..064cd80 --- /dev/null +++ b/src/platform/wii/pngu.h @@ -0,0 +1,176 @@ +/******************************************************************************************** + +PNGU Version : 0.2a + +Coder : frontier + +More info : http://frontier-dev.net + +Modified by Tantric, 2009 + +********************************************************************************************/ +#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); + +int PNGU_EncodeFromRGB (IMGCTX ctx, PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride); +int PNGU_EncodeFromGXTexture (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/src/platform/wii/sounds/button_click.pcm b/src/platform/wii/sounds/button_click.pcm new file mode 100644 index 0000000000000000000000000000000000000000..de3d55e3bfe93b57942bea05e10832a4faec5a5b GIT binary patch literal 7204 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 MaWFJxUR0I;11V@^NB{r; literal 0 HcmV?d00001 diff --git a/src/platform/wii/sounds/enter.ogg b/src/platform/wii/sounds/enter.ogg new file mode 100644 index 0000000000000000000000000000000000000000..3f74df8eb31778d1644df9fbb6febeb520422f6d GIT binary patch literal 59779 zcmce-by!_Jw=X&u?(R^mP~5FJ#i2m);!@n*ZJ`BlbxNHor{Y@pOXHav5T20rGq`Cv4gssCgnTscYh!AaBy(&=`%d_1%dykB_?qR zO$dMl=5Q!b_CY7(Gynh#0E}oDFcQt>7z&f}=)KaCWS?Sv>=9{65q(4^Q5^mM2`ITu z@c{r1fYKw!6#SC29_P0pqKk3P<+oCLSxku-sq&p4x##kQiG5LBk&XR011%D|HatiL zKune@6i@Xjco$9;Mg#`$$(#qNiZh*27)x@4P&fxEgB3V;KP4&&?3NV72<*|zlOdm)B>xA0K5R%>ISgR>L=sBm1Zswa0=Xh{>q@JS z4VIr9>_%#sM{3ai76Ew-_cR(H*D9mv|K-iKQ_TMFUd(ck5fB4uIp9P#;6x*(N;BZh zjQE$rg8;}=F;(V1XKoo6?g1Ae(95_=4kam!T_|AwhY3$?2LLe+vOXuWA&?rXEHlnL zZ(W4vT@>d*Qan-Q|Lg+);su-tRhD^*H57>}&iF4`pkT8^c}Nmy|MUbF2+#XTnK7Ah zKE{ucv6Wkj_i?5>Lj5CMb7|VgzZ|q3C*A~k$uOMWmzzGB;W93VmQnam%nRouVnyjR zm`A*wiP2AN43@K+&Oj_()B@{jPiFioX~qL%5lJ8E>YLU7Y42ZHlp17?ck^$6H6oTi z+B75UgR$SHnMY&DPkH}4e8fP$87~m^X9ZFE#UQ-Hqbj0GBzKL)NX+FJWcWlL9V0DY z8mz2JOjqa{^mpcz01$%p7sdYx{zCa56n{#HWE`fi9pxNhehSKa#|8GQda)$XI6xFL z^MELhuK$(oQpqoCQ&>Nz!&I20D$h{(k3@k?r8JFug+TmQNW#*bhZ%}Nsra9aJE57t zem3<#{KP4l3Jxvkr#M;ExP&#-HQu`0X{Wj^H-_pjxUJ9ot|JDDO z)i5ib|1(G7DUhJ9QiT7L0RYeyhxYpKI-;V=GONln ztIDFGF8qI%81U&Vx8e*pNZ4cmAO--5>R);xI0??m@*{HE@(kN01X~QIc!>+agLtZF zI!Z+*@(c(ZjW_~DXbEH{dr6!@+LP_BF$|VNNa^y(r3m1%2M4GC0D>H~9F5#GcfQO8 zv*Koi5H^&?TPSv0E_?9YlN^w{@Fbr|6pG(I6$1Ab2nP5g5YMAxf`Pt-03T?)$mPz2 z5`KDdE8 zRCCiW+Ivb;ErViD^F~cE!#sm~j5{LVN}Pvwj7#Icl0DqQYOeZ4yJZS9Zq1bk;_kvz zV6wDDK@ChY_a=j5)5Dg(IaFYNeB5QTCtYOo&xv(M}6kH@)byv%YrNfp`Ay zKgqqPBoizEOe(0QMC9+|NX?sNRHDw${;x@VjoFm+q61aM#jTbaRBZwcjfV6xCN2%_ zUoDkr+6dt2|2k+%S;LsRS-SNw=WMdsU?rGT51^nN;T(Nw#`55j#4Rt!K$|2hFI)O4Nlvb!evXr&6kM=q3+ps> zMm~L-)3HfU`le%30RUC7h~N)3@9(_G0U#(&HHv?mj7OHCKhq_K`V*eXIKL&fY9z-t zW!)TOUz!V+s(kXhQT}cELV1o~nJ!7H{b}H`4EFg{4O|fWiCkj9Rna7hpAG;@x*z~$ zmvsCXvUCs%x;8TK2?ih{8>snb+AJXYCvc@^)HPlw{7Z;raA&0lx7RX8P~t#pfzQeq zBa2F(h^6=TFIDE<{+lWg;5#w~AO_D{9^#ze zOrl=V7uFLM$Y%w|soHQzCkr4_70$hjQq546muKlGD&#IqN_tn$0nSyvqEE{vX?iY6 z=R@@zeW5F7zoyN^82`3uRCRwQDj;U*2mpdr#%>{r`PE$@yI=q>06 z=fD`KY1&kPvxo+DPCc8_dMz78d4??)TaJDm8xRUqrXNb5$iVL92tehloWhbn)%Hw% z|4WBv2!JO3G}C{#cDX_#Fb3{wPx@feUxban`kSOD{_h7N%JRFJE>A_C%H<_Z^CSLK z157d$y8K%Me5xw{Z&Od@KydzB11t;z_MblfcBCqg{9g^w7bf96l~aDo0|`=?^tbNc zj=bmxRR~!3Z|o^RqJ1j-ZxTlwnd;wiPtAkX{!>mI zIq#p2zy@f+mXY&7eu8od1)ejqd{yGA6oQe#<5Um}4J(HO#KHAQ0vzK%J@yj)CuaR5 zeE-$i|9>&Y|Bu~5pi{&c&?#K$Njd3csRhfPP{e;0QyuW^f~E#yfb1{mREPO@$P#1Z zUxSFE+9C@Xmy-p7Xo8j<403?BZd%8d17Ye35~?~N8i|6Vo`@>X(nnx2_NsqUjvj=h z^=S1k$`JZI5T-U9a~U8|TqjkmE9yyXD)^B$tmzr*HBHL-!T#b(UGd(1;ON(~K?h-^ zBhN9HW-}@bVoABwZNVCicU}+dbZ14!rg|>JrV8xT*O8@x$Oc?bDM9cokmyG7&!;_& zi^?>bqdx;w44>d8GC*Qd(L6`~gs*jz=;}E=7Z!k$uLlA+b<2wHG55+pw-AXq>QihnT7T(LxNzemG%OL>C)teTkEnE{`RANxWUy|rgejJb@;h5%PFa3SjxZFeuavWzoS{+g5_C|WNyy4Y zRD>uo{QT5Er;41ONMIAq5~>VBi4qB5$D-m26ocRq4FXWu5I6=Px2kKBLk|w_Ck`hH z8p=S3%d{0NfE6-`79XyKhJ#xcnEy@y(9$y+M1-P6N7sEo-Qnzl*I#j?455Uh3h#s9 z#X*B{?LB{>ziH}57LY2$0Cz3W!2sa?l7LQ0osrHTgAt1vhZT>VfRl)ugoppniVg?G z1fZty;o;`yF$KMwur!jUA)mhfgrk+B1s89zzm33QL7~vUv8OZ?`p;2ZjO1??@N^V^ zI?+dq_&5<7KR%*AKH@w+vOhkuJU)s(K1x153O%;bsBDt%{ZLVby%(~-1P|ur|q#Xy&m-0$OLmLw>K}iOQ2H{ygG>KWv zG$EJhs`irzlc|MEZ$5?$$2q<#`%iLT%*y-2jjYdp&+pZ&-aUU_TN(80TemuoLFOjI z8$t;@bvsT}eZS4NoQESv(XOk9HUCwFkZTUF`P@5*Lgf0;P`|vbHZ+XvInu>5)IB%E6}75qmD3(cuc<34 zCx=PHZ&2oljY$ou6bMl>2y9Lxe8UfqFo5rowq0i+#Z6>SblE69h4Okbv7RtFPZi@5zpaC3fC>HKs zcM2ITauGIlHpgKjn-d+01qfX7#s`SZ#B&NA7s*J6mA?HTkvkh;KMvo66GYWjreI^u z?2j3IN+G}Fs%vyD!w2owUO zp>~BrM7m~SQsF0k^b+IpZlDWa%dN{n1pIV<&4>-hfg2J>6JnXrT_yOO;x+mUNl(9D zv;=I~j1Ko`r*A9Xb?bfN?;nUhOMQ(E5NJhQS|}DA3he(hzS-h@u_^zpJ>}jy^(@M_ zh_YA6(&57EWE7=kK4;Z(^8FF4!+UfQcl~xwi&@ODj{4uB!{nXvBIHT5zVIBV)YRx2+FJ9&tXX<)-s53m z77wxQdZ=MkY@i_fynNi=nYFue7|ycZd%HXpBZPZ;F72mI_F+Ps%yVww!mX-T%2g)o z>)Nu$vM>qI$nidn$4o;p2Nr_%ira?#mCanehZGEehw=-`g9V?J-4~~W0i%RnEGmO% zC_RS^5HNm@w6^1r1As=nC}qr33uI`z;X}+rCAqB8n8*2S;-_0XZ$rSXjDYo6CLG2C zd;9RMn%~AB|AB2TsG8y6g>8pVm3ec;;o9$y;Yv`+Y3WMrkgw&>!`h+6X&9J~@yy;% zSc-q5NTKiqYpxsYHdLL720p)xI9yEC9YXUfGm~fyBju1peaC{II1Krm+WJH`- zzqT5Go;mk4|4G6bKC>Z}WCWxKB|_L0%Z+Qq@Qae+$kZD*IrrW%&9|aQC5ANhIg~LFbZAVQNok^@Hj5W2^xtvuO1F z^J&a;-$cJ`#MDK(BHE#V)_x_JNFkT>IoQ^gH~Utz-{0ft?3CrOr`a%^(jH^3bhPCg zB1&X^;p6sbNZ`#i`Zx9aY&}}Bn^+YEE^^xVN3(MHNFSLO#v!Y1a64wqhIvCSz)4?0)LIBf!#3 zx2_I?JcXa8-zia_bRFL0n;EV38wUJwX{@0+U0 z*ydCwEG#J7yV2Z7Y1X{?js5dMvo`yWFezG^q5pk8zv4}F=QaTpKkhjIJ@hNGwc(^& zAGN21v47dwfgyxNtVj?HSw|Fk;E57Vh6nqf}(!0pkP&GoanXI&rQE%Yn?7zbBU&5?ZR01AL_M*IljbczL2xkWp<=7wLQ zEBC9o=e19~dFJ}i0A4hps|QUFkpu}wzT`D{P6}B`ol3c`&6*({Cj<7&IL(%jx`ORUh3g`#bjJI89cvioHk z6W#?|cY9Zc_ZZ9c2mTKX7M2C#4B)6|*C!5eHdJ$oyoHzlIi6 zpRIP#aeYf7eLIji@Dp}j?1z=?q!S489Zmon~yi&*zbolCFIc?E^=uk!&-eFl|LHk;~!xOE&J#$#60+c9liJ4 z8E@=WJ27+PvLR=$%RQWtf2Q@>FA3Y?bs3&)4#t=f@`1NFf&Je`9*)&RU}B|!bRRkz zl{UhP*MGY9gJ~v(?{>ABI=z0?GIy!agaJLH?Q9A4)jEwV530r}r<+gIM`i}LTu{V-U zuPlI$m!{;St$e&*OeO@P1npE7TqL?qaU?g&cv3IowKc8tl?v&f(|%&UGlgcJLlIFz zYIAG3xi@DJ7jEU>nm~2+^bi1L1e6;Tv^`XcxVJ`njr}?%f&E7V&o3AuZ2&g_3n-gG zh?sCmNCU;uL5g$7a>yYuj&T+ceaYsOGQmPxitRrlgyk1;K>m!fsnnz1MLUJ3fupwG&%xv?US2@lyB-LZJh~iG_mzRauWz)D zWHGnfI~iE8GzBW*=)nUa$qK^s>%#JoR1|@>b+T!0enP?Ry)}>4fol;4>b6wy7?0JI z2Kd6t;p2VfXDB>8@IO}Kj1ecyZ)PcguYEolIcWdM8L76zb{z)~5jl@38?}N!eib67 znv3IRrV%8rVC%|h)~dEbdBj{7#Fo*57F zvO^0GLCP-Ih{>b`ivf-g_Z@b)uAKFPgf0U%O7ckLQj+}OXw(kJhX_5b6eTURZ@_|4 z^u2fA%o1=Jwx-`=&jG6du}A-ddEQmt-SKr+hdnIX+-udwo`fqEp>}TLog+yGx}pS6 zb^rpv{v!y6#~KJl9p!jEivVs>(D!!cfhx(bP+7iX<|(^VX-UAcCTYsC)Nz;77-hlr zstKUgY1ntS8t}qxv+7^;*GvsRa93jK#SW-^l!wplY4WKK*9dj~^ryC4MvG%RwJE1m z=w-igXXRkS{1_WO!A$B$2rIb+YR|uWL@(8?!6uw zFR49qf$^brLOyCM=`X{_f+>(+pfy#DFy|ed^$%QXC2SnG%R#0+B9G2sGi^p3n4YD; zmL&M=_E_NsP12jOyUg2fgE+s-tTVDazUCLEvli?@u=6nTbkX-}$J?-2UMdF~440tJ zxyC^->g1%v-316F(?_HnxHdkIeQfMEOe2h#{Jne3eJ!E=w&h`s9!_r0bp@j}FORDf z3%T{Xp*MAszrFgt>{s7w*%LXk z72T>_T!Z$uj^E7WlBVyY9lIurY6V%vF-d?qAOl;H-nAt@OlbD>Gd;fN^ z%K#)G;WB=CH-L*m;gbPS7Sp^UJPGGj`mc?Xv!I+*tQ2KdnOxvEd_`MO2-27k)G_c+ zYIWTxXM+gNdLYc#xnHqXpw)refNFn71JA8v81GoGG)AEggEii#c&(jG=alAsuwUXxn&8nmR2}piQ%oK z#-ey>5qMjPKKK3#_wZM9)(z;Ea!6g?oobpPc94tObmkL-?K;7Ksa}3?spv0nbm8H{ zpC_tVjGTfCD>S|-{A@pvIv42xZSdt|*jUg9@3xMChw-~aSSYN>^s|fwMx!Cq_NveD zQHm`6xGYv4pP)!$7TWC%)a-^$Z00emvX)2tT=kt|R6{8XLR-I^(`q4CYtXjlq?G!F zth%fYz`(0KNWm`AeW++6NZU32AnHw`l+Wa|*|c6hZ5m2Okfia|pD&9zA)n+1g`mac z(tVz(J)>PjoB2T1CVxN4ycfi`%i!8;YE+{4JFKg+3yJx!j>@hhn}hx0%sv^oSIf~+#; zf`X&$A2{Zi4>8t~hJHPDWr?7qBL4n1Ax|8FOsM*!I}9n6Ez`blPNF+-)(S^QMql@J zfV$qqa8P2+vst-qc;9UB@yXR?`}qMmMUAU8%LNo2~yUcc=LM z*L(pH;|00>#Bk7j0ZJ1sE|ET-;X!ZE`_VMw}Hf}sUQp&w7zWR zo0s0twk`fbxs-V~P1HK}XDGbL>~V4WXPg(Gf5@AMmz8f;W0jc*F<}fdS>EQc(gILK zsNGSs<`|v7sl)imUe@b{=|=QzJeT#tXmQQuJ`%V373-{(hU3<97Y_}RGrxnDa7?0< zTzr;lO_d-3GgjzKCfozFf%)po}XkkTVf!dTF72tO$Qr|v#>yt z+h(vO!r7Uh6?mUA&Szl$eX^1_nr<+i`XTjx6;Gz{Q51lxtMmNX6b5k7)k^1>X&-jD zj%(-NFGSFvylnlpx|^ic`DA_a1^m+_Y518)ZNI`cBw?f`sad?L-GFXf<*EiTYZT;5 zC*enMs%eih)1)Z+(TcN*c9E|&zlt^b<$J<*hno*^y#aN9H+V%8S6_OwV-elfK=Mj1 z_zuVo>-S)iahUQyWT_T>2y;#4GrW@*=5Lhmk3At!B+B|Oc+bmFX>u{cx4$X_wI8_z zB-LmEt~_{UnUcS17NGCqC<%f4?x2&tC{Z=O=rB~f1WI98AW54XbNk$AaNOSAIrP4jD3x&}MNd+fl}28r(m0|OD${%O~V zE^y) zdpBwc%IJ?T4ZL=US(yUxG`+$mR(m%EAKPPCkqWAtjNpwPgR{utk?|nxcVA0uV>q*| z4R7auk9}~Ug#c>;S?Jgt-gm|CsbJnK9`F*4E1K8q6fxK0)Ym_ARG`V{Gonih zNrt6fvIlgOYq!ZsxaSXX{D3Y}{sPRo(FMYkS2L4w>$^mH@HgyGOca4!XQR#4l1|s51r^ zq=qN9Hc;`HF)4BwGO-64^8QdxpE~v~B7DI;L&(>GN#dNVQvT6N{t^h+v0CudVLi}f zA>PENtg33`6rOKIrym01I))Uo*mANW%0`@3C=_{`>)ycoXdTWUsI>g%l48&-9!^fO zJ>^|qpeBRH{5jOP>Sp!cW@yP6CTTg7;g!Nb=K^)nx z9JxK5S%+09P3`t_IqM*ybr?Ot&KVXq6F(*sn;{cTr$aTdgW*C8@L0X&U784;dQN=t zcAIR^CU@2YC#O>1v;&3Hy#jCibYqp;eUIG}yeI_#j+O5yaDfU{EiHnGp{XtbtB%#$ z0Ebv6t4|gw2_uJ|Q+_U=$P+025iyB{u74(RMt_A32awz{=9{hDIf+f)f7S^_mMiA4 z&U%4@8;(w?i6G3O;`(heS%f0cV@I@M6A}8*!4R8ia2*Ecx<=yhZCd7!-M$CPQS5=i z;QGvt574x^T-H55&2>0S1i@>ojNM=QC?20BvKrrAsF{1~7{l=@jV3UiutO6+@aNL+ z;T_Wb>j4UdFE_elvMtLoA0%Mze7XXmvLdGPcT5g}l0f#yb~+K~yR^k#h)+4q+q)Az zQ}=Wvz6BFnU3F_4v%x5NMQoOZOfy9sWCn?J!MJopU$GerHuGOZjc(cnkR08{DnL`O z)`r`CW|T0c-uxDdf|i5Y$3k=jPotjN^O-Fw+N~)@IH2*`YvOu@=pE~_^#r{4Z~4HCU)qFL)$jJ0{A zVLq|#xWt_dxjvGetHNf-uf>nayD$c42AW*!lav`C{KUq=3!Xj_b(+Zzn6o@+-(2NK z-C7UF8!oXoZO=L-H}Y#eS!x!o=!lo^pKcmJ&+HSwyQsG@gfX}m*@E)%19O$9hmzR@&=T;$|!W4vVEi8eFJvLG;EgZYPPck}}4)Z($rL!gb^&XYi2=VT3>wbJWE*^k%G|r0f zAOgAX{b6lkg3$cWeU4yDTrho)=z6c*5WWM-Z;(DX-nkkMRJvxOM#`8CXrh|#59>lAl2>DU(jZAJQl zTrGFva_%j7#}YvomWaOVZGjU(m$=#sa5oNqNe}JDfsw?r@1Ah%K68PkwnSnonbvF;C)>6W-LLu7Ps9KqndZBGcGTqLrt0{3v`1z;}N|9tc zK498p-)?Kn`@E)3`|x(^F)%;l^ZBJ5hu?wsRGFx3aDt_9vysM*>dK7HJCcsaqsFP0 z;f)71Ua=ls#V%JO)`_xUNrv!?GYpro?{()o6}&3wiew$t>zQ`@^*^^GdE( zF6si-hp@qy!D%#?*K11ti|FN-sDQGG+|-SaXoUHJ6^UU5lP$C+1r*RXhcw z%ID7wHU8^dUuhD{NQ<~dWAs+sdz-WWEO1bfYA6GaQ|R!xvSrr&Lakg91b|LjT>Oil zE?dcd=K;6wr#Ea}+FL^Y1F&m8h7?)jbe-!HC5A_$H!CB>E)E4>f(ABM?0&bvU+ycQ zHTk(eZ~>m*40PfR7wHTg-nA{>1^OSyyR}?bW%!EtsyRE+^tHG?hOzKkejr7CLy}|! zUPnau-9J>PJ4B2Qz<<@iou)@liX{2Gwxo5dG=RS%gFpi~_nKaO_G#cb@l7q_MQc?K zgFB*l-wF$4C_TM|^94jc3gZy3M@3oIYgBnKHqRR15Y|73-7PUX)^z@1J8K0?tRlIJ z7b=Dmnb9(96{tyv{1c-VZ$`PG@Q@@dd5Zh94aphjsR$vx7^ICC4?ux$LMVaM6MTb8 z!`?5=bFfxrnEP&9^VyuQtaGqRA9!kPdP4wHh~)!x4n7K1EZF#6sWv#0!MrGBEHo$D z*YpVqpNsjFVz0!2s@wq(~1;=UXPqWPfkB4Bm z^GgXAe2o{=q&|u;H1-XZWXJslSqqK^sM|Z?s-o^@_uSD@@OJSN-doA+6|j^3S?{on zQe#J#CXU%wISf@D8}3#x^*T0HbMfPd#hrONg-wG*mF~CETSWn_oG)jZ2I3TJ-BS;3 zf5z{wMYy3>j~~P4h9?UM#o?flnrSdtU2MRi4sp)Ib%y-LzTwF5(xVK*^C~o@k;vlL z0KLh*4JOi-S3u#MpsdHfr6ef;_wSj_BLU{S;Pub|F+@VqNne65Q#`$sww0ap zKF9m=ZeN~&f;52ky>AbEfoP=p^tL1@EtC?vqNUL1jP#nm&^JQDvp195HW1$yVdKv) zYR`1sH~T+j(-j8I0Wh}4LZI9sQZFj7-UD@K14-R(Ivh8z+`^1A~`R} zXCMB$XscQ*K!}pT{=(Jg;aH^hAeA9C?TmjnfY<1#RV3lsAZrS}Oot+n>vm0xvjx2+ zLL`Ck^Oe=wplAd8LjDWR#Je2K8mp4#SRWjZsDLZyPa6YtaQa!s;{F91OA3Nrt7f@f zmDlaCXK6Vef_-a+#T?Y>A1mt-RL*xx@8%!x!Z10b4r+cqfOiD|A-*3q6Q80&hLV@e zU(&Nf0Yua5#%p=9@t&wRKEfVk{azncMY^4u8S3hEaCNe)Ub@#Iw(TORb#`E5%TeT-PH;r4$C;Fm2zrr5;;pC;!I%as9x<1caWZHcnPK zz3Fov^ChgwUFXj&Ch;FADSxm#uCce8)zSEpxbWC`%m!ykH2Clb?%pM*tvMhuI>R@& z-elmjqlQ?^z&Fd0|i#r}c+&N$hl4l{uAuG}BZh`awxaR5VJO zw4rM}gL{eYn5n?zFO!5f59=*~rw)AksJZJnL?^ExEYP=z>_o+_A?8Sq+BqUV{0fSAKn_uNq z%ufR`9JPM0DP%bZr}C9oFW56xw?i@);D1RaLgXkfG?>;^sbfbFMJ`%HQoD!K>N6n2-&kS42k!1_vl(1 zp9$`|MUheH6|UBIt+Szhrpxa$&V{hv&kT>eu9lb^X+OX7T+CkXc!ryGROdZm2ypy9 zbl;cQc}I#9aqb&o*^jBDqJ!_wX1P8YLO4;r54@Rqcw2 zRX8O&I@z*|F<)Xwc728QT6u;%Vh%HW?sTw#y2E+u7hK|^m^JnmYE=*JadcTbTle;b0 z&QSbrG^{?QF-1qega|5IdGT)fI}AI_2o%vFR+}Ek_P#Wrh-pg7Yw%$(VN2Aobvtuh zDpcTTOXa!Jye$cXk<{e+kROM|X|pG-EjG7mu$M{*fFn4e5GA~i$bMBHFAd227 z7XoDfzBA!JGk4@j3lBjxfd{XC>=5-hkwGYPA?d?dFhD%69K)-HFjY)kqK<^^p%tyJ zWrW@a+Jc4_K#4`4u8Q;+x#C$NaeQ&djnqYBnxT5${JoY&1j)C~)ZmK9ZBC+}>77D_ zmAq9Y$i#znO`UyG0v)%X9+=M(4&(I1Hs2b3KH0y|DT=D0*MC&B#X3V2Y~)SFH?ld1<9@ zi96CQx=mAwS@!Ga$9d~ReGQ7sClIS91pLezX-0(p+!GiP8Q=FS0!B0uH*SJJ+y>mqE#J^G6=)h zFZ>M&i>lqXR0p7ODHNBYggj}b< zu$t@DE{}}vPnQ!Yw|UQS`)O?oDss;=La3o6UA{mG2PQWFP<20j?>b;8#P zi5qQY38?1lnDn!A)Lsa+w}Hs))zCTBpDN1n)_a%7b)QF$_z{oM)tY@km)Vu1oLwQ)Aer-7RQ>=%P>5dQIhT(R|BR-RQ zaCNYTWjWcfhWra*dlG}k<#=Jhdc*95&cZWCk7E~0X}uIBiw$0Q|9KhNNhAMb-a8&= zHS*R5#YAOZl}LIA3hnsVR_`34Z_84$;(R$u^7-racepn`OIV^Fn9Dte;N@c+jV^ za-{I_QZ|xPhLC@#P;=_B#^L#c)PIC)QM;*U{)VlKVj-(H%Z*9uMNNsWOy7%&!gE)^MMMV2F&bd`I!=ZaYPRzQ6<=rBPAFh-y1J zYezXQq~Z9YbYTJGS;;5eKoWu4yxSEG!(H-|JotsrX*y~{!!k~qi>IQ!+aXh&?jP{$ zklwa(Jfq1L2Ss|*Dfd7tnpb?|_m-9Nq8CL8;e7+g?qDR!xg9>td_25}RGj0F?Af=EomhbB7=gTrYP)7-bbE$l8X7zEM{;FaJ;DNe zQF@6bceBN6+BH&uQ6=dcqhY=9IY~7CGsEYvDTe9Yyu=f}Z+jf5Zzn=_4E@`)H@TQS z+p;M-XP1B5t%y3doLd@j8o`OZsup+!1AK!&CkA?TPH!=U$qT%{WAy%ZU=+(e73S)g zSi?1xof8sXM`i22q4pUjo_aC98NOj~>=Ii%(mf_Xl=nlLmg+$k#NN8G%2Mug^!U+0^9@??>|_HM;# z?Swz#5tQ)QIE#O@ruZ*0pFOOE1O61R zM9lmxIZS<81V{9XZsAHH{4qM(=pN}+Z^qYi^-tvOSA5O0Nxk92HIHXLBQB7%1oyyo zYSt4tArb0ghv%>*%8-t-=zUKvDfak>z*YRWjc!K+bD5-I9M@q9AD+fcrBoI1Y$qs5 z0QKw!pbb#01IkwlF#EsP=m5Gg90Xog<_&?|+p+K^Hbh&nj?Jxxd;(+TQ{0YI5ClB< zTt9WTOr8d(K1aKZeYIr5IBFX!nsS! z_jo#YQ}gUL)f-zqtE)*emk6;{ISa*$dy#J2c)a?`z~CaEr9e?B=hp_qLWilDM~uK= zck7px=TpAns!kK33~cd_P1_n6zAL4Mnr^GETpqtw%CmF}Q<~imii<=s?(f?rN`}iY z52t%wb3JN`RRRZoyyGXTz`kx9UQOxWRl5GcQ20nOSU%vHEk z*p9okQ@o$iT6I#iJ1mt-grhmN6FRC*pC2NBEJMW+13cS@s^> z@VKGtx-L+huQsigs#&nkOG&CGPv=zIE`H01`6#?&o%Q46^kgyL00UNxRnGls1ne{d z4hF^wHIuhQUh#S{+~{;bbzjPM!YsWF5&zBz^2C)!A`F!TA=`-O!Wi zv$j}dBu(yz^ieUS*TK68KWW&zpkh;G?CMUC%zEQ3sVIz}>kEFa=&SY!_bm{-E|N02 z%n?gTp8u)SMuiH7$Qdsnpmlkl1?)%P?BcRx57>WvY~|p8zkfaR>fyppOa8N&@~=0> zATa1jkzoze$IV9h0{MH(u z0jr%|DJDPJ2DY=IwmSeX(BVodJJ1CsR_ZitO}~bht|-@4LThShF!5;RykFq<(OlELt^Y$)%-+4XHYwl?+Hip` zq^^a;rws&1dgk+zQykhyuinF1GDO-BrHzY zcUp^)Fj0YUvN>7iwf{#e)0eEON#%DkorX6S_jzuX+i+q>Z>+d~lK8f9?om|DycIWPHScgU* zHts?WE`}VX72@LBjcsj8iQ8m~wvW$8!$jX07q&-c=#35`h?UFW)}s#-Y2ynyQ)~cm zkU9~qK{xKB7O7+Q(^$iYhZ~7~x>zO9ipwsR;NvKD66wd=Z*_ffsy%cehm_~Nu1 zzq%(YNP@Iq%uijRlk41esnKu%zZD=#B;s-B?Sqw*Ez+-?FGL}7bdLWdb=P%CO9)z&Ktbf9Q1Bxs-0F%N*n5?h6_q`bDVo9y}hDluRT0sU6!JFKr% z@G%2-qS>R_@}1KfgKZ~u~?O}1)TuF!SSEsT7O;@`&#Ln zR>rj5@4Nx!R7)}8DxI+1tuw3G+WU7Q!~*7ELh z);h1BC7+)wL>DL>RSjhx*`0coxc+Mp@qQZNKL(Mf!Tzk|ug1B1GJ{+_u05VUo;>b8 zo8xa>vpiEKv1&!ayV*w69&V`dIa?}e5a5-{)>mI9W+m-PZAu7Wu2sS~g5 ze6_qL&HDF?cNiIvsIbfFCUPwDI0YiYtr<$9Gh^;k&eiOlfCWS#>z*9RXdcm$jfIil zd#$PiwrZ|8a@=d;^wmN73bjzW)|Crk>y3!T&D!c`L3bNIpbH)zU^g&0?yYwF)<205wog(fq2B$#0=3nAlki)EAunFwj>{UUAVUr>o=g++e(;d+d z>{la6=oNN)q!xSsDRgF{c8>B5!?Gc-U<-a|?{U-j;`&;)#^(B*6D%e%w@TKU+f};l z-&v2d{&d@@9UDI8Q_q3XXOtpAL`R#q+MhfYDMWHUHI2yK3Q<|@T11t7cpFMVma0~Y z`4ak-8#sbxc$W0CIkR=BRS{mVkd;9;NkX;>?NXRpT;`AcYp(6%(|jHG_P0Bd!f_Kg zs}@&#v!87)U+`Y%Va?ALXwJI6P8gt{q-G=2R{3^BtR*EBs1*c}zNRx9`^_ zMhxJGT%P8%Cig`OW0^Yl`$T)dq%lK!cj<~>BA>F`SQM#bVBd5hW?SpWdWGSIRhH)Z z_h}rouyGX(YnJ9d^vUGuP$vxI5mTw8EYka|YL6b62f5$sBAXByL0@Sk+jQks_t!6G z71>l--fHA%`v5sy@R|1Lp4LX@e3Pw}K1YNReGAJ7%IR!7=&mG+-S^hmx#aNll|ed? zK@1}}EK;ARua*Eu9HpXo_5^L2{$=&*EdVM~a(c@(5PuepfaKBBgmc9)hg;`G^|O|w z0HfXfDtID=X2>`bw{}hPU6!7UWw=bh*YTs$d zoS!;<9jJYb^b+t!8_d62u0(Xzz1|T`Xk6bGRUf#+w71diN%^MMg_$yH_kra~@qYVE zF|0V_|DowCquP4Du7ejZl;T<_+TvCuNP!~7io3hJySF$LcXuxyv`BG^6WrYb1PJn` zzyI?lYb9&tL+)L3X6~7@&)$11tIuLzCXslT9ggX4KlmutwmeE#aiZP|y(J{m(*Z{b zl>e5zgeH7XQz(jfrkckH5Y}E&pNTN-@w{14EK{=hnZFOsdl#BeJNMe5?NfZN(9L~T zfsPU5ahHR4Jswjq4hxT3>pvS0TVL^;F}?UaMz;5yo}bym=QNj(>@K0 zpGae2ll+Cjwz(lek}F7kN{O5dd$||dO)9@-uI7%(v7MKY|kn%KU?qE?PvO|sJlJ`b$c3<3cX8(=s^?0b#NRRMw)eC4vqnOD0FCCUpLB)}~IQYohJKAna~k$kBTR23ybGyKlJ{6c!I00i&hg`0ToE zL51VHFE{>>W<%M`z1gZSE`jq@%X^}4)ez!6mH26i9*r3Q-J2o67G-00 zR8O?@_S0X|@v-sKalhnEk$7ByXM`GsBvoH^*@z@)=XgF)_@vGXV@ktQzuqkuj!R{t zq2j>&Nf#AfaC!y7tm0+=Zvb{o0Da4v!Nz)QKo5N8wxg0RYE6R#PVg z^t!BDh-D*<0=6FHH;EI$_U_dVs=wG#%TQ@kuM0ya?vG9tb0b;-&xdW7rGNU*2ybT7 z=xzD(?zhIjvs@yTJMLU|O)2DP3>iMc=DzYQ*kocQ+)Hn+e(cO-W}=r@eWC!ghmrBq z%3?t0Yn-{i`vh)rgEonH=n{tiRJQfzN;;7i9IGck1l}bty|V!X>OF)L!0r%&LEWAM zSNd%NAgG5vylU)368d|F$5NCw@v9O2Cnc5Ww|76kI?DRTlj`RNAJqU-KIn7j?mcB2 zu4jjojEJzIAkN{g#dTx&qX>M$4!AaUgVoa9kH_WMlXyR1gRX|_Mw##f+g~{Us`Fhl z1P%UOOnG?*)W!t>WDK@ zlDWVImL^6|b2SHP9jiMMP`ko#9&7&0M|Z({pnbuO?7(|_39y0?!UfoEKp1rUcy~v+ zNM@2rvf>(80^iqDtKN>{o3vUU$lnkFdQfeUQG)+KUp+;4mHx(Nv#?m6}ygIBaa0$n> zaen!mKhsl;^3Yd*R!waOJ}Y>(^&RJATd#-tn|(uzzk_YjCNVU}ogp8XG}0#^_&hAW z5yXAW7xx$1w`$yX_gkx6Xu79Y^M*sVx8R&*;L=KJriY8>bYn~16X(mjRF7!j0b?9m)Rfp>Pda-+;8B16@&mQ_W$)0vtg8U zci1yKQ-8*Schg>_CL{X>Gnx(7db|l|eDtqS1ZNtobvizc;G)Z!=x?)K{6d3~KiTkF z#@KF6#qm5n*}>!=-ESxT7nnsZKKai>?LEmUikEI%vMzGh1YmE!=+ULEH&o_UY;7I0 zUhg%R3hy6nw^nHPq)!J=r%?-!bPMO89z?Z}!b@mHu7%+d+n(;#`GVgvV9@-VXUUy0 zNk;O@YxuZS+SmpxmBM((W@s=Qe@ws_`F6X^L`>hVPmDE8wZ)@pXS8A~4mx&@+V1oq zE{eeKuFHD0_DksN4EKTFoWc2HFX3R*9cw0p8w+PS9<86>MmT52cE)~CH1kX%){TSD zCL=v}y;Y*oZo{Tso244vF6v>+pma7QR@S^hUhb-1&w2}@#k};)5-|w!6m#;ZHFuR0 zLQESgfXYRAqmTd#4hP!H+quj9m&gI!8N=Tb*h9Xn$k1nnt8bVgSET>?c`rLk+-B?c zYuWZx{Hx=qNAefh+R-N96Q5K?nlHg>&ZwPDgGiQ6IXc>!=h^Z?EZfETnztN|NHrH4 zp0_ZlN^w9{=Ek3xR1hS6DwdaBTJN$>Vu5fsq9PLK6-hdMs9xP@OW4$1w5?!oHosOr zNG+MASgZEbv^KA+XxDvx@z;jX^YH9b?2HAhk}9{>evPc<*XPli<0fRE8j%h~ja8Af zNkFGg*=&(h;nmLZm$0-QD6Po7R&1%ANU8K%B-)>nRfebaCn4C9+wXy~0EoQ0TI{Jv zcahrdD4+M`fmaI4%b9;Mmv*jwr*lz|G3Impmf1mj1&LUm{hQg7_G@=CCJryH*a1e4 zZkFyz)?%eBB+a2;;|hTrWFVJnqcGkeAK_UFfMam#g6F!33nc8Z$ChrJYXSm=LEnTv zo`}DdP0q331edrT^xo~M&YU?9oXxIuhfB|MwUq+^Iof)YT<=k==@(U;=Gq-5k+FEl zPpmr4%y`}|QrED|zE6d&+4rYD`-?+n$zz zwvvgv*-dy?X4@nhK@Z;m%0?m@BMOT1F=CF~44ZdjcSlb+qEaF1AI*QrF2M9%Bz=z> z#dxo##1^Bj{i#f(NS1~r2WPXbi410d6*IJ)Nt05;?OOW;jrd$gsz@t;$!UwUYe=Nu zfR*+)IYf{@&4x>+Gl)OGg7$XjQ)MTawdY@v(b>!KP{v0-G1|r$;T~CifKxEt~Xore!n#ntP9Eokd zbn4#h>4hzR)RsXamu?NPj$VVs^*f%QI-S3PCQ%$Ma)ERtj46TrmzQ>V?pW9`z8}6x zz5)Lv9{=ZCk@GJI{{t!hQB23gR-pJV>o3bM$8dOJ?`7d-{$=iE{yAgSzSb&{xPor+ zJ$x4gwj-CSNt9iD4;-=Oqj`s}EHO-gKa_M^2q82*b|N*_`Hq6BijZvL^Orl-jxA;u z`*QDZ%-RWd4oqOiag&f3KsALJy>{?HK4PIP2$L9Yjs<{X6rw7ly|L8zYqG3r?vIoiswbg78oX%`QX%eIlsASrkxsAyLxGmE;Stw zyJ@9nYD8E1A}kCva?ZN5cZ#E3^I9TMWB0hdmu3B^mie+irTnk}7Sz6desH1k?$)iL zWULLjUHziWe9AhwiW&o?TJbqw=acM~&&2i)e7zKBK2@Cr;RS=U&cNAm%28`wr%SV+ zhXU(pwsJoXC|eJ(7_{*BR<@9rBi2;?LIu_mh$hHA}A8D7qQFnq=MQn zv+b6-SZm1V@=!kEqK~0=p~Pvr$sC45)_ouLUA;6@2^9j^1Sxx$q#Y3fYu2l5 zEw1m&;*|mh7mjy@W~j^lZQpgIVuD`9(?|-Ki&COrjUwgT1wLtyzY@!h2Rp`lBIR2Yo1;a-9^2L9=g~Jp$ z!tdKs8s6gVJ#H0L@l<_G9-^y|Cg|(2bYM~PKTKwLZGVe2{o8*s=5wmmNUV0Icrau= z-ME#w6Bl*qMBQCzm3C zIY=_*C!byly>!Q}k*NPP?N^qLNd|1PKr^9R9+mi$Ilc=1^FXnYXD(rmeb&q!lJn~I zHUf(%3yGF3T3(zR!L>R+i;ksH9r1^FP3YPU2a5Zg8%Oafd{!sOUY;evB;G3GUxa@B zkfwsANxe0{-#oo+#}s_=(d*{(RuSEI(aH-pMf;n!hA#^NP9Wb+{~8M&m1lbAiDc;c z7*WZ|JQt5at)i5n5?(O$dAPe)I;0_}m^diPlgGCG-k!e^DG;Gh(Z{6TLSsK%va3BV zGVP2A#ugdkeN!~I5+`!^ZiaC&WkAbdLP3IYuYHFzGQX)nb6|><3p36pucEgC0ib{Q zb^8-U$=r&lT;hp}E7wnJpA|d(it5DcF7I1<6GnEN)9%P;~NgWqlr9-jEVw_yAdJ zY$9V``M|G1XiuVuRi+g*f#8=zUJbNJAD@tFga>Tj z-EFBTh<34JcCkdjO~*NA(%}0220thph8^Pz;WV0P=3PCF>GEfx;J+?A{wiM5VL!>` z^f`5F4f(g%1|IH%JL)~5lsJTH`_X*cm&BX+v0BDnPqAWoI^d*ED)+vpf=*Ar*ACT{Y3p5T|pb+&A z`dGQc#u#gMBLklFBWiUBz8j_5wE!v4E6RuvEd3m|kCOQOv&+T$H;%jqq2#30FFYzK zNt3scK66=pc$)jtK)}T%LMs4>K>@W94@>w#H(K}EFjCnD%wL{??2?odir;^G(m{fyfqk53wRkdWeEWOsjFfwIzW{E^pbNmj zB5D;yV+0^sE9H^a&cVfrz03&zCGD@n->{4n)=}-en*c^?T%m|r-oe0N%51z%481Ym?w(7Ye>#4z+4CxlENIxW~h<9<`U5z zU^9`?KG?gWwAb`B^h_D(;Cg7~x-$tKi3bY`m)Ih5nmPoxJ3BTt7G}}E1FnFQf?iHa zESB9u%y6ko^tlu}y0t6FOFl^dmD$*$R2idZGjSCLX0(S!s00AYR63#YfS<$l2zgZA z0zV9g^QLE{#B6qGz8_lk*Yj#xtD3&|Y9oUy8itaS#b-wgL_kl27z&^Z1GF;PR);nAVmP^JbL}o- zF8)fE=3l`rRQc`4hXSEDn&p`$9-`(;CPVMB77w4k%JVK+R#h>vKeipA*sh$A`y7Bg zbfJV12}JD^d3?Hrv^?QUYa(nKPcSz0-hE73bi~(6Bnj3T;+68{q61Tmm6+Pw+L{*1j+|#c31vbC?d$* zDU$PKd3T|&7S*$lj3xRCVyUskMHQq+em7dHAzs%Qr6E!_aL4}|`x}p4+)^brVxPBo zUW@Bm;63*muWeT~@}N)Wj5Ac_xsL&G4|XdUaug>#KSu{_GaCizf986v^&&}&TZhR% z5)}OD%;LHmbj(i_c-4U>^Kdr?;cM41qsmVwah!rJ-nF2{!xtR6_+g1SqG1z4j<+z| zLMUBq2D*D#_2TGm3Tr}jMu^^)8YeSAwkcAYqfh&0X8m4{BJ{MyeU;2&u+Fyiacznl z`=rExJ3~zD9ssu2U+f%o+w34h9YM)hanj@QB_Iuae_SAjyfy$FLWyDVIG^7AVW*P&r}ZWB2h+VtgDaytWsr^w6T#A*_1aIN?I1 z-}tB14-wzFx53KDoGm4y@2B5K*2+oLr{Ye2Yd^wY!<)w-d?!r%Zis+`Ern$_EHLY+ zclULfwJmV6iR!&+icB1LT4|fBM(jsh>{E=#jxdqdfP>Z*se}~^vvomGH#Axi^aS|! zeg8hkC|)0{Gq=sa38WvrzQvFl*YuOZ3ADJ6OE>J1TXy92#d2k|>4%41kC7JjanuRb z;V5YGsP$2SXZW-F#hKsKP%@r!gKP-S{V^CtV<=TUDVd&F7q)t=0e$resl}o@i;AuL zY11VxccRgMZTkku$px?ju7{+BON+JVa$_SZWVU%ca1`enFWlllUGv8Q(Hh*~wSPXBf(%S!o zL?vj2YBMR=B8ODFzTCx40RSOb=Cc-WpnyoSONci7J7~YO=(ZcdJyNzS5b`#IuR=)O zY@(v!ChPE&{FzY`FM)tmim#HwR8`|g3|(V#2sVG;`KPh`@7jy3<}`Y&$ETZh7!A?y ze?;%fCcR-qhSd1E)`R0vl&+Ls$=N+Q+na_f*$Cglwt>9RzQQV-T$^(e@ShtT64#S@ z5~|NXcVA{YYAPPoj|c9_JihmG+r81+`byOiZu}$U40L}G24SXGtYQ!^oRAhtYz9J< ze&*V%Drh%W_pl$H>vW&)9sH`r!!v_=nVePVxFSnuC_vn40(8jd)B8GF%eKGptx{84 zJ7X0;OH~T*cP)zRedMX#O*jW=tWXFcedZL~Z)ZCu51XhpKXrauA;|?%srWtJK0E~> z+QWPN4{MLSXtlJ!G6p3MPJPMU{TwFAkin_pfE+wA^jcEoufRsioQzMN|4>)FNvu2DUyJT1k$R+u>&mifYv5%+&*x# zB;GjZ`3S|J0<{YxA2*O#NQv8KqivrjXPO;%3r;_(F4hGCvBg_}A+q7#GEKDnyCR+c zwK=HU2AC?6=?Gp)*e<=7H^VX>%pY-nm1{Uh2s3pOCfeep8xb2FZO>Seypvs_)y9gi}@y+=norV=^~JBU(wLj1W6r8h&Fgd9L;$QFnVK>GHzkI4Ay9RM*gYTYA8 z1K5C2`{AXV-eC10Qj-IMNMJ(Gl#ESd@v+`c0c3=Sh22SXS{uF>xL#JX6F#mq5Db-ha={4UxSLdH&5hyNc1e7 z`Xjq+*q^r+sB2wY4V=--2OKhxM6AjdUKsZ`mVuw{b8q*C&V7V%cfK^Gerc1+eTh1I z*{8^$K(}|W4FaFb(=IWvo?+`PK<`x)0x)y00(Oa0gC}P0K~w_PTk+NJjTYR^Ch5_iZ2&_ z2J@Ulh`Z$FM6YOf&9Wn%4KDE9QLrc|FuS?$i7_Nr8`H?~TzZhoGW zsW4PTMZaT~9DMy?A6%aV_}Dt7hi+->b3COBok}ZI?uL8-mQF|0WB~OuSqyLFT>Nly zV13P~W5l!8O{9TFQ$GE_tPYgohfn~dz>R85PhqDZ`~KO%sQ)?|qD{BbqCrjxW2vc5evJGJMFrf?IyD!kPu}v7+8%m&L;N!*#eI3u9~V7>N-VmvNu)j3alTd ztem;J>45R4xCZouR*olguO6bsk*ji!)=+s2BRsPmtfPJ%GWb?SKw0#H`uR7C$7jLV z?-0?!Dx1S>sMf0wWr@6{`< z68RkoO!ret+UXCeAoY{Vm|;zm<|KM{j1ij+V}LGI)}UM7(zvDJqNCrEp%Ri?5OzDL zrrq2%EO-+!5T_Lhr8QprF3{eiC(Ih*BJH0N3m|%zrallXCMhKxR#g~ZcHa{nq3swI z7T;nmN4k$r^1kF-oS2?Lrl}g!!EEvPTKXqro_kJQQ`kPm^74|rp#HJn`=ZToEww{v za3HblYBx;eNRczMc*fVI`{Losm5js1=aNG4#;fCoY~AiG_s7;r9HNC#6u~M>WPIiM z0wu=#qpQ9CJCPBi@g3uQBCsh?cWz*`)P>EOAl3A)yqh00FM>m z>?Tb#IOm>{T^k)Q6R`(Vya|z6Jx4WL*jJp(UqGr-R~yrW(<`o`!)%Rro0erHI^Z;T zr_sz#wco3(zi?ADe*w^X&Boq-`Rk)vPg&4vjWSX? zDQ2(!S9`plv|{R#$nU;o3N(D8$xQgL)EO4^ZQGx5Ff16JfP2weYrxERmfVYz-FaYc z#X=noZUJ4_R$z99_yk3nWFX5deD?f%1?4er$OI}UNQx19%J#BMy)Jr9Z89Z8jk_+I zr&Wgi5qom4@S|XF;X=o~&+JV-^xH&>3`>90RO0z&5)O08Ox@x6-C-;rbaP}b+6+{t z4*G1zX190u{#aVsqdW1b1^Bd3lC)01@Z#2j2K37+ckSz2E5R)Bm>4-{+M}x1et)mO z&&vulc~}$i=)?0ad!ig2c^F@Q;HlwRPcm&ht^6`_M&wYxCjU%W`~#!yzJ2_bDSM^= znCo5;2Uw7l7E|>}%vK}!{XHE)puy}@IUez#@wbpGu7|JJ~ET46D z+|p+gu+=ryI&n^y`~VR2;25$AS)&(pc;YvU{nR1`A{l|M=cF(y1%3^hPkggo8nIMn z^}-ArhSL63M+ii0V-9B>v83vej8;U>o)n z&-N%G_2|X!{56{Ao^QqIk?g$IhHn$JJzVJEl6bgdGwBC;(J(7)D=#CyP`#(um5n-$ zn?{?>UPBIEKfpibc#{a5Ha?`vy?Gl;k+)hZb2Y-l?7N!%B7fL)*=0~LLB~MdxcrL) zxVALqisQ9$V=XA|O9UGnao=dbO`@!A?hm^Sc!Ky|^8ZGfK5Wc8->!dr*R6|Rd#erJ z>n!yk4E9t#rF7or2uSq~k9xBYov1xIH+B}Fy%OGRNmG+{m~d0BPK`@<-8HluTU!*0zsF&U}pBdw*AAP z^#~4vAVzZNpu8yLHT0ougiT<7?t4(fAdNf_L6-v_9#3)Y>)a$cE9vDtlx}&cJWmdZ z(iQy1$GnzNk`>nHBEE@iTZh}epk}c?(|>KMm=~iUBz_0*|C7}MeuW%$UmEy8Va~hY zLY9hk7c5Lg^{rS`D&15nuPCqVqX|Z6PL#+V(g!#P#Lx}^`8R`9rY!hwdsT-F9%hK?)@Qm0PW2I$9OAh}Cpk^CRZ<~kxMmvnDVJ99;Ho8+%R zph-%r=les~3WV0lo%IPdcE%Vgw0q|(CZiYbKW})!X}dUAKI;M=-)m}C?kt=H1DbOy zLLk!Ufy3~S`D^MF2W&p=J0rD%l(Lw2%- zw%JxC69Wrw!}`nusRY5m4;z0r5JihnL*4lPTd08em2ap?$vM&1->3HZm{FPc&JA)o zO4yUSF9JU{1SQQ;`8Ad&PWVXP>w|9ZCGRAvHjDBOH8l1gK|iiKV7DQ(q&^E&cT0Xa z!&~NvO!KvKRh_YrKAZ$MqTZ*Ln)N~fi|>ul=SZ)chNU^M=aIt@(~>*e@t`0JdYD5&&a->U#DX|2AEy-3rDVSWM&78?JO%m%Ow&Trrw^r}PO)oX}JY zJ25^?S*NK^upnmfH_vbO9<;lrV&5`JZZ=X-uZ_*5EEWk->jS*A4S#`<|O6A-2+CipUMBaR5|7tu#97w*1mb> zTkr6_)pK#66SM>oXU#L--pRU!ikhz}YNNrR)8=V`8sc?D8c?S8lVPp(^C(bh_$$|x>_-==ar?SDOaPDKmLhlm3=6GoX?+MXBB^Vj9(4`jm@>2X53=1g@r!T9FG%&pPWi(R;z~&Dp^J7>h}iOFb4yV|`BfyvWK_&F)gVaC$(NGXDHd-V z3sI;?Lh4U}bphZ_+OejUK>h$uEP@2S;a4d%{dC<07iJc$XolIBi*IdNU@vI|p?v?i zQN|-CT5-k-tyt^KsItlendyE`eXwOx*#6j=7?%qV>Kzg>YXC+Ize;mkhc@Z~R~LQr zsCBgY;8|-RBKz#cW(XoX0FcjUO(a`X!~8$!DPO|SidA3bx&sh>H7`Ge`q5TNXt5^t z{srv9`Lnfe@_2WT59eBG@9y)sf1k;TWUt5UGz zj%2NDqTiqp>>r$(+uFZyI-!s_XMh8`d(3edJ>nwY3OU^T>bLGyG(D~AaN8b5X9|VJ zY1vspa@mr-$7BT3X;fS(fB6BA}r#oW&# zaDN>mFun-Rxi?I9oGJDq<$BY(3mIo}fj8(xL0AC9i+TnrtUOM4Twv8(Za!QyG7@BP zIAUm(M~}Bq>P(TqNp`}HX+}!CNsJkzzY$;W>PHmL2<;(QzmY{b^&F`Fg@tPZKk+rO zp^(iOp_+ajDIRwJO{F{$eBB&ad>2D_^9}&OAqD8Cuf*B)z1l#(@#$2Z*#C%)x?95Wh*7nXEFa24%=mFVtWeU#%a{Vy;lzSc z?`S0q%qN3Sds+*W5k{+nKX;Sa;&=lnI*iL5HcOvm4;XLPSI2qQeB!Rvlt)hNCTDQ? zy4h-4&$sz<_1{lGoH^=TL|}Z)5DUue_Oo}zM;m|34yIiOt>_nBXMNY7M@XSusgzP} zL+!G`msW~r5zJmts>{JmS8H5=X=dp;$jtu>P`U<&cv;P&$NE;3yu!XdEaWi{rh0L8 zDtKs?x`AFI;U8#@3+;7p>>d2PF7t=*LgaPd2!pNkn64gOpdQ=%d>`?Ng`xE}qk(f} z(dTjF3u`hfvQ~AcDVcI*(|84ps;yf72DcPj?91V@h^L|_)h5cTBYhpw z(U}>fxqgH3=FHgw{x5*nK_r0^i2p~<*!%a(|ImtmmpBXePVFs6dW7p=t1Y_%yIDAHg%5Y++)_(^QmkU$#|a8Q(o*=Mq2)iU zLR^Y2>a`7ejJKfgn~2CJj(`KM_O(M)pxM7K5V`FFCUx#rSr6g1U$D;fqwF)>`%mLr zLfte252tU#2kix*A|j=Sk7=_@kP?g?4%geOGd0RI;K1XIPxX{xzZl(l22usnYnh1K(+P3DeF|>Ryql-69Ky_bV)QDIOV(@od zN^3irv+YN9WX0CdjNU7-8Eg*QuT0m|8Ic4lR6OkaY zTJ2}(f8nJbr!g|6el0J1JC67sLYLebZ56urHL5u9`KEZ2xu$UTB(Kx;513JWGnuyJ~ zzlVWh^y);fiUR|oKjc1Pi1`+s`BVF}_jzobR^!dHouc{mDNSie+WStYHN0Gzzo#FK zsn$6Whc^z@i|vD4p|krpJ_!%<;;u@5!}j!n=D-r8zikujueDmemx;s>LcTXWw=#;6 z0Aj!0V6X!Y^o-E@+h5pm0>?ee%ijktvtWH@L@AH*KUPHJ@v_aT!ihKyJEwfockt^6 zmsae60$n&0xeWi%2ONiI8yI>90B>@iV<6}Nf0KVmApkIK2|xGB=S@G|*xOHrLG5}T zQiw_yBh4~2#;>iMn@z&d-LsDm5+^^pkS?{>4f}l%sEMPM`!JY_i0Xt{ONF4s2goU3 zBl`x2hJH6A!D}F)I5}~Aq#Px4u|8Ac_jkqVWkESt*$^3&=Fc-GPtIwK7nsRb!2!)1MfRigrdw<{T z{hw;|MZaRouVK|y3Xqo0u33*Mt<_gu2WP!Ki8Qlbwf&R@_vyJS;oy>B!raus-b(c_ z@6T(0e>``IZoHS1t5>K#XSG8kau)zzYxoz4-RlYpg0lF8FV${U{N$i1_{NbRrT*!rs&sVC*;g z>K(@4QRMe{Jz0mY9+Zw6WVr#~zQcK74t#jwqKym505e7wbjw>zQ$$0g-|Uf)ns|(J zdO2}p=|Kx5bQ{0@oA^c$$Hd+!*teF&O%IXk`haQF)anO3iy9In4J-BFyCwjZZRFqY z{MBfM~x-q$wInAH6WR4zV#ZUIt!B zNvHs-F}un;g;|y5EEf#}jP{&itqRhJLiuMyADM6FOBlHt1zum%>);*SthM1+#d7Jw zWH*1^u3>MV{{-5jXOsDfam9|ap zB1S90%U#`+8cP7ST)9mSK>i)FhsuY@e=hofsy5p&>zCt`6^uQCP;^x9@dbsApD>>m z&iblPl3qu0_oFXN3+IT67bDbevVJui4^`E&4o?BXB&WUZ9DS)Uzjf`MeUYq&d%yeL zNp~3~JgrWJn&%8$grCu=+x#-sHa=!VcT@>yZEXTh0Zy;xnpzSAzVRuzIG-?V?f+Sr z8K7CT(pQyJJ#;>Nv3_8HH{tqjY2)(2)vgUt{fbJ8DM4Kc%Zi_g{!-%e<2>z{11Z*D zr4`INS=2j&3D%z1H+m%cKV&zoA%(o^%<(d?A_ML_)Kmef4=y4owLV#f7N~dCLEp0{2makXZsEPYM9_VMX*^rZYI%ZPsoh zjbtA+5hC1Q#gs{epl{l`9JV zMp%{SHr%@38=&(aR@3yBW@|F~35l}1NCn0Xt4D>nv&+R141oY&(kp;c$OH;z&s%{E zEDe<^)%brQ)ao-OIjEWd@TYAtn{=c4;Loac>ryVYe>5o()EFW)L)&#p@1LHBA^8bs zt%SukzNy#j=+sNvFcIpwA)baiBlOt@L*x6;O1AJ0SwGyKbNhY6!EG%?q?UVW?K0h70 zeL~Q;IQ40BaiI6wf09EylFhAu@uVxiERj8j47j*|`Sa<@=_irewal^Cnp}D56$vds z^>^=m#{7QnSke#a_?%^3GM1*T2MhlN<>cpcWEW?bs!NS*44%j}c#Pv|AR$mxnR zVp(*gDM261bIjKscpgd{Z`i!#-!h$+fXCxxi&NyZ?IlcsY|S!hW@958IGlsR$IsRw zcZZ;K3uMztwz@qIHR9X^O%~?QL}DGh@c+R?mhiFY=2zAbYyR4iLh$TkC+>Cv4&WFpyS z#V61N=YZWlE=5P`=A_f=0Bm%^y9^9df-!;a!EMJGYr2`=`uEdn_8kM{f9g_TZ&-@7s8Mhu0U$^Y7J5 z8kzt!k<`0-#12a%DXq_UW)PX}kxke5lusJrsBIm5)=Xhr9wj-#;bUCFppy#uO`@@W ziia&%lB``}GE!&%9W>5GlUp5^ZC4K;(9knW=g1jrYEq71%Q?iOFTm#q-$I4fjG1s1 zh7ID=FR$IYso8{#!L&oNz+v`wkcqTO)qZD#%dJ7XEd56J>airu``n~nM+(Cs0P{4YanFk$iy#w#xDne-Uq}hcf-Br*V<67uD%?UzBPs@9Z-5pdiz1Y#O zzH8rtM{FP4w5?T7Hg42k<4Tz|6>0pM#l_|za{to>^;V1S{&*S{N z8^)5me98{*o{r<7KP#baaM@p74-XB<9;(7{M+`dHQX|@+Hj+vY5bj+zL`@?xL;>WX zMtv(AKD=I;!S~r1te&n2QIGJP*g}anHSu`eEju_;GfCzbp~m@&fFNe_+;feyc%Ivg?ew>T+e%K#$yxB;bDUm zjmwBa45$f4030|b2x6ppAplh8P~ZNBM;2fyGx}ciog?N}9C{dee*8BKC|3sRo*A~0Ern^kaeySu-diLOcrOikwM1C%FH`M3+5euI02vKYed=P69C>Uy zXZa0VD_H=^@cORDGe`*JkBYQBpVBQE27upZ>~)M4p=Hg~9Ixno<+k_5mAAiDLwk>Z zQyQY8Mx6NID^G#l8hh7IeM7@ehPMKU@f*tDZn{{Xzqa*S^L+Sn+9t9M%I4iYOkn7M zF}6(B2wv3Fy{P)(JpdUhpMO2fSOtLWq)Gi1SL8-d&%3ye#ief@i=_QRIBewaEWo%|y3pcbWs(~gCABKCwW)8eD5H#oVe=$7Yye!7vHF}dzUlJ3i^XRM z^gE3b^79rz0&TLJpIU>MN9QAu0hThXwX+(OLqN5@3lY=>OgV}n!z&^R)@arq)42*B zrhnoWWQF&Eq0-1x$cSOAhGQ<`o1)0^gYI^F;Hoxm{+?% z?-$}%E!atH9+v%;{nEMgMAa=6*g$b%RrPdgb37rjWnBozR? zHa?f;lDAfO9u<6WcdK<>1LwsBew96~>Ylk@8DG*S+6|{yJLjWpYmbDL{3R<=2cyw& z+Lbf3A)9-hE8t&AQ1mFhS8a#cif%2Id#C)yR30p3rpBR`5b!+p?5;c&`~4;>E1@G} z_|fq!HUS@NtLZ>lADf%H!I_%(KWwhTjjfljsAj{Oq zpURfzQ&0wr2L4(h*V7-BHnfqsleGx1@#spd0zI=a;Aa6N0jimc-T)$rnG3lo0gjRz z-+CyI#gPG&GD0n>p#<>t9FsKQ(jlK0h{#3 z($f1}LmwH;KBjwUvL^EVq0rJs$&z%V-D=Qf7EY5T5bae0h}siSOE6Mb4Wg#CE zG-6l?sG1)f4#by|nBNJ}9Xa;`mtIUsXTW_j{Z1G!X6c3CFQRo%VJ~ zeg~4&*2#E(${TXq9W3c(5;0N9QcAPj^gC;V8lky*1h_I9@xGrI^6m^G7td5iS$_V7BD#FGHes2^OQ1)@mcK zsumkyO1jEjE=CD0dl>p0=ED)Eu&yi@*+X^va?My9-sQV4^6(P7+HZKr-e14DvXY-U zg0HZ8cHbPGKKYui4VMA;iDBhsr|DyZJ<99@OjZt``75`Zt!6BY$_qj*%Ct*>Y-_VG zbKWjUg-%+C`Bvb#!~l@c)UD`lUsuP-zXLSnK?R^`$Vu7(EKL6)?QU6~hHGSrMs(HL0wZJwkZ0~imM2K} zC`v<`jyEryaz-m=hS-|wnmv@34lB9sv3#xyxW#Ne=!ZtHRPMc5$A>9Dt=6dJVqI?7 zLoZz8?~9dNEUXRteX0YYHYcL@6K_Tv)Q%yZ6s{E#2Ni?t&_ciYH`~k!Y0!$4Uw1QKpap>%0)Yzyu@~GS(FgA z60yvskDGo9!yzUjKucnQLVaW9#Ts-)P00^|O)A%0Q_1mH)u#weG!>iw605Y`Yv>|^ zthfL83(MczA4fPKq|gyd>7wP45$jSkXzPe$y?m+2qm@6d8uAi13h?p##-~;lEz2B= zH!ag##20~3AU+ZCkk$(aaJnr?g3&p)D!A|&5R75lkg~$F4(cp*Q#LHq5e$T>&lwDe z)|j^P93?d9)T-84L8Hh>kO?zPXV6fMji6qjfemj}&V)8*xFrT$)$`#Z`^fM-n=jvk z@H+|D-%s~U7*$vh5QJ0$q|E8}_b)OA0FeR8deAh)|GWLo1CwfivFR~VRdHsVau$0K zD*qBTrbpa(pEN#S>6|s=FoeByq6y?($K=u{v5B=Ek6sLus39FI)CRul^K^vrSS73+ zJ+i9jXtV{rVto*+diG5(`3sEPsb`Z5L&&RjV&b3b3;yZG%pM(wIK zS?05rv{r5Y-EaDo0H6(X3NMWcn7eTT%SE1KnpcGr4YoV^g87%Avksc&oP2RUX~eEqW>2B{P_Km5A=)`H!XIBU17!0;{IE5QQN7 zYlkFQKm{JCBtQ9Q#!QLeV?~+rD>NkA=r1uriCVrkdZCwbZ&?|;C0q<9TEw@YQzQ)) z*avHoj|bqS2~sl!zKe1dgAuxqVkDq&-F|w%!4c6Qd9GJM>Q>_~M`WLybxEFS>IaAw zB9$2DV~h(F7$CttQvqnwuoFv|Uc?B*%mb5!J|cLnoI=d1B?W z#7b2qhjfG+O-WS$;|~s*#^!7$Ca;_^zY!rTZhf=uKI3tuoOk*qeoahUaLtH&i(n@X z(hL`iaeLl(`}#!i00%`LqE|kMZW06L2pcpsU#N;I3;T5tru{q!?tJ(z4oUP#aOtN~ z?~}|UZoM-ro*}DU^c|g~y&T-FeDt5Guz zCI0L44x7g@_w=Vgu`vnS5Pcmwq959{SdI&~Qdo^znOobd23`6gx%gVMubx@{+j56#ia}S-G>f7MS{)E*!}5G0BU(Shj>27y3~_0H+O@{ z0xpyVpR(7Pl={$s_pr(LweP{mqT`>LF4}F-gEVP){|Qb5LYgv} z=x!|%2tQpX7QyXj?qWgA2z8M_D`ImisbBwJQeF(9Op(_8Y@*MnoqTQ;T4o2p8rSWF zRcd&P;xU9{OFVO2^oQB z8Q~bGo7B0M8)AeOGQ@|cs}f@3C1y$QO{n$L5IzOz1H<|oQ;^IPYLO@{mpd6LX@UHC zjVZUD@UqZj>%LAm4}v~n+i%b~%5@&h$c7HkrXvEXHett{0{T$9A#CpV)Bb6)%TT*z zY&@iCn9P??N*&6CR7>#qn~+*2u)0Md&m&Ax z`F&Js&WtD%ijY}Tq%a!&#?p_T2CJNQN2vCWysLo^3^mtuhQfNn)Q<6Tnd2eJHfo_2 zE6g6MM9*g`6+FE*FZNb`FOO41N723_mBrUjUq9dSK%KHXs!tMl*6zs^uvU+!%rr~g zORrYHTNIlu-U@PwdzW9D{o8%u0DEXvKMFqj(&|Fk7=c5-JV^8Y?O)b=!2Ts60C zP(3MxoEM;W;KP5vAYLZv4o;FM{jsEktdIymuKC@WZ*k5=qc179x`*qA>MNh`k=(iE z9c!W~k5bmBh;q zfQ?X!zzS35+9?#_x>cgx&=x-ss*w`#ySPV`N`f_1$x6MoCDq5(NjrA-sxGe8(7aMz z4zgn0q*fNcx3PoDgI(*Bo$CJ$!zP%>SV84205X6GaqVN{yvG0nn3m)1$Su1eJ_>N5 zQvvDU%)242pB*O&Q4?voYqnLenm#Siup9aSoExV8H#KX^!9hbquOi(v5V+kD(H8 z84D6c>{=$9uT9|>1C0V8lU{5(pW!#*x$ml-mLyglY(U!yxP$ebv2EUMw(k3kQgKf~ zQp+CK|LUUq+|FHW4oPvHt_q+6_pkn^e$T5Kw-j2Fw3qB&t7UT0JEav+4t+DvfY-+R znRzsw3XJgipF1Ivuj!V#rhAG35A&;)A7-d9+@ZI83-phT=cfVQXPf?ultPterv`3h z%!5|nanr=+?dkCysYnL&S7e*oEpm01#CpC& zvLk2afxq5!irk(ETXvXQ*+$@_rDLM!Czz1*0i?V> zcJgMv=6{(NwWPZ>=dGq-6?pq9<2oBs$O^#G4WRHrcFTj{n3Aw78p{5E0YP!gD>&AL zmN~SioBKiMSDxZGs%LW2hXVzah#4v3IDXt)Bdr~?3ErMZLc@DkC#p$p-gn-uYm{ws zBbV6+Wh!44269}_M7E<(PgA*bNj(xs+Tj0uO^M*RO`+rIpp%F zJMJ}qfw^9u)9c=st1bFwJ!ftni25V%tq(N42a{p9H0V9@J?H5;jQoTCP!t8f2MC|Qvi3yW;3eL11zp?PQfAd@^`E99DeIbV{cU~D7_$rYi zSAs=6uC|GZUJV7KZ%I zD$?L}OW=Wi;-?S92biQZ&f^+c0H?*hZ3Yn=gC<@H*p^2`q?8p6 zU{q?3P^#kMko50~Ptw$an6bRPb9&;m((iT??EQ;HGy2t#vV~O|2Dk-k$MCGC` zSI1DGZH-Dq-PS9^PR-y!o+kcGb7Z$;>+xyf!^2XECql0)AH6U4$`QX;h|cf#FEpO>~NQH}PcMW0M_=QDk>&EMFnV z;~;@<*r`UPQFH87ZR1_!nvPGSs*T71Vj}A#W+c+^7oAKaOM#Z#5b=XN!;QYq#-s z7mIgmZyg;xD5FhsBR?q(X0N{NTF@IFCl-iOlpE4>D^ZltD*E31jAB0YZqfO;%6Xr= zYF6BPU%$||?=)2(>0I^NW3iwW#lJrAVS%o6s_9z8P5pJ!GbHk(!}?DeIOZQ!`Ie%T zp7Jj?FUM{zB=VJIuVP8hCrChhhgMS-2>kDo2=vm*`jOefh5(l!oCTH-jFJsxa2R53 z8e;*RWaynX9GcpjXz@5!Mec&qO?n6J26*%QFJo^D#wKW4zJgPy@OO|686IV|$B9OzFH7oNA!CKj@7FJ`sWzLeHCbp6uk|GvAVm^My zv6?yHWR;;lt<@juAeeq%x2IV68IH(z$`hV&3l#=}n0((Cc#ql>^2u?XgQz!y-%Nl= z@k{&xxylrlluxj#4kY-3`zRS$tSb72S@BEVSZAp#${NE4Iq_OBE`r4>Ur9KeMjC9X z?rdJ#6?_(F1ATK}O=Le6KAd|Cx~rHFiJRVI<{E-%c@1J8`!X0%0nQ- z^UiTRvckSHx4-?f|Ly$=%EGnVk?MQ*4iBn78w(v)#umQ&?F!Ymy?jme`?F@OAgvHDo;6yk&>W-4aH5Q4RO)+@WD zn9Cb;KnedR@L6Rv7erfvlz-I!m)}Lo-`&*tnxQe0AjZ^L0*Fw|HW$zy%g3|$v34ik6_ zpM`)Sh{V|LN{n6)kJSC=fY=#hUnIs-eqEa3FA@wQ2{$$gAP_6_Fw;}^i_oBQ{`l!N zO!}FcmR4>cn}nJXlbGooR7px@?a{^gu-~@OzuzUs*?KDY0K-cD&^gk%Ri#^(mFxvu z(pDn)rFPhi#r&Xi#{>_oOMuo{X`4~hXUcw!z{(8Kp8h6Hmu&HUCU`$>UbP-W=l2g9 zP=+1qViM7xTQhu<>E4f7Xzry?!zegt>Q<}4CyPA+frLX>@ovoOue#dgJuc+s%=TVCNLI3E9_)nK!O5kT4wI0u* zS)yUpATkCc4||_)TtWpN(`$|39Hf6Vnp3J^o)P zROJlhP|4_(2;IK7?B4Z!I9O%2jT+oBh;3mYi&v1Fj14FaRz|Zbs__n8b|SVdURkw* zcC|tGDMm81C=-7zR4?2QkB#_aAef-b_F1HCW7`NTr9bqed}+qvrghwgvnw5z^QUN+;3lzPAg0pq5oK*-8RF z&nb$Oi=tqA{_cJxnMB$$Lz2SL0RDpVTo3?mPK3N{Q(8;|b~r<(cJjFr@$Rn~gFl>o zm$#Cz-Jhkb`J9jp(yTE5hTA9o0G4*d;)nG*JU;p43WJ$kr!|9>+_uR{2f;=#* zbe#?6Gs@Y$`pdN;?w$cypb&M$Q#2C_@?kX{CVT0Uz7eRGg-O@(0L49 zjk?sxbyZy9D7DiIuR9d}T12p7vfT z$W&VSaAv8|!br2#uQb-7^4HC&nJi^O!2^pp?AMwPQo)e&M-*1t&dB|z9CKi5{-HH~ zquPJw>p>~fx$es;SAu8x$`ofKrO1Pl1J&Kj5_hqD00EaK8?|CX1ZWaBRqe{tsRkhn zHgb}_h3|$3>Zw({IpG13U$t-$J+N-RIq`XzLT4(PCzhrNmaI#N?htVy09@~j2X)SY zWo~j*kU4vf01_E+4}4f6HIkcPJqRFH0f_>XFi0c=TaZJWPJ?}3PeefQq|<5kEbdXX zOy~?SJJhNbrMHDRo6U*jFML7V|F`n;}Zy8Dw+0Glxl( z6t990&+5aS`mfr>^lZ=WoM{xhT;|YMTKu25+_6QV4Vm063a&Sk-|pv6{{9gxzvR<0 z(f#RAChDKtY34i7ZnT}rt~vIoc=f4i?7pSAka4Vq7|$6>_KxmcXV!ZWB6&0Uc11Jq zV$c6%bNWZdz^p&5_(xo&v6jbRfh7Hh8Ol$p#;15LU3(wsa&Wftl8_2hC(AsMsEyZB zrWPTQUAX5Iz{67pqpz5Kruf?g;5-uv3DfvkT;@-*6ubbTN(2I8pNqQ=Nrxtc)Ex}g z19~Uu;((Ycr!PM~-L&H4WmSk^$EFDyV7pe!d>Rcx0o>Zw^8wQ`Izew<`;50Sc3=uX zz=W9j69f-jos|QuK=4WO&;d$;c6%orq~J2*Vh;h?<#8h3t0`*nlk-HrGC&dm^#{sA zM$%tLsr?x9ch4Wzvys5O&Go{}Y{x%MJQ~Qh2$kE;PtXf=NFVG^;n8lV7UvfxfeF86 zYXAvwMh2LK3FQ|d04-C(xSi( z@83!7u@w7X<1Z%Z%yNH(y(aX!6nkS^8Tg8D+I^xuw`9?R_Qm)$3?Wb5c^r5kF?PP3 zZNJP&26_&>j!Q32U7fhQUz#mpK2vm2iShfJhDzkQB_HJ;B0nl=?QwJ>-vpi{Djxh` zE38vN@3sd&+3z!~y#c|r0z5A1Vn-guCn0HNjJ*T+b&)}S=g!j9%rddI%&f5s{TChzMAd4yS#^^AAfx8?G>Eu zPesWo&uDOjw=Vpu@z0-2alPfP6@es*KwS}=P=AuD#s(*qj+XS7$GFC~`mKgtMh9IB=Ny8_X zHJAEYhZ-C|%-GqO7CCg8TK~X^F=Ge zcEPL6^(Il=et@8cPAHX zwbdgMgRjCuH;OZ4*Hx;tKR@|vtqCF1Ym9xUSN&+CU7Y z?fV!|{ZFwjslg)tR7!Rbqgg$rbz}l}ucH;cGBr6^Ae|7@{Sl=0c;9fCBEBAssGEGjQ)XJ{bfTzp-}#rh!xuK$ z@@qW&r;!UES%;C8L7mYkC)P1*?E%IgudNlM^Ir51R=puHqI|%`F2dhM(MV|sX9qOzGa+u zm=Y0ngA8d0TlQ{y<>}V=$29!hhlFb~j|@8+#sj}7`i zCnT!>kDx$@wO4E)iUjWKl3uKC$w&#wQ@(S^en`~YjO$yzysVc>=W5!)WMXUojRLTL&d_S7!*YVH0FK7ZWd zE28->#n@Nb>H90~p?U=(PxpB!Rr4t3HbWEdZY?F;;(ZW<*r{jd5x+VQaJn0AI5L4; z6ENTlrIS_p89P)quQPRV*uwjM75K9(=>PpkUeY4 zmel1x`ErUmxor3HDz-upLBm^ke7F(UbG;-<+vK&}^omOv_{`ZQufy^)5z~e9YT%c`PMLOx zb_OGFe(^_Q^pt-?Q$G}9Ut`|RkG&7N;uW1a6dLINyLL|*$fqsFG#THObyc+DdDWj{ zwI*f-I|8tJIB1{q^?};$YDK=@84f)dKM0IQsp5#fE(-@-xC401YD>_hi4sZodV7Dt(W$*4 zRpaA=@|&C$o~l6|e9OJpe#3u(t?e-EzU=XoQ(PEpTpv9oK5??J=u3$T2og`i=nCO6 zJx*A$>1rAEu2JMAd20b<;Gm|+r3+v?iXkkzIBv- zn=B(7$?GcCd%DNMEz-IFPtO#(w4n4iTM&CG@Dy7pba+MUP~AOQkc7rR?CZ%=*N-&5 zwHu*p&yfs&H}mRE zX?1{`l&hX5ZiLiIpodG0nBCT#8qOd? z#w-q~G4@xIJj*0TeGVu!bucv(>$F^4t2_5cRr2ToCLVLz?~BJOhG;S3m`t3?r=BxK zUnb#3z9Y@G9AI|bh?`u4h(>2Q*WrnDfgIBPu$BdQKOhPnwsav>#3Ij<25V)A$6V4# zqLi3SGzTf4B3$FL0NvsxhvzM=MZcr2?so;gRKijN3idEbY29Vn-rdm1I5DGk&(3jG z&84Ll{2Q5ZqJ=4cpRTpnM}lXAo29$HpE8Zag&3vLrH}fB&mX98gc58g&9>! zQSv_)TH~9_94?eic}wzg3F8W*Iv3Dk;AD{&1esLiIyUjdm_`>U9s?PiKmg-%s@^g+ z9-X+tryS>%YSfT!4rR43!xdb3AR74o-D_+Qlr%VH2%&Q&QAVd>bJgC#N>PwF55Lw5 zaYc>gy9_u)D{n__-qG2%RecZ-5^PzvuNhn#^o9v$&skPhW^4N2{7j95|CK(go6Rfu zZ7)7(Z{StN*h|h*^&OEomd8;ae-dp|2-TpdP)Uo=RVnVf%tCj5d$QjO7g?3s!erCu}l~DMGYc+s_6b7bop!=T&$gM$h&v z>#QCynGLtvsiz)r{b@Wg))nP$=wsy3bH+Vo`-jssm(IOMGof7UwMr^)v|xC;M#;Ro_fmhoDBwTt z>Jwfj#F?AdZcSS~|GxovpkcM@0Mg9m; zBg$|6@e2pMR}rF;T3-TD0-T85DT(pV1<$x&5cFq306EWLWHoR+DM&G`u7U$mLHb$w zQYU}`Cc~J313DRnotjWWtYZ-P82o##e4;}jaD23SI(o&VA0udM;oh6#+DD5q`5F5< z5R<~<(<_H>bPt~8w#xX;rjYpn`IdD;zEluEwRI_u3FDvOCqTh&%o-XA^sw4$CsEUu z!te2^NB^ir`G!iSU6%vD`l_BlKCfx*-KQI0&EIH+1@*MnQwXUYO&E_$I*p4w;B(~p zHfFJ(8r8tp^;$ldgSC%Q|Gk(l$sak>dsV)z%gwjfOGQM>D2~62VV1W{g%Wf+Y0}R@2b7x1>W+kpZJ*1PoLE{t4=KoWKvb2nze5%jr=$69#Ou~ zw4~+P`O%WYmhW-oa{N}Rtx8+RZ~eIh^Eo!IOC}F0@=6XQv#y#}Jv+~e$_fGqEwX6b zeTo;$$Ij69cRxj{Gj3Jj_Y#fY&%xtD{y~o%ah&T^W~sM+ua{d^cJ8|{twHwFOS7#n|n4e1#G z;(_&YTDVhDRyQ9swet7D3`iP?1)lOCfEpj5!-V4N5k4YE5kY3e^q_I-b z-h@lRr(RG5C%y^EQJDZJ8MB~zaOU}MOPEXXPLKHBCVXNPR~Yp0&`|@+SDTv%N;XP^ z8N}kNRkIPHtF6k!5Oe{h+ljQR7LtmZ^lD4=2gKuhL2INAqg~68V30Co1Ad*(6C4R- zkmfmt77hJR^N|~>Nv8fzIlgAeTIuzHdmVM_eck(+b~r!O*c41GdK$OD(OP-0vh*>3 zBbZdL^`oHSgv0_5L`E@?Bq$H|LkU$caj!LCc?5>8fLiL5< zjAX;0m*BS)ZNkA`y0w4|hD$`od)@33R$5LBYcMik$hbW23x|TDO!tLRzp)x08?cph zEE>Z7Izqh9u!PXh+Lt8aL$twj>xTx+YdU2}dA3aGPe%gQ*2-l_h_ID(_I* z^_iLN972ZKFde}~0ut6^RdGf~`%+j?tmoVpB|FVCtA*4_9dpZ6tr92q`|HPpAe2}a zU#MHp(WURX=iK4NCnMN_!Ism~uH3Ge;?!%W;e3ll0?h(!w-vMh+wzq~b%E#p-FrvZ zt(vL!nEQr0%qyswLhg!7r=W~-HC~zU+1Br@%Q|1rfR+Wp-n1R4Pj!|4dyvkt&pO?t z*3A->dyGF_-6tKJ?>81tyb@9|fc^a5=vX>gfr@)p)%J3F)_a^Q4`4NTRiP3hNOw|V zmc(M*Zu778dHUTspV9fyiLA^8nSO49L}_(bw`V}+zvRBSDPZVgtDM)omq3>s2IB+F znu)==go*F9OA7?1sWk9`E%1t<2v8O;dd({>Nf2`V;4bS`9~2T16H(=(?_uDbifCc# zW5$(FmhCt|OtDVPWo(Th-&25U8k~i9sc05f^@f5upGg3O2o@nG2F%s3*$9~E=pEnGlqZW@H94j5p&?O08jP!UD(a8&fjZKX+-tTcMM!_A> zWIHO-jLz8#&1Q>x-L``9SWMUPJb^92f?v6P7?^c^aH$*r*#crV%j&%aSCp|Z{g;6N zj`u)i^SMjLY+hiC=Ac?>cPER)WYz9Su(0JOr9 zk6q=qI5p-%=4z6s*AGFkj&JsrJ%y64`4rk?5BYq?~ zABW!#rq~m{4DF>1)7UEXuQ@b+5w0-w%w`|eNJ1!gHeOKH zNJ0ev-e7eSI`cSuX*g#Sa927aHoy)QdKj`1{$9*Ds4@O zE%0a-C*x}O*)cvm77C_b_>hJ)vn5!={7?$Is6=3Eh;ZjJ#z^GGykH#6_$ZNe`nli81@7|HB~C&&7(0hRI+?n?@M zk5iSG6j3`I;+-}~P3eu z%S~2(jWg>8p`4li7mG9KL1K%IQkQRtPO(6)YEB=o^MPBIbp{W2TG866RINKgq0)>2 za&4UxxYeX>Fzm~e8r#41N%-2#+OO?L%IrkNg5Cc;LI@aC$l!|I^DVYUYAZ`F!NHobu%pS{aJ7t zS-5|T{OonN$L!_0I=(vk%DbGluN>2#A#j|>RsKoqMf->_W`oO2I>M&fpTJD2f%h#_ z%}J;ae75L7-GE8~`iId*y^TWNd@@1_1Aus)4&|4zsaeDZTf~_SvEh zhu{s|{v)gvJ5Mhj)V|(hX+WRg?|=_h9@w1{wIweTK+=L*g4<7>_@Pgw2+vQjYZ6tBA4^<^2BhjGa`TZrZ93h*Dq9Jdp6h_r!R8H zH)7Sx<8|pGgndeD3*|Y}b}gY@u2pF>ip`0RT#wVY0R6Wbc ziWiIZ8N6Q8_btgNB5##n9?HTBUCujMP2+7lE$F>dp>6jfVx?K8ozYH&zDpuWEuAPM z3w=(nAgK&~otsz@+i4f3*m|@?5@9r&@ts-u7b1js?SYb)3&pj=bk-+&r-=5CwEh9)nE; z97prvsL}Xf&ZyL@q&S58oDpDCRY`;*03M(QAjjjgTo+mlf*(52>3#Bf6a^f(y8%P2 z@d}yxsu;onp|}Vje}m_na zAA@Vp^Mf@)0xHb6>b-lXU?`huehk`3rs6`s2Qz!AFdBbh#v9J=^tsOpcB{=v*S_`1n}BUR@Ay~ZO>I|<7o$AlcF?DFmJ*&8s}!=bp2z5

    XW&Ke{sq0c>n|{_vZluA6168S{9`w#U;e-dEAv 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

    !eBHRh{Z&*8X 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 GpbY?!1M>3# literal 0 HcmV?d00001 diff --git a/src/platform/wii/wiihardware.cpp b/src/platform/wii/wiihardware.cpp index 3a3e689..e58a658 100644 --- a/src/platform/wii/wiihardware.cpp +++ b/src/platform/wii/wiihardware.cpp @@ -1,3 +1,8 @@ +/**************************************************************************** + * DOSBox Wii + * Tantric 2009 + ***************************************************************************/ + #include #include #include @@ -8,13 +13,200 @@ #include #include #include +#include +#include + #include "wiihardware.h" +#include "FreeTypeGX.h" +#include "filelist.h" +#include "SDL_events.h" + +extern "C" { +extern void __exception_setreload(int t); +extern void WII_AudioStart(); +extern void WII_AudioStop(); +extern void WII_VideoStart(); +extern void WII_VideoStop(); +} + +void MAPPER_CheckEvent(SDL_Event * event); +void HomeMenu(); char appPath[1024]; +char dosboxCommand[1024] = { 0 }; +static lwp_t keythread = LWP_THREAD_NULL; +static char shiftkey[100]; + +/**************************************************************************** + * SwitchAudioMode + * + * Switches between menu sound and emulator sound + ***************************************************************************/ +static void SwitchAudioMode(int mode) +{ + if(mode == 0) // emulator + { + ASND_Pause(1); + AUDIO_StopDMA(); + WII_AudioStart(); + } + else // menu + { + WII_AudioStop(); + ASND_Init(); + ASND_Pause(0); + } +} + +static void * PressKeys (void *arg) +{ + SDL_Event event; + int shift; + u16 i; + + memset(shiftkey, 0, sizeof(shiftkey)); + shiftkey[33] = 49; + shiftkey[34] = 39; + shiftkey[35] = 51; + shiftkey[36] = 52; + shiftkey[37] = 53; + shiftkey[38] = 55; + shiftkey[40] = 57; + shiftkey[41] = 48; + shiftkey[42] = 56; + shiftkey[58] = 59; + shiftkey[60] = 44; + shiftkey[62] = 46; + shiftkey[63] = 47; + shiftkey[64] = 50; + shiftkey[94] = 54; + shiftkey[95] = 45; + + while(1) + { + LWP_SuspendThread(keythread); + usleep(1200); + + for(i=0; i= 65 && dosboxCommand[i] <= 90)) + { + dosboxCommand[i] += 32; + shift = 1; + } + else if(dosboxCommand[i] > 0 && dosboxCommand[i] < 100 && + shiftkey[(int)dosboxCommand[i]] > 0) + { + dosboxCommand[i] = shiftkey[(int)dosboxCommand[i]]; + shift = 1; + } + + if(shift) + { + event.type = SDL_KEYDOWN; + event.key.keysym.sym = SDLK_LSHIFT; + MAPPER_CheckEvent(&event); + usleep(600); + } + + event.type = SDL_KEYDOWN; + event.key.keysym.sym = (SDLKey)dosboxCommand[i]; + MAPPER_CheckEvent(&event); + usleep(600); + + event.type = SDL_KEYUP; + event.key.keysym.sym = (SDLKey)dosboxCommand[i]; + MAPPER_CheckEvent(&event); + usleep(600); + + if(shift) + { + event.type = SDL_KEYUP; + event.key.keysym.sym = SDLK_LSHIFT; + MAPPER_CheckEvent(&event); + usleep(600); + } + } + + event.type = SDL_KEYDOWN; + event.key.keysym.sym = SDLK_RETURN; + MAPPER_CheckEvent(&event); + usleep(600); + + event.type = SDL_KEYUP; + event.key.keysym.sym = SDLK_RETURN; + MAPPER_CheckEvent(&event); + usleep(600); + + dosboxCommand[0] = 0; + } + return NULL; +} + +/**************************************************************************** + * USB Gecko Debugging + ***************************************************************************/ + +static bool gecko = false; +static mutex_t gecko_mutex = 0; + +static ssize_t __out_write(struct _reent *r, int fd, const char *ptr, size_t len) +{ + u32 level; + + if (!ptr || len <= 0 || !gecko) + return -1; + + LWP_MutexLock(gecko_mutex); + level = IRQ_Disable(); + usb_sendbuffer(1, ptr, len); + IRQ_Restore(level); + LWP_MutexUnlock(gecko_mutex); + return len; +} + +const devoptab_t gecko_out = { + "stdout", // device name + 0, // size of file structure + NULL, // device open + NULL, // device close + __out_write,// device write + NULL, // device read + NULL, // device seek + NULL, // device fstat + NULL, // device stat + NULL, // device link + NULL, // device unlink + NULL, // device chdir + NULL, // device rename + NULL, // device mkdir + 0, // dirStateSize + NULL, // device diropen_r + NULL, // device dirreset_r + NULL, // device dirnext_r + NULL, // device dirclose_r + NULL // device statvfs_r +}; + +void USBGeckoOutput() +{ + LWP_MutexInit(&gecko_mutex, false); + gecko = usb_isgeckoalive(1); + + devoptab_list[STD_OUT] = &gecko_out; + devoptab_list[STD_ERR] = &gecko_out; +} void WiiInit() { + //USBGeckoOutput(); // uncomment to enable USB gecko output + __exception_setreload(8); fatInitDefault(); + ASND_Init(); + InitFreeType((u8*)font_ttf, font_ttf_size); + LWP_CreateThread (&keythread, PressKeys, NULL, NULL, 0, 65); appPath[0] = 0; } @@ -43,6 +235,24 @@ void CreateAppPath(char origpath[]) free(path); } +void WiiMenu() +{ + // wait for thread to finish + while(!LWP_ThreadIsSuspended(keythread)) + usleep(100); + + WII_VideoStop(); + SwitchAudioMode(1); + + HomeMenu(); + + WII_VideoStart(); + SwitchAudioMode(0); + + if(dosboxCommand[0] != 0) + LWP_ResumeThread(keythread); +} + void WiiFinished() { diff --git a/src/platform/wii/wiihardware.h b/src/platform/wii/wiihardware.h index 6923f06..736147d 100644 --- a/src/platform/wii/wiihardware.h +++ b/src/platform/wii/wiihardware.h @@ -1,11 +1,18 @@ +/**************************************************************************** + * DOSBox Wii + * Tantric 2009 + ***************************************************************************/ + #ifndef WIIHARDWARE_H #define WIIHARDWARE_H #include void WiiInit(); +void WiiMenu(); void CreateAppPath(char origpath[]); void WiiFinished(); extern char appPath[1024]; +extern char dosboxCommand[1024]; #endif diff --git a/src/platform/wii/wiivideo.cpp b/src/platform/wii/wiivideo.cpp new file mode 100644 index 0000000..1280619 --- /dev/null +++ b/src/platform/wii/wiivideo.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** + * DOSBox Wii Home Menu + * Tantric 2009 + * + * video.cpp + * + * Video routines + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "input.h" + +extern unsigned int *xfb[2]; +extern int whichfb; +extern GXRModeObj* vmode; + +static Mtx GXmodelView2D; +u32 FrameTimer = 0; +int screenheight = 480; +int screenwidth = 640; + +/**************************************************************************** + * ResetVideo_Menu + * + * Reset the video/rendering mode for the menu +****************************************************************************/ +void ResetVideo_Menu () +{ + Mtx44 p; + f32 yscale; + u32 xfbHeight; + + // clears the bg to color and clears the z buffer + GXColor background = {0, 0, 0, 255}; + GX_SetCopyClear (background, 0x00ffffff); + + yscale = GX_GetYScaleFactor(vmode->efbHeight,vmode->xfbHeight); + xfbHeight = GX_SetDispCopyYScale(yscale); + GX_SetScissor(0,0,vmode->fbWidth,vmode->efbHeight); + GX_SetDispCopySrc(0,0,vmode->fbWidth,vmode->efbHeight); + GX_SetDispCopyDst(vmode->fbWidth,xfbHeight); + GX_SetCopyFilter(vmode->aa,vmode->sample_pattern,GX_TRUE,vmode->vfilter); + GX_SetFieldMode(vmode->field_rendering,((vmode->viHeight==2*vmode->xfbHeight)?GX_ENABLE:GX_DISABLE)); + + if (vmode->aa) + GX_SetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR); + else + GX_SetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR); + + // setup the vertex descriptor + // tells the flipper to expect direct data + GX_ClearVtxDesc(); + GX_InvVtxCache (); + GX_InvalidateTexAll(); + + GX_SetVtxDesc(GX_VA_TEX0, GX_NONE); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxDesc (GX_VA_CLR0, GX_DIRECT); + + GX_SetVtxAttrFmt (GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); + GX_SetVtxAttrFmt (GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); + GX_SetZMode (GX_FALSE, GX_LEQUAL, GX_TRUE); + + GX_SetNumChans(1); + GX_SetNumTexGens(1); + GX_SetTevOp (GX_TEVSTAGE0, GX_PASSCLR); + GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + + guMtxIdentity(GXmodelView2D); + guMtxTransApply (GXmodelView2D, GXmodelView2D, 0.0F, 0.0F, -50.0F); + GX_LoadPosMtxImm(GXmodelView2D,GX_PNMTX0); + + guOrtho(p,0,479,0,639,0,300); + GX_LoadProjectionMtx(p, GX_ORTHOGRAPHIC); + + GX_SetViewport(0,0,vmode->fbWidth,vmode->efbHeight,0,1); + GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); + GX_SetAlphaUpdate(GX_TRUE); +} + +/**************************************************************************** + * Menu_Render + * + * Renders everything current sent to GX, and flushes video + ***************************************************************************/ +void Menu_Render() +{ + whichfb ^= 1; // flip framebuffer + GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); + GX_SetColorUpdate(GX_TRUE); + GX_CopyDisp(xfb[whichfb],GX_TRUE); + VIDEO_SetNextFramebuffer(xfb[whichfb]); + VIDEO_Flush(); + VIDEO_WaitVSync(); + FrameTimer++; +} + +/**************************************************************************** + * Menu_DrawImg + * + * Draws the specified image on screen using GX + ***************************************************************************/ +void Menu_DrawImg(f32 xpos, f32 ypos, u16 width, u16 height, u8 data[], + f32 degrees, f32 scaleX, f32 scaleY, u8 alpha) +{ + if(data == NULL) + return; + + GXTexObj texObj; + guVector axis = (guVector) {0, 0, 1 }; + + GX_InitTexObj(&texObj, data, width,height, GX_TF_RGBA8,GX_CLAMP, GX_CLAMP,GX_FALSE); + GX_LoadTexObj(&texObj, GX_TEXMAP0); + GX_InvalidateTexAll(); + + GX_SetTevOp (GX_TEVSTAGE0, GX_MODULATE); + GX_SetVtxDesc (GX_VA_TEX0, GX_DIRECT); + + Mtx m,m1,m2, mv; + width *=.5; + height*=.5; + guMtxIdentity (m1); + guMtxRotAxisDeg (m2, &axis, degrees); + guMtxScaleApply(m1,m1,scaleX,scaleY,1.0); + guMtxConcat(m2,m1,m); // change me? + + guMtxTransApply(m,m, xpos+width,ypos+height,0); + guMtxConcat (GXmodelView2D, m, mv); + GX_LoadPosMtxImm (mv, GX_PNMTX0); + + GX_Begin(GX_QUADS, GX_VTXFMT0,4); + GX_Position3f32(-width, -height, 0); + GX_Color4u8(0xFF,0xFF,0xFF,alpha); + GX_TexCoord2f32(0, 0); + + GX_Position3f32(width, -height, 0); + GX_Color4u8(0xFF,0xFF,0xFF,alpha); + GX_TexCoord2f32(1, 0); + + GX_Position3f32(width, height, 0); + GX_Color4u8(0xFF,0xFF,0xFF,alpha); + GX_TexCoord2f32(1, 1); + + GX_Position3f32(-width, height, 0); + GX_Color4u8(0xFF,0xFF,0xFF,alpha); + GX_TexCoord2f32(0, 1); + GX_End(); + GX_DrawDone(); + GX_LoadPosMtxImm (GXmodelView2D, GX_PNMTX0); + + GX_SetTevOp (GX_TEVSTAGE0, GX_PASSCLR); + GX_SetVtxDesc (GX_VA_TEX0, GX_NONE); +} + +/**************************************************************************** + * Menu_DrawRectangle + * + * Draws a rectangle at the specified coordinates using GX + ***************************************************************************/ +void Menu_DrawRectangle(f32 x, f32 y, f32 width, f32 height, GXColor color, u8 filled) +{ + u8 fmt; + long n; + int i; + f32 x2 = x+width; + f32 y2 = y+height; + guVector v[] = {{x,y,0.0f}, {x2,y,0.0f}, {x2,y2,0.0f}, {x,y2,0.0f}, {x,y,0.0f}}; + + if(!filled) + { + fmt = GX_LINESTRIP; + n = 5; + } + else + { + fmt = GX_TRIANGLEFAN; + n = 4; + } + + GX_Begin(fmt, GX_VTXFMT0, n); + for(i=0; i + +void ResetVideo_Menu(); +void TakeScreenshot(); +void Menu_Render(); +void Menu_DrawImg(f32 xpos, f32 ypos, u16 width, u16 height, u8 data[], f32 degrees, f32 scaleX, f32 scaleY, u8 alphaF ); +void Menu_DrawRectangle(f32 x, f32 y, f32 width, f32 height, GXColor color, u8 filled); + +extern int screenheight; +extern int screenwidth; +extern u8 * screenTex; +extern u32 FrameTimer; + +#endif