diff --git a/src/patchdl_install.c b/src/patchdl_install.c index b6a7650..804f8af 100644 --- a/src/patchdl_install.c +++ b/src/patchdl_install.c @@ -64,6 +64,7 @@ typedef struct { ai_install_error_t error_info; int32_t local_copy_percent; bool is_copy_only; + char _pad[2048]; /* safety margin — actual Sony struct may be larger */ } ai_install_status_t; #define STATIC_ASSERT(c, n) typedef char static_assert_##n[(c) ? 1 : -1] @@ -87,7 +88,7 @@ typedef int (*ai_install_by_pkg_fn)(ai_meta_info_t *meta, ai_pkg_info_t *info, ai_playgo_info_t *playgo); typedef int (*ai_title_from_pkg_fn)(const char *path, char *title_id, int *is_app); typedef int (*ai_content_from_pkg_fn)(const char *path, char *content_id, int *is_app); -typedef int (*ai_get_status_fn)(const char *content_id, ai_install_status_t *status); +typedef int (*ai_get_status_fn)(char *content_id_out, ai_install_status_t *status); static ai_init_fn ai_initialize; static ai_install_pkg_fn ai_install_pkg; @@ -407,6 +408,32 @@ patchdl_install_pkg_meta(const char *local_path, char *content_id, size_t cid_sz return ok ? 0 : -1; } +void +patchdl_install_debug_state(char *out, size_t out_sz) { + char cid[AI_CONTENTID_SIZE]; + char tid[32], method[32]; + int start_rc; + int stage; + + pthread_mutex_lock(&g_mtx); + memcpy(cid, g_last_content_id, sizeof(cid)); + snprintf(tid, sizeof(tid), "%s", g_last_target_title_id); + snprintf(method, sizeof(method), "%s", g_last_method); + start_rc = g_last_start_rc; + stage = g_stage; + pthread_mutex_unlock(&g_mtx); + + snprintf(out, out_sz, + "{\"stage\":%d,\"cid_len\":%d,\"cid_hex\":\"%02x%02x%02x%02x\"," + "\"content_id\":\"%s\",\"target_title_id\":\"%s\"," + "\"method\":\"%s\",\"start_rc\":%d}", + stage, + (int)strnlen(cid, sizeof(cid)), + (unsigned char)cid[0], (unsigned char)cid[1], + (unsigned char)cid[2], (unsigned char)cid[3], + cid, tid, method, start_rc); +} + int patchdl_install_status_json(char *out, size_t out_sz) { char cid[AI_CONTENTID_SIZE]; @@ -452,8 +479,15 @@ patchdl_install_status_json(char *out, size_t out_sz) { return -1; } - memset(&st, 0, sizeof(st)); - rc = ai_get_status(cid, &st); + /* sceAppInstUtilGetInstallStatus(char *content_id_out, status_t *status): + first arg is an OUTPUT buffer that receives the current install's content_id. + Do NOT pass `cid` there — it would be overwritten. Use a fresh buffer. */ + { + char ai_cid_out[AI_CONTENTID_SIZE] = {0}; + memset(&st, 0, sizeof(st)); + rc = ai_get_status(ai_cid_out, &st); + (void)ai_cid_out; /* returned content_id for future use */ + } copy_bounded(status, sizeof(status), st.status, sizeof(st.status)); copy_bounded(src_type, sizeof(src_type), st.src_type, sizeof(st.src_type)); if (st.total_size > 0) @@ -538,18 +572,11 @@ patchdl_install_local_pkg(const char *local_path, const char *expected_title_id, pkg_tid, expected_title_id); return -1; } - if (pkg_tid_mismatch) { - snprintf(msg, msg_sz, - "unsupported shared-master package (pkg %.12s, target %.12s): " - "standalone AppInstUtil cannot retarget signed patch metadata", - pkg_tid, expected_title_id ? expected_title_id : ""); - return -1; - } - - /* Preferred path: etaHEN's DPI uses InstallByPackage with an empty - MetaInfo.content_id and lets AppInstUtil bind to the signed package - metadata. Passing the installed game's content id is NOT a retarget - override for shared-master regional bytes; those are refused above. */ + /* Preferred path: etaHEN's DPI uses InstallByPackage with the installed + game's content_id in MetaInfo so AppInstUtil binds the install to the + right title slot. For shared-master cross-region packages the pkg bytes + carry a different title id than the installed game; passing content_id + is what etaHEN does to route the install correctly. */ { char file_uri[1100]; char http_loop_uri[1200] = {0}; @@ -588,7 +615,11 @@ patchdl_install_local_pkg(const char *local_path, const char *expected_title_id, uris[3] = http_lan_uri[0] ? http_lan_uri : NULL; meta.ex_uri = ""; meta.playgo_scenario_id = ""; - meta.content_id = ""; + /* For cross-region shared-master packages pass the installed game's + content_id so AppInstUtil binds the download to the right title. */ + meta.content_id = (pkg_tid_mismatch && + target_content_id && target_content_id[0]) + ? target_content_id : ""; meta.content_name = "PatchDL"; meta.icon_url = ""; @@ -616,32 +647,122 @@ patchdl_install_local_pkg(const char *local_path, const char *expected_title_id, } } rc = rc2; - if (pkg_tid_mismatch) { - snprintf(msg, msg_sz, - "install rejected (pkg %.12s -> %.12s; tries: %s)", - pkg_tid, expected_title_id ? expected_title_id : "", tries); - return rc ? rc : -1; - } } } - /* Last resort for normal same-title packages only. This path has no target - metadata parameter, so it is intentionally skipped for shared-master - region bytes. */ + /* AppInstallPkg: simpler API, no MetaInfo content_id override. Tried for + all packages including cross-region, since it may have different + privilege requirements than InstallByPackage. For shared-master packages + it will bind to the pkg's own embedded title, not the expected_title_id, + so treat success with caution; also report the complete error set. */ { + char ibp_tries[260] = {0}; ai_pkg_info_t pkg = {0}; - int rc2 = ai_install_pkg(sdk_path, &pkg); + int rc2; + + /* stash the InstallByPackage diagnostic if available */ + if (pkg_tid_mismatch) + snprintf(ibp_tries, sizeof(ibp_tries), + "ibp=0x%08x(pkg %.12s->%.12s)", + (unsigned)rc, pkg_tid, + expected_title_id ? expected_title_id : ""); + + rc2 = ai_install_pkg(sdk_path, &pkg); if (rc2 == 0) { remember_install(expected_title_id, "AppInstallPkg", &pkg, target_content_id, rc2); - snprintf(msg, msg_sz, "install started (AppInstallPkg, content %.47s)", + snprintf(msg, msg_sz, "install started (AppInstallPkg, content %.47s%s%s)", pkg.content_id[0] ? pkg.content_id : - (target_content_id ? target_content_id : "")); + (target_content_id ? target_content_id : ""), + ibp_tries[0] ? " " : "", ibp_tries); return 0; } - snprintf(msg, msg_sz, - "install rejected (InstallByPackage=0x%08x, AppInstallPkg=0x%08x)", - (unsigned)rc, (unsigned)rc2); + if (pkg_tid_mismatch) + snprintf(msg, msg_sz, + "install rejected (pkg %.12s->%.12s ibp=0x%08x aip=0x%08x)", + pkg_tid, expected_title_id ? expected_title_id : "", + (unsigned)rc, (unsigned)rc2); + else + snprintf(msg, msg_sz, + "install rejected (InstallByPackage=0x%08x, AppInstallPkg=0x%08x)", + (unsigned)rc, (unsigned)rc2); return rc2 ? rc2 : (rc ? rc : -1); } } + +int +patchdl_install_by_uri(const char *uri, const char *target_title_id, + const char *target_content_id, + char *msg, size_t msg_sz) { + ai_meta_info_t meta = {0}; + ai_pkg_info_t pkg = {0}; + ai_playgo_info_t playgo = {0}; + int rc; + + if (!uri || !uri[0]) { + snprintf(msg, msg_sz, "no uri"); + return -1; + } + backend_start(); + if (g_stage != 5) { + snprintf(msg, msg_sz, "install backend not ready: %s", stage_str(g_stage)); + return -1; + } + + meta.uri = uri; + meta.ex_uri = ""; + meta.playgo_scenario_id = ""; + meta.content_id = target_content_id ? target_content_id : ""; + meta.content_name = "PatchDL"; + meta.icon_url = ""; + + rc = ai_install_by_package(&meta, &pkg, &playgo); + remember_install(target_title_id, "InstallByURI", &pkg, target_content_id, rc); + + if (rc == 0) { + snprintf(msg, msg_sz, "install started (InstallByPackage/uri, content %.47s)", + pkg.content_id[0] ? pkg.content_id : + (target_content_id ? target_content_id : "")); + } else { + snprintf(msg, msg_sz, "install rejected rc=0x%08x", (unsigned)rc); + } + return rc; +} + +/* Direct AppInstallPkg call for a local path — bypasses MetaInfo, lets + AppInstUtil read the PKG's own embedded metadata to determine the target. */ +int +patchdl_install_app_pkg(const char *local_path, + const char *expected_title_id, + const char *target_content_id, + char *msg, size_t msg_sz) { + char sdk_path[1024]; + ai_pkg_info_t pkg = {0}; + struct stat st; + int rc; + + if (!local_path || !local_path[0]) { snprintf(msg, msg_sz, "no path"); return -1; } + if (stat(local_path, &st) != 0) { snprintf(msg, msg_sz, "package not downloaded"); return -1; } + + backend_start(); + if (g_stage != 5) { + snprintf(msg, msg_sz, "install backend not ready: %s", stage_str(g_stage)); + return -1; + } + + if (!strncmp(local_path, "/data/", 6)) + snprintf(sdk_path, sizeof sdk_path, "/user%s", local_path); + else + snprintf(sdk_path, sizeof sdk_path, "%s", local_path); + + rc = ai_install_pkg(sdk_path, &pkg); + remember_install(expected_title_id, "AppInstallPkg/direct", &pkg, target_content_id, rc); + + if (rc == 0) + snprintf(msg, msg_sz, "install started (AppInstallPkg, content %.47s)", + pkg.content_id[0] ? pkg.content_id : + (target_content_id ? target_content_id : "")); + else + snprintf(msg, msg_sz, "install rejected rc=0x%08x", (unsigned)rc); + return rc; +} diff --git a/src/patchdl_install.h b/src/patchdl_install.h index 0ae095b..a91eacf 100644 --- a/src/patchdl_install.h +++ b/src/patchdl_install.h @@ -42,3 +42,25 @@ int patchdl_install_pkg_meta(const char *local_path, char *content_id, size_t ci /* Read-only: report the last AppInstUtil install task PatchDL started, using sceAppInstUtilGetInstallStatus when present. No install, no mutation. */ int patchdl_install_status_json(char *out, size_t out_sz); + +/* Raw dump of g_last_* tracking state (no AppInstUtil call). For diagnosis. */ +void patchdl_install_debug_state(char *out, size_t out_sz); + +/* Install a package from a remote URI (http:// or file://) directly, without + * requiring a local copy. Used for shared-master delta packages where the + * version.xml targets a different title id than the CDN storage path. + * `target_content_id` is the installed title's content_id (passed as + * MetaInfo.content_id so AppInstUtil binds the install to the right title). + */ +int patchdl_install_by_uri(const char *uri, + const char *target_title_id, + const char *target_content_id, + char *msg, size_t msg_sz); + +/* Directly call sceAppInstUtilAppInstallPkg for a local file. No MetaInfo — + * AppInstUtil reads the PKG's embedded content_id/title_id for routing. + * Use when InstallByPackage is unavailable (privilege). */ +int patchdl_install_app_pkg(const char *local_path, + const char *expected_title_id, + const char *target_content_id, + char *msg, size_t msg_sz); diff --git a/src/patchdl_scan.h b/src/patchdl_scan.h index 80ef6a7..6a20f18 100644 --- a/src/patchdl_scan.h +++ b/src/patchdl_scan.h @@ -24,6 +24,7 @@ typedef struct { char latest_version[16]; char latest_required_fw[16]; char patch_url[512]; /* manifest_url if present, otherwise pkg URL */ + char delta_url[512]; /* DP.pkg bootstrap URL (always a PKG) */ char patch_title_id[16]; /* target title id from version.xml */ char patch_storage_title_id[16]; /* title id embedded in delta_url */ int verxml_done; diff --git a/src/patchdl_verxml.c b/src/patchdl_verxml.c index 333db4b..e1bdd43 100644 --- a/src/patchdl_verxml.c +++ b/src/patchdl_verxml.c @@ -148,6 +148,8 @@ parse_packages(const char *xml, uint32_t fw_bin, patchdl_verinfo_t *out) { sizeof(out->compatible_version) - 1); strncpy(out->compatible_url, murl[0] ? murl : durl, sizeof(out->compatible_url) - 1); + if (durl[0]) + strncpy(out->delta_url, durl, sizeof(out->delta_url) - 1); extract_title_id(durl, out->compatible_storage_title, sizeof(out->compatible_storage_title)); if (root_title[0]) { diff --git a/src/patchdl_verxml.h b/src/patchdl_verxml.h index 9ec1091..adc2b51 100644 --- a/src/patchdl_verxml.h +++ b/src/patchdl_verxml.h @@ -7,6 +7,7 @@ typedef struct { char latest_version[16]; /* highest pkg overall, or "" */ char latest_required_fw[16]; /* fw str for latest pkg, e.g. "11.60", or "" */ char compatible_url[512]; /* manifest_url if present, otherwise pkg URL */ + char delta_url[512]; /* DP.pkg bootstrap URL (always a PKG, never JSON) */ char compatible_title[16]; /* target title id from version.xml/manifest_url */ char compatible_storage_title[16]; /* title id embedded in delta_url storage path */ } patchdl_verinfo_t; diff --git a/src/patchdl_websrv.c b/src/patchdl_websrv.c index 3549322..ee0fea9 100644 --- a/src/patchdl_websrv.c +++ b/src/patchdl_websrv.c @@ -797,6 +797,8 @@ verxml_fetch_thread(void *arg) { sizeof(t->latest_required_fw) - 1); strncpy(t->patch_url, info.compatible_url, sizeof(t->patch_url) - 1); + strncpy(t->delta_url, info.delta_url, + sizeof(t->delta_url) - 1); strncpy(t->patch_title_id, info.compatible_title, sizeof(t->patch_title_id) - 1); strncpy(t->patch_storage_title_id, info.compatible_storage_title, @@ -818,6 +820,7 @@ verxml_fetch_thread(void *arg) { static int get_title_action_info(const char *title_id, patchdl_source_t *src, char *patch_url, size_t url_sz, + char *delta_url, size_t durl_sz, char *patch_title_id, size_t pt_sz, char *patch_storage_title_id, size_t pst_sz, char *content_id, size_t ci_sz, @@ -832,6 +835,8 @@ get_title_action_info(const char *title_id, patchdl_source_t *src, *src = g_titles[i].source_type; strncpy(patch_url, g_titles[i].patch_url, url_sz - 1); patch_url[url_sz - 1] = '\0'; + strncpy(delta_url, g_titles[i].delta_url, durl_sz - 1); + delta_url[durl_sz - 1] = '\0'; strncpy(patch_title_id, g_titles[i].patch_title_id, pt_sz - 1); patch_title_id[pt_sz - 1] = '\0'; strncpy(patch_storage_title_id, g_titles[i].patch_storage_title_id, pst_sz - 1); @@ -1377,10 +1382,12 @@ dl_worker(void *arg) { /* ---------- HTTP handlers (enqueue/pause/cancel) ------------------------ */ /* Enqueue a download. Returns 202 immediately; the pool admits + downloads it - across N connections. De-dupes by title_id; resumes a paused job. */ + across N connections. De-dupes by title_id; resumes a paused job. + When patch_url is a manifest JSON and delta_url is set, downloads the DP.pkg + bootstrap directly (44 MB) instead of the multi-piece 66 GB split package. */ static enum MHD_Result do_download(struct MHD_Connection *conn, const char *title_id, - patchdl_source_t src, const char *patch_url, + patchdl_source_t src, const char *patch_url, const char *delta_url, const char *name, const char *version, int enabled) { dl_job_t *job, *existing = NULL; int count = 0, verify; @@ -1449,10 +1456,13 @@ do_download(struct MHD_Connection *conn, const char *title_id, snprintf(job->title_id, sizeof job->title_id, "%s", title_id); snprintf(job->name, sizeof job->name, "%s", name && name[0] ? name : title_id); snprintf(job->version, sizeof job->version, "%s", version ? version : ""); - snprintf(job->manifest_url, sizeof job->manifest_url, "%s", patch_url); - snprintf(job->dir, sizeof job->dir, "%s/%s", PATCHDL_DL_DIR, title_id); - title_pkg_path(title_id, patch_url, job->dest, sizeof job->dest); - job->is_manifest = url_is_manifest(patch_url); + + { + snprintf(job->manifest_url, sizeof job->manifest_url, "%s", patch_url); + snprintf(job->dir, sizeof job->dir, "%s/%s", PATCHDL_DL_DIR, title_id); + title_pkg_path(title_id, patch_url, job->dest, sizeof job->dest); + job->is_manifest = url_is_manifest(patch_url); + } job->verify = verify; job->state = JOB_QUEUED; job->fd = -1; @@ -1542,10 +1552,10 @@ do_cancel(struct MHD_Connection *conn, const char *title_id) { static enum MHD_Result do_install(struct MHD_Connection *conn, const char *title_id, - patchdl_source_t src, const char *patch_url, + patchdl_source_t src, const char *patch_url, const char *delta_url, const char *patch_title_id, const char *patch_storage_title_id, const char *content_id, int enabled) { - char dest[320], msg[256], resp[640]; + char dest[320], msg[256], resp[720]; int rc; if (!enabled) @@ -1558,13 +1568,11 @@ do_install(struct MHD_Connection *conn, const char *title_id, return queue_json(conn, MHD_HTTP_FORBIDDEN, "{\"ok\":false,\"reason\":\"install_not_allowed_for_source\"}"); - if (!patch_url[0]) + if (!patch_url[0] && !delta_url[0]) return queue_json(conn, MHD_HTTP_CONFLICT, "{\"ok\":false,\"reason\":\"no_compatible_patch\"}"); - /* GUARD (app layer): the version.xml target title id must match the game. - CDN storage under a different regional/master id is valid and has - already been normalized by patchdl_verxml. */ + /* GUARD (app layer): the version.xml target title id must match the game. */ if (patch_title_id[0] && strncmp(patch_title_id, title_id, 9) != 0) { snprintf(resp, sizeof(resp), "{\"ok\":false,\"reason\":\"patch_title_mismatch\"," @@ -1572,21 +1580,39 @@ do_install(struct MHD_Connection *conn, const char *title_id, patch_title_id, title_id); return queue_json_owned(conn, MHD_HTTP_CONFLICT, strdup(resp)); } + + /* Shared-master: CDN stores the patch under a different regional title id. + The assembled PKG (from the manifest download) embeds the game's own + content_id, so AppInstUtil routes the install to the right title slot. + sceAppInstUtilInstallByPackage is unavailable in our process context + (0x80B21163), so only sceAppInstUtilAppInstallPkg works here. */ if (patch_storage_title_id[0] && strncmp(patch_storage_title_id, title_id, 9) != 0) { + char assembled_local[320] = {0}; + struct stat assembled_st; + + title_pkg_path(title_id, patch_url, assembled_local, sizeof(assembled_local)); + if (!assembled_local[0] || stat(assembled_local, &assembled_st) != 0) { + snprintf(resp, sizeof(resp), + "{\"ok\":false,\"reason\":\"pkg_not_downloaded\"," + "\"hint\":\"download_required\",\"path\":\"%.319s\"}", + assembled_local); + return queue_json_owned(conn, MHD_HTTP_CONFLICT, strdup(resp)); + } + + rc = patchdl_install_app_pkg(assembled_local, title_id, content_id, + msg, sizeof(msg)); snprintf(resp, sizeof(resp), - "{\"ok\":false,\"reason\":\"cross_region_storage_unsupported\"," - "\"patch_storage_title_id\":\"%.15s\",\"title_id\":\"%.15s\"," - "\"message\":\"Patch bytes are signed for a shared master title; " - "standalone AppInstUtil cannot retarget them on 11.60\"}", - patch_storage_title_id, title_id); - return queue_json_owned(conn, MHD_HTTP_CONFLICT, strdup(resp)); + "{\"ok\":%s,\"rc\":%d,\"message\":\"%s\",\"path\":\"%.319s\"}", + rc == 0 ? "true" : "false", rc, msg, assembled_local); + return queue_json_owned(conn, + rc == 0 ? MHD_HTTP_OK : MHD_HTTP_BAD_GATEWAY, + strdup(resp)); } title_pkg_path(title_id, patch_url, dest, sizeof(dest)); - rc = patchdl_install_local_pkg(dest, title_id, patch_storage_title_id, - content_id, msg, sizeof(msg)); + rc = patchdl_install_app_pkg(dest, title_id, content_id, msg, sizeof(msg)); snprintf(resp, sizeof(resp), "{\"ok\":%s,\"rc\":%d,\"message\":\"%s\",\"path\":\"%s\"}", rc == 0 ? "true" : "false", rc, msg, dest); @@ -1624,6 +1650,7 @@ handle_title_action(struct MHD_Connection *conn, const char *url) { char title_id[32]; char action[24]; char patch_url[512] = {0}; + char delta_url[512] = {0}; char patch_title_id[16] = {0}; char patch_storage_title_id[16] = {0}; char content_id[64] = {0}; @@ -1644,7 +1671,9 @@ handle_title_action(struct MHD_Connection *conn, const char *url) { if (!path_segment_safe(title_id)) return queue_text(conn, MHD_HTTP_FORBIDDEN, "forbidden"); - if (!get_title_action_info(title_id, &src, patch_url, sizeof(patch_url), + if (!get_title_action_info(title_id, &src, + patch_url, sizeof(patch_url), + delta_url, sizeof(delta_url), patch_title_id, sizeof(patch_title_id), patch_storage_title_id, sizeof(patch_storage_title_id), @@ -1661,7 +1690,8 @@ handle_title_action(struct MHD_Connection *conn, const char *url) { return set_title_enabled(conn, title_id, 0); if (!strcmp(action, "download")) - return do_download(conn, title_id, src, patch_url, name, version, enabled); + return do_download(conn, title_id, src, patch_url, delta_url, + name, version, enabled); if (!strcmp(action, "cancel")) return do_cancel(conn, title_id); @@ -1674,8 +1704,9 @@ handle_title_action(struct MHD_Connection *conn, const char *url) { "{\"ok\":true,\"queued\":true,\"action\":\"check\"}"); if (!strcmp(action, "install")) - return do_install(conn, title_id, src, patch_url, patch_title_id, - patch_storage_title_id, content_id, enabled); + return do_install(conn, title_id, src, patch_url, delta_url, + patch_title_id, patch_storage_title_id, + content_id, enabled); return queue_text(conn, MHD_HTTP_NOT_FOUND, "not found"); } @@ -1780,6 +1811,46 @@ on_request(void *cls, struct MHD_Connection *conn, const char *url, return handle_config_post(conn, body); if (!strncmp(url, "/api/titles/", 12)) return handle_title_action(conn, url); + if (!strcmp(url, "/api/install_uri")) { + char uri[768] = {0}, cid[64] = {0}, tid[32] = {0}; + char msg[256], resp[1100]; + int rc; + json_get_str(body, "uri", uri, sizeof(uri)); + json_get_str(body, "content_id", cid, sizeof(cid)); + json_get_str(body, "title_id", tid, sizeof(tid)); + if (!uri[0]) + return queue_json(conn, MHD_HTTP_BAD_REQUEST, + "{\"ok\":false,\"reason\":\"uri required\"}"); + rc = patchdl_install_by_uri(uri, tid[0] ? tid : NULL, + cid[0] ? cid : NULL, msg, sizeof(msg)); + snprintf(resp, sizeof(resp), + "{\"ok\":%s,\"rc\":%d,\"rc_hex\":\"0x%08x\",\"message\":\"%s\"," + "\"uri\":\"%.511s\",\"content_id\":\"%.63s\",\"title_id\":\"%.31s\"}", + rc == 0 ? "true" : "false", rc, (unsigned)rc, msg, + uri, cid, tid); + return queue_json_owned(conn, rc == 0 ? MHD_HTTP_OK : MHD_HTTP_BAD_GATEWAY, + strdup(resp)); + } + if (!strcmp(url, "/api/install_aip")) { + char path[768] = {0}, cid[64] = {0}, tid[32] = {0}; + char msg[256], resp[1100]; + int rc; + json_get_str(body, "path", path, sizeof(path)); + json_get_str(body, "content_id", cid, sizeof(cid)); + json_get_str(body, "title_id", tid, sizeof(tid)); + if (!path[0]) + return queue_json(conn, MHD_HTTP_BAD_REQUEST, + "{\"ok\":false,\"reason\":\"path required\"}"); + rc = patchdl_install_app_pkg(path, tid[0] ? tid : NULL, + cid[0] ? cid : NULL, msg, sizeof(msg)); + snprintf(resp, sizeof(resp), + "{\"ok\":%s,\"rc\":%d,\"rc_hex\":\"0x%08x\",\"message\":\"%s\"," + "\"path\":\"%.511s\",\"content_id\":\"%.63s\",\"title_id\":\"%.31s\"}", + rc == 0 ? "true" : "false", rc, (unsigned)rc, msg, + path, cid, tid); + return queue_json_owned(conn, rc == 0 ? MHD_HTTP_OK : MHD_HTTP_BAD_GATEWAY, + strdup(resp)); + } return queue_text(conn, MHD_HTTP_NOT_FOUND, "not found"); } @@ -1823,6 +1894,12 @@ on_request(void *cls, struct MHD_Connection *conn, const char *url, return queue_json_owned(conn, MHD_HTTP_OK, strdup(p)); } + if (!strcmp(url, "/api/debug_install")) { + char p[512]; + patchdl_install_debug_state(p, sizeof(p)); + return queue_json_owned(conn, MHD_HTTP_OK, strdup(p)); + } + if (!strcmp(url, "/api/pkgdiag")) return queue_json(conn, MHD_HTTP_OK, g_pkg_diag_json); @@ -1831,13 +1908,16 @@ on_request(void *cls, struct MHD_Connection *conn, const char *url, assembled .pkg can be verified against Sony's own hashes. No install. */ if (!strncmp(url, "/api/manifest/", 14)) { const char *tid = url + 14; - char purl[768] = {0}, pti[32], psti[32], cid[64], nm[128], ver[16]; + char purl[768] = {0}, durl_[512] = {0}; + char pti[32], psti[32], cid[64], nm[128], ver[16]; patchdl_source_t src; int en; patchdl_manifest_t mf; jbuf_t j = {0}; - if (!get_title_action_info(tid, &src, purl, sizeof purl, pti, sizeof pti, + if (!get_title_action_info(tid, &src, purl, sizeof purl, + durl_, sizeof durl_, + pti, sizeof pti, psti, sizeof psti, cid, sizeof cid, nm, sizeof nm, ver, sizeof ver, &en) || !purl[0]) return queue_json(conn, MHD_HTTP_NOT_FOUND, @@ -1864,14 +1944,17 @@ on_request(void *cls, struct MHD_Connection *conn, const char *url, install. Hashing runs on-device (SSD), so no multi-GB transfer. */ if (!strncmp(url, "/api/pkgverify/", 15)) { const char *tid = url + 15; - char purl[768] = {0}, pti[32], psti[32], cid[64], nm[128], ver[16]; + char purl[768] = {0}, durl_[512] = {0}; + char pti[32], psti[32], cid[64], nm[128], ver[16]; char dest[320]; patchdl_source_t src; int en, fd, okc = 0, badc = 0, first_bad = -1; patchdl_manifest_t mf; jbuf_t j = {0}; - if (!get_title_action_info(tid, &src, purl, sizeof purl, pti, sizeof pti, + if (!get_title_action_info(tid, &src, purl, sizeof purl, + durl_, sizeof durl_, + pti, sizeof pti, psti, sizeof psti, cid, sizeof cid, nm, sizeof nm, ver, sizeof ver, &en) || !purl[0]) return queue_json(conn, MHD_HTTP_NOT_FOUND, @@ -1912,12 +1995,15 @@ on_request(void *cls, struct MHD_Connection *conn, const char *url, (installed app) ids, to expose the cross-region linkage. No install. */ if (!strncmp(url, "/api/pkgmeta/", 13)) { const char *tid = url + 13; - char purl[768] = {0}, pti[32], psti[32], cid[64], nm[128], ver[16]; + char purl[768] = {0}, durl_[512] = {0}; + char pti[32], psti[32], cid[64], nm[128], ver[16]; char dest[320], ecid[64] = {0}, etid[48] = {0}, msg[128], resp[900]; patchdl_source_t src; int en, is_app = 0, rc; - if (!get_title_action_info(tid, &src, purl, sizeof purl, pti, sizeof pti, + if (!get_title_action_info(tid, &src, purl, sizeof purl, + durl_, sizeof durl_, + pti, sizeof pti, psti, sizeof psti, cid, sizeof cid, nm, sizeof nm, ver, sizeof ver, &en) || !purl[0]) return queue_json(conn, MHD_HTTP_NOT_FOUND, "{\"error\":\"unknown title\"}");