Files
Mateusz Najdek 3c9891652f Release v1.2.2 (#7)
* fix(parser): honor PS4 base SFO category

* feat(cache): rescan after cache invalidation

* bump version to 1.2.2

* docs: document cache refresh and package fixes
2026-09-22 12:29:49 +02:00

160 lines
5.7 KiB
C

/*
* Host test: pkg_parser_parse_mem matches pkg_parser_parse (NEW additive
* function; the file parser itself is untouched). Guards against logic
* drift between the two paths that installer_start[_live] depend on.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include "pkg_parser.h"
#include "multipart.h"
#include "test_fixture.h"
#define PM_DIR "/tmp/test_parse_mem"
static uint8_t *read_file(const char *path, size_t *out_len) {
FILE *f = fopen(path, "rb");
assert(f);
assert(fseek(f, 0, SEEK_END) == 0);
long sz = ftell(f);
assert(sz > 0);
assert(fseek(f, 0, SEEK_SET) == 0);
uint8_t *b = malloc((size_t)sz);
assert(b);
assert(fread(b, 1, (size_t)sz, f) == (size_t)sz);
fclose(f);
*out_len = (size_t)sz;
return b;
}
static void expect_equal(const pkg_detail_t *a, const pkg_detail_t *b,
const char *what) {
assert(strcmp(a->title_id, b->title_id) == 0);
assert(strcmp(a->title_name, b->title_name) == 0);
assert(strcmp(a->content_id, b->content_id) == 0);
assert(strcmp(a->app_version, b->app_version) == 0);
assert(strcmp(a->category, b->category) == 0);
assert(a->pkg_type == b->pkg_type);
assert(strcmp(a->pkg_type_str, b->pkg_type_str) == 0);
assert(a->is_multipart == b->is_multipart);
printf(" match %s: tid='%s' name='%.32s' ver='%s' kind='%s'\n", what,
a->title_id, a->title_name, a->app_version, a->pkg_type_str);
}
static void test_ps5_mem(void) {
const char *p = PM_DIR "/ps5.pkg";
assert(fixture_write_ps5_pkg(p, "PPSA90012", "MemGame", "gd",
"01.000.000", 1) == 0);
pkg_detail_t file_d;
assert(pkg_parser_parse(p, &file_d) == 0);
size_t len = 0;
uint8_t *buf = read_file(p, &len);
pkg_detail_t mem_d;
assert(pkg_parser_parse_mem(buf, len, len, "MemGame.pkg", &mem_d, NULL) == 0);
expect_equal(&file_d, &mem_d, "ps5");
assert(mem_d.has_icon == 0); /* live skips icon extraction */
free(buf);
}
static void test_ps4_mem(void) {
const char *p = PM_DIR "/ps4.pkg";
assert(fixture_write_ps4_pkg(p, "CUSA90013", "MemFour", "gd", "01.00") == 0);
FILE *f = fopen(p, "r+b");
assert(f);
assert(fseek(f, 4, SEEK_SET) == 0);
const unsigned char cnt_type_one[] = {0, 0, 0, 1};
assert(fwrite(cnt_type_one, 1, sizeof(cnt_type_one), f) == sizeof(cnt_type_one));
fclose(f);
pkg_detail_t file_d;
assert(pkg_parser_parse(p, &file_d) == 0);
assert(file_d.pkg_type == PKG_TYPE_BASE);
size_t len = 0;
uint8_t *buf = read_file(p, &len);
pkg_detail_t mem_d;
assert(pkg_parser_parse_mem(buf, len, len, "MemFour.pkg", &mem_d, NULL) == 0);
expect_equal(&file_d, &mem_d, "ps4");
assert(mem_d.pkg_type == PKG_TYPE_BASE);
free(buf);
}
static void test_ps5_cnt_type_dlc(void) {
const char *p = PM_DIR "/ps5_dlc_variant.pkg";
assert(fixture_write_ps5_pkg(p, "TEST00015", "Synthetic DLC Package", "gd",
"01.000.000", 0) == 0);
FILE *f = fopen(p, "r+b");
assert(f);
assert(fseek(f, 0x10004, SEEK_SET) == 0);
const unsigned char dlc_type[] = {0x00, 0x02, 0x00, 0x01};
assert(fwrite(dlc_type, 1, sizeof(dlc_type), f) == sizeof(dlc_type));
fclose(f);
pkg_detail_t file_d;
assert(pkg_parser_parse(p, &file_d) == 0);
assert(file_d.pkg_type == PKG_TYPE_DLC);
size_t len = 0;
uint8_t *buf = read_file(p, &len);
pkg_detail_t mem_d;
assert(pkg_parser_parse_mem(buf, len, len, "MemDlc.pkg", &mem_d, NULL) == 0);
assert(mem_d.pkg_type == PKG_TYPE_DLC);
assert(strcmp(mem_d.pkg_type_str, "dlc") == 0);
free(buf);
}
static void test_truncated_fails(void) {
const char *p = PM_DIR "/ps5.pkg";
size_t len = 0;
uint8_t *buf = read_file(p, &len);
assert(len > 128);
pkg_detail_t mem_d;
/* 64 bytes: no CNT discoverable -> must fail closed, not misparse. */
int stage = 0;
assert(pkg_parser_parse_mem(buf, 64, len, "x.pkg", &mem_d, &stage) != 0);
assert(stage == 1 || stage == 2);
free(buf);
printf(" truncated-fails ok\n");
}
static void test_multipart_mem(void) {
multipart_header_t h;
memset(&h, 0, sizeof(h));
memcpy(h.magic, MULTIPART_MAGIC, MULTIPART_MAGIC_LEN);
h.header_version = 1;
h.part_index = 1;
h.total_parts = 2;
snprintf(h.pkg_filename, sizeof(h.pkg_filename), "Big.pkg");
snprintf(h.title_id, sizeof(h.title_id), "PPSA90014");
snprintf(h.title_name, sizeof(h.title_name), "BigGame");
snprintf(h.content_id, sizeof(h.content_id), "EP9000-PPSA90014_00-BIGGAME000001");
snprintf(h.app_version, sizeof(h.app_version), "01.000.000");
snprintf(h.pkg_type, sizeof(h.pkg_type), "base");
h.total_pkg_size = 50ULL * 1024 * 1024 * 1024;
pkg_detail_t mem_d;
assert(pkg_parser_parse_mem((const uint8_t *)&h, sizeof(h),
h.total_pkg_size, "live:abc", &mem_d, NULL) == 0);
assert(mem_d.is_multipart == 1 && mem_d.total_parts == 2);
assert(strcmp(mem_d.title_id, "PPSA90014") == 0);
assert(strcmp(mem_d.pkg_type_str, "base") == 0);
assert(strcmp(mem_d.filename, "live:abc.pkg") == 0);
printf(" multipart-mem ok\n");
}
int main(void) {
setvbuf(stdout, NULL, _IOLBF, 0);
printf("==============================================\n");
printf(">>> RUNNING PARSE_MEM DIFFERENTIAL TEST <<<\n");
printf("==============================================\n");
assert(system("rm -rf " PM_DIR " && mkdir -p " PM_DIR) == 0);
test_ps5_mem();
test_ps4_mem();
test_ps5_cnt_type_dlc();
test_truncated_fails();
test_multipart_mem();
printf("\n>>> ALL PARSE_MEM TESTS PASSED! <<<\n");
return 0;
}