#include "menu.hpp" #include "banner/BannerWindow.hpp" #include "gc/gcdisc.hpp" #include "gui/WiiMovie.hpp" //sounds extern const u8 gc_ogg[]; extern const u32 gc_ogg_size; bool m_zoom_banner = false; bool m_banner_loaded = false; s16 m_gameBtnPlayFull; s16 m_gameBtnBackFull; s16 m_gameBtnToggle; s16 m_gameBtnToggleFull; s16 m_gameLblSnapBg; s16 m_gameLblSnapFrame; s16 m_gameLblBannerFrame; static inline int loopNum(int i, int s) { return (i + s) % s; } void CMenu::_extractBnr(const dir_discHdr *hdr) { u32 size = 0; DeviceHandle.OpenWBFS(currentPartition); wbfs_disc_t *disc = WBFS_OpenDisc((u8 *) &hdr->id, (char *) hdr->path); if(disc != NULL) { void *bnr = NULL; size = wbfs_extract_file(disc, (char*)"opening.bnr", &bnr); if(size > 0) CurrentBanner.SetBanner((u8*)bnr, size, false, true); WBFS_CloseDisc(disc); } WBFS_Close(); } void CMenu::_setCurrentItem(const dir_discHdr *hdr) { const char *fn_id = CoverFlow.getFilenameId(hdr); if(m_current_view == COVERFLOW_PLUGIN) { if(hdr->type == TYPE_PLUGIN) strncpy(m_plugin.PluginMagicWord, fmt("%08x", hdr->settings[0]), 8); else { if(hdr->type == TYPE_WII_GAME) strncpy(m_plugin.PluginMagicWord, WII_PMAGIC, 9); else if(hdr->type == TYPE_GC_GAME) strncpy(m_plugin.PluginMagicWord, GC_PMAGIC, 9); else if(hdr->type == TYPE_CHANNEL) strncpy(m_plugin.PluginMagicWord, NAND_PMAGIC, 9); else if(hdr->type == TYPE_EMUCHANNEL) strncpy(m_plugin.PluginMagicWord, ENAND_PMAGIC, 9); else //HOMEBREW strncpy(m_plugin.PluginMagicWord, HB_PMAGIC, 9); } m_cfg.setString(PLUGIN_DOMAIN, "cur_magic", m_plugin.PluginMagicWord); m_cfg.setString("plugin_item", m_plugin.PluginMagicWord, fn_id); } else { m_cfg.setString(_domainFromView(), "current_item", fn_id); } } void CMenu::_hideGame(bool instant) { _cleanupVideo(); m_fa.unload(); CoverFlow.showCover(); m_btnMgr.hide(m_gameBtnPlay, instant); m_btnMgr.hide(m_gameBtnBack, instant); m_btnMgr.hide(m_gameBtnPlayFull, instant); m_btnMgr.hide(m_gameBtnBackFull, instant); m_btnMgr.hide(m_gameBtnDelete, instant); m_btnMgr.hide(m_gameBtnSettings, instant); m_btnMgr.hide(m_gameBtnToggle, instant); m_btnMgr.hide(m_gameBtnToggleFull, instant); m_btnMgr.hide(m_gameBtnFavoriteOn, instant); m_btnMgr.hide(m_gameBtnFavoriteOff, instant); m_btnMgr.hide(m_gameBtnCategories, instant); m_btnMgr.hide(m_gameLblSnapBg, instant); m_btnMgr.hide(m_gameLblSnap, instant); m_btnMgr.hide(m_gameLblOverlay, instant); m_btnMgr.hide(m_gameLblSnapFrame, instant); m_btnMgr.hide(m_gameLblBannerFrame, instant); for(u8 i = 0; i < ARRAY_SIZE(m_gameLblUser); ++i) if(m_gameLblUser[i] != -1) m_btnMgr.hide(m_gameLblUser[i], instant); } void CMenu::_showGame(void) { const dir_discHdr *GameHdr = CoverFlow.getHdr(); const char *FanartPath = NULL; /* set fanart path */ if(GameHdr->type == TYPE_PLUGIN) FanartPath = fmt("%s/%s", m_fanartDir.c_str(), m_plugin.GetCoverFolderName(GameHdr->settings[0])); else FanartPath = fmt("%s", m_fanartDir.c_str()); /* Load fanart config if available */ if(m_fa.load(m_cfg, FanartPath, GameHdr)) { const TexData *bg = NULL; const TexData *bglq = NULL; m_fa.getBackground(bg, bglq); _setBg(*bg, *bglq); CoverFlow.hideCover(); } else // no fanart config so we show the cover and game menu background { CoverFlow.showCover(); if(customBg) _setBg(m_mainCustomBg[curCustBg], m_mainCustomBg[curCustBg]); else _setBg(m_gameBg, m_gameBgLQ); } } void CMenu::_cleanupBanner(bool gamechange) { //banner m_gameSound.FreeMemory(); _stopGameSoundThread();// stop banner and gamesound loading m_banner.DeleteBanner(gamechange); //movie _cleanupVideo(); } void CMenu::_cleanupVideo() { m_video_playing = false; movie.DeInit(); } bool CMenu::_startVideo() { const dir_discHdr *GameHdr = CoverFlow.getHdr(); const char *videoPath = NULL; const char *THP_Path = NULL; if(GameHdr->type == TYPE_PLUGIN) { const char *fn = CoverFlow.getFilenameId(GameHdr);//title.ext const char *coverDir = m_plugin.GetCoverFolderName(GameHdr->settings[0]); videoPath = fmt("%s/%s/%s", m_videoDir.c_str(), coverDir, fn); THP_Path = fmt("%s.thp", videoPath); if(!fsop_FileExist(THP_Path)) { if(strrchr(videoPath, '.') != NULL) { *strrchr(videoPath, '.') = '\0'; THP_Path = fmt("%s.thp", videoPath); } if(!fsop_FileExist(THP_Path))//default video for all games of this plugin { videoPath = fmt("%s/%s", m_videoDir.c_str(), m_plugin.PluginMagicWord);// use magic number as the filename THP_Path = fmt("%s.thp", videoPath); } } } else { videoPath = fmt("%s/%s.3", m_videoDir.c_str(), GameHdr->id);// try id3 first THP_Path = fmt("%s.thp", videoPath); if(!fsop_FileExist(THP_Path)) { videoPath = fmt("%s/%s", m_videoDir.c_str(), GameHdr->id); THP_Path = fmt("%s.thp", videoPath); } } if(fsop_FileExist(THP_Path)) { m_gameSound.FreeMemory(); _stopGameSoundThread(); m_banner.SetShowBanner(false); /* Lets play the movie */ movie.Init(THP_Path); m_gameSound.Load(fmt("%s.ogg", videoPath)); m_gameSound.SetVolume(m_cfg.getInt("GENERAL", "sound_volume_bnr", 255)); m_video_playing = true; m_gameSound.Play(); movie.Play(true); //video loops sound doesnt return true; } return false; } void CMenu::_game(bool launch) { m_banner_loaded = false; bool coverFlipped = false; int cf_version = 1; string domain; string key; Vector3D v; Vector3D savedv; dir_discHdr *hdr = (dir_discHdr*)MEM2_alloc(sizeof(dir_discHdr)); memcpy(hdr, CoverFlow.getHdr(), sizeof(dir_discHdr)); _setCurrentItem(hdr); char gcfg1Key[74]; char gameTitle[64]; memset(gcfg1Key, 0, sizeof(gcfg1Key)); memset(gameTitle, 0, sizeof(gameTitle)); if(hdr->type == TYPE_HOMEBREW) wcstombs(gcfg1Key, hdr->title, 63);// uses title which is the folder name in apps. else if(hdr->type == TYPE_PLUGIN) { strncpy(m_plugin.PluginMagicWord, fmt("%08x", hdr->settings[0]), 8); // if game has an id from the plugin database we use the new method which uses platform name/id if(strcmp(hdr->id, "PLUGIN") != 0 && !m_platform.getString("PLUGINS", m_plugin.PluginMagicWord, "").empty()) strncpy(gcfg1Key, fmt("%s/%s", m_platform.getString("PLUGINS", m_plugin.PluginMagicWord).c_str(), hdr->id), 73); else // old pre 5.4.4 method which uses plugin magic/title of game { if(strrchr(hdr->path, '/') != NULL) wcstombs(gameTitle, hdr->title, 63); else memcpy(gameTitle, hdr->path, 63);// scummvm strncpy(gcfg1Key, fmt("%s/%s", m_plugin.PluginMagicWord, gameTitle), 73); } } else // wii, gc, channels strcpy(gcfg1Key, hdr->id); m_zoom_banner = m_cfg.getBool(_domainFromView(), "show_full_banner", false); if(m_banner.GetZoomSetting() != m_zoom_banner) m_banner.ToggleZoom(); if(m_banner.GetInGameSettings()) m_banner.ToggleGameSettings(); m_gameSelected = true; s8 startGameSound = -7; SetupInput(); while(!m_exit) { if(m_fa.isLoaded() && m_fa.isAnimationComplete()) { if(m_fa.noLoop()) { m_fa.unload(); CoverFlow.showCover(); if(customBg) _setBg(m_mainCustomBg[curCustBg], m_mainCustomBg[curCustBg]); else _setBg(m_gameBg, m_gameBgLQ); } else //loop fanart m_fa.reset(); } if(startGameSound < 1) startGameSound++; if(startGameSound == -5) _showGame();// this also starts fanart with unloading previous fanart. if(!launch) _mainLoopCommon(true); if(startGameSound == 0) { startGameSound = 1; _playGameSound(); } /* move and zoom flipped cover */ if(coverFlipped) { float step = 0.05f; if(BTN_PLUS_PRESSED || BTN_MINUS_PRESSED) { if(BTN_MINUS_PRESSED) step = -step; v.z = min(max(-15.f, v.z + step), 15.f); } else if(BTN_LEFT_PRESSED || BTN_RIGHT_PRESSED) { if(BTN_RIGHT_PRESSED) step = -step; v.x = min(max(-15.f, v.x + step), 15.f); } else if(BTN_UP_PRESSED || BTN_DOWN_PRESSED) { if(BTN_UP_PRESSED) step = -step; v.y = min(max(-15.f, v.y + step), 15.f); } CoverFlow.setCoverFlipPos(v); } /* exit game menu */ if(BTN_HOME_PRESSED) { _cleanupBanner();// also cleans up trailer movie including trailer sound break; } else if(BTN_B_PRESSED) { /* de flip cover */ if(coverFlipped) { CoverFlow.flip(); coverFlipped = false; m_banner.SetShowBanner(true); } /* exit game selected menu */ else { _cleanupBanner(); break; } } /* display game info screen */ else if(BTN_PLUS_PRESSED && hdr->type != TYPE_HOMEBREW && hdr->type != TYPE_SOURCE && !coverFlipped && !m_video_playing) { _hideGame(); m_banner.SetShowBanner(false); m_gameSelected = false; launch = _gameinfo(); m_gameSelected = true; _showGame(); if(m_newGame) startGameSound = -10; m_banner.SetShowBanner(true); } /* play or stop a trailer video */ else if(BTN_MINUS_PRESSED && !coverFlipped) { if(m_video_playing) { m_video_playing = false; movie.DeInit(); m_gameSound.FreeMemory(); m_banner.SetShowBanner(true); if(!m_gameSound.IsPlaying()) startGameSound = -6; } else _startVideo(); } /* switch coverflow layout */ else if((BTN_1_PRESSED || BTN_2_PRESSED) && !CFLocked && !coverFlipped && !m_video_playing) { u32 curPos = CoverFlow._currentPos(); s8 direction = BTN_1_PRESSED ? 1 : -1; int cfVersion = loopNum((_getCFVersion() - 1) + direction, m_numCFVersions) + 1; _setCFVersion(cfVersion); _loadCFLayout(cfVersion); CoverFlow._setCurPos(curPos); CoverFlow.applySettings(); } else if(launch || BTN_A_PRESSED) { if(m_fa.isLoaded() && ShowPointer())// stop and unload fanart { m_fa.unload(); CoverFlow.showCover(); if(customBg) _setBg(m_mainCustomBg[curCustBg], m_mainCustomBg[curCustBg]); else _setBg(m_gameBg, m_gameBgLQ); continue; } /* delete button */ else if(m_btnMgr.selected(m_gameBtnDelete)) { _hideGame(); m_banner.SetShowBanner(false); if(m_locked) _error(_t("errgame15", L"WiiFlow locked! Unlock WiiFlow to use this feature.")); else if(hdr->type == TYPE_CHANNEL) _error(_t("errgame17", L"Can not delete real NAND Channels!")); else if(hdr->type == TYPE_HOMEBREW) { bool smallBox = m_cfg.getBool(HOMEBREW_DOMAIN, "smallbox", false); const char *gameNameOrID = CoverFlow.getFilenameId(hdr); if(smallBox) fsop_deleteFile(fmt("%s/homebrew/%s_small.wfc", m_cacheDir.c_str(), gameNameOrID)); else fsop_deleteFile(fmt("%s/homebrew/%s.wfc", m_cacheDir.c_str(), gameNameOrID)); _initCF(); CoverFlow.select(); CoverFlow.applySettings(); } else /* delete wii, gamecube, emunand game or plugin rom */ { if(_wbfsOp(WO_REMOVE_GAME)) { m_refreshGameList = false; _setCurrentItem(CoverFlow.getNextHdr()); _cleanupBanner(); _loadList(); _initCF(); CoverFlow.select(); CoverFlow.applySettings(); startGameSound = -10; } } _showGame(); m_banner.SetShowBanner(true); } else if(m_btnMgr.selected(m_gameBtnSettings)) { _hideGame(); if(m_locked) { m_banner.SetShowBanner(false); _error(_t("errgame15", L"WiiFlow locked! Unlock WiiFlow to use this feature.")); m_banner.SetShowBanner(true); } else { // the mainloop handles drawing banner while in settings m_banner.ToggleZoom();//zoom to full m_banner.ToggleGameSettings();// dim brightness if(hdr->type == TYPE_GC_GAME) _configGCGame(hdr); else _configGame(hdr); m_banner.ToggleGameSettings();//reset brightness m_banner.ToggleZoom();//de zoom to small /*if(m_newGame) { m_newGame = false; startGameSound = 1; _playGameSound(); }*/ } _showGame(); } else if(m_btnMgr.selected(m_gameBtnFavoriteOn) || m_btnMgr.selected(m_gameBtnFavoriteOff)) { if(hdr->type == TYPE_PLUGIN) { m_gcfg1.setBool("FAVORITES_PLUGINS", gcfg1Key, !m_gcfg1.getBool("FAVORITES_PLUGINS", gcfg1Key, false)); if(!m_gcfg1.getBool("FAVORITES_PLUGINS", gcfg1Key, false)) m_gcfg1.remove("FAVORITES_PLUGINS", gcfg1Key); } else { m_gcfg1.setBool("FAVORITES", gcfg1Key, !m_gcfg1.getBool("FAVORITES", gcfg1Key, false)); if(!m_gcfg1.getBool("FAVORITES", gcfg1Key, false)) m_gcfg1.remove("FAVORITES", gcfg1Key); } if(m_favorites) m_refreshGameList = true; } else if(m_btnMgr.selected(m_gameBtnCategories)) { if(m_locked) { m_banner.SetShowBanner(false); _error(_t("errgame15", L"WiiFlow locked! Unlock WiiFlow to use this feature.")); m_banner.SetShowBanner(true); } else { _hideGame(); // the mainloop handles drawing banner while in settings m_banner.ToggleZoom();//zoom to full m_banner.ToggleGameSettings();// dim brightness _CategorySettings(true); m_banner.ToggleGameSettings();//reset brightness m_banner.ToggleZoom();//de zoom to small } _showGame(); if(m_newGame) startGameSound = -10; } else if(m_btnMgr.selected(m_gameBtnBack) || m_btnMgr.selected(m_gameBtnBackFull)) { _cleanupBanner(); break; } else if(m_btnMgr.selected(m_gameBtnToggle) || m_btnMgr.selected(m_gameBtnToggleFull)) { m_zoom_banner = m_banner.ToggleZoom(); m_cfg.setBool(_domainFromView(), "show_full_banner", m_zoom_banner); m_show_zone_game = false; m_btnMgr.hide(m_gameBtnPlayFull); m_btnMgr.hide(m_gameBtnBackFull); m_btnMgr.hide(m_gameBtnToggleFull); } else if(launch || m_btnMgr.selected(m_gameBtnPlay) || m_btnMgr.selected(m_gameBtnPlayFull) || (!ShowPointer() && !m_video_playing && !coverFlipped)) { _hideGame(); if(isWiiVC && (hdr->type == TYPE_WII_GAME || hdr->type == TYPE_EMUCHANNEL)) { _error(_t("errgame19", L"Can't launch in Wii virtual console mode!")); launch = false; _showGame(); } else { _cleanupBanner(); _launch(hdr); if(m_exit) break; _hideWaitMessage(); launch = false; for(int chan = WPAD_MAX_WIIMOTES-1; chan >= 0; chan--) WPAD_SetVRes(chan, m_vid.width() + m_cursor[chan].width(), m_vid.height() + m_cursor[chan].height()); m_gcfg2.unload(); _showGame(); } } else if(!coverFlipped) { /* flip cover if mouse over */ for(int chan = WPAD_MAX_WIIMOTES-1; chan >= 0; chan--) { if(CoverFlow.mouseOver(m_cursor[chan].x(), m_cursor[chan].y())) { cf_version = _getCFVersion(); domain = fmt("%s_%i_S", cf_domain, cf_version); key = "flip_pos"; if(!m_vid.wide()) key += "_4_3"; v = m_coverflow.getVector3D(domain, key); coverFlipped = true; CoverFlow.flip(); m_banner.SetShowBanner(false); } } } } if(!coverFlipped && !m_video_playing) { /* move to new cover if needed */ if((startGameSound == 1 || startGameSound < -8) && (BTN_UP_REPEAT || RIGHT_STICK_UP)) { CoverFlow.up(); startGameSound = -10; } if((startGameSound == 1 || startGameSound < -8) && (BTN_RIGHT_REPEAT || RIGHT_STICK_RIGHT)) { CoverFlow.right(); startGameSound = -10; } if((startGameSound == 1 || startGameSound < -8) && (BTN_DOWN_REPEAT || RIGHT_STICK_DOWN)) { CoverFlow.down(); startGameSound = -10; } if((startGameSound == 1 || startGameSound < -8) && (BTN_LEFT_REPEAT || RIGHT_STICK_LEFT)) { CoverFlow.left(); startGameSound = -10; } if(startGameSound == -10)// if -10 then we moved to new cover { memcpy(hdr, CoverFlow.getHdr(), sizeof(dir_discHdr));// get new game header _setCurrentItem(hdr); memset(gcfg1Key, 0, sizeof(gcfg1Key)); memset(gameTitle, 0, sizeof(gameTitle)); if(hdr->type == TYPE_HOMEBREW) wcstombs(gcfg1Key, hdr->title, 63);// uses title which is the folder name in apps. else if(hdr->type == TYPE_PLUGIN) { strncpy(m_plugin.PluginMagicWord, fmt("%08x", hdr->settings[0]), 8); // if game has an id from the plugin database we use the new method which uses platform name/id if(strcmp(hdr->id, "PLUGIN") != 0 && !m_platform.getString("PLUGINS", m_plugin.PluginMagicWord, "").empty()) strncpy(gcfg1Key, fmt("%s/%s", m_platform.getString("PLUGINS", m_plugin.PluginMagicWord).c_str(), hdr->id), 73); else // old pre 5.4.4 method which uses plugin magic/title of game { if(strrchr(hdr->path, '/') != NULL) wcstombs(gameTitle, hdr->title, 63); else memcpy(gameTitle, hdr->path, 63);// scummvm strncpy(gcfg1Key, fmt("%s/%s", m_plugin.PluginMagicWord, gameTitle), 73); } } else // wii, gc, channels strcpy(gcfg1Key, hdr->id); if(m_newGame) { m_newGame = false; startGameSound = 1; _playGameSound(); } } } if(!m_fa.isLoaded() && !coverFlipped && !m_video_playing) { if(m_banner_loaded && !m_soundThrdBusy && m_zoom_banner) { m_btnMgr.show(m_gameBtnPlayFull); m_btnMgr.show(m_gameBtnBackFull); m_btnMgr.show(m_gameBtnToggleFull); m_btnMgr.hide(m_gameLblSnapBg, true); m_btnMgr.hide(m_gameLblSnap, true); m_btnMgr.hide(m_gameLblOverlay, true); m_btnMgr.hide(m_gameBtnToggle, true); m_btnMgr.hide(m_gameLblSnapFrame, true); m_btnMgr.hide(m_gameLblBannerFrame, true); m_btnMgr.hide(m_gameBtnFavoriteOn); m_btnMgr.hide(m_gameBtnFavoriteOff); m_btnMgr.hide(m_gameBtnCategories); m_btnMgr.hide(m_gameBtnSettings); m_btnMgr.hide(m_gameBtnDelete); m_btnMgr.hide(m_gameBtnPlay); m_btnMgr.hide(m_gameBtnBack); for(u8 i = 0; i < ARRAY_SIZE(m_gameLblUser); ++i) if(m_gameLblUser[i] != -1) m_btnMgr.hide(m_gameLblUser[i]); } else { if((!Auto_hide_icons || m_show_zone_game) && !m_soundThrdBusy) { m_btnMgr.show(m_gameBtnPlay); m_btnMgr.show(m_gameBtnBack); m_btnMgr.show(m_gameBtnSettings); m_btnMgr.show(m_gameBtnDelete); m_btnMgr.show(m_gameBtnCategories); bool b; if(hdr->type == TYPE_PLUGIN) { b = m_gcfg1.getBool("FAVORITES_PLUGINS", gcfg1Key, false); if(!b) m_gcfg1.remove("FAVORITES_PLUGINS", gcfg1Key); } else { b = m_gcfg1.getBool("FAVORITES", gcfg1Key, false); if(!b) m_gcfg1.remove("FAVORITES", gcfg1Key); } m_btnMgr.show(b ? m_gameBtnFavoriteOn : m_gameBtnFavoriteOff); m_btnMgr.hide(b ? m_gameBtnFavoriteOff : m_gameBtnFavoriteOn); for(u8 i = 0; i < ARRAY_SIZE(m_gameLblUser); ++i) if(m_gameLblUser[i] != -1) m_btnMgr.show(m_gameLblUser[i]); } else if(!m_soundThrdBusy) { m_btnMgr.hide(m_gameBtnFavoriteOn); m_btnMgr.hide(m_gameBtnFavoriteOff); m_btnMgr.hide(m_gameBtnCategories); m_btnMgr.hide(m_gameBtnSettings); m_btnMgr.hide(m_gameBtnDelete); m_btnMgr.hide(m_gameBtnPlay); m_btnMgr.hide(m_gameBtnBack); for(u8 i = 0; i < ARRAY_SIZE(m_gameLblUser); ++i) if (m_gameLblUser[i] != -1) m_btnMgr.hide(m_gameLblUser[i]); } if(m_banner_loaded && !m_soundThrdBusy && !m_zoom_banner) { m_btnMgr.hide(m_gameBtnPlayFull); m_btnMgr.hide(m_gameBtnBackFull); m_btnMgr.hide(m_gameBtnToggleFull); m_btnMgr.hide(m_gameLblSnapBg, true); m_btnMgr.hide(m_gameLblSnap, true); m_btnMgr.hide(m_gameLblOverlay, true); m_btnMgr.hide(m_gameLblSnapFrame, true); m_btnMgr.show(m_gameBtnToggle); m_btnMgr.show(m_gameLblBannerFrame); } if(m_snapshot_loaded && !m_soundThrdBusy) { m_btnMgr.hide(m_gameBtnPlayFull); m_btnMgr.hide(m_gameBtnBackFull); m_btnMgr.hide(m_gameBtnToggleFull); m_btnMgr.hide(m_gameBtnToggle); m_btnMgr.hide(m_gameLblBannerFrame); m_btnMgr.show(m_gameLblSnapBg); m_btnMgr.show(m_gameLblSnap); m_btnMgr.show(m_gameLblOverlay); m_btnMgr.show(m_gameLblSnapFrame); } if(!m_banner_loaded && !m_snapshot_loaded && !m_soundThrdBusy) { m_btnMgr.hide(m_gameBtnPlayFull); m_btnMgr.hide(m_gameBtnBackFull); m_btnMgr.hide(m_gameBtnToggleFull); m_btnMgr.hide(m_gameBtnToggle); m_btnMgr.hide(m_gameLblSnapBg); m_btnMgr.hide(m_gameLblSnap); m_btnMgr.hide(m_gameLblOverlay); m_btnMgr.hide(m_gameLblSnapFrame); m_btnMgr.hide(m_gameLblBannerFrame); } } } else { m_btnMgr.hide(m_gameLblSnapFrame); m_btnMgr.hide(m_gameLblBannerFrame); m_btnMgr.hide(m_gameLblSnapBg); m_btnMgr.hide(m_gameLblSnap); m_btnMgr.hide(m_gameLblOverlay); m_btnMgr.hide(m_gameBtnPlayFull); m_btnMgr.hide(m_gameBtnBackFull); m_btnMgr.hide(m_gameBtnToggle); m_btnMgr.hide(m_gameBtnToggleFull); m_btnMgr.hide(m_gameBtnPlay); m_btnMgr.hide(m_gameBtnBack); m_btnMgr.hide(m_gameBtnDelete); m_btnMgr.hide(m_gameBtnSettings); m_btnMgr.hide(m_gameBtnFavoriteOn); m_btnMgr.hide(m_gameBtnFavoriteOff); m_btnMgr.hide(m_gameBtnCategories); for(u8 i = 0; i < ARRAY_SIZE(m_gameLblUser); ++i) if(m_gameLblUser[i] != -1) m_btnMgr.hide(m_gameLblUser[i]); } } if(coverFlipped) { m_coverflow.setVector3D(domain, key, savedv); _loadCFLayout(cf_version, true);// true? CoverFlow.applySettings(); } m_snapshot_loaded = false; TexData emptyTex; m_btnMgr.setTexture(m_gameLblSnap, emptyTex); m_btnMgr.setTexture(m_gameLblOverlay, emptyTex); TexHandle.Cleanup(m_game_snap); TexHandle.Cleanup(m_game_overlay); m_gameSelected = false; MEM2_free(hdr); _hideGame(); } void CMenu::_initGameMenu() { //CColor fontColor(0xD0BFDFFF); TexData texGameFavOn; TexData texGameFavOnSel; TexData texGameFavOff; TexData texGameFavOffSel; TexData texCategories; TexData texCategoriesSel; TexData texDelete; TexData texDeleteSel; TexData texSettings; TexData texSettingsSel; TexData texToggleBanner; TexData texSnapShotBg; TexData texSnapShotFrame; TexData texBannerFrame; TexData bgLQ; TexHandle.fromImageFile(texGameFavOn, fmt("%s/gamefavon.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texGameFavOnSel, fmt("%s/gamefavons.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texGameFavOff, fmt("%s/gamefavoff.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texGameFavOffSel, fmt("%s/gamefavoffs.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texCategories, fmt("%s/btncat.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texCategoriesSel, fmt("%s/btncats.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texDelete, fmt("%s/delete.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texDeleteSel, fmt("%s/deletes.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texSettings, fmt("%s/btnconfig.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texSettingsSel, fmt("%s/btnconfigs.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texToggleBanner, fmt("%s/blank.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texSnapShotBg, fmt("%s/blank.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texSnapShotFrame, fmt("%s/blank.png", m_imgsDir.c_str())); TexHandle.fromImageFile(texBannerFrame, fmt("%s/blank.png", m_imgsDir.c_str())); _addUserLabels(m_gameLblUser, ARRAY_SIZE(m_gameLblUser), "GAME"); m_gameBg = _texture("GAME/BG", "texture", theme.bg, false); if(m_theme.loaded() && TexHandle.fromImageFile(bgLQ, fmt("%s/%s", m_themeDataDir.c_str(), m_theme.getString("GAME/BG", "texture").c_str()), GX_TF_CMPR, 64, 64) == TE_OK) m_gameBgLQ = bgLQ; m_gameBtnPlay = _addButton("GAME/PLAY_BTN", theme.btnFont, L"", 420, 344, 200, 48, theme.btnFontColor); m_gameBtnBack = _addButton("GAME/BACK_BTN", theme.btnFont, L"", 420, 400, 200, 48, theme.btnFontColor); m_gameBtnFavoriteOn = _addPicButton("GAME/FAVORITE_ON", texGameFavOn, texGameFavOnSel, 460, 220, 48, 48); m_gameBtnFavoriteOff = _addPicButton("GAME/FAVORITE_OFF", texGameFavOff, texGameFavOffSel, 460, 220, 48, 48); m_gameBtnCategories = _addPicButton("GAME/CATEGORIES_BTN", texCategories, texCategoriesSel, 532, 220, 48, 48); m_gameBtnSettings = _addPicButton("GAME/SETTINGS_BTN", texSettings, texSettingsSel, 460, 280, 48, 48); m_gameBtnDelete = _addPicButton("GAME/DELETE_BTN", texDelete, texDeleteSel, 532, 280, 48, 48); m_gameBtnBackFull = _addButton("GAME/BACK_FULL_BTN", theme.btnFont, L"", 100, 390, 200, 56, theme.btnFontColor); m_gameBtnPlayFull = _addButton("GAME/PLAY_FULL_BTN", theme.btnFont, L"", 340, 390, 200, 56, theme.btnFontColor); m_gameBtnToggle = _addPicButton("GAME/TOOGLE_BTN", texToggleBanner, texToggleBanner, 385, 31, 246, 135); m_gameBtnToggleFull = _addPicButton("GAME/TOOGLE_FULL_BTN", texToggleBanner, texToggleBanner, 20, 12, 608, 344); m_gameLblSnapBg = _addLabel("GAME/SNAP_BG", theme.txtFont, L"", 385, 31, 246, 170, theme.txtFontColor, 0, texSnapShotBg); m_gameLblSnap = _addLabel("GAME/SNAP", theme.txtFont, L"", 385, 31, 100, 100, theme.txtFontColor, 0, m_game_snap); m_gameLblOverlay = _addLabel("GAME/OVERLAY", theme.txtFont, L"", 385, 31, 100, 100, theme.txtFontColor, 0, m_game_overlay); // 8 pixel width frames m_gameLblSnapFrame = _addLabel("GAME/SNAP_FRAME", theme.txtFont, L"", 377, 23, 262, 186, theme.txtFontColor, 0, texSnapShotFrame); m_gameLblBannerFrame = _addLabel("GAME/BANNER_FRAME", theme.txtFont, L"", 377, 23, 262, 151, theme.txtFontColor, 0, texBannerFrame); m_gameButtonsZone.x = m_theme.getInt("GAME/ZONES", "buttons_x", 380); m_gameButtonsZone.y = m_theme.getInt("GAME/ZONES", "buttons_y", 0); m_gameButtonsZone.w = m_theme.getInt("GAME/ZONES", "buttons_w", 640); m_gameButtonsZone.h = m_theme.getInt("GAME/ZONES", "buttons_h", 480); m_gameButtonsZone.hide = m_theme.getBool("GAME/ZONES", "buttons_hide", true); _setHideAnim(m_gameBtnPlay, "GAME/PLAY_BTN", 0, 0, 1.f, -1.f); _setHideAnim(m_gameBtnBack, "GAME/BACK_BTN", 0, 0, 1.f, -1.f); _setHideAnim(m_gameBtnFavoriteOn, "GAME/FAVORITE_ON", 0, 0, 1.f, -1.f); _setHideAnim(m_gameBtnFavoriteOff, "GAME/FAVORITE_OFF", 0, 0, 1.f, -1.f); _setHideAnim(m_gameBtnCategories, "GAME/CATEGORIES_BTN", 0, 0, 1.f, -1.f); _setHideAnim(m_gameBtnSettings, "GAME/SETTINGS_BTN", 0, 0, 1.f, -1.f); _setHideAnim(m_gameBtnDelete, "GAME/DELETE_BTN", 0, 0, 1.f, -1.f); _setHideAnim(m_gameBtnPlayFull, "GAME/PLAY_FULL_BTN", 0, 0, 1.f, 0.f); _setHideAnim(m_gameBtnBackFull, "GAME/BACK_FULL_BTN", 0, 0, 1.f, 0.f); _setHideAnim(m_gameBtnToggle, "GAME/TOOGLE_BTN", 200, 0, 1.f, 0.f); _setHideAnim(m_gameBtnToggleFull, "GAME/TOOGLE_FULL_BTN", 200, 0, 1.f, 0.f); _setHideAnim(m_gameLblSnapBg, "GAME/SNAP_BG", 0, 0, 1.f, 1.f); _setHideAnim(m_gameLblSnap, "GAME/SNAP", 0, 0, 1.f, 1.f); _setHideAnim(m_gameLblOverlay, "GAME/OVERLAY", 0, 0, 1.f, 1.f); _setHideAnim(m_gameLblSnapFrame, "GAME/SNAP_FRAME", 0, 0, 1.f, 1.f); _setHideAnim(m_gameLblBannerFrame, "GAME/BANNER_FRAME", 0, 0, 1.f, 1.f); _hideGame(true); _textGame(); snapbg_x = m_theme.getInt("GAME/SNAP_BG", "x", 385); snapbg_y = m_theme.getInt("GAME/SNAP_BG", "y", 31); snapbg_w = m_theme.getInt("GAME/SNAP_BG", "width", 246); snapbg_h = m_theme.getInt("GAME/SNAP_BG", "height", 170); /* gc disc prompt menu */ m_promptBg = _texture("PROMPT/BG", "texture", theme.bg, false); m_promptLblQuestion = _addLabel("PROMPT/QUESTION", theme.lblFont, L"", 112, 0, 500, 420, theme.lblFontColor, FTGX_JUSTIFY_LEFT | FTGX_ALIGN_MIDDLE); m_promptBtnChoice1 = _addButton("PROMPT/CHOICE1", theme.btnFont, L"", 112, 320, 200, 48, theme.btnFontColor); m_promptBtnChoice2 = _addButton("PROMPT/CHOICE2", theme.btnFont, L"", 332, 320, 200, 48, theme.btnFontColor); _setHideAnim(m_promptLblQuestion, "PROMPT/QUESTION", 0, 0, 0.f, 0.f); _setHideAnim(m_promptBtnChoice1, "PROMPT/CHOICE1", 0, 0, 1.f, -1.f); _setHideAnim(m_promptBtnChoice2, "PROMPT/CHOICE2", 0, 0, 1.f, -1.f); m_btnMgr.hide(m_promptLblQuestion, true); m_btnMgr.hide(m_promptBtnChoice1, true); m_btnMgr.hide(m_promptBtnChoice2, true); } void CMenu::_textGame(void) { m_btnMgr.setText(m_gameBtnPlay, _t("gm1", L"Play")); m_btnMgr.setText(m_gameBtnBack, _t("gm2", L"Back")); m_btnMgr.setText(m_gameBtnPlayFull, _t("gm1", L"Play")); m_btnMgr.setText(m_gameBtnBackFull, _t("gm2", L"Back")); } struct IMD5Header { u32 fcc; u32 filesize; u8 zeroes[8]; u8 crypto[16]; } ATTRIBUTE_PACKED; // loads game banner and sound to be played by mainloop void * CMenu::_gameSoundThread(void *obj) { CMenu *m = (CMenu*)obj; m->m_soundThrdBusy = true; m->m_gamesound_changed = false; m->m_snapshot_loaded = false; m_banner_loaded = false; CurrentBanner.ClearBanner();//clear current banner from memory /* Set to empty textures to clear current snapshot from screen */ TexData emptyTex; m_btnMgr.setTexture(m->m_gameLblSnap, emptyTex); m_btnMgr.setTexture(m->m_gameLblOverlay, emptyTex); u8 *custom_bnr_file = NULL; u32 custom_bnr_size = 0; char custom_banner[256]; custom_banner[255] = '\0'; u8 *cached_bnr_file = NULL; u32 cached_bnr_size = 0; char cached_banner[256]; cached_banner[255] = '\0'; const dir_discHdr *GameHdr = CoverFlow.getHdr(); if(GameHdr->type == TYPE_PLUGIN) { char game_sound[256]; game_sound[255] = '\0'; char fileNameFull[128]; fileNameFull[127] = '\0'; char fileName[128]; fileName[127] = '\0'; strncpy(fileNameFull, CoverFlow.getFilenameId(GameHdr), sizeof(fileNameFull) - 1); strcpy(fileName, fileNameFull); if(strrchr(fileName, '.') != NULL) *strrchr(fileName, '.') = '\0';// remove .ext const char *coverDir = m_plugin.GetCoverFolderName(GameHdr->settings[0]); strncpy(custom_banner, fmt("%s/%s/%s.bnr", m->m_customBnrDir.c_str(), coverDir, fileNameFull), sizeof(custom_banner) - 1); if(!fsop_FileExist(custom_banner)) strncpy(custom_banner, fmt("%s/%s/%s.bnr", m->m_customBnrDir.c_str(), coverDir, fileName), sizeof(custom_banner) - 1); strncpy(game_sound, fmt("%s/gamesounds/%s/%s", m->m_dataDir.c_str(), coverDir, fileNameFull), sizeof(game_sound) - 1); if(!fsop_FileExist(game_sound)) strncpy(game_sound, fmt("%s/gamesounds/%s/%s", m->m_dataDir.c_str(), coverDir, fileName), sizeof(game_sound) - 1); /* get plugin rom custom banner */ fsop_GetFileSizeBytes(custom_banner, &custom_bnr_size); if(custom_bnr_size > 0) { custom_bnr_file = (u8*)MEM2_lo_alloc(custom_bnr_size); if(custom_bnr_file != NULL) { fsop_ReadFileLoc(custom_banner, custom_bnr_size, (void*)custom_bnr_file); m_banner_loaded = true; } } /* if no banner try getting snap shot */ if((custom_bnr_size == 0 || custom_bnr_file == NULL) && m->m_platform.loaded()) { //gprintf("trying to get snapshot\n"); m_banner.DeleteBanner(); char GameID[7]; char platformName[16]; const char *TMP_Char = NULL; GameTDB gametdb; strncpy(m_plugin.PluginMagicWord, fmt("%08x", GameHdr->settings[0]), 8); snprintf(platformName, sizeof(platformName), "%s", m->m_platform.getString("PLUGINS", m_plugin.PluginMagicWord, "").c_str()); strcpy(GameID, GameHdr->id);// GameHdr->id is null terminated if(strlen(platformName) != 0 && strcasecmp(GameID, "PLUGIN") != 0) { string newName = m->m_platform.getString("COMBINED", platformName); if(newName.empty()) m->m_platform.remove("COMBINED", platformName); else snprintf(platformName, sizeof(platformName), "%s", newName.c_str()); /* Load platform name.xml database to get game's info using the gameID */ gametdb.OpenFile(fmt("%s/%s/%s.xml", m->m_pluginDataDir.c_str(), platformName, platformName)); if(gametdb.IsLoaded()) { gametdb.SetLanguageCode(m->m_loc.getString(m->m_curLanguage, "gametdb_code", "EN").c_str()); /* Get roms's title without the extra ()'s or []'s */ string ShortName; if(strrchr(GameHdr->path, '/') != NULL) ShortName = m_plugin.GetRomName(GameHdr->path); else { char title[64]; title[63] = '\0'; wcstombs(title, GameHdr->title, sizeof(title) - 1); ShortName = title; } const char *snap_path = NULL; if(strcasestr(platformName, "ARCADE") || strcasestr(platformName, "CPS") || !strncasecmp(platformName, "NEOGEO", 6)) snap_path = fmt("%s/%s/%s.png", m->m_snapDir.c_str(), platformName, ShortName.c_str()); else if(gametdb.GetName(GameID, TMP_Char)) snap_path = fmt("%s/%s/%s.png", m->m_snapDir.c_str(), platformName, TMP_Char); gametdb.CloseFile(); if(snap_path == NULL || !fsop_FileExist(snap_path)) snap_path = fmt("%s/%s/%s.png", m->m_snapDir.c_str(), platformName, GameID); if(fsop_FileExist(snap_path)) { m->m_snapshot_loaded = true; TexHandle.fromImageFile(m->m_game_snap, snap_path); /* get snapshot position and dimensions to center it on the snap background */ int snap_w = m->m_game_snap.width; int snap_h = m->m_game_snap.height; int width_over = snap_w - m->snapbg_w; int height_over = snap_h - m->snapbg_h; float aspect_ratio = (float)snap_w / (float)snap_h; if(width_over > 0 || height_over > 0) { if(width_over > height_over) { snap_w = m->snapbg_w; snap_h = (int)((float)snap_w / aspect_ratio); } else { snap_h = m->snapbg_h; snap_w = (int)((float)snap_h * aspect_ratio); } } int x_pos = (m->snapbg_w - snap_w) / 2 + m->snapbg_x; int y_pos = (m->snapbg_h - snap_h) / 2 + m->snapbg_y; m_btnMgr.setTexture(m->m_gameLblSnap, m->m_game_snap, x_pos, y_pos, snap_w, snap_h); /* get possible overlay */ const char *overlay_path = fmt("%s/%s_overlay.png", m->m_snapDir.c_str(), platformName); if(fsop_FileExist(overlay_path)) { TexHandle.fromImageFile(m->m_game_overlay, overlay_path); m_btnMgr.setTexture(m->m_gameLblOverlay, m->m_game_overlay, x_pos, y_pos, snap_w, snap_h); } else TexHandle.Cleanup(m->m_game_overlay); } } } if(!m->m_snapshot_loaded) { TexHandle.Cleanup(m->m_game_snap); TexHandle.Cleanup(m->m_game_overlay); } /* try to get plugin rom gamesound or just the default plugin gamesound */ bool found = false; if(fsop_FileExist(fmt("%s.mp3", game_sound))) { strcat(game_sound, ".mp3"); found = true; } else if(fsop_FileExist(fmt("%s.wav", game_sound))) { strcat(game_sound, ".wav"); found = true; } else if(fsop_FileExist(fmt("%s.ogg", game_sound))) { strcat(game_sound, ".ogg"); found = true; } if(found) { u32 size = 0; u8 *sf = fsop_ReadFile(game_sound, &size); m->m_gameSound.Load(sf, size); } else m->m_gameSound.Load(m_plugin.GetBannerSound(GameHdr->settings[0]), m_plugin.GetBannerSoundSize()); if(m->m_gameSound.IsLoaded()) m->m_gamesound_changed = true; m->m_soundThrdBusy = false; return NULL; } } else { /* try to get custom banner for wii, gc, and channels */ /* check custom ID6 first */ strncpy(custom_banner, fmt("%s/%s.bnr", m->m_customBnrDir.c_str(), GameHdr->id), 255); fsop_GetFileSizeBytes(custom_banner, &custom_bnr_size); if(custom_bnr_size > 0) { custom_bnr_file = (u8*)MEM2_lo_alloc(custom_bnr_size); if(custom_bnr_file != NULL) { fsop_ReadFileLoc(custom_banner, custom_bnr_size, (void*)custom_bnr_file); m_banner_loaded = true; } } else /* no custom ID6 or too big, try ID3 */ { strncpy(custom_banner, fmt("%s/%.3s.bnr", m->m_customBnrDir.c_str(), GameHdr->id), 255); fsop_GetFileSizeBytes(custom_banner, &custom_bnr_size); if(custom_bnr_size > 0) { custom_bnr_file = (u8*)MEM2_lo_alloc(custom_bnr_size); if(custom_bnr_file != NULL) { fsop_ReadFileLoc(custom_banner, custom_bnr_size, (void*)custom_bnr_file); m_banner_loaded = true; } } } } if(GameHdr->type == TYPE_GC_GAME && custom_bnr_file == NULL) { /* gc game but no custom banner. so we make one ourselves. and exit sound thread. */ //get the gc game's opening.bnr from ISO - a 96x32 image to add to the gc banner included with wiiflow GC_Disc_Reader.init(GameHdr->path); u8 *opening_bnr = GC_Disc_Reader.GetGameCubeBanner(); if(opening_bnr != NULL) { //creategcbanner adds the opening.bnr image and game title to the wiiflow gc banner m_banner.CreateGCBanner(opening_bnr, m->m_wbf1_font, m->m_wbf2_font, GameHdr->title); m_banner_loaded = true; } else m_banner.DeleteBanner(); GC_Disc_Reader.clear(); if(m->m_gc_play_default_sound) { //get wiiflow gc ogg sound to play with banner m->m_gameSound.Load(gc_ogg, gc_ogg_size, false); if(m->m_gameSound.IsLoaded()) m->m_gamesound_changed = true; } m->m_soundThrdBusy = false; return NULL; } if(custom_bnr_file == NULL)/* no custom banner load and if wii or channel game try cached banner id6 only*/ { strncpy(cached_banner, fmt("%s/%s.bnr", m->m_bnrCacheDir.c_str(), GameHdr->id), 255); fsop_GetFileSizeBytes(cached_banner, &cached_bnr_size); if(cached_bnr_size > 0) { cached_bnr_file = (u8*)MEM2_lo_alloc(cached_bnr_size); if(cached_bnr_file != NULL) { fsop_ReadFileLoc(cached_banner, cached_bnr_size, (void*)cached_bnr_file); m_banner_loaded = true; } } } if(custom_bnr_file != NULL) CurrentBanner.SetBanner(custom_bnr_file, custom_bnr_size, true, true); else if(cached_bnr_file != NULL) CurrentBanner.SetBanner(cached_bnr_file, cached_bnr_size, false, true); else if(GameHdr->type == TYPE_WII_GAME) { m->_extractBnr(GameHdr); m_banner_loaded = true; } else if(GameHdr->type == TYPE_CHANNEL || GameHdr->type == TYPE_EMUCHANNEL) { ChannelHandle.GetBanner(TITLE_ID(GameHdr->settings[0], GameHdr->settings[1])); m_banner_loaded = true; } if(!CurrentBanner.IsValid()) { m_banner_loaded = false; m->m_gameSound.FreeMemory(); m_banner.DeleteBanner(); CurrentBanner.ClearBanner(); m->m_soundThrdBusy = false; return NULL; } //save new wii or channel banner to cache folder, gc and custom banners are not cached if(cached_bnr_file == NULL && custom_bnr_file == NULL) fsop_WriteFile(cached_banner, CurrentBanner.GetBannerFile(), CurrentBanner.GetBannerFileSize()); //load and init banner m_banner.LoadBanner(m->m_wbf1_font, m->m_wbf2_font); //get sound from wii, channel, or custom banner and load it to play with the banner u32 sndSize = 0; u8 *soundBin = CurrentBanner.GetFile("sound.bin", &sndSize); CurrentBanner.ClearBanner();// got sound.bin and banner for displaying is loaded so no longer need current banner. if(soundBin != NULL && (GameHdr->type != TYPE_GC_GAME || m->m_gc_play_banner_sound)) { if(memcmp(&((IMD5Header *)soundBin)->fcc, "IMD5", 4) == 0) { u32 newSize = 0; u8 *newSound = DecompressCopy(soundBin, sndSize, &newSize); free(soundBin);// no longer needed, now using decompressed newSound if(newSound == NULL || newSize == 0 || !m->m_gameSound.Load(newSound, newSize)) { m->m_gameSound.FreeMemory();// frees newSound m_banner.DeleteBanner();// the same as UnloadBanner m->m_soundThrdBusy = false; return NULL; } } else m->m_gameSound.Load(soundBin, sndSize); if(m->m_gameSound.IsLoaded()) m->m_gamesound_changed = true; else { m->m_gameSound.FreeMemory();// frees soundBin m_banner.DeleteBanner(); } } else { if(soundBin != NULL) free(soundBin); //gprintf("WARNING: No sound found in banner!\n"); m->m_gamesound_changed = true; m->m_gameSound.FreeMemory();// frees previous game sound } m->m_soundThrdBusy = false; return NULL; } u8 *GameSoundStack = NULL; u32 GameSoundSize = 0x10000; //64kb void CMenu::_playGameSound(void)// starts banner and gamesound loading thread { _cleanupBanner(true); m_gamesound_changed = false; if(m_bnrSndVol == 0) return; if(m_gameSoundThread != LWP_THREAD_NULL) _stopGameSoundThread(); GameSoundStack = (u8*)MEM2_lo_alloc(GameSoundSize); LWP_CreateThread(&m_gameSoundThread, _gameSoundThread, this, GameSoundStack, GameSoundSize, 60); } void CMenu::_stopGameSoundThread()//stops banner and gamesound loading thread { if(m_gameSoundThread == LWP_THREAD_NULL) return; if(LWP_ThreadIsSuspended(m_gameSoundThread)) LWP_ResumeThread(m_gameSoundThread); while(m_soundThrdBusy) usleep(500); LWP_JoinThread(m_gameSoundThread, NULL); m_gameSoundThread = LWP_THREAD_NULL; if(GameSoundStack) MEM2_lo_free(GameSoundStack); GameSoundStack = NULL; }