/* Copyright (c) 2012 - Dimok This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #if __GNUC__ > 8 #pragma GCC diagnostic ignored "-Waddress-of-packed-member" #endif #include #include #include "Channels/channels.h" #include "GameCube/GCGames.h" #include "libs/libwbfs/gcdisc.h" #include "FileOperations/fileops.h" #include "language/gettext.h" #include "usbloader/disc.h" #include "usbloader/wbfs.h" #include "usbloader/wdvd.h" #include "usbloader/wbfs/wbfs_rw.h" #include "utils/sjis.h" #include "utils/uncompress.h" #include "themes/CTheme.h" #include "settings/GameTitles.h" #include "wstring.hpp" #include "OpeningBNR.hpp" BNRInstance *BNRInstance::instance = NULL; OpeningBNR::OpeningBNR() : imetHdr(0), filesize(0) { memset(gameID, 0, sizeof(gameID)); } OpeningBNR::~OpeningBNR() { if (imetHdr) free(imetHdr); } bool OpeningBNR::LoadCachedBNR(const char *id) { char path[255]; snprintf(path, sizeof(path), "%s%.6s.bnr", Settings.BNRCachePath, id); if ((filesize = FileSize(path)) == 0) { snprintf(path, sizeof(path), "%s%.3s.bnr", Settings.BNRCachePath, id); if ((filesize = FileSize(path)) == 0) return false; } FILE *f = fopen(path, "rb"); if (!f) return false; imetHdr = (IMETHeader *)malloc(filesize); if (!imetHdr) { fclose(f); return false; } fread(imetHdr, 1, filesize, f); fclose(f); if (imetHdr->fcc != 'IMET') { //! check if it's a channel .app file IMETHeader *channelImet = (IMETHeader *)(((u8 *)imetHdr) + 0x40); if (channelImet->fcc != 'IMET') { free(imetHdr); imetHdr = NULL; return false; } //! just move it 0x40 bytes back, the rest 0x40 bytes will just be unused //! as it's a temporary file usually it's not worth to reallocate filesize -= 0x40; memcpy(imetHdr, channelImet, filesize); } return true; } void OpeningBNR::WriteCachedBNR(const char *id, const u8 *buffer, u32 size) { char path[255]; snprintf(path, sizeof(path), "%s%.6s.bnr", Settings.BNRCachePath, id); CreateSubfolder(Settings.BNRCachePath); FILE *f = fopen(path, "wb"); if (!f) return; fwrite(buffer, 1, size, f); fclose(f); } bool OpeningBNR::Load(const discHdr *header) { if (!header) return false; if (memcmp(gameID, header->id, 6) == 0) return true; memcpy(gameID, header->id, 6); if (imetHdr) free(imetHdr); imetHdr = NULL; switch (header->type) { case TYPE_GAME_WII_IMG: case TYPE_GAME_WII_DISC: return LoadWiiBanner(header); case TYPE_GAME_NANDCHAN: case TYPE_GAME_EMUNANDCHAN: return LoadChannelBanner(header); case TYPE_GAME_GC_IMG: case TYPE_GAME_GC_DISC: case TYPE_GAME_GC_EXTRACTED: if (!Settings.CacheBNRFiles) return false; return LoadCachedBNR((char *)header->id); default: break; } return false; } bool OpeningBNR::LoadWiiBanner(const discHdr *header) { if (!header || ((header->type != TYPE_GAME_WII_IMG) && (header->type != TYPE_GAME_WII_DISC))) return false; if (Settings.CacheBNRFiles && LoadCachedBNR((const char *)header->id)) return true; if (header->type == TYPE_GAME_WII_DISC) { wiidisc_t *wdisc = wd_open_disc(__ReadDVD, 0); if (!wdisc) return false; imetHdr = (IMETHeader *)wd_extract_file(wdisc, ALL_PARTITIONS, (char *)"opening.bnr"); filesize = wdisc->extracted_size; wd_close_disc(wdisc); } else { wbfs_disc_t *disc = WBFS_OpenDisc((u8 *)gameID); if (!disc) return false; wiidisc_t *wdisc = wd_open_disc((s32(*)(void *, u32, u32, void *))wbfs_disc_read, disc); if (!wdisc) { WBFS_CloseDisc(disc); return false; } imetHdr = (IMETHeader *)wd_extract_file(wdisc, ALL_PARTITIONS, (char *)"opening.bnr"); filesize = wdisc->extracted_size; wd_close_disc(wdisc); WBFS_CloseDisc(disc); } if (!imetHdr) return false; if (imetHdr->fcc != 'IMET') { free(imetHdr); imetHdr = NULL; return false; } if (Settings.CacheBNRFiles) WriteCachedBNR((const char *)header->id, (u8 *)imetHdr, filesize); return true; } bool OpeningBNR::LoadChannelBanner(const discHdr *header) { if (!header || (header->tid == 0) || ((header->type != TYPE_GAME_NANDCHAN) && (header->type != TYPE_GAME_EMUNANDCHAN))) return false; if (Settings.CacheBNRFiles && LoadCachedBNR((char *)header->id)) return true; const u64 &tid = header->tid; const char *pathPrefix = (header->type == TYPE_GAME_EMUNANDCHAN) ? Settings.NandEmuChanPath : ""; imetHdr = (IMETHeader *)Channels::GetOpeningBnr(tid, &filesize, pathPrefix); if (!imetHdr) return false; if (imetHdr->fcc != 'IMET') { free(imetHdr); imetHdr = NULL; return false; } if (Settings.CacheBNRFiles) WriteCachedBNR((char *)header->id, (u8 *)imetHdr, filesize); return true; } const u16 *OpeningBNR::GetIMETTitle(int lang) { if (!imetHdr || lang < 0 || lang >= 10) return NULL; if (imetHdr->fcc != 'IMET') return NULL; if (imetHdr->names[lang][0] == 0) lang = CONF_LANG_ENGLISH; return imetHdr->names[lang]; // possible unaligned pointer value } static s32 GC_Disc_Read(void *fp, u32 offset, u32 count, void *iobuf) { if (fp) { fseek((FILE *)fp, offset, SEEK_SET); return fread(iobuf, 1, count, (FILE *)fp); } return __ReadDVDPlain(iobuf, count, offset); } u8 *OpeningBNR::LoadGCBNR(const discHdr *header, u32 *len) { if (!header || ((header->type != TYPE_GAME_GC_IMG) && (header->type != TYPE_GAME_GC_DISC) && (header->type != TYPE_GAME_GC_EXTRACTED))) return NULL; const char *path = GCGames::Instance()->GetPath((char *)header->id); if (!path) return NULL; FILE *file = NULL; GC_OpeningBnr *openingBnr = NULL; // read from file if ((header->type == TYPE_GAME_GC_IMG) || (header->type == TYPE_GAME_GC_DISC)) { //! open iso file if it's iso if (header->type == TYPE_GAME_GC_IMG) { file = fopen(path, "rb"); if (!file) return NULL; } gcdisc_t *disc = gc_open_disc(GC_Disc_Read, file); if (!disc) { fclose(file); return NULL; } if (!strcmp(Settings.db_language, "JA")) { bool loaded = gc_extract_file(disc, "openingJA.bnr"); if (!loaded) loaded = gc_extract_file(disc, "opening.bnr"); if (!loaded) loaded = gc_extract_file(disc, "openingUS.bnr"); if (!loaded) loaded = gc_extract_file(disc, "openingEU.bnr"); } else if (!strcmp(Settings.db_language, "EN")) { bool loaded = gc_extract_file(disc, "openingUS.bnr"); if (!loaded) loaded = gc_extract_file(disc, "opening.bnr"); if (!loaded) loaded = gc_extract_file(disc, "openingEU.bnr"); if (!loaded) loaded = gc_extract_file(disc, "openingJA.bnr"); } else { bool loaded = gc_extract_file(disc, "openingEU.bnr"); if (!loaded) loaded = gc_extract_file(disc, "opening.bnr"); if (!loaded) loaded = gc_extract_file(disc, "openingUS.bnr"); if (!loaded) loaded = gc_extract_file(disc, "openingJA.bnr"); } openingBnr = (GC_OpeningBnr *)disc->extracted_buffer; if (len) *len = disc->extracted_size; gc_close_disc(disc); } else if (header->type == TYPE_GAME_GC_EXTRACTED) { std::string gamePath = path; gamePath += "root/"; //! open default file first file = fopen((gamePath + "opening.bnr").c_str(), "rb"); // if not found try the region specific ones if (!strcmp(Settings.db_language, "JA")) { if (!file) file = fopen((gamePath + "openingJA.bnr").c_str(), "rb"); if (!file) file = fopen((gamePath + "openingUS.bnr").c_str(), "rb"); if (!file) file = fopen((gamePath + "openingEU.bnr").c_str(), "rb"); } else if (!strcmp(Settings.db_language, "EN")) { if (!file) file = fopen((gamePath + "openingUS.bnr").c_str(), "rb"); if (!file) file = fopen((gamePath + "openingEU.bnr").c_str(), "rb"); if (!file) file = fopen((gamePath + "openingJA.bnr").c_str(), "rb"); } else { if (!file) file = fopen((gamePath + "openingEU.bnr").c_str(), "rb"); if (!file) file = fopen((gamePath + "openingUS.bnr").c_str(), "rb"); if (!file) file = fopen((gamePath + "openingJA.bnr").c_str(), "rb"); } // file not found if (!file) return NULL; fseek(file, 0, SEEK_END); int size = ftell(file); rewind(file); openingBnr = (GC_OpeningBnr *)malloc(size); if (openingBnr) { if (len) *len = size; fread(openingBnr, 1, size, file); } } if (file) fclose(file); if (!openingBnr) return NULL; // check magic of the opening bnr if (openingBnr->magic != 'BNR1' && openingBnr->magic != 'BNR2') { free(openingBnr); return NULL; } return (u8 *)openingBnr; } CustomBanner *OpeningBNR::CreateGCBanner(const discHdr *header) { int language = 0; u32 openingBnrSize; wString developer; GC_OpeningBnr *openingBnr = (GC_OpeningBnr *)LoadGCBNR(header, &openingBnrSize); CustomBanner *banner = new CustomBanner; banner->LoadBanner(Resources::GetFile("custom_banner.bnr"), Resources::GetFileSize("custom_banner.bnr")); banner->SetBannerPngImage("bg.tpl", Resources::GetFile("gc_banner_bg.png"), Resources::GetFileSize("gc_banner_bg.png")); banner->SetBannerText("T_PF", tr("GameCube")); if (openingBnr) { banner->SetBannerTexture("HBPic.tpl", openingBnr->tpl_data, 96, 32, GX_TF_RGB5A3); // European opening bnr file if (openingBnr->magic == 'BNR2') { if (!strcmp(Settings.db_language, "DE")) language = 1; else if (!strcmp(Settings.db_language, "FR")) language = 2; else if (!strcmp(Settings.db_language, "ES")) language = 3; else if (!strcmp(Settings.db_language, "IT")) language = 4; else if (!strcmp(Settings.db_language, "NL")) language = 5; if ((0x1820 + sizeof(openingBnr->description[0]) * language) > openingBnrSize) { language = 0; } } // sets the developer developer.resize(strlen((char *)openingBnr->description[language].developer)); for (u32 i = 0; i < developer.size(); i++) developer[i] = *(openingBnr->description[language].developer + i); banner->SetBannerText("T_Coded_by", tr("Developer:")); banner->SetBannerText("T_coder", developer.toUTF8().c_str()); // sets the description and converts encodings (Japan and Taiwan) if (header->id[3] == 'J' || header->id[3] == 'W') { std::string description((char *)openingBnr->description[language].long_description); banner->SetBannerText("T_short_descript", sj2utf8(description).c_str()); } else { wString description; description.resize(strlen((char *)openingBnr->description[language].long_description)); for (u32 i = 0; i < description.size(); i++) description[i] = *(openingBnr->description[language].long_description + i); banner->SetBannerText("T_short_descript", description.toUTF8().c_str()); } // free buffer free(openingBnr); } else { banner->SetBannerPngImage("HBPic.tpl", Resources::GetFile("gc_icon_bg.png"), Resources::GetFileSize("gc_icon_bg.png")); banner->SetBannerText("T_Coded_by", tr("Developer:")); banner->SetBannerText("T_coder", tr("Unknown")); banner->SetBannerText("T_short_descript", " "); } if (strcmp(Settings.db_language, "KO") == 0) { banner->SetBannerText("T_Coded_by", developer.toUTF8().c_str()); banner->SetBannerPaneVisible("T_coder", false); } else banner->SetBannerText("T_name", GameTitles.GetTitle(header)); banner->SetBannerPaneVisible("Line1", false); banner->SetBannerPaneVisible("Line2", false); banner->SetBannerPaneVisible("T_Released", false); banner->SetBannerPaneVisible("T_release_date", false); banner->SetBannerPaneVisible("T_versiontext", false); banner->SetBannerPaneVisible("T_version", false); banner->SetBannerTextureScale(Settings.GCBannerScale); return banner; } CustomBanner *OpeningBNR::CreateGCIcon(const discHdr *header) { GC_OpeningBnr *openingBnr = (GC_OpeningBnr *)LoadGCBNR(header); CustomBanner *newBanner = new CustomBanner; newBanner->LoadIcon(Resources::GetFile("custom_banner.bnr"), Resources::GetFileSize("custom_banner.bnr")); if (openingBnr) newBanner->SetIconTexture("Iconpng.tpl", openingBnr->tpl_data, 96, 32, GX_TF_RGB5A3); else newBanner->SetIconPngImage("Iconpng.tpl", Resources::GetFile("gc_icon_bg.png"), Resources::GetFileSize("gc_icon_bg.png")); newBanner->SetIconPngImage("HBLogo.tpl", Resources::GetFile("gc_icon_bg.png"), Resources::GetFileSize("gc_icon_bg.png")); // free buffer if (openingBnr) free(openingBnr); return newBanner; }