798 lines
23 KiB
C
798 lines
23 KiB
C
// The MIT License (MIT)
|
|
//
|
|
// Copyright (C) 2026 Scott Duensing
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
|
|
// pkgRead.c -- finding the DVX package and unpacking it (see pkgRead.h)
|
|
|
|
#include "pkgRead.h"
|
|
#include "fileops/fileOps.h"
|
|
#include "miniz.h"
|
|
#include "stb_ds_wrap.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include "dvxMem.h"
|
|
|
|
// Bytes read from a part at a time, and the work one step does
|
|
#define PKG_IN_BUF 16384
|
|
#define PKG_STEP_BYTES 32768
|
|
|
|
// Local header field offsets (after the signature)
|
|
#define LH_FLAGS 6
|
|
#define LH_METHOD 8
|
|
#define LH_TIME 10
|
|
#define LH_DATE 12
|
|
#define LH_CRC 14
|
|
#define LH_PACKED 18
|
|
#define LH_SIZE 22
|
|
#define LH_NAME_LEN 26
|
|
#define LH_EXTRA_LEN 28
|
|
#define ZIP_SIG_SIZE 4
|
|
|
|
// DOS date and time fields
|
|
#define DOS_YEAR_SHIFT 9
|
|
#define DOS_YEAR_MASK 0x7F
|
|
#define DOS_YEAR_BASE 1980
|
|
#define DOS_MONTH_SHIFT 5
|
|
#define DOS_MONTH_MASK 0x0F
|
|
#define DOS_DAY_MASK 0x1F
|
|
#define DOS_HOUR_SHIFT 11
|
|
#define DOS_MINUTE_SHIFT 5
|
|
#define DOS_MINUTE_MASK 0x3F
|
|
#define DOS_SECOND_MASK 0x1F
|
|
#define TM_YEAR_BASE 1900
|
|
|
|
#define PERCENT_FULL 100
|
|
|
|
typedef enum {
|
|
JobHeaderE, // next: a local header or the central directory
|
|
JobDataE, // unpacking a file
|
|
JobDoneE
|
|
} JobStateE;
|
|
|
|
struct PkgJobT {
|
|
PkgSourceT src;
|
|
char destDir[DVX_MAX_PATH];
|
|
PkgAskDiskFnT ask;
|
|
PkgKeepFnT keep;
|
|
void *user;
|
|
FILE *part;
|
|
int32_t disk;
|
|
uint32_t partLeft; // bytes of the part not read yet
|
|
uint32_t zipPos; // ZIP bytes read from all parts
|
|
uint8_t in[PKG_IN_BUF];
|
|
uint32_t inPos;
|
|
uint32_t inLen;
|
|
JobStateE state;
|
|
bool cancel;
|
|
bool failed;
|
|
char msg[PKG_MSG_MAX];
|
|
// the file being unpacked
|
|
char name[PKG_NAME_MAX];
|
|
char outPath[DVX_MAX_PATH];
|
|
FILE *out;
|
|
bool skipping; // kept: its data is read, not written
|
|
uint16_t method;
|
|
uint16_t dosDate;
|
|
uint16_t dosTime;
|
|
uint32_t crcWant;
|
|
uint32_t crcHave;
|
|
uint32_t packedLeft;
|
|
uint32_t size;
|
|
uint32_t sizeDone;
|
|
tinfl_decompressor inflator;
|
|
uint8_t dict[TINFL_LZ_DICT_SIZE];
|
|
uint32_t dictPos;
|
|
// totals
|
|
uint64_t bytesDone; // files finished
|
|
int32_t filesDone;
|
|
char **files;
|
|
char **written;
|
|
char **createdDirs;
|
|
};
|
|
|
|
// ============================================================
|
|
// Prototypes
|
|
// ============================================================
|
|
|
|
static void dirOfPath(const char *path, char *out, int32_t outSize);
|
|
static time_t dosTimeToTime(uint16_t date, uint16_t time);
|
|
static bool ensureDirs(PkgJobT *job, const char *relName, bool lastIsDir);
|
|
static void entryAbort(PkgJobT *job);
|
|
static PkgStepE entryData(PkgJobT *job);
|
|
static bool entryFinish(PkgJobT *job);
|
|
static PkgStepE entryHeader(PkgJobT *job);
|
|
static bool entryOpen(PkgJobT *job);
|
|
static PkgStepE fail(PkgJobT *job, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
|
|
static bool fill(PkgJobT *job);
|
|
static bool openPart(PkgJobT *job, int32_t disk);
|
|
static PkgDiskProblemE otherDisk(const PkgJobT *job, int32_t wanted);
|
|
static bool partPath(const PkgSourceT *src, int32_t disk, char *out, int32_t outSize);
|
|
void pkgFind(const char *exePath, PkgSourceT *src);
|
|
void pkgJobCancel(PkgJobT *job);
|
|
PkgJobT *pkgJobCreate(const PkgSourceT *src, const char *destDir, PkgAskDiskFnT ask, PkgKeepFnT keep, void *user);
|
|
char *const *pkgJobCreatedDirs(const PkgJobT *job);
|
|
char *const *pkgJobFiles(const PkgJobT *job);
|
|
void pkgJobFree(PkgJobT *job);
|
|
const char *pkgJobMessage(const PkgJobT *job);
|
|
void pkgJobProgress(const PkgJobT *job, PkgProgressT *out);
|
|
PkgStepE pkgJobStep(PkgJobT *job);
|
|
char *const *pkgJobWritten(const PkgJobT *job);
|
|
static bool readBytes(PkgJobT *job, uint8_t *buf, uint32_t len);
|
|
static bool writeOut(PkgJobT *job, const uint8_t *data, uint32_t len);
|
|
|
|
|
|
// The folder part of path ("." when it has none).
|
|
static void dirOfPath(const char *path, char *out, int32_t outSize) {
|
|
const char *end = platformPathDirEnd(path);
|
|
|
|
if (!end) {
|
|
snprintf(out, outSize, ".");
|
|
return;
|
|
}
|
|
|
|
snprintf(out, outSize, "%.*s", (int)(end - path), path);
|
|
}
|
|
|
|
|
|
static time_t dosTimeToTime(uint16_t date, uint16_t time) {
|
|
struct tm t;
|
|
|
|
memset(&t, 0, sizeof(t));
|
|
t.tm_year = ((date >> DOS_YEAR_SHIFT) & DOS_YEAR_MASK) + DOS_YEAR_BASE - TM_YEAR_BASE;
|
|
t.tm_mon = ((date >> DOS_MONTH_SHIFT) & DOS_MONTH_MASK) - 1;
|
|
t.tm_mday = date & DOS_DAY_MASK;
|
|
t.tm_hour = time >> DOS_HOUR_SHIFT;
|
|
t.tm_min = (time >> DOS_MINUTE_SHIFT) & DOS_MINUTE_MASK;
|
|
t.tm_sec = (time & DOS_SECOND_MASK) * 2;
|
|
t.tm_isdst = -1;
|
|
return mktime(&t);
|
|
}
|
|
|
|
|
|
// Make every folder of relName that does not exist yet (the last part
|
|
// too when lastIsDir), recording each one made.
|
|
static bool ensureDirs(PkgJobT *job, const char *relName, bool lastIsDir) {
|
|
int32_t len = (int32_t)strlen(relName);
|
|
|
|
for (int32_t i = 1; i <= len; i++) {
|
|
if (relName[i] != PKG_NAME_SEP && !(i == len && lastIsDir)) {
|
|
continue;
|
|
}
|
|
|
|
char rel[PKG_NAME_MAX];
|
|
char full[DVX_MAX_PATH];
|
|
struct stat st;
|
|
|
|
snprintf(rel, sizeof(rel), "%.*s", (int)i, relName);
|
|
|
|
if (!pkgLocalPath(job->destDir, rel, full, sizeof(full))) {
|
|
fail(job, "The path of %s is too long.", rel);
|
|
return false;
|
|
}
|
|
|
|
if (stat(full, &st) == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (platformMkdirRecursive(full) != 0) {
|
|
fail(job, "Setup could not create the folder %s.", full);
|
|
return false;
|
|
}
|
|
|
|
arrput(job->createdDirs, strdup(full));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Close and remove a half-written file.
|
|
static void entryAbort(PkgJobT *job) {
|
|
if (!job->out) {
|
|
return;
|
|
}
|
|
|
|
fclose(job->out);
|
|
job->out = NULL;
|
|
remove(job->outPath);
|
|
|
|
if (arrlen(job->written) > 0) {
|
|
free(arrpop(job->written));
|
|
}
|
|
}
|
|
|
|
|
|
// Unpack (or skip) up to PKG_STEP_BYTES of the current file.
|
|
static PkgStepE entryData(PkgJobT *job) {
|
|
uint32_t work = 0;
|
|
|
|
while (work < PKG_STEP_BYTES) {
|
|
if (job->packedLeft > 0 && !fill(job)) {
|
|
return job->cancel ? PkgStepCancelledE : PkgStepErrorE;
|
|
}
|
|
|
|
uint32_t avail = job->inLen - job->inPos;
|
|
|
|
if (avail > job->packedLeft) {
|
|
avail = job->packedLeft;
|
|
}
|
|
|
|
bool done;
|
|
|
|
if (job->skipping || job->method == PKG_ZIP_STORED) {
|
|
if (!job->skipping && !writeOut(job, job->in + job->inPos, avail)) {
|
|
return PkgStepErrorE;
|
|
}
|
|
|
|
job->inPos += avail;
|
|
job->packedLeft -= avail;
|
|
work += avail;
|
|
done = job->packedLeft == 0;
|
|
} else {
|
|
size_t inBytes = avail;
|
|
size_t outBytes = TINFL_LZ_DICT_SIZE - job->dictPos;
|
|
int flags = job->packedLeft > avail ? TINFL_FLAG_HAS_MORE_INPUT : 0;
|
|
|
|
tinfl_status status = tinfl_decompress(&job->inflator, job->in + job->inPos, &inBytes, job->dict, job->dict + job->dictPos, &outBytes, (mz_uint32)flags);
|
|
|
|
job->inPos += (uint32_t)inBytes;
|
|
job->packedLeft -= (uint32_t)inBytes;
|
|
work += (uint32_t)(inBytes + outBytes);
|
|
|
|
if (outBytes > 0 && !writeOut(job, job->dict + job->dictPos, (uint32_t)outBytes)) {
|
|
return PkgStepErrorE;
|
|
}
|
|
|
|
job->dictPos = (job->dictPos + (uint32_t)outBytes) & (TINFL_LZ_DICT_SIZE - 1);
|
|
|
|
if (status < TINFL_STATUS_DONE || (status == TINFL_STATUS_NEEDS_MORE_INPUT && job->packedLeft == 0)) {
|
|
entryAbort(job);
|
|
return fail(job, "The file %s in the DVX package is damaged (disk %d).", job->name, (int)job->disk);
|
|
}
|
|
|
|
done = status == TINFL_STATUS_DONE;
|
|
}
|
|
|
|
if (done) {
|
|
return entryFinish(job) ? PkgStepMoreE : PkgStepErrorE;
|
|
}
|
|
}
|
|
|
|
return PkgStepMoreE;
|
|
}
|
|
|
|
|
|
// The file's data has all been read: check it, close it, date it.
|
|
static bool entryFinish(PkgJobT *job) {
|
|
if (!job->skipping) {
|
|
if (job->packedLeft != 0 || job->sizeDone != job->size || job->crcHave != job->crcWant) {
|
|
entryAbort(job);
|
|
fail(job, "The file %s in the DVX package is damaged (disk %d).", job->name, (int)job->disk);
|
|
return false;
|
|
}
|
|
|
|
FILE *f = job->out;
|
|
|
|
job->out = NULL;
|
|
|
|
if (fclose(f) != 0) {
|
|
remove(job->outPath);
|
|
fail(job, "Setup could not write %s. The disk may be full.", job->outPath);
|
|
return false;
|
|
}
|
|
|
|
fileOpsSetTime(job->outPath, dosTimeToTime(job->dosDate, job->dosTime));
|
|
}
|
|
|
|
job->bytesDone += job->size;
|
|
job->filesDone++;
|
|
job->state = JobHeaderE;
|
|
return true;
|
|
}
|
|
|
|
|
|
// The next local header (or the central directory: the end).
|
|
static PkgStepE entryHeader(PkgJobT *job) {
|
|
uint8_t hdr[PKG_ZIP_LOCAL_SIZE];
|
|
|
|
if (!readBytes(job, hdr, ZIP_SIG_SIZE)) {
|
|
return job->cancel ? PkgStepCancelledE : PkgStepErrorE;
|
|
}
|
|
|
|
uint32_t sig = pkgGet32(hdr);
|
|
|
|
if (sig == PKG_ZIP_CENTRAL_SIG) {
|
|
if (job->filesDone != (int32_t)job->src.info.fileCount) {
|
|
return fail(job, "The DVX package is incomplete: %d of %d files.", (int)job->filesDone, (int)job->src.info.fileCount);
|
|
}
|
|
|
|
job->state = JobDoneE;
|
|
return PkgStepDoneE;
|
|
}
|
|
|
|
if (sig != PKG_ZIP_LOCAL_SIG) {
|
|
return fail(job, "The DVX package is damaged (disk %d).", (int)job->disk);
|
|
}
|
|
|
|
if (!readBytes(job, hdr + ZIP_SIG_SIZE, PKG_ZIP_LOCAL_SIZE - ZIP_SIG_SIZE)) {
|
|
return job->cancel ? PkgStepCancelledE : PkgStepErrorE;
|
|
}
|
|
|
|
uint16_t flags = pkgGet16(hdr + LH_FLAGS);
|
|
uint16_t nameLen = pkgGet16(hdr + LH_NAME_LEN);
|
|
uint16_t extraLen = pkgGet16(hdr + LH_EXTRA_LEN);
|
|
|
|
job->method = pkgGet16(hdr + LH_METHOD);
|
|
job->dosTime = pkgGet16(hdr + LH_TIME);
|
|
job->dosDate = pkgGet16(hdr + LH_DATE);
|
|
job->crcWant = pkgGet32(hdr + LH_CRC);
|
|
job->packedLeft = pkgGet32(hdr + LH_PACKED);
|
|
job->size = pkgGet32(hdr + LH_SIZE);
|
|
|
|
if ((flags & (PKG_ZIP_FLAG_DESC | PKG_ZIP_FLAG_CRYPT)) || (job->method != PKG_ZIP_STORED && job->method != PKG_ZIP_DEFLATED) || nameLen == 0 || nameLen >= PKG_NAME_MAX) {
|
|
return fail(job, "The DVX package uses a form Setup cannot read (disk %d).", (int)job->disk);
|
|
}
|
|
|
|
if (!readBytes(job, (uint8_t *)job->name, nameLen)) {
|
|
return job->cancel ? PkgStepCancelledE : PkgStepErrorE;
|
|
}
|
|
|
|
job->name[nameLen] = '\0';
|
|
|
|
for (uint16_t left = extraLen; left > 0; ) {
|
|
uint8_t skip[PKG_ZIP_LOCAL_SIZE];
|
|
uint16_t n = left < sizeof(skip) ? left : (uint16_t)sizeof(skip);
|
|
|
|
if (!readBytes(job, skip, n)) {
|
|
return job->cancel ? PkgStepCancelledE : PkgStepErrorE;
|
|
}
|
|
|
|
left -= n;
|
|
}
|
|
|
|
bool isDir = job->name[nameLen - 1] == PKG_NAME_SEP;
|
|
|
|
if (isDir) {
|
|
job->name[nameLen - 1] = '\0';
|
|
}
|
|
|
|
if (!pkgNameIsValid(job->name)) {
|
|
return fail(job, "The DVX package holds a name Setup does not accept: %s", job->name);
|
|
}
|
|
|
|
if (isDir) {
|
|
if (!ensureDirs(job, job->name, true)) {
|
|
return PkgStepErrorE;
|
|
}
|
|
|
|
char *listed = (char *)malloc(nameLen + 1);
|
|
|
|
if (listed) {
|
|
snprintf(listed, nameLen + 1, "%s%c", job->name, PKG_NAME_SEP);
|
|
arrput(job->files, listed);
|
|
}
|
|
|
|
return PkgStepMoreE;
|
|
}
|
|
|
|
return entryOpen(job) ? PkgStepMoreE : PkgStepErrorE;
|
|
}
|
|
|
|
|
|
// Set up the file of the header just read: kept, or opened for writing.
|
|
static bool entryOpen(PkgJobT *job) {
|
|
struct stat st;
|
|
|
|
if (!ensureDirs(job, job->name, false)) {
|
|
return false;
|
|
}
|
|
|
|
if (!pkgLocalPath(job->destDir, job->name, job->outPath, sizeof(job->outPath))) {
|
|
fail(job, "The path of %s is too long.", job->name);
|
|
return false;
|
|
}
|
|
|
|
arrput(job->files, strdup(job->name));
|
|
job->skipping = stat(job->outPath, &st) == 0 && job->keep && job->keep(job->user, job->name);
|
|
job->sizeDone = 0;
|
|
job->crcHave = (uint32_t)MZ_CRC32_INIT;
|
|
job->dictPos = 0;
|
|
job->state = JobDataE;
|
|
tinfl_init(&job->inflator);
|
|
|
|
if (job->skipping) {
|
|
return true;
|
|
}
|
|
|
|
job->out = fopen(job->outPath, "wb");
|
|
|
|
// A read-only copy (an older install copied from a CD) is replaced.
|
|
if (!job->out && fileOpsSetAttr(job->outPath, 0)) {
|
|
job->out = fopen(job->outPath, "wb");
|
|
}
|
|
|
|
if (!job->out) {
|
|
fail(job, "Setup could not write %s. The disk may be full or write-protected.", job->outPath);
|
|
return false;
|
|
}
|
|
|
|
arrput(job->written, strdup(job->outPath));
|
|
return true;
|
|
}
|
|
|
|
|
|
static PkgStepE fail(PkgJobT *job, const char *fmt, ...) {
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(job->msg, sizeof(job->msg), fmt, ap);
|
|
va_end(ap);
|
|
job->failed = true;
|
|
return PkgStepErrorE;
|
|
}
|
|
|
|
|
|
// Make sure the input buffer has a byte, opening the next part when this
|
|
// one is used up. False on an error (message set) or a cancel.
|
|
static bool fill(PkgJobT *job) {
|
|
if (job->inPos < job->inLen) {
|
|
return true;
|
|
}
|
|
|
|
while (job->partLeft == 0) {
|
|
if (job->disk >= job->src.info.diskCount) {
|
|
fail(job, "The DVX package ends too early (disk %d).", (int)job->disk);
|
|
return false;
|
|
}
|
|
|
|
if (!openPart(job, job->disk + 1)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
uint32_t n = job->partLeft < PKG_IN_BUF ? job->partLeft : PKG_IN_BUF;
|
|
|
|
if (fread(job->in, 1, n, job->part) != n) {
|
|
fail(job, "Setup could not read the DVX package on disk %d.", (int)job->disk);
|
|
return false;
|
|
}
|
|
|
|
job->inPos = 0;
|
|
job->inLen = n;
|
|
job->partLeft -= n;
|
|
job->zipPos += n;
|
|
return true;
|
|
}
|
|
|
|
|
|
// Open disk's part (asking for the disk until it is the right one) and
|
|
// position it at its first byte. False on a cancel or an error.
|
|
static bool openPart(PkgJobT *job, int32_t disk) {
|
|
char path[DVX_MAX_PATH];
|
|
|
|
if (job->part) {
|
|
fclose(job->part);
|
|
job->part = NULL;
|
|
}
|
|
|
|
if (job->src.found == PkgFoundAppendedE) {
|
|
snprintf(path, sizeof(path), "%s", job->src.path);
|
|
} else if (!partPath(&job->src, disk, path, sizeof(path))) {
|
|
fail(job, "The folder %s is too long.", job->src.partDir);
|
|
return false;
|
|
}
|
|
|
|
for (;;) {
|
|
PkgHeaderT h;
|
|
uint32_t start = 0;
|
|
PkgDiskProblemE problem = PkgDiskMissingE;
|
|
FILE *f = fopen(path, "rb");
|
|
|
|
if (f) {
|
|
if (!pkgHeaderRead(f, &h, &start)) {
|
|
problem = PkgDiskDamagedE;
|
|
} else if (h.setId != job->src.info.setId) {
|
|
problem = PkgDiskOtherSetE;
|
|
} else if (h.disk != disk || h.partOffset != job->zipPos) {
|
|
problem = PkgDiskWrongE;
|
|
} else if (fseek(f, (long)start, SEEK_SET) != 0) {
|
|
problem = PkgDiskDamagedE;
|
|
} else {
|
|
job->part = f;
|
|
job->disk = disk;
|
|
job->partLeft = h.partLength;
|
|
return true;
|
|
}
|
|
|
|
fclose(f);
|
|
} else {
|
|
problem = otherDisk(job, disk);
|
|
}
|
|
|
|
if (job->src.found == PkgFoundAppendedE || !job->ask) {
|
|
fail(job, "Setup could not read the DVX package in %s.", path);
|
|
return false;
|
|
}
|
|
|
|
if (!job->ask(job->user, disk, problem)) {
|
|
job->cancel = true;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// The wanted part is not there: is another disk's part (of this set or
|
|
// another one) in the drive instead? The disk before the wanted one is
|
|
// the normal case (not swapped yet): just missing.
|
|
static PkgDiskProblemE otherDisk(const PkgJobT *job, int32_t wanted) {
|
|
char **names = dvxReadDir(job->src.partDir);
|
|
PkgDiskProblemE problem = PkgDiskMissingE;
|
|
|
|
for (int32_t i = 0; i < (int32_t)arrlen(names) && problem == PkgDiskMissingE; i++) {
|
|
char upper[PKG_PART_NAME_MAX];
|
|
char path[DVX_MAX_PATH];
|
|
int partNo = 0; // sscanf's %d wants an int
|
|
char end = '\0';
|
|
uint32_t start = 0;
|
|
PkgHeaderT h;
|
|
|
|
snprintf(upper, sizeof(upper), "%s", names[i]);
|
|
|
|
for (char *p = upper; *p; p++) {
|
|
*p = (char)toupper((unsigned char)*p);
|
|
}
|
|
|
|
if (strlen(names[i]) >= sizeof(upper) || sscanf(upper, PKG_PART_SCAN "%c", &partNo, &end) != 1 || !fileOpsPathJoin(path, sizeof(path), job->src.partDir, names[i])) {
|
|
continue;
|
|
}
|
|
|
|
FILE *f = fopen(path, "rb");
|
|
|
|
if (!f) {
|
|
continue;
|
|
}
|
|
|
|
if (!pkgHeaderRead(f, &h, &start)) {
|
|
problem = PkgDiskDamagedE;
|
|
} else if (h.setId != job->src.info.setId) {
|
|
problem = PkgDiskOtherSetE;
|
|
} else if (h.disk != wanted - 1) {
|
|
problem = PkgDiskWrongE;
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
dvxReadDirFree(names);
|
|
return problem;
|
|
}
|
|
|
|
|
|
static bool partPath(const PkgSourceT *src, int32_t disk, char *out, int32_t outSize) {
|
|
char name[PKG_PART_NAME_MAX];
|
|
|
|
return pkgPartName(disk, name, sizeof(name)) && fileOpsPathJoin(out, outSize, src->partDir, name);
|
|
}
|
|
|
|
|
|
void pkgFind(const char *exePath, PkgSourceT *src) {
|
|
PkgHeaderT h;
|
|
uint32_t start = 0;
|
|
|
|
memset(&h, 0, sizeof(h));
|
|
memset(src, 0, sizeof(*src));
|
|
src->found = PkgFoundNoneE;
|
|
dirOfPath(exePath, src->partDir, sizeof(src->partDir));
|
|
|
|
FILE *f = fopen(exePath, "rb");
|
|
|
|
if (f) {
|
|
bool appended = pkgHeaderRead(f, &h, &start) && h.diskCount == 1 && h.partOffset == 0 && h.partLength == h.zipSize;
|
|
|
|
fclose(f);
|
|
|
|
if (appended) {
|
|
src->found = PkgFoundAppendedE;
|
|
src->info = h;
|
|
snprintf(src->path, sizeof(src->path), "%s", exePath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!partPath(src, 1, src->path, sizeof(src->path))) {
|
|
return;
|
|
}
|
|
|
|
f = fopen(src->path, "rb");
|
|
|
|
if (!f) {
|
|
return;
|
|
}
|
|
|
|
src->found = pkgHeaderRead(f, &h, &start) && h.disk == 1 && h.partOffset == 0 ? PkgFoundPartsE : PkgFoundDamagedE;
|
|
src->info = h;
|
|
fclose(f);
|
|
}
|
|
|
|
|
|
void pkgJobCancel(PkgJobT *job) {
|
|
job->cancel = true;
|
|
}
|
|
|
|
|
|
PkgJobT *pkgJobCreate(const PkgSourceT *src, const char *destDir, PkgAskDiskFnT ask, PkgKeepFnT keep, void *user) {
|
|
PkgJobT *job = (PkgJobT *)calloc(1, sizeof(PkgJobT));
|
|
|
|
if (!job) {
|
|
return NULL;
|
|
}
|
|
|
|
job->src = *src;
|
|
job->ask = ask;
|
|
job->keep = keep;
|
|
job->user = user;
|
|
job->state = JobHeaderE;
|
|
snprintf(job->destDir, sizeof(job->destDir), "%s", destDir);
|
|
|
|
// Disk 1 is the one pkgFind looked at; asking again is only for the
|
|
// disks after it.
|
|
if (!openPart(job, 1)) {
|
|
job->state = JobDoneE;
|
|
}
|
|
|
|
return job;
|
|
}
|
|
|
|
|
|
char *const *pkgJobCreatedDirs(const PkgJobT *job) {
|
|
return job->createdDirs;
|
|
}
|
|
|
|
|
|
char *const *pkgJobFiles(const PkgJobT *job) {
|
|
return job->files;
|
|
}
|
|
|
|
|
|
void pkgJobFree(PkgJobT *job) {
|
|
if (!job) {
|
|
return;
|
|
}
|
|
|
|
entryAbort(job);
|
|
|
|
if (job->part) {
|
|
fclose(job->part);
|
|
}
|
|
|
|
for (int32_t i = 0; i < (int32_t)arrlen(job->files); i++) {
|
|
free(job->files[i]);
|
|
}
|
|
|
|
for (int32_t i = 0; i < (int32_t)arrlen(job->written); i++) {
|
|
free(job->written[i]);
|
|
}
|
|
|
|
for (int32_t i = 0; i < (int32_t)arrlen(job->createdDirs); i++) {
|
|
free(job->createdDirs[i]);
|
|
}
|
|
|
|
arrfree(job->files);
|
|
arrfree(job->written);
|
|
arrfree(job->createdDirs);
|
|
free(job);
|
|
}
|
|
|
|
|
|
const char *pkgJobMessage(const PkgJobT *job) {
|
|
return job->msg;
|
|
}
|
|
|
|
|
|
void pkgJobProgress(const PkgJobT *job, PkgProgressT *out) {
|
|
uint64_t done = job->bytesDone + (job->state == JobDataE ? job->sizeDone : 0);
|
|
|
|
memset(out, 0, sizeof(*out));
|
|
out->bytesTotal = job->src.info.unpackedBytes;
|
|
out->bytesDone = done > out->bytesTotal ? out->bytesTotal : done;
|
|
out->filesDone = job->filesDone;
|
|
out->fileCount = (int32_t)job->src.info.fileCount;
|
|
out->percent = out->bytesTotal ? (int32_t)(out->bytesDone * PERCENT_FULL / out->bytesTotal) : PERCENT_FULL;
|
|
out->disk = job->disk;
|
|
out->current = job->state == JobDataE ? job->name : "";
|
|
}
|
|
|
|
|
|
PkgStepE pkgJobStep(PkgJobT *job) {
|
|
if (job->failed) {
|
|
return PkgStepErrorE;
|
|
}
|
|
|
|
if (job->cancel) {
|
|
entryAbort(job);
|
|
return PkgStepCancelledE;
|
|
}
|
|
|
|
switch (job->state) {
|
|
case JobHeaderE:
|
|
return entryHeader(job);
|
|
|
|
case JobDataE:
|
|
return entryData(job);
|
|
|
|
default:
|
|
return PkgStepDoneE;
|
|
}
|
|
}
|
|
|
|
|
|
char *const *pkgJobWritten(const PkgJobT *job) {
|
|
return job->written;
|
|
}
|
|
|
|
|
|
// Exactly len bytes of the stream, across parts.
|
|
static bool readBytes(PkgJobT *job, uint8_t *buf, uint32_t len) {
|
|
while (len > 0) {
|
|
if (!fill(job)) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t n = job->inLen - job->inPos;
|
|
|
|
if (n > len) {
|
|
n = len;
|
|
}
|
|
|
|
memcpy(buf, job->in + job->inPos, n);
|
|
job->inPos += n;
|
|
buf += n;
|
|
len -= n;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool writeOut(PkgJobT *job, const uint8_t *data, uint32_t len) {
|
|
if (job->sizeDone + len > job->size) {
|
|
entryAbort(job);
|
|
fail(job, "The file %s in the DVX package is damaged (disk %d).", job->name, (int)job->disk);
|
|
return false;
|
|
}
|
|
|
|
if (fwrite(data, 1, len, job->out) != len) {
|
|
entryAbort(job);
|
|
fail(job, "Setup could not write %s. The disk may be full.", job->outPath);
|
|
return false;
|
|
}
|
|
|
|
job->crcHave = (uint32_t)mz_crc32(job->crcHave, data, len);
|
|
job->sizeDone += len;
|
|
return true;
|
|
}
|