commit e9f5847bb927267fe18bb599cd60815c7f36c421 Author: Luke Street Date: Tue Mar 3 23:04:25 2026 -0700 Initial commit diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..fb26af0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: Build + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Install MinGW-w64 + run: | + sudo apt-get update + sudo apt-get install -y gcc-mingw-w64-i686 + + - name: Build + run: make + + - name: Upload artifacts + uses: actions/upload-artifact@v7 + with: + name: mwccwrap + path: | + mwccwrap.exe + PluginLib2.dll + PluginLib3.dll + ASINTPPC.DLL diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e31feb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.exe +*.dll +*.DLL +*.o diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4743373 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2026 Luke Street (encounter) + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d282c11 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +CC = i686-w64-mingw32-gcc +CFLAGS = -Wall -O2 -std=c99 +LDFLAGS = -lversion + +all: PluginLib2.dll PluginLib3.dll ASINTPPC.DLL mwccwrap.exe + +PluginLib2.dll: pluginlib.c pluginlib2.def cw_types.h host_ctx.h + $(CC) $(CFLAGS) -shared -o $@ pluginlib.c pluginlib2.def -DPLUGINLIB_VER=2 -Wl,--kill-at,--enable-stdcall-fixup + +PluginLib3.dll: pluginlib.c pluginlib3.def cw_types.h host_ctx.h + $(CC) $(CFLAGS) -shared -o $@ pluginlib.c pluginlib3.def -DPLUGINLIB_VER=3 -Wl,--kill-at,--enable-stdcall-fixup + +# PluginLib5.dll: pluginlib.c pluginlib5.def cw_types.h host_ctx.h +# $(CC) $(CFLAGS) -shared -o $@ pluginlib.c pluginlib5.def -DPLUGINLIB_VER=5 -Wl,--kill-at,--enable-stdcall-fixup + +ASINTPPC.DLL: asintppc.c asintppc.def + $(CC) $(CFLAGS) -shared -o $@ asintppc.c asintppc.def -Wl,--kill-at,--enable-stdcall-fixup + +mwccwrap.exe: mwccwrap.c cw_types.h host_ctx.h + $(CC) $(CFLAGS) -o $@ mwccwrap.c $(LDFLAGS) + +# Quick test - try to compile a minimal C file +test: all test.c + wibo ./mwccwrap.exe -v -o test.o test.c + +# Test with just initialization (no compile) +test-init: all + wibo ./mwccwrap.exe -v test.c || true + +clean: + rm -f PluginLib2.dll PluginLib3.dll ASINTPPC.DLL mwccwrap.exe test.o + +.PHONY: all test test-init clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4381b3 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# mwccwrap + +Command-line wrapper for CodeWarrior compiler plugin DLLs. + +## Background + +CodeWarrior for PlayStation shipped its C/C++ compiler only as an IDE plugin +DLL (`cc_mips.dll`) — no standalone `mwcc` command-line executable was ever +provided for the PS1 target. Later CW MIPS targets (PS2, PSP) did ship CLI +tools (`mwccps2.exe`, etc.), but PS1 never got one. A similar gap exists for +CW Wii 1.2, where the only available installer is truncated and missing +`mwcceppc.exe`, while `ppc_eabi.dll` is intact. + +**mwccwrap** fills this gap. It builds a Win32 CLI host (`mwccwrap.exe`) that +loads a CW compiler DLL and drives it through the CW plugin lifecycle, plus +replacement DLLs that implement the API callbacks the compiler DLL imports. +This makes it possible to use these compilers from the command line and +from build systems, enabling decompilation projects and other workflows +that need reproducible builds with the original compiler. + +For usage on Linux or macOS, use [wibo](https://github.com/decompals/wibo) (a +lightweight Win32 userspace emulator) or Wine. + +## Supported compilers + +| Platform | DLL | Versions | +|----------|-----|----------| +| PlayStation | `cc_mips.dll` | CW PS R3, R4, R4.1, R5, R5.2 | +| GameCube | `ppc_eabi.dll` | CW GC 1.1 through 2.7 | + +## Building + +Requires an `i686-w64-mingw32-gcc` cross-compiler (MinGW-w64). + +```sh +make # builds mwccwrap.exe and shim DLLs +make clean # remove build artifacts +``` + +### Build outputs + +| File | Description | +|------|-------------| +| `mwccwrap.exe` | CLI host executable | +| `PluginLib2.dll` | CW API shim for 1997-era DLLs | +| `PluginLib3.dll` | CW API shim for 1998-2005-era DLLs | +| `ASINTPPC.DLL` | Mac OS Toolbox API shim | + +## Usage + +```sh +# Basic compilation (requires cc_mips.dll or ppc_eabi.dll in cwd) +wibo ./mwccwrap.exe -o output.o input.c + +# Specify compiler DLL explicitly +wibo ./mwccwrap.exe -dll /path/to/cc_mips.dll -o output.o input.c + +# Common flags +wibo ./mwccwrap.exe -O2 -sym on -lang c -I include/ -D MY_DEFINE -o output.o input.c + +# See all options +wibo ./mwccwrap.exe -help +``` + +### Runtime requirements + +A compiler DLL must be available either: + +- In the current working directory (`cc_mips.dll` or `ppc_eabi.dll`), or +- Passed explicitly via `-dll ` + +## License + +This project is licensed under the MIT License. See `LICENSE` for details. diff --git a/asintppc.c b/asintppc.c new file mode 100644 index 0000000..0c47ae9 --- /dev/null +++ b/asintppc.c @@ -0,0 +1,376 @@ +/* + * asintppc.c - Lightweight ASINTPPC.DLL shim + */ + +#include +#include +#include +#include +#include +#include +#include "cw_types.h" + +enum { + kNoErr = 0, + kMemFullErr = -108, + kFnfErr = -43, + kEofErr = -39, + kParamErr = -50, + kOpWrErr = -49 +}; + +static SInt16 g_mem_error = kNoErr; +static FILE* g_open_files[256]; +static SInt16 g_next_refnum = 16; +static int g_verbose = 0; + +#define ASILOG(fmt, ...) do { if (g_verbose) { fprintf(stderr, "[ASINTPPC] " fmt "\n", ##__VA_ARGS__); fflush(stderr); } } while(0) + +static void pstr_to_cstr(const UInt8* pstr, char* out, size_t out_size) { + size_t len; + if (!out || out_size == 0) return; + out[0] = '\0'; + if (!pstr) return; + len = pstr[0]; + if (len + 1 > out_size) len = out_size - 1; + if (len > 0) memcpy(out, pstr + 1, len); + out[len] = '\0'; +} + +static void cstr_to_pstr(const char* cstr, UInt8* out) { + size_t len = 0; + if (!out) return; + if (cstr) len = strlen(cstr); + if (len > 255) len = 255; + out[0] = (UInt8)len; + if (len > 0) memcpy(out + 1, cstr, len); +} + +static SInt16 alloc_refnum(FILE* f) { + for (int i = 0; i < 256; i++) { + SInt16 ref = (SInt16)((g_next_refnum + i) & 0xFF); + if (ref == 0) continue; + if (!g_open_files[(unsigned char)ref]) { + g_open_files[(unsigned char)ref] = f; + g_next_refnum = (SInt16)(ref + 1); + return ref; + } + } + return 0; +} + +static FILE* lookup_refnum(SInt16 refnum) { + return g_open_files[(unsigned char)refnum]; +} + +static void close_refnum(SInt16 refnum) { + g_open_files[(unsigned char)refnum] = NULL; +} + +/* + * EqualString - Mac Toolbox Pascal string comparison + * + * caseSensitive: if 0, compare case-insensitively + * diacSensitive: ignored in this shim + */ +int __stdcall EqualString(const UInt8* str1, const UInt8* str2, + int caseSensitive, int diacSensitive) +{ + UInt8 len1, len2; + ASILOG("EqualString"); + (void)diacSensitive; + + if (!str1 || !str2) return 0; + len1 = str1[0]; + len2 = str2[0]; + if (len1 != len2) return 0; + + for (UInt8 i = 1; i <= len1; i++) { + UInt8 c1 = str1[i]; + UInt8 c2 = str2[i]; + if (!caseSensitive) { + c1 = (UInt8)tolower(c1); + c2 = (UInt8)tolower(c2); + } + if (c1 != c2) return 0; + } + return 1; +} + +void __stdcall ASI_CopyPtoC(const UInt8* pstr, char* cstr) { + ASILOG("ASI_CopyPtoC"); + if (!cstr) return; + if (!pstr) { + cstr[0] = '\0'; + return; + } + pstr_to_cstr(pstr, cstr, 1024); +} + +SInt16 __stdcall CharacterByteType(const char* text, SInt32 offset, SInt32 script) { + ASILOG("CharacterByteType"); + (void)script; + if (!text || offset < 0) return 0; + /* Treat all bytes as single-byte characters in this shim. */ + return 1; +} + +void __stdcall GetIndString(UInt8* out, SInt16 list_id, SInt16 index) { + ASILOG("GetIndString(list=%d,index=%d)", (int)list_id, (int)index); + (void)list_id; + (void)index; + if (!out) return; + out[0] = 0; +} + +HandleStructure* __stdcall NewHandle(SInt32 size) { + HandleStructure* h; + size_t alloc_size; + ASILOG("NewHandle(size=%d)", (int)size); + if (size < 0) size = 0; + + h = (HandleStructure*)calloc(1, sizeof(HandleStructure)); + if (!h) { + g_mem_error = kMemFullErr; + return NULL; + } + + alloc_size = (size > 0) ? (size_t)size : 1u; + h->hand.addr = calloc(1, alloc_size); + if (!h->hand.addr) { + free(h); + g_mem_error = kMemFullErr; + return NULL; + } + + h->addr = (char*)h->hand.addr; + h->hand.used = (UInt32)size; + h->hand.size = (UInt32)alloc_size; + g_mem_error = kNoErr; + return h; +} + +HandleStructure* __stdcall TempNewHandle(SInt32 size, SInt16* result) { + ASILOG("TempNewHandle(size=%d)", (int)size); + HandleStructure* h = NewHandle(size); + if (result) *result = h ? kNoErr : g_mem_error; + return h; +} + +void __stdcall DisposeHandle(HandleStructure* h) { + ASILOG("DisposeHandle"); + if (!h) return; + free(h->hand.addr); + free(h); + g_mem_error = kNoErr; +} + +SInt16 __stdcall SetHandleSize(HandleStructure* h, SInt32 new_size) { + ASILOG("SetHandleSize(size=%d)", (int)new_size); + void* new_data; + size_t alloc_size; + if (!h || new_size < 0) { + g_mem_error = kParamErr; + return g_mem_error; + } + + alloc_size = (new_size > 0) ? (size_t)new_size : 1u; + new_data = realloc(h->hand.addr, alloc_size); + if (!new_data) { + g_mem_error = kMemFullErr; + return g_mem_error; + } + + h->hand.addr = new_data; + h->addr = (char*)new_data; + h->hand.used = (UInt32)new_size; + h->hand.size = (UInt32)alloc_size; + g_mem_error = kNoErr; + return kNoErr; +} + +SInt16 __stdcall MemError(void) { + ASILOG("MemError -> %d", (int)g_mem_error); + return g_mem_error; +} + +UInt32 __stdcall TickCount(void) { + ASILOG("TickCount"); + return (UInt32)GetTickCount(); +} + +SInt16 __stdcall HDelete(SInt16 vRefNum, SInt32 dirID, const UInt8* name) { + char path[MAX_PATH]; + ASILOG("HDelete"); + (void)vRefNum; + (void)dirID; + pstr_to_cstr(name, path, sizeof(path)); + if (!path[0]) return kParamErr; + return (remove(path) == 0) ? kNoErr : kFnfErr; +} + +SInt16 __stdcall HCreate(SInt16 vRefNum, SInt32 dirID, const UInt8* name, UInt32 creator, UInt32 fileType) { + FILE* f; + char path[MAX_PATH]; + ASILOG("HCreate"); + (void)vRefNum; + (void)dirID; + (void)creator; + (void)fileType; + pstr_to_cstr(name, path, sizeof(path)); + if (!path[0]) return kParamErr; + f = fopen(path, "wb"); + if (!f) return kOpWrErr; + fclose(f); + return kNoErr; +} + +SInt16 __stdcall HOpen(SInt16 vRefNum, SInt32 dirID, const UInt8* name, SInt16 permission, SInt16* refNum) { + FILE* f = NULL; + SInt16 ref; + char path[MAX_PATH]; + ASILOG("HOpen"); + (void)vRefNum; + (void)dirID; + pstr_to_cstr(name, path, sizeof(path)); + if (!path[0] || !refNum) return kParamErr; + + if (permission == 1) f = fopen(path, "rb"); + if (!f) f = fopen(path, "r+b"); + if (!f) f = fopen(path, "rb"); + if (!f) return kFnfErr; + + ref = alloc_refnum(f); + if (ref == 0) { + fclose(f); + return kMemFullErr; + } + *refNum = ref; + return kNoErr; +} + +SInt16 __stdcall HGetFInfo(SInt16 vRefNum, SInt32 dirID, const UInt8* name, void* fileInfo) { + char path[MAX_PATH]; + FILE* f; + ASILOG("HGetFInfo"); + (void)vRefNum; + (void)dirID; + pstr_to_cstr(name, path, sizeof(path)); + if (!path[0]) return kParamErr; + f = fopen(path, "rb"); + if (!f) return kFnfErr; + fclose(f); + if (fileInfo) memset(fileInfo, 0, 64); + return kNoErr; +} + +SInt16 __stdcall GetEOF(SInt16 refNum, SInt32* eofPos) { + ASILOG("GetEOF(ref=%d)", (int)refNum); + long pos; + long end; + FILE* f = lookup_refnum(refNum); + if (!f || !eofPos) return kParamErr; + pos = ftell(f); + if (fseek(f, 0, SEEK_END) != 0) return kParamErr; + end = ftell(f); + fseek(f, pos, SEEK_SET); + *eofPos = (SInt32)end; + return kNoErr; +} + +SInt16 __stdcall GetFPos(SInt16 refNum, SInt32* position) { + ASILOG("GetFPos(ref=%d)", (int)refNum); + long pos; + FILE* f = lookup_refnum(refNum); + if (!f || !position) return kParamErr; + pos = ftell(f); + *position = (SInt32)pos; + return kNoErr; +} + +SInt16 __stdcall SetFPos(SInt16 refNum, SInt16 posMode, SInt32 offset) { + ASILOG("SetFPos(ref=%d,mode=%d,off=%d)", (int)refNum, (int)posMode, (int)offset); + int whence = SEEK_SET; + FILE* f = lookup_refnum(refNum); + if (!f) return kParamErr; + if (posMode == 2) whence = SEEK_CUR; + else if (posMode == 3) whence = SEEK_END; + if (fseek(f, (long)offset, whence) != 0) return kParamErr; + return kNoErr; +} + +SInt16 __stdcall FSRead(SInt16 refNum, SInt32* count, void* buffer) { + ASILOG("FSRead(ref=%d,count=%d)", (int)refNum, (int)(count ? *count : -1)); + size_t got; + FILE* f = lookup_refnum(refNum); + if (!f || !count || !buffer || *count < 0) return kParamErr; + got = fread(buffer, 1, (size_t)*count, f); + *count = (SInt32)got; + if (got == 0 && feof(f)) return kEofErr; + return kNoErr; +} + +SInt16 __stdcall FSWrite(SInt16 refNum, SInt32* count, const void* buffer) { + ASILOG("FSWrite(ref=%d,count=%d)", (int)refNum, (int)(count ? *count : -1)); + size_t put; + FILE* f = lookup_refnum(refNum); + if (!f || !count || !buffer || *count < 0) return kParamErr; + put = fwrite(buffer, 1, (size_t)*count, f); + *count = (SInt32)put; + return (put > 0 || *count == 0) ? kNoErr : kOpWrErr; +} + +SInt16 __stdcall FSClose(SInt16 refNum) { + ASILOG("FSClose(ref=%d)", (int)refNum); + FILE* f = lookup_refnum(refNum); + if (!f) return kParamErr; + fclose(f); + close_refnum(refNum); + return kNoErr; +} + +SInt16 __stdcall FSMakeFSSpec(SInt16 vRefNum, SInt32 dirID, const UInt8* name, FSSpec* outSpec) { + ASILOG("FSMakeFSSpec"); + if (!outSpec) return kParamErr; + memset(outSpec, 0, 0x120); + outSpec->vRefNum = vRefNum; + outSpec->parID = dirID; + if (name) { + size_t n = name[0]; + if (n > 255) n = 255; + outSpec->name[0] = (UInt8)n; + if (n > 0) memcpy(outSpec->name + 1, name + 1, n); + } + return kNoErr; +} + +SInt16 __stdcall PBGetCatInfoSync(void* pb) { + ASILOG("PBGetCatInfoSync"); + if (pb) memset(pb, 0, 128); + return kNoErr; +} + +SInt16 __stdcall GetVInfo(SInt16 drvNum, UInt8* volumeName, SInt16* vRefNum, SInt32* freeBytes) { + ASILOG("GetVInfo"); + (void)drvNum; + if (volumeName) cstr_to_pstr("", volumeName); + if (vRefNum) *vRefNum = 0; + if (freeBytes) *freeBytes = 0; + return kNoErr; +} + +SInt16 __stdcall PBHGetFInfoSync(void* pb) { + ASILOG("PBHGetFInfoSync"); + if (pb) memset(pb, 0, 128); + return kNoErr; +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { + (void)hinstDLL; + (void)fdwReason; + (void)lpReserved; + if (fdwReason == DLL_PROCESS_ATTACH) { + g_verbose = (getenv("MWCC_ASI_VERBOSE") != NULL); + } + return TRUE; +} diff --git a/asintppc.def b/asintppc.def new file mode 100644 index 0000000..f3cec78 --- /dev/null +++ b/asintppc.def @@ -0,0 +1,26 @@ +LIBRARY ASINTPPC +EXPORTS + ASI_CopyPtoC @92 + DisposeHandle @250 + GetIndString @302 + NewHandle @383 + SetHandleSize @473 + TickCount @521 + FSClose @578 + FSWrite @580 + MemError @586 + EqualString @602 + GetVInfo @609 + FSRead @647 + GetEOF @654 + GetFPos @656 + SetFPos @728 + HGetFInfo @768 + TempNewHandle @769 + FSMakeFSSpec @955 + HCreate @1064 + HDelete @1066 + HOpen @1067 + PBGetCatInfoSync @2237 + PBHGetFInfoSync @2267 + CharacterByteType @6028 diff --git a/cw_types.h b/cw_types.h new file mode 100644 index 0000000..934ea75 --- /dev/null +++ b/cw_types.h @@ -0,0 +1,460 @@ +/* + * cw_types.h - CodeWarrior Plugin API types for Win32 + * + * Reconstructed from the MWCC decomp SDK headers, adapted for Win32 target. + * This is the minimal set needed by the mwccwrap host and PluginLib3 shim. + */ + +#ifndef CW_TYPES_H +#define CW_TYPES_H + +#include +#include + +#ifndef PLUGINLIB_VER +#define PLUGINLIB_VER 3 +#endif + +#define _STRINGIFY(s) #s +#define STRINGIFY(s) _STRINGIFY(s) + +/* Basic types matching CW SDK */ +typedef int32_t SInt32; +typedef int16_t SInt16; +typedef uint32_t UInt32; +typedef uint16_t UInt16; +typedef uint8_t UInt8; +typedef unsigned char Boolean; + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +/* CWResult is the error/status result returned by all IDE API routines */ +typedef SInt32 CWResult; + +/* Four-character code */ +typedef SInt32 CWFourCharType; +#define CWFOURCHAR(a, b, c, d) \ + (((CWFourCharType) ((a) & 0xff) << 24) \ + | ((CWFourCharType) ((b) & 0xff) << 16) \ + | ((CWFourCharType) ((c) & 0xff) << 8) \ + | ((CWFourCharType) ((d) & 0xff))) + +typedef UInt32 CWDataType; + +/* Mac-style FSSpec with Pascal name */ +#pragma pack(push, 2) +typedef struct FSSpec { + SInt16 vRefNum; + SInt32 parID; + UInt8 name[256]; +} FSSpec; +#pragma pack(pop) + +typedef struct OSHandle { + void* addr; + UInt32 used; + UInt32 size; +} OSHandle; + +typedef struct HandleStructure { + char* addr; /* Mac Handle-compatible first field */ + OSHandle hand; +} HandleStructure; + +#if PLUGINLIB_VER < 3 +typedef FSSpec CWFileSpec; +#else +typedef struct CWFileSpec { + char path[MAX_PATH]; +} CWFileSpec; +#endif + +typedef char CWFileName[65]; +typedef FILETIME CWFileTime; +typedef DWORD CWOSResult; + +/* Calling conventions */ +#define CW_CALLBACK CWResult __stdcall + +/* Memory handle - opaque pointer */ +typedef struct CWMemHandlePrivateStruct* CWMemHandle; + +/* Plugin context - pointer to private context struct */ +typedef struct CWPluginPrivateContext* CWPluginContext; + +/* Error codes */ +enum { + cwNoErr = 0, + cwErrUserCanceled, + cwErrRequestFailed, + cwErrInvalidParameter, + cwErrInvalidCallback, + cwErrInvalidMPCallback, + cwErrOSError, + cwErrOutOfMemory, + cwErrFileNotFound, + cwErrUnknownFile, + cwErrSilent, + cwErrCantSetAttribute, + cwErrStringBufferOverflow, + cwErrDirectoryNotFound, + cwErrLastCommonError = 512, + + cwErrUnknownSegment, + cwErrSBMNotFound, + cwErrObjectFileNotStored, + cwErrLicenseCheckFailed, + cwErrFileSpecNotSpecified, + cwErrFileSpecInvalid, + cwErrLastCompilerLinkerError = 1024 +}; + +/* Request codes */ +enum { + reqInitialize = -2, + reqTerminate = -1, + reqIdle = -100, + reqAbout = -101, + reqPrefsChange = -102 +}; + +/* Compiler request codes */ +enum { + reqCompile = 0, + reqMakeParse, + reqCompDisassemble, + reqCheckSyntax, + reqPreprocessForDebugger +}; + +/* Dependency types */ +typedef enum CWDependencyType { + cwNoDependency, + cwNormalDependency, + cwInterfaceDependency +} CWDependencyType; + +/* File data types */ +enum { + cwFileTypeUnknown, + cwFileTypeText, + cwFileTypePrecompiledHeader +}; + +/* Message types */ +enum { + messagetypeInfo, + messagetypeWarning, + messagetypeError +}; + +/* Target CPU/OS constants */ +enum { + targetCPU68K = CWFOURCHAR('6','8','k',' '), + targetCPUPowerPC = CWFOURCHAR('p','p','c',' '), + targetCPUi80x86 = CWFOURCHAR('8','0','8','6'), + targetCPUMips = CWFOURCHAR('m','i','p','s'), + targetCPUNECv800 = CWFOURCHAR('v','8','0','0'), + targetCPUEmbeddedPowerPC = CWFOURCHAR('e','P','P','C'), + targetCPUARM = CWFOURCHAR('a','r','m',' '), + targetCPUSparc = CWFOURCHAR('s','p','r','c'), + targetCPUIA64 = CWFOURCHAR('I','A','6','4'), + targetCPUAny = CWFOURCHAR('*','*','*','*'), + targetCPUMCORE = CWFOURCHAR('m','c','o','r'), + targetCPU_Intent = CWFOURCHAR('n','t','n','t') +}; + +enum { + targetOSMacintosh = CWFOURCHAR('m','a','c',' '), + targetOSWindows = CWFOURCHAR('w','i','n','t'), + targetOSNetware = CWFOURCHAR('n','l','m',' '), + targetOSMagicCap = CWFOURCHAR('m','c','a','p'), + targetOSOS9 = CWFOURCHAR('o','s','9',' '), + targetOSEmbeddedABI = CWFOURCHAR('E','A','B','I'), + targetOSJava = CWFOURCHAR('j','a','v','a'), /* java (no VM specification) */ + targetOSJavaMS = CWFOURCHAR('j','v','m','s'), /* Microsoft VM */ + targetOSJavaSun = CWFOURCHAR('j','v','s','n'), /* Sun VM */ + targetOSJavaMRJ = CWFOURCHAR('j','v','m','r'), /* MRJ VM */ + targetOSJavaMW = CWFOURCHAR('j','v','m','w'), /* Metrowerks VM */ + targetOSPalm = CWFOURCHAR('p','a','l','m'), + targetOSGTD5 = CWFOURCHAR('g','t','d','5'), + targetOSSolaris = CWFOURCHAR('s','l','r','s'), + targetOSLinux = CWFOURCHAR('l','n','u','x'), + targetOSAny = CWFOURCHAR('*','*','*','*'), + targetOS_Intent = CWFOURCHAR('n','t','n','t') +}; + +/* Linkage types */ +enum { + exelinkageFlat, + exelinkageSegmented, + exelinkageOverlay1 +}; + +/* Output types */ +enum { + linkOutputNone, + linkOutputFile, + linkOutputDirectory +}; + +/* + * CW SDK structs use mac68k alignment (Metrowerks) or 2-byte packing (MSVC). + */ +#pragma pack(push, 2) + +/* Message reference */ +typedef struct CWMessageRef { + CWFileSpec sourcefile; + SInt32 linenumber; + short tokenoffset; + short tokenlength; + SInt32 selectionoffset; + SInt32 selectionlength; +} CWMessageRef; + +/* File info returned by CWFindAndLoadFile */ +typedef struct CWFileInfo { + Boolean fullsearch; + char dependencyType; + SInt32 isdependentoffile; + Boolean suppressload; + Boolean padding; + const char* filedata; + SInt32 filedatalength; + short filedatatype; + short fileID; + CWFileSpec filespec; + Boolean alreadyincluded; + Boolean recordbrowseinfo; +} CWFileInfo; + +/* Browse options */ +typedef struct CWBrowseOptions { + Boolean recordClasses; + Boolean recordEnums; + Boolean recordMacros; + Boolean recordTypedefs; + Boolean recordConstants; + Boolean recordTemplates; + Boolean recordUndefinedFunctions; + SInt32 reserved1; + SInt32 reserved2; +} CWBrowseOptions; + +/* Object data for StoreObjectData */ +typedef struct CWDependencyInfo { + SInt32 fileIndex; + CWFileSpec fileSpec; + short fileSpecAccessType; + short dependencyType; +} CWDependencyInfo; + +typedef struct CWObjectData { + CWMemHandle objectdata; + CWMemHandle browsedata; + SInt32 reserved1; + SInt32 codesize; + SInt32 udatasize; + SInt32 idatasize; + SInt32 compiledlines; + Boolean interfaceChanged; + SInt32 reserved2; + void* compilecontext; + CWDependencyInfo* dependencies; + short dependencyCount; + CWFileSpec* objectfile; +} CWObjectData; + +/* Legacy target info (API <= 9 era) */ +typedef struct CWTargetInfoV7 { + CWFileSpec outfile; + CWFileSpec symfile; + short linkType; + Boolean canRun; + Boolean canDebug; + Boolean useRunHelperApp; + char reserved1; + CWDataType debuggerCreator; + CWDataType runHelperCreator; + SInt32 reserved2[2]; +} CWTargetInfoV7; + +/* Target info - Win32 version */ +typedef struct CWTargetInfo { + short outputType; + CWFileSpec outfile; + CWFileSpec symfile; + CWFileSpec runfile; + short linkType; + Boolean canRun; + Boolean canDebug; + CWDataType targetCPU; + CWDataType targetOS; + // Boolean runHelperIsRegKey; + // Boolean debugHelperIsRegKey; + // char args[512]; + // char runHelperName[512]; + // Boolean runHelperRequiresURL; + // char reserved2; + // char debugHelperName[512]; + // CWFileSpec linkAgainstFile; +} CWTargetInfo; + +/* Project file info */ +typedef struct CWProjectFileInfo { + CWFileSpec filespec; + CWFileTime moddate; + short segment; + Boolean hasobjectcode; + Boolean hasresources; + Boolean isresourcefile; + Boolean weakimport; + Boolean initbefore; + Boolean gendebug; + CWFileTime objmoddate; + CWFileName dropinname; + short fileID; + Boolean recordbrowseinfo; + Boolean reserved; + Boolean hasunitdata; + Boolean mergeintooutput; + UInt32 unitdatadependencytag; +} CWProjectFileInfo; + +/* Segment info */ +typedef struct CWProjectSegmentInfo { + char name[32]; + short attributes; +} CWProjectSegmentInfo; + +/* New text document */ +typedef struct CWNewTextDocumentInfo { + const char* documentname; + CWMemHandle text; + Boolean markDirty; +} CWNewTextDocumentInfo; + +/* IDE info */ +typedef struct CWIDEInfo { + unsigned short majorVersion; + unsigned short minorVersion; + unsigned short bugFixVersion; + unsigned short buildVersion; + unsigned short dropinAPIVersion; +} CWIDEInfo; + +/* Access path info */ +typedef enum CWAccessPathType { + cwSystemPath, + cwUserPath +} CWAccessPathType; + +typedef struct CWAccessPathInfo { + CWFileSpec pathSpec; + Boolean recursive; + SInt32 subdirectoryCount; +} CWAccessPathInfo; + +typedef struct CWAccessPathListInfo { + SInt32 systemPathCount; + SInt32 userPathCount; + Boolean alwaysSearchUserPaths; + Boolean convertPaths; +} CWAccessPathListInfo; + +/* Relative path */ +typedef struct CWRelativePath { + short version; + unsigned char pathType; + unsigned char pathFormat; + char userDefinedTree[256]; + char pathString[512]; +} CWRelativePath; + +/* New project entry */ +typedef struct CWNewProjectEntryInfo { + SInt32 position; + SInt32 segment; + SInt32 overlayGroup; + SInt32 overlay; + const char* groupPath; + Boolean mergeintooutput; + Boolean weakimport; + Boolean initbefore; +} CWNewProjectEntryInfo; + +/* Overlay info */ +typedef struct CWAddr64 { + SInt32 lo; + SInt32 hi; +} CWAddr64; + +typedef struct CWOverlay1GroupInfo { + char name[256]; + CWAddr64 address; + SInt32 numoverlays; +} CWOverlay1GroupInfo; + +typedef struct CWOverlay1Info { + char name[256]; + SInt32 numfiles; +} CWOverlay1Info; + +typedef struct CWOverlay1FileInfo { + SInt32 whichfile; +} CWOverlay1FileInfo; + +/* Framework info */ +typedef struct CWFrameworkInfo { + CWFileSpec fileSpec; + char version[256]; +} CWFrameworkInfo; + +/* Drop-in flags */ +typedef struct DropInFlags { + short rsrcversion; + CWDataType dropintype; + unsigned short earliestCompatibleAPIVersion; + UInt32 dropinflags; + CWDataType edit_language; + unsigned short newestAPIVersion; +} DropInFlags; + +/* Panel list */ +typedef struct CWPanelList { + short version; + short count; + const char** names; +} CWPanelList; + +/* Compiler mapping */ +typedef unsigned long CompilerMappingFlags; + +typedef struct CWExtensionMapping { + CWDataType type; + char extension[32]; + CompilerMappingFlags flags; +} CWExtensionMapping; + +typedef struct CWExtMapList { + short version; + short nMappings; + CWExtensionMapping* mappings; +} CWExtMapList; + +/* Target list */ +typedef struct CWTargetList { + short version; + short cpuCount; + CWDataType* cpus; + short osCount; + CWDataType* oss; +} CWTargetList; + +#pragma pack(pop) + +#endif /* CW_TYPES_H */ diff --git a/host_ctx.h b/host_ctx.h new file mode 100644 index 0000000..ebe0364 --- /dev/null +++ b/host_ctx.h @@ -0,0 +1,427 @@ +/* + * host_ctx.h - Host context structures for mwccwrap + * + * This defines the CWPluginPrivateContext layout that the DLL expects, + * plus our host-side state. + */ + +#ifndef HOST_CTX_H +#define HOST_CTX_H + +#include "cw_types.h" + +/* + * Memory handle implementation. + * CWMemHandle is a pointer to one of these. + */ +typedef struct CWMemHandlePrivateStruct { + void* data; + SInt32 size; + int locked; +} CWMemHandleImpl; + +/* + * Access path entry + */ +typedef struct HostAccessPath { + char path[MAX_PATH]; + Boolean recursive; +} HostAccessPath; + +enum { + hostIncludeSearchProj = 0, + hostIncludeSearchSource = 1, + hostIncludeSearchExplicit = 2, + hostIncludeSearchInclude = 3 +}; + +typedef struct HostFileRecord { + short fileID; + Boolean isSystem; + char path[MAX_PATH]; +} HostFileRecord; + +typedef struct HostIncludeRecord { + char path[MAX_PATH]; +} HostIncludeRecord; + +/* ============================================================ + * Preference panel structs + * + * These match the binary layout the DLL reads via + * CWSecretGetNamedPreferences(). Adapted from the MWCC decomp + * pref_structs.h (CW Pro era). We use #pragma pack(1) to match + * the Win32 packing the DLL was compiled with. + * ============================================================ */ + +#pragma pack(push, 1) + +/* + * "C/C++ Compiler" panel (332 bytes) + */ +typedef struct PFrontEndC { + SInt16 version; /* 0x00: current = 0x12 (18) */ + Boolean cplusplus; /* 0x02 */ + Boolean checkprotos; /* 0x03 */ + Boolean arm; /* 0x04 */ + Boolean trigraphs; /* 0x05 */ + Boolean onlystdkeywords; /* 0x06 */ + Boolean enumsalwaysint; /* 0x07 */ + Boolean mpwpointerstyle; /* 0x08 */ + unsigned char oldprefixname[32];/* 0x09: legacy, superseded by newprefixname in v10+ */ + Boolean ansistrict; /* 0x29 */ + Boolean mpwcnewline; /* 0x2A */ + Boolean wchar_type; /* 0x2B */ + Boolean enableexceptions; /* 0x2C */ + Boolean dontreusestrings; /* 0x2D */ + Boolean poolstrings; /* 0x2E */ + Boolean dontinline; /* 0x2F */ + Boolean useRTTI; /* 0x30 */ + Boolean multibyteaware; /* 0x31 */ + Boolean unsignedchars; /* 0x32 */ + Boolean autoinline; /* 0x33 */ + Boolean booltruefalse; /* 0x34 */ + Boolean direct_to_som; /* 0x35 */ + Boolean som_env_check; /* 0x36 */ + Boolean alwaysinline; /* 0x37 */ + SInt16 inlinelevel; /* 0x38 */ + Boolean ecplusplus; /* 0x3A */ + Boolean objective_c; /* 0x3B */ + Boolean defer_codegen; /* 0x3C */ + /* --- fields below are ignored by PS1 DLLs (they copy <= 62 bytes) --- */ + Boolean templateparser; /* 0x3D */ + Boolean c99; /* 0x3E */ + Boolean bottomupinline; /* 0x3F */ + unsigned char prefixname[256]; /* 0x40: Pascal string (byte 0 = length) */ + UInt8 old_version; /* 0x140 */ + Boolean warned_missing_cpp_panel;/* 0x141 */ + Boolean gcc_extensions; /* 0x142 */ + Boolean instance_manager; /* 0x143 */ + UInt8 ipa_mode; /* 0x144 */ + UInt8 reserved_145[7]; /* 0x145 */ +} PFrontEndC; + +/* + * "C/C++ Warnings" panel (34 bytes) + */ +typedef struct PWarningC { + SInt16 version; /* 0x00 */ + Boolean warn_illpragma; /* 0x02 */ + Boolean warn_emptydecl; /* 0x03 */ + Boolean warn_possunwant; /* 0x04 */ + Boolean warn_unusedvar; /* 0x05 */ + Boolean warn_unusedarg; /* 0x06 */ + Boolean warn_extracomma; /* 0x07 */ + Boolean pedantic; /* 0x08 */ + Boolean warningerrors; /* 0x09 */ + Boolean warn_hidevirtual; /* 0x0A */ + Boolean warn_implicitconv; /* 0x0B */ + Boolean warn_notinlined; /* 0x0C */ + Boolean warn_structclass; /* 0x0D */ + Boolean warn_missingreturn; /* 0x0E */ + Boolean warn_no_side_effect; /* 0x0F */ + Boolean warn_resultnotused; /* 0x10 */ + Boolean warn_padding; /* 0x11 */ + Boolean warn_impl_i2f_conv; /* 0x12 */ + Boolean warn_impl_f2i_conv; /* 0x13 */ + Boolean warn_impl_s2u_conv; /* 0x14 */ + Boolean warn_illtokenpasting; /* 0x15 */ + Boolean warn_filenamecaps; /* 0x16 */ + Boolean warn_filenamecapssystem; /* 0x17 */ + Boolean warn_undefmacro; /* 0x18 */ + Boolean warn_ptrintconv; /* 0x19 */ + UInt8 reserved_1A[8]; /* 0x1A-0x21 */ +} PWarningC; + +/* + * "Global Optimizer" / "PS Global Optimizer" / "EPPC Global Optimizer" + * panel (12 bytes) + */ +typedef struct PGlobalOptimizer { + SInt16 version; /* 0x00 */ + UInt8 optimizationlevel; /* 0x02: 0=off, 1-4=levels */ + UInt8 optfor; /* 0x03: 0=default, 1=speed, 2=space */ + UInt8 reserved[8]; /* 0x04-0x0B */ +} PGlobalOptimizer; + +/* + * "PPC EABI CodeGen" panel (58 bytes) + */ +typedef struct PPCEABICodeGen { + SInt16 version; /* 0x00 */ + char structalignment; /* 0x02 */ + UInt8 readonlystrings; /* 0x03 */ + UInt8 pooldata; /* 0x04 */ + UInt8 filler_05; /* 0x05 */ + UInt8 profiler; /* 0x06 */ + UInt8 filler_07; /* 0x07 */ + UInt8 peephole; /* 0x08 */ + UInt8 filler_09; /* 0x09 */ + char filler_0A; /* 0x0A */ + char scheduling; /* 0x0B */ + UInt8 filler_0C; /* 0x0C */ + UInt8 commonsect; /* 0x0D */ + char floatingpoint; /* 0x0E */ + UInt8 use_lmw_stmw; /* 0x0F */ + SInt16 processor; /* 0x10 */ + char function_align; /* 0x12 */ + UInt8 fpcontract; /* 0x13 */ + UInt8 altivec; /* 0x14 */ + UInt8 vrsave; /* 0x15 */ + UInt8 use_e500_fp; /* 0x16 */ + UInt8 use_isel; /* 0x17 */ + UInt8 use_fsel; /* 0x18 */ + UInt8 volatileasm; /* 0x19 */ + UInt8 strictfp; /* 0x1A */ + UInt8 genfsel; /* 0x1B */ + char processorname[16]; /* 0x1C */ + UInt8 orderedfpcmp; /* 0x2C */ + UInt8 altivec_move_block; /* 0x2D */ + UInt8 linkerpoolstrings; /* 0x2E */ + UInt8 poolconst; /* 0x2F */ + UInt8 vectors; /* 0x30 */ + UInt8 gen_vle; /* 0x31 */ + UInt8 use_e500v2_fp; /* 0x32 */ + UInt8 ppc_asm_to_vle; /* 0x33 */ + UInt8 reserved_34[5]; /* 0x34-0x38 */ + UInt8 reserved_39; /* 0x39 */ +} PPCEABICodeGen; +/* static_assert: sizeof == 58 (0x3A) */ + +/* + * "PPC EABI Linker" panel (124 bytes) + */ +typedef struct PPCEABILinker { + SInt16 version; /* 0x00 */ + UInt8 linksym; /* 0x02 */ + UInt8 symfullpath; /* 0x03 */ + UInt8 linkmap; /* 0x04 */ + UInt8 nolinkwarnings; /* 0x05 */ + UInt8 genSrecFile; /* 0x06 */ + UInt8 linkunused; /* 0x07 */ + UInt8 use_lcf; /* 0x08 */ + UInt8 use_codeaddr; /* 0x09 */ + UInt8 use_dataaddr; /* 0x0A */ + UInt8 use_sdataaddr; /* 0x0B */ + UInt8 use_sdata2addr; /* 0x0C */ + UInt8 use_stackaddr; /* 0x0D */ + UInt8 use_heapaddr; /* 0x0E */ + UInt8 genROMimage; /* 0x0F */ + UInt32 codeaddr; /* 0x10 */ + UInt32 dataaddr; /* 0x14 */ + UInt32 smalldataaddr; /* 0x18 */ + UInt32 smalldata2addr; /* 0x1C */ + UInt32 stackaddr; /* 0x20 */ + UInt32 rambuffer; /* 0x24 */ + UInt32 romimage_addr; /* 0x28 */ + SInt16 srecLength; /* 0x2C */ + UInt8 srecEOL; /* 0x2E */ + UInt8 pad; /* 0x2F */ + char mainname[64]; /* 0x30 */ + UInt32 heapaddr; /* 0x70 */ + UInt8 linkmode; /* 0x74 */ + UInt8 listdwarf; /* 0x75 */ + UInt8 listclosure; /* 0x76 */ + UInt8 sortSrec; /* 0x77 */ + UInt8 gen_bin_file; /* 0x78 */ + UInt8 reserved2[3]; /* 0x79-0x7B */ +} PPCEABILinker; +/* static_assert: sizeof == 124 (0x7C) */ + +/* + * "PPC EABI Project" panel (580 bytes) + */ +typedef struct PPCEABIProject { + SInt16 version; /* 0x00 */ + SInt16 projtype; /* 0x02 */ + char old_outfile[32]; /* 0x04 */ + UInt32 heapsize; /* 0x24 */ + UInt32 stacksize; /* 0x28 */ + UInt8 bigendian; /* 0x2C */ + UInt8 pad; /* 0x2D */ + SInt16 datathreshold; /* 0x2E */ + SInt16 sdata2threshold; /* 0x30 */ + SInt16 codeModel; /* 0x32 */ + UInt8 filler1; /* 0x34 */ + UInt8 filler2; /* 0x35 */ + UInt8 filler3; /* 0x36 */ + UInt8 disable_extensions; /* 0x37 */ + UInt8 deadstrip_partiallink; /* 0x38 */ + UInt8 final_partiallink; /* 0x39 */ + UInt8 resolved_partiallink; /* 0x3A */ + char long_outfile[256]; /* 0x3B */ + UInt8 abi_type; /* 0x13B */ + UInt8 dwarf_version; /* 0x13C */ + UInt8 tune_relocations; /* 0x13D */ + UInt8 reserved_13E; /* 0x13E */ + UInt8 reserved_13F[4]; /* 0x13F-0x142 */ + char interpreter[256]; /* 0x143 */ +} PPCEABIProject; +/* static_assert: sizeof == 580 (0x244) */ + +/* + * "C/C++ Preprocessor" panel (32790 bytes) + */ +typedef struct PPreprocessor { + SInt16 version; /* 0x00: current = 4 */ + UInt8 emit_line; /* 0x02 */ + UInt8 emit_fullpath; /* 0x03 */ + UInt8 keep_comments; /* 0x04 */ + UInt8 unused1; /* 0x05 */ + UInt8 pch_uses_prefix_text; /* 0x06 */ + UInt8 emit_pragmas; /* 0x07 */ + UInt8 keep_whitespace; /* 0x08 */ + UInt8 emit_file; /* 0x09 */ + UInt8 multibyte_encoding; /* 0x0A */ + UInt8 reserved_0B[11]; /* 0x0B-0x15 */ + char prefix_text[32768]; /* 0x16-0x8015 */ +} PPreprocessor; +/* static_assert: sizeof == 32790 (0x8016) */ + +/* ============================================================ + * MIPS preference panel structs + * ============================================================ */ + +/* "MIPS CodeGen" panel (20 bytes) */ +typedef struct PMIPSCodeGen { + SInt16 version; /* 0x00: ignored by DLL */ + UInt8 structalignment; /* 0x02: struct alignment (bulk-copied, not individually read) */ + UInt8 tracebacktables; /* 0x03: traceback tables (bulk-copied, not individually read) */ + SInt16 processor; /* 0x04: processor type (overridden to 0x1000 for PSX in init) */ + SInt16 fpuType; /* 0x06: FPU type: 0=none, 1=single, 2=double, 3=all */ + SInt16 isaLevel; /* 0x08: MIPS ISA level (I/II/III/IV) */ + UInt8 multibyteAware; /* 0x0A: multibyte string handling */ + UInt8 peephole; /* 0x0B: peephole optimization enable */ + UInt8 reserved_0C; /* 0x0C: unused */ + UInt8 useIntrinsics; /* 0x0D: inline intrinsics for strcpy/memcpy/etc */ + UInt8 reserved_0E; /* 0x0E: unused */ + UInt8 reserved_0F; /* 0x0F: unused */ + UInt32 reserved_10; /* 0x10: stored but never read */ +} PMIPSCodeGen; + +/* "MIPS Linker Panel" (340 bytes) */ +typedef struct PMIPSLinker { + SInt16 version; /* 0x00 */ + UInt8 reserved_02; /* 0x02 */ + UInt8 reserved_03; /* 0x03 */ + UInt8 reserved_04; /* 0x04 */ + UInt8 genOutput; /* 0x05: controls linker output behavior */ + UInt8 reserved_06[334]; /* 0x06..0x153 */ +} PMIPSLinker; + +/* "MIPS Project" (60 bytes) */ +typedef struct PMIPSProject { + SInt16 version; /* 0x00 */ + UInt8 reserved_02; /* 0x02 */ + UInt8 reserved_03; /* 0x03 */ + SInt16 projectSetting; /* 0x04: project-level setting */ + UInt8 reserved_06[54]; /* 0x06..0x3B */ +} PMIPSProject; + +#pragma pack(pop) + +/* Maximum size of the accumulated define/pragma text buffer */ +#define DEFINE_TEXT_MAX (64 * 1024) +/* Virtual prefix file name used for command-line defines/pragmas */ +#define CMDLINE_DEFINES_VFILE "(command-line defines)" + +/* + * CWPluginPrivateContext - the context structure passed to the DLL. + * + * The DLL does not read this struct directly, so we store our host state here. + */ +typedef struct CWPluginPrivateContext { + SInt32 request; + SInt32 apiVersion; + SInt32 numFiles; + SInt32 whichFile; + + /* Source file */ + char sourceFile[MAX_PATH]; + char* sourceText; + SInt32 sourceTextSize; + + /* Output */ + char outputFile[MAX_PATH]; + void* objectData; + SInt32 objectDataSize; + CWObjectData storedObject; + int objectStored; + + /* Include paths */ + HostAccessPath* userPaths; + SInt32 userPathCount; + HostAccessPath* systemPaths; + SInt32 systemPathCount; + + /* Include search behavior */ + SInt16 includeSearchMode; /* hostIncludeSearch* */ + Boolean noSysPath; /* -nosyspath */ + Boolean useDefaultIncludes; /* -stdinc / -defaults */ + Boolean searchPaths; /* -search */ + Boolean gccIncludes; /* -gccincludes set */ + Boolean usedDashIMinus; /* -I- seen */ + + /* + * Typed preference structs. + */ + PFrontEndC prefsFrontEnd; + PWarningC prefsWarnings; + PGlobalOptimizer prefsOptimizer; + + /* MIPS-specific panels */ + PMIPSCodeGen prefsMIPSCodeGen; + PMIPSLinker prefsMIPSLinker; + PMIPSProject prefsMIPSProject; + UInt8 prefsMIPSCodeGenPanel[sizeof(PMIPSCodeGen)]; + UInt8 prefsMIPSLinkerPanel[sizeof(PMIPSLinker)]; + Boolean prefsMIPSCodeGenR4Compat; + + /* PPC EABI-specific panels */ + PPCEABICodeGen prefsPPCCodeGen; + PPCEABILinker prefsPPCLinker; + PPCEABIProject prefsPPCProject; + PPreprocessor prefsPreprocessor; + + /* Define/pragma text exposed as a virtual prefix file */ + char* defineText; /* accumulated #define/#undef/#pragma/#include lines */ + SInt32 defineTextLen; + + /* Flags from command line */ + int preprocess; /* -E flag */ + int preprocessOnly; /* -P flag (preprocess to file, no line markers) */ + int disassemble; /* -dis / -disassemble / -S */ + int disassembleToFile; /* -S */ + int debugInfo; /* -g flag */ + int verbose; /* -v flag */ + int noWarnings; /* -w off */ + int warningsAreErrors; /* -w err / -Werror */ + int maxErrors; /* -maxerrors N (0=unlimited) */ + int maxWarnings; /* -maxwarnings N (0=unlimited) */ + int msgStyle; /* 0=std, 1=gcc, 2=parseable */ + int noFail; /* -nofail (continue after per-file failures) */ + int forceIncludeOnce; /* -once / -notonce host-side compatibility */ + int dependencyMode; /* 0=off, 1=deps-only (-M/-MM/-make), 2=deps+compile (-MD/-MMD) */ + int depsOnlyUserFiles; /* -MM / -MMD */ + char dependencyOutputFile[MAX_PATH]; /* -o makefile path for deps-only mode */ + + /* Error tracking */ + int numErrors; + int numWarnings; + + /* File ID counter for includes */ + short nextFileID; + HostFileRecord* fileRecords; + SInt32 fileRecordCount; + SInt32 fileRecordCap; + char lastIncludeDir[MAX_PATH]; + HostIncludeRecord* includeRecords; + SInt32 includeRecordCount; + SInt32 includeRecordCap; + + /* Preprocessed output text */ + char* preprocessedText; + SInt32 preprocessedTextSize; +} CWPluginPrivateContext; + +#endif /* HOST_CTX_H */ diff --git a/mwccwrap.c b/mwccwrap.c new file mode 100644 index 0000000..726e878 --- /dev/null +++ b/mwccwrap.c @@ -0,0 +1,2532 @@ +/* + * mwccwrap.c - Command-line host for CodeWarrior compiler DLLs + * + * Behavior and flags intended to mirror the official MWCC command-line tools as closely as possible. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "host_ctx.h" + +#define MWCCWRAP_VERSION "1.0" +#define MAX_INCLUDE_PATHS 64 +#define MAX_SOURCE_FILES 256 + +static const char* const kKnownCompilerDllNames[] = { + "cc_mips.dll", + "ppc_eabi.dll" +}; + +#define KNOWN_COMPILER_DLL_COUNT ((int)(sizeof(kKnownCompilerDllNames) / sizeof(kKnownCompilerDllNames[0]))) + +/* Plugin entry point type */ +typedef short (__stdcall *PluginMainFunc)(CWPluginContext context); + +/* PluginLib string table init */ +typedef void (__cdecl *MWCC_InitStringTableFunc)(HMODULE); + +/* PluginLib handle */ +static HMODULE g_registered_pluginlib_module = NULL; +static int g_registered_pluginlib_version = 0; + +__declspec(dllexport) void __cdecl MWCC_RegisterPluginLib(HMODULE module, int version) { + g_registered_pluginlib_module = module; + g_registered_pluginlib_version = version; +} + +static void copy_cstr(char* dst, size_t dst_size, const char* src); + +typedef struct CompilerVersionInfo { + char product_name[128]; + char file_description[128]; + char company_name[128]; + char legal_copyright[160]; + char product_version[64]; + char file_version[64]; + char special_build[128]; + DWORD version_ms; + DWORD version_ls; + int has_fixed_version; + DWORD link_timestamp; + int has_link_timestamp; +} CompilerVersionInfo; + +/* ============================================================ + * Help & Version + * ============================================================ */ + +static void print_help(void) { + fprintf(stderr, + "mwccwrap v" MWCCWRAP_VERSION " - CodeWarrior compiler DLL wrapper\n" + "\n" + "Usage: mwccwrap [options] [-o output] input1.c [input2.c ...]\n" + "\n" + "General:\n" + " -help Display this help\n" + " -version Display version information from compiler DLL\n" + " -v, -verbose Verbose output (show plugin callbacks)\n" + " -c, -nolink Compile only (implicit, no linker)\n" + " -dll Compiler DLL path/name\n" + " (default: auto-search known compiler DLLs)\n" + " Known names: cc_mips.dll, ppc_eabi.dll\n" + " -msgstyle std|gcc|parseable Message format style\n" + " -maxerrors Stop after N errors (0=unlimited)\n" + " -maxwarnings Stop after N warnings (0=unlimited)\n" + " -nofail Continue compiling later files after failures\n" + "\n" + "Preprocessing/Input:\n" + " -o Output file (default: input.o)\n" + " -E Preprocess only (to stdout)\n" + " -EP Preprocess, strip #line directives\n" + " -P Preprocess to file\n" + " -M / -MM / -make Emit Makefile dependencies (no object code)\n" + " -MD / -MMD Emit dependencies to .d file and compile object\n" + " -dis, -disassemble Disassemble to stdout\n" + " -S Disassemble to file\n" + " -D [=] Define preprocessor macro\n" + " -U Undefine preprocessor macro\n" + " -I Add user include path\n" + " -i- / -I- Switch subsequent -I to system paths\n" + " -ir Add recursive include path\n" + " -prefix Prefix file onto all source files\n" + " -include Same as -prefix\n" + " -pragma \"\" Inject #pragma directive\n" + " -nosyspath Treat <> includes like \"\" includes\n" + " -gccincludes GCC-style include semantics\n" + " -cwd proj|source|explicit|include\n" + " #include search semantics\n" + " -stdinc / -nostdinc Enable/disable %%MWCIncludes%% defaults\n" + " -defaults / -nodefaults Alias for [no]stdinc\n" + " -search Search access paths for source file args\n" + "\n" + "C/C++ Language:\n" + " -lang c|c++|ec++ Source language\n" + " -dialect c|c++ Source language (alias)\n" + " -char signed|unsigned Default char signedness (default: signed)\n" + " -enum min|int Enum sizing (default: int)\n" + " -inline on|smart|off|none|auto|noauto|all|deferred|level=\n" + " Inlining control (default: on)\n" + " -bool on|off Enable bool/true/false (default: on)\n" + " -Cpp_exceptions on|off Enable C++ exceptions (default: on)\n" + " -RTTI on|off Enable runtime type info (default: on)\n" + " -ARM on|off ARM conformance checking (default: off)\n" + " -ansi off|on|relaxed|strict ANSI conformance level\n" + " -strict on|off Strict ANSI checking (default: off)\n" + " -trigraphs on|off Enable trigraphs (default: off)\n" + " -stdkeywords on|off Restrict to standard keywords (default: off)\n" + " -wchar_t on|off wchar_t as built-in type (default: off)\n" + " -r, -requireprotos Require function prototypes\n" + " -str[ings] [no]reuse|[no]pool|[no]readonly\n" + " String constant handling\n" + " -multibyte[aware] Enable multibyte character support\n" + " -once Prevent repeated header processing\n" + " -relax_pointers Relax pointer type checking\n" + "\n" + "Warnings:\n" + " -w off Disable all warnings\n" + " -w on Enable default warnings\n" + " -w all Enable all warnings, require prototypes\n" + " -w [no]error Treat warnings as errors\n" + " -w [no]pragmas Illegal #pragmas\n" + " -w [no]empty Empty declarations\n" + " -w [no]possible Possible unwanted effects\n" + " -w [no]unusedarg Unused arguments\n" + " -w [no]unusedvar Unused variables\n" + " -w [no]unused All unused (arg+var)\n" + " -w [no]extracomma Extra commas\n" + " -w [no]pedantic Pedantic error checking\n" + " -w [no]hidevirtual Hidden virtual functions\n" + " -w [no]implicit Implicit arithmetic conversions\n" + " -w [no]notinlined Inline functions not inlined\n" + " -w [no]largeargs Large args to unprototyped functions\n" + " -w [no]structclass Inconsistent struct/class usage\n" + " -Wall / -Werror GCC-compatible aliases\n" + "\n" + "MIPS Backend:\n" + " -fp off|single Floating-point options (default: single)\n" + " -profile Enable calls to profiler\n" + "\n" + "Optimizer:\n" + " -O0 Same as -opt off\n" + " -O1 Same as -opt level=1\n" + " -O2, -O Same as -opt level=2\n" + " -O3 Same as -opt level=3\n" + " -O4 Same as -opt level=4\n" + " -Os Same as -opt space\n" + " -Op Same as -opt speed\n" + " -opt off|on|all|full Optimization level\n" + " -opt speed|space Optimization target\n" + " -opt level= Set optimization level (0-4)\n" + " -opt [no]intrinsics Inline intrinsic functions\n" + " -opt [no]peephole Peephole optimization\n" + "\n" + "Debug:\n" + " -g Generate debug info (same as -sym full)\n" + " -sym off|on|full Debug symbol control\n" + "\n" + ); +} + +static void print_wrapper_version(void) { + fprintf(stderr, + "mwccwrap v" MWCCWRAP_VERSION " - CodeWarrior compiler DLL wrapper\n" + "Use -version to query version metadata from the selected compiler DLL.\n" + ); +} + +static int read_pe_link_timestamp(const char* path, DWORD* out_timestamp) { + FILE* f = fopen(path, "rb"); + IMAGE_DOS_HEADER dos; + DWORD nt_sig; + IMAGE_FILE_HEADER file_hdr; + + if (!out_timestamp) return 0; + *out_timestamp = 0; + if (!f) return 0; + + if (fread(&dos, 1, sizeof(dos), f) != sizeof(dos) || dos.e_magic != IMAGE_DOS_SIGNATURE) { + fclose(f); + return 0; + } + if (fseek(f, dos.e_lfanew, SEEK_SET) != 0) { + fclose(f); + return 0; + } + if (fread(&nt_sig, 1, sizeof(nt_sig), f) != sizeof(nt_sig) || nt_sig != IMAGE_NT_SIGNATURE) { + fclose(f); + return 0; + } + if (fread(&file_hdr, 1, sizeof(file_hdr), f) != sizeof(file_hdr)) { + fclose(f); + return 0; + } + + *out_timestamp = file_hdr.TimeDateStamp; + fclose(f); + return 1; +} + +static int query_version_string(const void* version_blob, + WORD lang, + WORD codepage, + const char* key, + char* out, + size_t out_size) +{ + char query[128]; + LPSTR value = NULL; + UINT value_len = 0; + + if (!version_blob || !key || !out || out_size == 0) return 0; + out[0] = '\0'; + + snprintf(query, sizeof(query), "\\StringFileInfo\\%04x%04x\\%s", + (unsigned int)lang, (unsigned int)codepage, key); + + if (!VerQueryValueA((LPVOID)version_blob, query, (LPVOID*)&value, &value_len) || + !value || value_len == 0 || value[0] == '\0') + { + return 0; + } + + copy_cstr(out, out_size, value); + return 1; +} + +static int get_compiler_version_info(const char* dll_path, CompilerVersionInfo* out_info) { + DWORD dummy = 0; + DWORD info_size; + void* info_blob = NULL; + VS_FIXEDFILEINFO* ffi = NULL; + UINT ffi_len = 0; + struct LangCodePage { + WORD lang; + WORD codepage; + }; + struct LangCodePage* translations = NULL; + UINT translations_len = 0; + struct LangCodePage probes[4]; + int probe_count = 0; + int found_any = 0; + + if (!out_info) return 0; + memset(out_info, 0, sizeof(*out_info)); + if (!dll_path || !dll_path[0]) return 0; + + info_size = GetFileVersionInfoSizeA(dll_path, &dummy); + if (info_size == 0) return 0; + + info_blob = malloc(info_size); + if (!info_blob) return 0; + if (!GetFileVersionInfoA(dll_path, 0, info_size, info_blob)) { + free(info_blob); + return 0; + } + + if (VerQueryValueA(info_blob, "\\", (LPVOID*)&ffi, &ffi_len) && + ffi && ffi_len >= sizeof(VS_FIXEDFILEINFO) && + ffi->dwSignature == 0xFEEF04BD) + { + out_info->version_ms = ffi->dwFileVersionMS; + out_info->version_ls = ffi->dwFileVersionLS; + out_info->has_fixed_version = 1; + found_any = 1; + } + + if (VerQueryValueA(info_blob, "\\VarFileInfo\\Translation", (LPVOID*)&translations, &translations_len) && + translations && translations_len >= sizeof(*translations)) + { + probes[probe_count++] = translations[0]; + } + + probes[probe_count++] = (struct LangCodePage){0x0409, 0x04B0}; + probes[probe_count++] = (struct LangCodePage){0x0409, 0x04E4}; + probes[probe_count++] = (struct LangCodePage){0x0409, 0x0000}; + + for (int i = 0; i < probe_count; i++) { + if (!out_info->product_name[0] && + query_version_string(info_blob, probes[i].lang, probes[i].codepage, + "ProductName", + out_info->product_name, sizeof(out_info->product_name))) { + found_any = 1; + } + if (!out_info->file_description[0] && + query_version_string(info_blob, probes[i].lang, probes[i].codepage, + "FileDescription", + out_info->file_description, sizeof(out_info->file_description))) { + found_any = 1; + } + if (!out_info->company_name[0] && + query_version_string(info_blob, probes[i].lang, probes[i].codepage, + "CompanyName", + out_info->company_name, sizeof(out_info->company_name))) { + found_any = 1; + } + if (!out_info->legal_copyright[0] && + query_version_string(info_blob, probes[i].lang, probes[i].codepage, + "LegalCopyright", + out_info->legal_copyright, sizeof(out_info->legal_copyright))) { + found_any = 1; + } + if (!out_info->product_version[0] && + query_version_string(info_blob, probes[i].lang, probes[i].codepage, + "ProductVersion", + out_info->product_version, sizeof(out_info->product_version))) { + found_any = 1; + } + if (!out_info->file_version[0] && + query_version_string(info_blob, probes[i].lang, probes[i].codepage, + "FileVersion", + out_info->file_version, sizeof(out_info->file_version))) { + found_any = 1; + } + if (!out_info->special_build[0] && + query_version_string(info_blob, probes[i].lang, probes[i].codepage, + "SpecialBuild", + out_info->special_build, sizeof(out_info->special_build))) { + found_any = 1; + } + } + + if (read_pe_link_timestamp(dll_path, &out_info->link_timestamp) && + out_info->link_timestamp != 0) + { + out_info->has_link_timestamp = 1; + found_any = 1; + } + + free(info_blob); + return found_any; +} + +static int should_enable_mips_r4_compat(const CompilerVersionInfo* info) { + unsigned int major, minor, patch, build; + + if (!info || !info->has_fixed_version) return 0; + + major = (unsigned int)HIWORD(info->version_ms); + minor = (unsigned int)LOWORD(info->version_ms); + patch = (unsigned int)HIWORD(info->version_ls); + build = (unsigned int)LOWORD(info->version_ls); + + if (!(major == 2 && minor == 44 && patch == 14 && build == 0)) return 0; + + if (info->file_description[0] && + _stricmp(info->file_description, "MIPS Compiler for PlayStation") != 0) + { + return 0; + } + + return 1; +} + +static void format_version_line(const CompilerVersionInfo* info, char* out, size_t out_size) { + unsigned int major, minor, patch, build; + + if (!out || out_size == 0) return; + out[0] = '\0'; + if (!info) return; + + if (info->has_fixed_version) { + major = (unsigned int)HIWORD(info->version_ms); + minor = (unsigned int)LOWORD(info->version_ms); + patch = (unsigned int)HIWORD(info->version_ls); + build = (unsigned int)LOWORD(info->version_ls); + snprintf(out, out_size, "Version %u.%u.%u build %u", major, minor, patch, build); + return; + } + + if (info->product_version[0]) { + snprintf(out, out_size, "Version %s", info->product_version); + } else if (info->file_version[0]) { + snprintf(out, out_size, "Version %s", info->file_version); + } +} + +static void format_timestamp_line(const CompilerVersionInfo* info, char* out, size_t out_size) { + time_t ts; + struct tm* utc_tm; + + if (!out || out_size == 0) return; + out[0] = '\0'; + if (!info || !info->has_link_timestamp) return; + + ts = (time_t)info->link_timestamp; + utc_tm = gmtime(&ts); + if (!utc_tm) return; + if (strftime(out, out_size, "%b %d %Y %H:%M:%S UTC", utc_tm) == 0) { + out[0] = '\0'; + } +} + +static void print_ascii_line(const char* text) { + char buf[320]; + size_t j = 0; + + if (!text) return; + for (size_t i = 0; text[i] && j + 1 < sizeof(buf); i++) { + unsigned char ch = (unsigned char)text[i]; + if (ch == 0xA9) { + if (j + 3 < sizeof(buf)) { + buf[j++] = '('; + buf[j++] = 'c'; + buf[j++] = ')'; + } + } else if (ch >= 32 && ch <= 126) { + buf[j++] = (char)ch; + } else { + buf[j++] = '?'; + } + } + buf[j] = '\0'; + fprintf(stderr, "%s\n", buf); +} + +static void print_dynamic_version(const char* dll_path, const CompilerVersionInfo* info) { + char version_line[96]; + char timestamp_line[96]; + const char* title = NULL; + + if (info) { + if (info->file_description[0]) title = info->file_description; + else if (info->product_name[0]) title = info->product_name; + } + + if (title) print_ascii_line(title); + else fprintf(stderr, "Metrowerks C/C++ Compiler\n"); + + if (info && info->legal_copyright[0]) print_ascii_line(info->legal_copyright); + + format_version_line(info, version_line, sizeof(version_line)); + if (version_line[0]) fprintf(stderr, "%s\n", version_line); + + if (info && info->special_build[0]) fprintf(stderr, "Special Build: %s\n", info->special_build); + + format_timestamp_line(info, timestamp_line, sizeof(timestamp_line)); + if (timestamp_line[0]) fprintf(stderr, "Runtime Built: %s\n", timestamp_line); + + if (dll_path && dll_path[0]) fprintf(stderr, "Compiler DLL: %s\n", dll_path); +} + +static const char* basename_from_path(const char* path) { + const char* slash; + const char* bslash; + const char* base; + + if (!path || !path[0]) return "mwccwrap.exe"; + slash = strrchr(path, '/'); + bslash = strrchr(path, '\\'); + base = slash; + if (!base || (bslash && bslash > base)) base = bslash; + return base ? (base + 1) : path; +} + +/* ============================================================ + * Define/Pragma text buffer helpers + * ============================================================ */ + +static void append_define_text(CWPluginContext ctx, const char* text) { + if (!ctx->defineText) { + ctx->defineText = (char*)calloc(1, DEFINE_TEXT_MAX); + if (!ctx->defineText) return; + ctx->defineTextLen = 0; + } + int len = (int)strlen(text); + if (ctx->defineTextLen + len + 1 >= DEFINE_TEXT_MAX) { + fprintf(stderr, "warning: define text buffer full, ignoring: %s", text); + return; + } + memcpy(ctx->defineText + ctx->defineTextLen, text, len); + ctx->defineTextLen += len; + ctx->defineText[ctx->defineTextLen] = '\0'; +} + +/* -D name or -D name=value */ +static void add_define(CWPluginContext ctx, const char* arg) { + char buf[1024]; + const char* eq = strchr(arg, '='); + if (eq) { + snprintf(buf, sizeof(buf), "#define %.*s %s\n", (int)(eq - arg), arg, eq + 1); + } else { + snprintf(buf, sizeof(buf), "#define %s 1\n", arg); + } + append_define_text(ctx, buf); +} + +/* -U name */ +static void add_undef(CWPluginContext ctx, const char* name) { + char buf[1024]; + snprintf(buf, sizeof(buf), "#undef %s\n", name); + append_define_text(ctx, buf); +} + +/* -pragma "text" */ +static void add_pragma(CWPluginContext ctx, const char* text) { + char buf[1024]; + snprintf(buf, sizeof(buf), "#pragma %s\n", text); + append_define_text(ctx, buf); +} + +/* -prefix / -include */ +static void add_prefix_include(CWPluginContext ctx, const char* filename) { + char buf[MAX_PATH + 32]; + size_t len; + int copy_len; + + if (!filename || !filename[0]) return; + + len = strlen(filename); + copy_len = (len > (size_t)(MAX_PATH - 1)) ? (MAX_PATH - 1) : (int)len; + if (filename[0] == '<' && len > 1 && filename[len - 1] == '>') { + snprintf(buf, sizeof(buf), "#include %.*s\n", copy_len, filename); + } else { + snprintf(buf, sizeof(buf), "#include \"%.*s\"\n", copy_len, filename); + } + append_define_text(ctx, buf); +} + +static void set_oldprefixname(PFrontEndC* fe, const char* name) { + size_t len; + + memset(fe->oldprefixname, 0, sizeof(fe->oldprefixname)); + if (!name || !name[0]) return; + + /* + * MWCC stores oldprefixname as a Str31 (Pascal string). + */ + len = strlen(name); + if (len > 31) len = 31; + fe->oldprefixname[0] = (unsigned char)len; + memcpy(fe->oldprefixname + 1, name, len); +} + +/* ============================================================ + * Warning flag helpers + * ============================================================ */ + +static void set_all_warnings(PWarningC* w, Boolean val) { + w->warn_illpragma = val; + w->warn_emptydecl = val; + w->warn_possunwant = val; + w->warn_unusedvar = val; + w->warn_unusedarg = val; + w->warn_extracomma = val; + w->pedantic = val; + w->warn_hidevirtual = val; + w->warn_implicitconv = val; + w->warn_notinlined = val; + w->warn_structclass = val; + w->warn_missingreturn = val; + w->warn_no_side_effect = val; + w->warn_resultnotused = val; + w->warn_padding = val; + w->warn_impl_i2f_conv = val; + w->warn_impl_f2i_conv = val; + w->warn_impl_s2u_conv = val; + w->warn_illtokenpasting = val; + w->warn_filenamecaps = val; + w->warn_filenamecapssystem = val; + w->warn_undefmacro = val; + w->warn_ptrintconv = val; +} + +/* ============================================================ + * Inline parsing helper + * ============================================================ */ + +static int parse_onoff(const char* val) { + if (strcmp(val, "on") == 0) return 1; + if (strcmp(val, "off") == 0) return 0; + return -1; +} + +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'; +} + +typedef struct PendingIncludePath { + char path[MAX_PATH]; + Boolean system; + Boolean recursive; +} PendingIncludePath; + +static void add_include_path(PendingIncludePath include_paths[], + int* num_includes, + const char* path, + Boolean system, + Boolean recursive) +{ + if (*num_includes >= MAX_INCLUDE_PATHS || !path || !path[0]) return; + copy_cstr(include_paths[*num_includes].path, MAX_PATH, path); + include_paths[*num_includes].system = system; + include_paths[*num_includes].recursive = recursive; + (*num_includes)++; +} + +static void move_pending_system_paths_to_user(PendingIncludePath include_paths[], + int num_includes) +{ + for (int i = 0; i < num_includes; i++) { + if (include_paths[i].system) { + include_paths[i].system = FALSE; + } + } +} + +static void move_pending_user_paths_to_system(PendingIncludePath include_paths[], + int num_includes) +{ + for (int i = 0; i < num_includes; i++) { + include_paths[i].system = TRUE; + } +} + +static int parse_ppc_processor(const char* name, SInt16* out_value) { + struct ProcessorNameMap { + const char* name; + SInt16 value; + }; + static const struct ProcessorNameMap kMap[] = { + {"401", 0x00}, {"403", 0x01}, {"505", 0x02}, {"509", 0x03}, + {"555", 0x04}, {"556", 0x19}, {"565", 0x1A}, + {"601", 0x05}, {"602", 0x06}, {"603", 0x07}, {"603e", 0x08}, + {"604", 0x09}, {"604e", 0x0A}, + {"740", 0x0B}, {"750", 0x0C}, {"7400", 0x15}, {"7450", 0x18}, + {"801", 0x0D}, {"821", 0x0E}, {"823", 0x0F}, + {"8240", 0x12}, {"8260", 0x13}, + {"850", 0x10}, {"860", 0x11}, + {"gekko", 0x16}, {"e500", 0x17}, + {"generic", 0x14} + }; + if (!name || !out_value) return 0; + for (size_t i = 0; i < sizeof(kMap) / sizeof(kMap[0]); i++) { + if (_stricmp(name, kMap[i].name) == 0) { + *out_value = kMap[i].value; + return 1; + } + } + return 0; +} + +static int set_ppc_alignment(PPCEABICodeGen* ppc, const char* value) { + if (!ppc || !value) return 0; + /* MWCC AlignMode enum: + * 0=mac68k, 1=mac68k4byte, 2=powerpc, 3=1-byte, 4=2-byte, + * 5=4-byte, 6=8-byte, 7=16-byte, 8=packed. + */ + if (_stricmp(value, "power") == 0 || _stricmp(value, "powerpc") == 0 || + _stricmp(value, "ppc") == 0) { + ppc->structalignment = 2; + return 1; + } + if (_stricmp(value, "mac68k") == 0) { + ppc->structalignment = 0; + return 1; + } + if (_stricmp(value, "mac68k4byte") == 0) { + ppc->structalignment = 1; + return 1; + } + if (strcmp(value, "1") == 0 || _stricmp(value, "1byte") == 0) { + ppc->structalignment = 3; + return 1; + } + if (strcmp(value, "2") == 0 || _stricmp(value, "2byte") == 0) { + ppc->structalignment = 4; + return 1; + } + if (strcmp(value, "4") == 0 || _stricmp(value, "4byte") == 0) { + ppc->structalignment = 5; + return 1; + } + if (strcmp(value, "8") == 0 || _stricmp(value, "8byte") == 0) { + ppc->structalignment = 6; + return 1; + } + if (strcmp(value, "16") == 0 || _stricmp(value, "16byte") == 0) { + ppc->structalignment = 7; + return 1; + } + if (_stricmp(value, "packed") == 0) { + ppc->structalignment = 8; + return 1; + } + if (_stricmp(value, "array") == 0 || _stricmp(value, "arraymembers") == 0) { + /* Stored in parser-side options on newer compilers; no direct panel bit in PBackEnd. */ + return 1; + } + return 0; +} + +static int apply_ppc_string_option(PFrontEndC* fe, PPCEABICodeGen* ppc, const char* token) { + if (!fe || !ppc || !token || !token[0]) return 0; + + if (_stricmp(token, "reuse") == 0) { + fe->dontreusestrings = 0; + return 1; + } + if (_stricmp(token, "noreuse") == 0) { + fe->dontreusestrings = 1; + return 1; + } + if (_stricmp(token, "pool") == 0) { + fe->poolstrings = 1; + return 1; + } + if (_stricmp(token, "nopool") == 0) { + fe->poolstrings = 0; + return 1; + } + if (_stricmp(token, "readonly") == 0) { + ppc->readonlystrings = 1; + return 1; + } + if (_stricmp(token, "noreadonly") == 0) { + ppc->readonlystrings = 0; + return 1; + } + return 0; +} + +static void apply_dash_o_token(const char* token, + PGlobalOptimizer* opt, + PMIPSCodeGen* mips, + PPCEABICodeGen* ppc, + int* handled) +{ + if (!token || !token[0]) return; + + if (strcmp(token, "0") == 0) { + opt->optimizationlevel = 0; + return; + } + if (strcmp(token, "1") == 0) { + opt->optimizationlevel = 1; + return; + } + if (strcmp(token, "2") == 0) { + opt->optimizationlevel = 2; + mips->peephole = 1; + ppc->peephole = 1; + return; + } + if (strcmp(token, "3") == 0) { + opt->optimizationlevel = 3; + mips->peephole = 1; + ppc->peephole = 1; + return; + } + if (strcmp(token, "4") == 0) { + opt->optimizationlevel = 4; + mips->peephole = 1; + ppc->peephole = 1; + ppc->scheduling = 1; + return; + } + if (strcmp(token, "p") == 0) { + opt->optfor = 2; + return; + } + if (strcmp(token, "s") == 0) { + opt->optfor = 1; + return; + } + if (handled) *handled = 0; +} + +static int parse_dash_o_option(const char* arg, + PGlobalOptimizer* opt, + PMIPSCodeGen* mips, + PPCEABICodeGen* ppc) +{ + const char* spec; + int handled = 1; + + if (!arg || strncmp(arg, "-O", 2) != 0) return 0; + spec = arg + 2; + + if (!spec[0]) { + apply_dash_o_token("2", opt, mips, ppc, &handled); + return handled; + } + + if (strchr(spec, ',')) { + char tmp[64]; + size_t len = strlen(spec); + if (len >= sizeof(tmp)) len = sizeof(tmp) - 1; + memcpy(tmp, spec, len); + tmp[len] = '\0'; + + char* tok = strtok(tmp, ","); + while (tok) { + if (tok[0]) { + apply_dash_o_token(tok, opt, mips, ppc, &handled); + } + tok = strtok(NULL, ","); + } + return handled; + } + + apply_dash_o_token(spec, opt, mips, ppc, &handled); + return handled; +} + +static int apply_opt_keyword(const char* token, + PGlobalOptimizer* opt, + PMIPSCodeGen* mips, + PPCEABICodeGen* ppc) +{ + if (!token || !token[0]) return 0; + + if (strcmp(token, "off") == 0 || strcmp(token, "none") == 0) { + opt->optimizationlevel = 0; + return 1; + } + if (strcmp(token, "on") == 0) { + opt->optimizationlevel = 2; + mips->peephole = 1; + ppc->peephole = 1; + return 1; + } + if (strcmp(token, "all") == 0 || strcmp(token, "full") == 0) { + opt->optimizationlevel = 4; + opt->optfor = 1; + mips->useIntrinsics = 1; + mips->peephole = 1; + ppc->peephole = 1; + ppc->scheduling = 1; + return 1; + } + if (strcmp(token, "speed") == 0) { + opt->optfor = 2; + return 1; + } + if (strcmp(token, "space") == 0 || strcmp(token, "size") == 0) { + opt->optfor = 1; + return 1; + } + if (strncmp(token, "level=", 6) == 0 || strncmp(token, "l=", 2) == 0) { + const char* num = strchr(token, '=') + 1; + int lv = atoi(num); + if (lv >= 0 && lv <= 4) { + opt->optimizationlevel = (UInt8)lv; + return 1; + } + return 0; + } + if (strcmp(token, "intrinsics") == 0) { + mips->useIntrinsics = 1; + return 1; + } + if (strcmp(token, "nointrinsics") == 0) { + mips->useIntrinsics = 0; + return 1; + } + if (strcmp(token, "peephole") == 0 || strcmp(token, "peep") == 0) { + mips->peephole = 1; + ppc->peephole = 1; + return 1; + } + if (strcmp(token, "nopeephole") == 0 || strcmp(token, "nopeep") == 0) { + mips->peephole = 0; + ppc->peephole = 0; + return 1; + } + if (strcmp(token, "schedule") == 0) { + ppc->scheduling = 1; + return 1; + } + if (strcmp(token, "noschedule") == 0) { + ppc->scheduling = 0; + return 1; + } + return 0; +} + +static int is_drive_prefix(const char* token, int token_len, char next_ch) { + return token_len == 1 && + isalpha((unsigned char)token[0]) && + (next_ch == '\\' || next_ch == '/'); +} + +static const char* find_default_include_env(const CWPluginContext ctx, const char** matched_name) { + static const char* env_names_c[] = { + "MWCIncludes" + }; + static const char* env_names_cpp[] = { + "MWCppIncludes", + "MWCIncludes" + }; + const char** env_names = env_names_c; + size_t env_count = sizeof(env_names_c) / sizeof(env_names_c[0]); + const char* last_name = NULL; + + if (ctx && ctx->prefsFrontEnd.cplusplus) { + env_names = env_names_cpp; + env_count = sizeof(env_names_cpp) / sizeof(env_names_cpp[0]); + } + + for (size_t i = 0; i < env_count; i++) { + const char* name = env_names[i]; + const char* val = getenv(name); + last_name = name; + if (val) { + if (matched_name) *matched_name = name; + return val; + } + } + + if (matched_name) *matched_name = last_name; + return NULL; +} + +/* + * MWCC uses AddAccessPathList(..., ':', ',', ...), while Windows users often + * provide semicolon-separated lists. Accept ':', ';', and ',' separators. + * A leading '+' on an entry means recursive access path. + */ +static void add_default_include_paths(CWPluginContext ctx, + PendingIncludePath include_paths[], + int* num_includes) +{ + const char* env_name = NULL; + const char* env = find_default_include_env(ctx, &env_name); + char token[MAX_PATH]; + int t = 0; + + if (!env) { + if (env_name && env_name[0]) { + fprintf(stderr, "warning: Environment variable '%s' not found\n", env_name); + } + return; + } + if (!env[0]) return; + + for (int i = 0;; i++) { + char ch = env[i]; + char next = env[i + 1]; + int sep = 0; + + if (ch == '\0' || ch == ';' || ch == ',') { + sep = 1; + } else if (ch == ':') { + sep = !is_drive_prefix(token, t, next); + } + + if (sep) { + token[t] = '\0'; + if (t > 0) { + const char* path = token; + Boolean recursive = FALSE; + if (token[0] == '+') { + recursive = TRUE; + path++; + } + add_include_path(include_paths, num_includes, path, TRUE, recursive); + } + t = 0; + if (ch == '\0') break; + continue; + } + + if (t + 1 < MAX_PATH) { + token[t++] = ch; + } + } +} + +/* ============================================================ + * File I/O helpers + * ============================================================ */ + +static char* read_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 = (SInt32)size; + return buf; +} + +static int file_exists(const char* path) { + DWORD attrs; + if (!path || !path[0]) return 0; + attrs = GetFileAttributesA(path); + if (attrs == INVALID_FILE_ATTRIBUTES) return 0; + return (attrs & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +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 int find_file_in_dir_recursive(const char* dir, const char* filename, + char* outpath, size_t outsize, int depth) +{ + char candidate[MAX_PATH]; + char pattern[MAX_PATH]; + WIN32_FIND_DATAA ffd; + HANDLE h; + + if (depth > 32) return 0; + + if (join_path(candidate, sizeof(candidate), dir, filename) && file_exists(candidate)) { + copy_cstr(outpath, outsize, candidate); + return 1; + } + + if (!join_path(pattern, sizeof(pattern), dir, "*")) return 0; + h = FindFirstFileA(pattern, &ffd); + if (h == INVALID_HANDLE_VALUE) return 0; + + do { + char child[MAX_PATH]; + 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 (find_file_in_dir_recursive(child, filename, outpath, outsize, depth + 1)) { + FindClose(h); + return 1; + } + } while (FindNextFileA(h, &ffd)); + + FindClose(h); + return 0; +} + +static int find_file_in_access_paths(HostAccessPath* paths, SInt32 count, + const char* filename, char* outpath, size_t outsize) +{ + for (SInt32 i = 0; i < count; i++) { + if (paths[i].recursive) { + if (find_file_in_dir_recursive(paths[i].path, filename, outpath, outsize, 0)) { + return 1; + } + } else { + char candidate[MAX_PATH]; + if (join_path(candidate, sizeof(candidate), paths[i].path, filename) && + file_exists(candidate)) + { + copy_cstr(outpath, outsize, candidate); + return 1; + } + } + } + return 0; +} + +/* + * Convert line endings to \r (Mac convention). + * cc_mips.dll is a Mac-heritage compiler that uses \r for line endings. + * Without this conversion, \n is treated as whitespace and preprocessor + * output loses line structure. + * + * Converts in-place: \r\n -> \r, \n -> \r (buffer can only shrink). + * Returns the new size. + */ +static SInt32 convert_line_endings_to_cr(char* buf, SInt32 size) { + SInt32 out = 0; + for (SInt32 i = 0; i < size; i++) { + if (buf[i] == '\r' && i + 1 < size && buf[i + 1] == '\n') { + buf[out++] = '\r'; + i++; /* skip the \n */ + } else if (buf[i] == '\n') { + buf[out++] = '\r'; + } else { + buf[out++] = buf[i]; + } + } + buf[out] = '\0'; + return out; +} + +static void write_object_file(CWPluginContext ctx) { + if (!ctx->objectStored || !ctx->objectData || ctx->objectDataSize <= 0) { + fprintf(stderr, "No object data to write.\n"); + return; + } + + const char* outpath = ctx->outputFile[0] ? ctx->outputFile : "output.o"; + FILE* f = fopen(outpath, "wb"); + if (!f) { + fprintf(stderr, "Cannot open output file: %s\n", outpath); + return; + } + + fwrite(ctx->objectData, 1, ctx->objectDataSize, f); + fclose(f); + + if (ctx->verbose) { + fprintf(stderr, "Wrote %d bytes to %s\n", ctx->objectDataSize, outpath); + } +} + +static int is_directory_path(const char* path) { + DWORD attrs; + if (!path || !path[0]) return 0; + attrs = GetFileAttributesA(path); + if (attrs == INVALID_FILE_ATTRIBUTES) return 0; + return (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +static const char* get_path_leaf(const char* path) { + const char* slash = NULL; + const char* backslash = NULL; + if (!path) return ""; + slash = strrchr(path, '/'); + backslash = strrchr(path, '\\'); + if (!slash && !backslash) return path; + if (!slash) return backslash + 1; + if (!backslash) return slash + 1; + return (slash > backslash ? slash : backslash) + 1; +} + +static void replace_extension(const char* path, const char* ext, char* out, size_t out_size) { + const char* dot; + const char* slash; + const char* backslash; + const char* sep; + size_t stem_len; + int n; + + if (!out || out_size == 0) return; + if (!path || !path[0]) { + out[0] = '\0'; + return; + } + + dot = strrchr(path, '.'); + slash = strrchr(path, '/'); + backslash = strrchr(path, '\\'); + if (slash && backslash) sep = (slash > backslash) ? slash : backslash; + else if (slash) sep = slash; + else sep = backslash; + + if (dot && (!sep || dot > sep)) { + stem_len = (size_t)(dot - path); + } else { + stem_len = strlen(path); + } + + n = snprintf(out, out_size, "%.*s%s", (int)stem_len, path, ext); + if (n < 0 || (size_t)n >= out_size) { + out[out_size - 1] = '\0'; + } +} + +static void write_make_escaped(FILE* f, const char* path) { + if (!f || !path) return; + for (const unsigned char* p = (const unsigned char*)path; *p; p++) { + if (*p == ' ') fputc('\\', f); + fputc((int)*p, f); + } +} + +static int emit_dependency_rule(CWPluginContext ctx, + const char* source_path, + const char* object_path, + const char* output_path, + int append) +{ + FILE* out = stdout; + HostFileRecord* deps = NULL; + int dep_count = 0; + int visible_deps = 0; + int ret = 1; + + if (!ctx || !source_path || !source_path[0] || !object_path || !object_path[0]) { + return 1; + } + + if (output_path && output_path[0]) { + out = fopen(output_path, append ? "ab" : "wb"); + if (!out) { + fprintf(stderr, "Cannot open dependency output file: %s\n", output_path); + return 0; + } + } + + if (ctx->fileRecordCount > 0) { + deps = (HostFileRecord*)calloc((size_t)ctx->fileRecordCount, sizeof(HostFileRecord)); + if (!deps) { + fprintf(stderr, "Out of memory while building dependency list\n"); + ret = 0; + goto done; + } + } + + for (SInt32 i = 0; i < ctx->fileRecordCount; i++) { + const HostFileRecord* rec = &ctx->fileRecords[i]; + int existing = -1; + + if (!rec->path[0]) continue; + if (rec->fileID == 0) continue; /* main source tracked separately */ + if (strcmp(rec->path, source_path) == 0) continue; + + for (int j = 0; j < dep_count; j++) { + if (strcmp(deps[j].path, rec->path) == 0) { + existing = j; + break; + } + } + + if (existing >= 0) { + if (deps[existing].isSystem && !rec->isSystem) { + deps[existing].isSystem = FALSE; + } + continue; + } + + deps[dep_count].isSystem = rec->isSystem; + copy_cstr(deps[dep_count].path, sizeof(deps[dep_count].path), rec->path); + dep_count++; + } + + for (int i = 0; i < dep_count; i++) { + if (ctx->depsOnlyUserFiles && deps[i].isSystem) continue; + visible_deps++; + } + + write_make_escaped(out, object_path); + fputs(": ", out); + write_make_escaped(out, source_path); + if (visible_deps > 0) fputs(" \\\n", out); + else fputc('\n', out); + + for (int i = 0; i < dep_count; i++) { + if (ctx->depsOnlyUserFiles && deps[i].isSystem) continue; + visible_deps--; + fputc('\t', out); + write_make_escaped(out, deps[i].path); + if (visible_deps > 0) fputs(" \\\n", out); + else fputc('\n', out); + } + +done: + free(deps); + if (out != stdout) fclose(out); + return ret; +} + +/* ============================================================ + * Command-line define/pragma prefix handling + * ============================================================ */ + +static void finalize_cmdline_prefix(CWPluginContext ctx) { + if (!ctx->defineText || ctx->defineTextLen <= 0) { + set_oldprefixname(&ctx->prefsFrontEnd, NULL); + return; + } + + /* Match compiler line-ending expectations for virtual prefix text. */ + ctx->defineTextLen = convert_line_endings_to_cr(ctx->defineText, ctx->defineTextLen); + set_oldprefixname(&ctx->prefsFrontEnd, CMDLINE_DEFINES_VFILE); +} + +/* ============================================================ + * Initialize preference defaults + * ============================================================ */ + +static void init_pref_defaults(CWPluginContext ctx) { + /* Shared front-end defaults. */ + memset(&ctx->prefsFrontEnd, 0, sizeof(PFrontEndC)); + ctx->prefsFrontEnd.version = 0x12; /* struct version 18 (PPC era) */ + ctx->prefsFrontEnd.enumsalwaysint = 0; /* -enum min (default) */ + ctx->prefsFrontEnd.wchar_type = 1; /* -wchar_t on (default=1 on PPC) */ + ctx->prefsFrontEnd.enableexceptions = 1; /* -Cpp_exceptions on */ + ctx->prefsFrontEnd.useRTTI = 1; /* -RTTI on */ + ctx->prefsFrontEnd.booltruefalse = 1; /* -bool on */ + ctx->prefsFrontEnd.inlinelevel = 8; /* default inline depth */ + + /* Shared warnings defaults */ + memset(&ctx->prefsWarnings, 0, sizeof(PWarningC)); + ctx->prefsWarnings.version = 7; + + /* Shared optimizer defaults */ + memset(&ctx->prefsOptimizer, 0, sizeof(PGlobalOptimizer)); + + /* ---- MIPS-specific panels ---- */ + memset(&ctx->prefsMIPSCodeGen, 0, sizeof(PMIPSCodeGen)); + ctx->prefsMIPSCodeGen.fpuType = 1; /* -fp single (default) */ + + memset(&ctx->prefsMIPSLinker, 0, sizeof(PMIPSLinker)); + memset(&ctx->prefsMIPSProject, 0, sizeof(PMIPSProject)); + + /* ---- PPC EABI-specific panels ---- */ + memset(&ctx->prefsPPCCodeGen, 0, sizeof(PPCEABICodeGen)); + ctx->prefsPPCCodeGen.version = 0x0C; /* version 12 (enables processorname) */ + ctx->prefsPPCCodeGen.pooldata = 1; /* -pooldata on (default) */ + ctx->prefsPPCCodeGen.floatingpoint = 1; /* -fp soft (default) */ + ctx->prefsPPCCodeGen.processor = 0x14; /* generic PPC */ + copy_cstr(ctx->prefsPPCCodeGen.processorname, + sizeof(ctx->prefsPPCCodeGen.processorname), "generic"); + + memset(&ctx->prefsPPCLinker, 0, sizeof(PPCEABILinker)); + memset(&ctx->prefsPPCProject, 0, sizeof(PPCEABIProject)); + ctx->prefsPPCProject.bigendian = 1; /* -big (default) */ + ctx->prefsPPCProject.datathreshold = 8; /* -sdata(threshold) default */ + ctx->prefsPPCProject.sdata2threshold = 8; /* -sdata2(threshold) default */ + ctx->prefsPPCProject.codeModel = 0; /* -model absolute (default) */ + + memset(&ctx->prefsPreprocessor, 0, sizeof(PPreprocessor)); + ctx->prefsPreprocessor.version = 4; +} + +static HMODULE load_compiler_dll(const char* requested_dll, + char* loaded_dll_name, + size_t loaded_dll_name_size, + int verbose) +{ + HMODULE hDll; + DWORD last_error = ERROR_MOD_NOT_FOUND; + + if (loaded_dll_name && loaded_dll_name_size > 0) { + loaded_dll_name[0] = '\0'; + } + + if (requested_dll && requested_dll[0]) { + if (verbose) fprintf(stderr, "Loading compiler DLL: %s\n", requested_dll); + hDll = LoadLibraryA(requested_dll); + if (!hDll) { + return NULL; + } + if (loaded_dll_name && loaded_dll_name_size > 0) { + copy_cstr(loaded_dll_name, loaded_dll_name_size, requested_dll); + } + return hDll; + } + + for (int i = 0; i < KNOWN_COMPILER_DLL_COUNT; i++) { + const char* candidate = kKnownCompilerDllNames[i]; + if (verbose) fprintf(stderr, "Trying compiler DLL: %s\n", candidate); + hDll = LoadLibraryA(candidate); + if (hDll) { + if (loaded_dll_name && loaded_dll_name_size > 0) { + copy_cstr(loaded_dll_name, loaded_dll_name_size, candidate); + } + return hDll; + } + last_error = GetLastError(); + } + + SetLastError(last_error); + return NULL; +} + +/* ============================================================ + * Argument parser + * ============================================================ */ + +/* Helper: consume next arg or error */ +#define NEXT_ARG(var) do { \ + if (i + 1 >= argc) { \ + fprintf(stderr, "Error: %s requires an argument\n", argv[i]); \ + return 1; \ + } \ + var = argv[++i]; \ +} while(0) + +static int parse_args(int argc, char* argv[], CWPluginContext ctx, + const char** requested_dll, + int* show_version_only, + const char* source_files[], int* num_sources, + const char** output_file, + PendingIncludePath include_paths[], int* num_includes, + int* system_path_mode) +{ + PFrontEndC* fe = &ctx->prefsFrontEnd; + PWarningC* warn = &ctx->prefsWarnings; + PGlobalOptimizer* opt = &ctx->prefsOptimizer; + PMIPSCodeGen* cg = &ctx->prefsMIPSCodeGen; + PPCEABICodeGen* ppc = &ctx->prefsPPCCodeGen; + const char* arg_val; + + for (int i = 1; i < argc; i++) { + const char* arg = argv[i]; + + /* Not a flag - source file */ + if (arg[0] != '-') { + if (*num_sources >= MAX_SOURCE_FILES) { + fprintf(stderr, "Error: too many input files (max %d)\n", MAX_SOURCE_FILES); + return 1; + } + source_files[*num_sources] = arg; + (*num_sources)++; + continue; + } + + /* --- General --- */ + if (strcmp(arg, "-help") == 0 || strcmp(arg, "--help") == 0 || + strcmp(arg, "-h") == 0) { + print_help(); + exit(0); + } + else if (strcmp(arg, "-version") == 0 || strcmp(arg, "--version") == 0) { + *show_version_only = 1; + } + else if (strcmp(arg, "-v") == 0 || strcmp(arg, "-verbose") == 0) { + ctx->verbose = 1; + } + else if (strcmp(arg, "-c") == 0 || strcmp(arg, "-nolink") == 0) { + /* implicit - we never link */ + } + else if (strcmp(arg, "-dll") == 0) { + NEXT_ARG(*requested_dll); + } + else if (strcmp(arg, "-msgstyle") == 0) { + NEXT_ARG(arg_val); + if (strcmp(arg_val, "std") == 0) ctx->msgStyle = 0; + else if (strcmp(arg_val, "gcc") == 0) ctx->msgStyle = 1; + else if (strcmp(arg_val, "parseable") == 0) ctx->msgStyle = 2; + else fprintf(stderr, "warning: unknown -msgstyle: %s\n", arg_val); + } + else if (strcmp(arg, "-maxerrors") == 0) { + NEXT_ARG(arg_val); + ctx->maxErrors = atoi(arg_val); + } + else if (strcmp(arg, "-maxwarnings") == 0) { + NEXT_ARG(arg_val); + ctx->maxWarnings = atoi(arg_val); + } + else if (strcmp(arg, "-nofail") == 0) { + ctx->noFail = 1; + } + + /* --- Output --- */ + else if (strcmp(arg, "-o") == 0) { + NEXT_ARG(*output_file); + } + + /* --- Preprocessing --- */ + else if (strcmp(arg, "-E") == 0) { + ctx->preprocess = 1; + } + else if (strcmp(arg, "-M") == 0 || strcmp(arg, "-make") == 0) { + ctx->dependencyMode = 1; + ctx->depsOnlyUserFiles = 0; + } + else if (strcmp(arg, "-MM") == 0) { + ctx->dependencyMode = 1; + ctx->depsOnlyUserFiles = 1; + } + else if (strcmp(arg, "-MD") == 0) { + ctx->dependencyMode = 2; + ctx->depsOnlyUserFiles = 0; + } + else if (strcmp(arg, "-MMD") == 0) { + ctx->dependencyMode = 2; + ctx->depsOnlyUserFiles = 1; + } + else if (strcmp(arg, "-P") == 0 || strcmp(arg, "-preprocess") == 0) { + ctx->preprocess = 1; + ctx->preprocessOnly = 1; + } + else if (strcmp(arg, "-EP") == 0) { + ctx->preprocess = 1; + } + else if (strcmp(arg, "-dis") == 0 || strcmp(arg, "-disassemble") == 0) { + ctx->disassemble = 1; + } + else if (strcmp(arg, "-S") == 0) { + ctx->disassemble = 1; + ctx->disassembleToFile = 1; + } + + /* -D name or -Dname or -D name=val or -Dname=val */ + else if (strcmp(arg, "-D") == 0) { + NEXT_ARG(arg_val); + add_define(ctx, arg_val); + } + else if (strncmp(arg, "-D", 2) == 0 && arg[2]) { + add_define(ctx, arg + 2); + } + + /* -U name or -Uname */ + else if (strcmp(arg, "-U") == 0) { + NEXT_ARG(arg_val); + add_undef(ctx, arg_val); + } + else if (strncmp(arg, "-U", 2) == 0 && arg[2]) { + add_undef(ctx, arg + 2); + } + + /* -pragma "text" */ + else if (strcmp(arg, "-pragma") == 0) { + NEXT_ARG(arg_val); + add_pragma(ctx, arg_val); + } + + /* Include paths */ + else if (strcmp(arg, "-I-") == 0 || strcmp(arg, "-i-") == 0) { + if (ctx->gccIncludes && !ctx->usedDashIMinus) { + move_pending_system_paths_to_user(include_paths, *num_includes); + } + ctx->usedDashIMinus = TRUE; + ctx->includeSearchMode = hostIncludeSearchExplicit; + *system_path_mode = 1; + } + else if (strcmp(arg, "-ir") == 0) { + NEXT_ARG(arg_val); + add_include_path(include_paths, num_includes, arg_val, + (Boolean)*system_path_mode, TRUE); + } + else if (strncmp(arg, "-ir", 3) == 0 && arg[3]) { + add_include_path(include_paths, num_includes, arg + 3, + (Boolean)*system_path_mode, TRUE); + } + else if (strcmp(arg, "-I") == 0) { + NEXT_ARG(arg_val); + add_include_path(include_paths, num_includes, arg_val, + (Boolean)*system_path_mode, FALSE); + } + else if (strcmp(arg, "-i") == 0) { + NEXT_ARG(arg_val); + add_include_path(include_paths, num_includes, arg_val, + (Boolean)*system_path_mode, FALSE); + } + else if (strncmp(arg, "-I", 2) == 0 && arg[2]) { + add_include_path(include_paths, num_includes, arg + 2, + (Boolean)*system_path_mode, FALSE); + } + + /* -include / -prefix */ + else if (strcmp(arg, "-include") == 0 || strcmp(arg, "-prefix") == 0) { + NEXT_ARG(arg_val); + add_prefix_include(ctx, arg_val); + } + + /* -nosyspath */ + else if (strcmp(arg, "-nosyspath") == 0) { + ctx->noSysPath = TRUE; + } + else if (strcmp(arg, "-stdinc") == 0 || strcmp(arg, "-defaults") == 0) { + ctx->useDefaultIncludes = TRUE; + } + else if (strcmp(arg, "-nostdinc") == 0 || strcmp(arg, "-nodefaults") == 0) { + ctx->useDefaultIncludes = FALSE; + } + else if (strcmp(arg, "-search") == 0) { + ctx->searchPaths = TRUE; + } + + /* --- C/C++ Language (PFrontEndC) --- */ + else if (strcmp(arg, "-lang") == 0 || strcmp(arg, "-dialect") == 0 || + strncmp(arg, "-lang=", 6) == 0 || strncmp(arg, "-dialect=", 9) == 0) { + if (arg[5] == '=') { + arg_val = arg + 6; + } else if (arg[8] == '=') { + arg_val = arg + 9; + } else { + NEXT_ARG(arg_val); + } + if (strcmp(arg_val, "c") == 0) { + fe->cplusplus = 0; + fe->ecplusplus = 0; + } else if (strcmp(arg_val, "c++") == 0) { + fe->cplusplus = 1; + fe->ecplusplus = 0; + } else if (strcmp(arg_val, "ec++") == 0) { + fe->cplusplus = 1; + fe->ecplusplus = 1; + } else { + fprintf(stderr, "warning: unknown language: %s\n", arg_val); + } + } + else if (strcmp(arg, "-char") == 0) { + NEXT_ARG(arg_val); + if (strcmp(arg_val, "signed") == 0) fe->unsignedchars = 0; + else if (strcmp(arg_val, "unsigned") == 0) fe->unsignedchars = 1; + else fprintf(stderr, "warning: unknown -char value: %s\n", arg_val); + } + else if (strcmp(arg, "-enum") == 0) { + NEXT_ARG(arg_val); + if (strcmp(arg_val, "min") == 0) fe->enumsalwaysint = 0; + else if (strcmp(arg_val, "int") == 0) fe->enumsalwaysint = 1; + else fprintf(stderr, "warning: unknown -enum value: %s\n", arg_val); + } + else if (strcmp(arg, "-inline") == 0) { + NEXT_ARG(arg_val); + if (strcmp(arg_val, "off") == 0 || strcmp(arg_val, "none") == 0) { + fe->dontinline = 1; + fe->autoinline = 0; + fe->alwaysinline = 0; + } else if (strcmp(arg_val, "on") == 0 || strcmp(arg_val, "smart") == 0) { + fe->dontinline = 0; + fe->autoinline = 0; + fe->alwaysinline = 0; + } else if (strcmp(arg_val, "auto") == 0) { + fe->dontinline = 0; + fe->autoinline = 1; + } else if (strcmp(arg_val, "noauto") == 0) { + fe->autoinline = 0; + } else if (strcmp(arg_val, "all") == 0) { + fe->dontinline = 0; + fe->autoinline = 1; + fe->alwaysinline = 1; + } else if (strcmp(arg_val, "deferred") == 0) { + fe->dontinline = 0; + fe->defer_codegen = 1; + } else if (strncmp(arg_val, "level=", 6) == 0) { + fe->inlinelevel = (SInt16)atoi(arg_val + 6); + fe->dontinline = 0; + } else { + fprintf(stderr, "warning: unknown -inline value: %s\n", arg_val); + } + } + else if (strcmp(arg, "-bool") == 0) { + NEXT_ARG(arg_val); + int v = parse_onoff(arg_val); + if (v >= 0) fe->booltruefalse = (Boolean)v; + } + else if (strcmp(arg, "-Cpp_exceptions") == 0) { + NEXT_ARG(arg_val); + int v = parse_onoff(arg_val); + if (v >= 0) fe->enableexceptions = (Boolean)v; + } + else if (strcmp(arg, "-RTTI") == 0) { + NEXT_ARG(arg_val); + int v = parse_onoff(arg_val); + if (v >= 0) fe->useRTTI = (Boolean)v; + } + else if (strcmp(arg, "-ansi") == 0) { + NEXT_ARG(arg_val); + if (strcmp(arg_val, "off") == 0) { + /* -stdkeywords on, -enum min, -strict off */ + fe->onlystdkeywords = 1; + fe->enumsalwaysint = 0; + fe->ansistrict = 0; + } else if (strcmp(arg_val, "on") == 0 || strcmp(arg_val, "relaxed") == 0) { + /* -stdkeywords off, -enum min, -strict on */ + fe->onlystdkeywords = 0; + fe->enumsalwaysint = 0; + fe->ansistrict = 1; + } else if (strcmp(arg_val, "strict") == 0) { + /* -stdkeywords off, -enum int, -strict on */ + fe->onlystdkeywords = 0; + fe->enumsalwaysint = 1; + fe->ansistrict = 1; + } else { + fprintf(stderr, "warning: unknown -ansi value: %s\n", arg_val); + } + } + else if (strcmp(arg, "-strict") == 0) { + NEXT_ARG(arg_val); + int v = parse_onoff(arg_val); + if (v >= 0) fe->ansistrict = (Boolean)v; + } + else if (strcmp(arg, "-trigraphs") == 0) { + NEXT_ARG(arg_val); + int v = parse_onoff(arg_val); + if (v >= 0) fe->trigraphs = (Boolean)v; + } + else if (strcmp(arg, "-stdkeywords") == 0) { + NEXT_ARG(arg_val); + int v = parse_onoff(arg_val); + if (v >= 0) fe->onlystdkeywords = (Boolean)v; + } + else if (strcmp(arg, "-wchar_t") == 0) { + NEXT_ARG(arg_val); + int v = parse_onoff(arg_val); + if (v >= 0) fe->wchar_type = (Boolean)v; + } + else if (strcmp(arg, "-r") == 0 || strcmp(arg, "-requireprotos") == 0) { + fe->checkprotos = 1; + } + else if (strcmp(arg, "-ARM") == 0) { + NEXT_ARG(arg_val); + int v = parse_onoff(arg_val); + if (v >= 0) fe->arm = (Boolean)v; + } + else if (strcmp(arg, "-str") == 0 || strcmp(arg, "-strings") == 0) { + NEXT_ARG(arg_val); + if (strchr(arg_val, ',')) { + char tmp[128]; + size_t len = strlen(arg_val); + if (len >= sizeof(tmp)) len = sizeof(tmp) - 1; + memcpy(tmp, arg_val, len); + tmp[len] = '\0'; + for (char* tok = strtok(tmp, ","); + tok; + tok = strtok(NULL, ",")) + { + if (!apply_ppc_string_option(fe, ppc, tok)) { + fprintf(stderr, "warning: unknown -str value: %s\n", tok); + } + } + } else if (!apply_ppc_string_option(fe, ppc, arg_val)) { + fprintf(stderr, "warning: unknown -str value: %s\n", arg_val); + } + } + else if (strcmp(arg, "-rostr") == 0 || strcmp(arg, "-readonlystrings") == 0) { + /* Deprecated alias for -str readonly. Does not imply [no]reuse. */ + ppc->readonlystrings = 1; + } + else if (strcmp(arg, "-multibyte") == 0 || strcmp(arg, "-multibyteaware") == 0) { + fe->multibyteaware = 1; + } + else if (strcmp(arg, "-once") == 0) { + ctx->forceIncludeOnce = 1; + add_pragma(ctx, "once on"); + } + else if (strcmp(arg, "-notonce") == 0) { + ctx->forceIncludeOnce = 0; + add_pragma(ctx, "once off"); + } + else if (strcmp(arg, "-relax_pointers") == 0) { + fe->mpwpointerstyle = 1; + } + + /* --- Warnings --- */ + else if (strcmp(arg, "-w") == 0) { + NEXT_ARG(arg_val); + if (strcmp(arg_val, "off") == 0) { + ctx->noWarnings = 1; + set_all_warnings(warn, 0); + } else if (strcmp(arg_val, "on") == 0) { + ctx->noWarnings = 0; + /* enable common warnings */ + warn->warn_illpragma = 1; + warn->warn_possunwant = 1; + } else if (strcmp(arg_val, "all") == 0) { + ctx->noWarnings = 0; + set_all_warnings(warn, 1); + fe->checkprotos = 1; + } else if (strcmp(arg_val, "error") == 0 || strcmp(arg_val, "err") == 0 || + strcmp(arg_val, "iserror") == 0 || strcmp(arg_val, "iserr") == 0) { + ctx->warningsAreErrors = 1; + warn->warningerrors = 1; + } else if (strcmp(arg_val, "noerror") == 0 || strcmp(arg_val, "noerr") == 0 || + strcmp(arg_val, "noiserror") == 0 || strcmp(arg_val, "noiserr") == 0) { + ctx->warningsAreErrors = 0; + warn->warningerrors = 0; + } + /* individual warnings (with mwccps2-compatible aliases) */ + else if (strcmp(arg_val, "pragmas") == 0 || strcmp(arg_val, "illpragmas") == 0) + warn->warn_illpragma = 1; + else if (strcmp(arg_val, "nopragmas") == 0 || strcmp(arg_val, "noillpragmas") == 0) + warn->warn_illpragma = 0; + else if (strcmp(arg_val, "empty") == 0 || strcmp(arg_val, "emptydecl") == 0) + warn->warn_emptydecl = 1; + else if (strcmp(arg_val, "noempty") == 0 || strcmp(arg_val, "noemptydecl") == 0) + warn->warn_emptydecl = 0; + else if (strcmp(arg_val, "possible") == 0 || strcmp(arg_val, "unwanted") == 0) + warn->warn_possunwant = 1; + else if (strcmp(arg_val, "nopossible") == 0 || strcmp(arg_val, "nounwanted") == 0) + warn->warn_possunwant = 0; + else if (strcmp(arg_val, "unusedarg") == 0) warn->warn_unusedarg = 1; + else if (strcmp(arg_val, "nounusedarg") == 0) warn->warn_unusedarg = 0; + else if (strcmp(arg_val, "unusedvar") == 0) warn->warn_unusedvar = 1; + else if (strcmp(arg_val, "nounusedvar") == 0) warn->warn_unusedvar = 0; + else if (strcmp(arg_val, "unused") == 0) { + warn->warn_unusedarg = 1; + warn->warn_unusedvar = 1; + } + else if (strcmp(arg_val, "nounused") == 0) { + warn->warn_unusedarg = 0; + warn->warn_unusedvar = 0; + } + else if (strcmp(arg_val, "extracomma") == 0 || strcmp(arg_val, "comma") == 0) + warn->warn_extracomma = 1; + else if (strcmp(arg_val, "noextracomma") == 0 || strcmp(arg_val, "nocomma") == 0) + warn->warn_extracomma = 0; + else if (strcmp(arg_val, "pedantic") == 0 || strcmp(arg_val, "extended") == 0) + warn->pedantic = 1; + else if (strcmp(arg_val, "nopedantic") == 0 || strcmp(arg_val, "noextended") == 0) + warn->pedantic = 0; + else if (strcmp(arg_val, "hidevirtual") == 0 || strcmp(arg_val, "hidden") == 0 || + strcmp(arg_val, "hiddenvirtual") == 0) + warn->warn_hidevirtual = 1; + else if (strcmp(arg_val, "nohidevirtual") == 0 || strcmp(arg_val, "nohidden") == 0 || + strcmp(arg_val, "nohiddenvirtual") == 0) + warn->warn_hidevirtual = 0; + else if (strcmp(arg_val, "implicit") == 0 || strcmp(arg_val, "implicitconv") == 0) + warn->warn_implicitconv = 1; + else if (strcmp(arg_val, "noimplicit") == 0 || strcmp(arg_val, "noimplicitconv") == 0) + warn->warn_implicitconv = 0; + else if (strcmp(arg_val, "notinlined") == 0) warn->warn_notinlined = 1; + else if (strcmp(arg_val, "nonotinlined") == 0) warn->warn_notinlined = 0; + else if (strcmp(arg_val, "largeargs") == 0 || strcmp(arg_val, "nolargeargs") == 0) + { /* PExtraWarningC - not in base PWarningC struct */ } + else if (strcmp(arg_val, "structclass") == 0) warn->warn_structclass = 1; + else if (strcmp(arg_val, "nostructclass") == 0) warn->warn_structclass = 0; + else if (strcmp(arg_val, "padding") == 0 || strcmp(arg_val, "nopadding") == 0) + { /* PExtraWarningC - not in base PWarningC struct */ } + else if (strcmp(arg_val, "notused") == 0 || strcmp(arg_val, "nonotused") == 0) + { /* PExtraWarningC - not in base PWarningC struct */ } + else if (strcmp(arg_val, "unusedexpr") == 0 || strcmp(arg_val, "nounusedexpr") == 0) + { /* PExtraWarningC - not in base PWarningC struct */ } + else if (strcmp(arg_val, "cmdline") == 0 || strcmp(arg_val, "nocmdline") == 0) + { /* command-line parser warnings - handled by driver */ } + else { + /* Try numeric warning level */ + int wl = atoi(arg_val); + if (wl > 0 && wl <= 3) { + ctx->noWarnings = 0; + warn->warn_illpragma = 1; + warn->warn_possunwant = 1; + if (wl >= 2) { + warn->warn_unusedvar = 1; + warn->warn_unusedarg = 1; + warn->warn_extracomma = 1; + } + if (wl >= 3) { + set_all_warnings(warn, 1); + } + } else { + fprintf(stderr, "warning: unknown -w value: %s\n", arg_val); + } + } + } + /* GCC-compat warning aliases */ + else if (strcmp(arg, "-Wall") == 0) { + ctx->noWarnings = 0; + set_all_warnings(warn, 1); + } + else if (strcmp(arg, "-Werror") == 0) { + ctx->warningsAreErrors = 1; + warn->warningerrors = 1; + } + else if (strcmp(arg, "-Wmost") == 0) { + ctx->noWarnings = 0; + warn->warn_illpragma = 1; + warn->warn_possunwant = 1; + warn->warn_unusedvar = 1; + warn->warn_unusedarg = 1; + } + else if (strcmp(arg, "-Wunused") == 0) { + warn->warn_unusedvar = 1; + warn->warn_unusedarg = 1; + } + else if (strcmp(arg, "-Wno-unused") == 0) { + warn->warn_unusedvar = 0; + warn->warn_unusedarg = 0; + } + + /* --- Optimizer --- */ + else if (arg[0] == '-' && arg[1] == 'O') { + if (!parse_dash_o_option(arg, opt, cg, ppc)) { + fprintf(stderr, "warning: unknown -O option: %s\n", arg); + } + } + else if (strcmp(arg, "-opt") == 0) { + NEXT_ARG(arg_val); + if (strchr(arg_val, ',')) { + char tmp[128]; + size_t len = strlen(arg_val); + if (len >= sizeof(tmp)) len = sizeof(tmp) - 1; + memcpy(tmp, arg_val, len); + tmp[len] = '\0'; + for (char* tok = strtok(tmp, ","); + tok; + tok = strtok(NULL, ",")) + { + if (!apply_opt_keyword(tok, opt, cg, ppc)) { + fprintf(stderr, "warning: unknown -opt value: %s\n", tok); + } + } + } else if (!apply_opt_keyword(arg_val, opt, cg, ppc)) { + fprintf(stderr, "warning: unknown -opt value: %s\n", arg_val); + } + } + + /* --- MIPS Backend --- */ + else if (strcmp(arg, "-fp") == 0) { + NEXT_ARG(arg_val); + if (_stricmp(arg_val, "off") == 0 || _stricmp(arg_val, "none") == 0) { + cg->fpuType = 0; + ppc->floatingpoint = 0; + } else if (_stricmp(arg_val, "single") == 0) { + cg->fpuType = 1; + } else if (_stricmp(arg_val, "soft") == 0 || _stricmp(arg_val, "software") == 0) { + ppc->floatingpoint = 1; + } else if (_stricmp(arg_val, "hard") == 0 || _stricmp(arg_val, "hardware") == 0) { + ppc->floatingpoint = 2; + } else if (_stricmp(arg_val, "fmadd") == 0) { + ppc->floatingpoint = 2; + ppc->fpcontract = 1; + } else { + fprintf(stderr, "warning: unknown -fp value: %s\n", arg_val); + } + } + else if (strcmp(arg, "-fp_contract") == 0 || strcmp(arg, "-maf") == 0) { + NEXT_ARG(arg_val); + { + int v = parse_onoff(arg_val); + if (v >= 0) { + ppc->fpcontract = (UInt8)v; + } else { + fprintf(stderr, "warning: unknown -fp_contract value: %s\n", arg_val); + } + } + } + else if (strcmp(arg, "-common") == 0) { + NEXT_ARG(arg_val); + { + int v = parse_onoff(arg_val); + if (v >= 0) { + ppc->commonsect = (UInt8)v; + } else { + fprintf(stderr, "warning: unknown -common value: %s\n", arg_val); + } + } + } + else if (strcmp(arg, "-big") == 0) { + ctx->prefsPPCProject.bigendian = 1; + } + else if (strcmp(arg, "-little") == 0) { + ctx->prefsPPCProject.bigendian = 0; + } + else if (strcmp(arg, "-sdatathreshold") == 0 || + strcmp(arg, "-sdata") == 0 || + strcmp(arg, "-sdatathreshold") == 0) { + NEXT_ARG(arg_val); + ctx->prefsPPCProject.datathreshold = (SInt16)atoi(arg_val); + } + else if (strcmp(arg, "-sdata2") == 0 || + strcmp(arg, "-sdata2threshold") == 0) { + NEXT_ARG(arg_val); + ctx->prefsPPCProject.sdata2threshold = (SInt16)atoi(arg_val); + } + else if (strcmp(arg, "-model") == 0) { + NEXT_ARG(arg_val); + if (_stricmp(arg_val, "absolute") == 0) { + ctx->prefsPPCProject.codeModel = 0; + } else if (_stricmp(arg_val, "other") == 0) { + ctx->prefsPPCProject.codeModel = 1; + } else { + fprintf(stderr, "warning: unknown -model value: %s\n", arg_val); + } + } + else if (strcmp(arg, "-use_lmw_stmw") == 0) { + NEXT_ARG(arg_val); + { + int v = parse_onoff(arg_val); + if (v >= 0) { + ppc->use_lmw_stmw = (UInt8)v; + } else { + fprintf(stderr, "warning: unknown -use_lmw_stmw value: %s\n", arg_val); + } + } + } + else if (strcmp(arg, "-align") == 0) { + NEXT_ARG(arg_val); + if (strchr(arg_val, ',')) { + char tmp[128]; + size_t len = strlen(arg_val); + if (len >= sizeof(tmp)) len = sizeof(tmp) - 1; + memcpy(tmp, arg_val, len); + tmp[len] = '\0'; + for (char* tok = strtok(tmp, ","); + tok; + tok = strtok(NULL, ",")) + { + if (!set_ppc_alignment(ppc, tok)) { + fprintf(stderr, "warning: unknown -align value: %s\n", tok); + } + } + } else if (!set_ppc_alignment(ppc, arg_val)) { + fprintf(stderr, "warning: unknown -align value: %s\n", arg_val); + } + } + else if (strcmp(arg, "-proc") == 0 || strcmp(arg, "-processor") == 0 || + strncmp(arg, "-proc=", 6) == 0 || strncmp(arg, "-processor=", 11) == 0) { + SInt16 proc_val = 0; + if (arg[5] == '=') { + arg_val = arg + 6; + } else if (arg[10] == '=') { + arg_val = arg + 11; + } else { + NEXT_ARG(arg_val); + } + if (parse_ppc_processor(arg_val, &proc_val)) { + ppc->processor = proc_val; + copy_cstr(ppc->processorname, sizeof(ppc->processorname), arg_val); + } else { + fprintf(stderr, "warning: unknown processor: %s\n", arg_val); + } + } + else if (strcmp(arg, "-profile") == 0) { + /* profiler - stored but cc_mips.dll may not use it for PS1 */ + ppc->profiler = 1; + } + else if (strcmp(arg, "-farcall") == 0) { + NEXT_ARG(arg_val); + /* far call - stored but mainly relevant for PS2/larger address spaces */ + } + + /* --- Include search semantics --- */ + else if (strcmp(arg, "-cwd") == 0) { + NEXT_ARG(arg_val); + if (strcmp(arg_val, "proj") == 0) { + ctx->includeSearchMode = hostIncludeSearchProj; + } else if (strcmp(arg_val, "source") == 0) { + ctx->includeSearchMode = hostIncludeSearchSource; + } else if (strcmp(arg_val, "explicit") == 0) { + ctx->includeSearchMode = hostIncludeSearchExplicit; + } else if (strcmp(arg_val, "include") == 0) { + ctx->includeSearchMode = hostIncludeSearchInclude; + } else { + fprintf(stderr, "warning: unknown -cwd mode: %s\n", arg_val); + } + } + else if (strcmp(arg, "-gccincludes") == 0 || strcmp(arg, "-gccinc") == 0) { + ctx->gccIncludes = TRUE; + ctx->includeSearchMode = hostIncludeSearchInclude; + if (!ctx->usedDashIMinus) { + move_pending_user_paths_to_system(include_paths, *num_includes); + } + *system_path_mode = 1; + } + + /* --- Debug --- */ + else if (strcmp(arg, "-g") == 0) { + ctx->debugInfo = 1; + } + else if (strcmp(arg, "-sym") == 0) { + NEXT_ARG(arg_val); + if (strcmp(arg_val, "off") == 0) ctx->debugInfo = 0; + else if (strcmp(arg_val, "on") == 0) ctx->debugInfo = 1; + else if (strcmp(arg_val, "full") == 0) ctx->debugInfo = 1; + } + + /* --- Unknown flag --- */ + else { + if (ctx->verbose) { + fprintf(stderr, "Ignoring unknown flag: %s\n", arg); + } + } + } + + return 0; +} + +/* ============================================================ + * main + * ============================================================ */ + +int main(int argc, char* argv[]) { + const char* requested_dll = NULL; + const char* output_file = NULL; + const char* source_files[MAX_SOURCE_FILES]; + int num_sources = 0; + int had_compile_failure = 0; + int show_version_only = 0; + int plugin_api_version = 12; + CompilerVersionInfo active_version_info; + int has_active_version_info = 0; + char loaded_dll_name[MAX_PATH]; + char loaded_dll_path[MAX_PATH]; + + /* Temporary storage for include paths */ + PendingIncludePath include_paths[MAX_INCLUDE_PATHS]; + int num_includes = 0; + int system_path_mode = 0; /* 0=user paths, 1=system paths after -I- */ + + /* Initialize plugin context */ + CWPluginPrivateContext ctx; + memset(&ctx, 0, sizeof(ctx)); + init_pref_defaults(&ctx); + ctx.includeSearchMode = hostIncludeSearchProj; + ctx.useDefaultIncludes = TRUE; + ctx.nextFileID = 1; + + /* Parse arguments */ + if (parse_args(argc, argv, &ctx, &requested_dll, &show_version_only, + source_files, &num_sources, &output_file, + include_paths, &num_includes, &system_path_mode) != 0) { + return 1; + } + + if (show_version_only) { + CompilerVersionInfo version_info; + int has_version_info = 0; + HMODULE hVersionDll = load_compiler_dll(requested_dll, loaded_dll_name, sizeof(loaded_dll_name), ctx.verbose); + + if (!hVersionDll) { + if (requested_dll && requested_dll[0]) { + fprintf(stderr, "Failed to load compiler DLL %s (error %lu)\n", + requested_dll, GetLastError()); + return 1; + } + print_wrapper_version(); + return 0; + } + + loaded_dll_path[0] = '\0'; + if (GetModuleFileNameA(hVersionDll, loaded_dll_path, sizeof(loaded_dll_path)) == 0) { + copy_cstr(loaded_dll_path, sizeof(loaded_dll_path), loaded_dll_name); + } + has_version_info = get_compiler_version_info(loaded_dll_path, &version_info); + print_dynamic_version(loaded_dll_path, has_version_info ? &version_info : NULL); + FreeLibrary(hVersionDll); + return 0; + } + + if (num_sources <= 0) { + CompilerVersionInfo version_info; + int has_version_info = 0; + const char* prog_name = basename_from_path(argv[0]); + HMODULE hVersionDll = load_compiler_dll(requested_dll, loaded_dll_name, sizeof(loaded_dll_name), ctx.verbose); + + if (hVersionDll) { + loaded_dll_path[0] = '\0'; + if (GetModuleFileNameA(hVersionDll, loaded_dll_path, sizeof(loaded_dll_path)) == 0) { + copy_cstr(loaded_dll_path, sizeof(loaded_dll_path), loaded_dll_name); + } + has_version_info = get_compiler_version_info(loaded_dll_path, &version_info); + print_dynamic_version(loaded_dll_path, has_version_info ? &version_info : NULL); + FreeLibrary(hVersionDll); + } else { + print_wrapper_version(); + } + + fprintf(stderr, "\nUsage: %s [options] [-o output] input1.c [input2.c ...]\n", prog_name); + fprintf(stderr, "\nPlease enter '%s -help' for information about options.\n", prog_name); + return 1; + } + + if (ctx.dependencyMode == 1 && !ctx.preprocess && !ctx.disassemble && output_file) { + copy_cstr(ctx.dependencyOutputFile, sizeof(ctx.dependencyOutputFile), output_file); + output_file = NULL; + } + + if (output_file && num_sources > 1 && + (ctx.disassembleToFile || (!ctx.preprocess && ctx.dependencyMode != 1)) && + !is_directory_path(output_file)) + { + fprintf(stderr, + "Error: when multiple input files are specified, -o must name an existing directory: %s\n", + output_file); + return 1; + } + + if (ctx.dependencyMode == 1 && ctx.dependencyOutputFile[0]) { + FILE* depf = fopen(ctx.dependencyOutputFile, "wb"); + if (!depf) { + fprintf(stderr, "Cannot open dependency output file: %s\n", ctx.dependencyOutputFile); + return 1; + } + fclose(depf); + } + + if (ctx.useDefaultIncludes) { + add_default_include_paths(&ctx, include_paths, &num_includes); + } + + /* Set up include paths */ + if (num_includes > 0) { + int user_count = 0; + int system_count = 0; + + for (int i = 0; i < num_includes; i++) { + if (include_paths[i].system) system_count++; + else user_count++; + } + + if (user_count > 0) { + ctx.userPaths = (HostAccessPath*)calloc(user_count, sizeof(HostAccessPath)); + if (!ctx.userPaths) { + fprintf(stderr, "Out of memory allocating user include paths\n"); + return 1; + } + ctx.userPathCount = user_count; + } + if (system_count > 0) { + ctx.systemPaths = (HostAccessPath*)calloc(system_count, sizeof(HostAccessPath)); + if (!ctx.systemPaths) { + fprintf(stderr, "Out of memory allocating system include paths\n"); + return 1; + } + ctx.systemPathCount = system_count; + } + + { + int u = 0; + int s = 0; + for (int i = 0; i < num_includes; i++) { + HostAccessPath* dst; + if (include_paths[i].system) { + dst = &ctx.systemPaths[s++]; + } else { + dst = &ctx.userPaths[u++]; + } + copy_cstr(dst->path, MAX_PATH, include_paths[i].path); + dst->recursive = include_paths[i].recursive; + } + } + } + + /* Expose command-line defines/pragmas as MWCC-style virtual prefix file. */ + finalize_cmdline_prefix(&ctx); + + /* Load compiler DLL (explicit via -dll/-compiler-dll, else known-name probe). */ + g_registered_pluginlib_module = NULL; + g_registered_pluginlib_version = 0; + HMODULE hDll = load_compiler_dll(requested_dll, loaded_dll_name, sizeof(loaded_dll_name), ctx.verbose); + if (!hDll) { + if (requested_dll && requested_dll[0]) { + fprintf(stderr, "Failed to load compiler DLL %s (error %lu)\n", + requested_dll, GetLastError()); + } else { + fprintf(stderr, "Failed to load compiler DLL (error %lu)\n", GetLastError()); + fprintf(stderr, "Tried:"); + for (int i = 0; i < KNOWN_COMPILER_DLL_COUNT; i++) { + fprintf(stderr, "%s%s", (i == 0) ? " " : ", ", kKnownCompilerDllNames[i]); + } + fprintf(stderr, "\n"); + } + return 1; + } + if (ctx.verbose) { + fprintf(stderr, "Loaded compiler DLL: %s\n", + loaded_dll_name[0] ? loaded_dll_name : ""); + } + + loaded_dll_path[0] = '\0'; + if (GetModuleFileNameA(hDll, loaded_dll_path, sizeof(loaded_dll_path)) != 0) { + copy_cstr(loaded_dll_name, sizeof(loaded_dll_name), loaded_dll_path); + } + + has_active_version_info = get_compiler_version_info(loaded_dll_path, &active_version_info); + ctx.prefsMIPSCodeGenR4Compat = + has_active_version_info ? should_enable_mips_r4_compat(&active_version_info) : FALSE; + if (ctx.verbose && ctx.prefsMIPSCodeGenR4Compat) { + fprintf(stderr, "Enabled prefsMIPSCodeGenR4Compat based on compiler version metadata.\n"); + } + + if (g_registered_pluginlib_version == 2) { + plugin_api_version = 8; + } + if (ctx.verbose) { + if (g_registered_pluginlib_version != 0) { + fprintf(stderr, "Registered compiler import: PluginLib%d.dll\n", g_registered_pluginlib_version); + } else { + fprintf(stderr, "Registered compiler import: \n"); + } + fprintf(stderr, "Using Plugin API version: %d\n", plugin_api_version); + } + + if (!g_registered_pluginlib_module || g_registered_pluginlib_version == 0) { + fprintf(stderr, "Failed to resolve active PluginLib via registration\n"); + FreeLibrary(hDll); + return 1; + } + + /* Extract string table from compiler DLL's Mac resource fork. */ + if (g_registered_pluginlib_module) { + MWCC_InitStringTableFunc initFunc = + (MWCC_InitStringTableFunc)GetProcAddress(g_registered_pluginlib_module, "MWCC_InitStringTable"); + if (initFunc) { + initFunc(hDll); + } else if (ctx.verbose) { + fprintf(stderr, "Warning: MWCC_InitStringTable not found in PluginLib\n"); + } + } else if (ctx.verbose) { + fprintf(stderr, "Warning: PluginLib not loaded\n"); + } + + PluginMainFunc plugin_main = (PluginMainFunc)GetProcAddress(hDll, "main"); + if (!plugin_main) { + fprintf(stderr, "Failed to find 'main' export in %s\n", + loaded_dll_name[0] ? loaded_dll_name : ""); + FreeLibrary(hDll); + return 1; + } + + if (ctx.verbose) fprintf(stderr, "Found plugin main at %p\n", (void*)plugin_main); + + ctx.apiVersion = plugin_api_version; + ctx.numFiles = num_sources; + + /* Source and output fields are assigned per-file in the compile loop. */ + // ctx.precompile = FALSE; + // ctx.autoprecompile = FALSE; + if (ctx.dependencyMode == 1 && !ctx.preprocess && !ctx.disassemble) { + ctx.preprocess = 2; + } else { + ctx.preprocess = ctx.preprocess ? TRUE : FALSE; + } + + short result; + + /* === reqInitialize === */ + if (ctx.verbose) fprintf(stderr, "\n=== reqInitialize ===\n"); + ctx.request = reqInitialize; + result = plugin_main(&ctx); + if (ctx.verbose) fprintf(stderr, "reqInitialize returned: %d\n", result); + if (result != 0) { + fprintf(stderr, "Plugin initialization failed (result=%d)\n", result); + goto cleanup; + } + + { + int compile_failed = 0; + + for (int src_idx = 0; src_idx < num_sources; src_idx++) { + const char* source_file = source_files[src_idx]; + const char* active_source = source_file; + const char* active_ext = ctx.disassembleToFile ? ".s" : ".o"; + char resolved_source_file[MAX_PATH]; + char per_file_output[MAX_PATH]; + char per_file_object[MAX_PATH]; + int file_failed = 0; + SInt32 errors_before_compile; + + /* Reset per-file plugin state (MWCC-style compile loop over source list). */ + if (ctx.sourceText) { + free(ctx.sourceText); + ctx.sourceText = NULL; + } + if (ctx.objectData) { + free(ctx.objectData); + ctx.objectData = NULL; + } + ctx.sourceTextSize = 0; + ctx.objectDataSize = 0; + ctx.objectStored = 0; + ctx.preprocessedTextSize = 0; + ctx.nextFileID = 1; + ctx.fileRecordCount = 0; + ctx.lastIncludeDir[0] = '\0'; + ctx.includeRecordCount = 0; + + resolved_source_file[0] = '\0'; + if (ctx.searchPaths && !file_exists(source_file)) { + if (!find_file_in_access_paths(ctx.userPaths, ctx.userPathCount, source_file, + resolved_source_file, sizeof(resolved_source_file))) + { + find_file_in_access_paths(ctx.systemPaths, ctx.systemPathCount, source_file, + resolved_source_file, sizeof(resolved_source_file)); + } + if (resolved_source_file[0]) { + active_source = resolved_source_file; + } + } + + ctx.whichFile = src_idx; + copy_cstr(ctx.sourceFile, MAX_PATH, active_source); + + ctx.sourceText = read_file(active_source, &ctx.sourceTextSize); + if (!ctx.sourceText) { + fprintf(stderr, "Cannot read source file: %s\n", active_source); + ctx.numErrors++; + file_failed = 1; + } else { + ctx.sourceTextSize = convert_line_endings_to_cr(ctx.sourceText, ctx.sourceTextSize); + } + + if (!file_failed) { + if (output_file) { + int output_is_dir = is_directory_path(output_file); + if (output_is_dir || + (num_sources > 1 && + (ctx.disassembleToFile || (!ctx.preprocess && ctx.dependencyMode != 1)))) + { + const char* leaf = get_path_leaf(active_source); + char leaf_out[MAX_PATH]; + char leaf_obj[MAX_PATH]; + replace_extension(leaf, ".o", leaf_obj, sizeof(leaf_obj)); + replace_extension(leaf, active_ext, leaf_out, sizeof(leaf_out)); + if (!join_path(per_file_object, sizeof(per_file_object), output_file, leaf_obj)) { + fprintf(stderr, "Output path too long: %s/%s\n", output_file, leaf_obj); + ctx.numErrors++; + file_failed = 1; + } else if (!(ctx.dependencyMode == 1 && !ctx.preprocess && !ctx.disassemble) && + !join_path(per_file_output, sizeof(per_file_output), output_file, leaf_out)) + { + fprintf(stderr, "Output path too long: %s/%s\n", output_file, leaf_out); + ctx.numErrors++; + file_failed = 1; + } + } else { + copy_cstr(per_file_object, MAX_PATH, output_file); + copy_cstr(per_file_output, MAX_PATH, output_file); + } + } else { + replace_extension(active_source, ".o", per_file_object, sizeof(per_file_object)); + if (ctx.disassembleToFile) { + replace_extension(active_source, ".s", per_file_output, sizeof(per_file_output)); + } else if (!ctx.preprocess) { + replace_extension(active_source, ".o", per_file_output, sizeof(per_file_output)); + } else { + per_file_output[0] = '\0'; + } + } + + if (!file_failed && ctx.dependencyMode == 1 && !ctx.preprocess && !ctx.disassemble) { + per_file_output[0] = '\0'; + } + } + + if (file_failed) { + compile_failed = 1; + if (!ctx.noFail) break; + continue; + } + + copy_cstr(ctx.outputFile, MAX_PATH, per_file_output); + + errors_before_compile = ctx.numErrors; + + if (ctx.verbose) { + fprintf(stderr, "\n=== reqCompile (%d/%d): %s ===\n", + src_idx + 1, num_sources, active_source); + } + ctx.request = reqCompile; + result = plugin_main(&ctx); + if (ctx.verbose) fprintf(stderr, "reqCompile returned: %d\n", result); + + if (ctx.disassemble) { + if (result == 0) { + if (ctx.disassembleToFile) { + const char* disasm_path = ctx.outputFile[0] ? ctx.outputFile : "output.s"; + if (!freopen(disasm_path, "wb", stdout)) { + fprintf(stderr, "Cannot open disassembly output file: %s\n", disasm_path); + ctx.numErrors++; + } + } + + if (ctx.numErrors == errors_before_compile) { + if (ctx.verbose) fprintf(stderr, "=== reqCompDisassemble ===\n"); + ctx.request = reqCompDisassemble; + result = plugin_main(&ctx); + if (ctx.verbose) fprintf(stderr, "reqCompDisassemble returned: %d\n", result); + if (result != 0 && result != cwErrRequestFailed) { + fprintf(stderr, "Disassembly failed (result=%d)\n", result); + } + if (result == 0 && ctx.preprocessedTextSize <= 0) { + fprintf(stderr, "Disassembly produced no output (compiler plugin does not support reqCompDisassemble).\n"); + ctx.numErrors++; + } + } + } else if (result != cwErrRequestFailed) { + fprintf(stderr, "Compilation failed (result=%d)\n", result); + } + } else if (result != 0 && result != cwErrRequestFailed) { + fprintf(stderr, "Compilation failed (result=%d)\n", result); + } + + if (ctx.objectStored && !ctx.preprocess && !ctx.disassemble && ctx.dependencyMode != 1) { + write_object_file(&ctx); + } + + if (ctx.dependencyMode != 0 && !file_failed) { + char per_file_dep[MAX_PATH]; + const char* dep_output_path = NULL; + int dep_append = 0; + + if (ctx.dependencyMode == 1) { + dep_output_path = ctx.dependencyOutputFile[0] ? ctx.dependencyOutputFile : NULL; + dep_append = ctx.dependencyOutputFile[0] ? 1 : 0; + } else { + replace_extension(per_file_object, ".d", per_file_dep, sizeof(per_file_dep)); + dep_output_path = per_file_dep; + } + + if (!emit_dependency_rule(&ctx, active_source, per_file_object, dep_output_path, dep_append)) { + ctx.numErrors++; + } + } + + if (ctx.numErrors > errors_before_compile || (result != 0 && result != cwErrRequestFailed)) { + compile_failed = 1; + if (!ctx.noFail) break; + } + } + + if (compile_failed) had_compile_failure = 1; + } + + /* === reqTerminate === */ + if (ctx.verbose) fprintf(stderr, "\n=== reqTerminate ===\n"); + ctx.request = reqTerminate; + result = plugin_main(&ctx); + if (ctx.verbose) fprintf(stderr, "reqTerminate returned: %d\n", result); + + /* Summary */ + if (ctx.numErrors > 0) { + fprintf(stderr, "%d error(s), %d warning(s)\n", ctx.numErrors, ctx.numWarnings); + } else if (ctx.numWarnings > 0) { + fprintf(stderr, "%d warning(s)\n", ctx.numWarnings); + } + +cleanup: + FreeLibrary(hDll); + free(ctx.sourceText); + if (ctx.objectData) free(ctx.objectData); + if (ctx.userPaths) free(ctx.userPaths); + if (ctx.systemPaths) free(ctx.systemPaths); + if (ctx.fileRecords) free(ctx.fileRecords); + if (ctx.includeRecords) free(ctx.includeRecords); + if (ctx.defineText) free(ctx.defineText); + + return (ctx.numErrors > 0 || had_compile_failure) ? 1 : 0; +} diff --git a/pluginlib.c b/pluginlib.c new file mode 100644 index 0000000..2b6817a --- /dev/null +++ b/pluginlib.c @@ -0,0 +1,1882 @@ +/* + * 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; +} diff --git a/pluginlib2.def b/pluginlib2.def new file mode 100644 index 0000000..ca08870 --- /dev/null +++ b/pluginlib2.def @@ -0,0 +1,40 @@ +LIBRARY PluginLib2 +EXPORTS + ; Custom exports for mwccwrap + MWCC_InitStringTable + + ; CW Plugin Core (8000-8034) + CWGetPluginRequest @8000 + CWDonePluginRequest @8001 + CWGetAPIVersion @8002 + CWGetProjectFile @8005 + CWGetProjectFileCount @8007 + CWFindAndLoadFile @8009 + CWGetFileText @8010 + CWReleaseFileText @8011 + CWReportMessage @8017 + CWSetModDate @8024 + CWCreateNewTextDocument @8026 + CWAllocMemHandle @8029 + CWFreeMemHandle @8030 + CWLockMemHandle @8033 + CWUnlockMemHandle @8034 + + ; CW Compiler-Specific (9000-9033) + CWSecretAttachHandle @9000 + CWIsPrecompiling @9003 + CWIsAutoPrecompiling @9004 + CWIsPreprocessing @9005 + CWIsGeneratingDebugInfo @9006 + CWIsCachingPrecompiledHeaders @9007 + CWGetBrowseOptions @9008 + CWGetMainFileNumber @9012 + CWGetMainFileID @9013 + CWGetMainFileSpec @9014 + CWGetMainFileText @9015 + CWCachePrecompiledHeader @9016 + CWLoadObjectData @9017 + CWStoreObjectData @9019 + CWDisplayLines @9020 + CWGetPrecompiledHeaderSpec @9023 + CWSecretGetNamedPreferences @9033 diff --git a/pluginlib3.def b/pluginlib3.def new file mode 100644 index 0000000..d67a716 --- /dev/null +++ b/pluginlib3.def @@ -0,0 +1,93 @@ +LIBRARY PluginLib3 +EXPORTS + ; Custom exports for mwccwrap + MWCC_InitStringTable + + ; COS Handle Management (2000-2007) + COS_NewHandle @2000 + COS_NewOSHandle @2001 + COS_FreeHandle @2002 + COS_ResizeHandle @2003 + COS_LockHandle @2005 + COS_LockHandleHi @2006 + COS_UnlockHandle @2007 + + ; COS File I/O (2101-2113) + COS_FileNew @2101 + COS_FileOpen @2102 + COS_FileGetType @2103 + COS_FileGetSize @2104 + COS_FileRead @2105 + COS_FileWrite @2106 + COS_FileGetPos @2107 + COS_FileSetPos @2108 + COS_FileClose @2109 + COS_FileSetFSSpec @2110 + COS_FileGetFSSpecInfo @2112 + COS_FileGetPathName @2113 + + ; COS Utility (2201-2205) + COS_GetTicks @2201 + COS_GetString @2203 + COS_IsMultiByte @2205 + + ; CW Plugin Core (8000-8034) + CWGetPluginRequest @8000 + CWDonePluginRequest @8001 + CWGetAPIVersion @8002 + CWGetProjectFile @8005 + CWGetOutputFileDirectory @8006 + CWGetProjectFileCount @8007 + CWGetFileInfo @8008 + CWFindAndLoadFile @8009 + CWGetFileText @8010 + CWReleaseFileText @8011 + CWGetOverlay1GroupsCount @8013 + CWGetOverlay1GroupInfo @8014 + CWGetOverlay1Info @8015 + CWGetOverlay1FileInfo @8016 + CWReportMessage @8017 + CWAlert @8018 + CWShowStatus @8019 + CWUserBreak @8020 + CWGetNamedPreferences @8021 + CWSetModDate @8024 + CWCreateNewTextDocument @8026 + CWAllocateMemory @8027 + CWFreeMemory @8028 + CWAllocMemHandle @8029 + CWFreeMemHandle @8030 + CWGetMemHandleSize @8031 + CWResizeMemHandle @8032 + CWLockMemHandle @8033 + CWUnlockMemHandle @8034 + + ; CW Licensing (8037-8038, R5+) + CWCheckoutLicense @8037 + CWCheckinLicense @8038 + + ; CW Compiler-Specific (9000-9034) + CWSecretAttachHandle @9000 + ; CWSecretDetachHandle @9001 + CWSecretPeekHandle @9002 + CWIsPrecompiling @9003 + CWIsAutoPrecompiling @9004 + CWIsPreprocessing @9005 + CWIsGeneratingDebugInfo @9006 + CWIsCachingPrecompiledHeaders @9007 + CWGetBrowseOptions @9008 + CWGetBuildSequenceNumber @9009 + CWGetTargetInfo @9010 + CWSetTargetInfo @9011 + CWGetMainFileNumber @9012 + CWGetMainFileID @9013 + CWGetMainFileSpec @9014 + CWGetMainFileText @9015 + CWCachePrecompiledHeader @9016 + CWLoadObjectData @9017 + CWFreeObjectData @9018 + CWStoreObjectData @9019 + CWDisplayLines @9020 + CWGetPrecompiledHeaderSpec @9023 + CWSecretGetNamedPreferences @9033 + CWGetTargetName @9034 diff --git a/pluginlib5.def b/pluginlib5.def new file mode 100644 index 0000000..e5f045f --- /dev/null +++ b/pluginlib5.def @@ -0,0 +1,54 @@ +LIBRARY PluginLib5 +EXPORTS + CWAllocMemHandle @55 + CWAllocateMemory @56 + CWCachePrecompiledHeader @59 + CWCheckinLicense @60 + CWCheckoutLicense @61 + CWCreateNewTextDocument @64 + CWDisplayLines @65 + CWDonePluginRequest @67 + CWFindAndLoadFile @70 + CWFindLogicalDirectory @71 + CWFreeMemHandle @72 + CWFreeMemory @73 + CWFreeObjectData @74 + CWGetAPIVersion @75 + CWGetBrowseOptions @82 + CWGetCompilerQuery @92 + CWGetFileInfo @98 + CWGetFileText @99 + CWGetMainFileID @109 + CWGetMainFileNumber @110 + CWGetMainFileSpec @111 + CWGetMainFileText @112 + CWGetMemHandleSize @113 + CWGetNamedPreferences @116 + CWGetPluginData @124 + CWGetPluginRequest @125 + CWGetPrecompiledHeaderSpec @126 + CWGetProjectFile @127 + CWGetProjectFileCount @128 + CWGetSuggestedObjectFileSpec @136 + CWGetTargetInfo @138 + CWGetTargetName @139 + CWIsAutoPrecompiling @146 + CWIsCachingPrecompiledHeaders @147 + CWIsCheckingSyntax @148 + CWIsGeneratingDebugInfo @150 + CWIsPrecompiling @151 + CWIsPreprocessing @152 + CWIsUsingSourceRelativeIncludes @154 + CWLoadObjectData @155 + CWLockMemHandle @156 + CWOpenFileInEditor @158 + CWReleaseFileText @232 + CWReportMessage @235 + CWResizeMemHandle @236 + CWSetFileDirty @242 + CWSetModDate @247 + CWShowStatus @257 + CWStoreObjectData @258 + CWStorePluginData @259 + CWUnlockMemHandle @263 + CWUserBreak @264 diff --git a/test.c b/test.c new file mode 100644 index 0000000..5e665e2 --- /dev/null +++ b/test.c @@ -0,0 +1,25 @@ +/* More complex test for mwccwrap */ + +static int global_counter = 0; + +int factorial(int n) { + if (n <= 1) return 1; + return n * factorial(n - 1); +} + +void increment(void) { + global_counter++; +} + +int get_counter(void) { + return global_counter; +} + +int sum_array(int *arr, int count) { + int total = 0; + int i; + for (i = 0; i < count; i++) { + total += arr[i]; + } + return total; +}