/* * pluginlib.c - PluginLib shim for CodeWarrior compiler DLLs */ #include #include #include #include #include #include #include "cw_types.h" #include "host_ctx.h" CWPluginContext g_context = NULL; #define LOG(fmt, ...) do { if (g_context && g_context->verbose) { fprintf(stderr, "[PluginLib" STRINGIFY(PLUGINLIB_VER) "] " fmt "\n", ##__VA_ARGS__); fflush(stderr); } } while(0) #define STUB(name) LOG("STUB: %s called", name) /* Forward declarations */ CW_CALLBACK CWGetMemHandleSize(CWPluginContext context, CWMemHandle handle, SInt32* size); CW_CALLBACK CWLockMemHandle(CWPluginContext context, CWMemHandle handle, Boolean moveHi, void** ptr); CW_CALLBACK CWUnlockMemHandle(CWPluginContext context, CWMemHandle handle); /* * Convert line endings to \r (Mac convention) in-place. * Matches MWCC FixTextHandle(): \n -> \r, \r\n -> \r\n (unchanged). * Buffer can only shrink (standalone \n replaced by \r, same length; * \r\n pairs are left alone). Returns new size. */ static SInt32 fix_text_line_endings(char* buf, SInt32 size) { SInt32 out = 0; for (SInt32 i = 0; i < size; i++) { if (buf[i] == '\r') { buf[out++] = '\r'; if (i + 1 < size && buf[i + 1] == '\n') { buf[out++] = '\n'; i++; /* skip the \n of \r\n pair */ } } else if (buf[i] == '\n') { buf[out++] = '\r'; /* convert standalone \n to \r */ } else { buf[out++] = buf[i]; } } buf[out] = '\0'; return out; } /* * Read a file into a malloc'd buffer and convert line endings to \r. * Returns NULL on failure. Sets *out_size to the converted size. */ static char* read_and_fix_file(const char* path, SInt32* out_size) { FILE* f = fopen(path, "rb"); if (!f) return NULL; fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); char* buf = (char*)malloc(size + 1); if (!buf) { fclose(f); return NULL; } fread(buf, 1, size, f); buf[size] = '\0'; fclose(f); *out_size = fix_text_line_endings(buf, (SInt32)size); return buf; } static void copy_cstr(char* dst, size_t dst_size, const char* src) { size_t i = 0; if (!dst || dst_size == 0) return; if (!src) { dst[0] = '\0'; return; } while (i + 1 < dst_size && src[i]) { dst[i] = src[i]; i++; } dst[i] = '\0'; } static void cwfilespec_from_cpath(CWFileSpec* spec, const char* path) { if (!spec) return; if (!path) path = ""; #if PLUGINLIB_VER == 2 size_t len; memset(spec, 0, sizeof(*spec)); len = strlen(path); if (len > 255) len = 255; spec->name[0] = (UInt8)len; if (len > 0) { memcpy(spec->name + 1, path, len); } #else copy_cstr(spec->path, MAX_PATH, path); #endif } static void cwfilespec_to_cpath(const CWFileSpec* spec, char* out, size_t out_size) { if (!out || out_size == 0) return; out[0] = '\0'; if (!spec) return; #if PLUGINLIB_VER == 2 size_t len = spec->name[0]; if (len > 255) len = 255; if (len + 1 > out_size) len = out_size - 1; if (len > 0) { memcpy(out, spec->name + 1, len); } out[len] = '\0'; #else copy_cstr(out, out_size, spec->path); #endif } static void cw_filename_arg_to_cpath(const char* filename, char* out, size_t out_size) { if (!out || out_size == 0) return; out[0] = '\0'; if (!filename) return; #if PLUGINLIB_VER == 2 { UInt8 len = (UInt8)filename[0]; if (len > 0 && len < out_size && memchr(filename + 1, '\0', len) == NULL) { memcpy(out, filename + 1, len); out[len] = '\0'; return; } } #endif copy_cstr(out, out_size, filename); } static int join_path(char* out, size_t out_size, const char* dir, const char* leaf) { int n; if (!out || out_size == 0 || !dir || !leaf) return 0; n = snprintf(out, out_size, "%s/%s", dir, leaf); return n > 0 && (size_t)n < out_size; } static void get_directory(const char* filepath, char* dir, size_t dirsize) { char* sep; copy_cstr(dir, dirsize, filepath); sep = strrchr(dir, '\\'); if (!sep) sep = strrchr(dir, '/'); if (sep) *(sep + 1) = '\0'; else copy_cstr(dir, dirsize, "."); } static int is_full_path(const char* path) { if (!path || !path[0]) return 0; if ((isalpha((unsigned char)path[0]) && path[1] == ':') || path[0] == '\\' || path[0] == '/') return 1; return 0; } static int ensure_file_record_capacity(CWPluginContext ctx) { HostFileRecord* recs; SInt32 new_cap; if (ctx->fileRecordCount < ctx->fileRecordCap) return 1; new_cap = (ctx->fileRecordCap > 0) ? (ctx->fileRecordCap * 2) : 64; recs = (HostFileRecord*)realloc(ctx->fileRecords, (size_t)new_cap * sizeof(HostFileRecord)); if (!recs) return 0; ctx->fileRecords = recs; ctx->fileRecordCap = new_cap; return 1; } static void record_file_id(CWPluginContext ctx, short file_id, const char* path, Boolean is_system) { for (SInt32 i = 0; i < ctx->fileRecordCount; i++) { if (ctx->fileRecords[i].fileID == file_id) { copy_cstr(ctx->fileRecords[i].path, MAX_PATH, path); ctx->fileRecords[i].isSystem = is_system; return; } } if (!ensure_file_record_capacity(ctx)) return; ctx->fileRecords[ctx->fileRecordCount].fileID = file_id; ctx->fileRecords[ctx->fileRecordCount].isSystem = is_system; copy_cstr(ctx->fileRecords[ctx->fileRecordCount].path, MAX_PATH, path); ctx->fileRecordCount++; } static const char* lookup_file_id_path(const CWPluginContext ctx, short file_id) { for (SInt32 i = 0; i < ctx->fileRecordCount; i++) { if (ctx->fileRecords[i].fileID == file_id) return ctx->fileRecords[i].path; } return NULL; } static void normalize_include_path(const char* path, char* out, size_t out_size) { if (!out || out_size == 0) return; out[0] = '\0'; if (!path || !path[0]) return; copy_cstr(out, out_size, path); for (size_t i = 0; out[i]; i++) { if (out[i] == '\\') out[i] = '/'; out[i] = (char)tolower((unsigned char)out[i]); } } static int ensure_include_record_capacity(CWPluginContext ctx) { HostIncludeRecord* recs; SInt32 new_cap; if (ctx->includeRecordCount < ctx->includeRecordCap) return 1; new_cap = (ctx->includeRecordCap > 0) ? (ctx->includeRecordCap * 2) : 64; recs = (HostIncludeRecord*)realloc(ctx->includeRecords, (size_t)new_cap * sizeof(HostIncludeRecord)); if (!recs) return 0; ctx->includeRecords = recs; ctx->includeRecordCap = new_cap; return 1; } static Boolean was_include_loaded(const CWPluginContext ctx, const char* path) { char normalized[MAX_PATH]; if (!ctx || !path || !path[0]) return FALSE; normalize_include_path(path, normalized, sizeof(normalized)); if (!normalized[0]) return FALSE; for (SInt32 i = 0; i < ctx->includeRecordCount; i++) { if (strcmp(ctx->includeRecords[i].path, normalized) == 0) return TRUE; } return FALSE; } static void mark_include_loaded(CWPluginContext ctx, const char* path) { char normalized[MAX_PATH]; if (!ctx || !path || !path[0]) return; normalize_include_path(path, normalized, sizeof(normalized)); if (!normalized[0]) return; for (SInt32 i = 0; i < ctx->includeRecordCount; i++) { if (strcmp(ctx->includeRecords[i].path, normalized) == 0) return; } if (!ensure_include_record_capacity(ctx)) return; copy_cstr(ctx->includeRecords[ctx->includeRecordCount].path, MAX_PATH, normalized); ctx->includeRecordCount++; } static CWResult load_file_for_include(const char* path, CWFileInfo* fileinfo, CWPluginContext ctx, Boolean suppressload, Boolean is_system) { SInt32 size; char* buf = NULL; Boolean already_included; already_included = was_include_loaded(ctx, path); if (!suppressload) { if (already_included && ctx->forceIncludeOnce) { /* Match mwccps2 -once behavior even when cc_mips.dll doesn't * honor #pragma once on/off toggles from the frontend. */ buf = (char*)malloc(1); if (!buf) return cwErrOutOfMemory; buf[0] = '\0'; fileinfo->filedata = buf; fileinfo->filedatalength = 0; } else { buf = read_and_fix_file(path, &size); if (!buf) return cwErrFileNotFound; fileinfo->filedata = buf; fileinfo->filedatalength = size; } } else { if (!is_full_path(path) && !strchr(path, '/')) { /* For suppress-load checks, existence still matters for leaf names. */ FILE* f = fopen(path, "rb"); if (!f) return cwErrFileNotFound; fclose(f); } else { FILE* f = fopen(path, "rb"); if (!f) return cwErrFileNotFound; fclose(f); } fileinfo->filedata = NULL; fileinfo->filedatalength = 0; } fileinfo->filedatatype = cwFileTypeText; fileinfo->fileID = ctx->nextFileID++; cwfilespec_from_cpath(&fileinfo->filespec, path); fileinfo->alreadyincluded = already_included; fileinfo->recordbrowseinfo = FALSE; record_file_id(ctx, fileinfo->fileID, path, is_system); if (!suppressload) { mark_include_loaded(ctx, path); } get_directory(path, ctx->lastIncludeDir, sizeof(ctx->lastIncludeDir)); return cwNoErr; } static CWResult try_load_include_recursive(const char* dir, const char* filename, CWFileInfo* fileinfo, CWPluginContext ctx, Boolean suppressload, Boolean is_system, int depth) { char pattern[MAX_PATH]; char candidate[MAX_PATH]; WIN32_FIND_DATAA ffd; HANDLE h; if (depth > 32) return cwErrFileNotFound; if (!join_path(pattern, sizeof(pattern), dir, "*")) return cwErrFileNotFound; h = FindFirstFileA(pattern, &ffd); if (h == INVALID_HANDLE_VALUE) return cwErrFileNotFound; do { char child[MAX_PATH]; CWResult r; if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue; if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue; if (!join_path(child, sizeof(child), dir, ffd.cFileName)) continue; if (!join_path(candidate, sizeof(candidate), child, filename)) continue; r = load_file_for_include(candidate, fileinfo, ctx, suppressload, is_system); if (r == cwNoErr) { FindClose(h); return cwNoErr; } if (try_load_include_recursive(child, filename, fileinfo, ctx, suppressload, is_system, depth + 1) == cwNoErr) { FindClose(h); return cwNoErr; } } while (FindNextFileA(h, &ffd)); FindClose(h); return cwErrFileNotFound; } /* * Helper: try to load a file from a directory path + filename. * If recursive is TRUE, scan subdirectories too (MWCC -ir behavior). */ static CWResult try_load_include(const char* dir, const char* filename, CWFileInfo* fileinfo, CWPluginContext ctx, Boolean suppressload, Boolean recursive, Boolean is_system) { char fullpath[MAX_PATH]; if (!join_path(fullpath, sizeof(fullpath), dir, filename)) return cwErrFileNotFound; if (load_file_for_include(fullpath, fileinfo, ctx, suppressload, is_system) == cwNoErr) { return cwNoErr; } if (recursive) { return try_load_include_recursive(dir, filename, fileinfo, ctx, suppressload, is_system, 0); } return cwErrFileNotFound; } /* ============================================================ * CW Plugin Core * ============================================================ */ CW_CALLBACK CWGetPluginRequest(CWPluginContext context, SInt32* request) { g_context = context; LOG("CWGetPluginRequest(context=%p)", context); if (!context || !request) return cwErrInvalidParameter; *request = context->request; LOG(" request=%d", *request); return cwNoErr; } CW_CALLBACK CWDonePluginRequest(CWPluginContext context, CWResult resultCode) { LOG("CWDonePluginRequest(result=%d)", resultCode); return cwNoErr; } CW_CALLBACK CWGetAPIVersion(CWPluginContext context, SInt32* version) { LOG("CWGetAPIVersion"); if (!context || !version) return cwErrInvalidParameter; *version = context->apiVersion; return cwNoErr; } CW_CALLBACK CWGetProjectFile(CWPluginContext context, CWFileSpec* projectSpec) { LOG("CWGetProjectFile"); if (!context || !projectSpec) return cwErrInvalidParameter; memset(projectSpec, 0, sizeof(*projectSpec)); // TODO: stub return cwNoErr; } CW_CALLBACK CWGetProjectFileCount(CWPluginContext context, SInt32* count) { LOG("CWGetProjectFileCount"); if (!context || !count) return cwErrInvalidParameter; *count = context->numFiles; return cwNoErr; } CW_CALLBACK CWGetOutputFileDirectory(CWPluginContext context, CWFileSpec* outputFileDirectory) { char outdir[MAX_PATH]; LOG("CWGetOutputFileDirectory"); if (!context || !outputFileDirectory) return cwErrInvalidParameter; if (context->outputFile[0]) { get_directory(context->outputFile, outdir, sizeof(outdir)); } else { DWORD n = GetCurrentDirectoryA(sizeof(outdir), outdir); if (n == 0 || n >= sizeof(outdir)) return cwErrRequestFailed; } cwfilespec_from_cpath(outputFileDirectory, outdir); return cwNoErr; } CW_CALLBACK CWGetFileInfo(CWPluginContext context, SInt32 whichfile, Boolean checkFileLocation, CWProjectFileInfo* fileinfo) { const char* path = NULL; LOG("CWGetFileInfo(whichfile=%d)", (int)whichfile); if (!context || !fileinfo) return cwErrInvalidParameter; memset(fileinfo, 0, sizeof(*fileinfo)); fileinfo->fileID = (short)whichfile; fileinfo->gendebug = context->debugInfo ? TRUE : FALSE; (void)checkFileLocation; if (whichfile == context->whichFile && context->sourceFile[0]) { path = context->sourceFile; } else { path = lookup_file_id_path(context, (short)whichfile); } if (path && path[0]) { cwfilespec_from_cpath(&fileinfo->filespec, path); GetSystemTimeAsFileTime(&fileinfo->moddate); return cwNoErr; } return cwErrUnknownFile; } CW_CALLBACK CWGetOverlay1GroupsCount(CWPluginContext context, SInt32* count) { LOG("CWGetOverlay1GroupsCount"); if (!context || !count) return cwErrInvalidParameter; *count = 0; return cwNoErr; } CW_CALLBACK CWGetOverlay1GroupInfo(CWPluginContext context, SInt32 whichgroup, CWOverlay1GroupInfo* groupinfo) { LOG("CWGetOverlay1GroupInfo(whichgroup=%d)", (int)whichgroup); if (!context || !groupinfo) return cwErrInvalidParameter; memset(groupinfo, 0, sizeof(*groupinfo)); return cwErrUnknownSegment; } CW_CALLBACK CWGetOverlay1Info(CWPluginContext context, SInt32 whichgroup, SInt32 whichoverlay, CWOverlay1Info* overlayinfo) { LOG("CWGetOverlay1Info(whichgroup=%d, whichoverlay=%d)", (int)whichgroup, (int)whichoverlay); if (!context || !overlayinfo) return cwErrInvalidParameter; memset(overlayinfo, 0, sizeof(*overlayinfo)); return cwErrUnknownSegment; } CW_CALLBACK CWGetOverlay1FileInfo(CWPluginContext context, SInt32 whichgroup, SInt32 whichoverlay, SInt32 whichoverlayfile, CWOverlay1FileInfo* fileinfo) { LOG("CWGetOverlay1FileInfo(whichgroup=%d, whichoverlay=%d, whichoverlayfile=%d)", (int)whichgroup, (int)whichoverlay, (int)whichoverlayfile); if (!context || !fileinfo) return cwErrInvalidParameter; memset(fileinfo, 0, sizeof(*fileinfo)); return cwErrUnknownSegment; } CW_CALLBACK CWAlert(CWPluginContext context, const char* msg1, const char* msg2, const char* msg3, const char* msg4) { LOG("CWAlert"); if (!context) return cwErrInvalidParameter; if (msg1) fprintf(stderr, "%s", msg1); if (msg2) fprintf(stderr, " %s", msg2); if (msg3) fprintf(stderr, " %s", msg3); if (msg4) fprintf(stderr, " %s", msg4); if (msg1 || msg2 || msg3 || msg4) fprintf(stderr, "\n"); return cwNoErr; } CW_CALLBACK CWShowStatus(CWPluginContext context, const char* line1, const char* line2) { if (line1) fprintf(stderr, "%s", line1); if (line2) fprintf(stderr, " %s", line2); if (line1 || line2) fprintf(stderr, "\n"); return cwNoErr; } CW_CALLBACK CWUserBreak(CWPluginContext context) { STUB("CWUserBreak"); return cwNoErr; } /* * The compiler reports line numbers in a shifted 16.16 form * (e.g. line 2 arrives as 0x00020000). Normalize before printing. */ static SInt32 normalize_message_line(SInt32 raw_line) { UInt32 line = (UInt32)raw_line; if ((line & 0xFFFFu) == 0 && (line >> 16) != 0) return (SInt32)(line >> 16); return raw_line; } CW_CALLBACK CWReportMessage(CWPluginContext ctx, const CWMessageRef* msgRef, const char* line1, const char* line2, short errorlevel, SInt32 errorNumber) { const char* level_str = "info"; SInt32 linenumber = 0; char source_path[MAX_PATH]; source_path[0] = '\0'; if (msgRef) { const CWMessageRef* msg = msgRef; linenumber = normalize_message_line(msg->linenumber); cwfilespec_to_cpath(&msg->sourcefile, source_path, sizeof(source_path)); } if (errorlevel == messagetypeWarning) { level_str = "warning"; ctx->numWarnings++; } else if (errorlevel == messagetypeError) { level_str = "error"; ctx->numErrors++; } if (source_path[0]) { fprintf(stderr, "%s:%d: %s: ", source_path, (int)linenumber, level_str); } else { fprintf(stderr, "%s: ", level_str); } if (line1) fprintf(stderr, "%s", line1); if (line2) fprintf(stderr, "\n %s", line2); fprintf(stderr, "\n"); return cwNoErr; } CW_CALLBACK CWSetModDate(CWPluginContext ctx, const CWFileSpec* filespec, CWFileTime* moddate, Boolean isGenerated) { STUB("CWSetModDate"); return cwNoErr; } CW_CALLBACK CWCreateNewTextDocument(CWPluginContext ctx, const CWNewTextDocumentInfo* docinfo) { LOG("CWCreateNewTextDocument"); /* Used for preprocessor output (-E mode) */ if (!ctx || !docinfo) return cwErrInvalidParameter; if (ctx->preprocess == 2 && !ctx->preprocess) { /* * MWCC dependency pass (-M/-MM/-make) runs with preprocess mode 2. * This pass is for dependency collection, not text emission. */ return cwNoErr; } if (docinfo->text) { void* ptr = NULL; SInt32 size = 0; SInt32 emitted = 0; CWGetMemHandleSize(ctx, docinfo->text, &size); CWLockMemHandle(ctx, docinfo->text, FALSE, &ptr); if (ptr) { /* Fall back to strlen if size is unknown (e.g. handle allocated * internally by the DLL without going through COS_NewHandle) */ if (size <= 0) size = (SInt32)strlen((const char*)ptr); /* Strip trailing null terminator if present (matches MWCC) */ if (size > 0 && ((const char*)ptr)[size - 1] == '\0') size--; if (size > 0) { /* * Normalize line endings: \r\n -> \n, \r -> \n. * The DLL uses Mac-convention \r for line endings. * Matches MWCC reference SendHandleToFile() behavior. */ const char* p = (const char*)ptr; const char* end = p + size; while (p < end) { const char* lineEnd = p; while (lineEnd < end && *lineEnd != '\r' && *lineEnd != '\n') lineEnd++; if (lineEnd > p) { fwrite(p, 1, lineEnd - p, stdout); emitted += (SInt32)(lineEnd - p); } fputc('\n', stdout); emitted++; if (lineEnd < end) { if (*lineEnd == '\r' && lineEnd + 1 < end && *(lineEnd + 1) == '\n') lineEnd++; lineEnd++; } p = lineEnd; } fflush(stdout); ctx->preprocessedTextSize += emitted; } CWUnlockMemHandle(ctx, docinfo->text); } } return cwNoErr; } /* ============================================================ * Memory Handle Management * ============================================================ */ CW_CALLBACK CWAllocateMemory(CWPluginContext ctx, SInt32 size, Boolean isPermanent, void** ptr) { void* p; LOG("CWAllocateMemory(size=%d, isPermanent=%d)", (int)size, (int)isPermanent); if (!ctx || !ptr) return cwErrInvalidParameter; if (size < 0) return cwErrInvalidParameter; if (size == 0) { *ptr = NULL; return cwNoErr; } p = calloc(1, (size_t)size); if (!p) return cwErrOutOfMemory; *ptr = p; return cwNoErr; } CW_CALLBACK CWFreeMemory(CWPluginContext ctx, void* ptr, Boolean isPermanent) { LOG("CWFreeMemory(ptr=%p, isPermanent=%d)", ptr, (int)isPermanent); if (!ctx) return cwErrInvalidParameter; if (ptr) free(ptr); return cwNoErr; } CW_CALLBACK CWAllocMemHandle(CWPluginContext ctx, SInt32 size, Boolean useTempMemory, CWMemHandle* handle) { LOG("CWAllocMemHandle(size=%d)", size); if (!handle) return cwErrInvalidParameter; CWMemHandleImpl* h = (CWMemHandleImpl*)calloc(1, sizeof(CWMemHandleImpl)); if (!h) return cwErrOutOfMemory; if (size > 0) { h->data = calloc(1, size); if (!h->data) { free(h); return cwErrOutOfMemory; } } h->size = size; h->locked = 0; *handle = (CWMemHandle)h; return cwNoErr; } CW_CALLBACK CWFreeMemHandle(CWPluginContext ctx, CWMemHandle handle) { LOG("CWFreeMemHandle"); if (!handle) return cwErrInvalidParameter; CWMemHandleImpl* h = (CWMemHandleImpl*)handle; if (h->data) free(h->data); free(h); return cwNoErr; } CW_CALLBACK CWGetMemHandleSize(CWPluginContext ctx, CWMemHandle handle, SInt32* size) { if (!handle || !size) return cwErrInvalidParameter; CWMemHandleImpl* h = (CWMemHandleImpl*)handle; *size = h->size; return cwNoErr; } CW_CALLBACK CWResizeMemHandle(CWPluginContext ctx, CWMemHandle handle, SInt32 newSize) { LOG("CWResizeMemHandle(newSize=%d)", newSize); if (!handle) return cwErrInvalidParameter; CWMemHandleImpl* h = (CWMemHandleImpl*)handle; void* newdata = realloc(h->data, newSize); if (!newdata && newSize > 0) return cwErrOutOfMemory; h->data = newdata; h->size = newSize; return cwNoErr; } CW_CALLBACK CWLockMemHandle(CWPluginContext ctx, CWMemHandle handle, Boolean moveHi, void** ptr) { if (!handle || !ptr) return cwErrInvalidParameter; CWMemHandleImpl* h = (CWMemHandleImpl*)handle; h->locked++; *ptr = h->data; return cwNoErr; } CW_CALLBACK CWUnlockMemHandle(CWPluginContext ctx, CWMemHandle handle) { if (!handle) return cwErrInvalidParameter; CWMemHandleImpl* h = (CWMemHandleImpl*)handle; if (h->locked > 0) h->locked--; return cwNoErr; } /* ============================================================ * Source File Access * ============================================================ */ CW_CALLBACK CWGetMainFileSpec(CWPluginContext ctx, CWFileSpec* fileSpec) { LOG("CWGetMainFileSpec"); if (!ctx || !fileSpec) return cwErrInvalidParameter; cwfilespec_from_cpath(fileSpec, ctx->sourceFile); return cwNoErr; } CW_CALLBACK CWGetMainFileText(CWPluginContext ctx, const char** text, SInt32* textLength) { LOG("CWGetMainFileText"); if (!ctx || !text || !textLength) return cwErrInvalidParameter; if (!ctx->sourceText) return cwErrFileNotFound; *text = ctx->sourceText; *textLength = ctx->sourceTextSize; return cwNoErr; } CW_CALLBACK CWGetMainFileNumber(CWPluginContext ctx, SInt32* fileNumber) { LOG("CWGetMainFileNumber"); if (!ctx || !fileNumber) return cwErrInvalidParameter; *fileNumber = ctx->whichFile; return cwNoErr; } CW_CALLBACK CWGetMainFileID(CWPluginContext ctx, short* fileID) { LOG("CWGetMainFileID"); if (!ctx || !fileID) return cwErrInvalidParameter; *fileID = (short)(ctx->whichFile + 1); return cwNoErr; } CW_CALLBACK CWGetFileText(CWPluginContext ctx, const CWFileSpec* filespec, const char** text, SInt32* textLength, short* filedatatype) { char path[MAX_PATH]; path[0] = '\0'; if (filespec) { cwfilespec_to_cpath(filespec, path, sizeof(path)); } LOG("CWGetFileText(%s)", filespec ? path : "NULL"); if (!ctx || !filespec || !text || !textLength) return cwErrInvalidParameter; SInt32 size; char* buf = read_and_fix_file(path, &size); if (!buf) return cwErrFileNotFound; *text = buf; *textLength = size; if (filedatatype) *filedatatype = cwFileTypeText; return cwNoErr; } CW_CALLBACK CWReleaseFileText(CWPluginContext ctx, const char* text) { LOG("CWReleaseFileText"); if (text && ctx) { /* Don't free the main source text - we own that */ if (text != ctx->sourceText) { free((void*)text); } } return cwNoErr; } static int is_cmdline_defines_name(const char* filename) { const size_t name_len = strlen(CMDLINE_DEFINES_VFILE); static const char* alt_name = "command-line defines)"; const size_t alt_len = 21; if (!filename) return 0; /* C-string form */ if (strncmp(filename, CMDLINE_DEFINES_VFILE, name_len) == 0 && filename[name_len] == '\0') return 1; if (strncmp(filename, alt_name, alt_len) == 0 && filename[alt_len] == '\0') return 1; /* Pascal Str31 form */ if ((unsigned char)filename[0] == name_len && memcmp(filename + 1, CMDLINE_DEFINES_VFILE, name_len) == 0) return 1; if ((unsigned char)filename[0] == alt_len && memcmp(filename + 1, alt_name, alt_len) == 0) return 1; return 0; } CW_CALLBACK CWFindAndLoadFile(CWPluginContext ctx, const char* filename, CWFileInfo* fileinfo) { CWFileInfo* fileinfo_out; if (!ctx || !filename || !fileinfo) return cwErrInvalidParameter; fileinfo_out = fileinfo; /* Keep request fields before clearing output. */ Boolean fullsearch = fileinfo_out->fullsearch; SInt32 dependent_file = fileinfo_out->isdependentoffile; Boolean suppressload = fileinfo_out->suppressload; /* * Copy filename BEFORE memset: the DLL may pass a pointer that overlaps * with fileinfo storage. */ char fname[MAX_PATH]; char special_dir[MAX_PATH]; int have_special_dir = 0; if (ctx->fileRecordCount == 0 && ctx->sourceFile[0]) { record_file_id(ctx, (short)(ctx->whichFile + 1), ctx->sourceFile, FALSE); } cw_filename_arg_to_cpath(filename, fname, sizeof(fname)); LOG("CWFindAndLoadFile(%s, fullsearch=%d, dep=%d, suppress=%d)", fname, (int)fullsearch, (int)dependent_file, (int)suppressload); if (is_cmdline_defines_name(fname)) { filename = CMDLINE_DEFINES_VFILE; } else { filename = fname; } memset(fileinfo_out, 0, sizeof(*fileinfo_out)); /* MWCC-style command-line virtual prefix file. */ if (ctx->defineText && ctx->defineTextLen > 0 && strcmp(filename, CMDLINE_DEFINES_VFILE) == 0) { if (!suppressload) { char* buf = (char*)malloc((size_t)ctx->defineTextLen + 1); if (!buf) return cwErrOutOfMemory; memcpy(buf, ctx->defineText, (size_t)ctx->defineTextLen); buf[ctx->defineTextLen] = '\0'; fileinfo_out->filedata = buf; fileinfo_out->filedatalength = ctx->defineTextLen; } else { fileinfo_out->filedata = NULL; fileinfo_out->filedatalength = 0; } fileinfo_out->filedatatype = cwFileTypeText; fileinfo_out->fileID = 0; cwfilespec_from_cpath(&fileinfo_out->filespec, CMDLINE_DEFINES_VFILE); fileinfo_out->alreadyincluded = FALSE; fileinfo_out->recordbrowseinfo = FALSE; return cwNoErr; } fullsearch = (fullsearch || ctx->noSysPath) ? TRUE : FALSE; if (is_full_path(filename)) { if (load_file_for_include(filename, fileinfo, ctx, suppressload, FALSE) == cwNoErr) { return cwNoErr; } } switch (ctx->includeSearchMode) { case hostIncludeSearchProj: have_special_dir = GetCurrentDirectoryA(MAX_PATH, special_dir) > 0; break; case hostIncludeSearchSource: if (ctx->sourceFile[0]) { get_directory(ctx->sourceFile, special_dir, sizeof(special_dir)); have_special_dir = 1; } break; case hostIncludeSearchInclude: if (fullsearch && dependent_file >= 0) { const char* dep_path = lookup_file_id_path(ctx, (short)dependent_file); if (dep_path && dep_path[0]) { get_directory(dep_path, special_dir, sizeof(special_dir)); have_special_dir = 1; } } if (fullsearch && !have_special_dir && ctx->lastIncludeDir[0]) { copy_cstr(special_dir, sizeof(special_dir), ctx->lastIncludeDir); have_special_dir = 1; } if (!have_special_dir && ctx->sourceFile[0]) { get_directory(ctx->sourceFile, special_dir, sizeof(special_dir)); have_special_dir = 1; } break; case hostIncludeSearchExplicit: default: break; } if (have_special_dir && try_load_include(special_dir, filename, fileinfo, ctx, suppressload, FALSE, FALSE) == cwNoErr) { return cwNoErr; } if (fullsearch) { for (SInt32 i = 0; i < ctx->userPathCount; i++) { if (try_load_include(ctx->userPaths[i].path, filename, fileinfo, ctx, suppressload, ctx->userPaths[i].recursive, FALSE) == cwNoErr) return cwNoErr; } } for (SInt32 i = 0; i < ctx->systemPathCount; i++) { if (try_load_include(ctx->systemPaths[i].path, filename, fileinfo, ctx, suppressload, ctx->systemPaths[i].recursive, TRUE) == cwNoErr) return cwNoErr; } fprintf(stderr, "Cannot find include file: %s\n", filename); return cwErrFileNotFound; } /* ============================================================ * Object Data Storage * ============================================================ */ CW_CALLBACK CWStoreObjectData(CWPluginContext ctx, SInt32 whichfile, CWObjectData* object) { LOG("CWStoreObjectData(whichfile=%d, codesize=%d, udatasize=%d, idatasize=%d)", whichfile, object ? object->codesize : 0, object ? object->udatasize : 0, object ? object->idatasize : 0); if (!ctx || !object) return cwErrInvalidParameter; ctx->storedObject = *object; ctx->objectStored = 1; /* Copy the object data to our own buffer */ if (object->objectdata) { void* ptr = NULL; SInt32 size = 0; CWGetMemHandleSize(ctx, object->objectdata, &size); CWLockMemHandle(ctx, object->objectdata, FALSE, &ptr); LOG(" objectdata: ptr=%p, size=%d", ptr, size); if (ptr && size > 0) { ctx->objectData = malloc(size); if (ctx->objectData) { memcpy(ctx->objectData, ptr, size); ctx->objectDataSize = size; LOG(" Captured %d bytes of object data", size); } } else if (ptr) { /* Size unknown (from CWSecretAttachHandle), use codesize+udatasize+idatasize */ SInt32 totalSize = object->codesize + object->udatasize + object->idatasize; if (totalSize <= 0) totalSize = 4096; /* fallback guess */ LOG(" Using computed size: %d", totalSize); ctx->objectData = malloc(totalSize); if (ctx->objectData) { memcpy(ctx->objectData, ptr, totalSize); ctx->objectDataSize = totalSize; LOG(" Captured %d bytes of object data (computed)", totalSize); } } CWUnlockMemHandle(ctx, object->objectdata); } return cwNoErr; } CW_CALLBACK CWLoadObjectData(CWPluginContext ctx, SInt32 whichfile, CWMemHandle* objectdata) { LOG("CWLoadObjectData(whichfile=%d)", whichfile); if (!ctx || !objectdata) return cwErrInvalidParameter; if (!ctx->objectData || ctx->objectDataSize <= 0) { return cwErrObjectFileNotStored; } CWMemHandle h = NULL; CWResult r = CWAllocMemHandle(ctx, ctx->objectDataSize, FALSE, &h); if (r != cwNoErr) return r; void* ptr = NULL; r = CWLockMemHandle(ctx, h, FALSE, &ptr); if (r != cwNoErr || !ptr) { CWFreeMemHandle(ctx, h); return r != cwNoErr ? r : cwErrRequestFailed; } memcpy(ptr, ctx->objectData, (size_t)ctx->objectDataSize); CWUnlockMemHandle(ctx, h); *objectdata = h; return cwNoErr; } CW_CALLBACK CWFreeObjectData(CWPluginContext ctx, SInt32 whichfile, CWMemHandle objectdata) { LOG("CWFreeObjectData(whichfile=%d, objectdata=%p)", (int)whichfile, objectdata); if (!ctx) return cwErrInvalidParameter; if (!objectdata) return cwNoErr; return CWFreeMemHandle(ctx, objectdata); } /* ============================================================ * Preferences * ============================================================ */ static void build_mips_codegen_panel(CWPluginContext ctx) { memcpy(ctx->prefsMIPSCodeGenPanel, &ctx->prefsMIPSCodeGen, sizeof(ctx->prefsMIPSCodeGenPanel)); if (ctx->prefsMIPSCodeGenR4Compat) { /* * R4 reads PMIPSCodeGen with a different layout than R5/R5.2. * Keep a 20-byte panel, but patch overlapping bytes so both * layouts see sensible values. */ ctx->prefsMIPSCodeGenPanel[0x04] = (UInt8)(ctx->prefsMIPSCodeGen.processor & 0xFF); ctx->prefsMIPSCodeGenPanel[0x05] = 0; ctx->prefsMIPSCodeGenPanel[0x06] = (UInt8)(ctx->prefsMIPSCodeGen.fpuType & 0xFF); ctx->prefsMIPSCodeGenPanel[0x07] = (ctx->prefsMIPSCodeGen.fpuType != 0) ? 1 : 0; ctx->prefsMIPSCodeGenPanel[0x0C] = ctx->prefsMIPSCodeGen.useIntrinsics; } } static void build_mips_linker_panel(CWPluginContext ctx) { memcpy(ctx->prefsMIPSLinkerPanel, &ctx->prefsMIPSLinker, sizeof(ctx->prefsMIPSLinkerPanel)); /* * R4 reads genOutput at 0x03, R5/R5.2 reads 0x05. * Mirror to both offsets to avoid version branching. */ ctx->prefsMIPSLinkerPanel[0x03] = ctx->prefsMIPSLinker.genOutput; ctx->prefsMIPSLinkerPanel[0x05] = ctx->prefsMIPSLinker.genOutput; } CW_CALLBACK CWSecretGetNamedPreferences(CWPluginContext ctx, const char* prefsname, void** prefsdata) { LOG("CWSecretGetNamedPreferences(\"%s\", %p)", prefsname ? prefsname : "NULL", prefsdata); if (!ctx || !prefsname || !prefsdata) return cwErrInvalidParameter; const void* prefs = NULL; SInt32 prefsSize = 0; if (strcmp(prefsname, "C/C++ Compiler") == 0) { prefs = &ctx->prefsFrontEnd; prefsSize = sizeof(ctx->prefsFrontEnd); } else if (strcmp(prefsname, "C/C++ Warnings") == 0) { prefs = &ctx->prefsWarnings; prefsSize = sizeof(ctx->prefsWarnings); } else if (strcmp(prefsname, "Global Optimizer") == 0 || strcmp(prefsname, "PS Global Optimizer") == 0 || strcmp(prefsname, "EPPC Global Optimizer") == 0) { prefs = &ctx->prefsOptimizer; prefsSize = sizeof(ctx->prefsOptimizer); } else if (strcmp(prefsname, "MIPS CodeGen") == 0) { build_mips_codegen_panel(ctx); prefs = ctx->prefsMIPSCodeGenPanel; prefsSize = sizeof(ctx->prefsMIPSCodeGenPanel); } else if (strcmp(prefsname, "MIPS Linker Panel") == 0) { build_mips_linker_panel(ctx); prefs = ctx->prefsMIPSLinkerPanel; prefsSize = sizeof(ctx->prefsMIPSLinkerPanel); } else if (strcmp(prefsname, "MIPS Project") == 0) { prefs = &ctx->prefsMIPSProject; prefsSize = sizeof(ctx->prefsMIPSProject); } else if (strcmp(prefsname, "IR Optimizer") == 0) { /* CW PS R4/R4.1 vestigial panel: data never read, just return zeros */ prefsSize = 12; } /* ---- PPC EABI panels (GC/Wii target) ---- */ else if (strcmp(prefsname, "PPC EABI CodeGen") == 0) { prefs = &ctx->prefsPPCCodeGen; prefsSize = sizeof(ctx->prefsPPCCodeGen); } else if (strcmp(prefsname, "PPC EABI Linker") == 0) { prefs = &ctx->prefsPPCLinker; prefsSize = sizeof(ctx->prefsPPCLinker); } else if (strcmp(prefsname, "PPC EABI Project") == 0) { prefs = &ctx->prefsPPCProject; prefsSize = sizeof(ctx->prefsPPCProject); } else if (strcmp(prefsname, "C/C++ Preprocessor") == 0) { prefs = &ctx->prefsPreprocessor; prefsSize = sizeof(ctx->prefsPreprocessor); } else { /* Return a zeroed blob for any unknown panel */ LOG(" Unknown preference panel: %s", prefsname); prefsSize = 256; } CWAllocMemHandle(ctx, prefsSize, FALSE, (CWMemHandle*)prefsdata); if (prefs && prefsSize > 0) { void* ptr = NULL; CWLockMemHandle(ctx, *(CWMemHandle*)prefsdata, FALSE, &ptr); if (ptr) { memcpy(ptr, prefs, (size_t)prefsSize); } CWUnlockMemHandle(ctx, *(CWMemHandle*)prefsdata); } return cwNoErr; } CW_CALLBACK CWGetNamedPreferences(CWPluginContext ctx, const char* prefsname, CWMemHandle* prefsdata) { LOG("CWGetNamedPreferences(\"%s\", %p)", prefsname ? prefsname : "NULL", prefsdata); return CWSecretGetNamedPreferences(ctx, prefsname, (void**)prefsdata); } /* ============================================================ * Compiler State Queries * ============================================================ */ CW_CALLBACK CWIsPrecompiling(CWPluginContext context, Boolean* isPrecompiling) { LOG("CWIsPrecompiling"); if (!context || !isPrecompiling) return cwErrInvalidParameter; *isPrecompiling = FALSE; // TODO: stub return cwNoErr; } CW_CALLBACK CWIsAutoPrecompiling(CWPluginContext context, Boolean* isAutoPrecompiling) { LOG("CWIsAutoPrecompiling"); if (!context || !isAutoPrecompiling) return cwErrInvalidParameter; *isAutoPrecompiling = FALSE; // TODO: stub return cwNoErr; } CW_CALLBACK CWIsPreprocessing(CWPluginContext context, Boolean* isPreprocessing) { LOG("CWIsPreprocessing"); if (!context || !isPreprocessing) return cwErrInvalidParameter; *isPreprocessing = context->preprocess; return cwNoErr; } CW_CALLBACK CWIsGeneratingDebugInfo(CWPluginContext context, Boolean* isGenerating) { LOG("CWIsGeneratingDebugInfo"); if (!context || !isGenerating) return cwErrInvalidParameter; *isGenerating = context->debugInfo; return cwNoErr; } CW_CALLBACK CWIsCachingPrecompiledHeaders(CWPluginContext context, Boolean* isCaching) { LOG("CWIsCachingPrecompiledHeaders"); if (!context || !isCaching) return cwErrInvalidParameter; *isCaching = FALSE; return cwNoErr; } CW_CALLBACK CWGetBrowseOptions(CWPluginContext context, CWBrowseOptions* browseOptions) { LOG("CWGetBrowseOptions"); if (!context || !browseOptions) return cwErrInvalidParameter; memset(browseOptions, 0, sizeof(*browseOptions)); // TODO: stub return cwNoErr; } CW_CALLBACK CWGetTargetInfo(CWPluginContext ctx, CWTargetInfo* targetInfo) { LOG("CWGetTargetInfo"); if (!ctx || !targetInfo) return cwErrInvalidParameter; memset(targetInfo, 0, sizeof(CWTargetInfo)); targetInfo->targetCPU = targetCPUMips; // TODO targetInfo->targetOS = targetOSAny; targetInfo->linkType = exelinkageFlat; targetInfo->outputType = linkOutputFile; // TODO return cwNoErr; } CW_CALLBACK CWGetBuildSequenceNumber(CWPluginContext ctx, SInt32* sequenceNumber) { LOG("CWGetBuildSequenceNumber"); if (!ctx || !sequenceNumber) return cwErrInvalidParameter; *sequenceNumber = 0; return cwNoErr; } CW_CALLBACK CWSetTargetInfo(CWPluginContext ctx, CWTargetInfo* targetInfo) { LOG("CWSetTargetInfo"); if (!ctx || !targetInfo) return cwErrInvalidParameter; return cwNoErr; } CW_CALLBACK CWGetTargetName(CWPluginContext ctx, char* name, short maxLength) { static const char* kTargetName = "command-line target"; size_t n; LOG("CWGetTargetName"); if (!ctx || !name || maxLength <= 0) return cwErrInvalidParameter; n = (size_t)maxLength; strncpy(name, kTargetName, n); name[n - 1] = '\0'; return cwNoErr; } /* ============================================================ * Precompiled Headers * ============================================================ */ CW_CALLBACK CWCachePrecompiledHeader(CWPluginContext context, const CWFileSpec* filespec, CWMemHandle pchhandle) { STUB("CWCachePrecompiledHeader"); return cwNoErr; } CW_CALLBACK CWGetPrecompiledHeaderSpec(CWPluginContext context, CWFileSpec* pchspec, const char* target) { STUB("CWGetPrecompiledHeaderSpec"); if (pchspec) memset(pchspec, 0, sizeof(CWFileSpec)); return cwErrFileNotFound; } /* ============================================================ * Secret/Internal * ============================================================ */ CW_CALLBACK CWSecretAttachHandle(CWPluginContext context, HandleStructure* handle, CWMemHandle* memHandle) { LOG("CWSecretAttachHandle(handle=%p)", handle); if (!memHandle) return cwErrInvalidParameter; /* * Wrap a Handle (HandleStructure*) into a CWMemHandle. * The handle's first field (addr) is the cached data pointer. * Size is read from hand.used in the HandleStructure. */ CWMemHandleImpl* h = (CWMemHandleImpl*)calloc(1, sizeof(CWMemHandleImpl)); if (!h) return cwErrOutOfMemory; if (handle) { h->data = handle->addr; h->size = (SInt32)handle->hand.used; LOG(" CWSecretAttachHandle: data=%p, size=%d", h->data, h->size); } h->locked = 0; *memHandle = (CWMemHandle)h; return cwNoErr; } CW_CALLBACK CWSecretPeekHandle(CWPluginContext context, CWMemHandle memHandle, HandleStructure** handle) { CWMemHandleImpl* h = (CWMemHandleImpl*)memHandle; LOG("CWSecretPeekHandle(memHandle=%p)", memHandle); if (!context || !handle) return cwErrInvalidParameter; if (!h) { *handle = NULL; return cwNoErr; } *handle = (HandleStructure*)calloc(1, sizeof(HandleStructure)); if (!*handle) return cwErrOutOfMemory; if (h->size > 0) { (*handle)->hand.addr = malloc((size_t)h->size); if (!(*handle)->hand.addr) { free(*handle); *handle = NULL; return cwErrOutOfMemory; } (*handle)->addr = (char*)(*handle)->hand.addr; (*handle)->hand.used = (UInt32)h->size; (*handle)->hand.size = (UInt32)h->size; if (h->data) memcpy((*handle)->addr, h->data, (size_t)h->size); } return cwNoErr; } /* ============================================================ * Display * ============================================================ */ CW_CALLBACK CWDisplayLines(CWPluginContext ctx, SInt32 nlines) { LOG("CWDisplayLines(%d)", nlines); return cwNoErr; } /* ============================================================ * Licensing (stub - bypass) * ============================================================ */ CW_CALLBACK CWCheckoutLicense(CWPluginContext context, const char* featureName, const char* licenseVersion, SInt32 flags, void* reserved, SInt32* cookie) { LOG("CWCheckoutLicense(\"%s\", \"%s\")", featureName ? featureName : "NULL", licenseVersion ? licenseVersion : "NULL"); if (cookie) *cookie = 1; /* fake cookie */ return cwNoErr; } CW_CALLBACK CWCheckinLicense(CWPluginContext context, SInt32 cookie) { LOG("CWCheckinLicense(%d)", cookie); return cwNoErr; } /* ============================================================ * COS Handle Management * ============================================================ */ static int cos_handle_count = 0; void* __cdecl COS_NewHandle(SInt32 byteCount) { cos_handle_count++; LOG("COS_NewHandle(%d) [#%d]", byteCount, cos_handle_count); if (byteCount <= 0) byteCount = 1; HandleStructure* hs = (HandleStructure*)calloc(1, sizeof(HandleStructure)); if (!hs) { LOG(" COS_NewHandle: handle alloc FAILED"); return NULL; } /* Allocate the data block (round up to 256-byte boundary) */ UInt32 allocSize = (byteCount + 255) & ~255; hs->hand.addr = calloc(1, allocSize); if (!hs->hand.addr) { free(hs); LOG(" COS_NewHandle: data alloc FAILED for %d bytes", byteCount); return NULL; } hs->hand.used = byteCount; hs->hand.size = allocSize; hs->addr = (char*)hs->hand.addr; /* Cache the data pointer at offset 0 */ LOG(" COS_NewHandle: handle=%p, *handle(data)=%p, used=%u, size=%u", (void*)hs, (void*)hs->addr, hs->hand.used, hs->hand.size); return (void*)hs; } static int cos_oshandle_count = 0; void* __cdecl COS_NewOSHandle(SInt32 logicalSize) { cos_oshandle_count++; LOG("COS_NewOSHandle(%d) [#%d] -> delegating to COS_NewHandle", logicalSize, cos_oshandle_count); return COS_NewHandle(logicalSize); } void __cdecl COS_FreeHandle(HandleStructure* handle) { LOG("COS_FreeHandle(%p)", handle); if (handle) { if (handle->hand.addr) free(handle->hand.addr); handle->addr = NULL; handle->hand.addr = NULL; handle->hand.used = 0; handle->hand.size = 0; free(handle); } } int __cdecl COS_ResizeHandle(HandleStructure* handle, SInt32 newSize) { LOG("COS_ResizeHandle(%p, %d)", handle, newSize); if (!handle) return 0; UInt32 allocSize = (newSize + 255) & ~255; void* newdata = realloc(handle->hand.addr, allocSize > 0 ? allocSize : 256); if (!newdata) return 0; handle->hand.addr = newdata; handle->hand.used = newSize; handle->hand.size = allocSize; handle->addr = (char*)newdata; /* Update cached pointer */ return 1; } void* __cdecl COS_LockHandle(HandleStructure* handle) { LOG("COS_LockHandle(%p)", handle); if (!handle) return NULL; handle->addr = (char*)handle->hand.addr; /* Refresh cached pointer */ LOG(" COS_LockHandle: *handle=%p", (void*)handle->addr); return handle->addr; } void* __cdecl COS_LockHandleHi(HandleStructure* handle) { LOG("COS_LockHandleHi(%p)", handle); if (!handle) return NULL; handle->addr = (char*)handle->hand.addr; /* Refresh cached pointer */ LOG(" COS_LockHandleHi: *handle=%p", (void*)handle->addr); return handle->addr; } void __cdecl COS_UnlockHandle(HandleStructure* handle) { /* no-op on flat memory system */ } /* ============================================================ * COS File I/O * ============================================================ */ typedef SInt32 OSErr; typedef SInt32 OSType; typedef unsigned char* StringPtr; typedef const unsigned char* ConstStringPtr; typedef struct COSOpenFile { FILE* fp; char path[MAX_PATH]; } COSOpenFile; #define COS_MAX_OPEN_FILES 256 static COSOpenFile g_open_files[COS_MAX_OPEN_FILES]; static SInt16 cos_alloc_refnum(FILE* fp, const char* path) { for (SInt16 i = 1; i < COS_MAX_OPEN_FILES; i++) { if (!g_open_files[i].fp) { g_open_files[i].fp = fp; if (path) { copy_cstr(g_open_files[i].path, MAX_PATH, path); } else { g_open_files[i].path[0] = '\0'; } return i; } } return -1; } static FILE* cos_get_fp(SInt16 refNum) { if (refNum <= 0 || refNum >= COS_MAX_OPEN_FILES) return NULL; return g_open_files[refNum].fp; } static void cos_release_refnum(SInt16 refNum) { if (refNum <= 0 || refNum >= COS_MAX_OPEN_FILES) return; g_open_files[refNum].fp = NULL; g_open_files[refNum].path[0] = '\0'; } static const char* cos_basename(const char* path) { const char* p; if (!path) return ""; p = strrchr(path, '/'); if (!p) p = strrchr(path, '\\'); return p ? (p + 1) : path; } static void cos_c_to_pascal(const char* src, StringPtr dst, size_t dst_cap) { size_t len; if (!dst || dst_cap == 0) return; if (!src) src = ""; len = strlen(src); if (len > 255) len = 255; if (len + 1 > dst_cap) len = dst_cap - 1; dst[0] = (unsigned char)len; if (len > 0) memcpy(dst + 1, src, len); if (len + 1 < dst_cap) dst[len + 1] = '\0'; } static void cos_pascal_to_c(ConstStringPtr src, char* dst, size_t dst_cap) { size_t len; if (!src || !dst || dst_cap == 0) return; len = src[0]; if (len + 1 > dst_cap) len = dst_cap - 1; if (len > 0) memcpy(dst, src + 1, len); dst[len] = '\0'; } OSErr __cdecl COS_FileNew(const CWFileSpec* spec, SInt16* refNum, OSType creator, OSType fileType) { FILE* fp; SInt16 ref; char path[MAX_PATH]; path[0] = '\0'; (void)creator; (void)fileType; if (spec) { cwfilespec_to_cpath(spec, path, sizeof(path)); } LOG("COS_FileNew(%s)", spec ? path : "NULL"); if (!spec || !refNum || !path[0]) return -1; fp = fopen(path, "wb"); if (!fp) return -1; fclose(fp); fp = fopen(path, "r+b"); if (!fp) return -1; ref = cos_alloc_refnum(fp, path); if (ref < 0) { fclose(fp); return -1; } *refNum = ref; return 0; } OSErr __cdecl COS_FileOpen(const CWFileSpec* spec, SInt16* refNum) { FILE* fp; SInt16 ref; char path[MAX_PATH]; path[0] = '\0'; if (spec) { cwfilespec_to_cpath(spec, path, sizeof(path)); } LOG("COS_FileOpen(%s)", spec ? path : "NULL"); if (!spec || !refNum || !path[0]) return -1; fp = fopen(path, "rb"); if (!fp) return -1; ref = cos_alloc_refnum(fp, path); if (ref < 0) { fclose(fp); return -1; } *refNum = ref; return 0; } OSErr __cdecl COS_FileGetType(const CWFileSpec* spec, OSType* fileType) { (void)spec; if (!fileType) return -1; *fileType = CWFOURCHAR('T', 'E', 'X', 'T'); return 0; } OSErr __cdecl COS_FileGetSize(SInt16 refNum, SInt32* size) { FILE* fp = cos_get_fp(refNum); long pos; if (!fp || !size) return -1; pos = ftell(fp); if (pos < 0) return -1; if (fseek(fp, 0, SEEK_END) != 0) return -1; *size = (SInt32)ftell(fp); if (fseek(fp, pos, SEEK_SET) != 0) return -1; return 0; } OSErr __cdecl COS_FileRead(SInt16 refNum, void* buf, SInt32 size) { FILE* fp = cos_get_fp(refNum); size_t r; if (!fp || !buf || size < 0) return -1; r = fread(buf, 1, (size_t)size, fp); return (r == (size_t)size) ? 0 : -1; } OSErr __cdecl COS_FileWrite(SInt16 refNum, const void* buf, SInt32 size) { FILE* fp = cos_get_fp(refNum); size_t w; if (!fp || !buf || size < 0) return -1; w = fwrite(buf, 1, (size_t)size, fp); return (w == (size_t)size) ? 0 : -1; } OSErr __cdecl COS_FileGetPos(SInt16 refNum, SInt32* pos) { FILE* fp = cos_get_fp(refNum); long v; if (!fp || !pos) return -1; v = ftell(fp); if (v < 0) return -1; *pos = (SInt32)v; return 0; } OSErr __cdecl COS_FileSetPos(SInt16 refNum, SInt32 pos) { FILE* fp = cos_get_fp(refNum); if (!fp) return -1; return (fseek(fp, pos, SEEK_SET) == 0) ? 0 : -1; } OSErr __cdecl COS_FileClose(SInt16 refNum) { FILE* fp = cos_get_fp(refNum); LOG("COS_FileClose(%d)", refNum); if (!fp) return -1; fclose(fp); cos_release_refnum(refNum); return 0; } void __cdecl COS_FileSetFSSpec(CWFileSpec* spec, ConstStringPtr path) { UInt8 len; char tmp[MAX_PATH]; LOG("COS_FileSetFSSpec"); if (!spec || !path) return; len = path[0]; if (len > 0 && len < MAX_PATH && memchr(path + 1, '\0', len) == NULL) { cos_pascal_to_c(path, tmp, sizeof(tmp)); } else { copy_cstr(tmp, sizeof(tmp), (const char*)path); } cwfilespec_from_cpath(spec, tmp); } void __cdecl COS_FileGetFSSpecInfo(const CWFileSpec* spec, SInt16* vRefNum, SInt32* dirID, StringPtr fileName) { char path[MAX_PATH]; path[0] = '\0'; if (spec) { cwfilespec_to_cpath(spec, path, sizeof(path)); } if (vRefNum) *vRefNum = 0; if (dirID) *dirID = 0; if (fileName) cos_c_to_pascal(spec ? cos_basename(path) : "", fileName, 256); } void __cdecl COS_FileGetPathName(char* buffer, const CWFileSpec* spec, SInt32* mdDat) { struct stat st; char path[MAX_PATH]; path[0] = '\0'; if (!buffer) return; if (spec) { cwfilespec_to_cpath(spec, path, sizeof(path)); } if (spec && path[0]) { copy_cstr(buffer, MAX_PATH, path); } else { buffer[0] = '\0'; } if (mdDat) { if (spec && path[0] && stat(path, &st) == 0) *mdDat = (SInt32)st.st_mtime; else *mdDat = 0; } } /* ============================================================ * COS Utility * ============================================================ */ UInt32 __cdecl COS_GetTicks(void) { return GetTickCount(); } /* ============================================================ * COS_GetString - String table from Mac resource fork * * The compiler DLL embeds a Mac resource fork as a Win32 custom * resource ("MACRSRC", ID 101). This contains STR# resources * with compiler error/warning message templates. * * The DLL's frontend code calls COS_GetString(buf, listID, idx) * to look up error messages by (strListID, 1-based index). * ============================================================ */ #define MAX_STR_LISTS 16 #define MAX_STRINGS_PER_LIST 512 typedef struct { SInt16 strListID; int numStrings; char* strings[MAX_STRINGS_PER_LIST]; } CachedStringList; static CachedStringList cached_str_lists[MAX_STR_LISTS]; static int num_cached_str_lists = 0; static int string_table_initialized = 0; /* Big-endian read helpers for Mac resource fork parsing */ static UInt32 read_be32(const unsigned char* p) { return ((UInt32)p[0] << 24) | ((UInt32)p[1] << 16) | ((UInt32)p[2] << 8) | p[3]; } static UInt16 read_be16(const unsigned char* p) { return ((UInt16)p[0] << 8) | p[1]; } static UInt32 read_be24(const unsigned char* p) { return ((UInt32)p[0] << 16) | ((UInt32)p[1] << 8) | p[2]; } /* * Decode a Pascal string from the Mac resource fork. * Handles Mac-Roman special characters: smart quotes -> ASCII quotes, * ellipsis (0xC9) -> "..." */ static char* decode_mac_pascal_string(const unsigned char* src, int len) { /* Worst case: each byte expands to 3 chars (ellipsis) */ char* str = (char*)malloc(len * 3 + 1); if (!str) return NULL; int out = 0; for (int i = 0; i < len; i++) { unsigned char ch = src[i]; switch (ch) { case 0xD4: str[out++] = '`'; break; /* open single quote */ case 0xD5: str[out++] = '\''; break; /* close single quote */ case 0xD2: str[out++] = '"'; break; /* open double quote */ case 0xD3: str[out++] = '"'; break; /* close double quote */ case 0xC9: str[out++] = '.'; str[out++] = '.'; str[out++] = '.'; break; default: str[out++] = (char)ch; break; } } str[out] = '\0'; return str; } /* * Parse a Mac STR# resource and cache the strings. * STR# format: 2-byte count (big-endian), then count Pascal strings * (1-byte length + string data). */ static void parse_str_list(const unsigned char* data, UInt32 dataLen, SInt16 resID) { if (num_cached_str_lists >= MAX_STR_LISTS) return; if (dataLen < 2) return; UInt16 numStrings = read_be16(data); if (numStrings > MAX_STRINGS_PER_LIST) numStrings = MAX_STRINGS_PER_LIST; CachedStringList* sl = &cached_str_lists[num_cached_str_lists++]; sl->strListID = resID; sl->numStrings = numStrings; memset(sl->strings, 0, sizeof(sl->strings)); UInt32 pos = 2; for (int i = 0; i < numStrings; i++) { if (pos >= dataLen) break; unsigned char slen = data[pos]; if (pos + 1 + slen > dataLen) break; sl->strings[i] = decode_mac_pascal_string(data + pos + 1, slen); pos += 1 + slen; } LOG(" Loaded STR# %d: %d strings", resID, numStrings); } /* * Parse a Mac resource fork embedded in a Win32 MACRSRC resource. * Finds all STR# resources and caches their strings. */ static void parse_mac_resource_fork(const unsigned char* rsrc, DWORD rsrcSize) { if (rsrcSize < 16) return; UInt32 dataOffset = read_be32(rsrc); UInt32 mapOffset = read_be32(rsrc + 4); if (mapOffset + 28 > rsrcSize) return; UInt16 typeListOffset = read_be16(rsrc + mapOffset + 24); UInt32 typeListStart = mapOffset + typeListOffset; if (typeListStart + 2 > rsrcSize) return; UInt16 numTypes = read_be16(rsrc + typeListStart) + 1; for (int i = 0; i < numTypes; i++) { UInt32 typeOff = typeListStart + 2 + i * 8; if (typeOff + 8 > rsrcSize) break; /* Check for 'STR#' type code */ if (rsrc[typeOff] != 'S' || rsrc[typeOff+1] != 'T' || rsrc[typeOff+2] != 'R' || rsrc[typeOff+3] != '#') continue; UInt16 numResources = read_be16(rsrc + typeOff + 4) + 1; UInt16 refListOff = read_be16(rsrc + typeOff + 6); UInt32 refListStart = typeListStart + refListOff; for (int j = 0; j < numResources; j++) { UInt32 refOff = refListStart + j * 12; if (refOff + 8 > rsrcSize) break; SInt16 resID = (SInt16)read_be16(rsrc + refOff); UInt32 resDataOff = read_be24(rsrc + refOff + 5); UInt32 absDataOff = dataOffset + resDataOff; if (absDataOff + 4 > rsrcSize) continue; UInt32 resLen = read_be32(rsrc + absDataOff); if (absDataOff + 4 + resLen > rsrcSize) continue; parse_str_list(rsrc + absDataOff + 4, resLen, resID); } } } /* * Initialize the string table by extracting the Mac resource fork * from the compiler DLL's MACRSRC Win32 resource. */ void __cdecl MWCC_InitStringTable(HMODULE hCompilerDll) { if (string_table_initialized) return; LOG("MWCC_InitStringTable: extracting strings from compiler DLL"); HRSRC hRes = FindResourceA(hCompilerDll, "IDR_MACRSRC1", "MACRSRC"); if (!hRes) { LOG(" MACRSRC resource not found in compiler DLL (err=%lu)", GetLastError()); return; } DWORD resSize = SizeofResource(hCompilerDll, hRes); HGLOBAL hGlob = LoadResource(hCompilerDll, hRes); if (!hGlob) { LOG(" Failed to load MACRSRC resource"); return; } const unsigned char* rsrcData = (const unsigned char*)LockResource(hGlob); if (!rsrcData) { LOG(" Failed to lock MACRSRC resource"); return; } parse_mac_resource_fork(rsrcData, resSize); string_table_initialized = 1; LOG("MWCC_InitStringTable: loaded %d string lists", num_cached_str_lists); } /* * COS_GetString - look up a string by (strListID, 1-based index). * * The DLL calls this to get error/warning message templates. * Buffer must be at least 256 bytes (Str255 convention). */ void __cdecl COS_GetString(char* buffer, SInt16 strListID, SInt16 index) { LOG("COS_GetString(buf=%p, listID=%d, index=%d)", (void*)buffer, strListID, index); if (!buffer) return; buffer[0] = '\0'; if (index < 1) return; for (int i = 0; i < num_cached_str_lists; i++) { if (cached_str_lists[i].strListID == strListID) { int idx = index - 1; /* Convert 1-based to 0-based */ if (idx < cached_str_lists[i].numStrings && cached_str_lists[i].strings[idx]) { strncpy(buffer, cached_str_lists[i].strings[idx], 255); buffer[255] = '\0'; LOG(" -> \"%s\"", buffer); return; } break; } } /* String not found - return a placeholder so it's obvious */ snprintf(buffer, 256, "[string %d:%d not found]", strListID, index); LOG(" -> not found"); } Boolean __cdecl COS_IsMultiByte(const char* str) { STUB("COS_IsMultiByte"); return FALSE; } /* ============================================================ * DLL entry point * ============================================================ */ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { if (fdwReason == DLL_PROCESS_ATTACH) { typedef void (__cdecl *MWCC_RegisterPluginLibFunc)(HMODULE, int); HMODULE host_module = GetModuleHandleA(NULL); if (host_module) { MWCC_RegisterPluginLibFunc register_fn = (MWCC_RegisterPluginLibFunc)GetProcAddress(host_module, "MWCC_RegisterPluginLib"); if (register_fn) { register_fn((HMODULE)hinstDLL, PLUGINLIB_VER); } } } return TRUE; }