/* NitroHax -- Cheat tool for the Nintendo DS Copyright (C) 2008 Michael "Chishm" Chisholm This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "ui.h" #include "bios_decompress_callback.h" #include "font.h" #include "bgtop.h" #include "bgsub.h" #include "bgtopTWL.h" #include "bgsubTWL.h" #include "scrollbar.h" #include "cursor.h" #include "textbox.h" #include "button_off.h" #include "button_on.h" #include "button_folder.h" #include "button_file.h" #include "button_go.h" #define MAX_CHARS_PER_SCREEN 768 #define CONSOLE_SCREEN_WIDTH 32 #define CONSOLE_SCREEN_HEIGHT 24 #define CONSOLE_SUB_START_ROW 24 #define MAP_SIZE 1024 #define FIRST_ROW 0 #define FIRST_COL 0 #define LAST_ROW (CONSOLE_SCREEN_HEIGHT - 1) #define LAST_COL (CONSOLE_SCREEN_WIDTH - 1) #define MENU_LAST_COL 28 #define MENU_NUM_ITEMS 10 #define MENU_FIRST_ROW 0 #define MENU_FIRST_COL 0 #define MENU_LAST_ROW (MENU_FIRST_ROW + (MENU_NUM_ITEMS - 1)) #define MENU_CONTROLS_ROW 11 #define TITLE_BOX_START_ROW 8 #define TITLE_BOX_END_ROW 12 #define TEXT_BOX_START_ROW 13 #define TEXT_BOX_END_ROW 23 #define TOUCH_GRID_SIZE 16 #define SCROLLBAR_START MENU_FIRST_ROW #define SCROLLBAR_END (MENU_FIRST_ROW + MENU_NUM_ITEMS - 1) #define SCROLLBAR_COL 15 #define GO_BUTTON_COL 8 #define GO_BUTTON_ROW 21 #define GO_BUTTON_WIDTH 16 #define GO_BUTTON_HEIGHT 3 #define CURSOR_VERTICAL_OFFSET (-8) // Compensate for position of cursor image within bitmap file #define TILE_SIZE 32 #define PALETTE_SIZE 16 // button combinations for the menus #define BUTTON_LINE_UP KEY_UP #define BUTTON_LINE_DOWN KEY_DOWN #define BUTTON_PAGE_UP KEY_L #define BUTTON_PAGE_DOWN KEY_R #define BUTTON_SELECT KEY_A #define BUTTON_BACK KEY_B #define BUTTON_EXIT KEY_START #define BUTTON_ENABLE_ALL KEY_X #define BUTTON_DISABLE_ALL KEY_Y #define MENU_PAGE_SCROLL 6 #define CHEAT_MENU_FOLDER_UP -1 const char CHEAT_MENU_FOLDER_UP_NAME[] = " [..]"; UserInterface ui; static void vramcpy (volatile void* dest, const void* src, int size) { vu16* destination = (vu16*)dest; const u16* source = (u16*)src; while (size > 0) { *destination++ = *source++; size-=2; } } UserInterface::UserInterface (void) { videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE | DISPLAY_BG2_ACTIVE); // BG0 = backdrop // BG1 = text box background & border // BG2 = text vramSetBankA (VRAM_A_MAIN_BG_0x06000000); REG_BG0CNT = BG_MAP_BASE(0) | BG_COLOR_16 | BG_TILE_BASE(2) | BG_PRIORITY(2); REG_BG1CNT = BG_MAP_BASE(2) | BG_COLOR_16 | BG_TILE_BASE(4) | BG_PRIORITY(1); REG_BG2CNT = BG_MAP_BASE(4) | BG_COLOR_16 | BG_TILE_BASE(6) | BG_PRIORITY(0); BG_PALETTE[0]=0; BG_PALETTE[255]=0xffff; videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE | DISPLAY_BG2_ACTIVE | DISPLAY_SPR_ACTIVE | DISPLAY_SPR_1D); // BG0 = backdrop // BG1 = scrollbar & highlights // BG2 = text vramSetBankC (VRAM_C_SUB_BG_0x06200000); REG_BG0CNT_SUB = BG_MAP_BASE(0) | BG_COLOR_16 | BG_TILE_BASE(2) | BG_PRIORITY(2); REG_BG1CNT_SUB = BG_MAP_BASE(2) | BG_COLOR_16 | BG_TILE_BASE(4) | BG_PRIORITY(1); REG_BG2CNT_SUB = BG_MAP_BASE(4) | BG_COLOR_16 | BG_TILE_BASE(6) | BG_PRIORITY(0); // Set up background image if ((REG_SCFG_EXT & BIT(31))) { swiDecompressLZSSVram ((void*)bgtopTWLTiles, (void*)CHAR_BASE_BLOCK(2), 0, &decompressBiosCallback); swiDecompressLZSSVram ((void*)bgsubTWLTiles, (void*)CHAR_BASE_BLOCK_SUB(2), 0, &decompressBiosCallback); vramcpy (&BG_PALETTE[0], bgtopTWLPal, bgtopTWLPalLen); vramcpy (&BG_PALETTE_SUB[0], bgsubTWLPal, bgsubTWLPalLen); } else { swiDecompressLZSSVram ((void*)bgtopTiles, (void*)CHAR_BASE_BLOCK(2), 0, &decompressBiosCallback); swiDecompressLZSSVram ((void*)bgsubTiles, (void*)CHAR_BASE_BLOCK_SUB(2), 0, &decompressBiosCallback); vramcpy (&BG_PALETTE[0], bgtopPal, bgtopPalLen); vramcpy (&BG_PALETTE_SUB[0], bgsubPal, bgsubPalLen); } u16* bgMapTop = (u16*)SCREEN_BASE_BLOCK(0); u16* bgMapSub = (u16*)SCREEN_BASE_BLOCK_SUB(0); for (int i = 0; i < CONSOLE_SCREEN_WIDTH*CONSOLE_SCREEN_HEIGHT; i++) { bgMapTop[i] = (u16)i; bgMapSub[i] = (u16)i; } // Copy text box for BG1 - use palette 3 vramcpy (&BG_PALETTE[TEXTBOX_PALETTE * PALETTE_SIZE], textboxPal, textboxPalLen); vramcpy ((void*)(CHAR_BASE_BLOCK(4) + TEXTBOX_OFFSET * TILE_SIZE), textboxTiles, textboxTilesLen); textboxMap = (u16*)SCREEN_BASE_BLOCK(2); // Clear tile 0 vu16 *const tile_0 = (vu16*) BG_TILE_RAM(4); for (int i = 0; i < TILE_SIZE/2; i++) { tile_0[i] = 0; } clearBox(); // Copy the font into top screen's tile base for BG2 u16* fontDestMain = (u16*)CHAR_BASE_BLOCK(6); swiDecompressLZSSVram ((void*)fontTiles, fontDestMain, 0, &decompressBiosCallback); vramcpy (&BG_PALETTE[FONT_PALETTE * PALETTE_SIZE], fontPal, fontPalLen); // Copy the font into sub screen's tile base for BG2 u16* fontDestSub = (u16*)CHAR_BASE_BLOCK_SUB(6); swiDecompressLZSSVram ((void*)fontTiles, fontDestSub, 0, &decompressBiosCallback); vramcpy (&BG_PALETTE_SUB[FONT_PALETTE * PALETTE_SIZE], fontPal, fontPalLen); BG_OFFSET_SUB[2].x = -4; BG_OFFSET_SUB[2].y = -4; vu16 *const fontMapTop = (u16*)SCREEN_BASE_BLOCK(4); vu16 *const fontMapSub = (u16*)SCREEN_BASE_BLOCK_SUB(4); // Initialise consoles topText = new ConsoleText (CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT, ConsoleText::CHAR_SIZE_8PX, fontMapTop, FONT_PALETTE); subText = new ConsoleText (MENU_LAST_COL - MENU_FIRST_COL + 1, CONSOLE_SCREEN_HEIGHT, ConsoleText::CHAR_SIZE_8PX, fontMapSub, FONT_PALETTE); for (int i = 0; i < MAP_SIZE; i++) { fontMapSub[i] = 0; } // Set up the GUI BG vu16 *const tileDestSub = (vu16*)CHAR_BASE_BLOCK_SUB(4); guiSubMap = (vu16*)SCREEN_BASE_BLOCK_SUB(2); BG_OFFSET[1].x = 0; BG_OFFSET[1].y = 0; // Create a double size blank tile for (int i = 0; i < 4 * TILE_SIZE; i++) { tileDestSub[i] = 0; } // Clear GUI tile map for (int i = 0; i < CONSOLE_SCREEN_WIDTH * CONSOLE_SCREEN_HEIGHT; i++) { guiSubMap[i] = 0; } // Load scroll bar data, use palette 1 vramcpy (&tileDestSub[SCROLLBAR_OFFSET * TILE_SIZE / 2], scrollbarTiles, scrollbarTilesLen); vramcpy (&BG_PALETTE_SUB[SCROLLBAR_PALETTE * PALETTE_SIZE], scrollbarPal, scrollbarPalLen); // Load button backgrounds vramcpy (&tileDestSub[BUTTON_BG_FOLDER * TILE_SIZE / 2], button_folderTiles, button_folderTilesLen); vramcpy (&BG_PALETTE_SUB[BUTTON_PALETTE_FOLDER * PALETTE_SIZE], button_folderPal, button_folderPalLen); vramcpy (&tileDestSub[BUTTON_BG_ON * TILE_SIZE / 2], button_onTiles, button_onTilesLen); vramcpy (&BG_PALETTE_SUB[BUTTON_PALETTE_ON * PALETTE_SIZE], button_onPal, button_onPalLen); vramcpy (&tileDestSub[BUTTON_BG_OFF * TILE_SIZE / 2], button_offTiles, button_offTilesLen); vramcpy (&BG_PALETTE_SUB[BUTTON_PALETTE_OFF * PALETTE_SIZE], button_offPal, button_offPalLen); vramcpy (&tileDestSub[BUTTON_BG_FILE * TILE_SIZE / 2], button_fileTiles, button_fileTilesLen); vramcpy (&BG_PALETTE_SUB[BUTTON_PALETTE_FILE * PALETTE_SIZE], button_filePal, button_filePalLen); // Load go button vramcpy (&tileDestSub[GO_BUTTON_OFFSET * TILE_SIZE / 2], button_goTiles, button_goTilesLen); vramcpy (&BG_PALETTE_SUB[GO_BUTTON_PALETTE * PALETTE_SIZE], button_goPal, button_goPalLen); // Erase any text already on screen topText->clearText(); subText->clearText(); // Set up sub screen sprites vramSetBankD (VRAM_D_SUB_SPRITE); Sprite::init(); for (int i = 0; i < NUM_CURSORS; i++) { cursor[i] = new Sprite (64, 32, (const u16*)&(cursorTiles[256 * i]), cursorPal); } scrollbox = new Sprite (16, 16, (const u16*)&(scrollbarTiles[96]), scrollbarPal); showScrollbar (false); } UserInterface::~UserInterface () { delete scrollbox; delete topText; delete subText; for (int i = 0; i < NUM_CURSORS; i++) { delete cursor[i]; } } void UserInterface::putGuiTile (int val, int row, int col, int palette, bool doubleSize) { u16 pal = (u16)(palette << 12); if (!doubleSize) { guiSubMap[col + row * CONSOLE_SCREEN_WIDTH] = pal | (u16)val; } else { guiSubMap[col + row * CONSOLE_SCREEN_WIDTH] = pal | (u16)(val); guiSubMap[(col + 1) + row * CONSOLE_SCREEN_WIDTH] = pal | (u16)(val + 1); guiSubMap[col + (row + 1) * CONSOLE_SCREEN_WIDTH] = pal | (u16)(val + 2); guiSubMap[(col + 1) + (row + 1) * CONSOLE_SCREEN_WIDTH] = pal | (u16)(val + 3); } } void UserInterface::showScrollbar (bool visible) { if (visible) { for (int i = SCROLLBAR_START + 1; i <= SCROLLBAR_END - 1; i++) { // vertical bar putGuiTile (SCROLLBAR_BAR, i*2, SCROLLBAR_COL * 2, SCROLLBAR_PALETTE, true); } // up arrow putGuiTile (SCROLLBAR_UP, SCROLLBAR_START*2, SCROLLBAR_COL * 2, SCROLLBAR_PALETTE, true); // down arrow putGuiTile (SCROLLBAR_DOWN, SCROLLBAR_END*2, SCROLLBAR_COL * 2, SCROLLBAR_PALETTE, true); } else { for (int i = SCROLLBAR_START; i <= SCROLLBAR_END ; i++) { // vertical bar putGuiTile (BLANK_TILE, i*2, SCROLLBAR_COL * 2, 0, true); } } scrollboxPosition = (SCROLLBAR_START + 1) * TOUCH_GRID_SIZE; scrollbox->setPosition (scrollboxPosition, (CONSOLE_SCREEN_WIDTH - 2) * 8); scrollbox->showSprite (visible); } void UserInterface::setScrollbarPosition (int offset, int listLength) { scrollboxPosition = ((SCROLLBAR_END - SCROLLBAR_START - 2) * TOUCH_GRID_SIZE * offset / listLength) + (SCROLLBAR_START + 1) * TOUCH_GRID_SIZE; scrollbox->setPosition (scrollboxPosition, (CONSOLE_SCREEN_WIDTH - 2) * 8); } void UserInterface::showCursor (bool visible) { for (int i = 0; i < NUM_CURSORS; i++) { cursor[i]->setPosition (16, 64 * i); cursor[i]->showSprite (visible); } } void UserInterface::setCursorPosition (int offset) { for (int i = 0; i < NUM_CURSORS; i++) { cursor[i]->setPosition (offset + CURSOR_VERTICAL_OFFSET, 64 * i); } } void UserInterface::drawBox (int startRow, int startCol, int endRow, int endCol) { for (int i = startCol + 1; i < endCol; i++) { textboxMap[startRow * CONSOLE_SCREEN_WIDTH + i] = TEXTBOX_N | (TEXTBOX_PALETTE << 12); textboxMap[endRow * CONSOLE_SCREEN_WIDTH + i] = TEXTBOX_S | (TEXTBOX_PALETTE << 12); } for (int i = startRow + 1; i < endRow; i++) { textboxMap[i * CONSOLE_SCREEN_WIDTH + startCol] = TEXTBOX_W | (TEXTBOX_PALETTE << 12); textboxMap[i * CONSOLE_SCREEN_WIDTH + endCol] = TEXTBOX_E | (TEXTBOX_PALETTE << 12); } textboxMap[startRow * CONSOLE_SCREEN_WIDTH + startCol] = TEXTBOX_NW | (TEXTBOX_PALETTE << 12); textboxMap[startRow * CONSOLE_SCREEN_WIDTH + endCol] = TEXTBOX_NE | (TEXTBOX_PALETTE << 12); textboxMap[endRow * CONSOLE_SCREEN_WIDTH + startCol] = TEXTBOX_SW | (TEXTBOX_PALETTE << 12); textboxMap[endRow * CONSOLE_SCREEN_WIDTH + endCol] = TEXTBOX_SE | (TEXTBOX_PALETTE << 12); for (int i = startRow + 1; i < endRow; i++) { for (int j = startCol + 1; j < endCol; j++) { textboxMap[i * CONSOLE_SCREEN_WIDTH + j] = TEXTBOX_C | (TEXTBOX_PALETTE << 12); } } } void UserInterface::clearBox (int startRow, int startCol, int endRow, int endCol) { for (int i = startRow; i <= endRow; i++) { for (int j = startCol; j <= endCol; j++) { textboxMap[i * CONSOLE_SCREEN_WIDTH + j] = 0 | (TEXTBOX_PALETTE << 12); } } } void UserInterface::clearBox (void) { for (int i = 0; i < CONSOLE_SCREEN_WIDTH*CONSOLE_SCREEN_HEIGHT; i++) { textboxMap[i] = 0 | (TEXTBOX_PALETTE << 12); } } void UserInterface::wordWrap (char* str, int height, int width) { int maxLen = 768; char wrappedText[768]; char *srcPos = str; char *destPos = wrappedText; char *wordStart; char *wordEnd; int numLines = 1; int spaceLeft = width; int wordLength; memset (wrappedText, 0, maxLen); while (*srcPos != '\0' && destPos < (wrappedText + maxLen)) { while (*srcPos && isspace(*srcPos)) { if (*srcPos == '\n') { *destPos++ = '\n'; spaceLeft = width; ++numLines; } ++srcPos; // find start of word } if (*srcPos == '\0') { break; } wordStart = srcPos; while (*srcPos && !isspace(*srcPos)) { ++srcPos; // find end of word } wordEnd = srcPos; wordLength = wordEnd - wordStart; if ((wordLength > spaceLeft) && (wordLength <= width)) { // Word needs to be put on a new line *destPos++ = '\n'; spaceLeft = width; ++numLines; } else if (spaceLeft < width) { // Not at the start of a line *destPos++ = ' '; spaceLeft = spaceLeft - 1; } memcpy (destPos, wordStart, wordLength); destPos += wordLength; spaceLeft -= wordLength; while (spaceLeft < 0) { spaceLeft += width; } } if (numLines <= height) { strcpy (str, wrappedText); } } void UserInterface::writeTextBox (TEXT_TYPE textType, const char* str, va_list args) { char dispStr[MAX_CHARS_PER_SCREEN]; vsniprintf(dispStr, MAX_CHARS_PER_SCREEN, str, args); int lastRow; clearMessage (textType); if (textType == TEXT_TITLE) { wordWrap (dispStr, TITLE_BOX_END_ROW - TITLE_BOX_START_ROW - 2, LAST_COL - FIRST_COL - 2); lastRow = topText->putText (dispStr, FIRST_COL + 1, TITLE_BOX_END_ROW - 1, LAST_COL - 1, TITLE_BOX_START_ROW + 1, FIRST_COL + 1); drawBox (TITLE_BOX_START_ROW, FIRST_COL, lastRow + 1, LAST_COL); } else { wordWrap (dispStr, TEXT_BOX_END_ROW - TEXT_BOX_START_ROW - 2, LAST_COL - FIRST_COL - 2); lastRow = topText->putText (dispStr, FIRST_COL + 1, TEXT_BOX_END_ROW - 1, LAST_COL - 1, TEXT_BOX_START_ROW + 1, FIRST_COL + 1); drawBox (TEXT_BOX_START_ROW, FIRST_COL, lastRow + 1, LAST_COL); } } void UserInterface::showMessage (const char* str, ...) { va_list args; va_start(args, str); writeTextBox (TEXT_INFO, str, args); va_end(args); } void UserInterface::showMessage (TEXT_TYPE textType, const char* str, ...) { va_list args; va_start(args, str); writeTextBox (textType, str, args); va_end(args); } void UserInterface::clearMessage (TEXT_TYPE textType) { if (textType == TEXT_TITLE) { topText->clearText (TITLE_BOX_START_ROW, FIRST_COL, TITLE_BOX_END_ROW, LAST_COL); clearBox (TITLE_BOX_START_ROW, FIRST_COL, TITLE_BOX_END_ROW, LAST_COL); } else { topText->clearText (TEXT_BOX_START_ROW, FIRST_COL, TEXT_BOX_END_ROW, LAST_COL); clearBox (TEXT_BOX_START_ROW, FIRST_COL, TEXT_BOX_END_ROW, LAST_COL); } } void UserInterface::clearMessage (void) { clearMessage (TEXT_TITLE); clearMessage (TEXT_INFO); } void UserInterface::putButtonBg (BUTTON_BG_OFFSETS buttonBg, int position) { int palette; if (buttonBg != BUTTON_BG_NONE) { if (buttonBg == BUTTON_BG_FOLDER) { palette = BUTTON_PALETTE_FOLDER; } else if (buttonBg == BUTTON_BG_FILE) { palette = BUTTON_PALETTE_FILE; } else if (buttonBg == BUTTON_BG_ON) { palette = BUTTON_PALETTE_ON; } else { palette = BUTTON_PALETTE_OFF; } for (int col = 0; col < 30; col++) { for (int row = 0; row < 2; row++) { putGuiTile (buttonBg + col + row * 30, row + position , col, palette, false); } } } else { for (int col = 0; col < 30; col++) { for (int row = 0; row < 2; row++) { putGuiTile (BLANK_TILE, row + position , col, 0, false); } } } } void UserInterface::clearFolderBackground (void) { for (int i = MENU_FIRST_ROW; i <= MENU_LAST_ROW; i++) { putButtonBg (BUTTON_BG_NONE, i * 2); } } void UserInterface::showGoButton (bool visible, int left, int top) { if (visible) { for (int col = 0; col < GO_BUTTON_WIDTH; col++) { for (int row = 0; row < GO_BUTTON_HEIGHT; row++) { putGuiTile (GO_BUTTON_OFFSET + col + row * GO_BUTTON_WIDTH, row + top, col + left, GO_BUTTON_PALETTE, false); } } } else { for (int col = 0; col < GO_BUTTON_WIDTH; col++) { for (int row = 0; row < GO_BUTTON_HEIGHT; row++) { putGuiTile (BLANK_TILE, row + top, col + left, 0, false); } } } } void UserInterface::showCheatFolder (std::vector &contents) { int menuLength; menuLevel.bottom = menuLevel.top + MENU_NUM_ITEMS - 1; if (menuLevel.bottom >= (int)contents.size()) { menuLevel.bottom = contents.size() - 1; } menuLength = menuLevel.bottom - menuLevel.top; subText->clearText(MENU_FIRST_ROW, 0, MENU_LAST_ROW*2, MENU_LAST_COL); clearFolderBackground(); for (int curItem = 0; curItem <= menuLength; curItem++) { if (CHEAT_MENU_FOLDER_UP == menuLevel.top + curItem) { putButtonBg (BUTTON_BG_FOLDER, (MENU_FIRST_ROW + curItem) * 2); subText->putText (CHEAT_MENU_FOLDER_UP_NAME, MENU_FIRST_COL, (MENU_FIRST_ROW + curItem) * 2, MENU_LAST_COL, (MENU_FIRST_ROW + curItem) * 2, MENU_FIRST_COL); } else { CheatCode* cheatCode = dynamic_cast(contents[menuLevel.top + curItem]); subText->putText (contents[menuLevel.top + curItem]->getName(), MENU_FIRST_COL, (MENU_FIRST_ROW + curItem) * 2, MENU_LAST_COL, (MENU_FIRST_ROW + curItem) * 2, MENU_FIRST_COL); if (cheatCode) { if (cheatCode->getEnabledStatus()) { putButtonBg (BUTTON_BG_ON, (MENU_FIRST_ROW + curItem) * 2); } else { putButtonBg (BUTTON_BG_OFF, (MENU_FIRST_ROW + curItem) * 2); } } else { putButtonBg (BUTTON_BG_FOLDER, (MENU_FIRST_ROW + curItem) * 2); } } } } #define CHEAT_MENU_TOP (gameCodes==top?0:CHEAT_MENU_FOLDER_UP) void UserInterface::cheatMenu (CheatFolder* gameCodes, CheatFolder* top) { menuLevel.top = 0; menuLevel.selected = 0; std::stack menuLevelStack; std::vector contents = gameCodes->getContents(); showScrollbar(true); showCheatFolder (contents); showCursor (true); showGoButton(true, GO_BUTTON_COL, GO_BUTTON_ROW); int pressed; while ((pressed = menuInput(true)) != BUTTON_EXIT) { if (pressed == BUTTON_LINE_DOWN) { if (menuLevel.selected < (int)contents.size()-1) { menuLevel.selected ++; if (menuLevel.selected > menuLevel.bottom) { menuLevel.top ++; showCheatFolder (contents); } } } else if (pressed == BUTTON_LINE_UP) { if (menuLevel.selected > CHEAT_MENU_TOP) { menuLevel.selected --; if (menuLevel.selected < menuLevel.top) { menuLevel.top --; showCheatFolder (contents); } } } else if (pressed == BUTTON_SELECT) { if (menuLevel.selected == CHEAT_MENU_FOLDER_UP) { CheatFolder* cheatFolder = gameCodes->getParent(); if (!cheatFolder || gameCodes == top) { // Back out of top level folder break; } else { gameCodes = cheatFolder; if (!menuLevelStack.empty()) { menuLevel = menuLevelStack.top(); menuLevelStack.pop(); } else { menuLevel.top = CHEAT_MENU_TOP; menuLevel.selected = CHEAT_MENU_TOP; } contents = gameCodes->getContents(); showCheatFolder (contents); } } else { CheatCode* cheatCode = dynamic_cast(contents[menuLevel.selected]); CheatFolder* cheatFolder = dynamic_cast(contents[menuLevel.selected]); if (cheatCode) { cheatCode->toggleEnabled(); showCheatFolder (contents); } else if (cheatFolder) { menuLevelStack.push(menuLevel); gameCodes = cheatFolder; menuLevel.top = CHEAT_MENU_TOP; menuLevel.selected = CHEAT_MENU_TOP; contents = gameCodes->getContents(); showCheatFolder (contents); } } } else if (pressed == BUTTON_BACK) { CheatFolder* cheatFolder = gameCodes->getParent(); if (!cheatFolder || gameCodes == top) { // Back out of top level folder break; } else { if (!menuLevelStack.empty()) { menuLevel = menuLevelStack.top(); menuLevelStack.pop(); } else { menuLevel.top = CHEAT_MENU_TOP; menuLevel.selected = CHEAT_MENU_TOP; } gameCodes = cheatFolder; contents = gameCodes->getContents(); showCheatFolder (contents); } } else if (pressed == BUTTON_PAGE_UP) { menuLevel.selected -= MENU_PAGE_SCROLL; if (menuLevel.selected < CHEAT_MENU_TOP) { menuLevel.selected = CHEAT_MENU_TOP; } menuLevel.top -= MENU_PAGE_SCROLL; if (menuLevel.top < CHEAT_MENU_TOP) { menuLevel.top = CHEAT_MENU_TOP; } showCheatFolder (contents); } else if (pressed == BUTTON_PAGE_DOWN) { menuLevel.selected += MENU_PAGE_SCROLL; if (menuLevel.selected > (int)contents.size()-1) { menuLevel.selected = (int)contents.size()-1; } menuLevel.top += MENU_PAGE_SCROLL; if (menuLevel.top > (int)contents.size()-MENU_NUM_ITEMS) { menuLevel.top = (int)contents.size()-MENU_NUM_ITEMS; } if (menuLevel.top < CHEAT_MENU_TOP) { menuLevel.top = CHEAT_MENU_TOP; } showCheatFolder (contents); } else if (pressed == BUTTON_ENABLE_ALL) { gameCodes->enableAll(true); showCheatFolder (contents); } else if (pressed == BUTTON_DISABLE_ALL) { gameCodes->enableAll(false); showCheatFolder (contents); } setCursorPosition ((MENU_FIRST_ROW + menuLevel.selected - menuLevel.top) * TOUCH_GRID_SIZE); setScrollbarPosition (CHEAT_MENU_TOP + menuLevel.selected, (int)contents.size()-1); showMessage (TEXT_TITLE, "%s", gameCodes->getName()); if (menuLevel.selected != CHEAT_MENU_FOLDER_UP) { if (contents[menuLevel.selected]->note.empty()) { showMessage (TEXT_INFO, "%s", contents[menuLevel.selected]->getName()); } else { showMessage (TEXT_INFO, "%s\n\n%s", contents[menuLevel.selected]->getName(), contents[menuLevel.selected]->getNote()); } } else { showMessage (TEXT_INFO, "Parent Directory"); } } clearMessage (); clearFolderBackground(); showCursor (false); showScrollbar (false); showGoButton(false, GO_BUTTON_COL, GO_BUTTON_ROW); subText->clearText (); } bool UserInterface::fileInfoPredicate (const FileInfo& lhs, const FileInfo& rhs) { if (!lhs.isDirectory && rhs.isDirectory) { return false; } if (lhs.isDirectory && !rhs.isDirectory) { return true; } return strcasecmp(lhs.filename.c_str(), rhs.filename.c_str()) < 0; } std::vector UserInterface::getDirContents (const char* extension) { std::vector files; struct dirent* dentry; size_t extLen = 0; if (extension) { extLen = strlen (extension); } DIR* dir = opendir ("."); while ( NULL != (dentry = readdir(dir)) ) { bool isDirectory = (dentry->d_type == DT_DIR); size_t nameLen = strlen(dentry->d_name); if ( (isDirectory && strcmp(dentry->d_name, ".") != 0) || extension == NULL || strcasecmp(extension, &dentry->d_name[nameLen-extLen]) == 0 ) { UserInterface::FileInfo fileInfo; fileInfo.filename = dentry->d_name; fileInfo.isDirectory = isDirectory; files.push_back (fileInfo); } } closedir (dir); std::sort(files.begin(), files.end(), UserInterface::fileInfoPredicate); return files; } void UserInterface::showFileFolder (std::vector &contents) { int menuLength; menuLevel.bottom = menuLevel.top + MENU_LAST_ROW; if (menuLevel.bottom >= (int)contents.size()) { menuLevel.bottom = contents.size() - 1; } menuLength = menuLevel.bottom - menuLevel.top; subText->clearText(MENU_FIRST_ROW, 0, MENU_LAST_ROW*2, MENU_LAST_COL); for (int i = MENU_FIRST_ROW; i <= MENU_LAST_ROW; i++) { putButtonBg (BUTTON_BG_NONE, i * 2); } for (int curItem = 0; curItem <= menuLength; curItem++) { subText->putText (contents[menuLevel.top + curItem].filename.c_str(), MENU_FIRST_COL, (MENU_FIRST_ROW + curItem) * 2, MENU_LAST_COL, (MENU_FIRST_ROW + curItem) * 2, MENU_FIRST_COL); if (contents[menuLevel.top + curItem].isDirectory) { putButtonBg (BUTTON_BG_FOLDER, (MENU_FIRST_ROW + curItem) * 2); } else { putButtonBg (BUTTON_BG_FILE, (MENU_FIRST_ROW + curItem) * 2); } } } int UserInterface::menuInput(bool enableGoButton) { int pressed; touchPosition touchXY; swiWaitForVBlank(); scanKeys(); pressed = keysDown(); touchRead(&touchXY); // Touch screen stuff if (pressed & KEY_TOUCH) { if ((touchXY.px < (SCROLLBAR_COL * TOUCH_GRID_SIZE)) && #if MENU_FIRST_ROW > 0 (touchXY.py >= (MENU_FIRST_ROW * TOUCH_GRID_SIZE)) && #endif (touchXY.py < ((MENU_FIRST_ROW + menuLevel.bottom - menuLevel.top + 1) * TOUCH_GRID_SIZE))) { // Touched an item menuLevel.selected = (touchXY.py / TOUCH_GRID_SIZE) + menuLevel.top - MENU_FIRST_ROW; pressed = BUTTON_SELECT; } if (touchXY.px >= (SCROLLBAR_COL * TOUCH_GRID_SIZE) && touchXY.px < ((SCROLLBAR_COL+1) * TOUCH_GRID_SIZE)) { if ( #if SCROLLBAR_START > 0 (touchXY.py >= (SCROLLBAR_START) * TOUCH_GRID_SIZE) && #endif (touchXY.py < (SCROLLBAR_START + 1) * TOUCH_GRID_SIZE)) { // Touched up arrow pressed = BUTTON_LINE_UP; } else if ((touchXY.py >= SCROLLBAR_END * TOUCH_GRID_SIZE) && (touchXY.py < (SCROLLBAR_END + 1) * TOUCH_GRID_SIZE)) { // Touched down arrow pressed = BUTTON_LINE_DOWN; } else if ((touchXY.py >= (SCROLLBAR_START + 1) * TOUCH_GRID_SIZE) && (touchXY.py < scrollboxPosition)) { // Touched scrollbar above scrollbox pressed = BUTTON_PAGE_UP; } else if ((touchXY.py >= scrollboxPosition + TOUCH_GRID_SIZE) && (touchXY.py < (SCROLLBAR_END) * TOUCH_GRID_SIZE)) { // Touched scrollbar below scrollbox pressed = BUTTON_PAGE_DOWN; } } if (enableGoButton) { if ((touchXY.px >= (GO_BUTTON_COL * TOUCH_GRID_SIZE / 2)) && (touchXY.px < ((GO_BUTTON_COL + GO_BUTTON_WIDTH) * TOUCH_GRID_SIZE / 2)) && (touchXY.py >= (GO_BUTTON_ROW * TOUCH_GRID_SIZE / 2)) && (touchXY.py < ((GO_BUTTON_ROW + GO_BUTTON_HEIGHT) * TOUCH_GRID_SIZE / 2))) { // Touched the go button pressed = BUTTON_EXIT; } } } return pressed; } std::string UserInterface::fileBrowser (const char* extension) { menuLevel.top = 0; menuLevel.selected = 0; std::vector contents; std::string filename; char* cwd = new char[MAXPATHLEN]; std::stack menuLevelStack; contents = getDirContents (extension); showScrollbar (true); showFileFolder (contents); showCursor (true); if (extension) { showMessage (TEXT_TITLE, "Open *%s file", extension); } else { showMessage (TEXT_TITLE, "Open file"); } int pressed; while ((pressed = menuInput(false)) != BUTTON_EXIT) { if (pressed == BUTTON_LINE_DOWN) { if (menuLevel.selected < (int)contents.size()-1) { menuLevel.selected ++; if (menuLevel.selected > menuLevel.bottom) { menuLevel.top ++; showFileFolder (contents); } } } else if (pressed == BUTTON_LINE_UP) { if (menuLevel.selected > 0) { menuLevel.selected --; if (menuLevel.selected < menuLevel.top) { menuLevel.top --; showFileFolder (contents); } } } else if (pressed == BUTTON_SELECT) { if (contents[menuLevel.selected].isDirectory) { if (contents[menuLevel.selected].filename == "..") { chdir(".."); if (!menuLevelStack.empty()) { menuLevel = menuLevelStack.top(); menuLevelStack.pop(); } else { menuLevel.top = 0; menuLevel.selected = 0; } } else { chdir (contents[menuLevel.selected].filename.c_str()); menuLevelStack.push(menuLevel); menuLevel.top = 0; menuLevel.selected = 0; } contents = getDirContents(extension); showFileFolder (contents); } else { filename = contents[menuLevel.selected].filename.c_str(); break; } } else if (pressed == BUTTON_BACK) { chdir (".."); if (!menuLevelStack.empty()) { menuLevel = menuLevelStack.top(); menuLevelStack.pop(); } else { menuLevel.top = 0; menuLevel.selected = 0; } contents = getDirContents(extension); showFileFolder (contents); } else if (pressed == BUTTON_PAGE_UP) { menuLevel.selected -= MENU_PAGE_SCROLL; if (menuLevel.selected < 0) { menuLevel.selected = 0; } menuLevel.top -= MENU_PAGE_SCROLL; if (menuLevel.top < 0) { menuLevel.top = 0; } showFileFolder (contents); } else if (pressed == BUTTON_PAGE_DOWN) { menuLevel.selected += MENU_PAGE_SCROLL; if (menuLevel.selected > (int)contents.size()-1) { menuLevel.selected = (int)contents.size()-1; } menuLevel.top += MENU_PAGE_SCROLL; if (menuLevel.top > (int)contents.size()-MENU_NUM_ITEMS) { menuLevel.top = (int)contents.size()-MENU_NUM_ITEMS; } if (menuLevel.top < 0) { menuLevel.top = 0; } showFileFolder (contents); } setCursorPosition ((MENU_FIRST_ROW + menuLevel.selected - menuLevel.top) * TOUCH_GRID_SIZE); setScrollbarPosition (menuLevel.selected, (int)contents.size()-1); getcwd (cwd, MAXPATHLEN); showMessage (TEXT_INFO, "%s\n%s", cwd, contents[menuLevel.selected].filename.c_str()); } delete [] cwd; clearMessage (); clearFolderBackground(); showCursor (false); showScrollbar (false); subText->clearText (); return filename; } #ifdef DEMO struct DEMO_STUFF { const char name[40]; int icon; }; void UserInterface::demo (void) { const DEMO_STUFF demoMenu[] = { {"Track Hacks (WiFi)", BUTTON_BG_FOLDER}, {"Shy Guy Codes", BUTTON_BG_FOLDER}, {"Press Y for safe DC", BUTTON_BG_ON}, {"Speed Codes", BUTTON_BG_FOLDER}, {"WiFi Win Loss Modifier", BUTTON_BG_FOLDER}, {"Item Codes: Always have...", BUTTON_BG_FOLDER}, {"ALWAYS USE CART", BUTTON_BG_FOLDER}, {"Always Play as CHARACTER", BUTTON_BG_FOLDER}, {"CRAZY SOUNDS", BUTTON_BG_OFF}, {"Always Play on an UNTEXTURED track", BUTTON_BG_OFF} }; int menuLength; menuLength = sizeof(demoMenu) / sizeof (DEMO_STUFF); subText->clearText(0, 0, MENU_LAST_ROW, MENU_LAST_COL); showScrollbar(true); showCursor (true); for (int col = 0; col < GO_BUTTON_WIDTH; col++) { for (int row = 0; row < GO_BUTTON_HEIGHT; row++) { putGuiTile (GO_BUTTON_OFFSET + col + row * GO_BUTTON_WIDTH, row + GO_BUTTON_ROW, col + GO_BUTTON_COL, GO_BUTTON_PALETTE, false); } } setCursorPosition ((MENU_FIRST_ROW + menuLength - menuLevel.top - 1) * TOUCH_GRID_SIZE); setScrollbarPosition (menuLength - 1, menuLength - 1); showMessage (TEXT_TITLE, "Mario Kart DS (USA)"); for (int curItem = 0; curItem < menuLength; curItem++) { subText->putText (demoMenu[curItem].name, (MENU_FIRST_ROW + curItem) * 2, MENU_FIRST_COL, (MENU_FIRST_ROW + curItem) * 2, MENU_LAST_COL, (MENU_FIRST_ROW + curItem) * 2, MENU_FIRST_COL); putButtonBg ((BUTTON_BG_OFFSETS)demoMenu[curItem].icon, (MENU_FIRST_ROW + curItem) * 2); showMessage (demoMenu[curItem].name); } } #endif // Sprite int Sprite::getSpriteNum (void) { static int nextNum = 0; return nextNum++; } void Sprite::init (void) { for (int i = 0; i < 128; i++) { OAM_SUB[i*4] = (2<<8); } } Sprite::Sprite (int width, int height, const u16* spriteData, const u16* paletteData) { u16 size_attrib; u16 shape_attrib; int spriteDataOffset; num = getSpriteNum(); spriteDataOffset = num * 64; // Assume 64 tiles per sprite for (int i = 0; i < PALETTE_SIZE; i++) { SPRITE_PALETTE_SUB[PALETTE_SIZE * num + i] = paletteData[i] | 0x8000; } vramcpy (SPRITE_GFX_SUB + TILE_SIZE / sizeof(u16) * spriteDataOffset, spriteData, 64 * TILE_SIZE); // 64 tiles per sprite if (width == height) { shape_attrib = ATTR0_SQUARE; if (width >= 64) { size_attrib = ATTR1_SIZE_64; } else if (width >= 32) { size_attrib = ATTR1_SIZE_32; } else if (width >= 16) { size_attrib = ATTR1_SIZE_16; } else { size_attrib = ATTR1_SIZE_8; } } else if (width > height) { shape_attrib = ATTR0_WIDE; if (width >= 64) { size_attrib = ATTR1_SIZE_64; } else if ((width >= 32) && (height >= 16)) { size_attrib = ATTR1_SIZE_32; } else if (width >= 32) { size_attrib = ATTR1_SIZE_16; } else { size_attrib = ATTR1_SIZE_8; } } else { shape_attrib = ATTR0_TALL; if (height >= 64) { size_attrib = ATTR1_SIZE_64; } else if ((height >= 32) && (width >= 16)) { size_attrib = ATTR1_SIZE_32; } else if (height >= 32) { size_attrib = ATTR1_SIZE_16; } else { size_attrib = ATTR1_SIZE_8; } } attrib0 = ATTR0_NORMAL | ATTR0_TYPE_NORMAL | ATTR0_COLOR_16 | shape_attrib; attrib1 = size_attrib; attrib2 = spriteDataOffset | ATTR2_PALETTE(num); showSprite (false); setPosition(0,0); } void Sprite::showSprite (bool visible) { this->visible = visible; attrib0 = (attrib0 & ~(3<<8)) | (visible ? 0 : (2<<8)); updateAttribs(); } void Sprite::setPosition (int top, int left) { this->top = top; this->left = left; updateAttribs(); } void Sprite::updateAttribs (void) { OAM_SUB[num*4 + 0] = attrib0 | (top & 0xFF); OAM_SUB[num*4 + 1] = attrib1 | (left & 0x1FF); OAM_SUB[num*4 + 2] = attrib2; }