diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d8077d73..2bcdf4cba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -771,7 +771,9 @@ target_sources(${PROJECT_NAME} PRIVATE core/imgread/cue.cpp core/imgread/gdi.cpp core/imgread/ImgReader.cpp - core/imgread/ioctl.cpp) + core/imgread/ioctl.cpp + core/imgread/isofs.cpp + core/imgread/isofs.h) if(NOT LIBRETRO) target_sources(${PROJECT_NAME} PRIVATE diff --git a/core/imgread/isofs.cpp b/core/imgread/isofs.cpp new file mode 100644 index 000000000..341e92b51 --- /dev/null +++ b/core/imgread/isofs.cpp @@ -0,0 +1,112 @@ +/* + Copyright 2022 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast 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 Flycast. If not, see . + */ +#include "isofs.h" +#include "reios/iso9660.h" + +static u32 decode_iso733(iso733_t v) +{ + return ((v >> 56) & 0x000000FF) + | ((v >> 40) & 0x0000FF00) + | ((v >> 24) & 0x00FF0000) + | ((v >> 8) & 0xFF000000); +} + +IsoFs::IsoFs(Disc *disc) : disc(disc) +{ + baseFad = disc->GetBaseFAD(); +} + +IsoFs::Directory *IsoFs::getRoot() +{ + u8 temp[2048]; + disc->ReadSectors(baseFad + 16, 1, temp, 2048); + // Primary Volume Descriptor + const iso9660_pvd_t *pvd = (const iso9660_pvd_t *)temp; + + Directory *root = new Directory(this); + if (pvd->type == 1 && !memcmp(pvd->id, ISO_STANDARD_ID, strlen(ISO_STANDARD_ID)) && pvd->version == 1) + { + u32 lba = decode_iso733(pvd->root_directory_record.extent); + u32 len = decode_iso733(pvd->root_directory_record.size); + + len = ((len + 2047) / 2048) * 2048; + root->data.resize(len); + + DEBUG_LOG(GDROM, "iso9660 root directory FAD: %d, len: %d", 150 + lba, len); + disc->ReadSectors(150 + lba, len / 2048, root->data.data(), 2048); + } + else { + WARN_LOG(GDROM, "iso9660 PVD NOT found"); + root->data.resize(1); + root->data[0] = 0; + } + return root; +} + +IsoFs::Entry *IsoFs::Directory::getEntry(const std::string& name) +{ + std::string isoname = name + ';'; + for (u32 i = 0; i < data.size(); ) + { + const iso9660_dir_t *dir = (const iso9660_dir_t *)&data[i]; + if (dir->length == 0) + break; + + if ((u8)dir->filename.str[0] > isoname.size() + && memcmp(dir->filename.str + 1, isoname.c_str(), isoname.size()) == 0) + { + DEBUG_LOG(GDROM, "Found %s at offset %X", name.c_str(), i); + u32 startFad = decode_iso733(dir->extent) + 150; + u32 len = decode_iso733(dir->size); + if ((dir->file_flags & ISO_DIRECTORY) == 0) + { + File *file = new File(fs); + file->startFad = startFad; + file->len = len; + + return file; + } + else + { + Directory *directory = new Directory(fs); + directory->data.resize(len); + fs->disc->ReadSectors(startFad, len / 2048, directory->data.data(), 2048); + + return directory; + } + } + i += dir->length; + } + return nullptr; +} + +u32 IsoFs::File::read(u8 *buf, u32 size, u32 offset) const +{ + size = std::min(size, len - offset); + u32 sectors = size / 2048; + fs->disc->ReadSectors(startFad + offset / 2048, sectors, buf, 2048); + size -= sectors * 2048; + if (size > 0) + { + u8 temp[2048]; + fs->disc->ReadSectors(startFad + offset / 2048 + sectors, 1, temp, 2048); + memcpy(buf + sectors * 2048, temp, size); + } + return sectors * 2048 + size; +} diff --git a/core/imgread/isofs.h b/core/imgread/isofs.h new file mode 100644 index 000000000..4d2944900 --- /dev/null +++ b/core/imgread/isofs.h @@ -0,0 +1,76 @@ +/* + Copyright 2022 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast 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 Flycast. If not, see . + */ +#pragma once +#include "common.h" + +class IsoFs +{ +public: + class Entry + { + public: + virtual bool isDirectory() const = 0; + virtual ~Entry() = default; + + protected: + Entry(IsoFs *fs) : fs(fs) {} + + IsoFs *fs; + }; + + class Directory final : public Entry + { + public: + bool isDirectory() const override { return true; } + + Entry *getEntry(const std::string& name); + + private: + Directory(IsoFs *fs) : Entry(fs) {} + + std::vector data; + + friend class IsoFs; + }; + + class File final : public Entry + { + public: + bool isDirectory() const override { return false; } + + u32 getSize() const { return len; } + + u32 read(u8 *buf, u32 size, u32 offset = 0) const; + + private: + File(IsoFs *fs) : Entry(fs) {} + + u32 startFad = 0; + u32 len = 0; + + friend class IsoFs; + }; + + IsoFs(Disc *disc); + Directory *getRoot(); + +private: + Disc *disc; + u32 baseFad; +}; diff --git a/core/reios/descrambl.cpp b/core/reios/descrambl.cpp index aa92ddd13..445cd35b2 100644 --- a/core/reios/descrambl.cpp +++ b/core/reios/descrambl.cpp @@ -7,25 +7,24 @@ */ #include "descrambl.h" -#include "imgread/common.h" #include #define MAXCHUNK (2048*1024) -static unsigned int seed; +static u32 seed; -static void my_srand(unsigned int n) +static void my_srand(u32 n) { seed = n & 0xffff; } -static unsigned int my_rand() +static u32 my_rand() { seed = (seed * 2109 + 9273) & 0x7fff; return (seed + 0xc000) & 0xffff; } -static void load_chunk(u8* &src, unsigned char *ptr, unsigned long sz) +static void load_chunk(const u8* &src, u8 *ptr, u32 sz) { verify(sz <= MAXCHUNK); @@ -53,32 +52,23 @@ static void load_chunk(u8* &src, unsigned char *ptr, unsigned long sz) } } -static void descrambl_buffer(u8* src, unsigned char *dst, unsigned long filesz) +void descrambl_buffer(const u8 *src, u8 *dst, u32 size) { - unsigned long chunksz; + u32 chunksz; - my_srand(filesz); + my_srand(size); /* Descramble 2 meg blocks for as long as possible, then gradually reduce the window down to 32 bytes (1 slice) */ for (chunksz = MAXCHUNK; chunksz >= 32; chunksz >>= 1) - while (filesz >= chunksz) + while (size >= chunksz) { load_chunk(src, dst, chunksz); - filesz -= chunksz; + size -= chunksz; dst += chunksz; } /* Load final incomplete slice */ - if (filesz) - memcpy(dst, src, filesz); -} - -void descrambl_file(u32 FAD, u32 file_size, u8* dst) { - u8* temp_file = new u8[file_size + 2048]; - libGDR_ReadSector(temp_file, FAD, (file_size+2047) / 2048, 2048); - - descrambl_buffer(temp_file, dst, file_size); - - delete[] temp_file; + if (size) + memcpy(dst, src, size); } diff --git a/core/reios/descrambl.h b/core/reios/descrambl.h index 77b02c013..333eea14c 100644 --- a/core/reios/descrambl.h +++ b/core/reios/descrambl.h @@ -1,4 +1,4 @@ #pragma once #include "types.h" -void descrambl_file(u32 FAD, u32 file_size, u8* dst); +void descrambl_buffer(const u8 *src, u8 *dst, u32 size); diff --git a/core/reios/reios.cpp b/core/reios/reios.cpp index 54ddf733b..6aee35e97 100644 --- a/core/reios/reios.cpp +++ b/core/reios/reios.cpp @@ -21,12 +21,12 @@ #include "hw/holly/sb_mem.h" #include "hw/holly/sb.h" #include "hw/naomi/naomi_cart.h" -#include "iso9660.h" #include "font.h" #include "hw/aica/aica.h" #include "hw/aica/aica_mem.h" #include "hw/pvr/pvr_regs.h" #include "imgread/common.h" +#include "imgread/isofs.h" #include "oslib/oslib.h" #include @@ -50,120 +50,82 @@ static MemChip *flashrom; static u32 base_fad = 45150; static bool descrambl = false; static u32 bootSectors; +extern Disc *disc; static void reios_pre_init() { - if (libGDR_GetDiscType() == GdRom) { - base_fad = 45150; - descrambl = false; - } else { - u8 ses[6]; - libGDR_GetSessionInfo(ses, 0); - libGDR_GetSessionInfo(ses, ses[2]); - base_fad = (ses[3] << 16) | (ses[4] << 8) | (ses[5] << 0); - descrambl = true; + if (disc != nullptr) + { + base_fad = disc->GetBaseFAD(); + descrambl = disc->type != GdRom; } } -static u32 decode_iso733(iso733_t v) -{ - return ((v >> 56) & 0x000000FF) - | ((v >> 40) & 0x0000FF00) - | ((v >> 24) & 0x00FF0000) - | ((v >> 8) & 0xFF000000); -} - static bool reios_locate_bootfile(const char* bootfile) { reios_pre_init(); + if (ip_meta.wince == '1' && descrambl) + { + ERROR_LOG(REIOS, "Unsupported CDI: wince == '1'"); + return false; + } // Load IP.BIN bootstrap libGDR_ReadSector(GetMemPtr(0x8c008000, 0), base_fad, 16, 2048); - u32 data_len = 2048 * 1024; - u8* temp = new u8[data_len]; - - libGDR_ReadSector(temp, base_fad + 16, 1, 2048); - iso9660_pvd_t *pvd = (iso9660_pvd_t *)temp; - - if (pvd->type == 1 && !memcmp(pvd->id, ISO_STANDARD_ID, strlen(ISO_STANDARD_ID)) && pvd->version == 1) + IsoFs isofs(disc); + std::unique_ptr root(isofs.getRoot()); + if (root == nullptr) { - INFO_LOG(REIOS, "iso9660 PVD found"); - u32 lba = decode_iso733(pvd->root_directory_record.extent); - u32 len = decode_iso733(pvd->root_directory_record.size); - - data_len = ((len + 2047) / 2048) * 2048; - - INFO_LOG(REIOS, "iso9660 root_directory, FAD: %d, len: %d", 150 + lba, data_len); - libGDR_ReadSector(temp, 150 + lba, data_len / 2048, 2048); + ERROR_LOG(REIOS, "ISO file system root not found"); + return false; } - else { - libGDR_ReadSector(temp, base_fad + 16, data_len / 2048, 2048); - } - - int bootfile_len = strlen(bootfile); - while (bootfile_len > 0 && isspace(bootfile[bootfile_len - 1])) - bootfile_len--; - for (u32 i = 0; i < data_len; ) + std::unique_ptr bootEntry(root->getEntry(trim_trailing_ws(bootfile))); + if (bootEntry == nullptr || bootEntry->isDirectory()) { - iso9660_dir_t *dir = (iso9660_dir_t *)&temp[i]; - if (dir->length == 0) - break; + ERROR_LOG(REIOS, "Boot file '%s' not found", bootfile); + return false; + } + IsoFs::File *bootFile = (IsoFs::File *)bootEntry.get(); - if ((dir->file_flags & ISO_DIRECTORY) == 0 && memcmp(dir->filename.str + 1, bootfile, bootfile_len) == 0) - { - INFO_LOG(REIOS, "Found %.*s at offset %X", bootfile_len, bootfile, i); + u32 offset = 0; + u32 size = bootFile->getSize(); + if (ip_meta.wince == '1') + { + bootFile->read(GetMemPtr(0x8ce01000, 2048), 2048); + offset = 2048; + size -= offset; + } + bootSectors = size / 2048; - u32 lba = decode_iso733(dir->extent) + 150; - u32 len = decode_iso733(dir->size); - - if (ip_meta.wince == '1') - { - if (descrambl) - { - WARN_LOG(REIOS, "Unsupported CDI: wince == '1'"); - delete[] temp; - return false; - } - libGDR_ReadSector(GetMemPtr(0x8ce01000, 0), lba, 1, 2048); - lba++; - len -= 2048; - } - - INFO_LOG(REIOS, "file LBA: %d", lba); - INFO_LOG(REIOS, "file LEN: %d", len); - - bootSectors = (len + 2047) / 2048; - if (descrambl) - descrambl_file(lba, len, GetMemPtr(0x8c010000, 0)); - else - libGDR_ReadSector(GetMemPtr(0x8c010000, 0), lba, bootSectors, 2048); - - delete[] temp; - - u8 data[24] = {0}; - // system id - for (u32 j = 0; j < 8; j++) - data[j] = _vmem_ReadMem8(0x0021a056 + j); - - // system properties - for (u32 j = 0; j < 5; j++) - data[8 + j] = _vmem_ReadMem8(0x0021a000 + j); - - // system settings - flash_syscfg_block syscfg{}; - verify(static_cast(flashrom)->ReadBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg)); - memcpy(&data[16], &syscfg.time_lo, 8); - - memcpy(GetMemPtr(0x8c000068, sizeof(data)), data, sizeof(data)); - - return true; - } - i += dir->length; + if (descrambl) + { + std::vector buf(size); + bootFile->read(buf.data(), size, offset); + descrambl_buffer(buf.data(), GetMemPtr(0x8c010000, size), size); + } + else + { + bootFile->read(GetMemPtr(0x8c010000, size), size, offset); } - delete[] temp; - return false; + u8 data[24] = {0}; + // system id + for (u32 j = 0; j < 8; j++) + data[j] = _vmem_ReadMem8(0x0021a056 + j); + + // system properties + for (u32 j = 0; j < 5; j++) + data[8 + j] = _vmem_ReadMem8(0x0021a000 + j); + + // system settings + flash_syscfg_block syscfg{}; + verify(static_cast(flashrom)->ReadBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg)); + memcpy(&data[16], &syscfg.time_lo, 8); + + memcpy(GetMemPtr(0x8c000068, sizeof(data)), data, sizeof(data)); + + return true; } ip_meta_t ip_meta; diff --git a/core/rend/TexCache.cpp b/core/rend/TexCache.cpp index 4e4be6556..6654a397c 100644 --- a/core/rend/TexCache.cpp +++ b/core/rend/TexCache.cpp @@ -13,7 +13,7 @@ #include #endif -u8* vq_codebook; +const u8 *vq_codebook; u32 palette_index; bool KillTex=false; u32 palette16_ram[1024]; @@ -22,7 +22,6 @@ u32 pal_hash_256[4]; u32 pal_hash_16[64]; bool palette_updated; extern bool pal_needs_update; -float fb_scale_x, fb_scale_y; // Rough approximation of LoD bias from D adjust param, only used to increase LoD const std::array D_Adjust_LoD_Bias = { @@ -339,7 +338,7 @@ namespace directx { #undef TEX_CONV_TABLE static const PvrTexInfo *pvrTexInfo = opengl::pvrTexInfo; -static const u32 VQMipPoint[11] = +extern const u32 VQMipPoint[11] = { 0x00000,//1 0x00001,//2 @@ -353,7 +352,7 @@ static const u32 VQMipPoint[11] = 0x05556,//512 0x15556//1024 }; -static const u32 OtherMipPoint[11] = +extern const u32 OtherMipPoint[11] = { 0x00003,//1 0x00004,//2 diff --git a/core/rend/TexCache.h b/core/rend/TexCache.h index b7e1f54af..76c5a9de5 100644 --- a/core/rend/TexCache.h +++ b/core/rend/TexCache.h @@ -9,7 +9,7 @@ #include #include -extern u8* vq_codebook; +extern const u8 *vq_codebook; extern u32 palette_index; extern u32 palette16_ram[1024]; extern u32 palette32_ram[1024]; @@ -227,9 +227,9 @@ struct ConvertPlanar using unpacked_type = typename Unpacker::unpacked_type; static constexpr u32 xpp = 4; static constexpr u32 ypp = 1; - static void Convert(PixelBuffer *pb, u8 *data) + static void Convert(PixelBuffer *pb, const u8 *data) { - u16 *p_in = (u16 *)data; + const u16 *p_in = (const u16 *)data; pb->prel(0, Unpacker::unpack(p_in[0])); pb->prel(1, Unpacker::unpack(p_in[1])); pb->prel(2, Unpacker::unpack(p_in[2])); @@ -243,10 +243,10 @@ struct ConvertPlanarYUV using unpacked_type = u32; static constexpr u32 xpp = 4; static constexpr u32 ypp = 1; - static void Convert(PixelBuffer *pb, u8 *data) + static void Convert(PixelBuffer *pb, const u8 *data) { //convert 4x1 4444 to 4x1 8888 - u32 *p_in = (u32 *)data; + const u32 *p_in = (const u32 *)data; s32 Y0 = (p_in[0] >> 8) & 255; // @@ -280,9 +280,9 @@ struct ConvertTwiddle using unpacked_type = typename Unpacker::unpacked_type; static constexpr u32 xpp = 2; static constexpr u32 ypp = 2; - static void Convert(PixelBuffer *pb, u8 *data) + static void Convert(PixelBuffer *pb, const u8 *data) { - u16 *p_in = (u16 *)data; + const u16 *p_in = (const u16 *)data; pb->prel(0, 0, Unpacker::unpack(p_in[0])); pb->prel(0, 1, Unpacker::unpack(p_in[1])); pb->prel(1, 0, Unpacker::unpack(p_in[2])); @@ -296,10 +296,10 @@ struct ConvertTwiddleYUV using unpacked_type = u32; static constexpr u32 xpp = 2; static constexpr u32 ypp = 2; - static void Convert(PixelBuffer *pb, u8 *data) + static void Convert(PixelBuffer *pb, const u8 *data) { //convert 4x1 4444 to 4x1 8888 - u16* p_in = (u16 *)data; + const u16* p_in = (const u16 *)data; s32 Y0 = (p_in[0] >> 8) & 255; // s32 Yu = (p_in[0] >> 0) & 255; //p_in[0] @@ -342,9 +342,9 @@ struct ConvertTwiddlePal4 using unpacked_type = typename Unpacker::unpacked_type; static constexpr u32 xpp = 4; static constexpr u32 ypp = 4; - static void Convert(PixelBuffer *pb, u8 *data) + static void Convert(PixelBuffer *pb, const u8 *data) { - u8 *p_in = (u8 *)data; + const u8 *p_in = data; pb->prel(0, 0, Unpacker::unpack(p_in[0] & 0xF)); pb->prel(0, 1, Unpacker::unpack((p_in[0] >> 4) & 0xF)); p_in++; @@ -374,9 +374,9 @@ struct ConvertTwiddlePal8 using unpacked_type = typename Unpacker::unpacked_type; static constexpr u32 xpp = 2; static constexpr u32 ypp = 4; - static void Convert(PixelBuffer *pb, u8 *data) + static void Convert(PixelBuffer *pb, const u8 *data) { - u8* p_in = (u8 *)data; + const u8* p_in = (const u8 *)data; pb->prel(0, 0, Unpacker::unpack(p_in[0])); p_in++; pb->prel(0, 1, Unpacker::unpack(p_in[0])); p_in++; @@ -392,7 +392,7 @@ struct ConvertTwiddlePal8 //handler functions template -void texture_PL(PixelBuffer* pb,u8* p_in,u32 Width,u32 Height) +void texture_PL(PixelBuffer* pb, const u8* p_in, u32 Width, u32 Height) { pb->amove(0,0); @@ -403,7 +403,7 @@ void texture_PL(PixelBuffer* pb,u8* p_in { for (u32 x=0;x* pb,u8* p_in } template -void texture_TW(PixelBuffer* pb,u8* p_in,u32 Width,u32 Height) +void texture_TW(PixelBuffer* pb, const u8* p_in, u32 Width, u32 Height) { pb->amove(0, 0); @@ -427,7 +427,7 @@ void texture_TW(PixelBuffer* pb,u8* p_in { for (u32 x = 0; x < Width; x += PixelConvertor::xpp) { - u8* p = &p_in[(twop(x, y, bcx, bcy) / divider) << 3]; + const u8* p = &p_in[(twop(x, y, bcx, bcy) / divider) << 3]; PixelConvertor::Convert(pb, p); pb->rmovex(PixelConvertor::xpp); @@ -437,7 +437,7 @@ void texture_TW(PixelBuffer* pb,u8* p_in } template -void texture_VQ(PixelBuffer* pb,u8* p_in,u32 Width,u32 Height) +void texture_VQ(PixelBuffer* pb, const u8* p_in, u32 Width, u32 Height) { p_in += 256 * 4 * 2; // Skip VQ codebook pb->amove(0, 0); @@ -459,9 +459,9 @@ void texture_VQ(PixelBuffer* pb,u8* p_in } } -typedef void (*TexConvFP)(PixelBuffer *pb, u8 *p_in, u32 width, u32 height); -typedef void (*TexConvFP8)(PixelBuffer *pb, u8 *p_in, u32 width, u32 height); -typedef void (*TexConvFP32)(PixelBuffer *pb, u8 *p_in, u32 width, u32 height); +typedef void (*TexConvFP)(PixelBuffer *pb, const u8 *p_in, u32 width, u32 height); +typedef void (*TexConvFP8)(PixelBuffer *pb, const u8 *p_in, u32 width, u32 height); +typedef void (*TexConvFP32)(PixelBuffer *pb, const u8 *p_in, u32 width, u32 height); //Planar constexpr TexConvFP tex565_PL = texture_PL>>; @@ -815,10 +815,3 @@ void dump_screenshot(u8 *buffer, u32 width, u32 height, bool alpha = false, u32 extern const std::array D_Adjust_LoD_Bias; #undef clamp - -extern float fb_scale_x, fb_scale_y; -static inline void rend_set_fb_scale(float x, float y) -{ - fb_scale_x = x; - fb_scale_y = y; -} diff --git a/core/rend/boxart/boxart.cpp b/core/rend/boxart/boxart.cpp index 78d12f769..b2954c182 100644 --- a/core/rend/boxart/boxart.cpp +++ b/core/rend/boxart/boxart.cpp @@ -35,25 +35,33 @@ const GameBoxart *Boxart::getBoxart(const GameMedia& media) { loadDatabase(); std::string fileName = getGameFileName(media.path); - const GameBoxart *boxart = nullptr; + GameBoxart *boxart = nullptr; { std::lock_guard guard(mutex); auto it = games.find(fileName); if (it != games.end()) + { boxart = &it->second; - else if (config::FetchBoxart) + if (config::FetchBoxart && !boxart->busy && !boxart->scraped) + { + boxart->busy = true; + boxart->gamePath = media.path; + toFetch.push_back(*boxart); + } + } + else { GameBoxart box; box.fileName = fileName; box.gamePath = media.path; box.name = media.name; box.searchName = media.gameName; // for arcade games + box.busy = true; games[box.fileName] = box; toFetch.push_back(box); } } - if (config::FetchBoxart) - fetchBoxart(); + fetchBoxart(); return boxart; } @@ -66,12 +74,17 @@ void Boxart::fetchBoxart() if (toFetch.empty()) return; fetching = std::async(std::launch::async, [this]() { - if (scraper == nullptr) + if (offlineScraper == nullptr) + { + offlineScraper = std::unique_ptr(new OfflineScraper()); + offlineScraper->initialize(getSaveDirectory()); + } + if (config::FetchBoxart && scraper == nullptr) { scraper = std::unique_ptr(new TheGamesDb()); if (!scraper->initialize(getSaveDirectory())) { - WARN_LOG(COMMON, "thegamesdb scraper initialization failed"); + ERROR_LOG(COMMON, "thegamesdb scraper initialization failed"); scraper.reset(); return; } @@ -84,22 +97,49 @@ void Boxart::fetchBoxart() toFetch.erase(toFetch.begin(), toFetch.begin() + size); } DEBUG_LOG(COMMON, "Scraping %d games", (int)boxart.size()); - try { - scraper->scrape(boxart); - { - std::lock_guard guard(mutex); - for (const GameBoxart& b : boxart) - if (b.scraped) + offlineScraper->scrape(boxart); + { + std::lock_guard guard(mutex); + for (GameBoxart& b : boxart) + if (b.scraped || b.parsed) + { + if (!config::FetchBoxart || b.scraped) + b.busy = false; + games[b.fileName] = b; + databaseDirty = true; + } + } + if (config::FetchBoxart) + { + try { + scraper->scrape(boxart); + { + std::lock_guard guard(mutex); + for (GameBoxart& b : boxart) + { + b.busy = false; games[b.fileName] = b; - } - databaseDirty = true; - } catch (const std::exception& e) { - if (*e.what() != '\0') - INFO_LOG(COMMON, "thegamesdb error: %s", e.what()); - { - // put back items into toFetch array - std::lock_guard guard(mutex); - toFetch.insert(toFetch.begin(), boxart.begin(), boxart.end()); + } + } + databaseDirty = true; + } catch (const std::exception& e) { + if (*e.what() != '\0') + INFO_LOG(COMMON, "thegamesdb error: %s", e.what()); + { + // put back failed items into toFetch array + std::lock_guard guard(mutex); + for (GameBoxart& b : boxart) + if (b.scraped) + { + b.busy = false; + games[b.fileName] = b; + databaseDirty = true; + } + else + { + toFetch.push_back(b); + } + } } } }); @@ -124,7 +164,7 @@ void Boxart::saveDatabase() { std::lock_guard guard(mutex); for (const auto& game : games) - if (game.second.scraped) + if (game.second.scraped || game.second.parsed) array.push_back(game.second.to_json()); } std::string serialized = array.dump(4); diff --git a/core/rend/boxart/boxart.h b/core/rend/boxart/boxart.h index 92caabb64..0f7d18247 100644 --- a/core/rend/boxart/boxart.h +++ b/core/rend/boxart/boxart.h @@ -42,6 +42,7 @@ private: std::unordered_map games; std::mutex mutex; std::unique_ptr scraper; + std::unique_ptr offlineScraper; bool databaseLoaded = false; bool databaseDirty = false; diff --git a/core/rend/boxart/gamesdb.cpp b/core/rend/boxart/gamesdb.cpp index d3ba0342a..ee818df60 100644 --- a/core/rend/boxart/gamesdb.cpp +++ b/core/rend/boxart/gamesdb.cpp @@ -18,10 +18,9 @@ */ #include "gamesdb.h" #include "http_client.h" -#include "reios/reios.h" -#include "imgread/common.h" #include "stdclass.h" #include "oslib/oslib.h" +#include "emulator.h" #include #define APIKEY "3fcc5e726a129924972be97abfd577ac5311f8f12398a9d9bcb5a377d4656fa8" @@ -215,13 +214,13 @@ void TheGamesDb::parseBoxart(GameBoxart& item, const json& j, int gameId) if (cached != boxartCache.end()) { copyFile(cached->second, filename); - item.boxartPath = filename; + item.setBoxartPath(filename); } else { if (downloadImage(url, filename)) { - item.boxartPath = filename; + item.setBoxartPath(filename); boxartCache[url] = filename; } } @@ -293,90 +292,8 @@ bool TheGamesDb::fetchGameInfo(GameBoxart& item, const std::string& url, const s return parseGameInfo(array, v["include"]["boxart"], item, diskId); } -void TheGamesDb::getUidAndSearchName(GameBoxart& media) -{ - int platform = getGamePlatform(media.gamePath.c_str()); - if (platform == DC_PLATFORM_DREAMCAST) - { - if (media.gamePath.empty()) - { - // Dreamcast BIOS - media.uniqueId.clear(); - media.searchName.clear(); - return; - } - Disc *disc; - try { - disc = OpenDisc(media.gamePath.c_str()); - } catch (const std::exception& e) { - WARN_LOG(COMMON, "Can't open disk %s: %s", media.gamePath.c_str(), e.what()); - // No need to retry if the disk is invalid/corrupted - media.scraped = true; - media.uniqueId.clear(); - media.searchName.clear(); - return; - } - - u32 base_fad; - if (disc->type == GdRom) { - base_fad = 45150; - } else { - u8 ses[6]; - disc->GetSessionInfo(ses, 0); - disc->GetSessionInfo(ses, ses[2]); - base_fad = (ses[3] << 16) | (ses[4] << 8) | (ses[5] << 0); - } - u8 sector[2048]; - disc->ReadSectors(base_fad, 1, sector, sizeof(sector)); - ip_meta_t diskId; - memcpy(&diskId, sector, sizeof(diskId)); - delete disc; - - media.uniqueId = trim_trailing_ws(std::string(diskId.product_number, sizeof(diskId.product_number))); - - media.searchName = trim_trailing_ws(std::string(diskId.software_name, sizeof(diskId.software_name))); - if (media.searchName.empty()) - media.searchName = media.name; - - if (diskId.area_symbols[0] != '\0') - { - media.region = 0; - if (diskId.area_symbols[0] == 'J') - media.region |= GameBoxart::JAPAN; - if (diskId.area_symbols[1] == 'U') - media.region |= GameBoxart::USA; - if (diskId.area_symbols[2] == 'E') - media.region |= GameBoxart::EUROPE; - } - else - media.region = GameBoxart::JAPAN | GameBoxart::USA | GameBoxart::EUROPE; - } - else - { - media.uniqueId.clear(); - // Use first one in case of alternate names (Virtua Tennis / Power Smash) - size_t spos = media.searchName.find('/'); - if (spos != std::string::npos) - media.searchName = trim_trailing_ws(media.searchName.substr(0, spos)); - // Delete trailing (...) and [...] - while (!media.searchName.empty()) - { - size_t pos{ std::string::npos }; - if (media.searchName.back() == ')') - pos = media.searchName.find_last_of('('); - else if (media.searchName.back() == ']') - pos = media.searchName.find_last_of('['); - if (pos == std::string::npos) - break; - media.searchName = trim_trailing_ws(media.searchName.substr(0, pos)); - } - } -} - void TheGamesDb::scrape(GameBoxart& item) { - item.found = false; - getUidAndSearchName(item); if (item.searchName.empty()) // invalid rom or disk return; @@ -387,7 +304,7 @@ void TheGamesDb::scrape(GameBoxart& item) std::string url = makeUrl("Games/ByGameUniqueID") + "&fields=overview,uids&include=boxart&filter%5Bplatform%5D=" + std::to_string(dreamcastPlatformId) + "&uid=" + http::urlEncode(item.uniqueId); if (fetchGameInfo(item, url, item.uniqueId)) - item.scraped = item.found = true; + item.scraped = true; } if (!item.scraped) fetchByName(item); @@ -407,7 +324,7 @@ void TheGamesDb::fetchByName(GameBoxart& item) url += std::to_string(arcadePlatformId); url += "&name=" + http::urlEncode(item.searchName); if (fetchGameInfo(item, url)) - item.scraped = item.found = true; + item.scraped = true; } void TheGamesDb::fetchByUids(std::vector& items) @@ -433,7 +350,7 @@ void TheGamesDb::fetchByUids(std::vector& items) for (GameBoxart& item : items) { if (!item.scraped && !item.uniqueId.empty() && parseGameInfo(array, boxartArray, item, item.uniqueId)) - item.scraped = item.found = true; + item.scraped = true; } } @@ -444,12 +361,6 @@ void TheGamesDb::scrape(std::vector& items) blackoutPeriod = 0.0; fetchPlatforms(); - for (GameBoxart& item : items) - { - if (!item.scraped) - item.found = false; - getUidAndSearchName(item); - } fetchByUids(items); for (GameBoxart& item : items) { @@ -461,9 +372,9 @@ void TheGamesDb::scrape(std::vector& items) { std::string localPath = makeUniqueFilename("dreamcast_logo_grey.png"); if (downloadImage("https://flyinghead.github.io/flycast-builds/dreamcast_logo_grey.png", localPath)) - item.boxartPath = localPath; + item.setBoxartPath(localPath); } + item.scraped = true; } - item.scraped = true; } } diff --git a/core/rend/boxart/gamesdb.h b/core/rend/boxart/gamesdb.h index 723151bc9..162e704cf 100644 --- a/core/rend/boxart/gamesdb.h +++ b/core/rend/boxart/gamesdb.h @@ -37,7 +37,6 @@ private: void copyFile(const std::string& from, const std::string& to); json httpGet(const std::string& url); void parseBoxart(GameBoxart& item, const json& j, int gameId); - void getUidAndSearchName(GameBoxart& media); void fetchByUids(std::vector& items); void fetchByName(GameBoxart& item); bool parseGameInfo(const json& gameArray, const json& boxartArray, GameBoxart& item, const std::string& diskId); diff --git a/core/rend/boxart/pvrparser.h b/core/rend/boxart/pvrparser.h new file mode 100644 index 000000000..5d97f5af2 --- /dev/null +++ b/core/rend/boxart/pvrparser.h @@ -0,0 +1,143 @@ +/* + Copyright 2022 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast 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 Flycast. If not, see . + */ +#pragma once +#include "types.h" +#include "rend/TexCache.h" + +extern const u32 VQMipPoint[11]; +extern const u32 OtherMipPoint[11]; + +enum PvrDataFormat { + PvrSquareTwiddled = 0x01, + PvrSquareTwiddledMipmaps = 0x02, + PvrVQ = 0x03, + PvrVQMipmaps = 0x04, + PvrPal4 = 0x05, + PvrPal4Mipmaps = 0x06, + PvrPal8 = 0x07, + PvrPal8Mipmaps = 0x08, + PvrRectangle = 0x09, + PvrStride = 0x0B, + PvrRectangleTwiddled = 0x0D, + PvrSmallVQ = 0x10, + PvrSmallVQMipmaps = 0x11, + PvrSquareTwiddledMipmapsAlt = 0x12, +}; + +static bool pvrParse(const u8 *data, u32 len, u32& width, u32& height, std::vector& out) +{ + if (len < 16) + return false; + const u8 *p = data; + if (!memcmp("GBIX", p, 4)) + { + p += 4; + u32 idxSize = *(u32 *)p; + p += 4 + idxSize; + } + if (memcmp("PVRT", p, 4)) + { + WARN_LOG(COMMON, "Invalid PVR file: header not found"); + return false; + } + p += 4; + u32 size = *(u32 *)p; + p += 4; + PixelFormat pixelFormat = (PixelFormat)*p++; + PvrDataFormat imgType = (PvrDataFormat)*p++; + p += 2; + width = *(u16 *)p; + p += 2; + height = *(u16 *)p; + p += 2; + + ::vq_codebook = p; + TexConvFP32 texConv; + switch (pixelFormat) + { + case Pixel1555: + if (imgType == PvrSquareTwiddled || imgType == PvrSquareTwiddledMipmaps + || imgType == PvrRectangleTwiddled || imgType == PvrSquareTwiddledMipmapsAlt) + texConv = opengl::tex1555_TW32; + else if (imgType == PvrVQ || imgType == PvrVQMipmaps) + texConv = opengl::tex1555_VQ32; + else if (imgType == PvrRectangle || imgType == PvrStride) + texConv = opengl::tex1555_PL32; + else + { + WARN_LOG(COMMON, "Unsupported 1555 image type: %d", imgType); + return false; + } + break; + + case Pixel565: + if (imgType == PvrSquareTwiddled || imgType == PvrSquareTwiddledMipmaps + || imgType == PvrRectangleTwiddled || imgType == PvrSquareTwiddledMipmapsAlt) + texConv = opengl::tex565_TW32; + else if (imgType == PvrVQ || imgType == PvrVQMipmaps) + texConv = opengl::tex565_VQ32; + else if (imgType == PvrRectangle || imgType == PvrStride) + texConv = opengl::tex565_PL32; + else + { + WARN_LOG(COMMON, "Unsupported 565 image type: %d", imgType); + return false; + } + break; + + case Pixel4444: + if (imgType == PvrSquareTwiddled || imgType == PvrSquareTwiddledMipmaps + || imgType == PvrRectangleTwiddled || imgType == PvrSquareTwiddledMipmapsAlt) + texConv = opengl::tex4444_TW32; + else if (imgType == PvrVQ || imgType == PvrVQMipmaps) + texConv = opengl::tex4444_VQ32; + else if (imgType == PvrRectangle || imgType == PvrStride) + texConv = opengl::tex4444_PL32; + else + { + WARN_LOG(COMMON, "Unsupported 4444 image type: %d", imgType); + return false; + } + break; + + default: + WARN_LOG(COMMON, "Unsupported PVR pixel type: %d", pixelFormat); + return false; + } + DEBUG_LOG(COMMON, "PVR file: size %d pixelFmt %d imgType %d w %d h %d\n", size, pixelFormat, imgType, width, height); + u32 texU = 3; + while (1u << texU < width) + texU++; + if (imgType == PvrSquareTwiddledMipmapsAlt) + // Hardcoding pixel size. Not correct for palette texs + p += OtherMipPoint[texU] * 2; + else if (imgType == PvrSquareTwiddledMipmaps) + // Hardcoding pixel size. Not correct for palette texs + p += (OtherMipPoint[texU] - 2) * 2; + else if (imgType == PvrVQMipmaps) + p += VQMipPoint[texU]; + + PixelBuffer pb; + pb.init(width, height); + texConv(&pb, p, width, height); + out.resize(width * height * 4); + memcpy(out.data(), pb.data(), out.size()); + + return true; +} diff --git a/core/rend/boxart/scraper.cpp b/core/rend/boxart/scraper.cpp index 92f67b401..d5dc704bd 100644 --- a/core/rend/boxart/scraper.cpp +++ b/core/rend/boxart/scraper.cpp @@ -19,6 +19,12 @@ #include "scraper.h" #include "http_client.h" #include "stdclass.h" +#include "emulator.h" +#include "imgread/common.h" +#include "imgread/isofs.h" +#include "reios/reios.h" +#include "pvrparser.h" +#include bool Scraper::downloadImage(const std::string& url, const std::string& localName) { @@ -56,3 +62,108 @@ std::string Scraper::makeUniqueFilename(const std::string& url) } while (file_exists(path)); return path; } + +void OfflineScraper::scrape(GameBoxart& item) +{ + if (item.parsed) + return; + item.parsed = true; + int platform = getGamePlatform(item.gamePath.c_str()); + if (platform == DC_PLATFORM_DREAMCAST) + { + if (item.gamePath.empty()) + { + // Dreamcast BIOS + item.uniqueId.clear(); + item.searchName.clear(); + return; + } + Disc *disc; + try { + disc = OpenDisc(item.gamePath.c_str()); + } catch (const std::exception& e) { + WARN_LOG(COMMON, "Can't open disk %s: %s", item.gamePath.c_str(), e.what()); + // No need to retry if the disk is invalid/corrupted + item.scraped = true; + item.uniqueId.clear(); + item.searchName.clear(); + return; + } + + u8 sector[2048]; + disc->ReadSectors(disc->GetBaseFAD(), 1, sector, sizeof(sector)); + ip_meta_t diskId; + memcpy(&diskId, sector, sizeof(diskId)); + + if (item.boxartPath.empty()) + { + IsoFs isofs(disc); + std::unique_ptr root(isofs.getRoot()); + if (root != nullptr) + { + std::unique_ptr entry(root->getEntry("0GDTEX.PVR")); + if (entry != nullptr && !entry->isDirectory()) + { + IsoFs::File *gdtexFile = (IsoFs::File *)entry.get(); + std::vector data(gdtexFile->getSize()); + gdtexFile->read(data.data(), data.size()); + + std::vector out; + u32 w, h; + if (pvrParse(data.data(), data.size(), w, h, out)) + { + stbi_flip_vertically_on_write(0); + item.setBoxartPath(makeUniqueFilename("gdtex.png")); + stbi_write_png(item.boxartPath.c_str(), w, h, 4, out.data(), 0); + } + } + } + } + delete disc; + + item.uniqueId = trim_trailing_ws(std::string(diskId.product_number, sizeof(diskId.product_number))); + + item.searchName = trim_trailing_ws(std::string(diskId.software_name, sizeof(diskId.software_name))); + if (item.searchName.empty()) + item.searchName = item.name; + else + { + for (char& c : item.searchName) + if (!std::isprint(c)) + c = ' '; + } + + if (diskId.area_symbols[0] != '\0') + { + item.region = 0; + if (diskId.area_symbols[0] == 'J') + item.region |= GameBoxart::JAPAN; + if (diskId.area_symbols[1] == 'U') + item.region |= GameBoxart::USA; + if (diskId.area_symbols[2] == 'E') + item.region |= GameBoxart::EUROPE; + } + else + item.region = GameBoxart::JAPAN | GameBoxart::USA | GameBoxart::EUROPE; + } + else + { + item.uniqueId.clear(); + // Use first one in case of alternate names (Virtua Tennis / Power Smash) + size_t spos = item.searchName.find('/'); + if (spos != std::string::npos) + item.searchName = trim_trailing_ws(item.searchName.substr(0, spos)); + // Delete trailing (...) and [...] + while (!item.searchName.empty()) + { + size_t pos{ std::string::npos }; + if (item.searchName.back() == ')') + pos = item.searchName.find_last_of('('); + else if (item.searchName.back() == ']') + pos = item.searchName.find_last_of('['); + if (pos == std::string::npos) + break; + item.searchName = trim_trailing_ws(item.searchName.substr(0, pos)); + } + } +} diff --git a/core/rend/boxart/scraper.h b/core/rend/boxart/scraper.h index 7946843ba..9c5201ca7 100644 --- a/core/rend/boxart/scraper.h +++ b/core/rend/boxart/scraper.h @@ -37,12 +37,11 @@ struct GameBoxart std::string overview; std::string gamePath; - std::string screenshotPath; - std::string fanartPath; std::string boxartPath; + bool parsed = false; bool scraped = false; - bool found = false; + bool busy = false; enum Region { JAPAN = 1, USA = 2, EUROPE = 4 }; @@ -52,14 +51,13 @@ struct GameBoxart { "file_name", fileName }, { "name", name }, { "unique_id", uniqueId }, + { "search_name", searchName }, { "region", region }, { "release_date", releaseDate }, { "overview", overview }, - { "screenshot_path", screenshotPath }, - { "fanart_path", fanartPath }, { "boxart_path", boxartPath }, + { "parsed", parsed }, { "scraped", scraped }, - { "found", found }, }; return j; } @@ -85,14 +83,19 @@ struct GameBoxart loadProperty(fileName, j, "file_name"); loadProperty(name, j, "name"); loadProperty(uniqueId, j, "unique_id"); + loadProperty(searchName, j, "search_name"); loadProperty(region, j, "region"); loadProperty(releaseDate, j, "release_date"); loadProperty(overview, j, "overview"); - loadProperty(screenshotPath, j, "screenshot_path"); - loadProperty(fanartPath, j, "fanart_path"); loadProperty(boxartPath, j, "boxart_path"); + loadProperty(parsed, j, "parsed"); loadProperty(scraped, j, "scraped"); - loadProperty(found, j, "found"); + } + + void setBoxartPath(const std::string& path) { + if (!boxartPath.empty()) + nowide::remove(boxartPath.c_str()); + boxartPath = path; } }; @@ -120,3 +123,9 @@ protected: private: std::string saveDirectory; }; + +class OfflineScraper : public Scraper +{ +public: + void scrape(GameBoxart& item) override; +};