boxart: extract 0GDTEX.PVR. Offline scraping
extract iso9660 file system utility out of reios .pvr limited parser
This commit is contained in:
parent
807b8dea91
commit
ad8b41adb0
|
@ -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
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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;
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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<u8> 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;
|
||||
};
|
|
@ -7,25 +7,24 @@
|
|||
*/
|
||||
|
||||
#include "descrambl.h"
|
||||
#include "imgread/common.h"
|
||||
#include <algorithm>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <map>
|
||||
|
@ -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<IsoFs::Directory> 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<IsoFs::Entry> 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<DCFlashChip*>(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<u8> 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<DCFlashChip*>(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;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include <omp.h>
|
||||
#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<f32, 16> 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
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
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<unpacked_type> *pb, u8 *data)
|
||||
static void Convert(PixelBuffer<unpacked_type> *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<u32> *pb, u8 *data)
|
||||
static void Convert(PixelBuffer<u32> *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<unpacked_type> *pb, u8 *data)
|
||||
static void Convert(PixelBuffer<unpacked_type> *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<u32> *pb, u8 *data)
|
||||
static void Convert(PixelBuffer<u32> *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<unpacked_type> *pb, u8 *data)
|
||||
static void Convert(PixelBuffer<unpacked_type> *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<unpacked_type> *pb, u8 *data)
|
||||
static void Convert(PixelBuffer<unpacked_type> *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<class PixelConvertor>
|
||||
void texture_PL(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in,u32 Width,u32 Height)
|
||||
void texture_PL(PixelBuffer<typename PixelConvertor::unpacked_type>* pb, const u8* p_in, u32 Width, u32 Height)
|
||||
{
|
||||
pb->amove(0,0);
|
||||
|
||||
|
@ -403,7 +403,7 @@ void texture_PL(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in
|
|||
{
|
||||
for (u32 x=0;x<Width;x++)
|
||||
{
|
||||
u8* p = p_in;
|
||||
const u8* p = p_in;
|
||||
PixelConvertor::Convert(pb,p);
|
||||
p_in+=8;
|
||||
|
||||
|
@ -414,7 +414,7 @@ void texture_PL(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in
|
|||
}
|
||||
|
||||
template<class PixelConvertor>
|
||||
void texture_TW(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in,u32 Width,u32 Height)
|
||||
void texture_TW(PixelBuffer<typename PixelConvertor::unpacked_type>* pb, const u8* p_in, u32 Width, u32 Height)
|
||||
{
|
||||
pb->amove(0, 0);
|
||||
|
||||
|
@ -427,7 +427,7 @@ void texture_TW(PixelBuffer<typename PixelConvertor::unpacked_type>* 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<typename PixelConvertor::unpacked_type>* pb,u8* p_in
|
|||
}
|
||||
|
||||
template<class PixelConvertor>
|
||||
void texture_VQ(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in,u32 Width,u32 Height)
|
||||
void texture_VQ(PixelBuffer<typename PixelConvertor::unpacked_type>* 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<typename PixelConvertor::unpacked_type>* pb,u8* p_in
|
|||
}
|
||||
}
|
||||
|
||||
typedef void (*TexConvFP)(PixelBuffer<u16> *pb, u8 *p_in, u32 width, u32 height);
|
||||
typedef void (*TexConvFP8)(PixelBuffer<u8> *pb, u8 *p_in, u32 width, u32 height);
|
||||
typedef void (*TexConvFP32)(PixelBuffer<u32> *pb, u8 *p_in, u32 width, u32 height);
|
||||
typedef void (*TexConvFP)(PixelBuffer<u16> *pb, const u8 *p_in, u32 width, u32 height);
|
||||
typedef void (*TexConvFP8)(PixelBuffer<u8> *pb, const u8 *p_in, u32 width, u32 height);
|
||||
typedef void (*TexConvFP32)(PixelBuffer<u32> *pb, const u8 *p_in, u32 width, u32 height);
|
||||
|
||||
//Planar
|
||||
constexpr TexConvFP tex565_PL = texture_PL<ConvertPlanar<UnpackerNop<u16>>>;
|
||||
|
@ -815,10 +815,3 @@ void dump_screenshot(u8 *buffer, u32 width, u32 height, bool alpha = false, u32
|
|||
|
||||
extern const std::array<f32, 16> 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;
|
||||
}
|
||||
|
|
|
@ -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<std::mutex> 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<Scraper>(new OfflineScraper());
|
||||
offlineScraper->initialize(getSaveDirectory());
|
||||
}
|
||||
if (config::FetchBoxart && scraper == nullptr)
|
||||
{
|
||||
scraper = std::unique_ptr<Scraper>(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<std::mutex> guard(mutex);
|
||||
for (const GameBoxart& b : boxart)
|
||||
if (b.scraped)
|
||||
offlineScraper->scrape(boxart);
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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);
|
||||
|
|
|
@ -42,6 +42,7 @@ private:
|
|||
std::unordered_map<std::string, GameBoxart> games;
|
||||
std::mutex mutex;
|
||||
std::unique_ptr<Scraper> scraper;
|
||||
std::unique_ptr<Scraper> offlineScraper;
|
||||
bool databaseLoaded = false;
|
||||
bool databaseDirty = false;
|
||||
|
||||
|
|
|
@ -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 <cctype>
|
||||
|
||||
#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<GameBoxart>& items)
|
||||
|
@ -433,7 +350,7 @@ void TheGamesDb::fetchByUids(std::vector<GameBoxart>& 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<GameBoxart>& 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<GameBoxart>& 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<GameBoxart>& items);
|
||||
void fetchByName(GameBoxart& item);
|
||||
bool parseGameInfo(const json& gameArray, const json& boxartArray, GameBoxart& item, const std::string& diskId);
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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<u8>& 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<u32> pb;
|
||||
pb.init(width, height);
|
||||
texConv(&pb, p, width, height);
|
||||
out.resize(width * height * 4);
|
||||
memcpy(out.data(), pb.data(), out.size());
|
||||
|
||||
return true;
|
||||
}
|
|
@ -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 <stb_image_write.h>
|
||||
|
||||
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<IsoFs::Directory> root(isofs.getRoot());
|
||||
if (root != nullptr)
|
||||
{
|
||||
std::unique_ptr<IsoFs::Entry> entry(root->getEntry("0GDTEX.PVR"));
|
||||
if (entry != nullptr && !entry->isDirectory())
|
||||
{
|
||||
IsoFs::File *gdtexFile = (IsoFs::File *)entry.get();
|
||||
std::vector<u8> data(gdtexFile->getSize());
|
||||
gdtexFile->read(data.data(), data.size());
|
||||
|
||||
std::vector<u8> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue