#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "memory.h" #include "fake-lib.h" uint32_t MsiGetFileVersionW(const char16_t *filename, char16_t *version, uint32_t *version_size, char16_t *language, uint32_t *language_size) { char *fname = ascii(filename, true); uint32_t toret = 1006; /* ERROR_FILE_INVALID == 'no version info found' */ int fd = -1; void *mapv = MAP_FAILED; if (version && *version_size) *version = 0; if (language && *language_size) *language = 0; fd = open(fname, O_RDONLY); if (fd < 0) err(1, "%s: open", fname); struct stat st; if (fstat(fd, &st) < 0) err(1, "%s: fstat", fname); size_t fsize = st.st_size; mapv = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, fd, 0); if (mapv == MAP_FAILED) err(1, "%s: mmap", fname); unsigned char *map = (unsigned char *)mapv; if (le(map, fsize, 0, 2) != (('Z'<<8) | 'M')) { warnx("MsiGetFileInfo(%s) -> no MZ", fname); goto cleanup; } unsigned pe_pos = le(map, fsize, 0x3c, 4); if (le(map, fsize, pe_pos, 4) != (('E'<<8) | 'P')) { warnx("MsiGetFileInfo(%s) -> no PE", fname); goto cleanup; } pe_pos += 4; /* skip to the main header */ unsigned nsections = le(map, fsize, pe_pos + 2, 2); unsigned opthdr_size = le(map, fsize, pe_pos + 16, 2); unsigned opthdr_pos = pe_pos + 20; /* bool sixtyfourbit = le(map, fsize, opthdr_pos, 2) == 0x020B; */ unsigned secthdr_pos = opthdr_pos + opthdr_size; while (nsections > 0) { if (le(map, fsize, secthdr_pos+0, 1) == '.' && le(map, fsize, secthdr_pos+1, 1) == 'r' && le(map, fsize, secthdr_pos+2, 1) == 's' && le(map, fsize, secthdr_pos+3, 1) == 'r' && le(map, fsize, secthdr_pos+4, 1) == 'c' && le(map, fsize, secthdr_pos+5, 1) == 0) goto found_resource_section; secthdr_pos += 0x28; nsections--; } warnx("MsiGetFileInfo(%s) -> no .rsrc", fname); goto cleanup; found_resource_section:; unsigned rsrc_size = le(map, fsize, secthdr_pos+8, 4); unsigned rsrc_offset = le(map, fsize, secthdr_pos+20, 4); unsigned rsrc_vaddr = le(map, fsize, secthdr_pos+12, 4); unsigned res_dir_offset = rsrc_offset; unsigned nnamed, nid; nnamed = le(map, fsize, res_dir_offset+12, 2); nid = le(map, fsize, res_dir_offset+14, 2); for (unsigned i = nnamed; i < nnamed+nid; i++) { unsigned id = le(map, fsize, res_dir_offset + 16 + 8*i, 4); unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); if (id == 16 && (entry & 0x80000000)) { res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); goto found_versioninfo_toplevel; } } warnx("MsiGetFileInfo(%s) -> no top-level numeric key 16 for versioninfo", fname); goto cleanup; found_versioninfo_toplevel: nnamed = le(map, fsize, res_dir_offset+12, 2); nid = le(map, fsize, res_dir_offset+14, 2); for (unsigned i = 0; i < nnamed+nid; i++) { unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); if (entry & 0x80000000) { res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); goto found_versioninfo_2ndlevel; } } warnx("MsiGetFileInfo(%s) -> no 2nd-level subdir", fname); goto cleanup; found_versioninfo_2ndlevel: nnamed = le(map, fsize, res_dir_offset+12, 2); nid = le(map, fsize, res_dir_offset+14, 2); for (unsigned i = 0; i < nnamed+nid; i++) { unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); if (!(entry & 0x80000000)) { res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); goto found_versioninfo_3rdlevel; } } warnx("MsiGetFileInfo(%s) -> no 3rd-level resource data", fname); goto cleanup; found_versioninfo_3rdlevel:; unsigned versioninfo_offset = le(map, fsize, res_dir_offset, 4); unsigned versioninfo_size = le(map, fsize, res_dir_offset+4, 4); versioninfo_offset = rsrc_offset + versioninfo_offset - rsrc_vaddr; unsigned name_offset = versioninfo_offset + 6; const char *next_name_chr = "VS_VERSION_INFO"; do { if (le(map, fsize, name_offset, 2) != *next_name_chr) goto cleanup; /* identifying string didn't match */ name_offset += 2; } while (*next_name_chr++); unsigned fixed_offset = (name_offset + 3) & ~3; if (le(map, fsize, fixed_offset, 4) != 0xFEEF04BDU) { warnx("MsiGetFileInfo(%s) -> no VS_FIXEDFILEINFO magic number", fname); goto cleanup; } int four_part_version[4]; four_part_version[0] = le(map, fsize, fixed_offset + 10, 2); four_part_version[1] = le(map, fsize, fixed_offset + 8, 2); four_part_version[2] = le(map, fsize, fixed_offset + 14, 2); four_part_version[3] = le(map, fsize, fixed_offset + 12, 2); unsigned child_offset = fixed_offset + le(map, fsize, versioninfo_offset+2, 2); unsigned lcid; while (child_offset < versioninfo_offset + versioninfo_size) { unsigned this_child_offset = child_offset; child_offset += le(map, fsize, child_offset, 2); child_offset = (child_offset + 3) &~ 3; if (child_offset <= this_child_offset) { warnx("MsiGetFileInfo(%s) -> bad length field", fname); goto cleanup; } const char *next_name_chr = "VarFileInfo"; name_offset = this_child_offset + 6; do { if (le(map, fsize, name_offset, 2) != *next_name_chr) goto this_is_not_a_varfileinfo; name_offset += 2; } while (*next_name_chr++); unsigned subchild_offset = (name_offset + 3) & ~3; next_name_chr = "Translation"; name_offset = subchild_offset + 6; do { if (le(map, fsize, name_offset, 2) != *next_name_chr) { warnx("MsiGetFileInfo(%s) -> child not called 'Translation'", fname); goto cleanup; } name_offset += 2; } while (*next_name_chr++); subchild_offset = (name_offset + 3) & ~3; lcid = le(map, fsize, subchild_offset, 2); goto success; this_is_not_a_varfileinfo: continue; } warnx("MsiGetFileInfo(%s) -> no VarFileInfo found", fname); goto cleanup; success:; char verbuf[256], langbuf[256]; snprintf(verbuf, sizeof(verbuf), "%d.%d.%d.%d", four_part_version[0], four_part_version[1], four_part_version[2], four_part_version[3]); snprintf(langbuf, sizeof(langbuf), "%u", lcid); warnx("MsiGetFileInfo(%s) -> version %s lang %s", fname, verbuf, langbuf); if (version) c16cpy(version, version_size, verbuf); if (language) c16cpy(language, language_size, langbuf); toret = 0; cleanup: if (mapv != MAP_FAILED) munmap(mapv, fsize); if (fd != -1) close(fd); sfree(fname); return toret; } typedef struct MsiTypePrefix { enum { MAIN, VIEW, RECORD } type; } MsiTypePrefix; typedef struct MsiMainCtx { MsiTypePrefix t; char *tempdir; char *outfile; char **args; int nargs, argsize; } MsiMainCtx; uint32_t MsiOpenDatabaseW(const char16_t *filename, const char16_t *persist, MsiMainCtx **out_ctx) { MsiMainCtx *ctx = snew(MsiMainCtx); ctx->t.type = MAIN; ctx->outfile = ascii(filename, true); close(open(ctx->outfile, O_CREAT | O_WRONLY, 0666)); ctx->outfile = realpath(ctx->outfile, NULL); unlink(ctx->outfile); ctx->tempdir = dupcat(ctx->outfile, "-msiXXXXXX", (const char *)NULL); if (!mkdtemp(ctx->tempdir)) err(1, "%s: mkdtemp", ctx->tempdir); ctx->nargs = 0; ctx->argsize = 16; ctx->args = snewn(ctx->argsize, char *); ctx->args[ctx->nargs++] = dupcat("sh", (const char *)NULL); ctx->args[ctx->nargs++] = dupcat("-c", (const char *)NULL); ctx->args[ctx->nargs++] = dupcat("cd \"$0\" && \"$@\"", (const char *)NULL); ctx->args[ctx->nargs++] = dupcat(ctx->tempdir, (const char *)NULL); ctx->args[ctx->nargs++] = dupcat("msibuild", (const char *)NULL); ctx->args[ctx->nargs++] = dupcat(ctx->outfile, (const char *)NULL); *out_ctx = ctx; return 0; } uint32_t MsiDatabaseImportW(MsiMainCtx *ctx, const char16_t *folder, const char16_t *file) { assert(ctx->t.type == MAIN); system_argv("sh", "-c", "cd \"$0\" && cp \"$1\" \"$2\"", ascii(folder, true), ascii(file, true), ctx->tempdir, (const char *)NULL); if (ctx->nargs + 2 >= ctx->argsize) { ctx->argsize = ctx->nargs * 5 / 4 + 16; ctx->args = sresize(ctx->args, ctx->argsize, char *); } ctx->args[ctx->nargs++] = dupcat("-i", (const char *)NULL); ctx->args[ctx->nargs++] = dupcat(ctx->tempdir, "/", ascii(file, true), (const char *)NULL); return 0; } typedef struct MsiView { MsiTypePrefix t; FILE *fp; char *targetdir; MsiMainCtx *ctx; } MsiView; uint32_t MsiDatabaseOpenViewW(MsiMainCtx *ctx, const char16_t *query, MsiView **outview) { assert(ctx->t.type == MAIN); MsiView *view = snew(MsiView); view->t.type = VIEW; view->ctx = ctx; char *cquery = ascii(query, false); if (!strcmp(cquery, "SELECT `Name`, `Data` FROM `_Streams`")) view->fp = NULL; /* special case */ else { if (!strcmp(cquery, "SELECT `Name`, `Data` FROM `Binary`")) { view->fp = fopen(dupcat(ctx->tempdir, "/", "Binary.idt", (const char *)NULL), "a"); view->targetdir = dupcat(ctx->tempdir, "/", "Binary", (const char *)NULL); } else if (!strcmp(cquery, "SELECT `Name`, `Data` FROM `Icon`")) { view->fp = fopen(dupcat(ctx->tempdir, "/", "Icon.idt", (const char *)NULL), "a"); view->targetdir = dupcat(ctx->tempdir, "/", "Icon", (const char *)NULL); } else errx(1, "unrecognised query: %s", cquery); if (!view->fp) err(1, "open"); if (mkdir(view->targetdir, 0777) < 0) err(1, "%s: mkdir", view->targetdir); } *outview = view; return 0; } uint32_t MsiViewExecute(MsiView *view, void *params) { assert(view->t.type == VIEW); return 0; } typedef struct MsiRecord { MsiTypePrefix t; char *name, *data; } MsiRecord; MsiRecord *MsiCreateRecord(uint32_t nparams) { MsiRecord *rec = snew(MsiRecord); rec->t.type = RECORD; if (nparams != 2) errx(1, "bad MsiCreateRecord param count %u", (unsigned)nparams); rec->name = rec->data = NULL; return rec; } uint32_t MsiRecordSetStringW(MsiRecord *rec, uint32_t field, char16_t *value) { assert(rec->t.type == RECORD); if (field != 1) errx(1, "bad MsiRecordSetString param index %u", (unsigned)field); rec->name = ascii(value, false); return 0; } uint32_t MsiRecordSetStreamW(MsiRecord *rec, uint32_t field, char16_t *path) { assert(rec->t.type == RECORD); if (field != 2) errx(1, "bad MsiRecordSetStream param index %u", (unsigned)field); rec->data = ascii(path, true); return 0; } uint32_t MsiViewModify(MsiView *view, uint32_t mode, MsiRecord *rec) { assert(view->t.type == VIEW); assert(rec->t.type == RECORD); if (view->fp) { system_argv("sh", "-c", "cp \"$0\" \"$1\"/\"$2\"", rec->data, view->targetdir, rec->name, (const char *)NULL); fprintf(view->fp, "%s\t%s\r\n", rec->name, rec->name); } else { MsiMainCtx *ctx = view->ctx; if (ctx->nargs + 3 >= ctx->argsize) { ctx->argsize = ctx->nargs * 5 / 4 + 16; ctx->args = sresize(ctx->args, ctx->argsize, char *); } ctx->args[ctx->nargs++] = dupcat("-a", (const char *)NULL); ctx->args[ctx->nargs++] = dupcat(rec->name, (const char *)NULL); ctx->args[ctx->nargs++] = dupcat(rec->data, (const char *)NULL); } return 0; } uint32_t MsiCloseHandle(MsiTypePrefix *t) { if (t->type == VIEW) { MsiView *view = (MsiView *)t; if (view->fp) fclose(view->fp); } return 0; } uint32_t MsiDatabaseCommit(MsiMainCtx *ctx) { assert(ctx->t.type == MAIN); printf("commit:"); for (int i = 0; i < ctx->nargs; i++) { printf(" '"); for (const char *p = ctx->args[i]; *p; p++) { if (*p == '\'') printf("'\\''"); else putchar(*p); } printf("'"); } printf("\n"); if (ctx->nargs + 1 >= ctx->argsize) { ctx->argsize = ctx->nargs * 5 / 4 + 16; ctx->args = sresize(ctx->args, ctx->argsize, char *); } ctx->args[ctx->nargs++] = NULL; system_argv_array(ctx->args); return 0; }