682 lines
22 KiB
C
682 lines
22 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.
|
|
|
|
// obfuscate.c -- Release build name obfuscation
|
|
//
|
|
// See obfuscate.h for the high-level description.
|
|
|
|
#include "obfuscate.h"
|
|
#include "basEvents.h"
|
|
#include "lexer.h"
|
|
#include "../basRes.h"
|
|
#include "../runtime/values.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define BAS_OBF_TOKEN_LEN BAS_MAX_IDENT // longest form/control/type token kept from a .frm line
|
|
#define BAS_OBF_MAPPED_LEN 16 // "C" + decimal index + NUL
|
|
#define BAS_OBF_MAP_INIT_CAP 16 // initial name-map capacity (doubles on growth)
|
|
#define BAS_REM_KEYWORD_LEN 3 // "REM"
|
|
|
|
// .frm properties whose value names another control on the form; only
|
|
// these values follow a control rename.
|
|
static const char *sNameRefKeys[] = {
|
|
"DataSource",
|
|
"MasterSource",
|
|
NULL
|
|
};
|
|
|
|
// ============================================================
|
|
// Name map
|
|
// ============================================================
|
|
|
|
typedef struct {
|
|
char *orig; // original name (strdup'd, case-preserved)
|
|
char *mapped; // new name (strdup'd, "C1" .. "Cn")
|
|
} NameEntryT;
|
|
|
|
typedef struct {
|
|
NameEntryT *entries;
|
|
int32_t count;
|
|
int32_t cap;
|
|
} NameMapT;
|
|
|
|
|
|
// Function prototypes (alphabetical)
|
|
bool basObfuscateNames(BasModuleT *mod, const char **frmTexts, const int32_t *frmLens, int32_t frmCount, BasObfFrmT *outFrms);
|
|
int32_t basStripFrmComments(const char *src, int32_t srcLen, uint8_t *outBuf, int32_t outCap);
|
|
static bool collectNamesFromFrm(const char *text, int32_t len, NameMapT *names, NameMapT *reserved);
|
|
static void emitBytes(uint8_t *out, int32_t outCap, int32_t *outLen, const char *src, int32_t len);
|
|
static bool isNameRefKey(const char *key);
|
|
static bool isWholeLineRem(const char *text, int32_t len);
|
|
static const char *nameMapAdd(NameMapT *m, const char *name);
|
|
static void nameMapFree(NameMapT *m);
|
|
static void nameMapInit(NameMapT *m);
|
|
static const char *nameMapLookup(const NameMapT *m, const char *name);
|
|
static const char *readToken(const char *p, const char *end, char *buf, int32_t bufSize, bool *outTruncated);
|
|
static bool replaceConstant(BasModuleT *mod, int32_t idx, const char *newText);
|
|
static int32_t rewriteFrmText(const char *src, int32_t srcLen, const NameMapT *map, uint8_t *out, int32_t outCap);
|
|
static bool rewriteModuleConstants(BasModuleT *mod, const NameMapT *map);
|
|
static void rewriteModuleFormVars(BasModuleT *mod, const NameMapT *map);
|
|
static void rewriteModuleProcs(BasModuleT *mod, const NameMapT *map);
|
|
static const char *skipWhitespace(const char *p, const char *end);
|
|
|
|
// ============================================================
|
|
// Top-level entry point
|
|
// ============================================================
|
|
bool basObfuscateNames(BasModuleT *mod, const char **frmTexts, const int32_t *frmLens, int32_t frmCount, BasObfFrmT *outFrms) {
|
|
if (!mod || frmCount < 0) {
|
|
return true;
|
|
}
|
|
|
|
// Pass 1: collect all names from all .frm texts. A control whose name
|
|
// is also a control type or a property key (a TextBox named "Text", a
|
|
// Timer named "Timer") is left alone: the constant pool cannot tell a
|
|
// name reference apart from the identically spelled property/type name
|
|
// used by OP_LOAD_PROP and friends.
|
|
NameMapT names;
|
|
NameMapT reserved;
|
|
NameMapT map;
|
|
bool ok = true;
|
|
|
|
nameMapInit(&names);
|
|
nameMapInit(&reserved);
|
|
nameMapInit(&map);
|
|
|
|
for (int32_t i = 0; ok && i < frmCount; i++) {
|
|
if (frmTexts[i] && frmLens[i] > 0) {
|
|
ok = collectNamesFromFrm(frmTexts[i], frmLens[i], &names, &reserved);
|
|
}
|
|
}
|
|
|
|
for (int32_t i = 0; ok && i < names.count; i++) {
|
|
if (!nameMapLookup(&reserved, names.entries[i].orig)) {
|
|
ok = nameMapAdd(&map, names.entries[i].orig) != NULL;
|
|
}
|
|
}
|
|
|
|
nameMapFree(&names);
|
|
nameMapFree(&reserved);
|
|
|
|
// Pass 2: rewrite each .frm, sized by a measuring pass so a form with
|
|
// many short names that grow ("a" -> "C12") can never be truncated.
|
|
for (int32_t i = 0; i < frmCount; i++) {
|
|
outFrms[i].data = NULL;
|
|
outFrms[i].len = 0;
|
|
|
|
if (!ok || !frmTexts[i] || frmLens[i] <= 0) {
|
|
continue;
|
|
}
|
|
|
|
int32_t strippedLen = basFindFormEndPos(frmTexts[i], frmLens[i]);
|
|
int32_t outCap = rewriteFrmText(frmTexts[i], strippedLen, &map, NULL, 0) + 1;
|
|
uint8_t *outBuf = malloc(outCap);
|
|
|
|
if (!outBuf) {
|
|
ok = false;
|
|
continue;
|
|
}
|
|
|
|
int32_t outLen = rewriteFrmText(frmTexts[i], strippedLen, &map, outBuf, outCap);
|
|
|
|
// Ensure trailing newline
|
|
if (outLen > 0 && outBuf[outLen - 1] != '\n') {
|
|
outBuf[outLen++] = '\n';
|
|
}
|
|
|
|
outFrms[i].data = outBuf;
|
|
outFrms[i].len = outLen;
|
|
}
|
|
|
|
// Pass 3: rewrite module
|
|
if (ok) {
|
|
ok = rewriteModuleConstants(mod, &map);
|
|
rewriteModuleProcs(mod, &map);
|
|
rewriteModuleFormVars(mod, &map);
|
|
}
|
|
|
|
nameMapFree(&map);
|
|
return ok;
|
|
}
|
|
|
|
|
|
int32_t basStripFrmComments(const char *src, int32_t srcLen, uint8_t *outBuf, int32_t outCap) {
|
|
if (!src || srcLen <= 0 || !outBuf || outCap <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
// Inside the form block only whole-line comments exist (the form
|
|
// parser takes the rest of a property line verbatim); trailing
|
|
// comments are stripped from the BASIC code after it.
|
|
int32_t formEnd = basFindFormEndPos(src, srcLen);
|
|
int32_t outLen = 0;
|
|
int32_t i = 0;
|
|
|
|
while (i < srcLen) {
|
|
int32_t lineStart = i;
|
|
|
|
while (i < srcLen && src[i] != '\n' && src[i] != '\r') {
|
|
i++;
|
|
}
|
|
|
|
int32_t lineEnd = i;
|
|
|
|
if (i < srcLen && src[i] == '\r') {
|
|
i++;
|
|
}
|
|
|
|
if (i < srcLen && src[i] == '\n') {
|
|
i++;
|
|
}
|
|
|
|
int32_t firstNonWs = lineStart;
|
|
|
|
while (firstNonWs < lineEnd && (src[firstNonWs] == ' ' || src[firstNonWs] == '\t')) {
|
|
firstNonWs++;
|
|
}
|
|
|
|
int32_t contentEnd = lineEnd;
|
|
|
|
if (firstNonWs < lineEnd && src[firstNonWs] == '\'') {
|
|
contentEnd = firstNonWs;
|
|
} else if (lineStart >= formEnd) {
|
|
// Code section: scan for the first unquoted ' (comment start).
|
|
bool inStr = false;
|
|
|
|
for (int32_t j = firstNonWs; j < lineEnd; j++) {
|
|
if (src[j] == '"') {
|
|
inStr = !inStr;
|
|
} else if (src[j] == '\'' && !inStr) {
|
|
contentEnd = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isWholeLineRem(src + firstNonWs, contentEnd - firstNonWs)) {
|
|
contentEnd = firstNonWs;
|
|
}
|
|
|
|
// Trim trailing whitespace.
|
|
while (contentEnd > lineStart && (src[contentEnd - 1] == ' ' || src[contentEnd - 1] == '\t')) {
|
|
contentEnd--;
|
|
}
|
|
|
|
// Drop lines that have no non-whitespace content.
|
|
if (contentEnd <= firstNonWs) {
|
|
continue;
|
|
}
|
|
|
|
// Strip leading whitespace -- the form parser trims per line
|
|
// anyway, so shipping indentation just bloats the embedded resource.
|
|
int32_t writeLen = contentEnd - firstNonWs;
|
|
|
|
if (outLen + writeLen + 1 >= outCap) {
|
|
break;
|
|
}
|
|
|
|
memcpy(outBuf + outLen, src + firstNonWs, writeLen);
|
|
outLen += writeLen;
|
|
outBuf[outLen++] = '\n';
|
|
}
|
|
|
|
return outLen;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Pass 1: collect form/control names and the identifiers they must not
|
|
// collide with
|
|
// ============================================================
|
|
|
|
// Scan a .frm text: every "Begin <Type> <Name>" adds Name to names, and
|
|
// Type plus every "<Key> = ..." property key goes into reserved.
|
|
static bool collectNamesFromFrm(const char *text, int32_t len, NameMapT *names, NameMapT *reserved) {
|
|
const char *p = text;
|
|
const char *end = text + len;
|
|
|
|
while (p < end) {
|
|
// Read one line
|
|
const char *lineStart = p;
|
|
|
|
while (p < end && *p != '\n' && *p != '\r') {
|
|
p++;
|
|
}
|
|
|
|
const char *lineEnd = p;
|
|
|
|
if (p < end && *p == '\r') {
|
|
p++;
|
|
}
|
|
|
|
if (p < end && *p == '\n') {
|
|
p++;
|
|
}
|
|
|
|
// Trim leading whitespace
|
|
const char *l = skipWhitespace(lineStart, lineEnd);
|
|
int32_t beginOff = basMatchKeywordWs(l, (int32_t)(lineEnd - l), FRM_KW_BEGIN, FRM_KW_BEGIN_LEN);
|
|
char token[BAS_OBF_TOKEN_LEN];
|
|
bool truncated;
|
|
|
|
if (beginOff >= 0) {
|
|
l = readToken(l + beginOff, lineEnd, token, sizeof(token), &truncated);
|
|
|
|
if (token[0] == '\0') {
|
|
continue;
|
|
}
|
|
|
|
if (!truncated && !nameMapAdd(reserved, token)) {
|
|
return false;
|
|
}
|
|
|
|
l = skipWhitespace(l, lineEnd);
|
|
readToken(l, lineEnd, token, sizeof(token), &truncated);
|
|
|
|
// A name too long for the token buffer is never mapped.
|
|
if (!truncated && token[0] && basIsValidIdent(token) && !nameMapAdd(names, token)) {
|
|
return false;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// "<Key> = value": the key is a property name.
|
|
l = readToken(l, lineEnd, token, sizeof(token), &truncated);
|
|
l = skipWhitespace(l, lineEnd);
|
|
|
|
if (l < lineEnd && *l == '=' && !truncated && token[0] && basIsValidIdent(token) && !nameMapAdd(reserved, token)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Pass 2: rewrite .frm text with mapped names
|
|
// ============================================================
|
|
|
|
// Appends len bytes of src to out (bounded by outCap) and always advances
|
|
// *outLen, so a NULL out measures the exact output size.
|
|
static void emitBytes(uint8_t *out, int32_t outCap, int32_t *outLen, const char *src, int32_t len) {
|
|
if (out && *outLen + len <= outCap) {
|
|
memcpy(out + *outLen, src, len);
|
|
}
|
|
|
|
*outLen += len;
|
|
}
|
|
|
|
|
|
// True when a property key holds the name of another control.
|
|
static bool isNameRefKey(const char *key) {
|
|
for (int32_t i = 0; sNameRefKeys[i]; i++) {
|
|
if (strcasecmp(key, sNameRefKeys[i]) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// True when a line's content (leading whitespace already skipped) is a
|
|
// REM comment: "REM" alone or followed by whitespace.
|
|
static bool isWholeLineRem(const char *text, int32_t len) {
|
|
if (len < BAS_REM_KEYWORD_LEN || strncasecmp(text, "REM", BAS_REM_KEYWORD_LEN) != 0) {
|
|
return false;
|
|
}
|
|
|
|
return len == BAS_REM_KEYWORD_LEN || text[BAS_REM_KEYWORD_LEN] == ' ' || text[BAS_REM_KEYWORD_LEN] == '\t';
|
|
}
|
|
|
|
|
|
// Map name to the next generated name ("C1", "C2" ...) unless it is
|
|
// already mapped. Returns the mapped name, or NULL when memory runs out.
|
|
static const char *nameMapAdd(NameMapT *m, const char *name) {
|
|
const char *existing = nameMapLookup(m, name);
|
|
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
|
|
if (m->count >= m->cap) {
|
|
int32_t newCap = m->cap == 0 ? BAS_OBF_MAP_INIT_CAP : m->cap * 2;
|
|
NameEntryT *newEntries = realloc(m->entries, newCap * sizeof(NameEntryT));
|
|
|
|
if (!newEntries) {
|
|
return NULL;
|
|
}
|
|
|
|
m->entries = newEntries;
|
|
m->cap = newCap;
|
|
}
|
|
|
|
char mapped[BAS_OBF_MAPPED_LEN];
|
|
snprintf(mapped, sizeof(mapped), "C%ld", (long)(m->count + 1));
|
|
|
|
char *origCopy = strdup(name);
|
|
char *mappedCopy = strdup(mapped);
|
|
|
|
if (!origCopy || !mappedCopy) {
|
|
free(origCopy);
|
|
free(mappedCopy);
|
|
return NULL;
|
|
}
|
|
|
|
m->entries[m->count].orig = origCopy;
|
|
m->entries[m->count].mapped = mappedCopy;
|
|
m->count++;
|
|
|
|
return mappedCopy;
|
|
}
|
|
|
|
|
|
static void nameMapFree(NameMapT *m) {
|
|
for (int32_t i = 0; i < m->count; i++) {
|
|
free(m->entries[i].orig);
|
|
free(m->entries[i].mapped);
|
|
}
|
|
|
|
free(m->entries);
|
|
m->entries = NULL;
|
|
m->count = 0;
|
|
m->cap = 0;
|
|
}
|
|
|
|
|
|
static void nameMapInit(NameMapT *m) {
|
|
m->entries = NULL;
|
|
m->count = 0;
|
|
m->cap = 0;
|
|
}
|
|
|
|
|
|
// Look up an original name (case-insensitive). Returns mapped name or NULL.
|
|
static const char *nameMapLookup(const NameMapT *m, const char *name) {
|
|
for (int32_t i = 0; i < m->count; i++) {
|
|
if (strcasecmp(m->entries[i].orig, name) == 0) {
|
|
return m->entries[i].mapped;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// Copy the next whitespace-delimited token into buf and return a pointer
|
|
// past the WHOLE token. *outTruncated reports a token longer than buf.
|
|
static const char *readToken(const char *p, const char *end, char *buf, int32_t bufSize, bool *outTruncated) {
|
|
int32_t len = 0;
|
|
|
|
*outTruncated = false;
|
|
|
|
while (p < end && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') {
|
|
if (len < bufSize - 1) {
|
|
buf[len++] = *p;
|
|
} else {
|
|
*outTruncated = true;
|
|
}
|
|
|
|
p++;
|
|
}
|
|
|
|
buf[len] = '\0';
|
|
return p;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Module rewriting
|
|
// ============================================================
|
|
|
|
// Replace the contents of a constant pool entry with a new string.
|
|
static bool replaceConstant(BasModuleT *mod, int32_t idx, const char *newText) {
|
|
BasStringT *newStr = basStringNew(newText, (int32_t)strlen(newText));
|
|
|
|
if (!newStr) {
|
|
return false;
|
|
}
|
|
|
|
basStringUnref(mod->constants[idx]);
|
|
mod->constants[idx] = newStr;
|
|
return true;
|
|
}
|
|
|
|
|
|
// Rewrites the .frm text line by line. Only positions that hold a
|
|
// form/control NAME are remapped: the name token of a "Begin <Type> <Name>"
|
|
// line and the value of a control-reference property (sNameRefKeys) that
|
|
// is exactly a mapped name (bare, or the whole content of a quoted string
|
|
// such as DataSource = "datCat"). Type tokens, property keys and every
|
|
// other value (a Caption equal to the form name ...) are copied verbatim. Identifiers of any length
|
|
// are copied from the source span, never through a fixed buffer. With out
|
|
// NULL nothing is written and the return value is the required size.
|
|
static int32_t rewriteFrmText(const char *src, int32_t srcLen, const NameMapT *map, uint8_t *out, int32_t outCap) {
|
|
int32_t outLen = 0;
|
|
const char *p = src;
|
|
const char *end = src + srcLen;
|
|
|
|
while (p < end) {
|
|
const char *lineStart = p;
|
|
|
|
while (p < end && *p != '\n' && *p != '\r') {
|
|
p++;
|
|
}
|
|
|
|
const char *lineEnd = p;
|
|
|
|
if (p < end && *p == '\r') {
|
|
p++;
|
|
}
|
|
|
|
if (p < end && *p == '\n') {
|
|
p++;
|
|
}
|
|
|
|
const char *l = skipWhitespace(lineStart, lineEnd);
|
|
int32_t beginOff = basMatchKeywordWs(l, (int32_t)(lineEnd - l), FRM_KW_BEGIN, FRM_KW_BEGIN_LEN);
|
|
const char *replaceStart = NULL; // span of the original name to swap
|
|
const char *replaceEnd = NULL;
|
|
const char *mapped = NULL;
|
|
char token[BAS_OBF_TOKEN_LEN];
|
|
bool truncated;
|
|
|
|
if (beginOff >= 0) {
|
|
// Begin <Type> <Name>: only the name token is a candidate.
|
|
const char *t = readToken(l + beginOff, lineEnd, token, sizeof(token), &truncated);
|
|
t = skipWhitespace(t, lineEnd);
|
|
|
|
const char *nameStart = t;
|
|
t = readToken(t, lineEnd, token, sizeof(token), &truncated);
|
|
|
|
if (token[0] && !truncated) {
|
|
mapped = nameMapLookup(map, token);
|
|
replaceStart = nameStart;
|
|
replaceEnd = t;
|
|
}
|
|
} else {
|
|
// <Key> = value: the value of a control-reference property that
|
|
// is exactly a mapped name (bare or quoted) follows the rename.
|
|
const char *t = readToken(l, lineEnd, token, sizeof(token), &truncated);
|
|
t = skipWhitespace(t, lineEnd);
|
|
|
|
if (t < lineEnd && *t == '=' && !truncated && isNameRefKey(token)) {
|
|
t = skipWhitespace(t + 1, lineEnd);
|
|
|
|
const char *valueStart = t;
|
|
const char *valueEnd = lineEnd;
|
|
|
|
while (valueEnd > valueStart && (valueEnd[-1] == ' ' || valueEnd[-1] == '\t')) {
|
|
valueEnd--;
|
|
}
|
|
|
|
if (valueEnd - valueStart >= 2 && *valueStart == '"' && valueEnd[-1] == '"') {
|
|
valueStart++;
|
|
valueEnd--;
|
|
}
|
|
|
|
int32_t valueLen = (int32_t)(valueEnd - valueStart);
|
|
bool isIdent = (valueLen > 0 && valueLen < (int32_t)sizeof(token));
|
|
|
|
for (int32_t k = 0; isIdent && k < valueLen; k++) {
|
|
isIdent = basIsIdentChar(valueStart[k]);
|
|
}
|
|
|
|
if (isIdent) {
|
|
memcpy(token, valueStart, valueLen);
|
|
token[valueLen] = '\0';
|
|
mapped = nameMapLookup(map, token);
|
|
replaceStart = valueStart;
|
|
replaceEnd = valueEnd;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mapped) {
|
|
emitBytes(out, outCap, &outLen, lineStart, (int32_t)(replaceStart - lineStart));
|
|
emitBytes(out, outCap, &outLen, mapped, (int32_t)strlen(mapped));
|
|
emitBytes(out, outCap, &outLen, replaceEnd, (int32_t)(p - replaceEnd));
|
|
} else {
|
|
emitBytes(out, outCap, &outLen, lineStart, (int32_t)(p - lineStart));
|
|
}
|
|
}
|
|
|
|
return outLen;
|
|
}
|
|
|
|
|
|
static bool rewriteModuleConstants(BasModuleT *mod, const NameMapT *map) {
|
|
// KNOWN LIMITATION: the constant pool dedupes a plain string literal and a
|
|
// same-text control/form-name reference into ONE entry, so a release build
|
|
// that does e.g. MsgBox "Save" while also having a control named "Save"
|
|
// will see that literal rewritten to the obfuscated name ("C5"). Fully
|
|
// separating name references from literals would give name refs their own
|
|
// pool slots and change emitted bytecode for every release build, so it is
|
|
// deferred; avoid string literals that exactly match a control/form name
|
|
// in release builds. Names that collide with a property key or control
|
|
// type are never mapped at all (see basObfuscateNames).
|
|
for (int32_t i = 0; i < mod->constCount; i++) {
|
|
const BasStringT *s = mod->constants[i];
|
|
|
|
if (!s) {
|
|
continue;
|
|
}
|
|
|
|
const char *mapped = nameMapLookup(map, s->data);
|
|
|
|
if (mapped) {
|
|
if (!replaceConstant(mod, i, mapped)) {
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// "<Name>_<Event>" handler names used as SetEvent targets follow the
|
|
// procedure rename so basModuleFindProc still resolves them.
|
|
char prefix[BAS_MAX_IDENT];
|
|
const char *suffix;
|
|
|
|
if (!basSplitEventName(s->data, prefix, (int32_t)sizeof(prefix), &suffix)) {
|
|
continue;
|
|
}
|
|
|
|
mapped = nameMapLookup(map, prefix);
|
|
|
|
if (mapped) {
|
|
char newName[BAS_MAX_IDENT];
|
|
snprintf(newName, sizeof(newName), "%s_%s", mapped, suffix);
|
|
|
|
if (!replaceConstant(mod, i, newName)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static void rewriteModuleFormVars(BasModuleT *mod, const NameMapT *map) {
|
|
for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
|
|
BasFormVarInfoT *fv = &mod->formVarInfo[i];
|
|
const char *mapped = nameMapLookup(map, fv->formName);
|
|
|
|
if (mapped) {
|
|
snprintf(fv->formName, sizeof(fv->formName), "%s", mapped);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void rewriteModuleProcs(BasModuleT *mod, const NameMapT *map) {
|
|
for (int32_t i = 0; i < mod->procCount; i++) {
|
|
BasProcEntryT *proc = &mod->procs[i];
|
|
|
|
if (proc->name[0] == '\0') {
|
|
continue;
|
|
}
|
|
|
|
// Remap the owning form name (used at runtime to bind form-scope
|
|
// variables). The form itself gets renamed by the same pass.
|
|
if (proc->formName[0]) {
|
|
const char *mappedForm = nameMapLookup(map, proc->formName);
|
|
|
|
if (mappedForm) {
|
|
snprintf(proc->formName, sizeof(proc->formName), "%s", mappedForm);
|
|
}
|
|
}
|
|
|
|
// <Control>_<Event> handlers follow the control rename.
|
|
char prefix[BAS_MAX_IDENT];
|
|
const char *suffix;
|
|
|
|
if (!basSplitEventName(proc->name, prefix, (int32_t)sizeof(prefix), &suffix)) {
|
|
continue;
|
|
}
|
|
|
|
const char *mapped = nameMapLookup(map, prefix);
|
|
|
|
if (mapped) {
|
|
char newName[BAS_MAX_IDENT];
|
|
snprintf(newName, sizeof(newName), "%s_%s", mapped, suffix);
|
|
snprintf(proc->name, sizeof(proc->name), "%s", newName);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// .frm parsing helpers
|
|
// ============================================================
|
|
|
|
// Skip ASCII whitespace. Returns pointer past whitespace.
|
|
static const char *skipWhitespace(const char *p, const char *end) {
|
|
while (p < end && (*p == ' ' || *p == '\t')) {
|
|
p++;
|
|
}
|
|
|
|
return p;
|
|
}
|