diff --git a/database_info.c b/database_info.c index 4f14811b0a..1ed6cb9a85 100644 --- a/database_info.c +++ b/database_info.c @@ -219,20 +219,6 @@ char *bin_to_hex_alloc(const uint8_t *data, size_t len) return ret; } -/* Field selection flags for database_cursor_iterate_filtered. - * 0 = extract all fields (backward compatible). */ -#define DB_EXTRACT_NAME (1 << 0) -#define DB_EXTRACT_CRC (1 << 1) -#define DB_EXTRACT_SERIAL (1 << 2) -#define DB_EXTRACT_SIZE (1 << 3) -#define DB_EXTRACT_MD5 (1 << 4) -#define DB_EXTRACT_SHA1 (1 << 5) -#define DB_EXTRACT_ROM_NAME (1 << 6) - -/* Combination used by the scanner */ -#define DB_EXTRACT_SCAN_FIELDS \ - (DB_EXTRACT_NAME | DB_EXTRACT_CRC | DB_EXTRACT_SERIAL | DB_EXTRACT_SIZE) - static int database_cursor_iterate(libretrodb_cursor_t *cur, database_info_t *db_info) { @@ -683,6 +669,16 @@ static int database_info_fill_from_dom(struct rmsgpack_dom_value *item, db_info->serial = strdup(vs); } } + else if ( (fields & DB_EXTRACT_REGION) + && memcmp(str, "region", 6) == 0) + { + if (val->type == RDT_STRING || val->type == RDT_BINARY) + { + const char *vs = val->val.string.buff; + if (vs && *vs) + db_info->region = strdup(vs); + } + } break; case 8: diff --git a/database_info.h b/database_info.h index 9a1524f377..6a7c2d82a5 100644 --- a/database_info.h +++ b/database_info.h @@ -147,6 +147,7 @@ typedef struct #define DB_EXTRACT_MD5 (1 << 4) #define DB_EXTRACT_SHA1 (1 << 5) #define DB_EXTRACT_ROM_NAME (1 << 6) +#define DB_EXTRACT_REGION (1 << 7) /* Preset used by the ROM scanner */ #define DB_EXTRACT_SCAN_FIELDS \ diff --git a/libretro-common/formats/xdvdfs/xdvdfs.c b/libretro-common/formats/xdvdfs/xdvdfs.c index 4b555d3e11..00f985a3b1 100644 --- a/libretro-common/formats/xdvdfs/xdvdfs.c +++ b/libretro-common/formats/xdvdfs/xdvdfs.c @@ -34,7 +34,9 @@ * * A title is read from the head of an XBE (little endian), the * optional headers of an XEX or the header of an STFS package (big - * endian), each only as far as the title needs. */ + * endian), each only as far as the title needs: the ID, the place in + * a set and the regions - an XBE's certificate holds them, an XEX's + * execution info and security info. */ #include #include @@ -57,6 +59,8 @@ #define XDVDFS_XEX_INFO_KEY 0x00040006u #define XDVDFS_XEX_INFO 24 #define XDVDFS_STFS_FIELDS 0x360 /* title, platform, type, disc, discs */ +#define XDVDFS_XBE_REGION 0xA0 /* in the certificate */ +#define XDVDFS_XEX_REGION 0x178 /* in the security info */ /* Where whole disc images keep the game partition, after a trimmed * XISO's start: XGD1, XGD3, XGD2, and two seen in development images */ @@ -78,6 +82,8 @@ static const uint32_t xdvdfs_parts[] = { #define XDVDFS_TITLE_XEX_TABLE 4 #define XDVDFS_TITLE_XEX_INFO 5 #define XDVDFS_TITLE_STFS 6 +#define XDVDFS_TITLE_XBE_REGION 7 +#define XDVDFS_TITLE_XEX_REGION 8 struct xdvdfs { @@ -86,6 +92,7 @@ struct xdvdfs uint64_t dir; /* the directory searched, in the image */ uint64_t root; xdvdfs_extent_t file; /* what the lookup found, or the executable */ + xdvdfs_title_t title; /* the title read so far */ const char *path; /* the lookup's next component */ size_t got; /* octets of the range in hand */ size_t cap; /* octets of it asked for */ @@ -97,6 +104,7 @@ struct xdvdfs uint32_t xex_head; /* the XEX's header size */ uint32_t xex_count; /* its optional headers */ uint32_t xex_index; /* the next of them to look at */ + uint32_t xex_sec; /* its security info */ int op; int stage; /* the title read's, or the partition probed */ uint8_t buf[XDVDFS_BUF]; /* an entry, or a run of an executable */ @@ -129,6 +137,8 @@ xdvdfs_t *xdvdfs_new(uint64_t image_size) x->xex_head = 0; x->xex_count = 0; x->xex_index = 0; + x->xex_sec = 0; + memset(&x->title, 0, sizeof(x->title)); x->disc = false; x->xbe = false; x->op = XDVDFS_OP_OPEN; @@ -450,6 +460,7 @@ static int xdvdfs_title_run(xdvdfs_t *x, xdvdfs_request_t *req, else if (!memcmp(x->buf, "XEX2", 4) || !memcmp(x->buf, "XEX1", 4)) { x->xex_head = xdvdfs_be32(x->buf + 8); + x->xex_sec = xdvdfs_be32(x->buf + 16); x->xex_count = xdvdfs_be32(x->buf + 20); x->xex_index = 0; if ( x->xex_head < XDVDFS_EXE_HEAD @@ -473,17 +484,33 @@ static int xdvdfs_title_run(xdvdfs_t *x, xdvdfs_request_t *req, i = xdvdfs_le32(x->buf + 0x14); /* certificate */ if (i < v || i - v > x->file.size || x->file.size - (i - v) < 12) return XDVDFS_ERROR_DATA; - x->exe_at = i - v + 8; + x->exe_at = i - v; x->stage = XDVDFS_TITLE_XBE_ID; /* fall through */ case XDVDFS_TITLE_XBE_ID: if (x->got < 4) - return xdvdfs_exe_ask(x, req, x->exe_at, 4); - x->got = 0; - title->id = xdvdfs_le32(x->buf); - title->platform = XDVDFS_XBOX; - title->disc = 0; - title->discs = 0; + return xdvdfs_exe_ask(x, req, x->exe_at + 8, 4); + x->got = 0; + x->title.id = xdvdfs_le32(x->buf); + x->title.platform = XDVDFS_XBOX; + x->title.disc = 0; + x->title.discs = 0; + x->title.regions = 0; + x->stage = XDVDFS_TITLE_XBE_REGION; + /* fall through */ + case XDVDFS_TITLE_XBE_REGION: + /* a certificate too short to hold one says no region */ + if (x->file.size - x->exe_at >= XDVDFS_XBE_REGION + 4) + { + if (x->got < 4) + return xdvdfs_exe_ask(x, req, + x->exe_at + XDVDFS_XBE_REGION, 4); + x->got = 0; + x->title.regions = (uint8_t)(x->buf[0] + & (XDVDFS_REGION_NA | XDVDFS_REGION_JAPAN + | XDVDFS_REGION_REST)); + } + *title = x->title; return XDVDFS_OK; case XDVDFS_TITLE_XEX_TABLE: /* the optional headers, as many at a time as the buffer @@ -514,20 +541,42 @@ static int xdvdfs_title_run(xdvdfs_t *x, xdvdfs_request_t *req, case XDVDFS_TITLE_XEX_INFO: if (x->got < XDVDFS_XEX_INFO) return xdvdfs_exe_ask(x, req, x->exe_at, XDVDFS_XEX_INFO); - x->got = 0; - title->id = xdvdfs_be32(x->buf + 12); - title->platform = XDVDFS_XBOX360; - title->disc = x->buf[18]; - title->discs = x->buf[19]; + x->got = 0; + x->title.id = xdvdfs_be32(x->buf + 12); + x->title.platform = XDVDFS_XBOX360; + x->title.disc = x->buf[18]; + x->title.discs = x->buf[19]; + x->title.regions = 0; + x->stage = XDVDFS_TITLE_XEX_REGION; + /* fall through */ + case XDVDFS_TITLE_XEX_REGION: + /* security info the header does not hold says no region */ + if ( x->xex_sec <= x->xex_head + && x->xex_head - x->xex_sec >= XDVDFS_XEX_REGION + 4) + { + if (x->got < 4) + return xdvdfs_exe_ask(x, req, + x->xex_sec + XDVDFS_XEX_REGION, 4); + x->got = 0; + /* NTSC-U, NTSC-J and PAL, a byte each, low to high */ + v = xdvdfs_be32(x->buf); + x->title.regions = (uint8_t)( + ((v & 0x000000FFu) ? XDVDFS_REGION_NA : 0) + | ((v & 0x0000FF00u) ? XDVDFS_REGION_JAPAN : 0) + | ((v & 0x00FF0000u) ? XDVDFS_REGION_REST : 0)); + } + *title = x->title; return XDVDFS_OK; case XDVDFS_TITLE_STFS: if (x->got < 8) return xdvdfs_exe_ask(x, req, XDVDFS_STFS_FIELDS, 8); - x->got = 0; - title->id = xdvdfs_be32(x->buf); - title->platform = XDVDFS_XBOX360; - title->disc = x->buf[6]; - title->discs = x->buf[7]; + x->got = 0; + x->title.id = xdvdfs_be32(x->buf); + x->title.platform = XDVDFS_XBOX360; + x->title.disc = x->buf[6]; + x->title.discs = x->buf[7]; + x->title.regions = 0; + *title = x->title; return XDVDFS_OK; default: return XDVDFS_ERROR_STATE; diff --git a/libretro-common/include/formats/xdvdfs.h b/libretro-common/include/formats/xdvdfs.h index 47ad7e6b4a..97abae4862 100644 --- a/libretro-common/include/formats/xdvdfs.h +++ b/libretro-common/include/formats/xdvdfs.h @@ -64,6 +64,11 @@ RETRO_BEGIN_DECLS #define XDVDFS_XBOX 1 #define XDVDFS_XBOX360 2 +/* the regions a title is for */ +#define XDVDFS_REGION_NA 1 /* North America */ +#define XDVDFS_REGION_JAPAN 2 /* Japan and Asia */ +#define XDVDFS_REGION_REST 4 /* the rest of the world */ + typedef struct xdvdfs xdvdfs_t; /** @@ -92,6 +97,7 @@ typedef struct xdvdfs_title uint8_t platform; /* XDVDFS_XBOX or XDVDFS_XBOX360 */ uint8_t disc; /* of the set, from 1; 0 when not said */ uint8_t discs; /* in the set; 0 when not said */ + uint8_t regions; /* XDVDFS_REGION_*; 0 when not said */ } xdvdfs_title_t; /* @image_size: the image's size, so a partition past its end is not diff --git a/libretro-common/samples/formats/xdvdfs/xdvdfs_test.c b/libretro-common/samples/formats/xdvdfs/xdvdfs_test.c index c625047366..61dc8ebd4f 100644 --- a/libretro-common/samples/formats/xdvdfs/xdvdfs_test.c +++ b/libretro-common/samples/formats/xdvdfs/xdvdfs_test.c @@ -144,27 +144,39 @@ static void entry(image_t *im, uint64_t dir, uint32_t off, uint16_t l, } /* An XEX at @at: @n optional headers, the execution info's last, - * with @title, disc @disc of @discs */ -static void put_xex(image_t *im, uint64_t at, uint32_t n, uint32_t title, - uint8_t disc, uint8_t discs) + * with @title, disc @disc of @discs, then the security info with the + * region word @region, or none when @region is 0 */ +static void put_xex_region(image_t *im, uint64_t at, uint32_t n, + uint32_t title, uint8_t disc, uint8_t discs, uint32_t region) { uint32_t i; - uint32_t head = 24 + n * 8 + 32; + uint32_t info = 24 + n * 8; + uint32_t sec = info + 24; + uint32_t head = region ? sec + 0x178 + 4 : sec + 8; uint8_t d[2]; put(im, at, "XEX2", 4); put_be32(im, at + 8, head); + put_be32(im, at + 16, sec); put_be32(im, at + 20, n); + if (region) + put_be32(im, at + sec + 0x178, region); for (i = 0; i + 1 < n; i++) { put_be32(im, at + 24 + i * 8, 0x00010000u + i); put_be32(im, at + 24 + i * 8 + 4, 0x11111111u); } put_be32(im, at + 24 + i * 8, 0x00040006u); - put_be32(im, at + 24 + i * 8 + 4, head - 24); - put_be32(im, at + head - 24 + 12, title); + put_be32(im, at + 24 + i * 8 + 4, info); + put_be32(im, at + info + 12, title); d[0] = disc; d[1] = discs; - put(im, at + head - 24 + 18, d, 2); + put(im, at + info + 18, d, 2); +} + +static void put_xex(image_t *im, uint64_t at, uint32_t n, uint32_t title, + uint8_t disc, uint8_t discs) +{ + put_xex_region(im, at, n, title, disc, discs, 0); } /* The disc: a game partition at @base holding @@ -197,13 +209,14 @@ static void make_disc_for(image_t *im, uint64_t base, uint64_t size, if (x360) { - put_xex(im, xbe, 3, title, 1, 2); + put_xex_region(im, xbe, 3, title, 1, 2, 0x00FF00FFu); return; } put(im, xbe, "XBEH", 4); put32(im, xbe + 0x104, 0x10000); put32(im, xbe + 0x118, 0x10000 + 0x178); put32(im, xbe + 0x178 + 8, title); + put32(im, xbe + 0x178 + 0xA0, 0x80000001u); } static void make_disc(image_t *im, uint64_t base, uint64_t size, @@ -281,7 +294,13 @@ static void disc_lane(uint64_t base, uint64_t size, const char *what) * default.xbe found at the root entry, then the XBE's head, its * base and certificate addresses, the ID */ sprintf(name, "%s: title reads only what it needs", what); - check(pass || im.asked == 25 + 25 + 25 + 24 + 24 + 4, name); + check(pass || im.asked == 25 + 25 + 25 + 24 + 24 + 4 + 4, name); + { + xdvdfs_title_t t; + sprintf(name, "%s: regions of default.xbe", what); + check( title_of(&im, x, NULL, &t) == XDVDFS_OK + && t.regions == XDVDFS_REGION_NA, name); + } sprintf(name, "%s: nested lookup", what); check( lookup(&im, x, "\\MEDIA/Intro.WMV", &ext) == XDVDFS_OK @@ -322,10 +341,12 @@ static void x360_lanes(void) im.asked = 0; check( title_of(&im, x, NULL, &t) == XDVDFS_OK && t.id == 0x41560817u && t.platform == XDVDFS_XBOX360 - && t.disc == 1 && t.discs == 2, "360 XGD2: title of default.xex"); + && t.disc == 1 && t.discs == 2 + && t.regions == (XDVDFS_REGION_NA | XDVDFS_REGION_REST), + "360 XGD2: title of default.xex"); /* default.xex at the root entry, the XEX's head, its three * optional headers, the execution info */ - check(pass || im.asked == 25 + 24 + 3 * 8 + 24, + check(pass || im.asked == 25 + 24 + 3 * 8 + 24 + 4, "360 XGD2: title reads only what it needs"); xdvdfs_free(x); image_free(&im); @@ -340,8 +361,8 @@ static void x360_lanes(void) ext.size = 0x10000; im.asked = 0; check( title_of(&im, x, &ext, &t) == XDVDFS_OK - && t.id == 0x4D5307E6u && t.platform == XDVDFS_XBOX360, - "XEX on its own, many optional headers"); + && t.id == 0x4D5307E6u && t.platform == XDVDFS_XBOX360 + && !t.regions, "XEX on its own, many optional headers"); check(im.asked == 24 + 40 * 8 + 24, "XEX on its own: headers once each"); xdvdfs_free(x); image_free(&im); @@ -377,6 +398,19 @@ static void x360_lanes(void) xdvdfs_free(x); image_free(&im); + /* a certificate too short to hold the regions says none, and the + * read stays inside the XBE */ + make_disc(&im, 0, 0x100000, 0x4D530004u); + entry(&im, 33 * SECTOR, 0, 7, 13, 50, 0x178 + 0x40, 0x20, "default.xbe"); + open_image(&im, &x); + im.asked = 0; + check( title_of(&im, x, NULL, &t) == XDVDFS_OK + && t.id == 0x4D530004u && !t.regions + && im.asked == 25 + 25 + 25 + 24 + 24 + 4, + "short certificate: no regions"); + xdvdfs_free(x); + image_free(&im); + /* an empty root: one sector of padding */ make_disc(&im, 0, 0x100000, 1); { diff --git a/samples/tasks/database/database_scan_test.c b/samples/tasks/database/database_scan_test.c index 222348c105..d7c238cdb5 100644 --- a/samples/tasks/database/database_scan_test.c +++ b/samples/tasks/database/database_scan_test.c @@ -1269,8 +1269,10 @@ typedef struct /* One database holding @n records; a NULL serial or rom_name, or * unsized, leaves the key out. */ -static int write_db_set(const char *path, const db_rec_t *recs, - unsigned n) +/* As write_db_set(), each record @i given the region @regions[i] when + * that is not NULL */ +static int write_db_set_regions(const char *path, const db_rec_t *recs, + const char *const *regions, unsigned n) { buf_t body, meta; FILE *f; @@ -1285,8 +1287,14 @@ static int write_db_set(const char *path, const db_rec_t *recs, for (r = 0; r < n; r++) { bfixmap(&body, 2 + (recs[r].serial ? 1 : 0) - + (recs[r].rom_name ? 1 : 0) + (recs[r].unsized ? 0 : 1)); + + (recs[r].rom_name ? 1 : 0) + (recs[r].unsized ? 0 : 1) + + (regions && regions[r] ? 1 : 0)); bfixstr(&body, "name"); bfixstr(&body, recs[r].name); + if (regions && regions[r]) + { + bfixstr(&body, "region"); + bfixstr(&body, regions[r]); + } if (recs[r].rom_name) { bfixstr(&body, "rom_name"); @@ -1334,6 +1342,12 @@ static int write_db_set(const char *path, const db_rec_t *recs, return 1; } +static int write_db_set(const char *path, const db_rec_t *recs, + unsigned n) +{ + return write_db_set_regions(path, recs, NULL, n); +} + /* One database holding a single named record. */ static int write_db(const char *path, const char *game, uint32_t crc, uint32_t size) @@ -2348,8 +2362,10 @@ static void xbox_xex(uint8_t *x, uint32_t title, uint8_t disc, uint8_t discs) { memcpy(x, "XEX2", 4); - xbox_put_be32(x + 8, 0x60); + xbox_put_be32(x + 8, 0x200); + xbox_put_be32(x + 16, 0x60); xbox_put_be32(x + 20, 1); + xbox_put_be32(x + 0x60 + 0x178, 0x000000FFu); xbox_put_be32(x + 24, 0x00040006u); xbox_put_be32(x + 28, 0x48); xbox_put_be32(x + 0x48 + 12, title); @@ -2398,6 +2414,7 @@ static int write_xbox_disc(const char *path, uint32_t base, uint32_t size, xbox_put32(x + 0x104, 0x10000); xbox_put32(x + 0x118, 0x10000 + 0x178); xbox_put32(x + 0x178 + 8, title); + xbox_put32(x + 0x178 + 0xA0, 1); } if (!(f = fopen(path, "wb"))) @@ -2433,10 +2450,15 @@ static void xbox_lane(const char *root, const char *pl_dir, { /* sizes no trimmed image comes near, as Redump's do */ static const db_rec_t xbox_recs[] = { + /* an extra of another region's release, ahead of the game */ + { "Halo Extras", "MS-004", 0x3B0E0004u, 0xF0000000u }, { "Halo The Game", "MS-004", 0x3B0E0001u, 0xF0000000u }, { "Trim The Game", "MS-012", 0x3B0E0002u, 0xF0000000u }, { "Whole The Game", "EA-013", 0x3B0E0003u, 0xF0000000u } }; + static const char *const xbox_regions[] = { + "Japan", "USA, Europe", NULL, NULL + }; char *xbox_db = (char*)malloc(512); char *xbox_in = (char*)malloc(512); char *p = (char*)malloc(1024); @@ -2454,7 +2476,8 @@ static void xbox_lane(const char *root, const char *pl_dir, path_mkdir(xbox_in); sprintf(p, "%s/Test Xbox.rdb", xbox_db); - if (!write_db_set(p, xbox_recs, ARRAY_SIZE(xbox_recs))) + if (!write_db_set_regions(p, xbox_recs, xbox_regions, + ARRAY_SIZE(xbox_recs))) { check(0, "fixture", "could not write database"); goto end; } sprintf(p, "%s/60_halo.xiso", xbox_in); @@ -2484,6 +2507,8 @@ static void xbox_lane(const char *root, const char *pl_dir, check( file_contains(p, "60_halo.xiso") && file_contains(p, "Halo The Game"), "XISO matches by its XBE title", "60_halo.xiso"); + check(!file_contains(p, "Halo Extras"), + "the XBE's regions pick the release", "not the extra"); check( file_contains(p, "61_trim.iso") && file_contains(p, "Trim The Game"), "trimmed .iso not ruled out by size", "61_trim.iso"); @@ -2576,11 +2601,16 @@ static void x360_lane(const char *root, const char *pl_dir, { static const db_rec_t x360_recs[] = { { "Set The Game (Disc 1)", "FO-2046-0", 0x3B360001u, 0xF0000000u }, + { "Set The Game (Disc 2) (Japan)", "FO-2046-1", 0x3B360007u, + 0xF0000000u }, { "Set The Game (Disc 2)", "FO-2046-1", 0x3B360002u, 0xF0000000u }, { "Exe The Game", "AV-2071", 0x3B360003u, 0xF0000000u }, { "Arcade The Game", "XA-2209", 0x3B360004u, 0xF0000000u }, { "Zar The Game", "SE-2142", 0x3B360006u, 0xF0000000u } }; + static const char *const x360_regions[] = { + NULL, "Japan", "USA", NULL, NULL, NULL + }; char *x_db = (char*)malloc(512); char *x_in = (char*)malloc(512); char *p = (char*)malloc(1024); @@ -2599,7 +2629,8 @@ static void x360_lane(const char *root, const char *pl_dir, path_mkdir(x_in); sprintf(p, "%s/Test Xbox 360.rdb", x_db); - if (!write_db_set(p, x360_recs, ARRAY_SIZE(x360_recs))) + if (!write_db_set_regions(p, x360_recs, x360_regions, + ARRAY_SIZE(x360_recs))) { check(0, "fixture", "could not write database"); goto end; } sprintf(p, "%s/Test Atari.rdb", x_db); if (!write_db(p, "Atari The Game", 0x3B360005u, 0x1000)) @@ -2644,6 +2675,8 @@ static void x360_lane(const char *root, const char *pl_dir, && file_contains(p, "Set The Game (Disc 2)") && !file_contains(p, "Set The Game (Disc 1)"), "360 disc matches its place in the set", "70_disc2.iso"); + check(!file_contains(p, "Set The Game (Disc 2) (Japan)"), + "the XEX's regions pick the release", "not the Japanese one"); check( file_contains(p, "71_game.xex") && file_contains(p, "Exe The Game"), "XEX matches by its title", "71_game.xex"); diff --git a/tasks/task_database.c b/tasks/task_database.c index c4efa0e235..a57ff2cf5f 100644 --- a/tasks/task_database.c +++ b/tasks/task_database.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -346,6 +347,9 @@ typedef struct database_state_handle uint32_t crc_acc; uint8_t crc_dest; /* enum db_crc_dest */ uint8_t crc_fail; /* enum db_crc_fail */ + /* The XDVDFS_REGION_* an Xbox title's executable says it is for, + * or 0: picks among the releases its serial is shared by. */ + uint8_t regions; bool crc_done; /* The index build in flight, run by task_database_index_step() * until index_ready, then taken by the lookup that began it. */ @@ -2126,6 +2130,7 @@ static int task_database_iterate_playlist( database_state_handle_t *db_state, database_info_handle_t *db, const char *name) { + db_state->regions = 0; switch (extension_to_file_type(path_get_extension(name))) { case FILE_TYPE_COMPRESSED: @@ -2186,17 +2191,17 @@ static int task_database_iterate_playlist( * so other discs read nothing more for it. Its size says * nothing: an XISO is trimmed. */ if ( !intfstream_file_get_serial(name, 0, INT64_MAX, db_state->serial, sizeof(db_state->serial),&db_state->size) - && task_database_xbox_get_serial(name, true, db_state->serial, sizeof(db_state->serial),&db_state->size)) + && task_database_xbox_get_serial(name, true, db_state->serial, sizeof(db_state->serial),&db_state->size,&db_state->regions)) db->type = DATABASE_TYPE_SERIAL_LOOKUP; break; case FILE_TYPE_XISO: db_state->serial[0] = '\0'; - task_database_xbox_get_serial(name, true, db_state->serial, sizeof(db_state->serial),&db_state->size); + task_database_xbox_get_serial(name, true, db_state->serial, sizeof(db_state->serial),&db_state->size,&db_state->regions); db->type = DATABASE_TYPE_SERIAL_LOOKUP; break; case FILE_TYPE_ZAR: db_state->serial[0] = '\0'; - task_database_zar_get_serial(name, db_state->serial, sizeof(db_state->serial),&db_state->size); + task_database_zar_get_serial(name, db_state->serial, sizeof(db_state->serial),&db_state->size,&db_state->regions); db->type = DATABASE_TYPE_SERIAL_LOOKUP; break; /* An Xbox 360 executable or package; anything else by these names @@ -2204,7 +2209,7 @@ static int task_database_iterate_playlist( case FILE_TYPE_XEX: case FILE_TYPE_XCP: db_state->serial[0] = '\0'; - if (task_database_xbox_get_serial(name, false, db_state->serial, sizeof(db_state->serial),&db_state->size)) + if (task_database_xbox_get_serial(name, false, db_state->serial, sizeof(db_state->serial),&db_state->size,&db_state->regions)) { db->type = DATABASE_TYPE_SERIAL_LOOKUP; break; @@ -2215,7 +2220,7 @@ static int task_database_iterate_playlist( TASK_DATABASE_CRC_FILE, DB_CRC_DEST_FILE, DB_CRC_FAIL_ERROR); case FILE_TYPE_CHD: db_state->serial[0] = '\0'; - if (task_database_chd_get_serial(name, db_state->serial, sizeof(db_state->serial),&db_state->size)) + if (task_database_chd_get_serial(name, db_state->serial, sizeof(db_state->serial),&db_state->size,&db_state->regions)) db->type = DATABASE_TYPE_SERIAL_LOOKUP; else { @@ -2366,8 +2371,10 @@ static int database_info_list_iterate_new( database_info_list_free(db_state->info); free(db_state->info); } + /* the records' regions only for a title that says its own */ db_state->info = database_info_list_new_filtered( - new_database, query, DB_EXTRACT_SCAN_FIELDS); + new_database, query, DB_EXTRACT_SCAN_FIELDS + | (db_state->regions ? DB_EXTRACT_REGION : 0)); return 0; } @@ -3573,6 +3580,44 @@ static bool task_database_check_serial_and_crc( string_starts_with(db_name, "Sega - Dreamcast")); } +/* The XDVDFS_REGION_* a Redump region list ("USA, Europe") names: + * the Xbox's three, North America, Japan with the rest of Asia, and + * the rest of the world */ +static uint8_t task_database_xbox_regions(const char *list) +{ + static const char *const na[] = { "USA", "Canada", "Brazil" }; + static const char *const japan[] = { + "Japan", "Asia", "Korea", "Taiwan", "China", "Hong Kong" }; + uint8_t regions = 0; + + while (list && *list) + { + const char *end; + size_t len, i; + uint8_t r = XDVDFS_REGION_REST; + + while (*list == ' ' || *list == ',') + list++; + for (end = list; *end && *end != ','; end++) { } + len = (size_t)(end - list); + while (len && list[len - 1] == ' ') + len--; + if (!len) + break; + if (len == 5 && !strncmp(list, "World", 5)) + r = XDVDFS_REGION_NA | XDVDFS_REGION_JAPAN | XDVDFS_REGION_REST; + for (i = 0; i < ARRAY_SIZE(na); i++) + if (strlen(na[i]) == len && !strncmp(list, na[i], len)) + r = XDVDFS_REGION_NA; + for (i = 0; i < ARRAY_SIZE(japan); i++) + if (strlen(japan[i]) == len && !strncmp(list, japan[i], len)) + r = XDVDFS_REGION_JAPAN; + regions |= r; + list = end; + } + return regions; +} + static int task_database_iterate_serial_lookup( manual_scan_handle_t *_db, database_state_handle_t *db_state, @@ -3710,7 +3755,8 @@ static int task_database_iterate_serial_lookup( if (db_state->serial_index[db_state->list_index]) hits = database_info_list_new_serial( db_state->serial_index[db_state->list_index], rdb, - db_state->serial, DB_EXTRACT_SCAN_FIELDS); + db_state->serial, DB_EXTRACT_SCAN_FIELDS + | (db_state->regions ? DB_EXTRACT_REGION : 0)); } if (hits) @@ -3785,6 +3831,27 @@ serial_query_done: return database_info_list_iterate_next(db_state); } + /* An Xbox serial is shared by a title's releases, and by the + * extras that came with them: the regions its executable says it is + * for pick the first that fits, and when none does the first of them + * stands in, as the loop below finds it. */ + if (db_state->info && db_state->regions && !db_state->entry_index) + { + size_t i; + for (i = 0; i < db_state->info->count; i++) + { + const database_info_t *rec = &db_state->info->list[i]; + if ( rec->serial + && string_is_equal(db_state->serial, rec->serial) + && (task_database_xbox_regions(rec->region) + & db_state->regions)) + { + db_state->entry_index = i; + return database_info_list_iterate_found_match(_db, + db_state, db, NULL); + } + } + } if (db_state->info) { /* A 3DS product code is shared by a title's revisions and diff --git a/tasks/task_database_cue.c b/tasks/task_database_cue.c index c4f7b2906e..13fa10b25a 100644 --- a/tasks/task_database_cue.c +++ b/tasks/task_database_cue.c @@ -2215,10 +2215,10 @@ bool is_chd_file_cdi(const char *path) } static size_t detect_xbox_game(intfstream_t *fd, uint64_t pos, bool disc, - char *s, size_t len); + char *s, size_t len, uint8_t *regions); int task_database_chd_get_serial(const char *name, char *serial, - size_t len, uint64_t *filesize) + size_t len, uint64_t *filesize, uint8_t *regions) { int result; /* CD-i discs store data in AUDIO-labeled tracks, so we must @@ -2238,7 +2238,8 @@ int task_database_chd_get_serial(const char *name, char *serial, /* An Xbox DVD is looked for only once nothing else has been found, * so other discs read nothing more for it */ if (!result) - result = detect_xbox_game(fd, ~(uint64_t)0, true, serial, len) != 0; + result = detect_xbox_game(fd, ~(uint64_t)0, true, serial, len, + regions) != 0; intfstream_close(fd); free(fd); return result; @@ -2375,10 +2376,10 @@ static size_t xbox_title_serial(const xdvdfs_title_t *t, char *s, } /* An Xbox or Xbox 360 serial from the title of the executable @fd - * holds, or with @disc of the one the disc image boots. @pos is where - * the stream stands. */ + * holds, or with @disc of the one the disc image boots, and the + * regions it is for. @pos is where the stream stands. */ static size_t detect_xbox_game(intfstream_t *fd, uint64_t pos, bool disc, - char *s, size_t len) + char *s, size_t len, uint8_t *regions) { task_database_src_t src; xdvdfs_request_t req; @@ -2405,15 +2406,15 @@ static size_t detect_xbox_game(intfstream_t *fd, uint64_t pos, bool disc, while ((err = xdvdfs_title_step(x, &req, &title)) == XDVDFS_PENDING) xdvdfs_feed(x, task_database_pull(&src, req.offset, req.dst, req.length)); - if (err == XDVDFS_OK) - _len = xbox_title_serial(&title, s, len); + if (err == XDVDFS_OK && (_len = xbox_title_serial(&title, s, len))) + *regions = title.regions; } xdvdfs_free(x); return _len; } int task_database_xbox_get_serial(const char *name, bool disc, char *s, - size_t len, uint64_t *filesize) + size_t len, uint64_t *filesize, uint8_t *regions) { size_t _len; /* The descriptor path: the few ranges the probe needs land where @@ -2425,7 +2426,7 @@ int task_database_xbox_get_serial(const char *name, bool disc, char *s, if (!fd) return 0; *filesize = (uint64_t)intfstream_get_size(fd); - _len = detect_xbox_game(fd, 0, disc, s, len); + _len = detect_xbox_game(fd, 0, disc, s, len, regions); intfstream_close(fd); free(fd); return _len != 0; @@ -2455,7 +2456,7 @@ static size_t task_database_zar_pull(task_database_zar_src_t *zs, } int task_database_zar_get_serial(const char *name, char *s, size_t len, - uint64_t *filesize) + uint64_t *filesize, uint8_t *regions) { task_database_zar_src_t zs; zar_request_t req; @@ -2501,8 +2502,8 @@ int task_database_zar_get_serial(const char *name, char *s, size_t len, while ((err = xdvdfs_title_step(x, &xreq, &title)) == XDVDFS_PENDING) xdvdfs_feed(x, task_database_zar_pull(&zs, xreq.offset, xreq.dst, xreq.length)); - if (err == XDVDFS_OK) - _len = xbox_title_serial(&title, s, len); + if (err == XDVDFS_OK && (_len = xbox_title_serial(&title, s, len))) + *regions = title.regions; end: xdvdfs_free(x); diff --git a/tasks/task_database_cue.h b/tasks/task_database_cue.h index 9a9538c52b..f1f1d7c20f 100644 --- a/tasks/task_database_cue.h +++ b/tasks/task_database_cue.h @@ -73,15 +73,17 @@ int task_database_3ds_get_serial(const char *name, char *s, size_t len, int task_database_ciso_get_serial(const char *name, char *s, size_t len, uint64_t *filesize); /* An Xbox or Xbox 360 serial for @name: with @disc that of the disc - * image's game, otherwise that of the executable or package it is */ + * image's game, otherwise that of the executable or package it is. + * @regions gets the XDVDFS_REGION_* the title says it is for, or 0. */ int task_database_xbox_get_serial(const char *name, bool disc, char *s, - size_t len, uint64_t *filesize); -/* The Xbox 360 serial of the game a ZArchive holds */ + size_t len, uint64_t *filesize, uint8_t *regions); +/* The Xbox 360 serial of the game a ZArchive holds, @regions as above */ int task_database_zar_get_serial(const char *name, char *s, size_t len, - uint64_t *filesize); + uint64_t *filesize, uint8_t *regions); bool is_chd_file_cdi(const char *path); +/* @regions as for task_database_xbox_get_serial(), for an Xbox disc */ int task_database_chd_get_serial(const char *name, char *serial, size_t len, - uint64_t *filesize); + uint64_t *filesize, uint8_t *regions); enum task_database_crc_kind {