DVX_GUI/src/apps/kpunch/dvxbasic/basBuild.c
2026-09-30 09:57:59 -05:00

738 lines
25 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.
// basBuild.c -- shared "compile a project into an .app" back end
//
// See basBuild.h for the public contract. This pipeline used to live
// inline in two places: ideMain.c (the IDE's Make Executable path) and
// bascomp.c (the standalone command-line compiler). Both implementations
// were essentially identical, so they are consolidated here.
//
// Pipeline:
// 1. Strip comments from every .frm text.
// 2. Serialize the module; release builds deserialize a private copy,
// strip its debug info, obfuscate form/control names (rewriting the
// .frm texts too), compact the bytecode and re-serialize.
// 3. Debug builds serialize the debug info.
// 4. Extract the STUB resource from selfPath and write it as outPath.
// 5. Append the resource set in canonical order:
// BAS_RES_NAME (always, falls back to BAS_DEFAULT_APP_NAME)
// BAS_RES_AUTHOR / PUBLISHER / VERSION / COPYRIGHT / DESCRIPTION
// BAS_RES_ICON32 (project icon, else the NOICON fallback)
// BAS_RES_HELPFILE (basename only)
// BAS_RES_MODULE, BAS_RES_DEBUG (debug builds)
// FORM0, FORM1, ... cut at the end of the outer Begin Form block
// so the .frm's BASIC code section (already compiled into
// MODULE) never ships as a resource
// 6. Copy the help file next to outPath.
#include "basBuild.h"
#include "basRes.h"
#include "compiler/compact.h"
#include "compiler/obfuscate.h"
#include "compiler/strip.h"
#include "runtime/serialize.h"
#include "dvxRes.h"
#include "dvxTypes.h"
#include "dvxPlat.h"
#include "stb_ds_wrap.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Slack added to a .frm buffer for basStripFrmComments output.
#define BAS_BUILD_STRIP_MARGIN 16
// Generic resource-append failure (disk full, I/O error).
#define BAS_BUILD_ERR_WRITE_RES "Failed writing resources to output file."
// Longest progress line handed to the log callback.
#define BAS_BUILD_LOG_BUF 128
// Separator used when joining project-relative paths. '/' is accepted by
// DJGPP as well as every host OS bascomp runs on; DVX_PATH_SEP is not.
#define BAS_BUILD_SEP "/"
// Growth unit of the project source buffer.
#define BAS_BUILD_SOURCE_CHUNK 8192
// Room for a BEGINFORM "name" / BEGINCLASS "name" line.
#define BAS_BUILD_WRAP_SLACK (BAS_MAX_IDENT + 32)
// Longest [Modules]/[Forms] key, "File" plus a decimal index.
#define BAS_BUILD_KEY_LEN 32
// emitIcon's error text when it names the unreadable icon path (the
// other build errors are string literals)
static char sIconError[BAS_BUILD_ERROR_LEN];
// basBuildConcatProject passes, in source order.
typedef enum {
BAS_BUILD_PASS_MODULES,
BAS_BUILD_PASS_CLASSES,
BAS_BUILD_PASS_FORMS,
BAS_BUILD_PASS_COUNT
} BasBuildPassE;
// The directives around a form's and a class module's code.
static const char *const sWrapBegin[] = {
[BAS_BUILD_WRAP_FORM] = "BEGINFORM",
[BAS_BUILD_WRAP_CLASS] = "BEGINCLASS"
};
static const char *const sWrapEnd[] = {
[BAS_BUILD_WRAP_FORM] = "ENDFORM\n",
[BAS_BUILD_WRAP_CLASS] = "ENDCLASS\n"
};
// Function prototypes (alphabetical)
static bool appendSource(BasProjectSourceT *ps, const char *text, int32_t len);
static int32_t appendText(const char *path, const char *name, const char *value);
const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec);
bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, int32_t *textLine, int32_t *textLines);
bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out);
void basBuildDirOf(const char *path, char *out, int32_t outSize);
void basBuildFreeProject(BasProjectSourceT *out);
bool basBuildMapLine(const BasSourceMapT *map, int32_t line, const char **path, int32_t *fileLine);
bool basBuildOpenProject(const char *dbpPath, PrefsHandleT **prefs, char *projectDir, int32_t projectDirSize);
static void buildLog(const BasBuildSpecT *spec, const char *fmt, ...);
static const char *copyHelpFile(const char *outPath, const BasBuildSpecT *spec);
static int32_t countLines(const char *text, int32_t len);
static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec);
static const char *emitResources(const char *outPath, const BasBuildSpecT *spec, const uint8_t *modData, int32_t modLen, const uint8_t *dbgData, int32_t dbgLen, uint8_t **frmData, const int32_t *frmLens, const BasObfFrmT *obfFrms, int32_t frmCount);
static void freeFrmWork(uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms);
static const char *prepareModule(const BasBuildSpecT *spec, uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms, int32_t frmCount, uint8_t **outMod, int32_t *outModLen, uint8_t **outDbg, int32_t *outDbgLen);
static const char *writeStub(const char *outPath, const BasBuildSpecT *spec);
// Appends text to the project source and makes sure it ends on a newline
// so the next file starts on a fresh line.
static bool appendSource(BasProjectSourceT *ps, const char *text, int32_t len) {
int32_t need = ps->sourceLen + len + 2;
if (need > ps->sourceCap) {
int32_t newCap = ps->sourceCap ? ps->sourceCap : BAS_BUILD_SOURCE_CHUNK;
while (newCap < need) {
newCap *= 2;
}
char *grown = (char *)realloc(ps->source, (size_t)newCap);
if (!grown) {
snprintf(ps->error, sizeof(ps->error), "Out of memory.");
return false;
}
ps->source = grown;
ps->sourceCap = newCap;
}
memcpy(ps->source + ps->sourceLen, text, (size_t)len);
ps->sourceLen += len;
for (int32_t i = 0; i < len; i++) {
if (text[i] == '\n') {
ps->lineCount++;
}
}
if (ps->sourceLen > 0 && ps->source[ps->sourceLen - 1] != '\n') {
ps->source[ps->sourceLen++] = '\n';
ps->lineCount++;
}
ps->source[ps->sourceLen] = '\0';
return true;
}
static int32_t appendText(const char *path, const char *name, const char *value) {
if (!value || !value[0]) {
return 0;
}
return dvxResAppend(path, name, DVX_RES_TEXT, value, (uint32_t)strlen(value) + 1);
}
const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec) {
if (!outPath || !spec || !spec->module || !spec->selfPath) {
return "Invalid build request.";
}
// Strip comments from every .frm text. Comments are source-only and
// must not ship in the embedded resource for either build mode.
uint8_t **frmData = NULL; // stb_ds: stripped form text
int32_t *frmLens = NULL; // stb_ds: length of stripped form text
BasObfFrmT *obfFrms = NULL; // stb_ds: obfuscated variants (release)
for (int32_t i = 0; i < spec->frmCount; i++) {
const char *src = spec->frmSources[i];
if (!src) {
continue;
}
int32_t srcLen = (int32_t)strlen(src);
int32_t stripCap = srcLen + BAS_BUILD_STRIP_MARGIN;
uint8_t *stripped = (uint8_t *)malloc(stripCap);
// A form silently missing from the app would only surface as a
// LoadForm failure at runtime, so a failed allocation fails the
// build.
if (!stripped) {
freeFrmWork(frmData, frmLens, obfFrms);
return "Out of memory.";
}
int32_t strippedLen = basStripFrmComments(src, srcLen, stripped, stripCap);
BasObfFrmT empty = { NULL, 0 };
arrput(frmData, stripped);
arrput(frmLens, strippedLen);
arrput(obfFrms, empty);
}
int32_t frmCount = (int32_t)arrlen(frmData);
uint8_t *modData = NULL;
int32_t modLen = 0;
uint8_t *dbgData = NULL;
int32_t dbgLen = 0;
const char *failure = prepareModule(spec, frmData, frmLens, obfFrms, frmCount, &modData, &modLen, &dbgData, &dbgLen);
if (!failure) {
failure = writeStub(outPath, spec);
}
if (!failure) {
failure = emitResources(outPath, spec, modData, modLen, dbgData, dbgLen, frmData, frmLens, obfFrms, frmCount);
}
if (!failure) {
failure = copyHelpFile(outPath, spec);
}
free(modData);
free(dbgData);
freeFrmWork(frmData, frmLens, obfFrms);
return failure;
}
bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, int32_t *textLine, int32_t *textLines) {
if (wrap != BAS_BUILD_WRAP_NONE) {
char line[BAS_BUILD_WRAP_SLACK];
int32_t n = snprintf(line, sizeof(line), "%s \"%s\"\n", sWrapBegin[wrap], name);
if (n < 0 || n >= (int32_t)sizeof(line)) {
snprintf(ps->error, sizeof(ps->error), "Name too long: %s", name);
return false;
}
if (!appendSource(ps, line, n)) {
return false;
}
}
int32_t startLine = ps->lineCount + 1;
if (!appendSource(ps, text, len)) {
return false;
}
if (textLine) {
*textLine = startLine;
}
if (textLines) {
*textLines = ps->lineCount + 1 - startLine;
}
if (wrap != BAS_BUILD_WRAP_NONE) {
return appendSource(ps, sWrapEnd[wrap], (int32_t)strlen(sWrapEnd[wrap]));
}
return true;
}
bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out) {
static const char *const sections[BAS_BUILD_PASS_COUNT] = {
[BAS_BUILD_PASS_MODULES] = BAS_INI_SECTION_MODULES,
[BAS_BUILD_PASS_CLASSES] = BAS_INI_SECTION_CLASSES,
[BAS_BUILD_PASS_FORMS] = BAS_INI_SECTION_FORMS
};
memset(out, 0, sizeof(*out));
out->optionExplicit = prefsGetBool(prefs, BAS_INI_SECTION_SETTINGS, BAS_INI_KEY_OPTIONEXPLICIT, false);
// .bas modules first, then .cls class modules, then .frm code sections.
for (int32_t pass = 0; pass < BAS_BUILD_PASS_COUNT; pass++) {
bool isForm = (pass == BAS_BUILD_PASS_FORMS);
bool isClass = (pass == BAS_BUILD_PASS_CLASSES);
for (int32_t i = 0; ; i++) {
char key[BAS_BUILD_KEY_LEN];
snprintf(key, sizeof(key), "File%d", (int)i);
const char *val = prefsGetString(prefs, sections[pass], key, NULL);
if (!val) {
break;
}
BasSourceMapT entry;
memset(&entry, 0, sizeof(entry));
snprintf(entry.path, sizeof(entry.path), "%s%s%s", projectDir, BAS_BUILD_SEP, val);
int32_t srcLen = 0;
char *src = platformReadFile(entry.path, &srcLen);
if (!src) {
snprintf(out->error, sizeof(out->error), "Cannot read %s", entry.path);
return false;
}
const char *code = src;
char formName[BAS_MAX_IDENT] = "";
BasBuildWrapE wrap = BAS_BUILD_WRAP_NONE;
entry.firstFileLine = 1;
if (isClass) {
// The class code follows the .cls header
if (!basExtractClassName(src, srcLen, val, formName, BAS_MAX_IDENT)) {
snprintf(out->error, sizeof(out->error), "No class name for %s", entry.path);
free(src);
return false;
}
int32_t codeOff = basClassCodePos(src, srcLen);
code = src + codeOff;
entry.firstFileLine = countLines(src, codeOff) + 1;
wrap = BAS_BUILD_WRAP_CLASS;
}
if (isForm) {
// A .frm without a Begin Form line would otherwise compile
// its code unwrapped, as if it were a module.
if (!basExtractFormName(src, formName, BAS_MAX_IDENT)) {
snprintf(out->error, sizeof(out->error), "No Begin Form line in %s", entry.path);
free(src);
return false;
}
// The BASIC code section follows the outer Begin Form block.
int32_t codeOff = basFindFormEndPos(src, srcLen);
code = src + codeOff;
entry.firstFileLine = countLines(src, codeOff) + 1;
wrap = BAS_BUILD_WRAP_FORM;
}
if (!basBuildAppendFile(out, code, (int32_t)strlen(code), wrap, formName, &entry.startLine, &entry.lineCount)) {
free(src);
return false;
}
arrput(out->map, entry);
if (isForm) {
char *frmPath = (char *)malloc(strlen(entry.path) + 1);
if (!frmPath) {
snprintf(out->error, sizeof(out->error), "Out of memory.");
free(src);
return false;
}
strcpy(frmPath, entry.path);
arrput(out->frmPaths, frmPath);
arrput(out->frmTexts, src);
} else {
free(src);
}
out->fileCount++;
}
}
if (out->fileCount == 0) {
snprintf(out->error, sizeof(out->error), "Project has no source files.");
return false;
}
return true;
}
void basBuildDirOf(const char *path, char *out, int32_t outSize) {
snprintf(out, outSize, "%s", path);
char *sep = platformPathDirEnd(out);
if (sep) {
*sep = '\0';
} else {
snprintf(out, outSize, ".");
}
}
void basBuildFreeProject(BasProjectSourceT *out) {
for (int32_t i = 0; i < (int32_t)arrlen(out->frmPaths); i++) {
free(out->frmPaths[i]);
}
for (int32_t i = 0; i < (int32_t)arrlen(out->frmTexts); i++) {
free(out->frmTexts[i]);
}
arrfree(out->frmPaths);
arrfree(out->frmTexts);
arrfree(out->map);
free(out->source);
memset(out, 0, sizeof(*out));
}
bool basBuildMapLine(const BasSourceMapT *map, int32_t line, const char **path, int32_t *fileLine) {
for (int32_t i = 0; i < (int32_t)arrlen(map); i++) {
const BasSourceMapT *m = &map[i];
if (line >= m->startLine && line < m->startLine + m->lineCount) {
*path = m->path;
*fileLine = m->firstFileLine + (line - m->startLine);
return true;
}
}
return false;
}
bool basBuildOpenProject(const char *dbpPath, PrefsHandleT **prefs, char *projectDir, int32_t projectDirSize) {
// prefsLoad treats a missing file as an empty INI, so probe it first
// or a bad path would surface as a confusing "no source files" error.
FILE *probe = fopen(dbpPath, "rb");
if (!probe) {
return false;
}
fclose(probe);
*prefs = prefsLoad(dbpPath);
if (!*prefs) {
return false;
}
basBuildDirOf(dbpPath, projectDir, projectDirSize);
return true;
}
static void buildLog(const BasBuildSpecT *spec, const char *fmt, ...) {
if (!spec->log) {
return;
}
char msg[BAS_BUILD_LOG_BUF];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
spec->log(msg);
}
// Copy the project's help file next to the output app; the HELPFILE
// resource itself names only the basename, which the stub resolves
// relative to the app.
static const char *copyHelpFile(const char *outPath, const BasBuildSpecT *spec) {
if (!spec->helpFile || !spec->helpFile[0]) {
return NULL;
}
char helpSrc[DVX_MAX_PATH];
snprintf(helpSrc, sizeof(helpSrc), "%s" BAS_BUILD_SEP "%s", spec->projectDir, spec->helpFile);
char outDir[DVX_MAX_PATH];
basBuildDirOf(outPath, outDir, sizeof(outDir));
char helpDst[DVX_MAX_PATH];
snprintf(helpDst, sizeof(helpDst), "%s" BAS_BUILD_SEP "%s", outDir, platformPathBaseName(spec->helpFile));
if (!platformCopyFile(helpSrc, helpDst)) {
return "Failed copying help file to output directory.";
}
return NULL;
}
// Number of newlines in text[0..len).
static int32_t countLines(const char *text, int32_t len) {
int32_t n = 0;
for (int32_t i = 0; i < len; i++) {
if (text[i] == '\n') {
n++;
}
}
return n;
}
// Embed the project icon, or the NOICON fallback carried by the running
// executable when the project names none. Returns NULL on success, else
// an error message (a named icon that cannot be read names the path).
static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec) {
char iconPath[DVX_MAX_PATH];
void *iconData = NULL;
int32_t iconLen = 0;
if (spec->iconPath && spec->iconPath[0]) {
snprintf(iconPath, sizeof(iconPath), "%s" BAS_BUILD_SEP "%s", spec->projectDir, spec->iconPath);
iconData = platformReadFile(iconPath, &iconLen);
if (!iconData) {
// A named icon that cannot be read is a build failure --
// shipping the app without its ICON32 resource would never
// tell anyone.
snprintf(sIconError, sizeof(sIconError), "Cannot read icon file %s", iconPath);
return sIconError;
}
} else {
DvxResHandleT *selfRes = dvxResOpen(spec->selfPath);
if (selfRes) {
uint32_t size = 0;
iconData = dvxResRead(selfRes, BAS_RES_NOICON, &size);
iconLen = (int32_t)size;
dvxResClose(selfRes);
}
if (!iconData) {
return NULL;
}
}
int32_t rc = dvxResAppend(outPath, BAS_RES_ICON32, DVX_RES_ICON, iconData, (uint32_t)iconLen);
free(iconData);
return (rc == 0) ? NULL : BAS_BUILD_ERR_WRITE_RES;
}
static const char *emitResources(const char *outPath, const BasBuildSpecT *spec, const uint8_t *modData, int32_t modLen, const uint8_t *dbgData, int32_t dbgLen, uint8_t **frmData, const int32_t *frmLens, const BasObfFrmT *obfFrms, int32_t frmCount) {
// Accumulate every append result so a disk-full or I/O failure on any
// resource is reported instead of being silently treated as success.
int32_t rc = 0;
// Project metadata. Name is required -- fall back to a generic label
// so the compiled app always has something sensible to display.
const char *projName = (spec->projName && spec->projName[0]) ? spec->projName : BAS_DEFAULT_APP_NAME;
rc |= appendText(outPath, BAS_RES_NAME, projName);
rc |= appendText(outPath, BAS_RES_AUTHOR, spec->author);
rc |= appendText(outPath, BAS_RES_PUBLISHER, spec->publisher);
rc |= appendText(outPath, BAS_RES_VERSION, spec->version);
rc |= appendText(outPath, BAS_RES_COPYRIGHT, spec->copyright);
rc |= appendText(outPath, BAS_RES_DESCRIPTION, spec->description);
const char *iconFailure = emitIcon(outPath, spec);
if (iconFailure) {
return iconFailure;
}
if (spec->helpFile && spec->helpFile[0]) {
rc |= appendText(outPath, BAS_RES_HELPFILE, platformPathBaseName(spec->helpFile));
}
rc |= dvxResAppend(outPath, BAS_RES_MODULE, DVX_RES_BINARY, modData, (uint32_t)modLen);
if (dbgData && dbgLen > 0) {
rc |= dvxResAppend(outPath, BAS_RES_DEBUG, DVX_RES_BINARY, dbgData, (uint32_t)dbgLen);
}
// Form resources. Release builds embed the obfuscated variant when
// one was produced. Number the OUTPUT densely with outIdx rather
// than the source index: skipping an empty form by source index would
// leave a numbering gap (FORM0, FORM2, ...) and the stub's reader
// stops at the first missing index, silently dropping every later
// form.
int32_t outIdx = 0;
for (int32_t i = 0; i < frmCount; i++) {
const uint8_t *data = frmData[i];
int32_t len = frmLens[i];
if (spec->release && obfFrms[i].data) {
data = (const uint8_t *)obfFrms[i].data;
len = obfFrms[i].len;
}
if (!data || len <= 0) {
continue;
}
// The stub's loader scans FORM0..FORM(BAS_MAX_FORM_RESOURCES-1)
// and stops there, so anything past that cap would be written but
// unreachable at runtime. Fail the build loudly instead of
// shipping forms the app can never load.
if (outIdx >= BAS_MAX_FORM_RESOURCES) {
return "Too many forms for one application.";
}
char resName[BAS_RES_FORM_NAME_LEN];
int32_t formLen = basFindFormEndPos((const char *)data, len);
snprintf(resName, sizeof(resName), BAS_RES_FORM_FMT, (long)outIdx);
rc |= dvxResAppend(outPath, resName, DVX_RES_BINARY, data, (uint32_t)formLen);
outIdx++;
}
return (rc == 0) ? NULL : BAS_BUILD_ERR_WRITE_RES;
}
// Releases the stripped / obfuscated form work arrays of basBuildApp.
static void freeFrmWork(uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms) {
for (int32_t i = 0; i < (int32_t)arrlen(frmData); i++) {
free(frmData[i]);
free(obfFrms[i].data);
}
arrfree(frmData);
arrfree(frmLens);
arrfree(obfFrms);
}
// Serialize the module (and, for debug builds, its debug info). Release
// builds run strip -> obfuscate -> compact on a private copy so the
// caller's module stays intact; obfuscation also fills obfFrms.
static const char *prepareModule(const BasBuildSpecT *spec, uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms, int32_t frmCount, uint8_t **outMod, int32_t *outModLen, uint8_t **outDbg, int32_t *outDbgLen) {
uint8_t *modData = basModuleSerialize(spec->module, outModLen);
if (!modData) {
return "Failed to serialize module.";
}
if (!spec->release) {
*outMod = modData;
*outDbg = basDebugSerialize(spec->module, outDbgLen);
// basDebugSerialize fails only on allocation; a debug build that
// silently lacks its DEBUG resource is not a debug build.
return *outDbg ? NULL : "Failed to serialize debug info.";
}
BasModuleT *modCopy = basModuleDeserialize(modData, *outModLen);
free(modData);
if (!modCopy) {
return "Failed to prepare release module.";
}
basStripModule(modCopy);
buildLog(spec, " stripped debug info");
if (frmCount > 0) {
const char **frmTexts = NULL;
for (int32_t i = 0; i < frmCount; i++) {
arrput(frmTexts, (const char *)frmData[i]);
}
bool obfOk = basObfuscateNames(modCopy, frmTexts, frmLens, frmCount, obfFrms);
arrfree(frmTexts);
// A failed obfuscation leaves the module and forms with
// inconsistent names; shipping them would break at runtime.
if (!obfOk) {
basModuleFree(modCopy);
return "Out of memory while obfuscating names.";
}
buildLog(spec, " obfuscated %d form(s)", (int)frmCount);
}
int32_t removed = basCompactBytecode(modCopy);
if (removed > 0) {
buildLog(spec, " compacted bytecode (-%d bytes)", (int)removed);
}
*outMod = basModuleSerialize(modCopy, outModLen);
basModuleFree(modCopy);
if (!*outMod) {
return "Failed to serialize release module.";
}
return NULL;
}
// Extract the STUB resource from the running executable and write it as
// the output file; every resource is appended behind it.
static const char *writeStub(const char *outPath, const BasBuildSpecT *spec) {
DvxResHandleT *selfRes = dvxResOpen(spec->selfPath);
if (!selfRes) {
return "Cannot open own executable to read embedded stub.";
}
uint32_t stubSize = 0;
void *stubData = dvxResRead(selfRes, BAS_RES_STUB, &stubSize);
dvxResClose(selfRes);
if (!stubData || stubSize == 0) {
free(stubData);
return "STUB resource not found in own executable.";
}
FILE *outFile = fopen(outPath, "wb");
if (!outFile) {
free(stubData);
return "Cannot create output file.";
}
size_t stubWritten = fwrite(stubData, 1, stubSize, outFile);
int32_t stubClose = fclose(outFile);
free(stubData);
if (stubWritten != stubSize || stubClose != 0) {
return "Failed writing stub to output file.";
}
return NULL;
}