Files
knutwurst--patchdl/src/patchdl_install.c
T

648 lines
24 KiB
C

#include "patchdl_install.h"
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <ps5/kernel.h>
#include <pthread.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
/* AppInstUtil structs/signatures, reverse-engineered by the PS5 homebrew
scene (notmaj0r/CheatRunner, ps5-payload-dev/websrv). Sizes per CheatRunner:
CONTENTID=0x30, PLAYGOSCENARIOID=3, LANGUAGE=8, NUM_LANGUAGES=30, NUM_IDS=64.
The trailing `unknown` padding makes the playgo struct safely large. */
#define AI_CONTENTID_SIZE 0x30
#define AI_PLAYGOSCENARIOID_SIZE 3
#define AI_LANGUAGE_SIZE 8
#define AI_NUM_LANGUAGES 30
#define AI_NUM_IDS 64
typedef struct {
char content_id[AI_CONTENTID_SIZE];
int content_type;
int content_platform;
} ai_pkg_info_t;
typedef struct {
const char *uri;
const char *ex_uri;
const char *playgo_scenario_id;
const char *content_id;
const char *content_name;
const char *icon_url;
} ai_meta_info_t;
typedef struct {
char languages[AI_NUM_LANGUAGES][AI_LANGUAGE_SIZE];
char playgo_scenario_ids[AI_NUM_IDS][AI_PLAYGOSCENARIOID_SIZE];
char content_ids[AI_NUM_IDS][AI_CONTENTID_SIZE];
long unknown[810];
} ai_playgo_info_t;
typedef struct {
int32_t error_code;
int32_t version;
char description[512];
char type[9];
} ai_install_error_t;
typedef struct {
char status[16];
char src_type[8];
uint32_t remain_time;
uint64_t downloaded_size;
uint64_t initial_chunk_size;
uint64_t total_size;
uint32_t promote_progress;
ai_install_error_t error_info;
int32_t local_copy_percent;
bool is_copy_only;
} ai_install_status_t;
#define STATIC_ASSERT(c, n) typedef char static_assert_##n[(c) ? 1 : -1]
STATIC_ASSERT(sizeof(ai_pkg_info_t) == 0x38, pkg_info_size);
STATIC_ASSERT(sizeof(ai_meta_info_t) == (6 * sizeof(void *)), meta_info_size);
STATIC_ASSERT(sizeof(ai_playgo_info_t) == 0x2700, playgo_info_size);
STATIC_ASSERT(offsetof(ai_meta_info_t, uri) == 0, meta_uri_offset);
STATIC_ASSERT(offsetof(ai_meta_info_t, icon_url) == (5 * sizeof(void *)),
meta_icon_offset);
/* Sysmodule IDs (from ps5-payload-dev/sdk crt/rtld_sprx.c). */
#define SYSMOD_IPMI 0x8000001d
#define SYSMOD_USERSERVICE 0x80000011
#define SYSMOD_SYSTEMSERVICE 0x80000010
#define SYSMOD_APPINSTUTIL 0x80000014
typedef int (*sysmod_load_fn)(unsigned int id);
typedef int (*ai_init_fn)(void);
typedef int (*ai_install_pkg_fn)(const char *path, ai_pkg_info_t *info);
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);
static ai_init_fn ai_initialize;
static ai_install_pkg_fn ai_install_pkg;
static ai_install_by_pkg_fn ai_install_by_package;
static ai_title_from_pkg_fn ai_title_from_pkg;
static ai_content_from_pkg_fn ai_content_from_pkg;
static ai_get_status_fn ai_get_status;
/* Resolve + initialize the AppInstUtil backend WITHOUT linking the sce libs
(that makes the ELF unloadable by the elfldr) and WITHOUT raw
sceKernelLoadStartModule (that wedged the process). Instead use the proper
sysmodule loader — the same path the SDK's rtld_sprx takes — then resolve
symbols via the kernel dynlib helpers. Runs in a detached thread; the HTTP
handler reports the stage and never blocks.
stage: 0 idle, 1 resolve loader, 2 load modules, 3 resolve symbols,
4 initialize, 5 ready, negative = failure at that step. */
static volatile int g_stage;
static int g_err;
static pthread_mutex_t g_mtx = PTHREAD_MUTEX_INITIALIZER;
static char g_probe_json[2048]; /* filled by the backend thread */
static char g_last_content_id[AI_CONTENTID_SIZE];
static char g_last_target_title_id[32];
static char g_last_method[32];
static int g_last_start_rc;
static intptr_t
dynsym(const char *module, const char *sym) {
uint32_t h = 0;
if (kernel_dynlib_handle(-1, module, &h) < 0)
return 0;
return kernel_dynlib_dlsym(-1, h, sym);
}
static void
local_ip(char *out, size_t n) {
struct ifaddrs *ifa = NULL, *p;
out[0] = '\0';
if (getifaddrs(&ifa))
return;
for (p = ifa; p; p = p->ifa_next) {
char ip[INET_ADDRSTRLEN];
struct sockaddr_in *s;
if (!p->ifa_addr || p->ifa_addr->sa_family != AF_INET)
continue;
s = (struct sockaddr_in *)p->ifa_addr;
if (!inet_ntop(AF_INET, &s->sin_addr, ip, sizeof(ip)))
continue;
if (strcmp(ip, "127.0.0.1") && strncmp(ip, "0.", 2)) {
strncpy(out, ip, n - 1);
out[n - 1] = '\0';
break;
}
}
freeifaddrs(ifa);
}
static void
copy_bounded(char *dst, size_t dst_sz, const char *src, size_t src_sz) {
size_t n;
if (!dst || !dst_sz) return;
dst[0] = '\0';
if (!src || !src_sz) return;
for (n = 0; n + 1 < dst_sz && n < src_sz && src[n]; n++)
dst[n] = src[n];
dst[n] = '\0';
}
static void
remember_install(const char *target_title_id, const char *method,
const ai_pkg_info_t *pkg, const char *fallback_content_id,
int rc) {
char cid[AI_CONTENTID_SIZE] = {0};
if (pkg)
copy_bounded(cid, sizeof(cid), pkg->content_id, sizeof(pkg->content_id));
if (!cid[0] && fallback_content_id)
copy_bounded(cid, sizeof(cid), fallback_content_id, strlen(fallback_content_id));
pthread_mutex_lock(&g_mtx);
snprintf(g_last_content_id, sizeof(g_last_content_id), "%s", cid);
snprintf(g_last_target_title_id, sizeof(g_last_target_title_id), "%s",
target_title_id ? target_title_id : "");
snprintf(g_last_method, sizeof(g_last_method), "%s", method ? method : "");
g_last_start_rc = rc;
pthread_mutex_unlock(&g_mtx);
}
/* Resolve (dlsym, never call) a list of candidate patch-install symbols and
record which exist. Runs inside the backend thread, where the AppInstUtil
module is already loaded — the same proven-safe context as the normal symbol
resolution. No sysmod_load and no calls, so it is side-effect free. */
static void
fill_probe(void) {
static const char *ai_syms[] = {
"sceAppInstUtilInitialize",
"sceAppInstUtilAppInstallPkg",
"sceAppInstUtilAppInstallTitleDir", /* takes an explicit title id */
"sceAppInstUtilInstallByPackage",
"sceAppInstUtilInstallByPackageEx",
"sceAppInstUtilGetTitleIdFromPkg",
"sceAppInstUtilGetContentIdFromPkg",
"sceAppInstUtilGetInstallStatus",
"sceAppInstUtilAppExist",
"sceAppInstUtilAppGetInstallStatus",
"sceAppInstUtilAppInstallStatus",
"sceAppInstUtilAppUnInstall",
"sceAppInstUtilGetMetaInfoFromPkg",
"sceAppInstUtilUpdateTitleByTitleId",
"sceAppInstUtilInstallByChunk",
NULL
};
static const char *bgft_syms[] = {
"sceBgftInitialize",
"sceBgftServiceIntInit",
"sceBgftServiceDownloadRegisterTask",
"sceBgftServiceDownloadRegisterTaskByStorage",
"sceBgftServiceDownloadRegisterTaskByStorageEx",
"sceBgftServiceIntDownloadRegisterTaskByStorageEx",
"sceBgftServiceDownloadStartTask",
"sceBgftServiceDownloadGetProgress",
"sceBgftServiceInstallPackage",
NULL
};
uint32_t h = 0;
int ai_loaded = (kernel_dynlib_handle(-1, "libSceAppInstUtil.sprx", &h) >= 0);
int bgft_loaded = (kernel_dynlib_handle(-1, "libSceBgft.sprx", &h) >= 0);
char tmp[sizeof(g_probe_json)];
size_t n = 0, sz = sizeof(tmp);
char *out = tmp;
int first = 1;
n += snprintf(out + n, sz - n,
"{\"appinstutil_loaded\":%s,\"bgft_loaded\":%s,\"symbols\":{",
ai_loaded ? "true" : "false", bgft_loaded ? "true" : "false");
for (int i = 0; ai_syms[i] && n < sz - 80; i++) {
intptr_t a = dynsym("libSceAppInstUtil.sprx", ai_syms[i]);
n += snprintf(out + n, sz - n, "%s\"%s\":%s",
first ? "" : ",", ai_syms[i], a ? "true" : "false");
first = 0;
}
for (int i = 0; bgft_syms[i] && n < sz - 80; i++) {
intptr_t a = bgft_loaded ? dynsym("libSceBgft.sprx", bgft_syms[i]) : 0;
n += snprintf(out + n, sz - n, "%s\"%s\":%s",
first ? "" : ",", bgft_syms[i], a ? "true" : "false");
first = 0;
}
snprintf(out + n, sz - n, "}}");
/* Publish atomically: the getter runs on an MHD worker thread and reads
g_probe_json under the same lock, so it never sees a half-built buffer. */
pthread_mutex_lock(&g_mtx);
memcpy(g_probe_json, tmp, sizeof(g_probe_json));
pthread_mutex_unlock(&g_mtx);
}
static void *
backend_init_thread(void *arg) {
(void)arg;
sysmod_load_fn sysmod_load;
g_stage = 1;
sysmod_load = (sysmod_load_fn)dynsym("libSceSysmodule.sprx",
"sceSysmoduleLoadModuleInternal");
if (!sysmod_load) { g_stage = -1; return NULL; }
g_stage = 2;
/* Dependencies first, then AppInstUtil. The sysmodule loader pulls in
further deps and is the non-wedging path. */
sysmod_load(SYSMOD_IPMI);
sysmod_load(SYSMOD_USERSERVICE);
sysmod_load(SYSMOD_SYSTEMSERVICE);
g_err = sysmod_load(SYSMOD_APPINSTUTIL);
if (g_err) { g_stage = -2; return NULL; }
g_stage = 3;
ai_initialize = (ai_init_fn)dynsym("libSceAppInstUtil.sprx",
"sceAppInstUtilInitialize");
ai_install_pkg = (ai_install_pkg_fn)dynsym("libSceAppInstUtil.sprx",
"sceAppInstUtilAppInstallPkg");
ai_install_by_package = (ai_install_by_pkg_fn)dynsym("libSceAppInstUtil.sprx",
"sceAppInstUtilInstallByPackage");
ai_title_from_pkg = (ai_title_from_pkg_fn)dynsym("libSceAppInstUtil.sprx",
"sceAppInstUtilGetTitleIdFromPkg");
ai_content_from_pkg = (ai_content_from_pkg_fn)dynsym("libSceAppInstUtil.sprx",
"sceAppInstUtilGetContentIdFromPkg");
ai_get_status = (ai_get_status_fn)dynsym("libSceAppInstUtil.sprx",
"sceAppInstUtilGetInstallStatus");
/* Read-only feasibility probe — module is loaded, safe context. */
fill_probe();
if (!ai_initialize || !ai_install_pkg || !ai_install_by_package) {
g_stage = -3;
return NULL;
}
g_stage = 4;
g_err = ai_initialize();
if (g_err) { g_stage = -4; return NULL; }
g_stage = 5;
return NULL;
}
static void
backend_start(void) {
pthread_mutex_lock(&g_mtx);
if (g_stage == 0) {
pthread_t tid;
pthread_attr_t attr;
g_stage = 1;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, backend_init_thread, NULL);
pthread_attr_destroy(&attr);
}
pthread_mutex_unlock(&g_mtx);
}
static const char *
stage_str(int s) {
switch (s) {
case 0: return "idle";
case 1: return "resolving sysmodule loader";
case 2: return "loading AppInstUtil module";
case 3: return "resolving AppInstUtil symbols";
case 4: return "initializing service";
case 5: return "ready";
case -1: return "sysmodule loader unavailable";
case -2: return "AppInstUtil module load failed";
case -3: return "AppInstUtil symbol resolution failed";
case -4: return "AppInstUtil init failed";
default: return "unknown";
}
}
int
patchdl_install_backend_check(char *msg, size_t msg_sz) {
int s;
backend_start();
s = g_stage;
snprintf(msg, msg_sz, "%s (rc=0x%08x)", stage_str(s), (unsigned)g_err);
return (s == 5) ? 0 : -1;
}
/* Public getter: trigger the backend (which fills the probe in its own thread)
and return the cached result. Runs from the MHD worker thread, so it only
reads the cached string — it never loads modules or resolves symbols here. */
int
patchdl_install_api_probe(char *out, size_t out_sz) {
int ready;
backend_start();
pthread_mutex_lock(&g_mtx);
ready = (g_probe_json[0] != '\0');
if (ready)
snprintf(out, out_sz, "%s", g_probe_json);
pthread_mutex_unlock(&g_mtx);
if (ready)
return 0;
snprintf(out, out_sz,
"{\"pending\":true,\"stage\":\"%s\"}", stage_str(g_stage));
return -1;
}
/* Read-only: report the .pkg's embedded content id + title id (and whether it
is a full app vs a patch). No install, no side effects. 0 if anything read. */
int
patchdl_install_pkg_meta(const char *local_path, char *content_id, size_t cid_sz,
char *title_id, size_t tid_sz, int *is_app,
char *msg, size_t msg_sz) {
char sdk_path[1024];
char cid[64] = {0}, tid[48] = {0};
int app_c = 0, app_t = 0, ok = 0;
struct stat st;
if (content_id && cid_sz) content_id[0] = '\0';
if (title_id && tid_sz) title_id[0] = '\0';
if (is_app) *is_app = 0;
if (!local_path || !local_path[0] || stat(local_path, &st) != 0) {
snprintf(msg, msg_sz, "package not on disk");
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);
if (ai_content_from_pkg &&
ai_content_from_pkg(sdk_path, cid, &app_c) == 0 && cid[0]) {
if (content_id && cid_sz) {
strncpy(content_id, cid, cid_sz - 1);
content_id[cid_sz - 1] = '\0';
}
if (is_app) *is_app = app_c;
ok = 1;
}
if (ai_title_from_pkg &&
ai_title_from_pkg(sdk_path, tid, &app_t) == 0 && tid[0]) {
if (title_id && tid_sz) {
strncpy(title_id, tid, tid_sz - 1);
title_id[tid_sz - 1] = '\0';
}
ok = 1;
}
snprintf(msg, msg_sz, ok ? "ok" : "could not read pkg metadata");
return ok ? 0 : -1;
}
int
patchdl_install_status_json(char *out, size_t out_sz) {
char cid[AI_CONTENTID_SIZE];
char tid[32];
char method[32];
int start_rc;
ai_install_status_t st;
char status[17], src_type[9];
int rc;
int progress = 0;
int terminal = 0;
if (!out || !out_sz)
return -1;
backend_start();
pthread_mutex_lock(&g_mtx);
snprintf(cid, sizeof(cid), "%s", g_last_content_id);
snprintf(tid, sizeof(tid), "%s", g_last_target_title_id);
snprintf(method, sizeof(method), "%s", g_last_method);
start_rc = g_last_start_rc;
pthread_mutex_unlock(&g_mtx);
if (!cid[0]) {
snprintf(out, out_sz, "{\"active\":false}");
return -1;
}
if (g_stage != 5) {
snprintf(out, out_sz,
"{\"active\":true,\"content_id\":\"%s\",\"target_title_id\":\"%s\","
"\"method\":\"%s\",\"start_rc\":%d,\"status\":\"backend_not_ready\","
"\"stage\":\"%s\"}",
cid, tid, method, start_rc, stage_str(g_stage));
return -1;
}
if (!ai_get_status) {
snprintf(out, out_sz,
"{\"active\":true,\"content_id\":\"%s\",\"target_title_id\":\"%s\","
"\"method\":\"%s\",\"start_rc\":%d,\"status\":\"unavailable\","
"\"message\":\"sceAppInstUtilGetInstallStatus not exported\"}",
cid, tid, method, start_rc);
return -1;
}
memset(&st, 0, sizeof(st));
rc = ai_get_status(cid, &st);
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)
progress = (int)((st.downloaded_size * 100) / st.total_size);
if (progress < 0) progress = 0;
if (progress > 100) progress = 100;
terminal = (!strcmp(status, "playable") ||
!strcmp(status, "error") ||
!strcmp(status, "none"));
snprintf(out, out_sz,
"{\"active\":true,\"terminal\":%s,\"content_id\":\"%s\","
"\"target_title_id\":\"%s\",\"method\":\"%s\",\"start_rc\":%d,"
"\"rc\":%d,\"status\":\"%s\",\"src_type\":\"%s\","
"\"progress\":%d,\"downloaded_size\":%llu,\"total_size\":%llu,"
"\"promote_progress\":%u,\"error_code\":%d}",
terminal ? "true" : "false", cid, tid, method, start_rc, rc,
status, src_type, progress,
(unsigned long long)st.downloaded_size,
(unsigned long long)st.total_size,
(unsigned)st.promote_progress,
(int)st.error_info.error_code);
return rc;
}
int
patchdl_install_local_pkg(const char *local_path, const char *expected_title_id,
const char *storage_title_id,
const char *target_content_id,
char *msg, size_t msg_sz) {
char sdk_path[1024];
char pkg_tid[48] = {0};
struct stat st;
int rc;
int pkg_tid_mismatch = 0;
if (!local_path || !local_path[0]) {
snprintf(msg, msg_sz, "no package 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;
}
/* Sony's installer sees the user partition as /user/data, not /data, and
PATH-ALLOWLISTS the URI passed to InstallByPackage: /user/data/ and
/mnt/usb are accepted, but a bare /data/... path is REJECTED with
0x80B2116F (empirically confirmed by the ps5upload project). So feed the
/user/data view of the file to both InstallByPackage and AppInstallPkg. */
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);
/* Diagnostic guard: Sony sometimes stores one patch byte stream under a
master title id while the version.xml targets a regional title id. That
is valid only when the caller supplies target metadata, so do not feed
such packages to the raw AppInstallPkg path. */
if (storage_title_id && storage_title_id[0] &&
expected_title_id && expected_title_id[0] &&
strncmp(storage_title_id, expected_title_id, 9) != 0) {
pkg_tid_mismatch = 1;
strncpy(pkg_tid, storage_title_id, sizeof(pkg_tid) - 1);
}
if (ai_title_from_pkg && expected_title_id && expected_title_id[0]) {
int is_app = 0;
if (ai_title_from_pkg(sdk_path, pkg_tid, &is_app) == 0 && pkg_tid[0] &&
strncmp(pkg_tid, expected_title_id, 9) != 0) {
pkg_tid_mismatch = 1;
}
}
if (pkg_tid_mismatch && (!target_content_id || !target_content_id[0])) {
snprintf(msg, msg_sz,
"refused: package metadata is %.12s, target is %.12s",
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. */
{
char file_uri[1100];
char http_loop_uri[1200] = {0};
char http_lan_uri[1200] = {0};
const char *uris[4];
ai_meta_info_t meta = {0};
ai_pkg_info_t pkg = {0};
ai_playgo_info_t playgo = {0};
int rc2 = -1;
const char *title_dir;
const char *file_base;
snprintf(file_uri, sizeof(file_uri), "file://%s", sdk_path);
title_dir = strstr(local_path, "/data/patchdl/");
file_base = strrchr(local_path, '/');
if (title_dir && file_base && file_base > title_dir + strlen("/data/patchdl/")) {
char title_id[32] = {0};
const char *t = title_dir + strlen("/data/patchdl/");
size_t tlen = (size_t)(file_base - t);
if (tlen > 0 && tlen < sizeof(title_id)) {
char ip[INET_ADDRSTRLEN] = {0};
memcpy(title_id, t, tlen);
snprintf(http_loop_uri, sizeof(http_loop_uri),
"http://127.0.0.1:%d/api/pkg/%s/%s",
PATCHDL_HTTP_PORT, title_id, file_base + 1);
local_ip(ip, sizeof(ip));
if (ip[0])
snprintf(http_lan_uri, sizeof(http_lan_uri),
"http://%s:%d/api/pkg/%s/%s",
ip, PATCHDL_HTTP_PORT, title_id, file_base + 1);
}
}
uris[0] = sdk_path; /* /user/data/... — the allowlisted path */
uris[1] = file_uri; /* file:///user/data/... */
uris[2] = http_loop_uri[0] ? http_loop_uri : NULL;
uris[3] = http_lan_uri[0] ? http_lan_uri : NULL;
meta.ex_uri = "";
meta.playgo_scenario_id = "";
meta.content_id = "";
meta.content_name = "PatchDL";
meta.icon_url = "";
{
char tries[260] = {0};
const char *labels[4] = { "userdata", "file", "loop", "lan" };
for (int i = 0; i < 4; i++) {
if (!uris[i]) continue;
memset(&pkg, 0, sizeof(pkg));
memset(&playgo, 0, sizeof(playgo));
meta.uri = uris[i];
rc2 = ai_install_by_package(&meta, &pkg, &playgo);
{
size_t l = strlen(tries);
snprintf(tries + l, sizeof(tries) - l, "%s%s=0x%08x",
l ? "," : "", labels[i], (unsigned)rc2);
}
if (rc2 == 0) {
remember_install(expected_title_id, "InstallByPackage",
&pkg, target_content_id, rc2);
snprintf(msg, msg_sz, "install started (InstallByPackage, content %.47s)",
pkg.content_id[0] ? pkg.content_id :
(target_content_id ? target_content_id : ""));
return 0;
}
}
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. */
{
ai_pkg_info_t pkg = {0};
int 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)",
pkg.content_id[0] ? pkg.content_id :
(target_content_id ? target_content_id : ""));
return 0;
}
snprintf(msg, msg_sz,
"install rejected (InstallByPackage=0x%08x, AppInstallPkg=0x%08x)",
(unsigned)rc, (unsigned)rc2);
return rc2 ? rc2 : (rc ? rc : -1);
}
}