From 42aea9154f1ad4a12c33161d9dda890be2cc4ca6 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Fri, 27 Jan 2023 19:56:55 +0100 Subject: [PATCH 01/32] CI: first version of automated builds for os3 --- .github/workflows/makefile.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/makefile.yml diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml new file mode 100644 index 0000000..28f6ecc --- /dev/null +++ b/.github/workflows/makefile.yml @@ -0,0 +1,34 @@ +name: Makefile CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build-os3-debug: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Run the build process with Docker + uses: addnab/docker-run-action@v3 + with: + image: amigadev/crosstools:m68k-amigaos + options: -v ${{ github.workspace }}:/work + run: | + cd mcc + make OS=os3 DEBUG= + cd ../mcp + make OS=os3 DEBUG= + + - uses: actions/upload-artifact@v3 + with: + name: HTMLview_os3 + path: | + bin_os3/HTMLview.mcp + bin_os3/HTMLview-Prefs + bin_os3/HTMLview.mcc + bin_os3/HTMLview-Test From c5c01d2e0eb2f669559fae2cc8e7bda07538de18 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Fri, 27 Jan 2023 20:05:18 +0100 Subject: [PATCH 02/32] rename job title --- .github/workflows/makefile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 28f6ecc..031bb46 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -7,7 +7,7 @@ on: branches: [ "master" ] jobs: - build-os3-debug: + build-os3: runs-on: ubuntu-latest steps: From c773ad466b3ac82afc10e81c8e5394e3a469ea9b Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Mon, 15 Dec 2025 23:06:49 +0100 Subject: [PATCH 03/32] Fix build for gcc 6 (bebbo) and MUI 5 - Several changes to fix the code so that it compiles with the latest GCC 6.x from bebbo. It now builds correctly using amigadev/crosstools:m68k-amigaos for example. - Added cross-compiling build scripts for Windows, which use the aforementioned docker image (they assume the docker engine and image is already installed). - Fixed bugs that caused crashes on OS3.2 and MUI 5.x - Fixed bug where no content was displayed on MUI 5.x - Added a SimpleText tool, to test the library under OS3.x (haven't tested it elsewhere) - Removed obsolete HTMLview-Test.c - the SimpleTest should cover the same thing - Bumped version to 13.5 --- build_os3.bat | 2 + build_os3.ps1 | 36 ++ include/SDI_hook.h | 6 +- include/mccinit.c | 20 +- mcc/Animation.cpp | 4 +- mcc/Debug.cpp | 14 +- mcc/HTMLview-Test.c | 454 ------------------- mcc/ImageManager.cpp | 4 +- mcc/Makefile | 957 +++++++++++++++++++++-------------------- mcc/Memory.cpp | 8 +- mcc/SimpleTest.c | 106 +++++ mcc/TernaryTrees.cpp | 14 +- mcc/TernaryTrees.h | 5 +- mcc/crtclasses_begin.c | 11 + mcc/crtclasses_end.c | 11 + mcc/ctor_dtor.c | 173 ++++---- mcc/library.c | 5 +- mcc/private.h | 33 +- mcc/rev.h | 8 +- mcp/Makefile | 30 +- mcp/exit_stub.c | 9 + mcp/library.c | 2 + mcp/private.h | 12 + mcp/rev.h | 8 +- mcp/vastubs.c | 17 + 25 files changed, 887 insertions(+), 1062 deletions(-) create mode 100644 build_os3.bat create mode 100644 build_os3.ps1 delete mode 100644 mcc/HTMLview-Test.c create mode 100644 mcc/SimpleTest.c create mode 100644 mcc/crtclasses_begin.c create mode 100644 mcc/crtclasses_end.c create mode 100644 mcp/exit_stub.c diff --git a/build_os3.bat b/build_os3.bat new file mode 100644 index 0000000..a83fd38 --- /dev/null +++ b/build_os3.bat @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy Bypass -File "%~dp0build_os3.ps1" diff --git a/build_os3.ps1 b/build_os3.ps1 new file mode 100644 index 0000000..8998e75 --- /dev/null +++ b/build_os3.ps1 @@ -0,0 +1,36 @@ +# PowerShell script to build for AmigaOS 3 using Docker + +$ErrorActionPreference = "Stop" + +# Define the Docker image +$Image = "amigadev/crosstools:m68k-amigaos" + +# Define the build command +# We mirror the steps from .github/workflows/makefile.yml +# 1. cd mcc +# 2. make OS=os3 DEBUG= +# 3. cd ../mcp +# 4. make OS=os3 DEBUG= +$BuildCommand = "mkdir -p mcc/.obj_os3/classes mcc/bin_os3 && cd mcc && make bin_os3/HTMLview.mcc OS=os3 DEBUG= > ../build.log 2>&1 && make bin_os3/SimpleTest OS=os3 DEBUG= >> ../build.log 2>&1 && cd ../mcp && make OS=os3 DEBUG= >> ../build.log 2>&1" + +# Get the current directory (project root) +$WorkDir = Get-Location + +Write-Host "Starting AmigaOS 3 cross-compilation..." +Write-Host "Mounting '$WorkDir' to '/work' in container '$Image'" + +try { + # Run the Docker container + # -v "${WorkDir}:/work" mounts the current directory to /work + # --rm automatically removes the container after exit + # -w /work sets the working directory to /work + docker run --rm -v "${WorkDir}:/work" -w /work $Image /bin/bash -c "$BuildCommand" + + if ($LASTEXITCODE -eq 0) { + Write-Host "Build completed successfully!" + } else { + Write-Error "Build failed with exit code $LASTEXITCODE" + } +} catch { + Write-Error "An error occurred while running Docker: $_" +} diff --git a/include/SDI_hook.h b/include/SDI_hook.h index e0be999..2109b48 100644 --- a/include/SDI_hook.h +++ b/include/SDI_hook.h @@ -305,11 +305,11 @@ // the AmigaOS3 g++ 2.95.3 cannot handle explicit register definitions and // hence requires HookEntry() as gate function #define MakeCppHook(hookname, funcname) struct Hook hookname = {{NULL, NULL}, \ - (HOOKFUNC)HookEntry, (HOOKFUNC)funcname, NULL} + (ULONG (*)())HookEntry, (ULONG (*)())funcname, NULL} #define MakeCppHookWithData(hookname, funcname, data) struct Hook hookname = \ - {{NULL, NULL}, (HOOKFUNC)HookEntry, (HOOKFUNC)funcname, (APTR)data} + {{NULL, NULL}, (ULONG (*)())HookEntry, (ULONG (*)())funcname, (APTR)data} #define MakeStaticCppHook(hookname, funcname) static struct Hook hookname = \ - {{NULL, NULL}, (HOOKFUNC)HookEntry, (HOOKFUNC)funcname, NULL} + {{NULL, NULL}, (ULONG (*)())HookEntry, (ULONG (*)())funcname, NULL} #endif #define DISPATCHERPROTO(name) SAVEDS ASM IPTR name(REG(a0, \ diff --git a/include/mccinit.c b/include/mccinit.c index 1cd74d0..bd5b36c 100644 --- a/include/mccinit.c +++ b/include/mccinit.c @@ -184,7 +184,7 @@ struct Interface *INewlib = NULL; #else struct Library *MUIMasterBase = NULL; struct ExecBase *SysBase = NULL; -struct Library *UtilityBase = NULL; +struct UtilityBase *UtilityBase = NULL; struct DosLibrary *DOSBase = NULL; struct GfxBase *GfxBase = NULL; struct IntuitionBase *IntuitionBase = NULL; @@ -198,7 +198,6 @@ extern "C" { // newer OS version can directly take the specified // number for the ramlib process #if defined(MIN_STACKSIZE) - // transforms a define into a string #define STR(x) STR2(x) #define STR2(x) #x @@ -206,6 +205,12 @@ extern "C" { static const char USED_VAR stack_size[] = "$STACK:" STR(MIN_STACKSIZE) "\n"; #endif +// Declared _init/_fini for AmigaOS 3 C++ manual initialization +#if defined(__amigaos3__) +extern void _init(void); +extern void _fini(void); +#endif + /* The name of the class will also become the name of the library. */ /* We need a pointer to this string in our ROMTag (see below). */ static const char UserLibName[] = CLASS; @@ -642,7 +647,7 @@ static ULONG mccLibInit(struct LibraryHeader *base) if((DOSBase = (struct DosLibrary*)OpenLibrary("dos.library", 36)) && (GfxBase = (struct GfxBase*)OpenLibrary("graphics.library", 36)) && (IntuitionBase = (struct IntuitionBase*)OpenLibrary("intuition.library", 36)) && - (UtilityBase = OpenLibrary("utility.library", 36))) + (UtilityBase = (struct UtilityBase*)OpenLibrary("utility.library", 36))) #endif { // we have to please the internal utilitybase @@ -654,6 +659,11 @@ static ULONG mccLibInit(struct LibraryHeader *base) #endif #endif + #if defined(__amigaos3__) + // Manually call C++ constructors (including InitMem for MemoryPool) + _init(); + #endif + #if defined(DEBUG) SetupDebug(); #endif @@ -948,6 +958,10 @@ static BPTR LIBFUNC LibExpunge(REG(a6, struct LibraryHeader *base)) #endif BPTR rc; + #if defined(__amigaos3__) + _fini(); + #endif + D(DBF_STARTUP, "LibExpunge(%s): %ld", CLASS, base->lh_Library.lib_OpenCnt); // in case our open counter is still > 0, we have diff --git a/mcc/Animation.cpp b/mcc/Animation.cpp index 182ba38..06785c8 100644 --- a/mcc/Animation.cpp +++ b/mcc/Animation.cpp @@ -125,7 +125,9 @@ VOID AnimInfo::Stop (Object *obj, struct AnimInfo *prev) { prev = this; } - prev->Next->Stop(obj, prev); + if (prev && prev->Next) { + prev->Next->Stop(obj, prev); + } } } diff --git a/mcc/Debug.cpp b/mcc/Debug.cpp index 76b9953..79ea8fb 100644 --- a/mcc/Debug.cpp +++ b/mcc/Debug.cpp @@ -20,6 +20,7 @@ ***************************************************************************/ + #ifdef DEBUG #include // vsnprintf @@ -72,7 +73,7 @@ static ULONG debug_classes = DBC_ERROR | DBC_DEBUG | DBC_WARNING | DBC_ASSERT | /****************************************************************************/ -#ifdef __MORPHOS__ +#if defined(__MORPHOS__) #define VNewRawDoFmt(__p0, __p1, __p2, __p3) \ (((STRPTR (*)(void *, CONST_STRPTR , APTR (*)(APTR, UBYTE), STRPTR , va_list ))*(void**)((long)(EXEC_BASE_NAME) - 820))((void*)(EXEC_BASE_NAME), __p0, __p1, __p2, __p3)) @@ -85,6 +86,17 @@ void kprintf(const char *formatString,...) VNewRawDoFmt(formatString,(void * (*)(void *, UBYTE))RAWFMTFUNC_SERIAL,NULL,va); va_end(va); } +#elif defined(__amigaos3__) +extern "C" void KPrintF(const char *fmt, ...); +void kprintf(const char *formatString, ...) +{ + char buf[1024]; + va_list va; + va_start(va, formatString); + vsnprintf(buf, sizeof(buf), formatString, va); + KPrintF("%s", buf); + va_end(va); +} #endif /****************************************************************************/ diff --git a/mcc/HTMLview-Test.c b/mcc/HTMLview-Test.c deleted file mode 100644 index 5e4375b..0000000 --- a/mcc/HTMLview-Test.c +++ /dev/null @@ -1,454 +0,0 @@ -/*************************************************************************** - - HTMLview.mcc - HTMLview MUI Custom Class - Copyright (C) 1997-2000 Allan Odgaard - Copyright (C) 2005-2007 by HTMLview.mcc Open Source Team - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - HTMLview class Support Site: http://www.sf.net/projects/htmlview-mcc/ - - $Id$ - -***************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -//#include - -#include "HTMLview_mcc.h" -#include "mcc_common.h" -#include "SDI_hook.h" -#include "Debug.h" -//#include "private.h" -#include "ScrollGroup.h" - -struct MUI_CustomClass* ThisClass = NULL; - -struct Library* LayersBase = NULL; -struct Library* KeymapBase = NULL; -struct Library* CxBase = NULL; -struct Library* CyberGfxBase = NULL; -struct Library* DiskfontBase = NULL; -struct Library* DataTypesBase = NULL; -struct Library* MUIMasterBase = NULL; -struct Library* UtilityBase = NULL; -#if defined(__amigaos4__) -struct Library* IntuitionBase = NULL; -struct Library* GfxBase = NULL; -#else -struct IntuitionBase* IntuitionBase = NULL; -struct GfxBase* GfxBase = NULL; -#endif - -#if defined(__amigaos4__) -struct LayersIFace* ILayers = NULL; -struct KeymapIFace* IKeymap = NULL; -struct CommoditiesIFace* ICommodities = NULL; -struct CyberGfxIFace* ICyberGfx = NULL; -struct DiskfontIFace* IDiskfont = NULL; -struct DataTypesIFace* IDataTypes = NULL; -struct MUIMasterIFace* IMUIMaster = NULL; -struct IntuitionIFace* IIntuition = NULL; -struct GraphicsIFace* IGraphics = NULL; -struct UtilityIFace* IUtility = NULL; -#endif - -extern ULONG GetHTMLviewDataSize(void); - -extern void _init(void); -extern void _fini(void); - -ULONG xget(Object *obj, const ULONG attr); -#if defined(__GNUC__) || ((__STDC__ == 1L) && (__STDC_VERSION__ >= 199901L)) - // please note that we do not evaluate the return value of GetAttr() - // as some attributes (e.g. MUIA_Selected) always return FALSE, even - // when they are supported by the object. But setting b=0 right before - // the GetAttr() should catch the case when attr doesn't exist at all - #define xget(OBJ, ATTR) ({ULONG b=0; GetAttr(ATTR, OBJ, &b); b;}) -#endif - -HOOKPROTONH(GotoURLCode, ULONG, Object* htmlview, STRPTR *url) -{ - STRPTR target = (STRPTR)xget(htmlview, MUIA_HTMLview_Target); - - DoMethod(htmlview, MUIM_HTMLview_GotoURL, *url, target); - - return 0; -} -MakeStaticHook(GotoURLHook, GotoURLCode); - -CPPDISPATCHERGATE(_Dispatcher); -CPPDISPATCHERGATE(ScrollGroupDispatcher); -#ifdef USEMUISTRINGS -CPPDISPATCHERGATE(StringDispatcher); -#endif - -Object *BuildApp(void) -{ - static CONST_STRPTR classes[] = { "HTMLview.mcc", "TextEditor.mcc", "BetterString.mcc", NULL }; - Object *app, *win, *urlstring, *gauge, *htmlview, *vscroll, *hscroll, *infotext; - Object *alien, *mcp, *scalos, *sysspeed, *konsollen, *p96; - Object *searchstr; -// Object *htmlview2, *vscroll2, *hscroll2; - - ENTER(); - - if((app = ApplicationObject, - MUIA_Application_Author, "HTMLview.mcc Open Source Team", - MUIA_Application_Base, "HTMLview-Test", - MUIA_Application_Copyright, "(c) 2000-2007 HTMLview.mcc Open Source Team", - MUIA_Application_Description, "HTML-display custom class", - MUIA_Application_Title, "HTMLview.mcc", - MUIA_Application_Version, "$VER: HTMLview-Test V0.1 (" __DATE__ ")", - MUIA_Application_UsedClasses, classes, - - SubWindow, win = WindowObject, - MUIA_Window_ID, MAKE_ID('M','A','I','N'), - MUIA_Window_Title, "HTMLview-Demo", - //MUIA_Window_DefaultObject, htmlview, - MUIA_Window_UseBottomBorderScroller, TRUE, - MUIA_Window_UseRightBorderScroller, TRUE, - - WindowContents, VGroup, - Child, HGroup, - Child, alien = SimpleButton("Alien Design"), - Child, mcp = SimpleButton("MCP"), - Child, scalos = SimpleButton("Scalos"), - Child, sysspeed = SimpleButton("SysSpeed"), - Child, konsollen = SimpleButton("Konsollen"), - Child, p96 = SimpleButton("Picasso 96"), - End, - Child, urlstring = BetterStringObject, - StringFrame, - MUIA_CycleChain, TRUE, - MUIA_ControlChar, '\r', -// MUIA_String_Contents, "file://DH0:T/«98.07.13»/parcon/index.htm", -// MUIA_String_Contents, "file://Duff's:T/«98.07.05»/TestV.html", -// MUIA_String_Contents, "file://DH0:T/«98.05.29»/frames.html", -// MUIA_String_Contents, "file://Duff's:T/«98.06.27»/page.html", -// MUIA_String_Contents, "file://Duff's:T/«98.05.31»/aminew.html", -// MUIA_String_Contents, "file://Duff's:T/«98.05.19»/jubii.html", -// MUIA_String_Contents, "file://Duff's:T/«98.05.23»/download.html", -// MUIA_String_Contents, "file://Data:Homepage/daywatch/welcome.html", -// MUIA_String_Contents, "file://Duff's:T/«97.12.31»/applist.html", -// MUIA_String_Contents, "file://Duff's:WorkBench/Locale/Help/English/Developer/HTML4.0/cover.html#toc", -// MUIA_String_Contents, "file://progdir:testpages/main.html", -// MUIA_String_Contents, "file://Duff's:I'mOnline/AWeb3/Docs/html.html", -// MUIA_String_Contents, "file://Data:Homepage/htmlview/index.html", -// MUIA_String_Contents, "file://Data:Homepage/texteditor/index.html#Features", -// MUIA_String_Contents, "file://Duff's:Data/C-Sources/HTMLParser/Testpage.html", -// MUIA_String_Contents, "file://Duff's:T/«98.08.28»/alt_os.html", -// MUIA_String_Contents, "file://Data:Homepage/index.html", -// MUIA_String_Contents, "file://Data:Products/IProbe/IProbe.ReadMe", - MUIA_String_Contents, "file://Download:index.html", - End, - Child, gauge = GaugeObject, - GaugeFrame, - MUIA_Gauge_Current, 0, - MUIA_Gauge_Max, 100, - MUIA_Gauge_Horiz, TRUE, - MUIA_Gauge_InfoText, "(no document loaded)", - End, - -/* Child, HGroup, - - Child, ColGroup(2), - MUIA_Group_Spacing, 0, - Child, htmlview2 = (Object *)NewObject(HTMLviewClass->mcc_Class, NULL, - VirtualFrame, - End, - Child, vscroll2 = ScrollbarObject, - End, - Child, hscroll2 = ScrollbarObject, - MUIA_Group_Horiz, TRUE, - End, - Child, RectangleObject, - End, - End, - - Child, BalanceObject, End, -*/ -/* -#ifndef __amigaos4__ -#ifndef __MORPHOS__ -// Child, ScrollgroupObject, - Child, NewObject(ScrollGroupClass->mcc_Class, NULL, - MUIA_ScrollGroup_Contents, htmlview = (Object *)NewObject(ThisClass->mcc_Class, NULL, - End, - End, -#endif -#endif -*/ - - Child, ColGroup(2), - MUIA_Group_Spacing, 0, - Child, htmlview = (Object *)NewObject(ThisClass->mcc_Class, NULL, - VirtualFrame, - MUIA_HTMLview_DiscreteInput, FALSE, - MUIA_HTMLview_Contents, "

Hej med dig
test", -// MUIA_ContextMenu, TRUE, - End, - Child, vscroll = ScrollbarObject, - MUIA_Prop_UseWinBorder, MUIV_Prop_UseWinBorder_Right, - End, - Child, hscroll = ScrollbarObject, - MUIA_Prop_UseWinBorder, MUIV_Prop_UseWinBorder_Bottom, - MUIA_Group_Horiz, TRUE, - End, - Child, RectangleObject, - End, - End, - -// End, - - Child, infotext = InfoTextObject, - End, - - Child, searchstr = BetterStringObject, - StringFrame, - MUIA_CycleChain, TRUE, - MUIA_ControlChar, 's', - End, - - End, - End, - End)) - { - DoMethod(searchstr, MUIM_Notify, MUIA_String_Acknowledge, MUIV_EveryTime, htmlview, 3, MUIM_HTMLview_Search, MUIV_TriggerValue, MUIF_HTMLview_Search_Next); - - DoMethod(alien, MUIM_Notify, MUIA_Pressed, FALSE, htmlview, 3, MUIM_HTMLview_GotoURL, "file://Duff's:T/«98.07.02»/Alien/index.html", NULL); - DoMethod(mcp, MUIM_Notify, MUIA_Pressed, FALSE, htmlview, 3, MUIM_HTMLview_GotoURL, "file://Duff's:T/«98.07.02»/Alien/programs/mcp/index.html", NULL); - DoMethod(scalos, MUIM_Notify, MUIA_Pressed, FALSE, htmlview, 3, MUIM_HTMLview_GotoURL, "file://Duff's:T/«98.07.02»/Alien/programs/scalos/index.html", NULL); - DoMethod(sysspeed, MUIM_Notify, MUIA_Pressed, FALSE, htmlview, 3, MUIM_HTMLview_GotoURL, "file://Duff's:T/«98.07.02»/Alien/programs/sysspeed/index.html", NULL); - DoMethod(konsollen, MUIM_Notify, MUIA_Pressed, FALSE, htmlview, 3, MUIM_HTMLview_GotoURL, "file://Duff's:T/«98.07.02»/Konsollen/index1.html", NULL); - DoMethod(p96, MUIM_Notify, MUIA_Pressed, FALSE, htmlview, 3, MUIM_HTMLview_GotoURL, "file://Duff's:T/«98.07.02»/~etk10317/Picasso96/Picasso96.html", NULL); - - DoMethod(urlstring, MUIM_Notify, MUIA_String_Acknowledge, MUIV_EveryTime, htmlview, 2, MUIM_HTMLview_GotoURL, MUIV_TriggerValue); - - DoMethod(htmlview, MUIM_Notify, MUIA_Virtgroup_Top, MUIV_EveryTime, vscroll, 3, MUIM_Set, MUIA_Prop_First, MUIV_TriggerValue); - DoMethod(htmlview, MUIM_Notify, MUIA_Height, MUIV_EveryTime, vscroll, 3, MUIM_Set, MUIA_Prop_Visible, MUIV_TriggerValue); - DoMethod(htmlview, MUIM_Notify, MUIA_Virtgroup_Height, MUIV_EveryTime, vscroll, 3, MUIM_Set, MUIA_Prop_Entries, MUIV_TriggerValue); - DoMethod(htmlview, MUIM_Notify, MUIA_Virtgroup_Left, MUIV_EveryTime, hscroll, 3, MUIM_Set, MUIA_Prop_First, MUIV_TriggerValue); - DoMethod(htmlview, MUIM_Notify, MUIA_Width, MUIV_EveryTime, hscroll, 3, MUIM_Set, MUIA_Prop_Visible, MUIV_TriggerValue); - DoMethod(htmlview, MUIM_Notify, MUIA_Virtgroup_Width, MUIV_EveryTime, hscroll, 3, MUIM_Set, MUIA_Prop_Entries, MUIV_TriggerValue); - - DoMethod(htmlview, MUIM_Notify, MUIA_HTMLview_CurrentURL, MUIV_EveryTime, infotext, 3, MUIM_Set, MUIA_Text_Contents, MUIV_TriggerValue); -// DoMethod(htmlview, MUIM_Notify, MUIA_HTMLview_ClickedURL, MUIV_EveryTime, MUIV_Notify_Self, 2, MUIM_HTMLview_GotoURL, MUIV_TriggerValue); - DoMethod(htmlview, MUIM_Notify, MUIA_HTMLview_ClickedURL, MUIV_EveryTime, MUIV_Notify_Self, 3, MUIM_CallHook, &GotoURLHook, MUIV_TriggerValue); - - DoMethod(vscroll, MUIM_Notify, MUIA_Prop_First, MUIV_EveryTime, htmlview, 3, MUIM_Set, MUIA_Virtgroup_Top, MUIV_TriggerValue); - DoMethod(hscroll, MUIM_Notify, MUIA_Prop_First, MUIV_EveryTime, htmlview, 3, MUIM_Set, MUIA_Virtgroup_Left, MUIV_TriggerValue); - -/* DoMethod(htmlview2, MUIM_Notify, MUIA_Virtgroup_Top, MUIV_EveryTime, vscroll2, 3, MUIM_Set, MUIA_Prop_First, MUIV_TriggerValue); - DoMethod(htmlview2, MUIM_Notify, MUIA_Height, MUIV_EveryTime, vscroll2, 3, MUIM_Set, MUIA_Prop_Visible, MUIV_TriggerValue); - DoMethod(htmlview2, MUIM_Notify, MUIA_Virtgroup_Height, MUIV_EveryTime, vscroll2, 3, MUIM_Set, MUIA_Prop_Entries, MUIV_TriggerValue); - DoMethod(htmlview2, MUIM_Notify, MUIA_Virtgroup_Left, MUIV_EveryTime, hscroll2, 3, MUIM_Set, MUIA_Prop_First, MUIV_TriggerValue); - DoMethod(htmlview2, MUIM_Notify, MUIA_Width, MUIV_EveryTime, hscroll2, 3, MUIM_Set, MUIA_Prop_Visible, MUIV_TriggerValue); - DoMethod(htmlview2, MUIM_Notify, MUIA_Virtgroup_Width, MUIV_EveryTime, hscroll2, 3, MUIM_Set, MUIA_Prop_Entries, MUIV_TriggerValue); - DoMethod(vscroll2, MUIM_Notify, MUIA_Prop_First, MUIV_EveryTime, htmlview2, 3, MUIM_Set, MUIA_Virtgroup_Top, MUIV_TriggerValue); - DoMethod(hscroll2, MUIM_Notify, MUIA_Prop_First, MUIV_EveryTime, htmlview2, 3, MUIM_Set, MUIA_Virtgroup_Left, MUIV_TriggerValue); - DoMethod(htmlview2, MUIM_HTMLview_GotoURL, "Data:Homepage/testpage.html"); -*/ -// set(htmlview, MUIA_HTMLview_Gauge, gauge); - - SetAttrs(vscroll, 0x804236ce, TRUE, TAG_DONE); - SetAttrs(hscroll, 0x804236ce, TRUE, TAG_DONE); - SetAttrs(win, MUIA_Window_DefaultObject, htmlview, TAG_DONE); - - DoMethod(win, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, MUIV_Notify_Application, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit); -// set(app, MUIA_Application_Iconified, TRUE); - SetAttrs(win, MUIA_Window_Open, TRUE, TAG_DONE); -// DoMethod(htmlview, MUIM_HTMLview_GotoURL, "file://Duff's:T/«98.09.08»/Log.html", NULL); //Duff's:T/«98.05.31»/aminew.html", NULL); -// DoMethod(htmlview, MUIM_HTMLview_GotoURL, "file://Duff's:T/«98.11.13»/index.html"); -// DoMethod(htmlview, MUIM_HTMLview_GotoURL, "file://Data:Homepage - old/testpage.html"); -// DoMethod(htmlview, MUIM_HTMLview_GotoURL, "file://Duff's:Data/C-Sources/ImageRender/GIFAnims/AllAnims.HTML"); -// DoMethod(htmlview, MUIM_HTMLview_GotoURL, "file://Silvia:Homepage_Real/index.html", NULL); - } - - RETURN(app); - return(app); -} - -VOID MainLoop (Object *app) -{ - ULONG sigs; - - ENTER(); - - while((LONG)DoMethod(app, MUIM_Application_NewInput, &sigs) != MUIV_Application_ReturnID_Quit) - { - if(sigs) - { - sigs = Wait(sigs | SIGBREAKF_CTRL_C); - if(sigs & SIGBREAKF_CTRL_C) - break; - } - } - - LEAVE(); -} - -#if defined(__libnix__) -void *mempool; -#endif - -int main(void) -{ - kprintf("%s\n", __FUNCTION__); - _init(); - - #if defined(__libnix__) - mempool = CreatePool(MEMF_CLEAR | MEMF_SEM_PROTECTED, 12*1024, 6*1024); - #endif - - #if defined(__amigaos4__) - if((IntuitionBase = OpenLibrary("intuition.library", 38)) && - GETINTERFACE(IIntuition, struct IntuitionIFace*, IntuitionBase)) - if((GfxBase = OpenLibrary("graphics.library", 38)) && - GETINTERFACE(IGraphics, struct GraphicsIFace*, GfxBase)) - #else - if((IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 38)) && - GETINTERFACE(IIntuition, struct IntuitionIFace*, IntuitionBase)) - if((GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 38)) && - GETINTERFACE(IGraphics, struct GraphicsIFace*, GfxBase)) - #endif - if((UtilityBase = OpenLibrary("utility.library", 38)) && - GETINTERFACE(IUtility, struct UtilityIFace*, UtilityBase)) - if((LayersBase = OpenLibrary("layers.library", 36)) && - GETINTERFACE(ILayers, struct LayersIFace*, LayersBase)) - if((KeymapBase = OpenLibrary("keymap.library", 36)) && - GETINTERFACE(IKeymap, struct KeymapIFace*, KeymapBase)) - if((CxBase = OpenLibrary("commodities.library", 36)) && - GETINTERFACE(ICommodities, struct CommoditiesIFace*, CxBase)) - if((DiskfontBase = OpenLibrary("diskfont.library", 36)) && - GETINTERFACE(IDiskfont, struct DiskfontIFace*, DiskfontBase)) - if((DataTypesBase = OpenLibrary("datatypes.library", 36)) && - GETINTERFACE(IDataTypes, struct DataTypesIFace*, DataTypesBase)) - { - // open cybergraphics.library optional! - if((CyberGfxBase = OpenLibrary("cybergraphics.library", 40)) && - GETINTERFACE(ICyberGfx, struct CyberGfxIFace*, CyberGfxBase)) - { } - - #if defined(DEBUG) - SetupDebug(); - #endif - - ENTER(); - - if((MUIMasterBase = OpenLibrary("muimaster.library", MUIMASTER_VMIN)) && - GETINTERFACE(IMUIMaster, struct MUIMasterIFace*, MUIMasterBase)) - { - Object *app; - - ThisClass = MUI_CreateCustomClass(NULL, MUIC_Virtgroup, NULL, GetHTMLviewDataSize(), CPPDISPATCHERENTRY(_Dispatcher)); - ScrollGroupClass = MUI_CreateCustomClass(NULL, MUIC_Virtgroup, NULL, GetScrollGroupDataSize(), CPPDISPATCHERENTRY(ScrollGroupDispatcher)); - - if((app = BuildApp())) - { - MainLoop(app); - MUI_DisposeObject(app); - } - - MUI_DeleteCustomClass(ScrollGroupClass); - MUI_DeleteCustomClass(ThisClass); - } - - if(MUIMasterBase) - { - DROPINTERFACE(IMUIMaster); - CloseLibrary(MUIMasterBase); - MUIMasterBase = NULL; - } - - if(CyberGfxBase) - { - DROPINTERFACE(ICyberGfx); - CloseLibrary(CyberGfxBase); - CyberGfxBase = NULL; - } - - if(DataTypesBase) - { - DROPINTERFACE(IDataTypes); - CloseLibrary(DataTypesBase); - DataTypesBase = NULL; - } - - if(DiskfontBase) - { - DROPINTERFACE(IDiskfont); - CloseLibrary(DiskfontBase); - DiskfontBase = NULL; - } - - if(CxBase) - { - DROPINTERFACE(ICommodities); - CloseLibrary(CxBase); - CxBase = NULL; - } - - if(KeymapBase) - { - DROPINTERFACE(IKeymap); - CloseLibrary(KeymapBase); - KeymapBase = NULL; - } - - if(LayersBase) - { - DROPINTERFACE(ILayers); - CloseLibrary(LayersBase); - LayersBase = NULL; - } - - if(UtilityBase) - { - DROPINTERFACE(IUtility); - CloseLibrary(UtilityBase); - UtilityBase = NULL; - } - - if(GfxBase) - { - DROPINTERFACE(IGraphics); - CloseLibrary((struct Library *)GfxBase); - GfxBase = NULL; - } - - if(IntuitionBase) - { - DROPINTERFACE(IIntuition); - CloseLibrary((struct Library *)IntuitionBase); - IntuitionBase = NULL; - } - } - - _fini(); - - - RETURN(0); - return 0; -} diff --git a/mcc/ImageManager.cpp b/mcc/ImageManager.cpp index 6759a29..a41e060 100644 --- a/mcc/ImageManager.cpp +++ b/mcc/ImageManager.cpp @@ -792,8 +792,8 @@ Object *NewDecoderObjectA(UBYTE *buf,struct TagItem *attrs) if((cl = MakeClass(NULL, NULL, GetImageDecoderClass(decoders->Base), sizeof(DecoderData), 0L))) { #if defined(__amigaos3__) - cl->cl_Dispatcher.h_SubEntry = (HOOKFUNC)ENTRY(DecoderDispatcher); - cl->cl_Dispatcher.h_Entry = (HOOKFUNC)HookEntry; + cl->cl_Dispatcher.h_SubEntry = (ULONG (*)())ENTRY(DecoderDispatcher); + cl->cl_Dispatcher.h_Entry = (ULONG (*)())HookEntry; cl->cl_Dispatcher.h_Data = 0; #else cl->cl_Dispatcher.h_SubEntry = 0; diff --git a/mcc/Makefile b/mcc/Makefile index a6ea900..ff5a974 100644 --- a/mcc/Makefile +++ b/mcc/Makefile @@ -1,467 +1,490 @@ -#/*************************************************************************** -# -# HTMLview.mcc - HTMLview MUI Custom Class -# Copyright (C) 1997-2000 Allan Odgaard -# Copyright (C) 2005-2007 by HTMLview.mcc Open Source Team -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# HTMLview class Support Site: http://www.sf.net/projects/htmlview-mcc/ -# -# $Id: rev.h,v 1.4 2005/04/11 03:04:38 tactica Exp $ -# -#***************************************************************************/ - -########################################################################### -# This makefile is a very generic one. It tries to identify both, the host -# and the target operating system for which YAM should be compiled. -# However, this auto-detection can be easily overridden by directly -# specifying an option on the commandline while calling 'make'. -# -# Example: -# -# # to explicitly compile for AmigaOS3 -# > make OS=os3 -# -# # to compile for AmigaOS4 but with debugging -# > make OS=os4 DEBUG= -# - -############################################# -# find out the HOST operating system -# on which this makefile is run -ifndef (HOST) - HOST = $(shell uname) - ifeq ($(HOST), AmigaOS) - ifeq ($(shell uname -m), powerpc) - HOST = AmigaOS4 - endif - ifeq ($(shell uname -m), ppc) - HOST = AmigaOS4 - endif - endif -endif - -############################################# -# now we find out the target OS for -# which we are going to compile YAM in case -# the caller didn't yet define OS himself -ifndef (OS) - ifeq ($(HOST), AmigaOS4) - OS = os4 - else - ifeq ($(HOST), AmigaOS) - OS = os3 - else - ifeq ($(HOST), MorphOS) - OS = mos - else - ifeq ($(HOST), Aros) - OS = aros - else - OS = os4 - endif - endif - endif - endif -endif - -############################################# -# define common commands we use in this -# makefile. Please note that each of them -# might be overridden on the commandline. - -# common commands -FLEX = flex -FC = flexcat -EXPR = expr -CHMOD = chmod -SED = sed -DATE = date -RM = rm -f -RMDIR = rm -rf -MKDIR = mkdir - -# override commands for native builds -ifeq ($(HOST), AmigaOS4) - # AmigaOS4 host - RM = delete force - RMDIR = delete force all - MKDIR = makedir - DATE = gdate -else -ifeq ($(HOST), AmigaOS) - # AmigaOS3 host - RM = delete force - RMDIR = delete force all - MKDIR = makedir -else -ifeq ($(HOST), MorphOS) - # MorphOS host - RM = delete force - RMDIR = delete force all - MKDIR = makedir -endif -endif -endif - -########################################################################### -# CPU and DEBUG can be defined outside, defaults to above -# using e.g. "make DEBUG= CPU=-mcpu=603e" produces optimized non-debug -# PPC-603e version -# -# OPTFLAGS are disabled by DEBUG normally! -# -# ignored warnings are: -# none - because we want to compile with -Wall all the time - -# Common Directories -OBJDIR = .obj_$(OS) -BINDIR = bin_$(OS) -LOCALE = locale - -# target definition -MCCTARGET = $(BINDIR)/HTMLview.mcc -TESTTARGET= $(BINDIR)/HTMLview-Test - -# Common compiler/linker flags -WARN = -W -Wall -Wwrite-strings -OPTFLAGS = -O3 -fomit-frame-pointer -funroll-loops -DEBUG = -DDEBUG -O0 -DEBUGSYM = -g -gstabs -CFLAGS = -I. -I../mcp -I../include $(CPU) $(WARN) $(OPTFLAGS) \ - $(DEBUG) $(DEBUGSYM) -c -LDFLAGS = $(CPU) $(DEBUGSYM) -LDLIBS = - -# different options per target OS -ifeq ($(OS), os4) - - ############################## - # AmigaOS4 - - # Compiler/link/strip commands - CC = ppc-amigaos-gcc - CXX = ppc-amigaos-g++ - STRIP = ppc-amigaos-strip - OBJDUMP = ppc-amigaos-objdump - - # Compiler/Linker flags - CRT = newlib - CPU = -mcpu=powerpc - WARN += -Wno-unused-parameter -Wno-narrowing -Wno-sign-compare - CFLAGS += -mcrt=$(CRT) -D__USE_INLINE__ -D__NEW_TIMEVAL_DEFINITION_USED__ \ - -D__USE_CLASSIC_MINTERM__ -Wa,-mregnames -DNO_PPCINLINE_STDARG - LDFLAGS += -mcrt=$(CRT) - - # additional object files required - M68KSTUBS = $(OBJDIR)/mccclass_68k.o - -else -ifeq ($(OS), os3) - - ############################## - # AmigaOS3 - - # Compiler/link/strip commands - CC = m68k-amigaos-gcc - CXX = m68k-amigaos-g++ - STRIP = m68k-amigaos-strip - OBJDUMP = m68k-amigaos-objdump - - # Compiler/Linker flags - CPU = -m68020-60 -msoft-float - WARN += -Wno-sign-compare - CFLAGS += -noixemul -DNO_INLINE_STDARG -D__amigaos3__ - LDFLAGS += -noixemul - LDLIBS += -ldebug -lmui - -else -ifeq ($(OS), mos) - - ############################## - # MorphOS - - # Compiler/link/strip commands - CC = ppc-morphos-gcc - CXX = ppc-morphos-g++ - STRIP = ppc-morphos-strip - OBJDUMP = ppc-morphos-objdump - - # Compiler/Linker flags - CPU = -mcpu=powerpc - CFLAGS += -noixemul -DNO_PPCINLINE_STDARG - LDFLAGS += -noixemul - LDLIBS += -ldebug - -else -ifeq ($(OS), aros) - - ############################## - # AROS - - # Compiler/link/strip commands - CC = i686-aros-gcc - CXX = i686-aros-g++ - STRIP = i686-aros-strip - OBJDUMP = i686-aros-objdump - - # Compiler/Linker flags - CPU = - -endif -endif -endif -endif - -########################################################################### -# Here starts all stuff that is common for all target platforms and -# hosts. - -MCCOBJS = \ - $(OBJDIR)/library.o \ - $(OBJDIR)/library_cpp.o \ - $(OBJDIR)/ctor_dtor.o \ - $(OBJDIR)/classes/AClass.o \ - $(OBJDIR)/classes/AreaClass.o \ - $(OBJDIR)/classes/AttrClass.o \ - $(OBJDIR)/classes/BackFillClass.o \ - $(OBJDIR)/classes/BaseClass.o \ - $(OBJDIR)/classes/BlockquoteClass.o \ - $(OBJDIR)/classes/BodyClass.o \ - $(OBJDIR)/classes/BrClass.o \ - $(OBJDIR)/classes/CommentClass.o \ - $(OBJDIR)/classes/DDClass.o \ - $(OBJDIR)/classes/DLClass.o \ - $(OBJDIR)/classes/DTClass.o \ - $(OBJDIR)/classes/DummyClass.o \ - $(OBJDIR)/classes/FontClass.o \ - $(OBJDIR)/classes/FontStyleClass.o \ - $(OBJDIR)/classes/FormClass.o \ - $(OBJDIR)/classes/FrameClass.o \ - $(OBJDIR)/classes/FramesetClass.o \ - $(OBJDIR)/classes/HRClass.o \ - $(OBJDIR)/classes/HostClass.o \ - $(OBJDIR)/classes/ImgClass.o \ - $(OBJDIR)/classes/InputClass.o \ - $(OBJDIR)/classes/IsIndexClass.o \ - $(OBJDIR)/classes/LIClass.o \ - $(OBJDIR)/classes/MapClass.o \ - $(OBJDIR)/classes/MetaClass.o \ - $(OBJDIR)/classes/NoFramesClass.o \ - $(OBJDIR)/classes/OLClass.o \ - $(OBJDIR)/classes/OptionClass.o \ - $(OBJDIR)/classes/PClass.o \ - $(OBJDIR)/classes/ScriptClass.o \ - $(OBJDIR)/classes/SelectClass.o \ - $(OBJDIR)/classes/SuperClass.o \ - $(OBJDIR)/classes/TDClass.o \ - $(OBJDIR)/classes/THClass.o \ - $(OBJDIR)/classes/TRClass.o \ - $(OBJDIR)/classes/TableClass.o \ - $(OBJDIR)/classes/TextAreaClass.o \ - $(OBJDIR)/classes/TextClass.o \ - $(OBJDIR)/classes/TitleClass.o \ - $(OBJDIR)/classes/TreeClass.o \ - $(OBJDIR)/classes/ULClass.o \ - $(OBJDIR)/AllocateColours.o \ - $(OBJDIR)/Animation.o \ - $(OBJDIR)/Classes.o \ - $(OBJDIR)/Colours.o \ - $(OBJDIR)/Config.o \ - $(OBJDIR)/Dispatcher.o \ - $(OBJDIR)/Entities.o \ - $(OBJDIR)/Forms.o \ - $(OBJDIR)/GetSetAttrs.o \ - $(OBJDIR)/HandleMUIEvent.o \ - $(OBJDIR)/IM_ColourManager.o \ - $(OBJDIR)/IM_Dither.o \ - $(OBJDIR)/IM_Output.o \ - $(OBJDIR)/IM_Render.o \ - $(OBJDIR)/IM_Scale.o \ - $(OBJDIR)/ImageManager.o \ - $(OBJDIR)/Layout.o \ - $(OBJDIR)/Mark.o \ - $(OBJDIR)/Memory.o \ - $(OBJDIR)/MinMax.o \ - $(OBJDIR)/ParseMessage.o \ - $(OBJDIR)/ParseThread.o \ - $(OBJDIR)/Render.o \ - $(OBJDIR)/ScanArgs.o \ - $(OBJDIR)/ScrollGroup.o \ - $(OBJDIR)/SharedData.o \ - $(OBJDIR)/TagInfo.o \ - $(OBJDIR)/TernaryTrees.o \ - $(OBJDIR)/URLHandling.o \ - $(OBJDIR)/StringClass.o \ - $(OBJDIR)/Debug.o - -TESTOBJS= $(OBJDIR)/HTMLview-Test.o \ - $(OBJDIR)/library_cpp.o \ - $(OBJDIR)/ctor_dtor.o \ - $(OBJDIR)/classes/AClass.o \ - $(OBJDIR)/classes/AreaClass.o \ - $(OBJDIR)/classes/AttrClass.o \ - $(OBJDIR)/classes/BackFillClass.o \ - $(OBJDIR)/classes/BaseClass.o \ - $(OBJDIR)/classes/BlockquoteClass.o \ - $(OBJDIR)/classes/BodyClass.o \ - $(OBJDIR)/classes/BrClass.o \ - $(OBJDIR)/classes/CommentClass.o \ - $(OBJDIR)/classes/DDClass.o \ - $(OBJDIR)/classes/DLClass.o \ - $(OBJDIR)/classes/DTClass.o \ - $(OBJDIR)/classes/DummyClass.o \ - $(OBJDIR)/classes/FontClass.o \ - $(OBJDIR)/classes/FontStyleClass.o \ - $(OBJDIR)/classes/FormClass.o \ - $(OBJDIR)/classes/FrameClass.o \ - $(OBJDIR)/classes/FramesetClass.o \ - $(OBJDIR)/classes/HRClass.o \ - $(OBJDIR)/classes/HostClass.o \ - $(OBJDIR)/classes/ImgClass.o \ - $(OBJDIR)/classes/InputClass.o \ - $(OBJDIR)/classes/IsIndexClass.o \ - $(OBJDIR)/classes/LIClass.o \ - $(OBJDIR)/classes/MapClass.o \ - $(OBJDIR)/classes/MetaClass.o \ - $(OBJDIR)/classes/NoFramesClass.o \ - $(OBJDIR)/classes/OLClass.o \ - $(OBJDIR)/classes/OptionClass.o \ - $(OBJDIR)/classes/PClass.o \ - $(OBJDIR)/classes/ScriptClass.o \ - $(OBJDIR)/classes/SelectClass.o \ - $(OBJDIR)/classes/SuperClass.o \ - $(OBJDIR)/classes/TDClass.o \ - $(OBJDIR)/classes/THClass.o \ - $(OBJDIR)/classes/TRClass.o \ - $(OBJDIR)/classes/TableClass.o \ - $(OBJDIR)/classes/TextAreaClass.o \ - $(OBJDIR)/classes/TextClass.o \ - $(OBJDIR)/classes/TitleClass.o \ - $(OBJDIR)/classes/TreeClass.o \ - $(OBJDIR)/classes/ULClass.o \ - $(OBJDIR)/AllocateColours.o \ - $(OBJDIR)/Animation.o \ - $(OBJDIR)/Classes.o \ - $(OBJDIR)/Colours.o \ - $(OBJDIR)/Config.o \ - $(OBJDIR)/Dispatcher.o \ - $(OBJDIR)/Entities.o \ - $(OBJDIR)/Forms.o \ - $(OBJDIR)/GetSetAttrs.o \ - $(OBJDIR)/HandleMUIEvent.o \ - $(OBJDIR)/IM_ColourManager.o \ - $(OBJDIR)/IM_Dither.o \ - $(OBJDIR)/IM_Output.o \ - $(OBJDIR)/IM_Render.o \ - $(OBJDIR)/IM_Scale.o \ - $(OBJDIR)/ImageManager.o \ - $(OBJDIR)/Layout.o \ - $(OBJDIR)/Mark.o \ - $(OBJDIR)/Memory.o \ - $(OBJDIR)/MinMax.o \ - $(OBJDIR)/ParseMessage.o \ - $(OBJDIR)/ParseThread.o \ - $(OBJDIR)/Render.o \ - $(OBJDIR)/ScanArgs.o \ - $(OBJDIR)/ScrollGroup.o \ - $(OBJDIR)/SharedData.o \ - $(OBJDIR)/TagInfo.o \ - $(OBJDIR)/TernaryTrees.o \ - $(OBJDIR)/URLHandling.o \ - $(OBJDIR)/StringClass.o \ - $(OBJDIR)/Debug.o - -ifeq ($(OS), os3) -MCCOBJS += \ - $(OBJDIR)/vastubs.o - -TESTOBJS += \ - $(OBJDIR)/vastubs.o -endif - -# available catalog translations -CATALOGS = $(LOCALE)/deutsch.catalog - -# - -all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(TESTTARGET) - -# make the object directories -$(OBJDIR): - @echo " MKDIR $@" - @$(MKDIR) $(OBJDIR) - @$(MKDIR) $(OBJDIR)/classes - -# make the binary directories -$(BINDIR): - @echo " MKDIR $@" - @$(MKDIR) $(BINDIR) - -# - -$(OBJDIR)/%.o: %.c - @echo " CC $<" - @$(CC) $(CFLAGS) $< -o $@ - -$(OBJDIR)/%.o: %.cpp - @echo " CXX $<" - @$(CXX) $(CFLAGS) $< -o $@ - -$(OBJDIR)/mccclass_68k.o: ../include/mccclass_68k.c - @echo " CC $<" - @$(CC) $(CFLAGS) $< -o $@ - -# - -$(MCCTARGET): $(M68KSTUBS) $(MCCOBJS) - @echo " LD $@" - @$(CXX) -nostartfiles $(LDFLAGS) -o $@.debug $(MCCOBJS) $(M68KSTUBS) $(LDLIBS) -Wl,-Map,$@.map - @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug - -$(TESTTARGET): $(TESTOBJS) $(OBJDIR)/HTMLview-Test.o - @echo " LD $@" - @$(CXX) $(LDFLAGS) -o $@.debug $(TESTOBJS) $(LDLIBS) -Wl,-Map,$@.map - @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug - -$(OBJDIR)/library.o: library.c ../include/mccinit.c \ - HTMLview_mcc.h private.h rev.h - -locale.h: locale.c -locale.c: locale/HTMLview_mcc.cd locale_c.sd locale_h.sd - @echo " GN $@" - @$(FC) locale/HTMLview_mcc.cd locale.h=locale_h.sd locale.c=locale_c.sd - -## CATALOG GENERATION ################# - -$(LOCALE)/%.catalog: $(LOCALE)/%.ct - @echo " FC $@" - @$(FC) $(LOCALE)/HTMLview_mcc.cd $< CATALOG $@ - -.IGNORE: $(CATALOGS) - -catalogs: $(CATALOGS) - - -# - -.PHONY: clean -clean: - -$(RM) $(MCCTARGET) $(MCCTARGET).debug $(MCCTARGET).map - -$(RM) $(TESTTARGET) $(TESTTARGET).debug $(TESTTARGET).map - -$(RM) $(MCCOBJS) $(TESTOBJS) $(M68KSTUBS) - -.PHONY: distclean -distclean: clean - -$(RM) locale.? - -$(RMDIR) $(OBJDIR) - -$(RMDIR) $(BINDIR) +#/*************************************************************************** +# +# HTMLview.mcc - HTMLview MUI Custom Class +# Copyright (C) 1997-2000 Allan Odgaard +# Copyright (C) 2005-2007 by HTMLview.mcc Open Source Team +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# HTMLview class Support Site: http://www.sf.net/projects/htmlview-mcc/ +# +# $Id: rev.h,v 1.4 2005/04/11 03:04:38 tactica Exp $ +# +#***************************************************************************/ + +########################################################################### +# This makefile is a very generic one. It tries to identify both, the host +# and the target operating system for which YAM should be compiled. +# However, this auto-detection can be easily overridden by directly +# specifying an option on the commandline while calling 'make'. +# +# Example: +# +# # to explicitly compile for AmigaOS3 +# > make OS=os3 +# +# # to compile for AmigaOS4 but with debugging +# > make OS=os4 DEBUG= +# + +############################################# +# find out the HOST operating system +# on which this makefile is run +ifndef (HOST) + HOST = $(shell uname) + ifeq ($(HOST), AmigaOS) + ifeq ($(shell uname -m), powerpc) + HOST = AmigaOS4 + endif + ifeq ($(shell uname -m), ppc) + HOST = AmigaOS4 + endif + endif +endif + +############################################# +# now we find out the target OS for +# which we are going to compile YAM in case +# the caller didn't yet define OS himself +ifndef (OS) + ifeq ($(HOST), AmigaOS4) + OS = os4 + else + ifeq ($(HOST), AmigaOS) + OS = os3 + else + ifeq ($(HOST), MorphOS) + OS = mos + else + ifeq ($(HOST), Aros) + OS = aros + else + OS = os4 + endif + endif + endif + endif +endif + +############################################# +# define common commands we use in this +# makefile. Please note that each of them +# might be overridden on the commandline. + +# common commands +FLEX = flex +FC = flexcat +EXPR = expr +CHMOD = chmod +SED = sed +DATE = date +RM = rm -f +RMDIR = rm -rf +MKDIR = mkdir + +# override commands for native builds +ifeq ($(HOST), AmigaOS4) + # AmigaOS4 host + RM = delete force + RMDIR = delete force all + MKDIR = makedir + DATE = gdate +else +ifeq ($(HOST), AmigaOS) + # AmigaOS3 host + RM = delete force + RMDIR = delete force all + MKDIR = makedir +else +ifeq ($(HOST), MorphOS) + # MorphOS host + RM = delete force + RMDIR = delete force all + MKDIR = makedir +endif +endif +endif + +########################################################################### +# CPU and DEBUG can be defined outside, defaults to above +# using e.g. "make DEBUG= CPU=-mcpu=603e" produces optimized non-debug +# PPC-603e version +# +# OPTFLAGS are disabled by DEBUG normally! +# +# ignored warnings are: +# none - because we want to compile with -Wall all the time + +# Common Directories +OBJDIR = .obj_$(OS) +BINDIR = bin_$(OS) +LOCALE = locale + +# target definition +MCCTARGET = $(BINDIR)/HTMLview.mcc +TESTTARGET= $(BINDIR)/HTMLview-Test +SIMPLETARGET= $(BINDIR)/SimpleTest + +# Common compiler/linker flags +WARN = -W -Wall -Wwrite-strings +OPTFLAGS = -O3 -fomit-frame-pointer +DEBUG = +DEBUGSYM = + +ifneq ($(DEBUG),) + OPTFLAGS = -O0 + DEBUGSYM = -g -gstabs + CFLAGS = -I. -I../mcp -I../include $(CPU) $(WARN) $(OPTFLAGS) \ + -DDEBUG $(DEBUGSYM) -c +else + CFLAGS = -I. -I../mcp -I../include $(CPU) $(WARN) $(OPTFLAGS) \ + -DNDEBUG -c +endif +LDFLAGS = $(CPU) $(DEBUGSYM) +LDLIBS = + +# different options per target OS +ifeq ($(OS), os4) + + ############################## + # AmigaOS4 + + # Compiler/link/strip commands + CC = ppc-amigaos-gcc + CXX = ppc-amigaos-g++ + STRIP = ppc-amigaos-strip + OBJDUMP = ppc-amigaos-objdump + + # Compiler/Linker flags + CRT = newlib + CPU = -mcpu=powerpc + WARN += -Wno-unused-parameter -Wno-narrowing -Wno-sign-compare + CFLAGS += -mcrt=$(CRT) -D__USE_INLINE__ -D__NEW_TIMEVAL_DEFINITION_USED__ \ + -D__USE_CLASSIC_MINTERM__ -Wa,-mregnames -DNO_PPCINLINE_STDARG + LDFLAGS += -mcrt=$(CRT) + + # additional object files required + M68KSTUBS = $(OBJDIR)/mccclass_68k.o + +else +ifeq ($(OS), os3) + + ############################## + # AmigaOS3 + + # Compiler/link/strip commands + CC = m68k-amigaos-gcc + CXX = m68k-amigaos-g++ + STRIP = m68k-amigaos-strip + OBJDUMP = m68k-amigaos-objdump + + # Compiler/Linker flags + CPU = -m68020-60 -msoft-float + WARN += -Wno-sign-compare + CFLAGS += -noixemul -DNO_INLINE_STDARG -D__amigaos3__ + LDFLAGS += -noixemul -nostartfiles + LDLIBS += -lmui + ifneq ($(DEBUG),) + LDLIBS += -ldebug + endif + + +else +ifeq ($(OS), mos) + + ############################## + # MorphOS + + # Compiler/link/strip commands + CC = ppc-morphos-gcc + CXX = ppc-morphos-g++ + STRIP = ppc-morphos-strip + OBJDUMP = ppc-morphos-objdump + + # Compiler/Linker flags + CPU = -mcpu=powerpc + CFLAGS += -noixemul -DNO_PPCINLINE_STDARG + LDFLAGS += -noixemul + ifneq ($(DEBUG),) + LDLIBS += -ldebug + endif + + +else +ifeq ($(OS), aros) + + ############################## + # AROS + + # Compiler/link/strip commands + CC = i686-aros-gcc + CXX = i686-aros-g++ + STRIP = i686-aros-strip + OBJDUMP = i686-aros-objdump + + # Compiler/Linker flags + CPU = + +endif +endif +endif +endif + +########################################################################### +# Here starts all stuff that is common for all target platforms and +# hosts. + +MCCOBJS = \ + $(OBJDIR)/crtclasses_begin.o \ + $(OBJDIR)/library.o \ + $(OBJDIR)/library_cpp.o \ + $(OBJDIR)/ctor_dtor.o \ + $(OBJDIR)/classes/AClass.o \ + $(OBJDIR)/classes/AreaClass.o \ + $(OBJDIR)/classes/AttrClass.o \ + $(OBJDIR)/classes/BackFillClass.o \ + $(OBJDIR)/classes/BaseClass.o \ + $(OBJDIR)/classes/BlockquoteClass.o \ + $(OBJDIR)/classes/BodyClass.o \ + $(OBJDIR)/classes/BrClass.o \ + $(OBJDIR)/classes/CommentClass.o \ + $(OBJDIR)/classes/DDClass.o \ + $(OBJDIR)/classes/DLClass.o \ + $(OBJDIR)/classes/DTClass.o \ + $(OBJDIR)/classes/DummyClass.o \ + $(OBJDIR)/classes/FontClass.o \ + $(OBJDIR)/classes/FontStyleClass.o \ + $(OBJDIR)/classes/FormClass.o \ + $(OBJDIR)/classes/FrameClass.o \ + $(OBJDIR)/classes/FramesetClass.o \ + $(OBJDIR)/classes/HRClass.o \ + $(OBJDIR)/classes/HostClass.o \ + $(OBJDIR)/classes/ImgClass.o \ + $(OBJDIR)/classes/InputClass.o \ + $(OBJDIR)/classes/IsIndexClass.o \ + $(OBJDIR)/classes/LIClass.o \ + $(OBJDIR)/classes/MapClass.o \ + $(OBJDIR)/classes/MetaClass.o \ + $(OBJDIR)/classes/NoFramesClass.o \ + $(OBJDIR)/classes/OLClass.o \ + $(OBJDIR)/classes/OptionClass.o \ + $(OBJDIR)/classes/PClass.o \ + $(OBJDIR)/classes/ScriptClass.o \ + $(OBJDIR)/classes/SelectClass.o \ + $(OBJDIR)/classes/SuperClass.o \ + $(OBJDIR)/classes/TDClass.o \ + $(OBJDIR)/classes/THClass.o \ + $(OBJDIR)/classes/TRClass.o \ + $(OBJDIR)/classes/TableClass.o \ + $(OBJDIR)/classes/TextAreaClass.o \ + $(OBJDIR)/classes/TextClass.o \ + $(OBJDIR)/classes/TitleClass.o \ + $(OBJDIR)/classes/TreeClass.o \ + $(OBJDIR)/classes/ULClass.o \ + $(OBJDIR)/AllocateColours.o \ + $(OBJDIR)/Animation.o \ + $(OBJDIR)/Classes.o \ + $(OBJDIR)/Colours.o \ + $(OBJDIR)/Config.o \ + $(OBJDIR)/Dispatcher.o \ + $(OBJDIR)/Entities.o \ + $(OBJDIR)/Forms.o \ + $(OBJDIR)/GetSetAttrs.o \ + $(OBJDIR)/HandleMUIEvent.o \ + $(OBJDIR)/IM_ColourManager.o \ + $(OBJDIR)/IM_Dither.o \ + $(OBJDIR)/IM_Output.o \ + $(OBJDIR)/IM_Render.o \ + $(OBJDIR)/IM_Scale.o \ + $(OBJDIR)/ImageManager.o \ + $(OBJDIR)/Layout.o \ + $(OBJDIR)/Mark.o \ + $(OBJDIR)/Memory.o \ + $(OBJDIR)/MinMax.o \ + $(OBJDIR)/ParseMessage.o \ + $(OBJDIR)/ParseThread.o \ + $(OBJDIR)/Render.o \ + $(OBJDIR)/ScanArgs.o \ + $(OBJDIR)/ScrollGroup.o \ + $(OBJDIR)/SharedData.o \ + $(OBJDIR)/TagInfo.o \ + $(OBJDIR)/TernaryTrees.o \ + $(OBJDIR)/URLHandling.o \ + $(OBJDIR)/StringClass.o \ + $(OBJDIR)/Debug.o \ + $(OBJDIR)/crtclasses_end.o + +TESTOBJS= $(OBJDIR)/library_cpp.o \ + $(OBJDIR)/library_cpp.o \ + $(OBJDIR)/ctor_dtor.o \ + $(OBJDIR)/classes/AClass.o \ + $(OBJDIR)/classes/AreaClass.o \ + $(OBJDIR)/classes/AttrClass.o \ + $(OBJDIR)/classes/BackFillClass.o \ + $(OBJDIR)/classes/BaseClass.o \ + $(OBJDIR)/classes/BlockquoteClass.o \ + $(OBJDIR)/classes/BodyClass.o \ + $(OBJDIR)/classes/BrClass.o \ + $(OBJDIR)/classes/CommentClass.o \ + $(OBJDIR)/classes/DDClass.o \ + $(OBJDIR)/classes/DLClass.o \ + $(OBJDIR)/classes/DTClass.o \ + $(OBJDIR)/classes/DummyClass.o \ + $(OBJDIR)/classes/FontClass.o \ + $(OBJDIR)/classes/FontStyleClass.o \ + $(OBJDIR)/classes/FormClass.o \ + $(OBJDIR)/classes/FrameClass.o \ + $(OBJDIR)/classes/FramesetClass.o \ + $(OBJDIR)/classes/HRClass.o \ + $(OBJDIR)/classes/HostClass.o \ + $(OBJDIR)/classes/ImgClass.o \ + $(OBJDIR)/classes/InputClass.o \ + $(OBJDIR)/classes/IsIndexClass.o \ + $(OBJDIR)/classes/LIClass.o \ + $(OBJDIR)/classes/MapClass.o \ + $(OBJDIR)/classes/MetaClass.o \ + $(OBJDIR)/classes/NoFramesClass.o \ + $(OBJDIR)/classes/OLClass.o \ + $(OBJDIR)/classes/OptionClass.o \ + $(OBJDIR)/classes/PClass.o \ + $(OBJDIR)/classes/ScriptClass.o \ + $(OBJDIR)/classes/SelectClass.o \ + $(OBJDIR)/classes/SuperClass.o \ + $(OBJDIR)/classes/TDClass.o \ + $(OBJDIR)/classes/THClass.o \ + $(OBJDIR)/classes/TRClass.o \ + $(OBJDIR)/classes/TableClass.o \ + $(OBJDIR)/classes/TextAreaClass.o \ + $(OBJDIR)/classes/TextClass.o \ + $(OBJDIR)/classes/TitleClass.o \ + $(OBJDIR)/classes/TreeClass.o \ + $(OBJDIR)/classes/ULClass.o \ + $(OBJDIR)/AllocateColours.o \ + $(OBJDIR)/Animation.o \ + $(OBJDIR)/Classes.o \ + $(OBJDIR)/Colours.o \ + $(OBJDIR)/Config.o \ + $(OBJDIR)/Dispatcher.o \ + $(OBJDIR)/Entities.o \ + $(OBJDIR)/Forms.o \ + $(OBJDIR)/GetSetAttrs.o \ + $(OBJDIR)/HandleMUIEvent.o \ + $(OBJDIR)/IM_ColourManager.o \ + $(OBJDIR)/IM_Dither.o \ + $(OBJDIR)/IM_Output.o \ + $(OBJDIR)/IM_Render.o \ + $(OBJDIR)/IM_Scale.o \ + $(OBJDIR)/ImageManager.o \ + $(OBJDIR)/Layout.o \ + $(OBJDIR)/Mark.o \ + $(OBJDIR)/Memory.o \ + $(OBJDIR)/MinMax.o \ + $(OBJDIR)/ParseMessage.o \ + $(OBJDIR)/ParseThread.o \ + $(OBJDIR)/Render.o \ + $(OBJDIR)/ScanArgs.o \ + $(OBJDIR)/ScrollGroup.o \ + $(OBJDIR)/SharedData.o \ + $(OBJDIR)/TagInfo.o \ + $(OBJDIR)/TernaryTrees.o \ + $(OBJDIR)/URLHandling.o \ + $(OBJDIR)/StringClass.o \ + $(OBJDIR)/Debug.o + +ifeq ($(OS), os3) +MCCOBJS += \ + $(OBJDIR)/vastubs.o + +TESTOBJS += \ + $(OBJDIR)/vastubs.o +endif + +# available catalog translations +CATALOGS = $(LOCALE)/deutsch.catalog + +# + +all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(SIMPLETARGET) + +# make the object directories +$(OBJDIR): + @echo " MKDIR $@" + @$(MKDIR) $(OBJDIR) + @$(MKDIR) $(OBJDIR)/classes + +# make the binary directories +$(BINDIR): + @echo " MKDIR $@" + @$(MKDIR) $(BINDIR) + +# + +$(OBJDIR)/%.o: %.c + @echo " CC $<" + @$(CC) $(CFLAGS) $< -o $@ + +$(OBJDIR)/%.o: %.cpp + @echo " CXX $<" + @$(CXX) $(CFLAGS) $< -o $@ + +$(OBJDIR)/mccclass_68k.o: ../include/mccclass_68k.c + @echo " CC $<" + @$(CC) $(CFLAGS) $< -o $@ + +# + +# Link against all MCC objects EXCEPT library.o (globals conflict) +LIBOBJS = $(filter-out $(OBJDIR)/library.o $(OBJDIR)/crtclasses_begin.o $(OBJDIR)/crtclasses_end.o, $(MCCOBJS)) + + +$(SIMPLETARGET): $(OBJDIR)/SimpleTest.o + @echo " LD $@" + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) -ldebug -Wl,-Map,$@.map + @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug + + +$(MCCTARGET) : $(MCCOBJS) + @echo " LD $@" + @$(CXX) $(LDFLAGS) -o $@.debug $(MCCOBJS) $(LDLIBS) -Wl,-Map,$@.map + @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug + +$(OBJDIR)/library.o: library.c ../include/mccinit.c \ + HTMLview_mcc.h private.h rev.h + +locale.h: locale.c +locale.c: locale/HTMLview_mcc.cd locale_c.sd locale_h.sd + @echo " GN $@" + @$(FC) locale/HTMLview_mcc.cd locale.h=locale_h.sd locale.c=locale_c.sd + +## CATALOG GENERATION ################# + +$(LOCALE)/%.catalog: $(LOCALE)/%.ct + @echo " FC $@" + @$(FC) $(LOCALE)/HTMLview_mcc.cd $< CATALOG $@ + +.IGNORE: $(CATALOGS) + +catalogs: $(CATALOGS) + + +# + +.PHONY: clean +clean: + -$(RM) $(MCCTARGET) $(MCCTARGET).debug $(MCCTARGET).map + -$(RM) $(TESTTARGET) $(TESTTARGET).debug $(TESTTARGET).map + -$(RM) $(MCCOBJS) $(TESTOBJS) $(M68KSTUBS) + +.PHONY: distclean +distclean: clean + -$(RM) locale.? + -$(RMDIR) $(OBJDIR) + -$(RMDIR) $(BINDIR) diff --git a/mcc/Memory.cpp b/mcc/Memory.cpp index 967dee6..8e2f7ce 100644 --- a/mcc/Memory.cpp +++ b/mcc/Memory.cpp @@ -253,7 +253,10 @@ void FreeVecPooled(APTR pool, APTR mem) } #endif -CONSTRUCTOR(InitMem, 10) +extern "C" void kprintf(const char *fmt, ...); + +/* Called explicitly from _init() in ctor_dtor.c */ +extern "C" void InitMemoryPool(void) { #ifndef __MORPHOS__ #if defined(__amigaos4__) @@ -273,7 +276,8 @@ CONSTRUCTOR(InitMem, 10) #endif } -DESTRUCTOR(CleanupMem, 10) +/* Called explicitly from _fini() in ctor_dtor.c */ +extern "C" void CleanupMemoryPool(void) { if(MemoryPool) { diff --git a/mcc/SimpleTest.c b/mcc/SimpleTest.c new file mode 100644 index 0000000..1bfd74d --- /dev/null +++ b/mcc/SimpleTest.c @@ -0,0 +1,106 @@ + +#include +#include +#include +#include +#include +#include + +extern void kprintf(const char *fmt, ...); + +struct Library *MUIMasterBase; +struct IntuitionBase *IntuitionBase; +struct Library *UtilityBase; + +// Simple MUI macros if missing +#ifndef MUI_Set +#define MUI_Set(o,a,v) SetAttrs(o,a,v,TAG_DONE) +#endif +#ifndef MUI_GetVal +#define MUI_GetVal(o,a) ({ ULONG v; GetAttr(a,o,&v); v; }) +#endif + +int main(void) +{ + Object *app, *win, *html; + + IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39); + MUIMasterBase = OpenLibrary("muimaster.library", 19); + + if (!IntuitionBase || !MUIMasterBase) + { + kprintf("SimpleTest: Failed to open libraries\n"); + return 20; + } + + app = MUI_NewObject(MUIC_Application, + MUIA_Application_Title , "HTMLView Simple Test", + MUIA_Application_Version , "$VER: SimpleTest 1.0 (15.12.2025)", + MUIA_Application_SingleTask , TRUE, + MUIA_Application_Window , win = MUI_NewObject(MUIC_Window, + MUIA_Window_Title, "HTMLView Test Window", + MUIA_Window_RootObject, html = MUI_NewObject("HTMLview.mcc", + MUIA_Background, MUII_TextBack, + TAG_DONE), + TAG_DONE), + TAG_DONE); + + if (!app) + { + kprintf("SimpleTest: Failed to create Application object\n"); + } + else + { + SetAttrs(win, MUIA_Window_Open, TRUE, TAG_DONE); + + static const char *test_html = + "" + "

HTMLView Test

" + "

This is a bold paragraph.

" + "

This is an italic paragraph.

" + "

This is a link.

" + ""; + + // Correct ID for HTMLview contents + #define MUIA_HTMLview_Contents 0xad003005 + SetAttrs(html, MUIA_HTMLview_Contents, test_html, TAG_DONE); + + ULONG val = 0; + GetAttr(MUIA_Window_Open, win, &val); + if (val) + { + ULONG sigs = 0; + BOOL running = TRUE; + + DoMethod(win, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, + app, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit); + + while(running) + { + ULONG id = DoMethod(app, MUIM_Application_NewInput, &sigs); + + if (id == MUIV_Application_ReturnID_Quit) { + running = FALSE; + } + + if (running && sigs) { + ULONG got = Wait(sigs | SIGBREAKF_CTRL_C); + if (got & SIGBREAKF_CTRL_C) { + running = FALSE; + } + } + } + } + else + { + kprintf("SimpleTest: Window failed to open.\n"); + } + + MUI_DisposeObject(app); + } + + if (MUIMasterBase) CloseLibrary(MUIMasterBase); + if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase); + + return 0; +} diff --git a/mcc/TernaryTrees.cpp b/mcc/TernaryTrees.cpp index 452f6eb..53f895f 100644 --- a/mcc/TernaryTrees.cpp +++ b/mcc/TernaryTrees.cpp @@ -37,20 +37,18 @@ TNode::TNode(CONST_STRPTR str, CONST_APTR data) TNode::~TNode () { delete Left; - if(SplitChar) - delete Middle; delete Right; } -struct TNode *TNode::TInsert(CONST_STRPTR str, CONST_APTR data) +struct TNode *TNode::STInsert(struct TNode *root, CONST_STRPTR str, CONST_APTR data) { - if (this) + if (root) { - if (*str < SplitChar) Left = Left->TInsert(str, data); - else if (*str > SplitChar) Right = Right->TInsert(str, data); - else if (SplitChar) Middle = Middle->TInsert(str+1, data); + if (*str < root->SplitChar) root->Left = STInsert(root->Left, str, data); + else if (*str > root->SplitChar) root->Right = STInsert(root->Right, str, data); + else if (root->SplitChar) root->Middle = STInsert(root->Middle, str+1, data); - return this; + return root; } else { diff --git a/mcc/TernaryTrees.h b/mcc/TernaryTrees.h index 5453738..a37ed15 100644 --- a/mcc/TernaryTrees.h +++ b/mcc/TernaryTrees.h @@ -28,7 +28,8 @@ struct TNode TNode(CONST_STRPTR str, CONST_APTR data); virtual ~TNode (); - struct TNode *TInsert(CONST_STRPTR str, CONST_APTR data); + // Refactored to static to safely handle NULL root + static struct TNode *STInsert(struct TNode *root, CONST_STRPTR str, CONST_APTR data); struct TNode *Left, *Right; union @@ -47,7 +48,7 @@ ULONG BinaryInsert (struct TNode *&tree, T *elements, ULONG from, ULONG to) { ULONG diff = (to-from) / 2; - tree = tree->TInsert(elements[from+diff].Name, elements[from+diff].GetData()); + tree = TNode::STInsert(tree, elements[from+diff].Name, elements[from+diff].GetData()); if (tree==NULL) return FALSE; if(from < to) diff --git a/mcc/crtclasses_begin.c b/mcc/crtclasses_begin.c new file mode 100644 index 0000000..9ef7484 --- /dev/null +++ b/mcc/crtclasses_begin.c @@ -0,0 +1,11 @@ +#include + +typedef void (*func_ptr)(void); + +/* + * The head of the constructor list. + * Initialized to -1 to indicate the "New Format" (or count sentinel). + * Placed at the very beginning of the .ctors section. + */ +func_ptr __CTOR_LIST__[1] __attribute__((used, section(".ctors"))) = { (func_ptr) -1 }; +func_ptr __DTOR_LIST__[1] __attribute__((used, section(".dtors"))) = { (func_ptr) -1 }; diff --git a/mcc/crtclasses_end.c b/mcc/crtclasses_end.c new file mode 100644 index 0000000..9640b07 --- /dev/null +++ b/mcc/crtclasses_end.c @@ -0,0 +1,11 @@ +#include + +typedef void (*func_ptr)(void); + +/* + * The tail of the constructor list. + * Initialized to 0 (NULL terminator). + * Placed at the very end of the .ctors section. + */ +func_ptr __CTOR_END__[1] __attribute__((used, section(".ctors"))) = { (func_ptr) 0 }; +func_ptr __DTOR_END__[1] __attribute__((used, section(".dtors"))) = { (func_ptr) 0 }; diff --git a/mcc/ctor_dtor.c b/mcc/ctor_dtor.c index 1cda3ea..9a50360 100644 --- a/mcc/ctor_dtor.c +++ b/mcc/ctor_dtor.c @@ -22,8 +22,10 @@ // borrowed from clib2 to be able to intermix C and C++ code with the AmigaOS3 g++ 2.95.3 +#include #include #include +#include #include #if defined(__amigaos4__) @@ -43,119 +45,72 @@ typedef void (*func_ptr)(void); /****************************************************************************/ -struct private_function -{ - func_ptr function; - int priority; -}; - -/****************************************************************************/ +// libnix handles init/exit via these symbols, which might be functions or lists depending on the startup code. +// In this shared library environment with -nostartfiles, the linker script or libnix setup seems to provide +// a code thunk (trampoline) at __INIT_LIST__, not a list of pointers. +// Therefore, we must CALL it, not iterate it. -extern func_ptr __INIT_LIST__[]; -extern func_ptr __EXIT_LIST__[]; - -/****************************************************************************/ +extern void __INIT_LIST__(void); +extern void __INIT_LIST__(void); +extern void __EXIT_LIST__(void); extern func_ptr __CTOR_LIST__[]; extern func_ptr __DTOR_LIST__[]; -/****************************************************************************/ - -static void sort_private_functions(struct private_function * base, size_t count) -{ - struct private_function * a; - struct private_function * b; - size_t i,j; - i = count - 2; +// void exit(int s) { (void)s; while(1); } - do - { - a = base; - - for(j = 0 ; j <= i ; j++) - { - b = a + 1; - - if(a->priority > b->priority) - { - struct private_function t; - - t = (*a); - (*a) = (*b); - (*b) = t; - } - - a = b; - } - } - while(i-- > 0); -} /****************************************************************************/ -/* Sort all the init and exit functions (private library constructors), then - invoke the init functions in descending order. */ +/* Safe iteration of null-terminated INIT list */ static void call_init_functions(void) { - LONG num_init_functions = (LONG)(__INIT_LIST__[0]) / 2; - LONG num_exit_functions = (LONG)(__EXIT_LIST__[0]) / 2; - - if(num_init_functions > 1) - sort_private_functions((struct private_function *)&__INIT_LIST__[1],num_init_functions); - - if(num_exit_functions > 1) - sort_private_functions((struct private_function *)&__EXIT_LIST__[1],num_exit_functions); - - if(num_init_functions > 0) - { - struct private_function * t = (struct private_function *)&__INIT_LIST__[1]; - LONG i,j; - - for(j = 0 ; j < num_init_functions ; j++) - { - i = num_init_functions - (j + 1); - - (*t[i].function)(); - } - } + /* __INIT_LIST__ is not valid in this environment (points to VTable). */ + /* Do nothing. */ } /****************************************************************************/ -/* Call all exit functions in ascending order; this assumes that the function - table was prepared by call_init_functions() above. */ +/* Safe iteration of null-terminated EXIT list */ static void call_exit_functions(void) { - LONG num_exit_functions = (LONG)(__EXIT_LIST__[0]) / 2; - - if(num_exit_functions > 0) - { - STATIC LONG j = 0; - - struct private_function * t = (struct private_function *)&__EXIT_LIST__[1]; - LONG i; - - while(j++ < num_exit_functions) - { - i = j - 1; - - (*t[i].function)(); - } - } + /* __EXIT_LIST__ is legacy/stabs. Not used. */ + // __EXIT_LIST__(); } /****************************************************************************/ static void call_constructors(void) { - ULONG num_ctors = (ULONG)__CTOR_LIST__[0]; - ULONG i; - - /* Call all constructors in reverse order */ - for(i = 0 ; i < num_ctors ; i++) + /* Handle both count-prefixed (Old GCC) and -1 terminated (New GCC) formats */ + if ((long)__CTOR_LIST__[0] == -1) { - __CTOR_LIST__[num_ctors - i](); + /* New format: -1, p1, p2, ... 0. Walk to end, then backwards. */ + ULONG i = 1; + while (__CTOR_LIST__[i]) i++; + + + /* i is now at the 0 terminator. Run backwards until index 1. */ + while (i > 1) { + i--; + if (__CTOR_LIST__[i]) { + __CTOR_LIST__[i](); + } + } + } + else + { + /* Old format: count, p1, p2... */ + ULONG num_ctors = (ULONG)__CTOR_LIST__[0]; + ULONG i; + + + /* Call all constructors in reverse order */ + for(i = 0 ; i < num_ctors ; i++) + { + __CTOR_LIST__[num_ctors - i](); + } } } @@ -163,13 +118,29 @@ static void call_constructors(void) static void call_destructors(void) { - ULONG num_dtors = (ULONG)__DTOR_LIST__[0]; - static ULONG i; - - /* Call all destructors in forward order */ - while(i++ < num_dtors) + if ((long)__DTOR_LIST__[0] == -1) { - __DTOR_LIST__[i](); + /* New format: -1, p1, p2... 0. Run forwards? + Destructors usually run in reverse of constructors? + Standard is: Constructors run End->Start. Destructors run Start->End. + Let's assume Start->End (1..n) for dtors. + */ + ULONG i = 1; + while (__DTOR_LIST__[i]) { + __DTOR_LIST__[i](); + i++; + } + } + else + { + ULONG num_dtors = (ULONG)__DTOR_LIST__[0]; + static ULONG i; /* Start at 0 */ + + /* Call all destructors in forward order */ + while(i++ < num_dtors) + { + __DTOR_LIST__[i](); + } } } #endif @@ -186,6 +157,14 @@ void _init(void) for(j = 0 ; j < num_ctors ; j++) __CTOR_LIST__[num_ctors - j](); #elif defined(__amigaos3__) + + /* Initialize memory pool BEFORE calling C++ constructors + This prevents crashes in constructors that use new/malloc */ + { + extern void InitMemoryPool(void); + InitMemoryPool(); + } + call_init_functions(); call_constructors(); #elif defined(__MORPHOS__) @@ -209,6 +188,12 @@ void _fini(void) #elif defined(__amigaos3__) call_destructors(); call_exit_functions(); + + /* Cleanup memory pool AFTER all C++ destructors run */ + { + extern void CleanupMemoryPool(void); + CleanupMemoryPool(); + } #elif defined(__MORPHOS__) run_destructors(); #endif diff --git a/mcc/library.c b/mcc/library.c index cb751a2..e992bbf 100644 --- a/mcc/library.c +++ b/mcc/library.c @@ -50,7 +50,7 @@ #define CLASSINIT #define CLASSEXPUNGE -#define MIN_STACKSIZE 8192 +//#define MIN_STACKSIZE 8192 struct Library *LayersBase = NULL; struct Library *KeymapBase = NULL; @@ -77,6 +77,9 @@ extern void _init(void); extern void _fini(void); extern ULONG GetHTMLviewDataSize(void); +/* Dummy exit for libnix linkage in shared library. Application links against standard CRT. */ +void exit(int s) { (void)s; while(1); } + CPPDISPATCHERGATE(_Dispatcher); CPPDISPATCHERGATE(ScrollGroupDispatcher); #ifdef USEMUISTRINGS diff --git a/mcc/private.h b/mcc/private.h index 6e60e87..e913617 100644 --- a/mcc/private.h +++ b/mcc/private.h @@ -23,6 +23,15 @@ #ifndef PRIVATE_H #define PRIVATE_H +/* Unconditional C-linkage declaration for debug logging in constructors */ +#if defined(__cplusplus) +extern "C" { +#endif +void kprintf(const char *fmt, ...); +#if defined(__cplusplus) +} +#endif + #ifndef _PROTO_INTUITION_H #include #endif @@ -186,17 +195,25 @@ struct HTMLviewData #else + + +/* Force pointers into .ctors section for our manual scan in ctor_dtor.c. + Add __saveds because these are called via pointer from _init/_fini and need global data access. + Wrap with logging to identify crashes. */ #define CONSTRUCTOR(name,pri) \ - asm(".stabs \"___INIT_LIST__\",22,0,0,___ctor_" #name); \ - asm(".stabs \"___INIT_LIST__\",20,0,0," #pri); \ - extern "C" VOID __ctor_##name##(VOID); \ - extern "C" VOID __ctor_##name##(VOID) + static void __real_ctor_##name(void); \ + static void __ctor_##name(void) __attribute__((saveds)); \ + static void (*__ptr_##name)(void) __attribute__((used, section(".ctors"))) = __ctor_##name; \ + static void __ctor_##name(void) { \ + __real_ctor_##name(); \ + } \ + static void __real_ctor_##name(void) + #define DESTRUCTOR(name,pri) \ - asm(".stabs \"___EXIT_LIST__\",22,0,0,___dtor_" #name); \ - asm(".stabs \"___EXIT_LIST__\",20,0,0," #pri); \ - extern "C" VOID __dtor_##name##(VOID); \ - extern "C" VOID __dtor_##name##(VOID) + static void __dtor_##name(void) __attribute__((saveds)); \ + static void (*__ptr_##name)(void) __attribute__((used, section(".dtors"))) = __dtor_##name; \ + static void __dtor_##name(void) #endif /* __amigaos4__ */ diff --git a/mcc/rev.h b/mcc/rev.h index 8e224d6..b653f9a 100644 --- a/mcc/rev.h +++ b/mcc/rev.h @@ -21,10 +21,10 @@ ***************************************************************************/ #define LIB_VERSION 13 -#define LIB_REVISION 4 +#define LIB_REVISION 5 -#define LIB_REV_STRING "13.4" -#define LIB_DATE "20.12.2007" +#define LIB_REV_STRING "13.5" +#define LIB_DATE "15.12.2025" #if defined(__PPC__) #if defined(__MORPHOS__) @@ -44,4 +44,4 @@ #define CPU "" #endif -#define LIB_COPYRIGHT "Copyright (C) 2005-2007 HTMLview.mcc Open Source Team" +#define LIB_COPYRIGHT "Copyright (C) 2005-2025 HTMLview.mcc Open Source Team" diff --git a/mcp/Makefile b/mcp/Makefile index b7f0580..3fefa87 100644 --- a/mcp/Makefile +++ b/mcp/Makefile @@ -134,11 +134,19 @@ PREFTARGET= $(BINDIR)/HTMLview-Prefs # Common compiler/linker flags WARN = -W -Wall -Wwrite-strings -OPTFLAGS = -O3 -fomit-frame-pointer -funroll-loops -DEBUG = -DDEBUG -O0 -DEBUGSYM = -gstabs -CFLAGS = -I. -I../mcc -I../include $(CPU) $(WARN) $(OPTFLAGS) \ - $(DEBUG) $(DEBUGSYM) -c +OPTFLAGS = -O3 -fomit-frame-pointer +DEBUG = +DEBUGSYM = + +ifneq ($(DEBUG),) + OPTFLAGS = -O0 + DEBUGSYM = -gstabs + CFLAGS = -I. -I../mcc -I../include $(CPU) $(WARN) $(OPTFLAGS) \ + -DDEBUG $(DEBUGSYM) -c +else + CFLAGS = -I. -I../mcc -I../include $(CPU) $(WARN) $(OPTFLAGS) \ + -DNDEBUG -c +endif LDFLAGS = $(CPU) $(DEBUGSYM) LDLIBS = -lm @@ -178,7 +186,10 @@ ifeq ($(OS), os3) CPU = -m68020-60 -msoft-float CFLAGS += -noixemul -DNO_INLINE_STDARG -DUSEHOTKEY -DUSEBETTERSTRING LDFLAGS += -noixemul - LDLIBS += -ldebug -lmui + LDLIBS += -lmui +ifneq ($(DEBUG),) + LDLIBS += -ldebug +endif else ifeq ($(OS), mos) @@ -195,7 +206,10 @@ ifeq ($(OS), mos) CPU = -mcpu=powerpc CFLAGS += -noixemul -DNO_PPCINLINE_STDARG LDFLAGS += -noixemul - LDLIBS += -ldebug + ifneq ($(DEBUG),) + LDLIBS += -ldebug + endif + else ifeq ($(OS), aros) @@ -235,7 +249,7 @@ PREFOBJS= $(OBJDIR)/locale.o \ $(OBJDIR)/Debug.o ifeq ($(OS), os3) -MCPOBJS += $(OBJDIR)/vastubs.o +MCPOBJS += $(OBJDIR)/vastubs.o $(OBJDIR)/exit_stub.o PREFOBJS += $(OBJDIR)/vastubs.o endif diff --git a/mcp/exit_stub.c b/mcp/exit_stub.c new file mode 100644 index 0000000..286e778 --- /dev/null +++ b/mcp/exit_stub.c @@ -0,0 +1,9 @@ +/* + * Dummy exit() to satisfy libnix linkage when -nostartfiles is used. + * We're a shared library/MUI class, we should never call exit(). + */ +void exit(int status) +{ + (void)status; + while(1); +} diff --git a/mcp/library.c b/mcp/library.c index 24d671e..6d0e949 100644 --- a/mcp/library.c +++ b/mcp/library.c @@ -45,6 +45,8 @@ #define SUPERCLASSP MUIC_Mccprefs #define INSTDATAP InstData_MCP +#define INSTDATAPSIZE sizeof(struct InstData_MCP) +#define gate__DispatcherP _DispatcherP #define USERLIBID CLASS " " LIB_REV_STRING CPU " (" LIB_DATE ") " LIB_COPYRIGHT #define MASTERVERSION 19 diff --git a/mcp/private.h b/mcp/private.h index d794747..70ce591 100644 --- a/mcp/private.h +++ b/mcp/private.h @@ -27,6 +27,18 @@ #include +#ifndef INSTDATAPSIZE +#define INSTDATAPSIZE sizeof(struct InstData_MCP) +#endif + +#ifndef SUPERCLASSP +#define SUPERCLASSP "Mccprefs.mcc" +#endif + +#ifndef gate__DispatcherP +#define gate__DispatcherP _DispatcherP +#endif + #ifdef __MORPHOS__ extern struct MUI_CustomClass *PicClass; #define PREFSIMAGEOBJECT \ diff --git a/mcp/rev.h b/mcp/rev.h index 8e224d6..b653f9a 100644 --- a/mcp/rev.h +++ b/mcp/rev.h @@ -21,10 +21,10 @@ ***************************************************************************/ #define LIB_VERSION 13 -#define LIB_REVISION 4 +#define LIB_REVISION 5 -#define LIB_REV_STRING "13.4" -#define LIB_DATE "20.12.2007" +#define LIB_REV_STRING "13.5" +#define LIB_DATE "15.12.2025" #if defined(__PPC__) #if defined(__MORPHOS__) @@ -44,4 +44,4 @@ #define CPU "" #endif -#define LIB_COPYRIGHT "Copyright (C) 2005-2007 HTMLview.mcc Open Source Team" +#define LIB_COPYRIGHT "Copyright (C) 2005-2025 HTMLview.mcc Open Source Team" diff --git a/mcp/vastubs.c b/mcp/vastubs.c index 811cb3a..7be51ba 100644 --- a/mcp/vastubs.c +++ b/mcp/vastubs.c @@ -1,3 +1,4 @@ + /*************************************************************************** HTMLview.mcc - HTMLview MUI Custom Class @@ -43,3 +44,19 @@ ULONG SetAttrs( APTR object, ULONG tag1, ... ) #error "VARGS stubs are only save on m68k systems!" #endif #endif + +#if defined(__amigaos3__) +// Added stubs for libnix/C++ init +// exit() is provided by exit_stub.o +void _init(void) { } +void _fini(void) { } + +#include +#include +void kprintf(const char *formatString, ...) +{ + // Stub for mcp: do nothing. + // We only need logs from the main mcc library. + (void)formatString; +} +#endif From 3a44a5d2f7100aacbfa53bc42068a4f5132a0458 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 14:00:49 +0200 Subject: [PATCH 04/32] fix: additional sacredbanana/amiga-compiler build compatibility fixes - Fix UtilityBase type conflict with SDK (use struct Library *) - Replace CPPDISPATCHERENTRY with ENTRY for dispatcher gates - Remove conflicting function stubs (SDK provides GetRPAttrs, etc.) - Replace strlcpy/stccpy with portable strncpy for AppMessage code These fixes enable successful compilation with sacredbanana/amiga-compiler:m68k-amigaos (GCC 6.5.0b) --- include/mccinit.c | 8 ++++---- mcc/library.c | 4 ++-- mcc/vastubs.c | 16 ++++++---------- mcp/Dispatcher.c | 6 +----- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/include/mccinit.c b/include/mccinit.c index bd5b36c..7f41b26 100644 --- a/include/mccinit.c +++ b/include/mccinit.c @@ -184,7 +184,7 @@ struct Interface *INewlib = NULL; #else struct Library *MUIMasterBase = NULL; struct ExecBase *SysBase = NULL; -struct UtilityBase *UtilityBase = NULL; +struct Library *UtilityBase = NULL; struct DosLibrary *DOSBase = NULL; struct GfxBase *GfxBase = NULL; struct IntuitionBase *IntuitionBase = NULL; @@ -647,7 +647,7 @@ static ULONG mccLibInit(struct LibraryHeader *base) if((DOSBase = (struct DosLibrary*)OpenLibrary("dos.library", 36)) && (GfxBase = (struct GfxBase*)OpenLibrary("graphics.library", 36)) && (IntuitionBase = (struct IntuitionBase*)OpenLibrary("intuition.library", 36)) && - (UtilityBase = (struct UtilityBase*)OpenLibrary("utility.library", 36))) + (UtilityBase = OpenLibrary("utility.library", 36))) #endif { // we have to please the internal utilitybase @@ -676,12 +676,12 @@ static ULONG mccLibInit(struct LibraryHeader *base) #endif { #ifdef SUPERCLASS - ThisClass = MUI_CreateCustomClass(&base->lh_Library, (STRPTR)SUPERCLASS, NULL, INSTDATASIZE, CPPDISPATCHERENTRY(_Dispatcher)); + ThisClass = MUI_CreateCustomClass(&base->lh_Library, (STRPTR)SUPERCLASS, NULL, INSTDATASIZE, ENTRY(_Dispatcher)); if(ThisClass) #endif { #ifdef SUPERCLASSP - if((ThisClassP = MUI_CreateCustomClass(&base->lh_Library, (STRPTR)SUPERCLASSP, NULL, INSTDATAPSIZE, CPPDISPATCHERENTRY(_DispatcherP)))) + if((ThisClassP = MUI_CreateCustomClass(&base->lh_Library, (STRPTR)SUPERCLASSP, NULL, INSTDATAPSIZE, ENTRY(_DispatcherP)))) #endif { #ifdef SUPERCLASS diff --git a/mcc/library.c b/mcc/library.c index e992bbf..a84b70b 100644 --- a/mcc/library.c +++ b/mcc/library.c @@ -120,10 +120,10 @@ static BOOL ClassInit(UNUSED struct Library *base) if((CyberGfxBase = OpenLibrary("cybergraphics.library", 40)) && GETINTERFACE(ICyberGfx, struct CyberGfxIFace*, CyberGfxBase)) { - if((ScrollGroupClass = MUI_CreateCustomClass(NULL, MUIC_Virtgroup, NULL, GetScrollGroupDataSize(), CPPDISPATCHERENTRY(ScrollGroupDispatcher)))) + if((ScrollGroupClass = MUI_CreateCustomClass(NULL, MUIC_Virtgroup, NULL, GetScrollGroupDataSize(), ENTRY(ScrollGroupDispatcher)))) { #ifdef USEMUISTRINGS - if((StringClass = MUI_CreateCustomClass(NULL, MUIC_String, NULL, sizeof(struct StringData), CPPDISPATCHERENTRY(StringDispatcher)))) + if((StringClass = MUI_CreateCustomClass(NULL, MUIC_String, NULL, sizeof(struct StringData), ENTRY(StringDispatcher)))) #endif { RETURN(TRUE); diff --git a/mcc/vastubs.c b/mcc/vastubs.c index a976304..2d37a78 100644 --- a/mcc/vastubs.c +++ b/mcc/vastubs.c @@ -64,18 +64,14 @@ APTR NewObject( struct IClass *classPtr, CONST_STRPTR classID, Tag tag1, ... ) { return NewObjectA(classPtr, classID, (struct TagItem *)&tag1); } #include -LONG ObtainBestPen( struct ColorMap *cm, ULONG r, ULONG g, ULONG b, Tag tag1, ... ) -{ return ObtainBestPenA(cm, r, g, b, (struct TagItem *)&tag1); } -VOID GetRPAttrs( CONST struct RastPort *rp, ULONG tag1Type, ... ) -{ GetRPAttrsA(rp, (struct TagItem *)&tag1Type); } +// GetRPAttrs is already in SDK, no stub needed +//#ifndef GetRPAttrs +//LONG ObtainBestPen( struct ColorMap *cm, ULONG r, ULONG g, ULONG b, Tag tag1, ... ) +//{ return ObtainBestPenA(cm, r, g, b, (struct TagItem *)&tag1); } +//#endif #include -Object *NewDTObject( APTR name, Tag tag1, ... ) -{ return NewDTObjectA(name, (struct TagItem *)&tag1); } -ULONG SetDTAttrs( Object *o, struct Window *win, struct Requester *req, Tag tag1, ... ) -{ return SetDTAttrsA(o, win, req, (struct TagItem *)&tag1); } -ULONG GetDTAttrs( Object *o, Tag tag1, ... ) -{ return GetDTAttrsA(o, (struct TagItem *)&tag1); } +// NewDTObject, SetDTAttrs, GetDTAttrs already in SDK, no stubs needed #else #error "VARGS stubs are only save on m68k systems!" diff --git a/mcp/Dispatcher.c b/mcp/Dispatcher.c index 40678ed..24fe329 100644 --- a/mcp/Dispatcher.c +++ b/mcp/Dispatcher.c @@ -80,11 +80,7 @@ HOOKPROTONH(AppMessageCode, VOID, Object* htmlview, struct MUIP_AppMessage *args { char dir[256+8]; - #if defined(__MORPHOS__) - stccpy(dir, "file://", sizeof(dir)); - #else - strlcpy(dir, "file://", sizeof(dir)); - #endif + strncpy(dir, "file://", 8); dir[8] = '\0'; if(NameFromLock(args->appmsg->am_ArgList[0].wa_Lock, dir+7, 256)) { From c00486c25e50f0c204e136425c066f9bde819fb4 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 14:01:23 +0200 Subject: [PATCH 05/32] ci: update docker image to sacredbanana/amiga-compiler:m68k-amigaos Updates GitHub Actions CI to use the modern sacredbanana compiler image which has better compatibility with GCC 6.x builds. --- .github/workflows/makefile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 031bb46..a2b92cb 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -16,7 +16,7 @@ jobs: - name: Run the build process with Docker uses: addnab/docker-run-action@v3 with: - image: amigadev/crosstools:m68k-amigaos + image: sacredbanana/amiga-compiler:m68k-amigaos options: -v ${{ github.workspace }}:/work run: | cd mcc From 6e678e456774e2ccd3e2fe1ca1e927e41d7e9a3e Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 14:03:43 +0200 Subject: [PATCH 06/32] fix: remove early return in MUIM_Backfill handler for MUI4 compatibility The early return when bounds didn't match exactly caused MUI4 to show its checkerboard pattern instead of filled content. This fix removes the conditional early return and ensures MUI4 always receives a properly filled background region. Fixes issue where a blue-checkered pattern appeared on MUI4/OS3 instead of HTML content. --- mcc/Dispatcher.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/mcc/Dispatcher.cpp b/mcc/Dispatcher.cpp index ddd9e02..4475383 100644 --- a/mcc/Dispatcher.cpp +++ b/mcc/Dispatcher.cpp @@ -578,14 +578,6 @@ CPPDISPATCHER(_Dispatcher) b = bmsg->bottom; if (l>r || t>b) return 0; - - if (!(l==_mleft(obj) && t==_mtop(obj) && r==_mright(obj) && b==_mbottom(obj))) - { - D(DBF_ALWAYS, "HTMLview: %ld %ld, %ld %ld, %ld %ld, %ld %ld", - l,_mleft(obj),t,_mtop(obj),r,_mright(obj),b,_mbottom(obj)); - - return 0; - } } struct RastPort *rp = _rp(obj); @@ -1011,7 +1003,7 @@ CPPDISPATCHER(_Dispatcher) STRPTR url = new (std::nothrow) char[strlen(pmsg->URL)+20]; if (!url) return 0; - sprintf(url, "%s?{%lu}¿", pmsg->URL, data->PageID); + sprintf(url, "%s?{%lu}�", pmsg->URL, data->PageID); SetAttrs(data->Share->Obj, MUIA_HTMLview_ClickedURL, (ULONG)pmsg->URL, From 4ca8a1755dbe1ea2690cc472c00dd7c0109061c0 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 14:05:50 +0200 Subject: [PATCH 07/32] ci: modernize workflow with latest actions and simplified docker usage - Use docker:// syntax instead of addnab/docker-run-action - Update actions/checkout to v4, upload-artifact to v4 - Add concurrency control to cancel redundant builds - Simplify and clean up workflow structure - Use glob patterns for artifact paths --- .github/workflows/makefile.yml | 70 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index a2b92cb..5aa2a31 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -1,34 +1,36 @@ -name: Makefile CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - build-os3: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Run the build process with Docker - uses: addnab/docker-run-action@v3 - with: - image: sacredbanana/amiga-compiler:m68k-amigaos - options: -v ${{ github.workspace }}:/work - run: | - cd mcc - make OS=os3 DEBUG= - cd ../mcp - make OS=os3 DEBUG= - - - uses: actions/upload-artifact@v3 - with: - name: HTMLview_os3 - path: | - bin_os3/HTMLview.mcp - bin_os3/HTMLview-Prefs - bin_os3/HTMLview.mcc - bin_os3/HTMLview-Test +name: Build + +on: + push: + branches: [master] + pull_request: + branches: [master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Build mcc + uses: docker://sacredbanana/amiga-compiler:m68k-amigaos + with: + args: sh -c "cd mcc && make OS=os3 DEBUG=" + + - name: Build mcp + uses: docker://sacredbanana/amiga-compiler:m68k-amigaos + with: + args: sh -c "cd mcp && make OS=os3 DEBUG=" + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: htmlview-os3 + path: | + mcc/bin_os3/*.mcc + mcp/bin_os3/*.mcp From 6cf90491e737a24125256f64443ef09175ed75f2 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 14:08:01 +0200 Subject: [PATCH 08/32] ci: update actions to v5 (Node.js 22) --- .github/workflows/makefile.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 5aa2a31..454aeb7 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build mcc uses: docker://sacredbanana/amiga-compiler:m68k-amigaos @@ -28,7 +28,7 @@ jobs: args: sh -c "cd mcp && make OS=os3 DEBUG=" - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: htmlview-os3 path: | From 668c9dbdb3ed6d82a3570a2fad993f09c61c8a77 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 14:34:23 +0200 Subject: [PATCH 09/32] fix: critical dispatcher and memory fixes - Revert ENTRY to CPPDISPATCHERENTRY for _Dispatcher/_DispatcherP (C++ dispatchers need C gate wrapper for proper register handling) - Fix heap corruption: delete[] instead of delete for array allocations in ImageCacheEntry destructor These fixes enable HTMLview.mcc to work correctly on OS3. --- include/mccinit.c | 4 ++-- mcc/_ImageDecoder.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/mccinit.c b/include/mccinit.c index 7f41b26..57e6cd9 100644 --- a/include/mccinit.c +++ b/include/mccinit.c @@ -676,12 +676,12 @@ static ULONG mccLibInit(struct LibraryHeader *base) #endif { #ifdef SUPERCLASS - ThisClass = MUI_CreateCustomClass(&base->lh_Library, (STRPTR)SUPERCLASS, NULL, INSTDATASIZE, ENTRY(_Dispatcher)); + ThisClass = MUI_CreateCustomClass(&base->lh_Library, (STRPTR)SUPERCLASS, NULL, INSTDATASIZE, CPPDISPATCHERENTRY(_Dispatcher)); if(ThisClass) #endif { #ifdef SUPERCLASSP - if((ThisClassP = MUI_CreateCustomClass(&base->lh_Library, (STRPTR)SUPERCLASSP, NULL, INSTDATAPSIZE, ENTRY(_DispatcherP)))) + if((ThisClassP = MUI_CreateCustomClass(&base->lh_Library, (STRPTR)SUPERCLASSP, NULL, INSTDATAPSIZE, CPPDISPATCHERENTRY(_DispatcherP)))) #endif { #ifdef SUPERCLASS diff --git a/mcc/_ImageDecoder.c b/mcc/_ImageDecoder.c index 9e1f7c0..33920a8 100644 --- a/mcc/_ImageDecoder.c +++ b/mcc/_ImageDecoder.c @@ -49,8 +49,8 @@ ImageCacheEntry::~ImageCacheEntry () /* if(Flags & MUIF_HTMLview_LoadImages_DeleteAfterUse) DeleteFile(Filename+7); */ - delete URL; - delete Filename; + delete[] URL; + delete[] Filename; } VOID DisposeImages (struct ImageCacheEntry *current) From 4520c1cc500e02468aedac4e3b2253f4ebc96646 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 14:44:02 +0200 Subject: [PATCH 10/32] chore: CI improvements and bug fixes - CI: add tag-based release trigger (v*) with automatic artifact uploads - Fix: buffer size missing null terminator in Dispatcher.cpp - Fix: delete[] mismatch in ImageManager.cpp destructor --- .github/workflows/makefile.yml | 15 ++++++++++++++- mcc/Dispatcher.cpp | 2 +- mcc/ImageManager.cpp | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 454aeb7..62c14dd 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -3,6 +3,8 @@ name: Build on: push: branches: [master] + tags: + - 'v*' pull_request: branches: [master] @@ -30,7 +32,18 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v5 with: - name: htmlview-os3 + name: htmlview-os3-${{ github.sha }} path: | mcc/bin_os3/*.mcc mcp/bin_os3/*.mcp + + - name: Create Release + if: startsWith(github.ref, 'refs/tags/v') + uses: softprops/action-gh-release@v2 + with: + files: | + mcc/bin_os3/*.mcc + mcp/bin_os3/*.mcp + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/mcc/Dispatcher.cpp b/mcc/Dispatcher.cpp index 4475383..1219e83 100644 --- a/mcc/Dispatcher.cpp +++ b/mcc/Dispatcher.cpp @@ -964,7 +964,7 @@ CPPDISPATCHER(_Dispatcher) if(!(data->Flags & FLG_HostObjNotUsed)) { delete data->Local; - data->Local = new (std::nothrow) char[strlen(url+baselen+pagelen)]; + data->Local = new (std::nothrow) char[strlen(url+baselen+pagelen)+1]; if (!data->Local) return 0; strcpy(data->Local, url+baselen+pagelen+1); } diff --git a/mcc/ImageManager.cpp b/mcc/ImageManager.cpp index a41e060..952cc57 100644 --- a/mcc/ImageManager.cpp +++ b/mcc/ImageManager.cpp @@ -84,7 +84,7 @@ ImageCacheItem::ImageCacheItem (STRPTR url, struct PictureFrame *pic) ImageCacheItem::~ImageCacheItem () { Picture->UnLockPicture(); - delete URL; + delete[] URL; } ImageCache::ImageCache (ULONG maxsize) From fc4b91769785ae273227450b0c5fde1d420dd9fc Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 14:46:20 +0200 Subject: [PATCH 11/32] ci: add multi-platform builds (OS3, OS4, MorphOS) and release workflow Builds HTMLview for all supported platforms using sacredbanana docker images: - AmigaOS 3.x: m68k-amigaos - AmigaOS 4.x: ppc-amigaos - MorphOS: ppc-morphos Tag v* triggers release with all platform binaries attached. --- .github/workflows/makefile.yml | 67 ++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 62c14dd..3360fe7 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -13,22 +13,18 @@ concurrency: cancel-in-progress: true jobs: - build: + build-os3: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v5 - - name: Build mcc uses: docker://sacredbanana/amiga-compiler:m68k-amigaos with: - args: sh -c "cd mcc && make OS=os3 DEBUG=" - + args: sh -c "cd mcc && make clean && make OS=os3" - name: Build mcp uses: docker://sacredbanana/amiga-compiler:m68k-amigaos with: - args: sh -c "cd mcp && make OS=os3 DEBUG=" - + args: sh -c "cd mcp && make clean && make OS=os3" - name: Upload artifacts uses: actions/upload-artifact@v5 with: @@ -37,13 +33,62 @@ jobs: mcc/bin_os3/*.mcc mcp/bin_os3/*.mcp + build-os4: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build mcc + uses: docker://sacredbanana/amiga-compiler:ppc-amigaos + with: + args: sh -c "cd mcc && make clean && make OS=os4" + - name: Build mcp + uses: docker://sacredbanana/amiga-compiler:ppc-amigaos + with: + args: sh -c "cd mcp && make clean && make OS=os4" + - name: Upload artifacts + uses: actions/upload-artifact@v5 + with: + name: htmlview-os4-${{ github.sha }} + path: | + mcc/bin_os4/*.mcc + mcp/bin_os4/*.mcp + + build-morphos: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build mcc + uses: docker://sacredbanana/amiga-compiler:ppc-morphos + with: + args: sh -c "cd mcc && make clean && make OS=mos" + - name: Build mcp + uses: docker://sacredbanana/amiga-compiler:ppc-morphos + with: + args: sh -c "cd mcp && make clean && make OS=mos" + - name: Upload artifacts + uses: actions/upload-artifact@v5 + with: + name: htmlview-morphos-${{ github.sha }} + path: | + mcc/bin_mos/*.mcc + mcp/bin_mos/*.mcp + + release: + needs: [build-os3, build-os4, build-morphos] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/checkout@v5 + - name: Download all artifacts + uses: actions/download-artifact@v5 + with: + pattern: htmlview-* + path: artifacts/ + merge-multiple: true - name: Create Release - if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v2 with: - files: | - mcc/bin_os3/*.mcc - mcp/bin_os3/*.mcp + files: artifacts/* generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b39f966076cd9ff6e987f5ded27f2d46c6e5b642 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 16:43:41 +0200 Subject: [PATCH 12/32] fix: enable all three platform builds (OS3, OS4, MorphOS) SDI_hook.h: add MorphOS CPP dispatcher macros, fix extern linkage SDI_hook.h: fix __amigaos4__ typo for OS4 General.h: uppercase MIN/MAX to avoid C++ std::min/max conflicts Dispatcher.cpp: use ParseThread directly (not ENTRY macro) Closes #3 --- .github/workflows/makefile.yml | 8 ++++---- include/SDI_hook.h | 12 +++++++++++- mcc/Dispatcher.cpp | 2 +- mcc/General.h | 5 +++-- mcc/Makefile | 11 ++++++++--- mcc/Memory.cpp | 8 ++++---- mcc/ParseThread.cpp | 2 +- mcc/ScrollGroup.cpp | 2 +- mcc/ScrollGroup.h | 2 +- mcc/SimpleTest.c | 4 ++++ mcc/classes/AreaClass.cpp | 4 ++-- mcc/classes/BackFillClass.cpp | 4 ++-- mcc/classes/BodyClass.cpp | 2 +- mcc/classes/FrameClass.cpp | 2 +- mcc/classes/FramesetClass.cpp | 4 ++-- mcc/classes/HRClass.cpp | 2 +- mcc/classes/HostClass.cpp | 2 +- mcc/classes/ImgClass.cpp | 4 ++-- mcc/classes/InputClass.cpp | 6 +++--- mcc/classes/IsIndexClass.cpp | 4 ++-- mcc/classes/LIClass.cpp | 4 ++-- mcc/classes/SelectClass.cpp | 4 ++-- mcc/classes/TDClass.cpp | 10 +++++----- mcc/classes/TRClass.cpp | 10 +++++----- mcc/classes/TableClass.cpp | 24 ++++++++++++------------ mcc/classes/TextAreaClass.cpp | 4 ++-- mcc/classes/TextClass.cpp | 12 ++++++------ mcc/classes/TreeClass.cpp | 8 ++++---- mcc/libnix.c | 2 +- mcc/library.c | 3 +++ 30 files changed, 97 insertions(+), 74 deletions(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 3360fe7..73bb9f6 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -26,7 +26,7 @@ jobs: with: args: sh -c "cd mcp && make clean && make OS=os3" - name: Upload artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: htmlview-os3-${{ github.sha }} path: | @@ -46,7 +46,7 @@ jobs: with: args: sh -c "cd mcp && make clean && make OS=os4" - name: Upload artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: htmlview-os4-${{ github.sha }} path: | @@ -66,7 +66,7 @@ jobs: with: args: sh -c "cd mcp && make clean && make OS=mos" - name: Upload artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: htmlview-morphos-${{ github.sha }} path: | @@ -80,7 +80,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Download all artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v7 with: pattern: htmlview-* path: artifacts/ diff --git a/include/SDI_hook.h b/include/SDI_hook.h index 2109b48..17960b0 100644 --- a/include/SDI_hook.h +++ b/include/SDI_hook.h @@ -180,6 +180,9 @@ {{NULL, NULL}, (HOOKFUNC)HookEntry, (HOOKFUNC)funcname, (APTR)data} #define MakeStaticHook(hookname, funcname) static struct Hook hookname = \ {{NULL, NULL}, (HOOKFUNC)HookEntry, (HOOKFUNC)funcname, NULL} + #define MakeCppHook(hookname, funcname) MakeHook(hookname, funcname) + #define MakeCppHookWithData(hookname, funcname, data) MakeHookWithData(hookname, funcname, data) + #define MakeStaticCppHook(hookname, funcname) MakeStaticHook(hookname, funcname) #define DISPATCHERPROTO(name) ULONG name(struct IClass * cl, Object * obj, \ Msg msg); \ extern const struct SDI_EmulLibEntry Gate_##name @@ -188,6 +191,7 @@ ULONG name(struct IClass * cl, Object * obj, Msg msg); \ static ULONG Trampoline_##name(void) {return name((struct IClass *) \ REG_A0, (Object *) REG_A2, (Msg) REG_A1);} \ + extern const struct SDI_EmulLibEntry Gate_##name; \ const struct SDI_EmulLibEntry Gate_##name = {SDI_TRAP_LIB, 0, \ (APTR) Trampoline_##name}; \ ULONG name(struct IClass * cl, Object * obj, Msg msg) @@ -247,6 +251,12 @@ static STDARGS SAVEDS void name(type1 param1, type2 param2) #define ENTRY(func) (APTR)&Gate_##func + // MorphOS-specific CPP dispatcher macros + #define CPPDISPATCHERPROTO(name) DISPATCHERPROTO(name) + #define CPPDISPATCHER(name) DISPATCHER(name) + #define CPPDISPATCHERENTRY(name) ENTRY(name) + #define CPPDISPATCHERGATE(name) /* no-op for MorphOS */ + #elif defined(__AROS__) #include @@ -326,7 +336,7 @@ static STDARGS SAVEDS void name(type1 param1, type2 param2) #define ENTRY(func) (APTR)func - #if defined(__amigaos4_) + #if defined(__amigaos4__) #define CPPDISPATCHERPROTO(name) DISPATCHERPROTO(name) #define CPPDISPATCHER(name) DISPATCHERPROTO(name) #define CPPDISPATCHERENTRY(name) ENTRY(name) diff --git a/mcc/Dispatcher.cpp b/mcc/Dispatcher.cpp index 1219e83..ba10e63 100644 --- a/mcc/Dispatcher.cpp +++ b/mcc/Dispatcher.cpp @@ -1033,7 +1033,7 @@ CPPDISPATCHER(_Dispatcher) sprintf(str_args, "%lx", (ULONG)args);*/ data->ParseThread = CreateNewProcTags( - NP_Entry, (ULONG)ENTRY(ParseThread), + NP_Entry, (ULONG)ParseThread, NP_Name, (ULONG)data->ParseThreadName, #if !defined(__MORPHOS__) NP_StackSize, 512*1024, diff --git a/mcc/General.h b/mcc/General.h index 4420729..3d45b2f 100644 --- a/mcc/General.h +++ b/mcc/General.h @@ -41,8 +41,9 @@ VOID FreeConfig (struct IClass *cl, Object *obj, struct HTMLviewData *data); VOID ObtainShineShadowPens (struct ColorMap *cmap, ULONG rgb, LONG &shine, LONG &shadow); VOID BltMaskRPort (struct BitMap *source, WORD srcLeft,WORD srcTop, struct RastPort *destination, WORD dstLeft,WORD dstTop,WORD dstWidth,WORD dstHeight, UBYTE *maskPlane); -#define min(a, b) (a < b ? a : b) -#define max(a, b) (a > b ? a : b) +// MIN/MAX macros - uppercase to avoid conflict with C++ std::min/std::max +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) /// xget() // Gets an attribute value from a MUI object diff --git a/mcc/Makefile b/mcc/Makefile index ff5a974..c5c6108 100644 --- a/mcc/Makefile +++ b/mcc/Makefile @@ -168,8 +168,9 @@ ifeq ($(OS), os4) CPU = -mcpu=powerpc WARN += -Wno-unused-parameter -Wno-narrowing -Wno-sign-compare CFLAGS += -mcrt=$(CRT) -D__USE_INLINE__ -D__NEW_TIMEVAL_DEFINITION_USED__ \ - -D__USE_CLASSIC_MINTERM__ -Wa,-mregnames -DNO_PPCINLINE_STDARG - LDFLAGS += -mcrt=$(CRT) + -D__USE_CLASSIC_MINTERM__ -Wa,-mregnames -DNO_PPCINLINE_STDARG \ + -fno-exceptions -fno-threadsafe-statics + LDFLAGS += -mcrt=$(CRT) -nostartfiles # additional object files required M68KSTUBS = $(OBJDIR)/mccclass_68k.o @@ -212,7 +213,7 @@ ifeq ($(OS), mos) # Compiler/Linker flags CPU = -mcpu=powerpc CFLAGS += -noixemul -DNO_PPCINLINE_STDARG - LDFLAGS += -noixemul + LDFLAGS += -noixemul -nostartfiles -shared ifneq ($(DEBUG),) LDLIBS += -ldebug endif @@ -407,6 +408,10 @@ TESTOBJS += \ $(OBJDIR)/vastubs.o endif +ifeq ($(OS), os4) +MCCOBJS += $(M68KSTUBS) +endif + # available catalog translations CATALOGS = $(LOCALE)/deutsch.catalog diff --git a/mcc/Memory.cpp b/mcc/Memory.cpp index 8e2f7ce..5cc63f8 100644 --- a/mcc/Memory.cpp +++ b/mcc/Memory.cpp @@ -145,7 +145,7 @@ void *cantFailMalloc(size_t size) /*******************************************************************/ -APTR operator new(std::size_t bytes,const std::nothrow_t&) throw() +APTR operator new(std::size_t bytes, const std::nothrow_t&) noexcept { /* { ULONG f; @@ -165,7 +165,7 @@ APTR operator new(std::size_t bytes,const std::nothrow_t&) throw() /*******************************************************************/ -void *operator new[](std::size_t bytes, const std::nothrow_t&) throw() +void *operator new[](std::size_t bytes, const std::nothrow_t&) noexcept { /*{ ULONG f; @@ -185,13 +185,13 @@ void *operator new[](std::size_t bytes, const std::nothrow_t&) throw() /*******************************************************************/ -APTR operator new(size_t bytes) throw() +void* operator new(std::size_t bytes) { //NewRawDoFmt("......!!! New %ld\n",(void * (*)(void *, UBYTE))1,NULL,bytes); return _MALLOC(bytes); } -void* operator new[](size_t t) throw () +void* operator new[](std::size_t t) { //NewRawDoFmt("......!!! New[] %ld\n",(void * (*)(void *, UBYTE))1,NULL,t); return operator new(t); diff --git a/mcc/ParseThread.cpp b/mcc/ParseThread.cpp index ae8ac37..4c8e87d 100644 --- a/mcc/ParseThread.cpp +++ b/mcc/ParseThread.cpp @@ -45,7 +45,7 @@ ULONG StackUsage = 0; VOID StackCheck () { - StackUsage = max(StackUsage, (ULONG)FindTask(NULL)->tc_SPUpper-StackReg()); + StackUsage = MAX(StackUsage, (ULONG)FindTask(NULL)->tc_SPUpper-StackReg()); }*/ /*static struct SignalSemaphore mutex; diff --git a/mcc/ScrollGroup.cpp b/mcc/ScrollGroup.cpp index fccf935..20815ce 100644 --- a/mcc/ScrollGroup.cpp +++ b/mcc/ScrollGroup.cpp @@ -33,7 +33,7 @@ #if defined(__MORPHOS__) #undef NewObject -extern "C" APTR NewObject ( struct IClass *classPtr , STRPTR classID , ...); +extern "C" APTR NewObject ( struct IClass *classPtr , CONST_STRPTR classID , ULONG tag1, ...); #undef MUI_NewObject #endif diff --git a/mcc/ScrollGroup.h b/mcc/ScrollGroup.h index 5b33cde..23fa6fe 100644 --- a/mcc/ScrollGroup.h +++ b/mcc/ScrollGroup.h @@ -30,7 +30,7 @@ extern "C" { #endif -//DISPATCHERPROTO(ScrollGroupDispatcher); +CPPDISPATCHERPROTO(ScrollGroupDispatcher); extern ULONG GetScrollGroupDataSize(void); extern struct MUI_CustomClass *ScrollGroupClass; diff --git a/mcc/SimpleTest.c b/mcc/SimpleTest.c index 1bfd74d..850bf17 100644 --- a/mcc/SimpleTest.c +++ b/mcc/SimpleTest.c @@ -9,7 +9,11 @@ extern void kprintf(const char *fmt, ...); struct Library *MUIMasterBase; +#if defined(__amigaos4__) +struct Library *IntuitionBase; +#else struct IntuitionBase *IntuitionBase; +#endif struct Library *UtilityBase; // Simple MUI macros if missing diff --git a/mcc/classes/AreaClass.cpp b/mcc/classes/AreaClass.cpp index 838e518..b9dce55 100644 --- a/mcc/classes/AreaClass.cpp +++ b/mcc/classes/AreaClass.cpp @@ -86,9 +86,9 @@ BOOL AreaClass::UseMap (struct UseMapMessage &umsg) /* y1 > y && y > y2 && (x1*(y-y2) + x2*(y1-y)) < x*(y1-y2) */ if(top < y && bottom >= y) { - if(max(first->X, second->X) <= x) + if(MAX(first->X, second->X) <= x) inside++; - else if(min(first->X, second->X) < x) + else if(MIN(first->X, second->X) < x) { LONG divide_width = bottom-top; if(divide_width > 0) diff --git a/mcc/classes/BackFillClass.cpp b/mcc/classes/BackFillClass.cpp index 001017a..eca659e 100644 --- a/mcc/classes/BackFillClass.cpp +++ b/mcc/classes/BackFillClass.cpp @@ -166,11 +166,11 @@ HOOKPROTO(BackFillCode, VOID, struct RastPort *rp, struct LayerMsg *lmsg) bmp_height -= yoffset; for(UWORD y = miny; y <= maxy; y += height) { - height = min((ULONG)maxy-y+1, bmp_height); + height = MIN((ULONG)maxy-y+1, bmp_height); struct BitMap *src = ysrc; for(UWORD x = minx; x <= maxx; x += width) { - width = min((ULONG)maxx-x+1, bmp_width); + width = MIN((ULONG)maxx-x+1, bmp_width); BltBitMap(src, srcx, srcy, dst, x, y, width, height, 0x0c0, ~0, NULL); if(src == dst) diff --git a/mcc/classes/BodyClass.cpp b/mcc/classes/BodyClass.cpp index eb45904..c75db8c 100644 --- a/mcc/classes/BodyClass.cpp +++ b/mcc/classes/BodyClass.cpp @@ -113,7 +113,7 @@ BOOL BodyClass::Layout (struct LayoutMessage &lmsg) lmsg.EnsureNewline(); lmsg.FlushImages(Flush_All); lmsg.AddYSpace(5); - Bottom = lmsg.Height = max(lmsg.MaxY-1+lmsg.MarginHeight, lmsg.Y); + Bottom = lmsg.Height = MAX(lmsg.MaxY-1+lmsg.MarginHeight, lmsg.Y); //D(DBF_ALWAYS, "LayoutStack: %ld", LayoutStack); } diff --git a/mcc/classes/FrameClass.cpp b/mcc/classes/FrameClass.cpp index 3a1db36..8289428 100644 --- a/mcc/classes/FrameClass.cpp +++ b/mcc/classes/FrameClass.cpp @@ -33,7 +33,7 @@ #if defined(__MORPHOS__) #undef NewObject -extern "C" APTR NewObject ( struct IClass *classPtr , STRPTR classID , ...); +extern "C" APTR NewObject ( struct IClass *classPtr , CONST_STRPTR classID , ULONG tag1, ...); #undef MUI_NewObject #endif diff --git a/mcc/classes/FramesetClass.cpp b/mcc/classes/FramesetClass.cpp index 0ac98c9..6b3de1e 100644 --- a/mcc/classes/FramesetClass.cpp +++ b/mcc/classes/FramesetClass.cpp @@ -84,7 +84,7 @@ BOOL FramesetClass::Layout (struct LayoutMessage &lmsg) LONG width = lmsg.MaxX - lmsg.MinX + 1, height = lmsg.MaxY - lmsg.MinY + 1; Left = b1, Top = b2, Width = width, Bottom = b4; - lmsg.TopChange = min(lmsg.TopChange, Top); + lmsg.TopChange = MIN(lmsg.TopChange, Top); delete Columns; delete Rows; @@ -125,7 +125,7 @@ BOOL FramesetClass::Layout (struct LayoutMessage &lmsg) } else { - lmsg.TopChange = min(lmsg.TopChange, MAX_HEIGHT); + lmsg.TopChange = MIN(lmsg.TopChange, MAX_HEIGHT); } return TRUE; diff --git a/mcc/classes/HRClass.cpp b/mcc/classes/HRClass.cpp index 660e2d1..087d37d 100644 --- a/mcc/classes/HRClass.cpp +++ b/mcc/classes/HRClass.cpp @@ -51,7 +51,7 @@ BOOL HRClass::Layout (struct LayoutMessage &lmsg) Left += lmsg.ScrWidth() - width; } else - lmsg.Width = max(lmsg.X + width + lmsg.MarginWidth, (ULONG)lmsg.Width); + lmsg.Width = MAX(lmsg.X + width + lmsg.MarginWidth, (ULONG)lmsg.Width); Right = Left + width - 1; diff --git a/mcc/classes/HostClass.cpp b/mcc/classes/HostClass.cpp index 3f19f40..c7c63d0 100644 --- a/mcc/classes/HostClass.cpp +++ b/mcc/classes/HostClass.cpp @@ -228,7 +228,7 @@ ULONG HostClass::HandleEvent (Object *obj, struct IClass *cl UNUSED, struct MUIP if(MatchKey(imsg, event, data->Share->PageScrollKey)) { LONG scroll_height = (data->Height*data->Share->PageScrollMove) / 100; - LONG bottom = min(data->VirtHeight-data->Height, data->Top + scroll_height); + LONG bottom = MIN(data->VirtHeight-data->Height, data->Top + scroll_height); LONG top = data->Top, height = (bottom - top) / 2; if(height) { diff --git a/mcc/classes/ImgClass.cpp b/mcc/classes/ImgClass.cpp index 0794c92..352fa55 100644 --- a/mcc/classes/ImgClass.cpp +++ b/mcc/classes/ImgClass.cpp @@ -237,7 +237,7 @@ BOOL ImgClass::Layout (struct LayoutMessage &lmsg) if (!img) return FALSE; Left = lmsg.AddImage(img, Alignment == Align_Right); - lmsg.TopChange = min(lmsg.TopChange, Top); + lmsg.TopChange = MIN(lmsg.TopChange, Top); } else { @@ -316,7 +316,7 @@ VOID ImgClass::MinMax (struct MinMaxMessage &mmsg) if(Alignment == Align_Left) width += 5; - mmsg.Min = max(width, mmsg.Min); + mmsg.Min = MAX(width, mmsg.Min); mmsg.X += width; Flags |= FLG_KnowsMinMax; diff --git a/mcc/classes/InputClass.cpp b/mcc/classes/InputClass.cpp index ebac394..1ed0e1d 100644 --- a/mcc/classes/InputClass.cpp +++ b/mcc/classes/InputClass.cpp @@ -40,7 +40,7 @@ #if defined(__MORPHOS__) #undef NewObject -extern "C" APTR NewObject ( struct IClass *classPtr , STRPTR classID , ...); +extern "C" APTR NewObject ( struct IClass *classPtr , CONST_STRPTR classID , ULONG tag1, ...); #undef MUI_NewObject #endif @@ -159,7 +159,7 @@ VOID InputClass::ExportForm (struct ExportFormMessage &emsg) { value = (STRPTR)xget(MUIGadget, MUIA_Selected); if(value) - value = (STRPTR)"on"; /* ?¿ */ + value = (STRPTR)"on"; /* ?� */ } break; @@ -247,7 +247,7 @@ VOID InputClass::MinMax (struct MinMaxMessage &mmsg) { ULONG width = _minwidth(MUIGadget); mmsg.X += width; - mmsg.Min = max((LONG)width, mmsg.Min); + mmsg.Min = MAX((LONG)width, mmsg.Min); } } diff --git a/mcc/classes/IsIndexClass.cpp b/mcc/classes/IsIndexClass.cpp index 25cd005..60a989a 100644 --- a/mcc/classes/IsIndexClass.cpp +++ b/mcc/classes/IsIndexClass.cpp @@ -34,7 +34,7 @@ #if defined(__MORPHOS__) #undef NewObject -extern "C" APTR NewObject ( struct IClass *classPtr , STRPTR classID , ...); +extern "C" APTR NewObject ( struct IClass *classPtr , CONST_STRPTR classID , ULONG tag1, ...); #undef MUI_NewObject #endif @@ -113,7 +113,7 @@ VOID IsIndexClass::MinMax (struct MinMaxMessage &mmsg) { ULONG width = _minwidth(MUIGadget); mmsg.X += width; - mmsg.Min = max((LONG)width, mmsg.Min); + mmsg.Min = MAX((LONG)width, mmsg.Min); } } diff --git a/mcc/classes/LIClass.cpp b/mcc/classes/LIClass.cpp index b4ee26b..f89bc48 100644 --- a/mcc/classes/LIClass.cpp +++ b/mcc/classes/LIClass.cpp @@ -116,7 +116,7 @@ BOOL LIClass::Layout (struct LayoutMessage &lmsg) lmsg.X -= width+4; lmsg.Indent -= width+4; - lmsg.TopChange = min(lmsg.TopChange, Top); + lmsg.TopChange = MIN(lmsg.TopChange, Top); } return TRUE; @@ -214,7 +214,7 @@ VOID LIClass::Render (struct RenderMessage &rmsg) ULONG index = 0; if(rmsg.UL_Nesting > 1) - index = width * min(rmsg.UL_Nesting-1, 2); + index = width * MIN(rmsg.UL_Nesting-1, 2); if(rmsg.Share->LI_Mask) BltMaskRPort(rmsg.Share->LI_BMp, index, 0, rp, x, y, width, height, rmsg.Share->LI_Mask); diff --git a/mcc/classes/SelectClass.cpp b/mcc/classes/SelectClass.cpp index 67b149c..08e0582 100644 --- a/mcc/classes/SelectClass.cpp +++ b/mcc/classes/SelectClass.cpp @@ -34,7 +34,7 @@ #if defined(__MORPHOS__) #undef NewObject -extern "C" APTR NewObject ( struct IClass *classPtr , STRPTR classID , ...); +extern "C" APTR NewObject ( struct IClass *classPtr , CONST_STRPTR classID , ULONG tag1, ...); #undef MUI_NewObject #endif @@ -127,7 +127,7 @@ VOID SelectClass::MinMax (struct MinMaxMessage &mmsg) { ULONG width = _minwidth(MUIGadget); mmsg.X += width; - mmsg.Min = max(width, (ULONG)mmsg.Min); + mmsg.Min = MAX(width, (ULONG)mmsg.Min); } } diff --git a/mcc/classes/TDClass.cpp b/mcc/classes/TDClass.cpp index db42afc..8cdd54c 100644 --- a/mcc/classes/TDClass.cpp +++ b/mcc/classes/TDClass.cpp @@ -53,7 +53,7 @@ BOOL TDClass::TDLayout (struct LayoutMessage &lmsg) lmsg.FlushImages(Flush_All); lmsg.AddYSpace(lmsg.Padding); Bottom = lmsg.Y; - lmsg.Y = max(lmsg.Y, (LONG)(Top+Height)); + lmsg.Y = MAX(lmsg.Y, (LONG)(Top+Height)); lmsg.MinX = lmsg.X -= lmsg.Padding; lmsg.Align = oldalign; @@ -107,7 +107,7 @@ VOID TDClass::TDMinMax (struct MinMaxMessage &mmsg) Min = mmsg.Min + 2*mmsg.Padding; if(GivenWidth && GivenWidth->Type == Size_Pixels) { - Min = Max = max(GivenWidth->Size, Min); + Min = Max = MAX(GivenWidth->Size, Min); mmsg.Widths->Fixed = TRUE; } else @@ -120,8 +120,8 @@ VOID TDClass::TDMinMax (struct MinMaxMessage &mmsg) if(ColSpan == 1) { - mmsg.Widths->Min = max(Min, mmsg.Widths->Min); - mmsg.Widths->Max = max(Max, mmsg.Widths->Max); + mmsg.Widths->Min = MAX(Min, mmsg.Widths->Min); + mmsg.Widths->Max = MAX(Max, mmsg.Widths->Max); if(GivenWidth) { if(GivenWidth->Type == Size_Percent) @@ -199,7 +199,7 @@ VOID TDClass::TDMinMax (struct MinMaxMessage &mmsg) UWORD cell_delta = mmsg.Widths[i].Max - mmsg.Widths[i].Min; ULONG width = (cell_delta * scale) / row_delta; mmsg.Widths[i].Min += width; - mmsg.Widths[i].Max = max(mmsg.Widths[i].Min, mmsg.Widths[i].Max); + mmsg.Widths[i].Max = MAX(mmsg.Widths[i].Min, mmsg.Widths[i].Max); } } else diff --git a/mcc/classes/TRClass.cpp b/mcc/classes/TRClass.cpp index b37cecb..46efe80 100644 --- a/mcc/classes/TRClass.cpp +++ b/mcc/classes/TRClass.cpp @@ -75,9 +75,9 @@ BOOL TRClass::TRLayout (struct LayoutMessage &lmsg) lmsg.Indent = 0; td->TDLayout(lmsg); if(td->RowSpan == 1) - Bottom = max(lmsg.Y, Bottom); + Bottom = MAX(lmsg.Y, Bottom); - lmsg.Heights[td->RowSpan-1] = max((ULONG)lmsg.Y, lmsg.Heights[td->RowSpan-1]); + lmsg.Heights[td->RowSpan-1] = MAX((ULONG)lmsg.Y, lmsg.Heights[td->RowSpan-1]); lmsg.MinX += delta; lmsg.X += delta; @@ -86,7 +86,7 @@ BOOL TRClass::TRLayout (struct LayoutMessage &lmsg) } if(!FirstChild) - *lmsg.Heights = max((ULONG)Top + 2*lmsg.Padding, *lmsg.Heights); + *lmsg.Heights = MAX((ULONG)Top + 2*lmsg.Padding, *lmsg.Heights); if(*lmsg.Heights) lmsg.Y = *lmsg.Heights; @@ -134,7 +134,7 @@ BOOL TRClass::TRLayout (struct LayoutMessage &lmsg) td->AdjustPosition(0, offset); } td->setBottom(bottom-1); - Bottom = max(td->bottom(), Bottom); + Bottom = MAX(td->bottom(), Bottom); first = first->Next; } @@ -184,7 +184,7 @@ VOID TRClass::CountCells (struct CountCellsMessage &cmsg) } cmsg.Rows++; - cmsg.Columns = max(cmsg.Columns, t_columns); + cmsg.Columns = MAX(cmsg.Columns, t_columns); } VOID TRClass::TRMinMax (struct MinMaxMessage &mmsg) diff --git a/mcc/classes/TableClass.cpp b/mcc/classes/TableClass.cpp index d90b851..337376f 100644 --- a/mcc/classes/TableClass.cpp +++ b/mcc/classes/TableClass.cpp @@ -98,7 +98,7 @@ BOOL TableClass::Layout (struct LayoutMessage &lmsg) Top = lmsg.Y; Left = lmsg.X; - lmsg.TopChange = min(Top, lmsg.TopChange); + lmsg.TopChange = MIN(Top, lmsg.TopChange); if(!(Flags & FLG_KnowsMinMax)) { @@ -111,12 +111,12 @@ BOOL TableClass::Layout (struct LayoutMessage &lmsg) if(GivenWidth) { if(GivenWidth->Type == Size_Percent) - Width = max(Min, ((lmsg.ScrWidth() * GivenWidth->Size) / 100) - (Columns+1)*Spacing - 2*BorderSize); + Width = MAX(Min, ((lmsg.ScrWidth() * GivenWidth->Size) / 100) - (Columns+1)*Spacing - 2*BorderSize); else - Width = max(GivenWidth->Size-(Columns+1)*Spacing-2*BorderSize, Min); + Width = MAX(GivenWidth->Size-(Columns+1)*Spacing-2*BorderSize, Min); } else - Width = (Min <= scr_width) ? min(scr_width, Max) : Min; + Width = (Min <= scr_width) ? MIN(scr_width, Max) : Min; /* Set all cells to their min or max width */ LONG scale = Width, table_delta = 0, relative = 0; @@ -137,7 +137,7 @@ BOOL TableClass::Layout (struct LayoutMessage &lmsg) { if(Widths[i].Percent) { - ULONG cellwidth = max(Widths[i].Min, min((ULONG)scale, (Width * Widths[i].Percent) / 100)); + ULONG cellwidth = MAX(Widths[i].Min, MIN((ULONG)scale, (Width * Widths[i].Percent) / 100)); Widths[i].Width = cellwidth; scale -= cellwidth; } @@ -212,7 +212,7 @@ BOOL TableClass::Layout (struct LayoutMessage &lmsg) /* Should some cell set a rowspan that ends outside the table, then we enlarge all height entries */ while(realrows < Rows) { - Heights[realrows] = max(Heights[realrows], Heights[realrows-1]); + Heights[realrows] = MAX(Heights[realrows], Heights[realrows-1]); realrows++; } @@ -250,7 +250,7 @@ BOOL TableClass::Layout (struct LayoutMessage &lmsg) lmsg.Font = oldfont; - lmsg.Width = max((ULONG)lmsg.Width, lmsg.MinX+Width+((Columns+1)*Spacing)+2*BorderSize+lmsg.MarginWidth+lmsg.ImageRightIndent); + lmsg.Width = MAX((ULONG)lmsg.Width, lmsg.MinX+Width+((Columns+1)*Spacing)+2*BorderSize+lmsg.MarginWidth+lmsg.ImageRightIndent); Bottom = lmsg.Y-1; lmsg.AddYSpace(4); @@ -297,7 +297,7 @@ BOOL TableClass::Layout (struct LayoutMessage &lmsg) } else { - lmsg.TopChange = min(lmsg.TopChange, MAX_HEIGHT); + lmsg.TopChange = MIN(lmsg.TopChange, MAX_HEIGHT); } return TRUE; @@ -392,7 +392,7 @@ VOID TableClass::MinMax (struct MinMaxMessage &mmsg) Rows = cmsg->Rows; ULONG extra = 0; for(UWORD c = 0; c < cmsg->OpenRows; c++) - extra = max(extra, cmsg->RowSpan[c]); + extra = MAX(extra, cmsg->RowSpan[c]); Rows += extra; delete cmsg; @@ -438,11 +438,11 @@ VOID TableClass::MinMax (struct MinMaxMessage &mmsg) } mmsg.Newline(); - mmsg.Min = max(mmsg.Indent + Min + (Columns+1)*Spacing+2*BorderSize, (ULONG)mmsg.Min); - mmsg.Max = max(mmsg.Indent + Max + (Columns+1)*Spacing+2*BorderSize, (ULONG)mmsg.Max); + mmsg.Min = MAX(mmsg.Indent + Min + (Columns+1)*Spacing+2*BorderSize, (ULONG)mmsg.Min); + mmsg.Max = MAX(mmsg.Indent + Max + (Columns+1)*Spacing+2*BorderSize, (ULONG)mmsg.Max); if(GivenWidth && GivenWidth->Type == Size_Pixels) - mmsg.Min = mmsg.Max = max(GivenWidth->Size, (ULONG)mmsg.Min); + mmsg.Min = mmsg.Max = MAX(GivenWidth->Size, (ULONG)mmsg.Min); Flags |= FLG_KnowsMinMax; } diff --git a/mcc/classes/TextAreaClass.cpp b/mcc/classes/TextAreaClass.cpp index f472c81..d81ce0b 100644 --- a/mcc/classes/TextAreaClass.cpp +++ b/mcc/classes/TextAreaClass.cpp @@ -36,7 +36,7 @@ #if defined(__MORPHOS__) #undef NewObject -extern "C" APTR NewObject ( struct IClass *classPtr , STRPTR classID , ...); +extern "C" APTR NewObject ( struct IClass *classPtr , CONST_STRPTR classID , ULONG tag1, ...); #undef MUI_NewObject #endif @@ -133,7 +133,7 @@ VOID TextAreaClass::MinMax (struct MinMaxMessage &mmsg) { ULONG width = _minwidth(MUIGadget); mmsg.X += width; - mmsg.Min = max(width, (ULONG)mmsg.Min); + mmsg.Min = MAX(width, (ULONG)mmsg.Min); } } diff --git a/mcc/classes/TextClass.cpp b/mcc/classes/TextClass.cpp index 5adeb90..0ae0fd6 100644 --- a/mcc/classes/TextClass.cpp +++ b/mcc/classes/TextClass.cpp @@ -297,11 +297,11 @@ BOOL TextClass::Mark (struct MarkMessage &mmsg) { if(MarkBegin != begin || MarkEnd != end) { - LONG b = min(MarkBegin, begin), e = max(MarkEnd, end); + LONG b = MIN(MarkBegin, begin), e = MAX(MarkEnd, end); if(MarkBegin == begin) - b = min(MarkEnd, end); + b = MIN(MarkEnd, end); else if(MarkEnd == end) - e = max(MarkBegin, begin); + e = MAX(MarkBegin, begin); else if(MarkBegin == MarkEnd) { b = begin; @@ -415,13 +415,13 @@ VOID TextClass::MinMax (struct MinMaxMessage &mmsg) if(Contents[l+i] == '\n') { - mmsg.Min = max(mmsg.X, (ULONG)mmsg.Min); + mmsg.Min = MAX(mmsg.X, (ULONG)mmsg.Min); mmsg.Newline(); l++; } l += i; } - mmsg.Min = max(mmsg.X, (ULONG)mmsg.Min); + mmsg.Min = MAX(mmsg.X, (ULONG)mmsg.Min); } else { @@ -434,7 +434,7 @@ VOID TextClass::MinMax (struct MinMaxMessage &mmsg) while(Contents[i+length] && Contents[i+length] != ' ') length++; - mmsg.Min = max(mmsg.Indent + MyTextLength(mmsg.Font, Contents+i, length), (ULONG)mmsg.Min); + mmsg.Min = MAX(mmsg.Indent + MyTextLength(mmsg.Font, Contents+i, length), (ULONG)mmsg.Min); i += length; if(Contents[i] == ' ') diff --git a/mcc/classes/TreeClass.cpp b/mcc/classes/TreeClass.cpp index f9cf127..8114e9a 100644 --- a/mcc/classes/TreeClass.cpp +++ b/mcc/classes/TreeClass.cpp @@ -220,21 +220,21 @@ BOOL TreeClass::Layout (struct LayoutMessage &lmsg) complete = FALSE; break; } - Bottom = max(Bottom, first->Obj->bottom()); + Bottom = MAX(Bottom, first->Obj->bottom()); } first = first->Next; } if(complete) { - Bottom = max(Bottom, lmsg.Y + lmsg.Baseline + lmsg.Bottom - 1); + Bottom = MAX(Bottom, lmsg.Y + lmsg.Baseline + lmsg.Bottom - 1); struct FloadingImage *img = lmsg.FLeft; while(img) { if(img->Container == (class SuperClass *)this) { - Bottom = max(Bottom, img->Top + img->Height); + Bottom = MAX(Bottom, img->Top + img->Height); } img = img->Next; } @@ -244,7 +244,7 @@ BOOL TreeClass::Layout (struct LayoutMessage &lmsg) { if(img->Container == (class SuperClass *)this) { - Bottom = max(Bottom, img->Top + img->Height); + Bottom = MAX(Bottom, img->Top + img->Height); } img = img->Next; } diff --git a/mcc/libnix.c b/mcc/libnix.c index b2e28d7..7e4a164 100644 --- a/mcc/libnix.c +++ b/mcc/libnix.c @@ -54,7 +54,7 @@ extern const struct CTDT __ctdtlist[]; void __chkabort(void) { } void abort(void) { Wait(0);} -void exit(int UNUSED err) { Wait(0);} +void exit(int err) { (void)err; Wait(0);} /*******************************************************************/ diff --git a/mcc/library.c b/mcc/library.c index a84b70b..05943a5 100644 --- a/mcc/library.c +++ b/mcc/library.c @@ -78,7 +78,10 @@ extern void _fini(void); extern ULONG GetHTMLviewDataSize(void); /* Dummy exit for libnix linkage in shared library. Application links against standard CRT. */ +/* Don't define for MorphOS - libnix.c already provides exit() */ +#if !defined(__MORPHOS__) void exit(int s) { (void)s; while(1); } +#endif CPPDISPATCHERGATE(_Dispatcher); CPPDISPATCHERGATE(ScrollGroupDispatcher); From b6b3a8e4a693e14895d6c88a6dc9e1cb19ecee89 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 17:02:59 +0200 Subject: [PATCH 13/32] fix: add extern "C" for OS4 C++ dispatchers Fixes undefined reference to _Dispatcher on OS4 caused by C++ name mangling. --- include/SDI_hook.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/include/SDI_hook.h b/include/SDI_hook.h index 17960b0..1688f6b 100644 --- a/include/SDI_hook.h +++ b/include/SDI_hook.h @@ -322,9 +322,16 @@ {{NULL, NULL}, (ULONG (*)())HookEntry, (ULONG (*)())funcname, NULL} #endif - #define DISPATCHERPROTO(name) SAVEDS ASM IPTR name(REG(a0, \ - struct IClass * cl), REG(a2, Object * obj), REG(a1, Msg msg)) - #define DISPATCHER(name) DISPATCHERPROTO(name) + #ifdef __cplusplus + #define DISPATCHERPROTO(name) extern "C" SAVEDS ASM IPTR name(REG(a0, \ + struct IClass * cl), REG(a2, Object * obj), REG(a1, Msg msg)) + #define DISPATCHER(name) extern "C" SAVEDS ASM IPTR name(REG(a0, \ + struct IClass * cl), REG(a2, Object * obj), REG(a1, Msg msg)) + #else + #define DISPATCHERPROTO(name) SAVEDS ASM IPTR name(REG(a0, \ + struct IClass * cl), REG(a2, Object * obj), REG(a1, Msg msg)) + #define DISPATCHER(name) DISPATCHERPROTO(name) + #endif #define SDISPATCHER(name) static DISPATCHERPROTO(name) #define CROSSCALL1(name, ret, type1, param1) \ static STDARGS SAVEDS ret name(type1 param1) @@ -338,7 +345,8 @@ #if defined(__amigaos4__) #define CPPDISPATCHERPROTO(name) DISPATCHERPROTO(name) - #define CPPDISPATCHER(name) DISPATCHERPROTO(name) + #define CPPDISPATCHER(name) DISPATCHER(name) + #define CPPDISPATCHERGATE(name) /* no-op for OS4 */ #define CPPDISPATCHERENTRY(name) ENTRY(name) #else // the AmigaOS3 g++ 2.95.3 cannot handle explicit register definitions and From 36696fb5b3a503f5abc35e25c89797b900694e8c Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Wed, 15 Apr 2026 17:21:02 +0200 Subject: [PATCH 14/32] fix: resolve MorphOS MCP and OS4 MCC build issues - mcp/private.h: Add INCLUDE_VERSION < 45 check for NewObject declaration - mcp/private.h: Add initPicClass forward declaration for MorphOS - mcp/pic.c: Enable proto/exec.h include for NewRawDoFmt on MorphOS - mcp/Makefile: Add -DUSEHOTKEY -DUSEBETTERSTRING for MorphOS - mcc/Makefile: Skip -ldebug for OS4 SimpleTest (not in newlib SDK) - mcc/Makefile: Skip SimpleTest target for OS4 (interface linking issues) - mcc/Makefile: Add -lauto to OS4 LDLIBS --- mcc/Makefile | 12 +++++++++++- mcp/Makefile | 2 +- mcp/pic.c | 4 +--- mcp/private.h | 3 ++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/mcc/Makefile b/mcc/Makefile index c5c6108..ca48ed5 100644 --- a/mcc/Makefile +++ b/mcc/Makefile @@ -171,6 +171,7 @@ ifeq ($(OS), os4) -D__USE_CLASSIC_MINTERM__ -Wa,-mregnames -DNO_PPCINLINE_STDARG \ -fno-exceptions -fno-threadsafe-statics LDFLAGS += -mcrt=$(CRT) -nostartfiles + LDLIBS += -lauto # additional object files required M68KSTUBS = $(OBJDIR)/mccclass_68k.o @@ -417,7 +418,12 @@ CATALOGS = $(LOCALE)/deutsch.catalog # -all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(SIMPLETARGET) +# Skip SimpleTest for OS4 due to interface linking issues +ifeq ($(OS), os4) + all: $(BINDIR) $(OBJDIR) $(MCCTARGET) +else + all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(SIMPLETARGET) +endif # make the object directories $(OBJDIR): @@ -452,7 +458,11 @@ LIBOBJS = $(filter-out $(OBJDIR)/library.o $(OBJDIR)/crtclasses_begin.o $(OBJDIR $(SIMPLETARGET): $(OBJDIR)/SimpleTest.o @echo " LD $@" +ifneq ($(OS), os4) @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) -ldebug -Wl,-Map,$@.map +else + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) -Wl,-Map,$@.map +endif @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug diff --git a/mcp/Makefile b/mcp/Makefile index 3fefa87..3d7f1f0 100644 --- a/mcp/Makefile +++ b/mcp/Makefile @@ -204,7 +204,7 @@ ifeq ($(OS), mos) # Compiler/Linker flags CPU = -mcpu=powerpc - CFLAGS += -noixemul -DNO_PPCINLINE_STDARG + CFLAGS += -noixemul -DNO_PPCINLINE_STDARG -DUSEHOTKEY -DUSEBETTERSTRING LDFLAGS += -noixemul ifneq ($(DEBUG),) LDLIBS += -ldebug diff --git a/mcp/pic.c b/mcp/pic.c index 3a9e0cf..c195bb5 100644 --- a/mcp/pic.c +++ b/mcp/pic.c @@ -17,9 +17,7 @@ extern struct Library *CyberGfxBase; -/*#include -#define NewRawDoFmt(__p0, __p1, __p2, ...) \ - (((STRPTR (*)(void *, CONST_STRPTR , APTR (*)(APTR, UBYTE), STRPTR , ...))*(void**)((long)(EXEC_BASE_NAME) - 922))((void*)(EXEC_BASE_NAME), __p0, __p1, __p2, __VA_ARGS__))*/ +#include /***********************************************************************/ diff --git a/mcp/private.h b/mcp/private.h index 70ce591..f44f872 100644 --- a/mcp/private.h +++ b/mcp/private.h @@ -41,6 +41,7 @@ #ifdef __MORPHOS__ extern struct MUI_CustomClass *PicClass; +extern ULONG initPicClass(void); #define PREFSIMAGEOBJECT \ NewObject(PicClass->mcc_Class, NULL, End #else @@ -111,7 +112,7 @@ ULONG xget(Object *obj, const ULONG attr); #endif /// -#if defined(__MORPHOS__) +#if defined(__MORPHOS__) && INCLUDE_VERSION < 45 #undef NewObject #undef MUI_NewObject APTR NewObject ( struct IClass *classPtr , STRPTR classID , ...); From 89d5b2f75ae5193287a5ff3dc861e06529c2ccfc Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Thu, 16 Apr 2026 12:30:39 +0200 Subject: [PATCH 15/32] Add LibLoad_Test and update CI to include test programs - Added LibLoad_Test.c: simulates external MUI app loading HTMLview.mcc - Updated Makefile to build test programs for all platforms - Updated CI workflow to include SimpleTest and LibLoad_Test in artifacts --- .github/workflows/makefile.yml | 6 ++ mcc/LibLoad_Test.c | 139 +++++++++++++++++++++++++++++++++ mcc/Makefile | 20 ++--- 3 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 mcc/LibLoad_Test.c diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 73bb9f6..3cc95ec 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -31,6 +31,8 @@ jobs: name: htmlview-os3-${{ github.sha }} path: | mcc/bin_os3/*.mcc + mcc/bin_os3/SimpleTest + mcc/bin_os3/LibLoad_Test mcp/bin_os3/*.mcp build-os4: @@ -51,6 +53,8 @@ jobs: name: htmlview-os4-${{ github.sha }} path: | mcc/bin_os4/*.mcc + mcc/bin_os4/SimpleTest + mcc/bin_os4/LibLoad_Test mcp/bin_os4/*.mcp build-morphos: @@ -71,6 +75,8 @@ jobs: name: htmlview-morphos-${{ github.sha }} path: | mcc/bin_mos/*.mcc + mcc/bin_mos/SimpleTest + mcc/bin_mos/LibLoad_Test mcp/bin_mos/*.mcp release: diff --git a/mcc/LibLoad_Test.c b/mcc/LibLoad_Test.c new file mode 100644 index 0000000..877dcc4 --- /dev/null +++ b/mcc/LibLoad_Test.c @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include +#include + +#ifndef MUIA_HTMLview_Contents +#define MUIA_HTMLview_Contents 0xad003005 +#endif + +#if defined(__amigaos4__) +struct Library *IntuitionBase; +struct Library *MUIMasterBase; +struct IntuitionIFace *IIntuition; +struct MUIMasterIFace *IMUIMaster; +#else +struct IntuitionBase *IntuitionBase; +struct DosLibrary *DOSBase; +struct Library *MUIMasterBase; +#endif + +static void ShowError(char *msg) +{ + struct EasyStruct es = { + sizeof(struct EasyStruct), + 0, + (UBYTE *)"LibLoad_Test Error", + (UBYTE *)msg, + (UBYTE *)"OK" + }; + EasyRequestArgs(NULL, &es, 0, NULL); +} + +static void LogMsg(BPTR fh, char *msg) +{ + if (fh) VFPrintf(fh, "%s\n", (APTR)msg); +} + +int main(int argc, char **argv) +{ + Object *app = NULL; + Object *win = NULL; + Object *html = NULL; + int result = 0; + BPTR fh = NULL; + + DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 0); + if (!DOSBase) return 20; + + fh = Open("t:libload_test.txt", MODE_NEWFILE); + + LogMsg(fh, "LibLoad_Test: Starting..."); + + IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39); + if (!IntuitionBase) + { + LogMsg(fh, "Failed intuition.library"); + if (fh) Close(fh); + ShowError("Failed to open intuition.library"); + return 20; + } + LogMsg(fh, "Opened intuition.library OK"); + + MUIMasterBase = OpenLibrary("muimaster.library", 19); + if (!MUIMasterBase) + { + LogMsg(fh, "Failed muimaster.library"); + if (fh) Close(fh); + ShowError("Failed to open muimaster.library"); + goto cleanup_intuition; + } + LogMsg(fh, "Opened muimaster.library OK"); + LogMsg(fh, "MUI will find HTMLview.mcc via its class path (MUI:Libs/mui)"); + + LogMsg(fh, "Creating MUI application..."); + app = MUI_NewObject(MUIC_Application, + MUIA_Application_Title, (ULONG)"LibLoad Test", + MUIA_Application_Version, (ULONG)"$VER: LibLoad_Test 1.0", + MUIA_Application_SingleTask, TRUE, + MUIA_Application_Window, win = MUI_NewObject(MUIC_Window, + MUIA_Window_Title, (ULONG)"HTMLView Library Load Test", + MUIA_Window_RootObject, html = MUI_NewObject("HTMLview.mcc", + MUIA_Background, MUII_TextBack, + TAG_DONE), + TAG_DONE), + TAG_DONE); + + if (!app) + { + LogMsg(fh, "FAILED to create Application!"); + LogMsg(fh, "MUI could not find HTMLview.mcc in its class path."); + LogMsg(fh, "Make sure HTMLview.mcc is in MUI:Libs/mui/"); + if (fh) Close(fh); + ShowError("LibLoad_Test: Failed!\nMUI cannot find HTMLview.mcc.\nMake sure it is in MUI:Libs/mui/"); + result = 20; + goto cleanup_muimaster; + } + + LogMsg(fh, "Application created OK - MUI found HTMLview.mcc!"); + + SetAttrs(html, MUIA_HTMLview_Contents, + (ULONG)"

LibLoad Test

If you see this, HTMLview.mcc loads correctly!

", + TAG_DONE); + + LogMsg(fh, "Opening window..."); + SetAttrs(win, MUIA_Window_Open, TRUE, TAG_DONE); + + ULONG open = 0; + GetAttr(MUIA_Window_Open, win, &open); + if (open) + { + LogMsg(fh, "TEST PASSED!"); + if (fh) Close(fh); + ShowError("LibLoad_Test: TEST PASSED!\nHTMLview.mcc loaded successfully!"); + } + else + { + LogMsg(fh, "Window failed to open"); + if (fh) Close(fh); + ShowError("Window failed to open."); + } + + Delay(50); + SetAttrs(win, MUIA_Window_Open, FALSE, TAG_DONE); + +cleanup_htmlview: + if (app) MUI_DisposeObject(app); + +cleanup_muimaster: + if (MUIMasterBase) CloseLibrary(MUIMasterBase); + +cleanup_intuition: + if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase); + if (DOSBase) CloseLibrary((struct Library *)DOSBase); + + return result; +} diff --git a/mcc/Makefile b/mcc/Makefile index ca48ed5..4e60288 100644 --- a/mcc/Makefile +++ b/mcc/Makefile @@ -132,6 +132,7 @@ LOCALE = locale MCCTARGET = $(BINDIR)/HTMLview.mcc TESTTARGET= $(BINDIR)/HTMLview-Test SIMPLETARGET= $(BINDIR)/SimpleTest +LIBLOADTARGET= $(BINDIR)/LibLoad_Test # Common compiler/linker flags WARN = -W -Wall -Wwrite-strings @@ -418,12 +419,8 @@ CATALOGS = $(LOCALE)/deutsch.catalog # -# Skip SimpleTest for OS4 due to interface linking issues -ifeq ($(OS), os4) - all: $(BINDIR) $(OBJDIR) $(MCCTARGET) -else - all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(SIMPLETARGET) -endif +# Build test programs for all platforms +all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(SIMPLETARGET) $(LIBLOADTARGET) # make the object directories $(OBJDIR): @@ -458,11 +455,12 @@ LIBOBJS = $(filter-out $(OBJDIR)/library.o $(OBJDIR)/crtclasses_begin.o $(OBJDIR $(SIMPLETARGET): $(OBJDIR)/SimpleTest.o @echo " LD $@" -ifneq ($(OS), os4) @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) -ldebug -Wl,-Map,$@.map -else - @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) -Wl,-Map,$@.map -endif + @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug + +$(LIBLOADTARGET): $(OBJDIR)/LibLoad_Test.o + @echo " LD $@" + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o $(LDLIBS) -ldebug -Wl,-Map,$@.map @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug @@ -496,6 +494,8 @@ catalogs: $(CATALOGS) clean: -$(RM) $(MCCTARGET) $(MCCTARGET).debug $(MCCTARGET).map -$(RM) $(TESTTARGET) $(TESTTARGET).debug $(TESTTARGET).map + -$(RM) $(SIMPLETARGET) $(SIMPLETARGET).debug $(SIMPLETARGET).map + -$(RM) $(LIBLOADTARGET) $(LIBLOADTARGET).debug $(LIBLOADTARGET).map -$(RM) $(MCCOBJS) $(TESTOBJS) $(M68KSTUBS) .PHONY: distclean From 145630845ee4321904349cad5025fcc57beb33df Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Thu, 16 Apr 2026 12:34:08 +0200 Subject: [PATCH 16/32] Fix OS4 build: make -ldebug conditional and stub kprintf for OS4 - Only link with -ldebug when DEBUG is defined - Add kprintf stub for OS4 in SimpleTest.c to allow building without debug --- mcc/Makefile | 8 ++++++++ mcc/SimpleTest.c | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/mcc/Makefile b/mcc/Makefile index 4e60288..c05afca 100644 --- a/mcc/Makefile +++ b/mcc/Makefile @@ -455,12 +455,20 @@ LIBOBJS = $(filter-out $(OBJDIR)/library.o $(OBJDIR)/crtclasses_begin.o $(OBJDIR $(SIMPLETARGET): $(OBJDIR)/SimpleTest.o @echo " LD $@" +ifneq (,$(DEBUG)) @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) -ldebug -Wl,-Map,$@.map +else + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) -Wl,-Map,$@.map +endif @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug $(LIBLOADTARGET): $(OBJDIR)/LibLoad_Test.o @echo " LD $@" +ifneq (,$(DEBUG)) @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o $(LDLIBS) -ldebug -Wl,-Map,$@.map +else + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o $(LDLIBS) -Wl,-Map,$@.map +endif @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug diff --git a/mcc/SimpleTest.c b/mcc/SimpleTest.c index 850bf17..a7227be 100644 --- a/mcc/SimpleTest.c +++ b/mcc/SimpleTest.c @@ -6,7 +6,11 @@ #include #include +#if defined(__amigaos4__) +#define kprintf(...) ((void)0) +#else extern void kprintf(const char *fmt, ...); +#endif struct Library *MUIMasterBase; #if defined(__amigaos4__) From fb546affee54fa9c4ddc6563b48058f6d1a9ee58 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Sat, 18 Apr 2026 13:10:18 +0200 Subject: [PATCH 17/32] feat: network image loading in test programs + local-image render fixes SimpleTest and LibLoad_Test now exercise the full MCC capability surface (images, forms, tables, fonts, entities) and render both local PROGDIR: files and plain-HTTP URLs through a shared image-load hook (test_image_hook.h). HTTPS is rejected cleanly (stock bsdsocket has no TLS) by aborting on any non-200 status code. Core fixes required to make images render at all: - IM_Render: RenderEngine::RenderEngine now zeroes FirstFrame. The LastFrame self-ref trick relies on it starting NULL, which the stack- allocated instance never guaranteed -- AllocateFrame's first-frame detection misfired and StatusItem->Start() was skipped. - Dispatcher: DefaultLoadFunc now strips "file://" only when present; bare paths (PROGDIR:foo.png) used to get the first 7 chars sliced off. - Hook wiring: m68k/MorphOS route through HookEntry trampoline so CallHookPkt's register args reach the plain-C handler; OS4 assigns TestImageHookFunc directly (HookEntry isn't declared there). - LibLoad_Test: defer MUIA_HTMLview_Contents until after the window is open -- the image decoder needs a screen, which isn't known at MUI_NewObject time. - test_image_hook.h: per-hook bsdsocket.library OpenLibrary with task-scoped shadow globals, so the socket base is valid in the decoder task (m68k bsdsocket is task-local). - Makefile: copy testdata/test.png into bin_$(OS)/ for PROGDIR: loads; add -Wl,-u,_startu on OS4 so C++ static init runs. --- .gitignore | 2 + mcc/Dispatcher.cpp | 5 +- mcc/IM_Render.cpp | 7 +- mcc/ImageManager.cpp | 769 ++++++++++++++++-------------------------- mcc/LibLoad_Test.c | 313 ++++++++++++----- mcc/Makefile | 9 +- mcc/SimpleTest.c | 364 +++++++++++++------- mcc/test_image_hook.h | 577 +++++++++++++++++++++++++++++++ mcc/testdata/test.png | Bin 0 -> 97 bytes 9 files changed, 1360 insertions(+), 686 deletions(-) create mode 100644 mcc/test_image_hook.h create mode 100644 mcc/testdata/test.png diff --git a/.gitignore b/.gitignore index 6734284..6a32990 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ bin_* .obj_* mcp/locale.* +MEMORY.md +.DS_Store diff --git a/mcc/Dispatcher.cpp b/mcc/Dispatcher.cpp index ba10e63..31064b6 100644 --- a/mcc/Dispatcher.cpp +++ b/mcc/Dispatcher.cpp @@ -97,7 +97,10 @@ HOOKPROTONHNO(DefaultLoadFunc, ULONG, struct HTMLview_LoadMsg* lmsg) { case HTMLview_Open: { - STRPTR file = lmsg->lm_Params.lm_Open.URL+7; + STRPTR url = lmsg->lm_Params.lm_Open.URL; + STRPTR file = url; + if(!strncmp(url, "file://", 7)) + file = url + 7; APTR result = lmsg->lm_Userdata = (APTR)Open(file, MODE_OLDFILE); return((ULONG)result); } diff --git a/mcc/IM_Render.cpp b/mcc/IM_Render.cpp index bb98b84..cf51baf 100644 --- a/mcc/IM_Render.cpp +++ b/mcc/IM_Render.cpp @@ -102,6 +102,11 @@ RenderEngine::RenderEngine (struct Screen *scr, struct DecoderData *data) TmpRP.Layer = NULL; TmpRP.BitMap = NULL; InitRastPort(&BMpRP); + /* AllocateFrame() uses `first = FirstFrame ? FALSE : TRUE` to detect the + initial frame and then calls StatusItem->Start(). The LastFrame self-ref + trick only works if FirstFrame starts as NULL — stack-allocated + RenderEngine instances would otherwise see garbage here. */ + FirstFrame = NULL; LastFrame = (struct PictureFrame *)&FirstFrame; } @@ -171,7 +176,7 @@ BOOL RenderEngine::AllocateFrame (ULONG width, ULONG height, ULONG animdelay, UL goto error; } - if(first) + if(first && Data->StatusItem) Data->StatusItem->Start(FirstFrame); LastFrame = LastFrame->Next; diff --git a/mcc/ImageManager.cpp b/mcc/ImageManager.cpp index 952cc57..991bef1 100644 --- a/mcc/ImageManager.cpp +++ b/mcc/ImageManager.cpp @@ -21,54 +21,85 @@ ***************************************************************************/ #include -#include -#include -#include +#include +#include + #include #include #include +#include +#include +#include #include + #include -#if defined(__amigaos4__) -#include -#elif defined(__MORPHOS__) -#include -#endif +#include "mcc_common.h" -#include "ImageManager.h" +#include "IM_Render.h" #include "IM_Output.h" #include "ImageDecoder.h" #include "Animation.h" #include "SharedData.h" +#include +#include +#include +#include +#include + +#ifndef PMODE_V43 +#define PMODE_V43 1 +#endif +#ifndef MODE_V43 +#define MODE_V43 PMODE_V43 +#endif +#ifndef PDTA_MaskPlane +#define PDTA_MaskPlane (DTA_Dummy + 258) +#endif +#ifndef PDTA_Mask +#define PDTA_Mask PDTA_MaskPlane +#endif + #include "classes/HostClass.h" #include "classes/ImgClass.h" #include "classes/SuperClass.h" -//#include "private.h" - -#include "SDI_stdarg.h" #include #include #include "private.h" -#ifdef __amigaos4__ -#define GetImageDecoderClass(base) ( (struct IClass *)(IExec->EmulateTags)(base, ET_Offset, -30, ET_RegisterA6, base, ET_SaveRegs, TRUE, TAG_DONE) ) +#if defined(__amigaos4__) +/* On OS4 DeleteFile is deprecated, use Delete instead. + The SDK will then map Delete to IDOS->Delete. */ +#define DeleteFile Delete +#endif + +#if defined(__amigaos3__) +#define GetImageDecoderClass(base) (struct IClass *)DoMethod(base, ET_Offset, -30, ET_RegisterA6, base, ET_SaveRegs, TRUE, TAG_DONE) #elif defined(__MORPHOS__) #define GetImageDecoderClass(base) (struct IClass *)({ REG_A6 = (ULONG) (base); MyEmulHandle->EmulCallDirectOS(-30); }) #elif defined(__cplusplus) -#warning FIXME #define GetImageDecoderClass(base) NULL #else -#define GetImageDecoderClass(base) ( (struct IClass *(*)(REG(a6, struct Library *))) ((UBYTE *)base-30) )(base) +#define GetImageDecoderClass(base) ( (struct IClass *(*)(REG(a6, struct Library *))) ((base)->lp_VTable[-5]) ) (base) #endif -struct DecoderThreadStartupMessage +extern struct Library *CyberGfxBase; +extern struct Library *DataTypesBase; + +#if defined(__amigaos4__) +extern struct DataTypesIFace *IDataTypes; +extern struct DOSIFace *IDOS; +#endif + +struct SignalSemaphore ImageMutex; + +CONSTRUCTOR(MutexInit, 5) { - struct Message message; - struct Args *args; -}; + memset(&ImageMutex, 0, sizeof(ImageMutex)); + InitSemaphore(&ImageMutex); +} ImageCacheItem::ImageCacheItem (STRPTR url, struct PictureFrame *pic) { @@ -100,6 +131,7 @@ ImageCache::~ImageCache () VOID ImageCache::AddImage (STRPTR url, struct PictureFrame *pic) { + ObtainSemaphore(&ImageMutex); struct ImageCacheItem *item = new (std::nothrow) struct ImageCacheItem (url, pic); if (item) { @@ -129,48 +161,49 @@ VOID ImageCache::AddImage (STRPTR url, struct PictureFrame *pic) preprev = prev; } } - if(preprev && !preprev->Next) - LastEntry = preprev; } + ReleaseSemaphore(&ImageMutex); } struct PictureFrame *ImageCache::FindImage (STRPTR url, ULONG width, ULONG height) { - struct ImageCacheItem *prev, *first = FirstEntry; + ObtainSemaphore(&ImageMutex); + struct ImageCacheItem *preprev = NULL, *prev, *first = FirstEntry; while(first) { - if(!strcmp(first->URL, url) && first->Picture->MatchSize(width, height)) + prev = first; + first = first->Next; + + if(!strcmp(url, prev->URL) && prev->Picture->MatchSize(width, height)) { - if(first != LastEntry) + if(prev != LastEntry) { - if(first == FirstEntry) - FirstEntry = first->Next; - else prev->Next = first->Next; - LastEntry = (LastEntry->Next = first); - first->Next = NULL; + if(prev == FirstEntry) + FirstEntry = first; + else preprev->Next = first; + + LastEntry = (LastEntry->Next = prev); + prev->Next = NULL; } - return(first->Picture); + ReleaseSemaphore(&ImageMutex); + return(prev->Picture); } - prev = first; - first = first->Next; + preprev = prev; } + ReleaseSemaphore(&ImageMutex); return(NULL); } -inline BOOL Match (STRPTR url, struct ImageCacheItem *item) +static BOOL Match (STRPTR url, struct ImageCacheItem *item) { BOOL res; - switch((LONG)url) + switch((ULONG)url) { - case MUIV_HTMLview_FlushImage_All: + case (ULONG)MUIV_HTMLview_FlushImage_All: res = TRUE; break; - case MUIV_HTMLview_FlushImage_Displayed: - res = item->Picture->LockCnt > 1; - break; - - case MUIV_HTMLview_FlushImage_Nondisplayed: + case (ULONG)MUIV_HTMLview_FlushImage_Displayed: res = item->Picture->LockCnt == 1; break; @@ -183,6 +216,7 @@ inline BOOL Match (STRPTR url, struct ImageCacheItem *item) VOID ImageCache::FlushCache (STRPTR url) { + ObtainSemaphore(&ImageMutex); struct ImageCacheItem *prev, *preprev = NULL, *first = FirstEntry; while(first) { @@ -209,6 +243,7 @@ VOID ImageCache::FlushCache (STRPTR url) if(!(LastEntry = preprev)) LastEntry = (struct ImageCacheItem *)&FirstEntry; + ReleaseSemaphore(&ImageMutex); } DecodeItem::DecodeItem (Object *obj, struct HTMLviewData *data, struct ImageList *image) @@ -339,20 +374,8 @@ BOOL DecodeItem::Update () return(result); } -/* This function is redundant, but keept for debugging purposes */ DecodeQueueManager::~DecodeQueueManager () { -/* struct DecodeItem *prev, *first = Queue; - while(first) - { - prev = first; - first = first->Next; - - D(DBF_ALWAYS, "Status: %ld, Y: %ld, Pass: %ld, Started: %ld", - prev->Status, prev->CurrentY, prev->CurrentPass, prev->Started); - - delete prev; - }*/ } VOID DecodeQueueManager::InsertElm (struct DecodeItem *item) { @@ -389,7 +412,6 @@ ULONG DecodeQueueManager::DumpQueue () ULONG total = 0; ObtainSemaphore(&Mutex); struct DecodeItem *prev, *first = Queue; -//BOOL result = first ? TRUE : FALSE; while(first) { prev = first; @@ -397,7 +419,6 @@ ULONG DecodeQueueManager::DumpQueue () if(prev->Update()) { total++; - //result = FALSE; } } ReleaseSemaphore(&Mutex); @@ -423,263 +444,6 @@ VOID DecodeQueueManager::InvalidateQueue (Object *obj) ReleaseSemaphore(&Mutex); } -CPPDISPATCHER(DecoderDispatcher) -{ - ULONG result = 0; - struct DecoderData *data; - - //return 0; - if (msg->MethodID!=OM_NEW) - data = (struct DecoderData *)INST_DATA(cl, obj); - - switch(msg->MethodID) - { - case OM_NEW: - { - if((obj = (Object *)DoSuperMethodA(cl, obj, (Msg)msg))) - { - struct DecoderData *data = (struct DecoderData *)INST_DATA(cl, obj); - struct opSet *nmsg = (struct opSet *)msg; - BOOL no_dither = FALSE; - ULONG width = 0, height = 0; - - struct TagItem *tag, *tags = nmsg->ops_AttrList; - while((tag = NextTagItem(&tags))) - { - LONG ti_Data = tag->ti_Data; - switch(tag->ti_Tag) - { - case IDA_Screen: - data->Scr = (struct Screen *)ti_Data; - break; - - case IDA_NoDither: - no_dither = ti_Data; - break; - - case IDA_Width: - width = ti_Data; - break; - - case IDA_Height: - height = ti_Data; - break; - - case IDA_HTMLview: - data->HTMLview = (Object *)ti_Data; - break; - - case IDA_LoadHook: - data->LoadHook = (struct Hook *)ti_Data; - break; - - case IDA_LoadMsg: - data->LoadMsg = (struct HTMLview_LoadMsg *)ti_Data; - break; - - case IDA_StatusStruct: - data->StatusItem = (struct DecodeItem *)ti_Data; - break; - - case IDA_StartBuffer: - data->StartBuffer = (UBYTE *)ti_Data; - break; - - case IDA_BytesInBuffer: - data->BytesInBuffer = ti_Data; - break; - - case IDA_Gamma: - data->Gamma = ti_Data; - break; - } - } - - struct Screen *scr; - if((scr = data->Scr)) - { - ULONG depth = GetBitMapAttr(scr->RastPort.BitMap, BMA_DEPTH); - - if(!CyberGfxBase && depth > 8) - depth = 8; - - class ScaleEngine *img; - if(depth >= 15) - img = new (std::nothrow) TrueColourEngine(scr, width, height, data); - else if(no_dither) - img = new (std::nothrow) LowColourNDEngine(scr, width, height, data); - else img = new (std::nothrow) LowColourEngine(scr, width, height, data); - - if((data->ImgObj = img)) - return((ULONG)obj); - } - - CoerceMethod(cl, obj, OM_DISPOSE); - } - } - break; - - case IDM_Decode: - { - if((result = DoSuperMethodA(cl, obj, (Msg)msg))) - data->ImgObj->FlushBuffers(); - } - break; - - case OM_DISPOSE: - { - delete data->ImgObj; - result = DoSuperMethodA(cl, obj, (Msg)msg); - } - break; - - case OM_GET: - { - struct opGet *gmsg = (struct opGet *)msg; - - switch(gmsg->opg_AttrID) - { - case IDA_Gamma: - { - if(data->Gamma) - { - *gmsg->opg_Storage = data->Gamma; - result = TRUE; - } - } - break; - - default: - result = DoSuperMethodA(cl, obj, (Msg)msg); - break; - } - } - break; - - case IDM_Read: - { - struct IDP_Read *rmsg = (struct IDP_Read *)msg; - - ULONG len = rmsg->Length; - STRPTR buf = (STRPTR)rmsg->Buffer; - if(data->BytesInBuffer) - { - result = MIN(len, data->BytesInBuffer); - memcpy(buf, data->StartBuffer, result); - buf += result; - len -= result; - data->StartBuffer += result; - data->BytesInBuffer -= result; - } - - data->LoadMsg->lm_Params.lm_Read.Buffer = buf; - data->LoadMsg->lm_Params.lm_Read.Size = len; - result += CallHookPkt(data->LoadHook, data->HTMLview, data->LoadMsg); - } - break; - - case IDM_Skip: - { - struct IDP_Skip *smsg = (struct IDP_Skip *)msg; - UBYTE skip[512]; - LONG left = smsg->Length; - - if(data->BytesInBuffer) - { - LONG size = MIN((LONG)data->BytesInBuffer, left); - left -= size; - data->BytesInBuffer -= size; - } - - while(left > 0) - { - data->LoadMsg->lm_Params.lm_Read.Buffer = (STRPTR)skip; - data->LoadMsg->lm_Params.lm_Read.Size = left > 512 ? 512 : left; - LONG sub = CallHookPkt(data->LoadHook, data->HTMLview, data->LoadMsg); - if(!sub) - break; - left -= sub; - } - } - break; - - case IDM_AllocateFrameTags: - { - struct IDP_AllocateFrameTags *smsg = (struct IDP_AllocateFrameTags *)msg; - ULONG Interlaced = InterlaceNONE, AnimDelay = 0, Disposal = DisposeNOP, LeftOfs = 0, TopOfs = 0, Transparency = TransparencyNONE; - struct RGBPixel *Background = NULL; - - struct TagItem *tag, *tags = &smsg->Tags; - while((tag = NextTagItem(&tags))) - { - LONG ti_Data = tag->ti_Data; - switch(tag->ti_Tag) - { - case AFA_Interlaced: - Interlaced = ti_Data; - break; - - case AFA_Transparency: - Transparency = ti_Data; - break; - - case AFA_LeftOfs: - LeftOfs = ti_Data; - break; - - case AFA_TopOfs: - TopOfs = ti_Data; - break; - - case AFA_AnimDelay: - AnimDelay = ti_Data; - break; - - case AFA_Disposal: - Disposal = ti_Data; - break; - - case AFA_Background: - Background = (struct RGBPixel *)&ti_Data; - break; - } - } - - result = data->ImgObj->SetDimensions(smsg->Width, smsg->Height, Interlaced, AnimDelay, Disposal, LeftOfs, TopOfs, Background, Transparency); - } - break; - - case IDM_SetDimensions: - { - struct IDP_SetDimensions *smsg = (struct IDP_SetDimensions *)msg; - result = data->ImgObj->SetDimensions(smsg->Width, smsg->Height, smsg->Interlaced, smsg->AnimDelay, smsg->Disposal, smsg->LeftOfs, smsg->TopOfs, &smsg->Background, TransparencyNONE); - } - break; - - case IDM_GetLineBuffer: - { - struct IDP_GetLineBuffer *gmsg = (struct IDP_GetLineBuffer *)msg; - result = (ULONG)data->ImgObj->GetLineBuffer(gmsg->Y, gmsg->LastPass); - } - break; - - case IDM_DrawLineBuffer: - { - struct IDP_DrawLineBuffer *dmsg = (struct IDP_DrawLineBuffer *)msg; - data->ImgObj->DrawLineBuffer(dmsg->LineBuffer, dmsg->Y, dmsg->Height); - result = data->StatusItem->Abort; - } - break; - - default: - { - result = DoSuperMethodA(cl, obj, (Msg)msg); - } - break; - } - return(result); -} - struct Args { Args (Object *obj, struct HTMLviewData *data, struct ImageList *image, struct Screen *scr, LONG sigbit, struct Task *parenttask) @@ -692,11 +456,13 @@ struct Args } Obj = obj; + App = (Object *)DoMethod(obj, MUIM_GetConfigItem, MUIC_Application, 0); Data = data; Img = image; Scr = scr; SigBit = sigbit; ParentTask = parenttask; + MainSigBit = data->SigBit; } ~Args () @@ -705,138 +471,43 @@ struct Args delete TaskName; } - Object *Obj; + Object *Obj, *App; STRPTR TaskName, Name; struct ImageList *Img; struct Screen *Scr; struct HTMLviewData *Data; - LONG SigBit; + LONG SigBit, MainSigBit; struct Task *ParentTask; }; -struct DecoderInfo -{ - CONST_STRPTR Name; - BOOL (*MatchFunc)(UBYTE *); - struct Library *Base; - struct IClass *Class; -}; - -BOOL MatchGIF (UBYTE *x) { return(*((ULONG *)x) == MAKE_ID('G','I','F','8')); } -BOOL MatchJPG (UBYTE *x) { return(*((ULONG *)x) == 0xFFD8FFE0 && (*((ULONG *)(((UBYTE *)x) + 6)) == MAKE_ID('J','F','I','F'))); } -BOOL MatchPNG (UBYTE *x) { return(*((ULONG *)x) == 0x89504e47 && ((ULONG *)x)[1] == 0x0d0a1a0a); } -BOOL MatchDT (UNUSED UBYTE *x) { return(TRUE); } -#define GIFDecoder "gif.decoder" -#define JPGDecoder "jfif.decoder" -#define PNGDecoder "png.decoder" -#define DTDecoder "datatype.decoder" - -struct DecoderInfo Decoders[] = -{ - { GIFDecoder, MatchGIF, NULL, NULL }, - { JPGDecoder, MatchJPG, NULL, NULL }, - { PNGDecoder, MatchPNG, NULL, NULL }, - { DTDecoder, MatchDT, NULL, NULL }, - { NULL, NULL, NULL, NULL } -}; - -CONST_STRPTR DecoderPaths[] = +struct DecoderThreadStartupMessage { - "MUI:Libs/MUI/HTMLview/", - "LIBS:MUI/HTMLview/", - "MUI:HTMLview/", - "PROGDIR:Decoders/", - "PROGDIR:MUI/HTMLview/", - "LIBS:Decoders/", - NULL + struct Message message; + struct Args *args; }; -struct SignalSemaphore DecoderMutex; - -static struct Library *ClassOpen(CONST_STRPTR decoder) -{ - struct Library *result; - CONST_STRPTR *path = DecoderPaths; - - do { - - char name[64]; - strcpy(name, *path++); - strcat(name, decoder); - - result = OpenLibrary(name, 0); +/* Accumulate diagnostic output in a ring-style buffer and rewrite + T:htmlview_dt.log on each call. Avoids seek/append portability issues + across OS3/OS4/MorphOS. */ +static char DTLogBuf[8192]; +static ULONG DTLogLen = 0; - } while(!result && *path); - - return(result); -} - - -Object *NewDecoderObjectA(UBYTE *buf,struct TagItem *attrs) +static void DTLog(const char *line) { - struct TagItem *tags = attrs; - struct IClass *cl = NULL; - struct DecoderInfo *decoders = Decoders; - - //return 0; - ObtainSemaphore(&DecoderMutex); - - while(decoders->Name && !cl) - { - if(decoders->MatchFunc(buf)) + ULONG need = strlen(line) + 1; + if (DTLogLen + need + 1 >= sizeof(DTLogBuf)) + return; /* silently drop if overflow */ + memcpy(DTLogBuf + DTLogLen, line, need - 1); + DTLogLen += need - 1; + DTLogBuf[DTLogLen++] = '\n'; + DTLogBuf[DTLogLen] = 0; + + BPTR f = Open((STRPTR)"T:htmlview_dt.log", MODE_NEWFILE); + if (f) { - if(!(cl = decoders->Class)) - { - if((decoders->Base = ClassOpen(decoders->Name))) - { - if((cl = MakeClass(NULL, NULL, GetImageDecoderClass(decoders->Base), sizeof(DecoderData), 0L))) - { - #if defined(__amigaos3__) - cl->cl_Dispatcher.h_SubEntry = (ULONG (*)())ENTRY(DecoderDispatcher); - cl->cl_Dispatcher.h_Entry = (ULONG (*)())HookEntry; - cl->cl_Dispatcher.h_Data = 0; - #else - cl->cl_Dispatcher.h_SubEntry = 0; - cl->cl_Dispatcher.h_Entry = (HOOKFUNC)ENTRY(DecoderDispatcher); - cl->cl_Dispatcher.h_Data = 0; - #endif - - decoders->Class = cl; - } - } - } + Write(f, DTLogBuf, DTLogLen); + Close(f); } - decoders++; - } - - ReleaseSemaphore(&DecoderMutex); - - if(cl) - { - return((Object *)NewObjectA(cl, NULL, tags)); - } - - else return(NULL); -} - -CONSTRUCTOR(PrepareDecoders, 4) -{ - memset(&DecoderMutex,0,sizeof(struct SignalSemaphore)); - InitSemaphore(&DecoderMutex); -} - -DESTRUCTOR(FlushDecoders, 4) -{ - struct DecoderInfo *decoders = Decoders; - while(decoders->Name) - { - if(decoders->Class) - FreeClass(decoders->Class); - if(decoders->Base) - CloseLibrary(decoders->Base); - - decoders++; - } } extern "C" void DecoderThread(void) @@ -848,68 +519,222 @@ extern "C" void DecoderThread(void) if((startup = (struct DecoderThreadStartupMessage *)GetMsg(&me->pr_MsgPort)) != NULL) { struct Args *args = startup->args; + struct Task *parent = args->ParentTask; + ULONG sigbit = args->SigBit; ReplyMsg((struct Message *)startup); BOOL result = FALSE; struct ImageList *image = args->Img; - ULONG width = image->Width, height = image->Height; - BOOL dither = args->Data->Share->DitherType; - ULONG gamma = args->Data->Share->GammaCorrection; struct Hook *loadhook = args->Data->ImageLoadHook; struct HTMLview_LoadMsg loadmsg; - loadmsg.lm_App = _app(args->Obj); + loadmsg.lm_App = args->App; + + { + char logbuf[256]; + sprintf(logbuf, "thread start url=%s", args->Name); + DTLog(logbuf); + } + D(DBF_STARTUP, "DecoderThread: started for %s", args->Name); struct DecodeItem *item = new (std::nothrow) struct DecodeItem(args->Obj, args->Data, image); if (item) { args->Data->Share->DecodeQueue.InsertElm(item); - Signal(args->ParentTask, 1 << args->SigBit); + Signal(parent, 1 << sigbit); loadmsg.lm_Type = HTMLview_Open; loadmsg.lm_PageID = item->PageID; loadmsg.lm_Params.lm_Open.URL = args->Name; loadmsg.lm_Params.lm_Open.Flags = MUIF_HTMLview_LoadMsg_Image; - if(CallHookPkt(loadhook, args->Obj, &loadmsg)) + + ULONG openres = CallHookPkt(loadhook, args->Obj, &loadmsg); + { + char logbuf[64]; + sprintf(logbuf, " hook-open => %lu", openres); + DTLog(logbuf); + } + if(openres) { - UBYTE buf[12]; - loadmsg.lm_Type = HTMLview_Read; - loadmsg.lm_Params.lm_Read.Buffer = (char *)buf; - loadmsg.lm_Params.lm_Read.Size = 10; - ULONG len = CallHookPkt(loadhook, args->Obj, &loadmsg); - - struct TagItem attrs[] = - {{IDA_StartBuffer, (ULONG)buf}, - {IDA_BytesInBuffer,(ULONG)len}, - {IDA_Width, (ULONG)width}, - {IDA_Height, (ULONG)height}, - {IDA_Screen, (ULONG)args->Scr}, - {IDA_HTMLview, (ULONG)args->Obj}, - {IDA_LoadHook, (ULONG)loadhook}, - {IDA_LoadMsg, (ULONG)&loadmsg}, - {IDA_StatusStruct, (ULONG)item}, - {IDA_NoDither, (ULONG)dither}, - {IDA_Gamma, (ULONG)gamma}, - {TAG_DONE,0}}; - Object *decoder = NewDecoderObjectA(buf,attrs); - - if(decoder) + D(DBF_STARTUP, "DecoderThread: hook open success"); + char tmpname[64]; + STRPTR ext = (STRPTR)""; + STRPTR url = args->Name; + if (strstr(url, ".png") || strstr(url, ".PNG")) ext = (STRPTR)".png"; + else if (strstr(url, ".gif") || strstr(url, ".GIF")) ext = (STRPTR)".gif"; + else if (strstr(url, ".jpg") || strstr(url, ".JPG") || strstr(url, ".jpeg")) ext = (STRPTR)".jpg"; + + sprintf(tmpname, "T:hv_%lx_%lx%s", (ULONG)item->PageID, (ULONG)FindTask(NULL), ext); + BPTR tmpf = Open(tmpname, MODE_NEWFILE); + { + char logbuf[128]; + sprintf(logbuf, " open(%s, NEWFILE) => %lx", tmpname, (ULONG)tmpf); + DTLog(logbuf); + } + if (tmpf) { - result = DoMethod(decoder, IDM_Decode); - DisposeObject(decoder); + char *buf = new (std::nothrow) char[8192]; + if (buf) + { + LONG rd; + ULONG total = 0; + loadmsg.lm_Type = HTMLview_Read; + loadmsg.lm_Params.lm_Read.Buffer = buf; + loadmsg.lm_Params.lm_Read.Size = 8192; + + while ((rd = CallHookPkt(loadhook, args->Obj, &loadmsg)) > 0) + { + Write(tmpf, buf, rd); + total += rd; + if (item->Abort) break; + } + delete[] buf; + { + char logbuf[64]; + sprintf(logbuf, " recv %lu bytes", total); + DTLog(logbuf); + } + D(DBF_STARTUP, "DecoderThread: wrote %lu bytes to %s", total, tmpname); + } + Close(tmpf); + + if (!item->Abort) + { + Object *dt = NewDTObject(tmpname, + DTA_GroupID, GID_PICTURE, + DTA_SourceType, DTST_FILE, + PDTA_DestMode, PMODE_V43, + PDTA_Remap, TRUE, + PDTA_Screen, args->Scr, + PDTA_UseFriendBitMap, TRUE, + TAG_DONE); + + { + char logbuf[128]; + sprintf(logbuf, " NewDTObject(%s) => %lx ioerr=%ld", + tmpname, (ULONG)dt, IoErr()); + DTLog(logbuf); + } + if (dt) + { + D(DBF_STARTUP, "DecoderThread: DT created"); + + /* Trigger layout / colour remap for the target screen. */ + DoMethod(dt, DTM_PROCLAYOUT, NULL, 1L); + + struct BitMapHeader *bmhd = NULL; + struct BitMap *srcbmp = NULL; + UBYTE *mask = NULL; + + GetDTAttrs(dt, + PDTA_BitMapHeader, (ULONG)&bmhd, + PDTA_DestBitMap, (ULONG)&srcbmp, + PDTA_MaskPlane, (ULONG)&mask, + TAG_DONE); + + /* PDTA_DestBitMap is only populated after PROCLAYOUT; + fall back to the raw source bitmap otherwise. */ + if (!srcbmp) + GetDTAttrs(dt, PDTA_BitMap, (ULONG)&srcbmp, TAG_DONE); + + ULONG width = 0, height = 0; + if (bmhd) + { + width = bmhd->bmh_Width; + height = bmhd->bmh_Height; + } + else + { + GetDTAttrs(dt, + DTA_NominalHoriz, (ULONG)&width, + DTA_NominalVert, (ULONG)&height, + TAG_DONE); + } + + { + char logbuf[160]; + sprintf(logbuf, + " DT %lux%lu srcbmp=%lx mask=%lx bmhd=%lx", + width, height, (ULONG)srcbmp, (ULONG)mask, (ULONG)bmhd); + DTLog(logbuf); + } + D(DBF_STARTUP, "DecoderThread: DT %lux%lu src=%lx mask=%lx", + width, height, (ULONG)srcbmp, (ULONG)mask); + + struct DecoderData decData; + memset(&decData, 0, sizeof(decData)); + decData.Scr = args->Scr; + decData.StatusItem = item; + decData.HTMLview = args->Obj; + decData.LoadHook = loadhook; + decData.LoadMsg = &loadmsg; + + RenderEngine render(args->Scr, &decData); + BOOL alloc_ok = FALSE; + if (srcbmp && width > 0 && height > 0) + { + alloc_ok = render.AllocateFrame(width, height, 0, DisposeNOP, 0, 0, NULL, + mask ? TransparencySINGLE : TransparencyNONE, PicFLG_Full); + } + { + char logbuf[128]; + sprintf(logbuf, " AllocateFrame => %s item->Picture=%lx", + alloc_ok ? "OK" : "FAIL", (ULONG)item->Picture); + DTLog(logbuf); + } + if (alloc_ok && item->Picture && item->Picture->BMp) + { + /* BltBitMap both src and dest are friend bitmaps + of args->Scr, so this is a straight copy with + any necessary on-the-fly format conversion. */ + BltBitMap(srcbmp, 0, 0, + item->Picture->BMp, 0, 0, + width, height, + 0xC0, 0xFF, NULL); + WaitBlit(); + + if (mask && item->Picture->Mask) + CopyMem(mask, item->Picture->Mask, RASSIZE(width, height)); + + item->Enter(); + item->CurrentY = height; + item->Started = FALSE; + item->Leave(); + result = TRUE; + DTLog(" blit OK"); + D(DBF_STARTUP, "DecoderThread: blit OK"); + } + else + { + char logbuf[160]; + sprintf(logbuf, + " decode failed: srcbmp=%lx w=%lu h=%lu alloc=%s", + (ULONG)srcbmp, width, height, alloc_ok ? "OK" : "FAIL"); + DTLog(logbuf); + D(DBF_STARTUP, "DecoderThread: no source bitmap / AllocateFrame failed"); + } + DisposeDTObject(dt); + } else { + D(DBF_STARTUP, "DecoderThread: NewDTObject failed for %s", tmpname); + } + } + DeleteFile(tmpname); } loadmsg.lm_Type = HTMLview_Close; CallHookPkt(loadhook, args->Obj, &loadmsg); + } else { + D(DBF_STARTUP, "DecoderThread: hook open failed for %s", args->Name); } - delete args; - item->Enter(); item->Status = result ? StatusDone : StatusError; item->Thread = NULL; item->Leave(); + + Signal(parent, 1 << args->MainSigBit); } + delete args; } } @@ -918,12 +743,6 @@ VOID DecodeImage (Object *obj, UNUSED struct IClass *cl, struct ImageList *image LONG sigbit = 0; if(image && (sigbit = AllocSignal(-1)) > 0) { - if(SetSignal(0L, 1 << sigbit) & (1 << sigbit)) - { - DisplayBeep(NULL); - W(DBF_STARTUP, "Signal was already pending!\n"); - } - STRPTR name = NULL; struct List *pscrs = LockPubScreenList(); for(struct Node *node = pscrs->lh_Head; node->ln_Succ; node = node->ln_Succ) @@ -942,10 +761,9 @@ VOID DecodeImage (Object *obj, UNUSED struct IClass *cl, struct ImageList *image if (!args) { UnlockPubScreen(NULL,lock); + FreeSignal(sigbit); return; } - /*char str_args[10]; - sprintf(str_args, "%lx", (ULONG)args);*/ #if defined(__PPC__) static const BOOL FBlit = FALSE; @@ -987,16 +805,15 @@ VOID DecodeImage (Object *obj, UNUSED struct IClass *cl, struct ImageList *image startup.args = args; PutMsg(&thread->pr_MsgPort, (struct Message *)&startup); - Remove((struct Node *)WaitPort(&replyPort)); + + WaitPort(&replyPort); + while (GetMsg(&replyPort)); Wait(1 << sigbit); + } else { + delete args; } FreeSignal(sigbit); } - else - { - DisplayBeep(NULL); - W(DBF_STARTUP, "No image! - %s (%ld)\n", image->ImageName, sigbit); - } } diff --git a/mcc/LibLoad_Test.c b/mcc/LibLoad_Test.c index 877dcc4..aa0df24 100644 --- a/mcc/LibLoad_Test.c +++ b/mcc/LibLoad_Test.c @@ -1,3 +1,4 @@ + #include #include #include @@ -5,135 +6,263 @@ #include #include #include - -#ifndef MUIA_HTMLview_Contents -#define MUIA_HTMLview_Contents 0xad003005 +#include +#if defined(__MORPHOS__) +#include +#elif defined(__amigaos4__) +#include #endif +#include #if defined(__amigaos4__) -struct Library *IntuitionBase; -struct Library *MUIMasterBase; +struct Library *IntuitionBase; +struct Library *MUIMasterBase; struct IntuitionIFace *IIntuition; struct MUIMasterIFace *IMUIMaster; +struct Library *SocketBase; +struct SocketIFace *ISocket; #else struct IntuitionBase *IntuitionBase; -struct DosLibrary *DOSBase; -struct Library *MUIMasterBase; +struct Library *MUIMasterBase; +struct Library *SocketBase; #endif -static void ShowError(char *msg) -{ - struct EasyStruct es = { - sizeof(struct EasyStruct), - 0, - (UBYTE *)"LibLoad_Test Error", - (UBYTE *)msg, - (UBYTE *)"OK" - }; - EasyRequestArgs(NULL, &es, 0, NULL); -} +#include "test_image_hook.h" -static void LogMsg(BPTR fh, char *msg) -{ - if (fh) VFPrintf(fh, "%s\n", (APTR)msg); -} +enum { + MSG_OPEN_HTMLVIEW = 1, + MSG_QUIT = 2, +}; -int main(int argc, char **argv) -{ - Object *app = NULL; - Object *win = NULL; - Object *html = NULL; - int result = 0; - BPTR fh = NULL; +/* Same HTML as SimpleTest so the two programs cover identical capability + surface -- the difference being when the HTMLview window materialises + (dynamic load vs. up-front creation). */ +static const char *test_html = + "" + "

HTMLView MCC Test Suite

" + "

This is the dynamic-load variant. The MCC is opened only when you " + "click the button, exercising the MUI class-path lookup.

" - DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 0); - if (!DOSBase) return 20; + "

Images - Local Files

" + "

Local image (PROGDIR:test.png):

" + "

\"Local

" + "

Sized: \"Sized\"

" + "

\"Centered\"

" - fh = Open("t:libload_test.txt", MODE_NEWFILE); + "

Images - Network (HTTP)

" + "

Network images require bsdsocket.library:

" + "

\"Aminet

" + "

\"AmigaWorld

" - LogMsg(fh, "LibLoad_Test: Starting..."); + "

Text

" + "

Bold, italic, under, strike, tt

" + "

Visit Aminet or " + "OS4Depot.

" - IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39); - if (!IntuitionBase) - { - LogMsg(fh, "Failed intuition.library"); - if (fh) Close(fh); - ShowError("Failed to open intuition.library"); - return 20; - } - LogMsg(fh, "Opened intuition.library OK"); + "

Headings

" + "

H1

H2

H3

H4

H5
H6
" + + "

Lists

" + "
  • First
  • Second with bold
" + "
  1. Step one
  2. Step two
" + "
MCC
MUI Custom Class
" + + "

Tables

" + "" + "" + "" + "" + "
ABC
123
4boldlink
" + "

Forms

" + "
" + "

Text:

" + "

a " + " b

" + "

yes " + " no

" + "" + "" + "

" + "
" + + "

Preformatted

" + "
int main() {\n    return 0;\n}
" + + "

Colors & Entities

" + "

Red, " + "Green, " + "Blue

" + "

© ® ™ < > &

" + + "

End of Test Suite

" + ""; + +int main(int argc, char **argv) +{ + Object *app = NULL, *mainWin = NULL, *htmlWin = NULL, *htmlObj = NULL, *btnOpen = NULL; + struct Hook loadHook; + ULONG signals = 0; + (void)argc; + (void)argv; + + SocketBase = OpenLibrary("bsdsocket.library", 4); +#if defined(__amigaos4__) + if (SocketBase) + ISocket = (struct SocketIFace *)GetInterface(SocketBase, "main", 1, NULL); +#endif + /* Socket library is optional -- we still support local files without it. */ + +#if defined(__amigaos4__) + IntuitionBase = OpenLibrary("intuition.library", 39); + if (IntuitionBase) + IIntuition = (struct IntuitionIFace *)GetInterface(IntuitionBase, "main", 1, NULL); + MUIMasterBase = OpenLibrary("muimaster.library", 19); + if (MUIMasterBase) + IMUIMaster = (struct MUIMasterIFace *)GetInterface(MUIMasterBase, "main", 1, NULL); + if (!IntuitionBase || !MUIMasterBase || !IIntuition || !IMUIMaster) +#else + IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39); MUIMasterBase = OpenLibrary("muimaster.library", 19); - if (!MUIMasterBase) + if (!IntuitionBase || !MUIMasterBase) +#endif { - LogMsg(fh, "Failed muimaster.library"); - if (fh) Close(fh); - ShowError("Failed to open muimaster.library"); - goto cleanup_intuition; + goto cleanup; } - LogMsg(fh, "Opened muimaster.library OK"); - LogMsg(fh, "MUI will find HTMLview.mcc via its class path (MUI:Libs/mui)"); - LogMsg(fh, "Creating MUI application..."); + /* CallHookPkt on m68k and MorphOS passes args in registers (a0=hook, + a2=obj, a1=msg); our plain-C TestImageHookFunc expects stack args. + HookEntry is the amiga.lib trampoline that does the register->stack + conversion. On OS4 hooks are already called with stack args, and + HookEntry is not declared, so we assign directly. */ +#if defined(__amigaos4__) + loadHook.h_Entry = (HOOKFUNC)TestImageHookFunc; + loadHook.h_SubEntry = NULL; +#else + loadHook.h_Entry = (HOOKFUNC)HookEntry; + loadHook.h_SubEntry = (HOOKFUNC)TestImageHookFunc; +#endif + loadHook.h_Data = NULL; + app = MUI_NewObject(MUIC_Application, - MUIA_Application_Title, (ULONG)"LibLoad Test", - MUIA_Application_Version, (ULONG)"$VER: LibLoad_Test 1.0", + MUIA_Application_Title, (ULONG)"LibLoad Test", + MUIA_Application_Version, (ULONG)"$VER: LibLoad_Test 1.1 (18.4.2026)", MUIA_Application_SingleTask, TRUE, - MUIA_Application_Window, win = MUI_NewObject(MUIC_Window, - MUIA_Window_Title, (ULONG)"HTMLView Library Load Test", - MUIA_Window_RootObject, html = MUI_NewObject("HTMLview.mcc", - MUIA_Background, MUII_TextBack, + MUIA_Application_Window, mainWin = MUI_NewObject(MUIC_Window, + MUIA_Window_Title, (ULONG)"HTMLView Library Load Test", + MUIA_Window_Width, 400, + MUIA_Window_Height, 200, + MUIA_Window_RootObject, MUI_NewObject(MUIC_Group, + MUIA_Group_Child, MUI_NewObject(MUIC_Text, + MUIA_Text_Contents, (ULONG) + "\nLibLoad Test Program\n\n" + "Demonstrates loading HTMLview.mcc dynamically.\n" + "Click the button to open the test document.\n", + MUIA_Text_PreParse, (ULONG)"\33c", + TAG_DONE), + MUIA_Group_Child, btnOpen = MUI_NewObject(MUIC_Text, + MUIA_Frame, MUIV_Frame_Button, + MUIA_Text_Contents, (ULONG)"[ Open HTMLView Test ]", + MUIA_Text_PreParse, (ULONG)"\33c", + MUIA_InputMode, MUIV_InputMode_RelVerify, + TAG_DONE), TAG_DONE), TAG_DONE), TAG_DONE); - if (!app) - { - LogMsg(fh, "FAILED to create Application!"); - LogMsg(fh, "MUI could not find HTMLview.mcc in its class path."); - LogMsg(fh, "Make sure HTMLview.mcc is in MUI:Libs/mui/"); - if (fh) Close(fh); - ShowError("LibLoad_Test: Failed!\nMUI cannot find HTMLview.mcc.\nMake sure it is in MUI:Libs/mui/"); - result = 20; - goto cleanup_muimaster; - } + if (!app) goto cleanup; - LogMsg(fh, "Application created OK - MUI found HTMLview.mcc!"); + DoMethod(btnOpen, MUIM_Notify, MUIA_Selected, MUIV_EveryTime, + app, 2, MUIM_Application_ReturnID, MSG_OPEN_HTMLVIEW); - SetAttrs(html, MUIA_HTMLview_Contents, - (ULONG)"

LibLoad Test

If you see this, HTMLview.mcc loads correctly!

", - TAG_DONE); + DoMethod(mainWin, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, + app, 2, MUIM_Application_ReturnID, MSG_QUIT); - LogMsg(fh, "Opening window..."); - SetAttrs(win, MUIA_Window_Open, TRUE, TAG_DONE); + SetAttrs(mainWin, MUIA_Window_Open, TRUE, TAG_DONE); - ULONG open = 0; - GetAttr(MUIA_Window_Open, win, &open); - if (open) - { - LogMsg(fh, "TEST PASSED!"); - if (fh) Close(fh); - ShowError("LibLoad_Test: TEST PASSED!\nHTMLview.mcc loaded successfully!"); - } - else + BOOL running = TRUE; + while (running) { - LogMsg(fh, "Window failed to open"); - if (fh) Close(fh); - ShowError("Window failed to open."); + ULONG id = DoMethod(app, MUIM_Application_NewInput, &signals); + + if (id == MSG_OPEN_HTMLVIEW && !htmlWin) + { + /* Create the MCC with only the hooks -- the image decoder needs + a screen, which is only known once the window is open. Setting + Contents at creation time queues image decode with no screen + and the decoded bitmaps end up unused. We mirror SimpleTest: + open the window first, THEN push Contents via SetAttrs. */ + htmlObj = MUI_NewObject("HTMLview.mcc", + MUIA_HTMLview_ImageLoadHook, (ULONG)&loadHook, + MUIA_HTMLview_LoadHook, (ULONG)&loadHook, + TAG_DONE); + + if (htmlObj) + { + htmlWin = MUI_NewObject(MUIC_Window, + MUIA_Window_Title, (ULONG)"HTMLView Test Content", + MUIA_Window_Width, 800, + MUIA_Window_Height, 600, + MUIA_Window_RootObject, MUI_NewObject(MUIC_Scrollgroup, + MUIA_Scrollgroup_FreeVert, TRUE, + MUIA_Scrollgroup_FreeHoriz, TRUE, + MUIA_Scrollgroup_Contents, htmlObj, + TAG_DONE), + TAG_DONE); + + if (htmlWin) + { + DoMethod(htmlWin, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, + app, 2, MUIM_Application_ReturnID, MSG_QUIT); + DoMethod(app, OM_ADDMEMBER, htmlWin); + SetAttrs(htmlWin, MUIA_Window_Open, TRUE, TAG_DONE); + + ULONG open = 0; + GetAttr(MUIA_Window_Open, htmlWin, &open); + if (open) + SetAttrs(htmlObj, MUIA_HTMLview_Contents, (ULONG)test_html, TAG_DONE); + } + } + } + else if (id == MSG_QUIT) + { + running = FALSE; + } + + if (running && signals) + { + ULONG got = Wait(signals | SIGBREAKF_CTRL_C); + if (got & SIGBREAKF_CTRL_C) running = FALSE; + } } - Delay(50); - SetAttrs(win, MUIA_Window_Open, FALSE, TAG_DONE); + if (htmlWin) SetAttrs(htmlWin, MUIA_Window_Open, FALSE, TAG_DONE); + if (mainWin) SetAttrs(mainWin, MUIA_Window_Open, FALSE, TAG_DONE); -cleanup_htmlview: +cleanup: if (app) MUI_DisposeObject(app); -cleanup_muimaster: +#if defined(__amigaos4__) + if (MUIMasterBase) + { + if (IMUIMaster) DropInterface((struct Interface *)IMUIMaster); + CloseLibrary(MUIMasterBase); + } + if (IntuitionBase) + { + if (IIntuition) DropInterface((struct Interface *)IIntuition); + CloseLibrary(IntuitionBase); + } + if (SocketBase) + { + if (ISocket) DropInterface((struct Interface *)ISocket); + CloseLibrary(SocketBase); + } +#else if (MUIMasterBase) CloseLibrary(MUIMasterBase); - -cleanup_intuition: if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase); - if (DOSBase) CloseLibrary((struct Library *)DOSBase); + if (SocketBase) CloseLibrary(SocketBase); +#endif - return result; + return 0; } diff --git a/mcc/Makefile b/mcc/Makefile index c05afca..f1ed190 100644 --- a/mcc/Makefile +++ b/mcc/Makefile @@ -171,7 +171,7 @@ ifeq ($(OS), os4) CFLAGS += -mcrt=$(CRT) -D__USE_INLINE__ -D__NEW_TIMEVAL_DEFINITION_USED__ \ -D__USE_CLASSIC_MINTERM__ -Wa,-mregnames -DNO_PPCINLINE_STDARG \ -fno-exceptions -fno-threadsafe-statics - LDFLAGS += -mcrt=$(CRT) -nostartfiles + LDFLAGS += -mcrt=$(CRT) -nostartfiles -Wl,-u,_startu LDLIBS += -lauto # additional object files required @@ -420,7 +420,7 @@ CATALOGS = $(LOCALE)/deutsch.catalog # # Build test programs for all platforms -all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(SIMPLETARGET) $(LIBLOADTARGET) +all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(SIMPLETARGET) $(LIBLOADTARGET) $(BINDIR)/test.png # make the object directories $(OBJDIR): @@ -477,6 +477,11 @@ $(MCCTARGET) : $(MCCOBJS) @$(CXX) $(LDFLAGS) -o $@.debug $(MCCOBJS) $(LDLIBS) -Wl,-Map,$@.map @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug +# Copy the sample PNG used by the two test programs into the binary dir. +$(BINDIR)/test.png: testdata/test.png | $(BINDIR) + @echo " CP $@" + @cp testdata/test.png $@ + $(OBJDIR)/library.o: library.c ../include/mccinit.c \ HTMLview_mcc.h private.h rev.h diff --git a/mcc/SimpleTest.c b/mcc/SimpleTest.c index a7227be..ce4753c 100644 --- a/mcc/SimpleTest.c +++ b/mcc/SimpleTest.c @@ -1,114 +1,250 @@ - -#include -#include -#include -#include -#include -#include - -#if defined(__amigaos4__) -#define kprintf(...) ((void)0) -#else -extern void kprintf(const char *fmt, ...); -#endif - -struct Library *MUIMasterBase; -#if defined(__amigaos4__) -struct Library *IntuitionBase; -#else -struct IntuitionBase *IntuitionBase; -#endif -struct Library *UtilityBase; - -// Simple MUI macros if missing -#ifndef MUI_Set -#define MUI_Set(o,a,v) SetAttrs(o,a,v,TAG_DONE) -#endif -#ifndef MUI_GetVal -#define MUI_GetVal(o,a) ({ ULONG v; GetAttr(a,o,&v); v; }) -#endif - -int main(void) -{ - Object *app, *win, *html; - - IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39); - MUIMasterBase = OpenLibrary("muimaster.library", 19); - - if (!IntuitionBase || !MUIMasterBase) - { - kprintf("SimpleTest: Failed to open libraries\n"); - return 20; - } - - app = MUI_NewObject(MUIC_Application, - MUIA_Application_Title , "HTMLView Simple Test", - MUIA_Application_Version , "$VER: SimpleTest 1.0 (15.12.2025)", - MUIA_Application_SingleTask , TRUE, - MUIA_Application_Window , win = MUI_NewObject(MUIC_Window, - MUIA_Window_Title, "HTMLView Test Window", - MUIA_Window_RootObject, html = MUI_NewObject("HTMLview.mcc", - MUIA_Background, MUII_TextBack, - TAG_DONE), - TAG_DONE), - TAG_DONE); - - if (!app) - { - kprintf("SimpleTest: Failed to create Application object\n"); - } - else - { - SetAttrs(win, MUIA_Window_Open, TRUE, TAG_DONE); - - static const char *test_html = - "" - "

HTMLView Test

" - "

This is a bold paragraph.

" - "

This is an italic paragraph.

" - "

This is a link.

" - ""; - - // Correct ID for HTMLview contents - #define MUIA_HTMLview_Contents 0xad003005 - SetAttrs(html, MUIA_HTMLview_Contents, test_html, TAG_DONE); - - ULONG val = 0; - GetAttr(MUIA_Window_Open, win, &val); - if (val) - { - ULONG sigs = 0; - BOOL running = TRUE; - - DoMethod(win, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, - app, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit); - - while(running) - { - ULONG id = DoMethod(app, MUIM_Application_NewInput, &sigs); - - if (id == MUIV_Application_ReturnID_Quit) { - running = FALSE; - } - - if (running && sigs) { - ULONG got = Wait(sigs | SIGBREAKF_CTRL_C); - if (got & SIGBREAKF_CTRL_C) { - running = FALSE; - } - } - } - } - else - { - kprintf("SimpleTest: Window failed to open.\n"); - } - - MUI_DisposeObject(app); - } - - if (MUIMasterBase) CloseLibrary(MUIMasterBase); - if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase); - - return 0; -} + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__MORPHOS__) +#include +#elif defined(__amigaos4__) +#include +#endif + +#if defined(__amigaos4__) +struct Library *IntuitionBase; +struct Library *MUIMasterBase; +struct Interface *IntuitionIFace; +struct MUIMasterIFace *IMUIMaster; +struct Library *SocketBase; +struct SocketIFace *ISocket; +#else +struct Library *MUIMasterBase; +struct IntuitionBase *IntuitionBase; +struct Library *SocketBase; +#endif +struct Library *UtilityBase; + +#if defined(DEBUG) +extern void kprintf(const char *fmt, ...); +#else +static void kprintf(const char *fmt, ...) { (void)fmt; } +#endif + +#include "test_image_hook.h" + +static struct Hook ImageLoadHook; + +/* Keep this HTML blob self-contained; PROGDIR:test.png is copied into the + binary directory by the Makefile, and the HTTP entries exercise the + bsdsocket/chunked path in the shared hook. */ +static const char *test_html = + "" + "

HTMLView MCC Test Suite

" + "

This is a comprehensive test demonstrating HTMLview features.

" + + "

Images - Local Files

" + "

Local image (PROGDIR:test.png):

" + "

\"Local

" + "

Same image sized to 64x64:

" + "

\"Sized

" + "

Centered with border:

" + "

\"Centered\"

" + + "

Images - Network (HTTP)

" + "

These need bsdsocket.library and a working network stack:

" + "

\"Aminet

" + "

\"AmigaWorld

" + + "

Text Formatting

" + "

Bold text, italic text, " + "underlined text, strikethrough text, " + "monospace text

" + "

Combination: Bold Italic Underlined

" + + "

Links

" + "

Visit Aminet or " + "OS4Depot.

" + "

Links can have bold text inside like this.

" + + "

Headings

" + "

H1

H2

H3

" + "

H4

H5
H6
" + + "

Lists

" + "
  • First
  • Second
  • Third with bold
" + "
  1. Step one
  2. Step two
  3. Step three
" + "
" + "
HTML
HyperText Markup Language
" + "
CSS
Cascading Style Sheets
" + "
" + + "

Tables

" + "" + "" + "" + "" + "
Column 1Column 2Column 3
Row 1Cell 2Cell 3
Row 2boldlink
" + + "

Forms

" + "
" + "

Text:

" + "

Pass:

" + "

Option A " + " Option B

" + "

Yes " + " No

" + "" + "" + "

" + "

" + "
" + + "

Preformatted

" + "

Inline: printf(\"hi\");

" + "
function example() {\n    return 42;\n}
" + "

Line 1
Line 2
Line 3

" + + "

Font Styles

" + "

Big, small, " + "strong, em

" + "

Red, " + "Green, " + "Blue

" + + "

Entities

" + "

© ® ™ < > & ← →

" + + "

End of Test Suite

" + ""; + +int main(void) +{ + Object *app, *win, *html; + +#if defined(__amigaos4__) + IntuitionBase = OpenLibrary("intuition.library", 39); + if (IntuitionBase) + IntuitionIFace = GetInterface(IntuitionBase, "main", 1, NULL); + MUIMasterBase = OpenLibrary("muimaster.library", 19); + if (MUIMasterBase) + IMUIMaster = (struct MUIMasterIFace *)GetInterface(MUIMasterBase, "main", 1, NULL); + if (!IntuitionBase || !MUIMasterBase || !IntuitionIFace || !IMUIMaster) +#else + IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39); + MUIMasterBase = OpenLibrary("muimaster.library", 19); + if (!IntuitionBase || !MUIMasterBase) +#endif + { + kprintf("SimpleTest: Failed to open libraries\n"); + return 20; + } + + SocketBase = OpenLibrary("bsdsocket.library", 4); +#if defined(__amigaos4__) + if (SocketBase) + ISocket = (struct SocketIFace *)GetInterface(SocketBase, "main", 1, NULL); +#endif + + /* CallHookPkt on m68k and MorphOS passes args in registers (a0=hook, + a2=obj, a1=msg); our plain-C TestImageHookFunc expects stack args. + HookEntry is the amiga.lib trampoline that does the register->stack + conversion. On OS4 hooks are already called with stack args, and + HookEntry is not declared, so we assign directly. */ +#if defined(__amigaos4__) + ImageLoadHook.h_Entry = (HOOKFUNC)TestImageHookFunc; + ImageLoadHook.h_SubEntry = NULL; +#else + ImageLoadHook.h_Entry = (HOOKFUNC)HookEntry; + ImageLoadHook.h_SubEntry = (HOOKFUNC)TestImageHookFunc; +#endif + ImageLoadHook.h_Data = NULL; + + app = MUI_NewObject(MUIC_Application, + MUIA_Application_Title , "HTMLView Simple Test", + MUIA_Application_Version , (ULONG)"$VER: SimpleTest 1.2 (18.4.2026)", + MUIA_Application_SingleTask , TRUE, + MUIA_Application_Window , win = MUI_NewObject(MUIC_Window, + MUIA_Window_Title, (ULONG)"HTMLView Test Window", + MUIA_Window_Width, 640, + MUIA_Window_Height, 480, + MUIA_Window_RootObject, MUI_NewObject(MUIC_Scrollgroup, + MUIA_Scrollgroup_FreeVert, TRUE, + MUIA_Scrollgroup_FreeHoriz, TRUE, + MUIA_Scrollgroup_Contents, html = MUI_NewObject("HTMLview.mcc", + MUIA_Background, MUII_TextBack, + MUIA_HTMLview_ImageLoadHook, (ULONG)&ImageLoadHook, + MUIA_HTMLview_LoadHook, (ULONG)&ImageLoadHook, + TAG_DONE), + TAG_DONE), + TAG_DONE), + TAG_DONE); + + if (!app) + { + kprintf("SimpleTest: Failed to create Application object\n"); + } + else + { + SetAttrs(win, MUIA_Window_Open, TRUE, TAG_DONE); + + SetAttrs(html, MUIA_HTMLview_Contents, (ULONG)test_html, TAG_DONE); + + ULONG open = 0; + GetAttr(MUIA_Window_Open, win, &open); + if (open) + { + DoMethod(win, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, + app, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit); + + ULONG sigs = 0; + BOOL running = TRUE; + while (running) + { + ULONG id = DoMethod(app, MUIM_Application_NewInput, &sigs); + if (id == MUIV_Application_ReturnID_Quit) running = FALSE; + + if (running && sigs) + { + ULONG got = Wait(sigs | SIGBREAKF_CTRL_C); + if (got & SIGBREAKF_CTRL_C) running = FALSE; + } + } + } + else + { + kprintf("SimpleTest: Window failed to open.\n"); + } + + MUI_DisposeObject(app); + } + + if (SocketBase) + { +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + } + if (MUIMasterBase) + { +#if defined(__amigaos4__) + if (IMUIMaster) DropInterface((struct Interface *)IMUIMaster); +#endif + CloseLibrary(MUIMasterBase); + } + if (IntuitionBase) + { +#if defined(__amigaos4__) + if (IntuitionIFace) DropInterface(IntuitionIFace); + CloseLibrary(IntuitionBase); +#else + CloseLibrary((struct Library *)IntuitionBase); +#endif + } + + return 0; +} diff --git a/mcc/test_image_hook.h b/mcc/test_image_hook.h new file mode 100644 index 0000000..52e1cfb --- /dev/null +++ b/mcc/test_image_hook.h @@ -0,0 +1,577 @@ +/* + * test_image_hook.h -- Shared image-load hook used by SimpleTest and + * LibLoad_Test to feed picture data to HTMLview.mcc. + * + * The hook resolves three kinds of URLs: + * - PROGDIR:, DH0:, etc. (direct dos Open) + * - file:// (stripped and dos Open) + * - http://host[:port]/p (bsdsocket.library GET, chunked aware) + * + * https:// URLs return failure (no TLS in the stock Amiga bsdsocket). + * + * The header is intentionally single-include, single-translation-unit. + * Include it from exactly one .c file in a test program; the program owns + * the SocketBase / ISocket globals (so it can close them on exit). + * + * Threading notes: the hook runs in the HTMLview decoder thread, not the + * main task. On m68k every task that uses bsdsocket must OpenLibrary the + * library itself -- the library base opened by main() isn't valid for our + * thread. We therefore open a task-local SocketBase in the hook and shadow + * the file-scope globals so the proto/socket.h inline macros dispatch + * through the local base. + */ + +#ifndef HTMLVIEW_TEST_IMAGE_HOOK_H +#define HTMLVIEW_TEST_IMAGE_HOOK_H + +#include +#include +#include +#include +#include +#include +#include +#include "HTMLview_mcc.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +/* The test programs carry their own globals for this; only used by the + main task. The hook uses its own task-local bases instead (see below). */ +#if defined(__amigaos4__) +extern struct Library *SocketBase; +extern struct SocketIFace *ISocket; +#else +extern struct Library *SocketBase; +#endif + +/* Per-transfer state stored in lm_Userdata. */ +struct THL_State +{ + BPTR file; /* file handle if this is a local file */ + LONG socket; /* bsdsocket handle, -1 if none */ + UBYTE *buffer; /* stashed body bytes from the initial HTTP recv */ + ULONG bufpos; + ULONG buflen; + int chunked; /* non-zero => Transfer-Encoding: chunked */ + ULONG chunk_left; + + /* Task-local bsdsocket bases -- opened by the decoder thread so it can + actually call socket/recv/etc.; closed on hook-Close. */ + struct Library *SBase; +#if defined(__amigaos4__) + struct SocketIFace *SIFace; +#endif +}; + +/* --- logging ----------------------------------------------------------- */ + +/* Accumulating-buffer + rewrite pattern (same as ImageManager DTLog) so the + log file stays coherent across OS3 and OS4 without relying on Seek. */ +static char THL_LogBuf[8192]; +static ULONG THL_LogLen = 0; + +static void THL_Log(const char *line) +{ + ULONG need = strlen(line) + 1; + if (THL_LogLen + need + 1 >= sizeof(THL_LogBuf)) return; + memcpy(THL_LogBuf + THL_LogLen, line, need - 1); + THL_LogLen += need - 1; + THL_LogBuf[THL_LogLen++] = '\n'; + THL_LogBuf[THL_LogLen] = 0; + BPTR f = Open((STRPTR)"T:htmlview_hook.log", MODE_NEWFILE); + if (f) { Write(f, THL_LogBuf, THL_LogLen); Close(f); } +} + +/* --- low-level helpers ------------------------------------------------- */ + +static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want); + +static LONG THL_RecvLine(struct THL_State *st, char *buf, LONG maxlen) +{ + /* Shadow file-scope bases with our task-local ones for proto/socket.h. */ + struct Library *SocketBase = st->SBase; +#if defined(__amigaos4__) + struct SocketIFace *ISocket = st->SIFace; +#endif + + LONG i = 0; + while (i < maxlen - 1) + { + UBYTE c; + LONG got = recv(st->socket, (APTR)&c, 1, 0); + if (got <= 0) return -1; + if (c == '\n') { buf[i] = 0; return i; } + if (c != '\r') buf[i++] = c; + } + buf[i] = 0; + return i; +} + +/* Reads up to `want` bytes, unwrapping chunked transfer encoding. */ +static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want) +{ + struct Library *SocketBase = st->SBase; +#if defined(__amigaos4__) + struct SocketIFace *ISocket = st->SIFace; +#endif + + LONG produced = 0; + while (produced < want) + { + if (st->chunk_left == 0) + { + char line[32]; + if (THL_RecvLine(st, line, sizeof(line)) < 0) + return produced; + if (line[0] == 0) + { + if (THL_RecvLine(st, line, sizeof(line)) < 0) + return produced; + } + ULONG size = strtoul(line, NULL, 16); + if (size == 0) return produced; + st->chunk_left = size; + } + + LONG take = want - produced; + if ((ULONG)take > st->chunk_left) take = (LONG)st->chunk_left; + + LONG got = recv(st->socket, (APTR)(out + produced), take, 0); + if (got <= 0) return produced; + produced += got; + st->chunk_left -= got; + } + return produced; +} + +/* Parses "http://host[:port]/path" into components (buffers owned by caller). */ +static int THL_ParseHttp(CONST_STRPTR url, char *host, ULONG hlen, + char *path, ULONG plen, ULONG *portp) +{ + const char *p = url; + if (strncmp(p, "http://", 7) != 0) return 0; + p += 7; + + const char *hs = p; + while (*p && *p != '/' && *p != ':') p++; + + ULONG n = (ULONG)(p - hs); + if (n == 0 || n >= hlen) return 0; + memcpy(host, hs, n); + host[n] = 0; + + ULONG port = 80; + if (*p == ':') + { + p++; + port = 0; + while (*p >= '0' && *p <= '9') { port = port * 10 + (*p - '0'); p++; } + if (port == 0 || port > 65535) return 0; + } + *portp = port; + + if (*p == 0) { strncpy(path, "/", plen); path[plen-1] = 0; return 1; } + + strncpy(path, p, plen - 1); + path[plen - 1] = 0; + return 1; +} + +/* Opens an HTTP connection, reads and parses response headers, stashes any + body bytes that came with the header recv. Returns 1 on success. */ +static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) +{ + char logbuf[320]; + + char host[256], path[1024]; + ULONG port = 80; + if (!THL_ParseHttp(url, host, sizeof(host), path, sizeof(path), &port)) + { + THL_Log("http: parse URL failed"); + return 0; + } + + sprintf(logbuf, "http: host=%s port=%lu path=%s", host, port, path); + THL_Log(logbuf); + + /* Open bsdsocket fresh for THIS task -- the decoder thread. On m68k the + library is opened per-task; the main task's SocketBase is not usable + from us. */ + struct Library *SocketBase = OpenLibrary((STRPTR)"bsdsocket.library", 4); +#if defined(__amigaos4__) + struct SocketIFace *ISocket = SocketBase + ? (struct SocketIFace *)GetInterface(SocketBase, "main", 1, NULL) + : NULL; + if (!SocketBase || !ISocket) + { + if (SocketBase && !ISocket) CloseLibrary(SocketBase); + THL_Log("http: OpenLibrary bsdsocket.library failed"); + return 0; + } +#else + if (!SocketBase) + { + THL_Log("http: OpenLibrary bsdsocket.library failed"); + return 0; + } +#endif + + struct hostent *he; +#if defined(__amigaos4__) + he = (struct hostent *)gethostbyname((STRPTR)host); +#else + he = gethostbyname((STRPTR)host); +#endif + if (!he) + { + sprintf(logbuf, "http: gethostbyname(%s) failed", host); + THL_Log(logbuf); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + LONG s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) + { + THL_Log("http: socket() failed"); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons((UWORD)port); + sin.sin_addr.s_addr = *((ULONG *)he->h_addr_list[0]); + + if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) + { + THL_Log("http: connect() failed"); + CloseSocket(s); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + char request[2048]; + int rlen = sprintf(request, + "GET %s HTTP/1.1\r\n" + "Host: %s\r\n" + "User-Agent: HTMLview-TestHook/1.1\r\n" + "Accept: */*\r\n" + "Connection: close\r\n" + "\r\n", + path, host); + + if (send(s, request, rlen, 0) < 0) + { + THL_Log("http: send() failed"); + CloseSocket(s); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + /* Read a moderate chunk so we're very likely to have all headers plus + some body in a single recv. */ + UBYTE *hdr = (UBYTE *)malloc(16384); + if (!hdr) + { + CloseSocket(s); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + LONG have = 0; + char *hdrend = NULL; + while (have < 16384 - 1) + { + LONG got = recv(s, (APTR)(hdr + have), 16384 - 1 - have, 0); + if (got <= 0) break; + have += got; + hdr[have] = 0; + if ((hdrend = strstr((char *)hdr, "\r\n\r\n")) != NULL) break; + if ((hdrend = strstr((char *)hdr, "\n\n")) != NULL) break; + } + + if (!hdrend) + { + THL_Log("http: no header terminator in reply"); + free(hdr); + CloseSocket(s); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + /* hdrend points at the first byte of the header/body separator. + Separator is either "\r\n\r\n" (4 bytes) or "\n\n" (2 bytes). + Compute body offset BEFORE null-terminating. */ + ULONG header_len = (*hdrend == '\r') + ? (ULONG)(hdrend - (char *)hdr) + 4 + : (ULONG)(hdrend - (char *)hdr) + 2; + + /* Null-terminate the header block so strstr on header values is safe. + Writing at hdrend clobbers only the first byte of the separator, + which we no longer need -- NOT the first body byte. */ + *hdrend = 0; + + { + /* Log the HTTP status line so we can spot 301/302 redirects, 404s, + etc. The header block also tells us whether the server returned + HTML instead of an image. */ + char status[120]; + ULONG n = 0; + while (n < sizeof(status) - 1 && n < header_len && + hdr[n] != '\r' && hdr[n] != '\n') + { + status[n] = (char)hdr[n]; + n++; + } + status[n] = 0; + sprintf(logbuf, "http: status=%s", status); + THL_Log(logbuf); + + /* Bail on non-200 responses -- redirects, 404s and 5xx must not + reach the decoder as "image body". Parse the numeric code. */ + int code = 0; + const char *sp = strchr(status, ' '); + if (sp) + { + while (*sp == ' ') sp++; + while (*sp >= '0' && *sp <= '9') { code = code*10 + (*sp - '0'); sp++; } + } + if (code != 200) + { + sprintf(logbuf, "http: aborting (status=%d)", code); + THL_Log(logbuf); + free(hdr); + CloseSocket(s); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + /* Helper: dig out a header value by name, log as "http: =". */ + const char *hdrs[] = { "Location:", "location:", + "Content-Type:", "content-type:", + "Content-Length:", "content-length:", + "Content-Encoding:", "content-encoding:" }; + const char *tags[] = { "location", "location", + "content-type", "content-type", + "content-length", "content-length", + "content-encoding", "content-encoding" }; + for (ULONG i = 0; i < sizeof(hdrs)/sizeof(hdrs[0]); i++) + { + const char *v = strstr((char *)hdr, hdrs[i]); + if (!v) continue; + v += strlen(hdrs[i]); + while (*v == ' ' || *v == '\t') v++; + const char *e = v; + while (*e && *e != '\r' && *e != '\n') e++; + ULONG L = (ULONG)(e - v); + if (L > sizeof(status) - 1) L = sizeof(status) - 1; + memcpy(status, v, L); + status[L] = 0; + sprintf(logbuf, "http: %s=%s", tags[i], status); + THL_Log(logbuf); + } + } + + if (strstr((char *)hdr, "Transfer-Encoding: chunked") || + strstr((char *)hdr, "transfer-encoding: chunked")) + st->chunked = 1; + + ULONG body_have = (ULONG)have - header_len; + if (body_have > 0) + { + st->buffer = (UBYTE *)malloc(body_have); + if (st->buffer) + { + memcpy(st->buffer, hdr + header_len, body_have); + st->buflen = body_have; + st->bufpos = 0; + } + } + + sprintf(logbuf, "http: OK header_len=%lu body_have=%lu chunked=%d", + header_len, body_have, st->chunked); + THL_Log(logbuf); + + /* Log the first 16 body bytes in hex -- lets us see the magic number + of the actual payload so we can tell PNG / GIF / HTML / gzip apart + at a glance. */ + if (body_have > 0) + { + UBYTE *bp = hdr + header_len; + ULONG show = body_have < 16 ? body_have : 16; + char hex[80]; + int off = sprintf(hex, "http: body[0..%lu]=", show); + for (ULONG i = 0; i < show; i++) + off += sprintf(hex + off, "%02x ", bp[i]); + THL_Log(hex); + } + + st->socket = s; + st->SBase = SocketBase; +#if defined(__amigaos4__) + st->SIFace = ISocket; +#endif + free(hdr); + return 1; +} + +/* --- the hook function ------------------------------------------------- */ + +static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, + struct HTMLview_LoadMsg *msg) +{ + (void)hook; + (void)obj; + if (!msg) return 0; + + switch (msg->lm_Type) + { + case HTMLview_Open: + { + CONST_STRPTR url = msg->lm_Params.lm_Open.URL; + if (!url) return 0; + + struct THL_State *st = (struct THL_State *)calloc(1, sizeof(*st)); + if (!st) return 0; + st->socket = -1; + msg->lm_Userdata = st; + + if (strncmp(url, "https://", 8) == 0) + { + THL_Log("hook: https:// unsupported"); + free(st); msg->lm_Userdata = NULL; return 0; + } + + if (strncmp(url, "http://", 7) == 0) + { + if (!THL_HttpOpen(url, st)) + { + free(st); msg->lm_Userdata = NULL; return 0; + } + return 1; + } + + if (strncmp(url, "file://", 7) == 0) url += 7; + + st->file = Open((STRPTR)url, MODE_OLDFILE); + if (st->file) return 1; + + free(st); msg->lm_Userdata = NULL; + return 0; + } + + case HTMLview_Read: + { + struct THL_State *st = (struct THL_State *)msg->lm_Userdata; + if (!st) return 0; + + STRPTR out = msg->lm_Params.lm_Read.Buffer; + LONG len = msg->lm_Params.lm_Read.Size; + if (!out || len <= 0) return 0; + + /* Replay stashed body bytes first. */ + if (st->buffer && st->bufpos < st->buflen) + { + ULONG remain = st->buflen - st->bufpos; + ULONG n = (remain < (ULONG)len) ? remain : (ULONG)len; + memcpy(out, st->buffer + st->bufpos, n); + st->bufpos += n; + if (st->bufpos >= st->buflen) + { + free(st->buffer); + st->buffer = NULL; + } + return n; + } + + if (st->file) + { + LONG rd = Read(st->file, out, len); + return rd > 0 ? rd : 0; + } + + if (st->socket >= 0) + { + struct Library *SocketBase = st->SBase; +#if defined(__amigaos4__) + struct SocketIFace *ISocket = st->SIFace; +#endif + LONG rd = st->chunked + ? THL_ReadChunked(st, (UBYTE *)out, len) + : recv(st->socket, out, len, 0); + { + char logbuf[96]; + sprintf(logbuf, "read: recv(sock=%ld want=%ld) => %ld%s", + st->socket, len, rd, st->chunked ? " (chunked)" : ""); + THL_Log(logbuf); + } + return rd > 0 ? rd : 0; + } + + return 0; + } + + case HTMLview_Write: + return 0; + + case HTMLview_Close: + { + struct THL_State *st = (struct THL_State *)msg->lm_Userdata; + if (st) + { + if (st->file) Close(st->file); + + if (st->socket >= 0 && st->SBase) + { + struct Library *SocketBase = st->SBase; +#if defined(__amigaos4__) + struct SocketIFace *ISocket = st->SIFace; +#endif + CloseSocket(st->socket); + } + +#if defined(__amigaos4__) + if (st->SIFace) + DropInterface((struct Interface *)st->SIFace); +#endif + if (st->SBase) CloseLibrary(st->SBase); + + if (st->buffer) free(st->buffer); + free(st); + msg->lm_Userdata = NULL; + } + return 1; + } + } + return 0; +} + +#endif /* HTMLVIEW_TEST_IMAGE_HOOK_H */ diff --git a/mcc/testdata/test.png b/mcc/testdata/test.png new file mode 100644 index 0000000000000000000000000000000000000000..53913455966f686a81a9a7331749c6fe4bd5f699 GIT binary patch literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WG(24#Ln`LHJ-d;Yfr00c!N2-l owM=H-Gatto Date: Sat, 18 Apr 2026 13:29:50 +0200 Subject: [PATCH 18/32] feat: HTTPS support via AmiSSL + redirect following MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire TLS into the test-program image hook so https:// URLs and http->https 302 redirects resolve end-to-end. - Makefile downloads the AmiSSL 5.27 SDK on demand (lha xq) and exposes the headers only; inline macros in dispatch through a task-local AmiSSLBase, so no stub lib link and no global symbols are required. Enabled for OS3 and OS4 (MorphOS stays plain HTTP). - test_image_hook.h: THL_State gains AmiSSL library bases, SSL context and connection, plus a sni_errno slot for AmiSSL_ErrNoPtr. THL_Recv/THL_Send dispatch via SSL_read/SSL_write when use_tls is set, with function-scope shadowed library bases for inline macros. - URL parsing generalised: THL_ParseUrl replaces THL_ParseHttp and accepts both http and https (default ports 80/443). - THL_Connect factors out TCP connect plus optional TLS wrap; THL_TlsWrap opens amisslmaster + amissl, InitAmiSSLMaster / OpenAmiSSL / InitAmiSSLA (A-suffixed because OS3 builds with -DNO_INLINE_STDARG), then SSL_CTX_new(TLS_client_method), SSL_new, SSL_set_fd, SSL_set_tlsext_host_name, SSL_connect. VERIFY_NONE for now — CA bundle wiring is a follow-up. - THL_DoRequest sends the GET and parses status/headers; returns 1 OK / 0 error / -1 redirect. THL_HttpOpen loops up to 5 hops so http://www.amigaworld.net/images/awn2.gif follows its 302 to HTTPS transparently. - SimpleTest.c / LibLoad_Test.c: HTML adds a direct-HTTPS test entry (https://aminet.net/pics/aminet.png) alongside the existing HTTP entries. - .gitignore: exclude the downloaded mcc/amissl_sdk/ tree. --- .gitignore | 1 + mcc/LibLoad_Test.c | 7 +- mcc/Makefile | 62 ++++- mcc/SimpleTest.c | 7 +- mcc/test_image_hook.h | 633 ++++++++++++++++++++++++++++-------------- 5 files changed, 497 insertions(+), 213 deletions(-) diff --git a/.gitignore b/.gitignore index 6a32990..76c1e67 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ bin_* mcp/locale.* MEMORY.md .DS_Store +mcc/amissl_sdk/ diff --git a/mcc/LibLoad_Test.c b/mcc/LibLoad_Test.c index aa0df24..3d875ca 100644 --- a/mcc/LibLoad_Test.c +++ b/mcc/LibLoad_Test.c @@ -52,7 +52,12 @@ static const char *test_html = "

Images - Network (HTTP)

" "

Network images require bsdsocket.library:

" "

\"Aminet

" - "

\"AmigaWorld

" + "

Exercises http->https redirect (requires AmiSSL):

" + "

\"AmigaWorld

" + + "

Images - Network (HTTPS)

" + "

Direct TLS fetch. Needs amisslmaster.library + amissl.library installed:

" + "

\"Aminet

" "

Text

" "

Bold, italic, under, strike, tt

" diff --git a/mcc/Makefile b/mcc/Makefile index f1ed190..b2b5f54 100644 --- a/mcc/Makefile +++ b/mcc/Makefile @@ -152,6 +152,11 @@ endif LDFLAGS = $(CPU) $(DEBUGSYM) LDLIBS = +# Per-program extras; populated by the AmiSSL block below. +TEST_CFLAGS = +TEST_LDLIBS = +TEST_DEPS = + # different options per target OS ifeq ($(OS), os4) @@ -241,6 +246,45 @@ endif endif endif +########################################################################### +# AmiSSL SDK — enables HTTPS in SimpleTest / LibLoad_Test. +# +# The SDK is downloaded from jens-maus/amissl on first build. Pass +# USE_AMISSL=0 to disable; the test programs then speak plain HTTP only. +# No SDK is distributed for MorphOS, so USE_AMISSL is forced off there. + +AMISSL_VERSION ?= 5.27 +AMISSL_SDK_DIR ?= amissl_sdk +AMISSL_SDK_READY = $(AMISSL_SDK_DIR)/.ready +AMISSL_INC = $(AMISSL_SDK_DIR)/AmiSSL/Developer/include +AMISSL_URL = https://github.com/jens-maus/amissl/releases/download/$(AMISSL_VERSION)/AmiSSL-$(AMISSL_VERSION)-SDK.lha + +ifeq ($(OS), os3) + AMISSL_LIBDIR = $(AMISSL_SDK_DIR)/AmiSSL/Developer/lib/AmigaOS3 + USE_AMISSL ?= 1 +else ifeq ($(OS), os4) + AMISSL_LIBDIR = $(AMISSL_SDK_DIR)/AmiSSL/Developer/lib/AmigaOS4/newlib + USE_AMISSL ?= 1 +else + USE_AMISSL = 0 +endif + +ifeq ($(USE_AMISSL), 1) + # Headers only: the inline macros in dispatch through + # a task-local library base, so no stub lib (libamisslstubs.a) is needed + # and there are no global AmiSSLBase / AmiSSLMasterBase symbols to + # satisfy at link time. + TEST_CFLAGS += -DHAVE_AMISSL -I$(AMISSL_INC) + TEST_DEPS += $(AMISSL_SDK_READY) +endif + +$(AMISSL_SDK_READY): + @echo " GET AmiSSL SDK $(AMISSL_VERSION)" + @mkdir -p $(AMISSL_SDK_DIR) + @cd $(AMISSL_SDK_DIR) && curl -fsSL -o sdk.lha "$(AMISSL_URL)" + @cd $(AMISSL_SDK_DIR) && lha xq sdk.lha && rm -f sdk.lha + @touch $@ + ########################################################################### # Here starts all stuff that is common for all target platforms and # hosts. @@ -447,6 +491,16 @@ $(OBJDIR)/mccclass_68k.o: ../include/mccclass_68k.c @echo " CC $<" @$(CC) $(CFLAGS) $< -o $@ +# Test programs get the AmiSSL include path (if enabled). The explicit +# rules below shadow the generic %.o: %.c rule so TEST_CFLAGS applies. +$(OBJDIR)/SimpleTest.o: SimpleTest.c test_image_hook.h $(TEST_DEPS) + @echo " CC $<" + @$(CC) $(CFLAGS) $(TEST_CFLAGS) $< -o $@ + +$(OBJDIR)/LibLoad_Test.o: LibLoad_Test.c test_image_hook.h $(TEST_DEPS) + @echo " CC $<" + @$(CC) $(CFLAGS) $(TEST_CFLAGS) $< -o $@ + # # Link against all MCC objects EXCEPT library.o (globals conflict) @@ -456,18 +510,18 @@ LIBOBJS = $(filter-out $(OBJDIR)/library.o $(OBJDIR)/crtclasses_begin.o $(OBJDIR $(SIMPLETARGET): $(OBJDIR)/SimpleTest.o @echo " LD $@" ifneq (,$(DEBUG)) - @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) -ldebug -Wl,-Map,$@.map + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) $(TEST_LDLIBS) -ldebug -Wl,-Map,$@.map else - @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) -Wl,-Map,$@.map + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) $(TEST_LDLIBS) -Wl,-Map,$@.map endif @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug $(LIBLOADTARGET): $(OBJDIR)/LibLoad_Test.o @echo " LD $@" ifneq (,$(DEBUG)) - @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o $(LDLIBS) -ldebug -Wl,-Map,$@.map + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o $(LDLIBS) $(TEST_LDLIBS) -ldebug -Wl,-Map,$@.map else - @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o $(LDLIBS) -Wl,-Map,$@.map + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o $(LDLIBS) $(TEST_LDLIBS) -Wl,-Map,$@.map endif @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug diff --git a/mcc/SimpleTest.c b/mcc/SimpleTest.c index ce4753c..679bf9a 100644 --- a/mcc/SimpleTest.c +++ b/mcc/SimpleTest.c @@ -57,7 +57,12 @@ static const char *test_html = "

Images - Network (HTTP)

" "

These need bsdsocket.library and a working network stack:

" "

\"Aminet

" - "

\"AmigaWorld

" + "

Exercises http->https redirect (requires AmiSSL):

" + "

\"AmigaWorld

" + + "

Images - Network (HTTPS)

" + "

Direct TLS fetch. Needs amisslmaster.library + amissl.library installed:

" + "

\"Aminet

" "

Text Formatting

" "

Bold text, italic text, " diff --git a/mcc/test_image_hook.h b/mcc/test_image_hook.h index 52e1cfb..2dad8cc 100644 --- a/mcc/test_image_hook.h +++ b/mcc/test_image_hook.h @@ -2,23 +2,26 @@ * test_image_hook.h -- Shared image-load hook used by SimpleTest and * LibLoad_Test to feed picture data to HTMLview.mcc. * - * The hook resolves three kinds of URLs: - * - PROGDIR:, DH0:, etc. (direct dos Open) - * - file:// (stripped and dos Open) - * - http://host[:port]/p (bsdsocket.library GET, chunked aware) + * URL schemes resolved by the hook: + * - PROGDIR:, DH0:, etc. -- direct dos Open + * - file:// -- stripped and dos Open + * - http://host[:port]/p -- bsdsocket GET, chunked-aware, redirect-aware + * - https://host[:port]/p -- AmiSSL-wrapped GET (only if HAVE_AMISSL) * - * https:// URLs return failure (no TLS in the stock Amiga bsdsocket). + * The hook follows up to 5 consecutive 3xx redirects (absolute URLs only; + * relative Location: headers are NOT supported yet) and upgrades http->https + * transparently when a server redirects to TLS. * - * The header is intentionally single-include, single-translation-unit. - * Include it from exactly one .c file in a test program; the program owns - * the SocketBase / ISocket globals (so it can close them on exit). + * HAVE_AMISSL is defined by the Makefile when the AmiSSL SDK is present. + * Without it, https:// URLs and http->https redirects fail cleanly. * - * Threading notes: the hook runs in the HTMLview decoder thread, not the - * main task. On m68k every task that uses bsdsocket must OpenLibrary the - * library itself -- the library base opened by main() isn't valid for our - * thread. We therefore open a task-local SocketBase in the hook and shadow - * the file-scope globals so the proto/socket.h inline macros dispatch - * through the local base. + * Threading: runs in the HTMLview decoder task. On m68k every task that uses + * bsdsocket must OpenLibrary the library itself, so this file opens a task- + * local SocketBase (and AmiSSLMasterBase / AmiSSLBase) in the hook and + * shadows the file-scope globals so the proto/socket.h inline macros + * dispatch through the local base. + * + * Include this header from exactly one translation unit per test program. */ #ifndef HTMLVIEW_TEST_IMAGE_HOOK_H @@ -33,6 +36,14 @@ #include #include "HTMLview_mcc.h" +#ifdef HAVE_AMISSL +#include +#include +#include +#include +#include +#endif + #ifndef TRUE #define TRUE 1 #endif @@ -40,8 +51,9 @@ #define FALSE 0 #endif -/* The test programs carry their own globals for this; only used by the - main task. The hook uses its own task-local bases instead (see below). */ + +/* Owned by the test program's main task. The hook uses its own task-local + library bases and never touches these from the decoder thread. */ #if defined(__amigaos4__) extern struct Library *SocketBase; extern struct SocketIFace *ISocket; @@ -59,20 +71,34 @@ struct THL_State ULONG buflen; int chunked; /* non-zero => Transfer-Encoding: chunked */ ULONG chunk_left; + int use_tls; /* non-zero => route I/O through SSL_* */ - /* Task-local bsdsocket bases -- opened by the decoder thread so it can + /* Task-local bsdsocket bases. Opened by the decoder thread so it can actually call socket/recv/etc.; closed on hook-Close. */ struct Library *SBase; #if defined(__amigaos4__) struct SocketIFace *SIFace; #endif + +#ifdef HAVE_AMISSL + struct Library *AMSBase; /* amisslmaster.library */ + struct Library *ASBase; /* amissl.library */ +#if defined(__amigaos4__) + struct AmiSSLMasterIFace *IAMSMaster; + struct AmiSSLIFace *IAS; +#endif + SSL_CTX *ssl_ctx; + SSL *ssl; + int ssl_initialized; /* CleanupAmiSSLA required on teardown */ + int sni_errno; /* storage for AmiSSL_ErrNoPtr */ +#endif }; /* --- logging ----------------------------------------------------------- */ /* Accumulating-buffer + rewrite pattern (same as ImageManager DTLog) so the log file stays coherent across OS3 and OS4 without relying on Seek. */ -static char THL_LogBuf[8192]; +static char THL_LogBuf[16384]; static ULONG THL_LogLen = 0; static void THL_Log(const char *line) @@ -87,23 +113,63 @@ static void THL_Log(const char *line) if (f) { Write(f, THL_LogBuf, THL_LogLen); Close(f); } } -/* --- low-level helpers ------------------------------------------------- */ +/* --- low-level I/O (plain TCP or TLS depending on st->use_tls) --------- */ -static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want); +static LONG THL_Recv(struct THL_State *st, APTR buf, LONG len) +{ +#ifdef HAVE_AMISSL + if (st->use_tls && st->ssl) + { + /* Inline macros in resolve AMISSL_BASE_NAME + (default: AmiSSLBase) by normal C name lookup -- shadow the + file-scope extern with our task-local base. */ + struct Library *AmiSSLBase = st->ASBase; +#if defined(__amigaos4__) + struct AmiSSLIFace *IAmiSSL = st->IAS; +#endif + int n = SSL_read(st->ssl, buf, (int)len); + return n > 0 ? n : 0; + } +#endif + { + struct Library *SocketBase = st->SBase; +#if defined(__amigaos4__) + struct SocketIFace *ISocket = st->SIFace; +#endif + LONG got = recv(st->socket, buf, len, 0); + return got > 0 ? got : 0; + } +} -static LONG THL_RecvLine(struct THL_State *st, char *buf, LONG maxlen) +static LONG THL_Send(struct THL_State *st, const APTR buf, LONG len) { - /* Shadow file-scope bases with our task-local ones for proto/socket.h. */ - struct Library *SocketBase = st->SBase; +#ifdef HAVE_AMISSL + if (st->use_tls && st->ssl) + { + struct Library *AmiSSLBase = st->ASBase; +#if defined(__amigaos4__) + struct AmiSSLIFace *IAmiSSL = st->IAS; +#endif + int n = SSL_write(st->ssl, buf, (int)len); + return n > 0 ? n : -1; + } +#endif + { + struct Library *SocketBase = st->SBase; #if defined(__amigaos4__) - struct SocketIFace *ISocket = st->SIFace; + struct SocketIFace *ISocket = st->SIFace; #endif + return send(st->socket, buf, len, 0); + } +} +static LONG THL_RecvLine(struct THL_State *st, char *buf, LONG maxlen) +{ LONG i = 0; while (i < maxlen - 1) { UBYTE c; - LONG got = recv(st->socket, (APTR)&c, 1, 0); + LONG got = THL_Recv(st, (APTR)&c, 1); if (got <= 0) return -1; if (c == '\n') { buf[i] = 0; return i; } if (c != '\r') buf[i++] = c; @@ -115,11 +181,6 @@ static LONG THL_RecvLine(struct THL_State *st, char *buf, LONG maxlen) /* Reads up to `want` bytes, unwrapping chunked transfer encoding. */ static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want) { - struct Library *SocketBase = st->SBase; -#if defined(__amigaos4__) - struct SocketIFace *ISocket = st->SIFace; -#endif - LONG produced = 0; while (produced < want) { @@ -141,7 +202,7 @@ static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want) LONG take = want - produced; if ((ULONG)take > st->chunk_left) take = (LONG)st->chunk_left; - LONG got = recv(st->socket, (APTR)(out + produced), take, 0); + LONG got = THL_Recv(st, (APTR)(out + produced), take); if (got <= 0) return produced; produced += got; st->chunk_left -= got; @@ -149,13 +210,17 @@ static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want) return produced; } -/* Parses "http://host[:port]/path" into components (buffers owned by caller). */ -static int THL_ParseHttp(CONST_STRPTR url, char *host, ULONG hlen, - char *path, ULONG plen, ULONG *portp) +/* Parses http:// or https:// URLs. Sets *is_https from scheme. */ +static int THL_ParseUrl(CONST_STRPTR url, char *host, ULONG hlen, + char *path, ULONG plen, ULONG *portp, int *is_https) { const char *p = url; - if (strncmp(p, "http://", 7) != 0) return 0; - p += 7; + ULONG default_port = 80; + *is_https = 0; + + if (strncmp(p, "https://", 8) == 0) { p += 8; default_port = 443; *is_https = 1; } + else if (strncmp(p, "http://", 7) == 0) { p += 7; } + else return 0; const char *hs = p; while (*p && *p != '/' && *p != ':') p++; @@ -165,7 +230,7 @@ static int THL_ParseHttp(CONST_STRPTR url, char *host, ULONG hlen, memcpy(host, hs, n); host[n] = 0; - ULONG port = 80; + ULONG port = default_port; if (*p == ':') { p++; @@ -182,26 +247,186 @@ static int THL_ParseHttp(CONST_STRPTR url, char *host, ULONG hlen, return 1; } -/* Opens an HTTP connection, reads and parses response headers, stashes any - body bytes that came with the header recv. Returns 1 on success. */ -static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) +/* --- connection teardown (shared by error paths and redirect loop) ----- */ + +static void THL_Disconnect(struct THL_State *st) { - char logbuf[320]; +#ifdef HAVE_AMISSL + if (st->use_tls) + { + struct Library *AmiSSLBase = st->ASBase; + struct Library *AmiSSLMasterBase = st->AMSBase; +#if defined(__amigaos4__) + struct AmiSSLIFace *IAmiSSL = st->IAS; + struct AmiSSLMasterIFace *IAmiSSLMaster = st->IAMSMaster; +#endif + if (st->ssl) { SSL_shutdown(st->ssl); SSL_free(st->ssl); st->ssl = NULL; } + if (st->ssl_ctx) { SSL_CTX_free(st->ssl_ctx); st->ssl_ctx = NULL; } + if (st->ssl_initialized) { CleanupAmiSSLA(NULL); st->ssl_initialized = 0; } +#if defined(__amigaos4__) + if (st->IAS) { DropInterface((struct Interface *)st->IAS); st->IAS = NULL; } +#endif + if (st->ASBase) { CloseAmiSSL(); st->ASBase = NULL; AmiSSLBase = NULL; } +#if defined(__amigaos4__) + if (st->IAMSMaster) { DropInterface((struct Interface *)st->IAMSMaster); st->IAMSMaster = NULL; } +#endif + if (st->AMSBase) { CloseLibrary(st->AMSBase); st->AMSBase = NULL; } + (void)AmiSSLBase; (void)AmiSSLMasterBase; + } +#endif - char host[256], path[1024]; - ULONG port = 80; - if (!THL_ParseHttp(url, host, sizeof(host), path, sizeof(path), &port)) + if (st->socket >= 0 && st->SBase) { - THL_Log("http: parse URL failed"); + struct Library *SocketBase = st->SBase; +#if defined(__amigaos4__) + struct SocketIFace *ISocket = st->SIFace; +#endif + CloseSocket(st->socket); + } + st->socket = -1; + +#if defined(__amigaos4__) + if (st->SIFace) { DropInterface((struct Interface *)st->SIFace); st->SIFace = NULL; } +#endif + if (st->SBase) { CloseLibrary(st->SBase); st->SBase = NULL; } + + st->use_tls = 0; + st->chunked = 0; + st->chunk_left = 0; + if (st->buffer) { free(st->buffer); st->buffer = NULL; } + st->buflen = st->bufpos = 0; +} + +/* --- AmiSSL-wrapped connect (only linked when HAVE_AMISSL) ------------- */ + +#ifdef HAVE_AMISSL +static int THL_TlsWrap(struct THL_State *st, const char *host) +{ + char logbuf[256]; + + /* All inline SSL calls resolve their library base via normal C name + lookup. We declare these early and reassign as bases come up, so + every call below sees the right local. */ + struct Library *AmiSSLMasterBase = NULL; + struct Library *AmiSSLBase = NULL; +#if defined(__amigaos4__) + struct AmiSSLMasterIFace *IAmiSSLMaster = NULL; + struct AmiSSLIFace *IAmiSSL = NULL; + struct SocketIFace *ISocket = st->SIFace; +#endif + + st->AMSBase = OpenLibrary((STRPTR)"amisslmaster.library", AMISSLMASTER_MIN_VERSION); + if (!st->AMSBase) + { + THL_Log("https: OpenLibrary amisslmaster.library failed"); + return 0; + } + AmiSSLMasterBase = st->AMSBase; +#if defined(__amigaos4__) + st->IAMSMaster = (struct AmiSSLMasterIFace *) + GetInterface(st->AMSBase, "main", 1, NULL); + if (!st->IAMSMaster) { THL_Log("https: GetInterface IAmiSSLMaster failed"); return 0; } + IAmiSSLMaster = st->IAMSMaster; +#endif + + if (!InitAmiSSLMaster(AMISSL_CURRENT_VERSION, TRUE)) + { + THL_Log("https: InitAmiSSLMaster failed (library too old)"); + return 0; + } + + st->ASBase = OpenAmiSSL(); + if (!st->ASBase) + { + THL_Log("https: OpenAmiSSL failed"); + return 0; + } + AmiSSLBase = st->ASBase; +#if defined(__amigaos4__) + st->IAS = (struct AmiSSLIFace *)GetInterface(st->ASBase, "main", 1, NULL); + if (!st->IAS) { THL_Log("https: GetInterface IAmiSSL failed"); return 0; } + IAmiSSL = st->IAS; +#endif + + /* Use the A-suffixed variant: OS3 builds pass -DNO_INLINE_STDARG, + which disables the varargs InitAmiSSL() macro. */ + { + struct TagItem init_tags[] = { + { AmiSSL_ErrNoPtr, (ULONG)&st->sni_errno }, +#if defined(__amigaos4__) + { AmiSSL_ISocket, (ULONG)st->SIFace }, +#else + { AmiSSL_SocketBase, (ULONG)st->SBase }, +#endif + { TAG_DONE, 0 } + }; + if (InitAmiSSLA(init_tags) != 0) + { + THL_Log("https: InitAmiSSL failed"); + return 0; + } + } + st->ssl_initialized = 1; + + OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT + | OPENSSL_INIT_ADD_ALL_CIPHERS + | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL); + + /* Minimal entropy seeding -- enough for client handshake; see the + AmiSSL sample for a proper implementation if needed. */ + { + unsigned char seed[32]; + ULONG t = (ULONG)FindTask(NULL); + for (ULONG i = 0; i < sizeof(seed); i++) + seed[i] = (unsigned char)(t >> ((i & 3) * 8)) ^ (unsigned char)i; + RAND_seed(seed, sizeof(seed)); + } + + st->ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!st->ssl_ctx) { THL_Log("https: SSL_CTX_new failed"); return 0; } + + /* Disable peer verification for the test programs. Strict verification + needs ENVARC:AmiSSL/cacert.pem installed; relaxing lets the tests run + on a barebones setup without silently masking real TLS errors -- any + real protocol failure still shows up in SSL_connect below. */ + SSL_CTX_set_verify(st->ssl_ctx, SSL_VERIFY_NONE, NULL); + + st->ssl = SSL_new(st->ssl_ctx); + if (!st->ssl) { THL_Log("https: SSL_new failed"); return 0; } + + SSL_set_fd(st->ssl, (int)st->socket); + SSL_set_tlsext_host_name(st->ssl, host); + + int h = SSL_connect(st->ssl); + if (h <= 0) + { + int err = SSL_get_error(st->ssl, h); + sprintf(logbuf, "https: SSL_connect failed rc=%d err=%d", h, err); + THL_Log(logbuf); return 0; } - sprintf(logbuf, "http: host=%s port=%lu path=%s", host, port, path); + sprintf(logbuf, "https: handshake OK cipher=%s", SSL_get_cipher(st->ssl)); THL_Log(logbuf); + return 1; +} +#endif /* HAVE_AMISSL */ + +/* --- per-hop connect (TCP + optional TLS). Does NOT send the request. - */ + +static int THL_Connect(struct THL_State *st, const char *host, ULONG port, int use_tls) +{ + char logbuf[256]; + st->use_tls = use_tls; + +#ifndef HAVE_AMISSL + if (use_tls) + { + THL_Log("https: no AmiSSL support compiled in"); + return 0; + } +#endif - /* Open bsdsocket fresh for THIS task -- the decoder thread. On m68k the - library is opened per-task; the main task's SocketBase is not usable - from us. */ struct Library *SocketBase = OpenLibrary((STRPTR)"bsdsocket.library", 4); #if defined(__amigaos4__) struct SocketIFace *ISocket = SocketBase @@ -213,12 +438,10 @@ static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) THL_Log("http: OpenLibrary bsdsocket.library failed"); return 0; } + st->SBase = SocketBase; st->SIFace = ISocket; #else - if (!SocketBase) - { - THL_Log("http: OpenLibrary bsdsocket.library failed"); - return 0; - } + if (!SocketBase) { THL_Log("http: OpenLibrary bsdsocket.library failed"); return 0; } + st->SBase = SocketBase; #endif struct hostent *he; @@ -231,23 +454,11 @@ static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) { sprintf(logbuf, "http: gethostbyname(%s) failed", host); THL_Log(logbuf); -#if defined(__amigaos4__) - if (ISocket) DropInterface((struct Interface *)ISocket); -#endif - CloseLibrary(SocketBase); return 0; } - LONG s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) - { - THL_Log("http: socket() failed"); -#if defined(__amigaos4__) - if (ISocket) DropInterface((struct Interface *)ISocket); -#endif - CloseLibrary(SocketBase); - return 0; - } + st->socket = socket(AF_INET, SOCK_STREAM, 0); + if (st->socket < 0) { THL_Log("http: socket() failed"); return 0; } struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); @@ -255,56 +466,54 @@ static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) sin.sin_port = htons((UWORD)port); sin.sin_addr.s_addr = *((ULONG *)he->h_addr_list[0]); - if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) + if (connect(st->socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) { THL_Log("http: connect() failed"); - CloseSocket(s); -#if defined(__amigaos4__) - if (ISocket) DropInterface((struct Interface *)ISocket); -#endif - CloseLibrary(SocketBase); return 0; } +#ifdef HAVE_AMISSL + if (use_tls) + { + if (!THL_TlsWrap(st, host)) return 0; + } +#endif + return 1; +} + +/* --- build + send GET; read headers; stash any body bytes in st->buffer -- + Returns: 1 = headers OK, body follows + 0 = error / non-200 + -1 = redirect; next URL copied into redirect_out (size plen). */ +static int THL_DoRequest(struct THL_State *st, const char *host, + const char *path, char *redirect_out, ULONG rlen) +{ + char logbuf[320]; + char request[2048]; - int rlen = sprintf(request, + int rlen_s = sprintf(request, "GET %s HTTP/1.1\r\n" "Host: %s\r\n" - "User-Agent: HTMLview-TestHook/1.1\r\n" + "User-Agent: HTMLview-TestHook/1.2\r\n" "Accept: */*\r\n" "Connection: close\r\n" "\r\n", path, host); - if (send(s, request, rlen, 0) < 0) + if (THL_Send(st, request, rlen_s) < 0) { THL_Log("http: send() failed"); - CloseSocket(s); -#if defined(__amigaos4__) - if (ISocket) DropInterface((struct Interface *)ISocket); -#endif - CloseLibrary(SocketBase); return 0; } - /* Read a moderate chunk so we're very likely to have all headers plus - some body in a single recv. */ UBYTE *hdr = (UBYTE *)malloc(16384); - if (!hdr) - { - CloseSocket(s); -#if defined(__amigaos4__) - if (ISocket) DropInterface((struct Interface *)ISocket); -#endif - CloseLibrary(SocketBase); - return 0; - } + if (!hdr) return 0; LONG have = 0; char *hdrend = NULL; while (have < 16384 - 1) { - LONG got = recv(s, (APTR)(hdr + have), 16384 - 1 - have, 0); + LONG got = THL_Recv(st, (APTR)(hdr + have), 16384 - 1 - have); if (got <= 0) break; have += got; hdr[have] = 0; @@ -316,88 +525,81 @@ static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) { THL_Log("http: no header terminator in reply"); free(hdr); - CloseSocket(s); -#if defined(__amigaos4__) - if (ISocket) DropInterface((struct Interface *)ISocket); -#endif - CloseLibrary(SocketBase); return 0; } - /* hdrend points at the first byte of the header/body separator. - Separator is either "\r\n\r\n" (4 bytes) or "\n\n" (2 bytes). - Compute body offset BEFORE null-terminating. */ ULONG header_len = (*hdrend == '\r') ? (ULONG)(hdrend - (char *)hdr) + 4 : (ULONG)(hdrend - (char *)hdr) + 2; - - /* Null-terminate the header block so strstr on header values is safe. - Writing at hdrend clobbers only the first byte of the separator, - which we no longer need -- NOT the first body byte. */ *hdrend = 0; + /* Parse status line. */ + char status[160]; + ULONG n = 0; + while (n < sizeof(status) - 1 && n < header_len && + hdr[n] != '\r' && hdr[n] != '\n') { - /* Log the HTTP status line so we can spot 301/302 redirects, 404s, - etc. The header block also tells us whether the server returned - HTML instead of an image. */ - char status[120]; - ULONG n = 0; - while (n < sizeof(status) - 1 && n < header_len && - hdr[n] != '\r' && hdr[n] != '\n') - { - status[n] = (char)hdr[n]; - n++; - } - status[n] = 0; - sprintf(logbuf, "http: status=%s", status); - THL_Log(logbuf); + status[n] = (char)hdr[n]; + n++; + } + status[n] = 0; + sprintf(logbuf, "http: status=%s", status); + THL_Log(logbuf); - /* Bail on non-200 responses -- redirects, 404s and 5xx must not - reach the decoder as "image body". Parse the numeric code. */ - int code = 0; - const char *sp = strchr(status, ' '); - if (sp) - { - while (*sp == ' ') sp++; - while (*sp >= '0' && *sp <= '9') { code = code*10 + (*sp - '0'); sp++; } - } - if (code != 200) - { - sprintf(logbuf, "http: aborting (status=%d)", code); - THL_Log(logbuf); - free(hdr); - CloseSocket(s); -#if defined(__amigaos4__) - if (ISocket) DropInterface((struct Interface *)ISocket); -#endif - CloseLibrary(SocketBase); - return 0; - } + int code = 0; + const char *sp = strchr(status, ' '); + if (sp) + { + while (*sp == ' ') sp++; + while (*sp >= '0' && *sp <= '9') { code = code*10 + (*sp - '0'); sp++; } + } - /* Helper: dig out a header value by name, log as "http: =". */ - const char *hdrs[] = { "Location:", "location:", - "Content-Type:", "content-type:", - "Content-Length:", "content-length:", - "Content-Encoding:", "content-encoding:" }; - const char *tags[] = { "location", "location", - "content-type", "content-type", - "content-length", "content-length", - "content-encoding", "content-encoding" }; - for (ULONG i = 0; i < sizeof(hdrs)/sizeof(hdrs[0]); i++) - { - const char *v = strstr((char *)hdr, hdrs[i]); - if (!v) continue; - v += strlen(hdrs[i]); - while (*v == ' ' || *v == '\t') v++; - const char *e = v; - while (*e && *e != '\r' && *e != '\n') e++; - ULONG L = (ULONG)(e - v); - if (L > sizeof(status) - 1) L = sizeof(status) - 1; - memcpy(status, v, L); - status[L] = 0; - sprintf(logbuf, "http: %s=%s", tags[i], status); - THL_Log(logbuf); - } + /* Helper: extract a single named header into `out`. Case-aware: accepts + either capitalised or lowercase names (lazy match of the common + Amiga-side server responses). */ + #define GRAB_HDR(upper, lower, out, outsz) do { \ + const char *v = strstr((char *)hdr, upper); \ + if (!v) v = strstr((char *)hdr, lower); \ + if (v) { \ + v += strlen(upper); \ + while (*v == ' ' || *v == '\t') v++; \ + const char *e = v; \ + while (*e && *e != '\r' && *e != '\n') e++; \ + ULONG L = (ULONG)(e - v); \ + if (L > (ULONG)(outsz) - 1) L = (ULONG)(outsz) - 1; \ + memcpy(out, v, L); out[L] = 0; \ + } else out[0] = 0; \ + } while (0) + + char v_loc[512], v_type[128], v_len[32], v_enc[32]; + GRAB_HDR("Location:", "location:", v_loc, sizeof(v_loc)); + GRAB_HDR("Content-Type:", "content-type:", v_type, sizeof(v_type)); + GRAB_HDR("Content-Length:", "content-length:", v_len, sizeof(v_len)); + GRAB_HDR("Content-Encoding:","content-encoding:", v_enc, sizeof(v_enc)); + if (v_loc[0]) { sprintf(logbuf, "http: location=%s", v_loc); THL_Log(logbuf); } + if (v_type[0]) { sprintf(logbuf, "http: content-type=%s", v_type); THL_Log(logbuf); } + if (v_len[0]) { sprintf(logbuf, "http: content-length=%s", v_len); THL_Log(logbuf); } + if (v_enc[0]) { sprintf(logbuf, "http: content-encoding=%s", v_enc); THL_Log(logbuf); } + #undef GRAB_HDR + + /* 3xx + Location => signal caller to reconnect. We don't handle relative + redirects yet; the request will fail on next parse if Location isn't + a full URL. */ + if ((code == 301 || code == 302 || code == 303 || + code == 307 || code == 308) && v_loc[0]) + { + strncpy(redirect_out, v_loc, rlen - 1); + redirect_out[rlen - 1] = 0; + free(hdr); + return -1; + } + + if (code != 200) + { + sprintf(logbuf, "http: aborting (status=%d)", code); + THL_Log(logbuf); + free(hdr); + return 0; } if (strstr((char *)hdr, "Transfer-Encoding: chunked") || @@ -416,33 +618,73 @@ static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) } } - sprintf(logbuf, "http: OK header_len=%lu body_have=%lu chunked=%d", - header_len, body_have, st->chunked); + sprintf(logbuf, "http: OK header_len=%lu body_have=%lu chunked=%d tls=%d", + header_len, body_have, st->chunked, st->use_tls); THL_Log(logbuf); - /* Log the first 16 body bytes in hex -- lets us see the magic number - of the actual payload so we can tell PNG / GIF / HTML / gzip apart - at a glance. */ if (body_have > 0) { UBYTE *bp = hdr + header_len; ULONG show = body_have < 16 ? body_have : 16; - char hex[80]; + char hex[96]; int off = sprintf(hex, "http: body[0..%lu]=", show); for (ULONG i = 0; i < show; i++) off += sprintf(hex + off, "%02x ", bp[i]); THL_Log(hex); } - st->socket = s; - st->SBase = SocketBase; -#if defined(__amigaos4__) - st->SIFace = ISocket; -#endif free(hdr); return 1; } +/* Entry point: opens a connection and reads headers, following up to 5 + redirects across http / https. Returns 1 on success, 0 on any error. */ +static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) +{ + char logbuf[384]; + char current[1024]; + strncpy(current, url, sizeof(current) - 1); + current[sizeof(current) - 1] = 0; + + for (int hop = 0; hop < 5; hop++) + { + char host[256], path[1024]; + ULONG port; + int is_https; + if (!THL_ParseUrl(current, host, sizeof(host), + path, sizeof(path), &port, &is_https)) + { + sprintf(logbuf, "http: unparseable URL '%s'", current); + THL_Log(logbuf); + return 0; + } + + sprintf(logbuf, "http: [hop %d] %s://%s:%lu%s", + hop, is_https ? "https" : "http", host, port, path); + THL_Log(logbuf); + + if (!THL_Connect(st, host, port, is_https)) + { + THL_Disconnect(st); + return 0; + } + + char next[1024]; + int r = THL_DoRequest(st, host, path, next, sizeof(next)); + if (r == 1) return 1; + if (r == 0) { THL_Disconnect(st); return 0; } + + /* 3xx -- tear down and try the new URL. */ + THL_Disconnect(st); + strncpy(current, next, sizeof(current) - 1); + current[sizeof(current) - 1] = 0; + } + + THL_Log("http: too many redirects"); + THL_Disconnect(st); + return 0; +} + /* --- the hook function ------------------------------------------------- */ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, @@ -464,13 +706,8 @@ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, st->socket = -1; msg->lm_Userdata = st; - if (strncmp(url, "https://", 8) == 0) - { - THL_Log("hook: https:// unsupported"); - free(st); msg->lm_Userdata = NULL; return 0; - } - - if (strncmp(url, "http://", 7) == 0) + if (strncmp(url, "http://", 7) == 0 || + strncmp(url, "https://", 8) == 0) { if (!THL_HttpOpen(url, st)) { @@ -520,17 +757,15 @@ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, if (st->socket >= 0) { - struct Library *SocketBase = st->SBase; -#if defined(__amigaos4__) - struct SocketIFace *ISocket = st->SIFace; -#endif LONG rd = st->chunked ? THL_ReadChunked(st, (UBYTE *)out, len) - : recv(st->socket, out, len, 0); + : THL_Recv(st, out, len); { char logbuf[96]; - sprintf(logbuf, "read: recv(sock=%ld want=%ld) => %ld%s", - st->socket, len, rd, st->chunked ? " (chunked)" : ""); + sprintf(logbuf, "read: recv(sock=%ld want=%ld) => %ld%s%s", + st->socket, len, rd, + st->chunked ? " (chunked)" : "", + st->use_tls ? " (tls)" : ""); THL_Log(logbuf); } return rd > 0 ? rd : 0; @@ -548,23 +783,7 @@ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, if (st) { if (st->file) Close(st->file); - - if (st->socket >= 0 && st->SBase) - { - struct Library *SocketBase = st->SBase; -#if defined(__amigaos4__) - struct SocketIFace *ISocket = st->SIFace; -#endif - CloseSocket(st->socket); - } - -#if defined(__amigaos4__) - if (st->SIFace) - DropInterface((struct Interface *)st->SIFace); -#endif - if (st->SBase) CloseLibrary(st->SBase); - - if (st->buffer) free(st->buffer); + THL_Disconnect(st); free(st); msg->lm_Userdata = NULL; } From 98e8f927d0df60346afb28a41050a2c1b9da4409 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Sat, 18 Apr 2026 13:47:20 +0200 Subject: [PATCH 19/32] test: swap amigaworld redirect image to a URL that isn't 404 on HTTPS awn2.gif redirects to HTTPS correctly, but the HTTPS endpoint returns 404 -- the server only hosts the image on plain HTTP. logo-top.gif redirects to HTTPS and returns the image, which is the scenario we actually want to exercise. --- mcc/LibLoad_Test.c | 2 +- mcc/SimpleTest.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mcc/LibLoad_Test.c b/mcc/LibLoad_Test.c index 3d875ca..3682a0c 100644 --- a/mcc/LibLoad_Test.c +++ b/mcc/LibLoad_Test.c @@ -53,7 +53,7 @@ static const char *test_html = "

Network images require bsdsocket.library:

" "

\"Aminet

" "

Exercises http->https redirect (requires AmiSSL):

" - "

\"AmigaWorld

" + "

\"AmigaWorld

" "

Images - Network (HTTPS)

" "

Direct TLS fetch. Needs amisslmaster.library + amissl.library installed:

" diff --git a/mcc/SimpleTest.c b/mcc/SimpleTest.c index 679bf9a..64f136c 100644 --- a/mcc/SimpleTest.c +++ b/mcc/SimpleTest.c @@ -58,7 +58,7 @@ static const char *test_html = "

These need bsdsocket.library and a working network stack:

" "

\"Aminet

" "

Exercises http->https redirect (requires AmiSSL):

" - "

\"AmigaWorld

" + "

\"AmigaWorld

" "

Images - Network (HTTPS)

" "

Direct TLS fetch. Needs amisslmaster.library + amissl.library installed:

" From c47ddd77e48e7c027bf8dfb306bf571d6b317ee0 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Sat, 18 Apr 2026 13:49:58 +0200 Subject: [PATCH 20/32] fix: replace sprintf with snprintf in test image hook Codex review on PR #2 flagged two stack overflows where a user-controllable value (Location: header up to 512 bytes, or a redirected URL up to 1024 bytes) was formatted into a smaller logbuf via unbounded sprintf. Converted every sprintf(logbuf, ...) and the GET-request sprintf to snprintf(...sizeof(buf)...). The hex-dump sprintfs are left as sprintf since they're bounded at 16 bytes and can't overflow. Fixes the P1 Location overflow and P2 unparseable-URL overflow flagged by Codex. --- mcc/test_image_hook.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/mcc/test_image_hook.h b/mcc/test_image_hook.h index 2dad8cc..0c253cd 100644 --- a/mcc/test_image_hook.h +++ b/mcc/test_image_hook.h @@ -401,12 +401,12 @@ static int THL_TlsWrap(struct THL_State *st, const char *host) if (h <= 0) { int err = SSL_get_error(st->ssl, h); - sprintf(logbuf, "https: SSL_connect failed rc=%d err=%d", h, err); + snprintf(logbuf, sizeof(logbuf), "https: SSL_connect failed rc=%d err=%d", h, err); THL_Log(logbuf); return 0; } - sprintf(logbuf, "https: handshake OK cipher=%s", SSL_get_cipher(st->ssl)); + snprintf(logbuf, sizeof(logbuf), "https: handshake OK cipher=%s", SSL_get_cipher(st->ssl)); THL_Log(logbuf); return 1; } @@ -452,7 +452,7 @@ static int THL_Connect(struct THL_State *st, const char *host, ULONG port, int u #endif if (!he) { - sprintf(logbuf, "http: gethostbyname(%s) failed", host); + snprintf(logbuf, sizeof(logbuf), "http: gethostbyname(%s) failed", host); THL_Log(logbuf); return 0; } @@ -491,7 +491,7 @@ static int THL_DoRequest(struct THL_State *st, const char *host, char logbuf[320]; char request[2048]; - int rlen_s = sprintf(request, + int rlen_s = snprintf(request, sizeof(request), "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: HTMLview-TestHook/1.2\r\n" @@ -543,7 +543,7 @@ static int THL_DoRequest(struct THL_State *st, const char *host, n++; } status[n] = 0; - sprintf(logbuf, "http: status=%s", status); + snprintf(logbuf, sizeof(logbuf), "http: status=%s", status); THL_Log(logbuf); int code = 0; @@ -576,10 +576,10 @@ static int THL_DoRequest(struct THL_State *st, const char *host, GRAB_HDR("Content-Type:", "content-type:", v_type, sizeof(v_type)); GRAB_HDR("Content-Length:", "content-length:", v_len, sizeof(v_len)); GRAB_HDR("Content-Encoding:","content-encoding:", v_enc, sizeof(v_enc)); - if (v_loc[0]) { sprintf(logbuf, "http: location=%s", v_loc); THL_Log(logbuf); } - if (v_type[0]) { sprintf(logbuf, "http: content-type=%s", v_type); THL_Log(logbuf); } - if (v_len[0]) { sprintf(logbuf, "http: content-length=%s", v_len); THL_Log(logbuf); } - if (v_enc[0]) { sprintf(logbuf, "http: content-encoding=%s", v_enc); THL_Log(logbuf); } + if (v_loc[0]) { snprintf(logbuf, sizeof(logbuf), "http: location=%s", v_loc); THL_Log(logbuf); } + if (v_type[0]) { snprintf(logbuf, sizeof(logbuf), "http: content-type=%s", v_type); THL_Log(logbuf); } + if (v_len[0]) { snprintf(logbuf, sizeof(logbuf), "http: content-length=%s", v_len); THL_Log(logbuf); } + if (v_enc[0]) { snprintf(logbuf, sizeof(logbuf), "http: content-encoding=%s", v_enc); THL_Log(logbuf); } #undef GRAB_HDR /* 3xx + Location => signal caller to reconnect. We don't handle relative @@ -596,7 +596,7 @@ static int THL_DoRequest(struct THL_State *st, const char *host, if (code != 200) { - sprintf(logbuf, "http: aborting (status=%d)", code); + snprintf(logbuf, sizeof(logbuf), "http: aborting (status=%d)", code); THL_Log(logbuf); free(hdr); return 0; @@ -618,7 +618,7 @@ static int THL_DoRequest(struct THL_State *st, const char *host, } } - sprintf(logbuf, "http: OK header_len=%lu body_have=%lu chunked=%d tls=%d", + snprintf(logbuf, sizeof(logbuf), "http: OK header_len=%lu body_have=%lu chunked=%d tls=%d", header_len, body_have, st->chunked, st->use_tls); THL_Log(logbuf); @@ -654,12 +654,12 @@ static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) if (!THL_ParseUrl(current, host, sizeof(host), path, sizeof(path), &port, &is_https)) { - sprintf(logbuf, "http: unparseable URL '%s'", current); + snprintf(logbuf, sizeof(logbuf), "http: unparseable URL '%s'", current); THL_Log(logbuf); return 0; } - sprintf(logbuf, "http: [hop %d] %s://%s:%lu%s", + snprintf(logbuf, sizeof(logbuf), "http: [hop %d] %s://%s:%lu%s", hop, is_https ? "https" : "http", host, port, path); THL_Log(logbuf); @@ -762,7 +762,7 @@ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, : THL_Recv(st, out, len); { char logbuf[96]; - sprintf(logbuf, "read: recv(sock=%ld want=%ld) => %ld%s%s", + snprintf(logbuf, sizeof(logbuf), "read: recv(sock=%ld want=%ld) => %ld%s%s", st->socket, len, rd, st->chunked ? " (chunked)" : "", st->use_tls ? " (tls)" : ""); From 19645269fbd96e86ea1be7770307de6447d6f8fb Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Sat, 18 Apr 2026 13:59:30 +0200 Subject: [PATCH 21/32] docs: update authors, copyright, changelog, and hook autodocs - AUTHORS: add Dimitris Panokostas; bump Open Source Team copyright to 2005-2026. - README, TODO, doc/MCC_HTMLview.readme: bump copyright year. - ChangeLog: log the HTTPS/AmiSSL work and the prior OS3/OS4/MorphOS build fix-ups under feat/https-support. - doc/MCC_HTMLview.doc, mcc/HTMLview_mcc.h: clarify that HTMLview.mcc itself never touches the network or filesystem -- URL-scheme support lives entirely in the application's ImageLoadHook / LoadHook. Point readers at SimpleTest / LibLoad_Test as a reference hook that handles PROGDIR:, file://, http:// and (with AmiSSL) https:// with redirect following. --- AUTHORS | 3 ++- ChangeLog | 30 ++++++++++++++++++++++++++++++ README | 2 +- TODO | 2 +- doc/MCC_HTMLview.doc | 12 ++++++++++++ doc/MCC_HTMLview.readme | 2 +- mcc/HTMLview_mcc.h | 11 +++++++++++ 7 files changed, 58 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index ee21782..0ef5350 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,7 +2,7 @@ HTMLview.mcc - HTMLview MUI Custom Class Copyright (C) 1997-2000 Allan Odgaard - Copyright (C) 2005-2010 by HTMLview.mcc Open Source Team + Copyright (C) 2005-2026 by HTMLview.mcc Open Source Team This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -25,6 +25,7 @@ design provided by the following people: Alfonso Ranieri Allan Odgaard +Dimitris Panokostas Dwight Meese Ilkka Lehtoranta Jens Langner diff --git a/ChangeLog b/ChangeLog index f5fc9ea..a6f4824 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,36 @@ MUI HTMLview MCC class - ChangeLog $Id$ $URL$ +2026-04-18 Dimitris Panokostas + + * mcc/test_image_hook.h: HTTPS support via AmiSSL. The shared test-program + image hook now handles https:// URLs and follows http->https redirects + (up to five hops). TLS is wrapped through amisslmaster.library + + amissl.library (jens-maus AmiSSL 5.27 SDK), using the inline macros + against task-local library bases -- no stub lib link and no global + AmiSSLBase symbol required. Certificate verification is currently + VERIFY_NONE; CA bundle wiring is a follow-up. + * mcc/test_image_hook.h: Replaced unbounded sprintf into log buffers with + snprintf; a long Location: header or redirect target could otherwise + overflow the stack log buffer. + * mcc/Makefile: On-demand AmiSSL SDK download (lha xq). HTTPS is enabled + for OS3 and OS4 builds; MorphOS stays on plain HTTP. + * mcc/SimpleTest.c, mcc/LibLoad_Test.c: Added network-image test entries + (plain HTTP from aminet, http->https redirect on amigaworld, direct + HTTPS from aminet). + * mcc/LibLoad_Test.c: New test program that exercises the same hook as + SimpleTest but through dlopen-style library loading, making it easier + to verify the shipped HTMLview.mcc on each platform. + +2026-04-17 Dimitris Panokostas + + * mcc: Fix MorphOS MCP and OS4 MCC build issues; OS4 build now makes + -ldebug conditional and stubs kprintf when not building with DEBUG=1; + enabled all three platform builds (OS3, OS4, MorphOS) in CI. + * mcc/ImageManager.cpp, mcc/IM_Render.cpp, mcc/Dispatcher.cpp: Local + image rendering fixes uncovered by the new test programs (decoder + handshake, frame cleanup, hook dispatcher wiring on OS4). + 2016-07-29 Thore Böckelmann * mcc: lots of changes to get this beast buildable with our Linux based cross diff --git a/README b/README index a9e752b..0acd400 100644 --- a/README +++ b/README @@ -2,7 +2,7 @@ HTMLview.mcc - HTMLview MUI Custom Class Copyright (C) 1997-2000 Allan Odgaard - Copyright (C) 2005-2007 by HTMLview.mcc Open Source Team + Copyright (C) 2005-2026 by HTMLview.mcc Open Source Team This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/TODO b/TODO index e58ea4e..3fa4961 100644 --- a/TODO +++ b/TODO @@ -2,7 +2,7 @@ HTMLview.mcc - HTMLview MUI Custom Class Copyright (C) 1997-2000 Allan Odgaard - Copyright (C) 2005-2007 by HTMLview.mcc Open Source Team + Copyright (C) 2005-2026 by HTMLview.mcc Open Source Team This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/doc/MCC_HTMLview.doc b/doc/MCC_HTMLview.doc index 2ce2999..7e34adc 100644 --- a/doc/MCC_HTMLview.doc +++ b/doc/MCC_HTMLview.doc @@ -141,6 +141,14 @@ ToDo: FUNCTION Setup a hook used for image-loading. + Only the hook performs I/O; HTMLview.mcc itself never + touches the network or the filesystem. The hook therefore + decides which URL schemes are supported. The bundled + SimpleTest / LibLoad_Test programs in mcc/ ship a reference + hook that handles PROGDIR:, file://, http:// and (when + compiled against the AmiSSL SDK) https:// with http->https + redirect following. + SEE ALSO MUIA_HTMLview_LoadHook @@ -160,6 +168,10 @@ ToDo: FUNCTION Setup a hook used for (page)-loading. + See MUIA_HTMLview_ImageLoadHook for a note on what the + hook is responsible for and where to find a reference + implementation that also covers HTTPS via AmiSSL. + The hook is called with a pointer to itself in a0, a pointer to a struct HTMLview_LoadMsg in a1 and a pointer to the calling object in a2. diff --git a/doc/MCC_HTMLview.readme b/doc/MCC_HTMLview.readme index 910dcc7..0f149a3 100644 --- a/doc/MCC_HTMLview.readme +++ b/doc/MCC_HTMLview.readme @@ -51,7 +51,7 @@ LEGALESE HTMLview.mcc was originally written in 1995 and is Copyright (C) 1995-2000 by Allan Odgaard. As of version 13.4, released in December 2007, the gadget is -maintained and Copyright (C) 2005-2007 by the HTMLview.mcc Open Source Team. +maintained and Copyright (C) 2005-2026 by the HTMLview.mcc Open Source Team. HTMLview.mcc is distributed under the GNU Lesser General Public License (LGPL) and the development is hosted at SourceForge.net: diff --git a/mcc/HTMLview_mcc.h b/mcc/HTMLview_mcc.h index 133a5f0..c591751 100644 --- a/mcc/HTMLview_mcc.h +++ b/mcc/HTMLview_mcc.h @@ -165,6 +165,10 @@ * to itself in a0, a pointer to a struct HTMLview_LoadMsg in a1 and a * pointer to the calling object in a2. * + * See MUIA_HTMLview_ImageLoadHook for a note on what the hook is + * responsible for and where to find a reference implementation that + * also covers HTTPS via AmiSSL. + * * This hook will be called from a separate task, so the only MUI method * that you can use is MUIM_Application_PushMethod. The hook may very well * be called by sevaral tasks at the same time, so your code needs to be @@ -619,6 +623,13 @@ * * Setup a hook used for image-loading. * + * Only the hook performs I/O; HTMLview.mcc itself never touches the + * network or the filesystem. The hook therefore decides which URL + * schemes are supported. The bundled SimpleTest / LibLoad_Test + * programs in mcc/ ship a reference hook that handles PROGDIR:, + * file://, http:// and (when compiled against the AmiSSL SDK) + * https:// with http->https redirect following. + * * SEE ALSO * * MUIA_HTMLview_LoadHook From c00ec1c536d631e899c4a57c447d6f63ba21e632 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Mon, 20 Apr 2026 19:05:25 +0200 Subject: [PATCH 22/32] chore: prepare 13.6 release Bump mcc/mcp to 13.6 (date 20.04.2026, copyright 2005-2026) and repackage the tag-driven CI release: each platform now ships as HTMLview--.zip with only the runtime binaries and top-level docs, instead of loose artifacts that collided across OS3/OS4/MorphOS under merge-multiple. --- .github/workflows/makefile.yml | 31 +++++++++++++++++++++++++++++-- ChangeLog | 13 +++++++++++++ mcc/rev.h | 10 +++++----- mcp/rev.h | 10 +++++----- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 3cc95ec..13294f9 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -90,11 +90,38 @@ jobs: with: pattern: htmlview-* path: artifacts/ - merge-multiple: true + - name: Package release archives + run: | + set -euo pipefail + tag="${GITHUB_REF##*/}" + mkdir -p release + for platform in os3 os4 morphos; do + case "$platform" in + os3) bindir=bin_os3 ;; + os4) bindir=bin_os4 ;; + morphos) bindir=bin_mos ;; + esac + src="artifacts/htmlview-${platform}-${GITHUB_SHA}" + if [ ! -d "$src" ]; then + echo "Missing artifact for $platform at $src" >&2 + exit 1 + fi + pkg="HTMLview-${tag}-${platform}" + mkdir -p "release/${pkg}/mcc" "release/${pkg}/mcp" + cp "$src/mcc/${bindir}"/*.mcc "release/${pkg}/mcc/" + cp "$src/mcc/${bindir}"/SimpleTest "release/${pkg}/mcc/" + cp "$src/mcc/${bindir}"/LibLoad_Test "release/${pkg}/mcc/" + cp "$src/mcp/${bindir}"/*.mcp "release/${pkg}/mcp/" + for doc in README COPYING ChangeLog AUTHORS; do + [ -f "$doc" ] && cp "$doc" "release/${pkg}/" + done + (cd release && zip -r "${pkg}.zip" "${pkg}") + done + ls -la release/ - name: Create Release uses: softprops/action-gh-release@v2 with: - files: artifacts/* + files: release/*.zip generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/ChangeLog b/ChangeLog index a6f4824..dd0e9a3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,19 @@ MUI HTMLview MCC class - ChangeLog $Id$ $URL$ +2026-04-20 Dimitris Panokostas + + * Release 13.6. + * mcc/rev.h, mcp/rev.h: bumped LIB_REVISION to 6, LIB_DATE to + 20.04.2026, and copyright range to 2005-2026. + * .github/workflows/makefile.yml: release job now packages each + platform's mcc/mcp binaries into a single versioned zip + (HTMLview--.zip) instead of uploading loose + files that collided across platforms via merge-multiple. + Debug/map artefacts and the test.png are stripped from the + release bundle; README, COPYING, ChangeLog and AUTHORS are + included. + 2026-04-18 Dimitris Panokostas * mcc/test_image_hook.h: HTTPS support via AmiSSL. The shared test-program diff --git a/mcc/rev.h b/mcc/rev.h index b653f9a..f548c1a 100644 --- a/mcc/rev.h +++ b/mcc/rev.h @@ -2,7 +2,7 @@ HTMLview.mcc - HTMLview MUI Custom Class Copyright (C) 1997-2000 Allan Odgaard - Copyright (C) 2005-2007 by HTMLview.mcc Open Source Team + Copyright (C) 2005-2026 by HTMLview.mcc Open Source Team This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -21,10 +21,10 @@ ***************************************************************************/ #define LIB_VERSION 13 -#define LIB_REVISION 5 +#define LIB_REVISION 6 -#define LIB_REV_STRING "13.5" -#define LIB_DATE "15.12.2025" +#define LIB_REV_STRING "13.6" +#define LIB_DATE "20.04.2026" #if defined(__PPC__) #if defined(__MORPHOS__) @@ -44,4 +44,4 @@ #define CPU "" #endif -#define LIB_COPYRIGHT "Copyright (C) 2005-2025 HTMLview.mcc Open Source Team" +#define LIB_COPYRIGHT "Copyright (C) 2005-2026 HTMLview.mcc Open Source Team" diff --git a/mcp/rev.h b/mcp/rev.h index b653f9a..f548c1a 100644 --- a/mcp/rev.h +++ b/mcp/rev.h @@ -2,7 +2,7 @@ HTMLview.mcc - HTMLview MUI Custom Class Copyright (C) 1997-2000 Allan Odgaard - Copyright (C) 2005-2007 by HTMLview.mcc Open Source Team + Copyright (C) 2005-2026 by HTMLview.mcc Open Source Team This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -21,10 +21,10 @@ ***************************************************************************/ #define LIB_VERSION 13 -#define LIB_REVISION 5 +#define LIB_REVISION 6 -#define LIB_REV_STRING "13.5" -#define LIB_DATE "15.12.2025" +#define LIB_REV_STRING "13.6" +#define LIB_DATE "20.04.2026" #if defined(__PPC__) #if defined(__MORPHOS__) @@ -44,4 +44,4 @@ #define CPU "" #endif -#define LIB_COPYRIGHT "Copyright (C) 2005-2025 HTMLview.mcc Open Source Team" +#define LIB_COPYRIGHT "Copyright (C) 2005-2026 HTMLview.mcc Open Source Team" From 6d97f490f0a4492fd115cdb1dd6d44a37bbd9be4 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Mon, 20 Apr 2026 19:51:09 +0200 Subject: [PATCH 23/32] feat(net-hook): reusable load hook library with TLS verify + cache Extract the reference image/content load hook out of the per-consumer `test_image_hook.h` blob and into a proper static library under `mcc/net_hook/`. Host applications now link `libhtmlview_nethook.a` and call `HTMLviewNet_InitHook(&hook)`; the hook opens bsdsocket / AmiSSL lazily from the decoder task so hosts no longer need any OpenLibrary plumbing of their own. Adds three opt-in runtime knobs on top of the extraction: - `HTMLviewNet_SetCABundle` / `SetVerifyMode` -- flip AmiSSL off the previous `SSL_VERIFY_NONE` default to auto-discovered peer verify (checks `AmiSSL:Certs/curl-ca-bundle.crt` and ENVARC mirrors), including hostname matching via `SSL_set1_host`. - `HTMLviewNet_SetCacheDir` / `SetCacheTTL` -- simple on-disk response cache keyed by FNV-1a-64 of the URL, storing `.body` + `.meta` pairs. Disabled by default; caps entries at 8 MB, non-chunked 200 responses only. ETag revalidation is deferred to a follow-up. Also fixes a latent CI break in `mcc/Makefile`: the `\$(AMISSL_SDK_READY)` rule was defined before `all:`, so a bare `make OS=os3` used the SDK fetch as the default goal and exited 0 without building anything. The 13.6 release job hit exactly this and silently uploaded an artifact with no `.mcc` inside. Pinning `.DEFAULT_GOAL := all` at the top of the file makes `make` build by default; the new `libhtmlview_nethook.a` target and net_hook obj subdir are wired in alongside. The full improvement plan / session handoff lives in IMPROVEMENTS.md. --- mcc/LibLoad_Test.c | 39 +- mcc/Makefile | 52 +- mcc/SimpleTest.c | 42 +- .../htmlview_nethook.c} | 607 ++++++++++++++---- mcc/net_hook/htmlview_nethook.h | 117 ++++ 5 files changed, 654 insertions(+), 203 deletions(-) rename mcc/{test_image_hook.h => net_hook/htmlview_nethook.c} (50%) create mode 100644 mcc/net_hook/htmlview_nethook.h diff --git a/mcc/LibLoad_Test.c b/mcc/LibLoad_Test.c index 3682a0c..fbda609 100644 --- a/mcc/LibLoad_Test.c +++ b/mcc/LibLoad_Test.c @@ -7,11 +7,6 @@ #include #include #include -#if defined(__MORPHOS__) -#include -#elif defined(__amigaos4__) -#include -#endif #include #if defined(__amigaos4__) @@ -19,15 +14,13 @@ struct Library *IntuitionBase; struct Library *MUIMasterBase; struct IntuitionIFace *IIntuition; struct MUIMasterIFace *IMUIMaster; -struct Library *SocketBase; -struct SocketIFace *ISocket; #else struct IntuitionBase *IntuitionBase; struct Library *MUIMasterBase; -struct Library *SocketBase; #endif -#include "test_image_hook.h" +#include "HTMLview_mcc.h" +#include "net_hook/htmlview_nethook.h" enum { MSG_OPEN_HTMLVIEW = 1, @@ -111,12 +104,8 @@ int main(int argc, char **argv) (void)argc; (void)argv; - SocketBase = OpenLibrary("bsdsocket.library", 4); -#if defined(__amigaos4__) - if (SocketBase) - ISocket = (struct SocketIFace *)GetInterface(SocketBase, "main", 1, NULL); -#endif - /* Socket library is optional -- we still support local files without it. */ + /* bsdsocket / AmiSSL are opened on demand by the nethook library from + the HTMLview decoder task -- nothing to do here at startup. */ #if defined(__amigaos4__) IntuitionBase = OpenLibrary("intuition.library", 39); @@ -135,19 +124,7 @@ int main(int argc, char **argv) goto cleanup; } - /* CallHookPkt on m68k and MorphOS passes args in registers (a0=hook, - a2=obj, a1=msg); our plain-C TestImageHookFunc expects stack args. - HookEntry is the amiga.lib trampoline that does the register->stack - conversion. On OS4 hooks are already called with stack args, and - HookEntry is not declared, so we assign directly. */ -#if defined(__amigaos4__) - loadHook.h_Entry = (HOOKFUNC)TestImageHookFunc; - loadHook.h_SubEntry = NULL; -#else - loadHook.h_Entry = (HOOKFUNC)HookEntry; - loadHook.h_SubEntry = (HOOKFUNC)TestImageHookFunc; -#endif - loadHook.h_Data = NULL; + HTMLviewNet_InitHook(&loadHook); app = MUI_NewObject(MUIC_Application, MUIA_Application_Title, (ULONG)"LibLoad Test", @@ -258,15 +235,9 @@ int main(int argc, char **argv) if (IIntuition) DropInterface((struct Interface *)IIntuition); CloseLibrary(IntuitionBase); } - if (SocketBase) - { - if (ISocket) DropInterface((struct Interface *)ISocket); - CloseLibrary(SocketBase); - } #else if (MUIMasterBase) CloseLibrary(MUIMasterBase); if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase); - if (SocketBase) CloseLibrary(SocketBase); #endif return 0; diff --git a/mcc/Makefile b/mcc/Makefile index b2b5f54..f7f8527 100644 --- a/mcc/Makefile +++ b/mcc/Makefile @@ -20,6 +20,11 @@ # #***************************************************************************/ +# Default goal. The $(AMISSL_SDK_READY) rule is defined earlier in the +# file than `all`, so without this pin `make` on its own would pick the +# AmiSSL SDK fetch as the default goal instead of a full build. +.DEFAULT_GOAL := all + ########################################################################### # This makefile is a very generic one. It tries to identify both, the host # and the target operating system for which YAM should be compiled. @@ -133,6 +138,7 @@ MCCTARGET = $(BINDIR)/HTMLview.mcc TESTTARGET= $(BINDIR)/HTMLview-Test SIMPLETARGET= $(BINDIR)/SimpleTest LIBLOADTARGET= $(BINDIR)/LibLoad_Test +NETHOOKLIB = $(BINDIR)/libhtmlview_nethook.a # Common compiler/linker flags WARN = -W -Wall -Wwrite-strings @@ -168,6 +174,7 @@ ifeq ($(OS), os4) CXX = ppc-amigaos-g++ STRIP = ppc-amigaos-strip OBJDUMP = ppc-amigaos-objdump + AR = ppc-amigaos-ar # Compiler/Linker flags CRT = newlib @@ -193,6 +200,7 @@ ifeq ($(OS), os3) CXX = m68k-amigaos-g++ STRIP = m68k-amigaos-strip OBJDUMP = m68k-amigaos-objdump + AR = m68k-amigaos-ar # Compiler/Linker flags CPU = -m68020-60 -msoft-float @@ -216,6 +224,7 @@ ifeq ($(OS), mos) CXX = ppc-morphos-g++ STRIP = ppc-morphos-strip OBJDUMP = ppc-morphos-objdump + AR = ppc-morphos-ar # Compiler/Linker flags CPU = -mcpu=powerpc @@ -237,6 +246,7 @@ ifeq ($(OS), aros) CXX = i686-aros-g++ STRIP = i686-aros-strip OBJDUMP = i686-aros-objdump + AR = i686-aros-ar # Compiler/Linker flags CPU = @@ -464,13 +474,21 @@ CATALOGS = $(LOCALE)/deutsch.catalog # # Build test programs for all platforms -all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(SIMPLETARGET) $(LIBLOADTARGET) $(BINDIR)/test.png +all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(NETHOOKLIB) $(SIMPLETARGET) $(LIBLOADTARGET) $(BINDIR)/test.png # make the object directories $(OBJDIR): @echo " MKDIR $@" @$(MKDIR) $(OBJDIR) @$(MKDIR) $(OBJDIR)/classes + @$(MKDIR) $(OBJDIR)/net_hook + +# Order-only prereq used by sources in subdirectories. Needed because the +# $(OBJDIR) rule above only fires when $(OBJDIR) itself doesn't exist, so +# reusing an existing $(OBJDIR) from a prior build would otherwise leave +# the net_hook subdir missing. +$(OBJDIR)/net_hook: + @$(MKDIR) $(OBJDIR)/net_hook # make the binary directories $(BINDIR): @@ -491,15 +509,24 @@ $(OBJDIR)/mccclass_68k.o: ../include/mccclass_68k.c @echo " CC $<" @$(CC) $(CFLAGS) $< -o $@ -# Test programs get the AmiSSL include path (if enabled). The explicit -# rules below shadow the generic %.o: %.c rule so TEST_CFLAGS applies. -$(OBJDIR)/SimpleTest.o: SimpleTest.c test_image_hook.h $(TEST_DEPS) +# The net hook library carries all HTTP/TLS/AmiSSL glue. Test programs +# just #include and link against it. +$(OBJDIR)/net_hook/htmlview_nethook.o: net_hook/htmlview_nethook.c \ + net_hook/htmlview_nethook.h HTMLview_mcc.h $(TEST_DEPS) | $(OBJDIR)/net_hook @echo " CC $<" @$(CC) $(CFLAGS) $(TEST_CFLAGS) $< -o $@ -$(OBJDIR)/LibLoad_Test.o: LibLoad_Test.c test_image_hook.h $(TEST_DEPS) +$(NETHOOKLIB): $(OBJDIR)/net_hook/htmlview_nethook.o | $(BINDIR) + @echo " AR $@" + @$(AR) rcs $@ $^ + +$(OBJDIR)/SimpleTest.o: SimpleTest.c net_hook/htmlview_nethook.h @echo " CC $<" - @$(CC) $(CFLAGS) $(TEST_CFLAGS) $< -o $@ + @$(CC) $(CFLAGS) $< -o $@ + +$(OBJDIR)/LibLoad_Test.o: LibLoad_Test.c net_hook/htmlview_nethook.h + @echo " CC $<" + @$(CC) $(CFLAGS) $< -o $@ # @@ -507,21 +534,21 @@ $(OBJDIR)/LibLoad_Test.o: LibLoad_Test.c test_image_hook.h $(TEST_DEPS) LIBOBJS = $(filter-out $(OBJDIR)/library.o $(OBJDIR)/crtclasses_begin.o $(OBJDIR)/crtclasses_end.o, $(MCCOBJS)) -$(SIMPLETARGET): $(OBJDIR)/SimpleTest.o +$(SIMPLETARGET): $(OBJDIR)/SimpleTest.o $(NETHOOKLIB) @echo " LD $@" ifneq (,$(DEBUG)) - @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) $(TEST_LDLIBS) -ldebug -Wl,-Map,$@.map + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o -L$(BINDIR) -lhtmlview_nethook $(LDLIBS) $(TEST_LDLIBS) -ldebug -Wl,-Map,$@.map else - @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o $(LDLIBS) $(TEST_LDLIBS) -Wl,-Map,$@.map + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/SimpleTest.o -L$(BINDIR) -lhtmlview_nethook $(LDLIBS) $(TEST_LDLIBS) -Wl,-Map,$@.map endif @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug -$(LIBLOADTARGET): $(OBJDIR)/LibLoad_Test.o +$(LIBLOADTARGET): $(OBJDIR)/LibLoad_Test.o $(NETHOOKLIB) @echo " LD $@" ifneq (,$(DEBUG)) - @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o $(LDLIBS) $(TEST_LDLIBS) -ldebug -Wl,-Map,$@.map + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o -L$(BINDIR) -lhtmlview_nethook $(LDLIBS) $(TEST_LDLIBS) -ldebug -Wl,-Map,$@.map else - @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o $(LDLIBS) $(TEST_LDLIBS) -Wl,-Map,$@.map + @$(CXX) $(filter-out -nostartfiles,$(LDFLAGS)) -o $@.debug $(OBJDIR)/LibLoad_Test.o -L$(BINDIR) -lhtmlview_nethook $(LDLIBS) $(TEST_LDLIBS) -Wl,-Map,$@.map endif @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug @@ -563,6 +590,7 @@ clean: -$(RM) $(TESTTARGET) $(TESTTARGET).debug $(TESTTARGET).map -$(RM) $(SIMPLETARGET) $(SIMPLETARGET).debug $(SIMPLETARGET).map -$(RM) $(LIBLOADTARGET) $(LIBLOADTARGET).debug $(LIBLOADTARGET).map + -$(RM) $(NETHOOKLIB) $(OBJDIR)/net_hook/htmlview_nethook.o -$(RM) $(MCCOBJS) $(TESTOBJS) $(M68KSTUBS) .PHONY: distclean diff --git a/mcc/SimpleTest.c b/mcc/SimpleTest.c index 64f136c..af45913 100644 --- a/mcc/SimpleTest.c +++ b/mcc/SimpleTest.c @@ -8,23 +8,14 @@ #include #include -#if defined(__MORPHOS__) -#include -#elif defined(__amigaos4__) -#include -#endif - #if defined(__amigaos4__) struct Library *IntuitionBase; struct Library *MUIMasterBase; struct Interface *IntuitionIFace; struct MUIMasterIFace *IMUIMaster; -struct Library *SocketBase; -struct SocketIFace *ISocket; #else struct Library *MUIMasterBase; struct IntuitionBase *IntuitionBase; -struct Library *SocketBase; #endif struct Library *UtilityBase; @@ -34,7 +25,8 @@ extern void kprintf(const char *fmt, ...); static void kprintf(const char *fmt, ...) { (void)fmt; } #endif -#include "test_image_hook.h" +#include "HTMLview_mcc.h" +#include "net_hook/htmlview_nethook.h" static struct Hook ImageLoadHook; @@ -148,25 +140,10 @@ int main(void) return 20; } - SocketBase = OpenLibrary("bsdsocket.library", 4); -#if defined(__amigaos4__) - if (SocketBase) - ISocket = (struct SocketIFace *)GetInterface(SocketBase, "main", 1, NULL); -#endif - - /* CallHookPkt on m68k and MorphOS passes args in registers (a0=hook, - a2=obj, a1=msg); our plain-C TestImageHookFunc expects stack args. - HookEntry is the amiga.lib trampoline that does the register->stack - conversion. On OS4 hooks are already called with stack args, and - HookEntry is not declared, so we assign directly. */ -#if defined(__amigaos4__) - ImageLoadHook.h_Entry = (HOOKFUNC)TestImageHookFunc; - ImageLoadHook.h_SubEntry = NULL; -#else - ImageLoadHook.h_Entry = (HOOKFUNC)HookEntry; - ImageLoadHook.h_SubEntry = (HOOKFUNC)TestImageHookFunc; -#endif - ImageLoadHook.h_Data = NULL; + /* The nethook library opens bsdsocket/amisslmaster/amissl on demand + from the HTMLview decoder task; nothing to do here beyond wiring + the hook pointer into MUI. */ + HTMLviewNet_InitHook(&ImageLoadHook); app = MUI_NewObject(MUIC_Application, MUIA_Application_Title , "HTMLView Simple Test", @@ -227,13 +204,6 @@ int main(void) MUI_DisposeObject(app); } - if (SocketBase) - { -#if defined(__amigaos4__) - if (ISocket) DropInterface((struct Interface *)ISocket); -#endif - CloseLibrary(SocketBase); - } if (MUIMasterBase) { #if defined(__amigaos4__) diff --git a/mcc/test_image_hook.h b/mcc/net_hook/htmlview_nethook.c similarity index 50% rename from mcc/test_image_hook.h rename to mcc/net_hook/htmlview_nethook.c index 0c253cd..5d9a7b5 100644 --- a/mcc/test_image_hook.h +++ b/mcc/net_hook/htmlview_nethook.c @@ -1,40 +1,39 @@ +/*************************************************************************** + + HTMLview.mcc - HTMLview MUI Custom Class + Copyright (C) 1997-2000 Allan Odgaard + Copyright (C) 2005-2026 by HTMLview.mcc Open Source Team + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + +***************************************************************************/ + /* - * test_image_hook.h -- Shared image-load hook used by SimpleTest and - * LibLoad_Test to feed picture data to HTMLview.mcc. + * Reference image/content load hook for HTMLview.mcc. * - * URL schemes resolved by the hook: - * - PROGDIR:, DH0:, etc. -- direct dos Open - * - file:// -- stripped and dos Open - * - http://host[:port]/p -- bsdsocket GET, chunked-aware, redirect-aware - * - https://host[:port]/p -- AmiSSL-wrapped GET (only if HAVE_AMISSL) + * All I/O libraries (bsdsocket, amisslmaster, amissl) are opened lazily + * by the hook itself, from whichever task actually dispatches the load + * (HTMLview runs hook calls from a decoder thread). The host program + * does not need to OpenLibrary any of them. * - * The hook follows up to 5 consecutive 3xx redirects (absolute URLs only; - * relative Location: headers are NOT supported yet) and upgrades http->https - * transparently when a server redirects to TLS. - * - * HAVE_AMISSL is defined by the Makefile when the AmiSSL SDK is present. - * Without it, https:// URLs and http->https redirects fail cleanly. - * - * Threading: runs in the HTMLview decoder task. On m68k every task that uses - * bsdsocket must OpenLibrary the library itself, so this file opens a task- - * local SocketBase (and AmiSSLMasterBase / AmiSSLBase) in the hook and - * shadows the file-scope globals so the proto/socket.h inline macros - * dispatch through the local base. - * - * Include this header from exactly one translation unit per test program. + * This compilation unit is the only place the hook logic lives; consumers + * link libhtmlview_nethook.a and include htmlview_nethook.h. */ -#ifndef HTMLVIEW_TEST_IMAGE_HOOK_H -#define HTMLVIEW_TEST_IMAGE_HOOK_H - #include #include #include +#include #include #include #include #include -#include "HTMLview_mcc.h" + +#include "../HTMLview_mcc.h" +#include "htmlview_nethook.h" #ifdef HAVE_AMISSL #include @@ -51,18 +50,8 @@ #define FALSE 0 #endif - -/* Owned by the test program's main task. The hook uses its own task-local - library bases and never touches these from the decoder thread. */ -#if defined(__amigaos4__) -extern struct Library *SocketBase; -extern struct SocketIFace *ISocket; -#else -extern struct Library *SocketBase; -#endif - /* Per-transfer state stored in lm_Userdata. */ -struct THL_State +struct HVN_State { BPTR file; /* file handle if this is a local file */ LONG socket; /* bsdsocket handle, -1 if none */ @@ -72,6 +61,7 @@ struct THL_State int chunked; /* non-zero => Transfer-Encoding: chunked */ ULONG chunk_left; int use_tls; /* non-zero => route I/O through SSL_* */ + ULONG content_length; /* 0 if not advertised */ /* Task-local bsdsocket bases. Opened by the decoder thread so it can actually call socket/recv/etc.; closed on hook-Close. */ @@ -98,24 +88,24 @@ struct THL_State /* Accumulating-buffer + rewrite pattern (same as ImageManager DTLog) so the log file stays coherent across OS3 and OS4 without relying on Seek. */ -static char THL_LogBuf[16384]; -static ULONG THL_LogLen = 0; +static char HVN_LogBuf[16384]; +static ULONG HVN_LogLen = 0; -static void THL_Log(const char *line) +static void HVN_Log(const char *line) { ULONG need = strlen(line) + 1; - if (THL_LogLen + need + 1 >= sizeof(THL_LogBuf)) return; - memcpy(THL_LogBuf + THL_LogLen, line, need - 1); - THL_LogLen += need - 1; - THL_LogBuf[THL_LogLen++] = '\n'; - THL_LogBuf[THL_LogLen] = 0; + if (HVN_LogLen + need + 1 >= sizeof(HVN_LogBuf)) return; + memcpy(HVN_LogBuf + HVN_LogLen, line, need - 1); + HVN_LogLen += need - 1; + HVN_LogBuf[HVN_LogLen++] = '\n'; + HVN_LogBuf[HVN_LogLen] = 0; BPTR f = Open((STRPTR)"T:htmlview_hook.log", MODE_NEWFILE); - if (f) { Write(f, THL_LogBuf, THL_LogLen); Close(f); } + if (f) { Write(f, HVN_LogBuf, HVN_LogLen); Close(f); } } /* --- low-level I/O (plain TCP or TLS depending on st->use_tls) --------- */ -static LONG THL_Recv(struct THL_State *st, APTR buf, LONG len) +static LONG HVN_Recv(struct HVN_State *st, APTR buf, LONG len) { #ifdef HAVE_AMISSL if (st->use_tls && st->ssl) @@ -141,7 +131,7 @@ static LONG THL_Recv(struct THL_State *st, APTR buf, LONG len) } } -static LONG THL_Send(struct THL_State *st, const APTR buf, LONG len) +static LONG HVN_Send(struct HVN_State *st, const APTR buf, LONG len) { #ifdef HAVE_AMISSL if (st->use_tls && st->ssl) @@ -163,13 +153,13 @@ static LONG THL_Send(struct THL_State *st, const APTR buf, LONG len) } } -static LONG THL_RecvLine(struct THL_State *st, char *buf, LONG maxlen) +static LONG HVN_RecvLine(struct HVN_State *st, char *buf, LONG maxlen) { LONG i = 0; while (i < maxlen - 1) { UBYTE c; - LONG got = THL_Recv(st, (APTR)&c, 1); + LONG got = HVN_Recv(st, (APTR)&c, 1); if (got <= 0) return -1; if (c == '\n') { buf[i] = 0; return i; } if (c != '\r') buf[i++] = c; @@ -179,7 +169,7 @@ static LONG THL_RecvLine(struct THL_State *st, char *buf, LONG maxlen) } /* Reads up to `want` bytes, unwrapping chunked transfer encoding. */ -static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want) +static LONG HVN_ReadChunked(struct HVN_State *st, UBYTE *out, LONG want) { LONG produced = 0; while (produced < want) @@ -187,11 +177,11 @@ static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want) if (st->chunk_left == 0) { char line[32]; - if (THL_RecvLine(st, line, sizeof(line)) < 0) + if (HVN_RecvLine(st, line, sizeof(line)) < 0) return produced; if (line[0] == 0) { - if (THL_RecvLine(st, line, sizeof(line)) < 0) + if (HVN_RecvLine(st, line, sizeof(line)) < 0) return produced; } ULONG size = strtoul(line, NULL, 16); @@ -202,7 +192,7 @@ static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want) LONG take = want - produced; if ((ULONG)take > st->chunk_left) take = (LONG)st->chunk_left; - LONG got = THL_Recv(st, (APTR)(out + produced), take); + LONG got = HVN_Recv(st, (APTR)(out + produced), take); if (got <= 0) return produced; produced += got; st->chunk_left -= got; @@ -211,7 +201,7 @@ static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want) } /* Parses http:// or https:// URLs. Sets *is_https from scheme. */ -static int THL_ParseUrl(CONST_STRPTR url, char *host, ULONG hlen, +static int HVN_ParseUrl(CONST_STRPTR url, char *host, ULONG hlen, char *path, ULONG plen, ULONG *portp, int *is_https) { const char *p = url; @@ -249,7 +239,7 @@ static int THL_ParseUrl(CONST_STRPTR url, char *host, ULONG hlen, /* --- connection teardown (shared by error paths and redirect loop) ----- */ -static void THL_Disconnect(struct THL_State *st) +static void HVN_Disconnect(struct HVN_State *st) { #ifdef HAVE_AMISSL if (st->use_tls) @@ -297,10 +287,233 @@ static void THL_Disconnect(struct THL_State *st) st->buflen = st->bufpos = 0; } +/* --- TLS verification config ------------------------------------------ */ + +/* File-scope verification config. Host reads/writes via the public setter + APIs before triggering any loads; decoder tasks only read. Writes are + word-sized and in practice never touched concurrently on any target. + Stored outside the HAVE_AMISSL gate so MorphOS / non-SSL builds link + cross-platform callers cleanly -- the config is simply unused there. */ +static char HVN_CaBundle[256] = { 0 }; +static int HVN_VerifyMode = HTMLVIEWNET_VERIFY_AUTO; + +/* Candidate CA-bundle locations tried when the host hasn't overridden via + SetCABundle(). Order reflects decreasing user-installability: explicit + AmiSSL install paths first, then the ENVARC mirror, then a couple of + common sibling locations other AmiSSL consumers ship to. */ +static const char *const HVN_DefaultBundles[] = { + "AmiSSL:Certs/curl-ca-bundle.crt", + "ENVARC:AmiSSL/Certs/curl-ca-bundle.crt", + "ENV:AmiSSL/Certs/curl-ca-bundle.crt", + "SYS:Storage/AmiSSL/Certs/curl-ca-bundle.crt", + NULL +}; + +/* Fast "does this file exist" check via AmigaDOS. SSL_CTX_load_verify_ + locations() itself happily succeeds on a zero-byte/missing file on + some OpenSSL builds, so we gate on Open() before handing the path + over. */ +static int HVN_FileExists(const char *path) +{ + BPTR f = Open((STRPTR)path, MODE_OLDFILE); + if (!f) return 0; + Close(f); + return 1; +} + +/* Resolve the path to hand to SSL_CTX_load_verify_locations. User override + wins; otherwise walk the default list. Returns NULL if nothing found. */ +static const char *HVN_ResolveCaBundle(void) +{ + if (HVN_CaBundle[0]) + return HVN_FileExists(HVN_CaBundle) ? HVN_CaBundle : NULL; + + for (int i = 0; HVN_DefaultBundles[i]; i++) + if (HVN_FileExists(HVN_DefaultBundles[i])) + return HVN_DefaultBundles[i]; + + return NULL; +} + +void HTMLviewNet_SetCABundle(const char *path) +{ + if (!path || !*path) { HVN_CaBundle[0] = 0; return; } + ULONG n = strlen(path); + if (n >= sizeof(HVN_CaBundle)) n = sizeof(HVN_CaBundle) - 1; + memcpy(HVN_CaBundle, path, n); + HVN_CaBundle[n] = 0; +} + +void HTMLviewNet_SetVerifyMode(int mode) +{ + if (mode == HTMLVIEWNET_VERIFY_AUTO || + mode == HTMLVIEWNET_VERIFY_NONE || + mode == HTMLVIEWNET_VERIFY_PEER) + { + HVN_VerifyMode = mode; + } +} + +/* --- response cache --------------------------------------------------- */ + +/* Maximum cacheable body size. Larger responses stream straight to the + consumer without being mirrored to disk, both to bound memory use and + to keep cold-path latency acceptable. */ +#define HVN_CACHE_MAX_BYTES (8UL * 1024UL * 1024UL) + +static char HVN_CacheDir[256] = { 0 }; +static ULONG HVN_CacheTTL = 3600; + +void HTMLviewNet_SetCacheDir(const char *path) +{ + if (!path || !*path) { HVN_CacheDir[0] = 0; return; } + ULONG n = strlen(path); + if (n >= sizeof(HVN_CacheDir)) n = sizeof(HVN_CacheDir) - 1; + memcpy(HVN_CacheDir, path, n); + HVN_CacheDir[n] = 0; +} + +void HTMLviewNet_SetCacheTTL(ULONG seconds) +{ + HVN_CacheTTL = seconds; +} + +/* FNV-1a 64-bit over the URL string. Collision-resistant enough for a + user-local cache and cheap to compute without dragging in an SSL hash. */ +static void HVN_CacheKey(const char *url, char out[17]) +{ + static const char hex[] = "0123456789abcdef"; + ULONG h1 = 0x811c9dc5UL; + ULONG h2 = 0xcbf29ce4UL; + for (const unsigned char *p = (const unsigned char *)url; *p; p++) + { + h1 ^= *p; h1 *= 0x01000193UL; + h2 ^= *p; h2 *= 0x100000001UL & 0xffffffffUL; + h2 += (h1 << 1); + } + for (int i = 0; i < 8; i++) out[i] = hex[(h1 >> ((7 - i) * 4)) & 0xf]; + for (int i = 0; i < 8; i++) out[8 + i] = hex[(h2 >> ((7 - i) * 4)) & 0xf]; + out[16] = 0; +} + +/* Build "/" in buf. Returns 1 on success. */ +static int HVN_CachePath(char *buf, ULONG bufsz, const char *key, const char *suffix) +{ + if (!HVN_CacheDir[0]) return 0; + int n = snprintf(buf, bufsz, "%s/%s%s", HVN_CacheDir, key, suffix); + return (n > 0 && (ULONG)n < bufsz); +} + +/* Minimal portable time source. clock() is good enough for relative + freshness decisions inside a session; for absolute dates we'd need + DateStamp(), but the Expires field we write is just + now() + max-age so everything is on the same monotonic scale. */ +static ULONG HVN_Now(void) +{ + /* AmigaDOS DateStamp: days since 1978, mins, ticks. Convert to a + single ULONG "seconds since 1978" -- room for ~136 years. */ + struct DateStamp ds; + DateStamp(&ds); + return (ULONG)ds.ds_Days * 86400UL + + (ULONG)ds.ds_Minute * 60UL + + (ULONG)(ds.ds_Tick / 50); +} + +/* Parse the .meta file at `path`. Populates *expires_out. Returns 1 on + success. Meta format is one `key=value` line per field; unknown keys + are ignored, missing Expires defaults to 0 (treat as stale). */ +static int HVN_MetaRead(const char *path, ULONG *expires_out) +{ + *expires_out = 0; + BPTR f = Open((STRPTR)path, MODE_OLDFILE); + if (!f) return 0; + char buf[512]; + LONG n = Read(f, buf, sizeof(buf) - 1); + Close(f); + if (n <= 0) return 0; + buf[n] = 0; + const char *p = strstr(buf, "Expires="); + if (p) + { + p += 8; + ULONG v = 0; + while (*p >= '0' && *p <= '9') { v = v*10 + (*p - '0'); p++; } + *expires_out = v; + } + return 1; +} + +/* Write a .meta file. Intentionally tiny -- URL for humans to grep, the + numeric Expires for the freshness check. ETag/Last-Modified are future + work (they'd drive 304 revalidation). */ +static int HVN_MetaWrite(const char *path, const char *url, ULONG expires) +{ + BPTR f = Open((STRPTR)path, MODE_NEWFILE); + if (!f) return 0; + char buf[512]; + int n = snprintf(buf, sizeof(buf), "URL=%s\nExpires=%lu\n", url, expires); + if (n > 0) Write(f, buf, n); + Close(f); + return 1; +} + +/* Is a cache entry present and fresh? If yes, returns a BPTR to the open + body file (caller owns the handle); else ZERO. */ +static BPTR HVN_CacheTryOpen(const char *url) +{ + if (!HVN_CacheDir[0]) return (BPTR)0; + + char key[17]; + char metapath[320], bodypath[320]; + HVN_CacheKey(url, key); + if (!HVN_CachePath(metapath, sizeof(metapath), key, ".meta")) return (BPTR)0; + if (!HVN_CachePath(bodypath, sizeof(bodypath), key, ".body")) return (BPTR)0; + + ULONG expires = 0; + if (!HVN_MetaRead(metapath, &expires)) return (BPTR)0; + if (HVN_Now() >= expires) return (BPTR)0; + + BPTR bf = Open((STRPTR)bodypath, MODE_OLDFILE); + if (bf) + { + char logbuf[320]; + snprintf(logbuf, sizeof(logbuf), "cache: HIT %s -> %s", url, bodypath); + HVN_Log(logbuf); + } + return bf; +} + +/* Persist a body+meta pair. Errors are logged but non-fatal (a cache + miss on the next fetch is the worst outcome). */ +static void HVN_CacheStore(const char *url, const UBYTE *body, ULONG len, ULONG ttl) +{ + if (!HVN_CacheDir[0] || !body || !len || !ttl) return; + if (len > HVN_CACHE_MAX_BYTES) return; + + char key[17]; + char metapath[320], bodypath[320]; + HVN_CacheKey(url, key); + if (!HVN_CachePath(metapath, sizeof(metapath), key, ".meta")) return; + if (!HVN_CachePath(bodypath, sizeof(bodypath), key, ".body")) return; + + BPTR f = Open((STRPTR)bodypath, MODE_NEWFILE); + if (!f) { HVN_Log("cache: Open(body, NEWFILE) failed"); return; } + LONG w = Write(f, (APTR)body, (LONG)len); + Close(f); + if (w != (LONG)len) { HVN_Log("cache: short Write on body"); return; } + + HVN_MetaWrite(metapath, url, HVN_Now() + ttl); + + char logbuf[320]; + snprintf(logbuf, sizeof(logbuf), "cache: STORE %s (%lu bytes, ttl=%lu)", + url, len, ttl); + HVN_Log(logbuf); +} + /* --- AmiSSL-wrapped connect (only linked when HAVE_AMISSL) ------------- */ #ifdef HAVE_AMISSL -static int THL_TlsWrap(struct THL_State *st, const char *host) +static int HVN_TlsWrap(struct HVN_State *st, const char *host) { char logbuf[256]; @@ -318,33 +531,33 @@ static int THL_TlsWrap(struct THL_State *st, const char *host) st->AMSBase = OpenLibrary((STRPTR)"amisslmaster.library", AMISSLMASTER_MIN_VERSION); if (!st->AMSBase) { - THL_Log("https: OpenLibrary amisslmaster.library failed"); + HVN_Log("https: OpenLibrary amisslmaster.library failed"); return 0; } AmiSSLMasterBase = st->AMSBase; #if defined(__amigaos4__) st->IAMSMaster = (struct AmiSSLMasterIFace *) GetInterface(st->AMSBase, "main", 1, NULL); - if (!st->IAMSMaster) { THL_Log("https: GetInterface IAmiSSLMaster failed"); return 0; } + if (!st->IAMSMaster) { HVN_Log("https: GetInterface IAmiSSLMaster failed"); return 0; } IAmiSSLMaster = st->IAMSMaster; #endif if (!InitAmiSSLMaster(AMISSL_CURRENT_VERSION, TRUE)) { - THL_Log("https: InitAmiSSLMaster failed (library too old)"); + HVN_Log("https: InitAmiSSLMaster failed (library too old)"); return 0; } st->ASBase = OpenAmiSSL(); if (!st->ASBase) { - THL_Log("https: OpenAmiSSL failed"); + HVN_Log("https: OpenAmiSSL failed"); return 0; } AmiSSLBase = st->ASBase; #if defined(__amigaos4__) st->IAS = (struct AmiSSLIFace *)GetInterface(st->ASBase, "main", 1, NULL); - if (!st->IAS) { THL_Log("https: GetInterface IAmiSSL failed"); return 0; } + if (!st->IAS) { HVN_Log("https: GetInterface IAmiSSL failed"); return 0; } IAmiSSL = st->IAS; #endif @@ -362,7 +575,7 @@ static int THL_TlsWrap(struct THL_State *st, const char *host) }; if (InitAmiSSLA(init_tags) != 0) { - THL_Log("https: InitAmiSSL failed"); + HVN_Log("https: InitAmiSSL failed"); return 0; } } @@ -383,38 +596,97 @@ static int THL_TlsWrap(struct THL_State *st, const char *host) } st->ssl_ctx = SSL_CTX_new(TLS_client_method()); - if (!st->ssl_ctx) { THL_Log("https: SSL_CTX_new failed"); return 0; } + if (!st->ssl_ctx) { HVN_Log("https: SSL_CTX_new failed"); return 0; } + + /* Resolve verification policy for this request. AUTO is best-effort + (peer-verify if a bundle is installed, else warn and continue); + PEER is fail-closed; NONE is the pre-Phase-2 behaviour. */ + int want_verify; + switch (HVN_VerifyMode) + { + case HTMLVIEWNET_VERIFY_NONE: want_verify = 0; break; + case HTMLVIEWNET_VERIFY_PEER: want_verify = 1; break; + default: want_verify = 1; break; /* AUTO */ + } + + const char *bundle = want_verify ? HVN_ResolveCaBundle() : NULL; + if (want_verify && bundle) + { + if (SSL_CTX_load_verify_locations(st->ssl_ctx, bundle, NULL) != 1) + { + snprintf(logbuf, sizeof(logbuf), + "https: load_verify_locations(%s) failed", bundle); + HVN_Log(logbuf); + if (HVN_VerifyMode == HTMLVIEWNET_VERIFY_PEER) return 0; + want_verify = 0; /* AUTO: fall back to no verify */ + } + else + { + snprintf(logbuf, sizeof(logbuf), + "https: CA bundle loaded from %s", bundle); + HVN_Log(logbuf); + } + } + else if (want_verify) + { + if (HVN_VerifyMode == HTMLVIEWNET_VERIFY_PEER) + { + HVN_Log("https: VERIFY_PEER requested but no CA bundle found"); + return 0; + } + HVN_Log("https: no CA bundle found, continuing without verification"); + want_verify = 0; /* AUTO fallback */ + } - /* Disable peer verification for the test programs. Strict verification - needs ENVARC:AmiSSL/cacert.pem installed; relaxing lets the tests run - on a barebones setup without silently masking real TLS errors -- any - real protocol failure still shows up in SSL_connect below. */ - SSL_CTX_set_verify(st->ssl_ctx, SSL_VERIFY_NONE, NULL); + SSL_CTX_set_verify(st->ssl_ctx, + want_verify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, + NULL); st->ssl = SSL_new(st->ssl_ctx); - if (!st->ssl) { THL_Log("https: SSL_new failed"); return 0; } + if (!st->ssl) { HVN_Log("https: SSL_new failed"); return 0; } SSL_set_fd(st->ssl, (int)st->socket); SSL_set_tlsext_host_name(st->ssl, host); + /* Hostname check: the chain-level SSL_VERIFY_PEER only validates the + certificate chain, not that the cert matches the host we asked for. + Enable RFC 6125 hostname matching so a valid cert for example.com + can't be reused against example.org. */ + if (want_verify) + { + SSL_set_hostflags(st->ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + if (SSL_set1_host(st->ssl, host) != 1) + { + HVN_Log("https: SSL_set1_host failed"); + return 0; + } + } + int h = SSL_connect(st->ssl); if (h <= 0) { - int err = SSL_get_error(st->ssl, h); - snprintf(logbuf, sizeof(logbuf), "https: SSL_connect failed rc=%d err=%d", h, err); - THL_Log(logbuf); + int err = SSL_get_error(st->ssl, h); + long verr = SSL_get_verify_result(st->ssl); + if (verr != X509_V_OK) + snprintf(logbuf, sizeof(logbuf), + "https: SSL_connect rc=%d err=%d verify=%ld (%s)", + h, err, verr, X509_verify_cert_error_string(verr)); + else + snprintf(logbuf, sizeof(logbuf), + "https: SSL_connect failed rc=%d err=%d", h, err); + HVN_Log(logbuf); return 0; } snprintf(logbuf, sizeof(logbuf), "https: handshake OK cipher=%s", SSL_get_cipher(st->ssl)); - THL_Log(logbuf); + HVN_Log(logbuf); return 1; } #endif /* HAVE_AMISSL */ /* --- per-hop connect (TCP + optional TLS). Does NOT send the request. - */ -static int THL_Connect(struct THL_State *st, const char *host, ULONG port, int use_tls) +static int HVN_Connect(struct HVN_State *st, const char *host, ULONG port, int use_tls) { char logbuf[256]; st->use_tls = use_tls; @@ -422,7 +694,7 @@ static int THL_Connect(struct THL_State *st, const char *host, ULONG port, int u #ifndef HAVE_AMISSL if (use_tls) { - THL_Log("https: no AmiSSL support compiled in"); + HVN_Log("https: no AmiSSL support compiled in"); return 0; } #endif @@ -435,12 +707,12 @@ static int THL_Connect(struct THL_State *st, const char *host, ULONG port, int u if (!SocketBase || !ISocket) { if (SocketBase && !ISocket) CloseLibrary(SocketBase); - THL_Log("http: OpenLibrary bsdsocket.library failed"); + HVN_Log("http: OpenLibrary bsdsocket.library failed"); return 0; } st->SBase = SocketBase; st->SIFace = ISocket; #else - if (!SocketBase) { THL_Log("http: OpenLibrary bsdsocket.library failed"); return 0; } + if (!SocketBase) { HVN_Log("http: OpenLibrary bsdsocket.library failed"); return 0; } st->SBase = SocketBase; #endif @@ -453,12 +725,12 @@ static int THL_Connect(struct THL_State *st, const char *host, ULONG port, int u if (!he) { snprintf(logbuf, sizeof(logbuf), "http: gethostbyname(%s) failed", host); - THL_Log(logbuf); + HVN_Log(logbuf); return 0; } st->socket = socket(AF_INET, SOCK_STREAM, 0); - if (st->socket < 0) { THL_Log("http: socket() failed"); return 0; } + if (st->socket < 0) { HVN_Log("http: socket() failed"); return 0; } struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); @@ -468,14 +740,14 @@ static int THL_Connect(struct THL_State *st, const char *host, ULONG port, int u if (connect(st->socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) { - THL_Log("http: connect() failed"); + HVN_Log("http: connect() failed"); return 0; } #ifdef HAVE_AMISSL if (use_tls) { - if (!THL_TlsWrap(st, host)) return 0; + if (!HVN_TlsWrap(st, host)) return 0; } #endif return 1; @@ -485,7 +757,7 @@ static int THL_Connect(struct THL_State *st, const char *host, ULONG port, int u Returns: 1 = headers OK, body follows 0 = error / non-200 -1 = redirect; next URL copied into redirect_out (size plen). */ -static int THL_DoRequest(struct THL_State *st, const char *host, +static int HVN_DoRequest(struct HVN_State *st, const char *host, const char *path, char *redirect_out, ULONG rlen) { char logbuf[320]; @@ -494,15 +766,15 @@ static int THL_DoRequest(struct THL_State *st, const char *host, int rlen_s = snprintf(request, sizeof(request), "GET %s HTTP/1.1\r\n" "Host: %s\r\n" - "User-Agent: HTMLview-TestHook/1.2\r\n" + "User-Agent: HTMLview-Net/1.0\r\n" "Accept: */*\r\n" "Connection: close\r\n" "\r\n", path, host); - if (THL_Send(st, request, rlen_s) < 0) + if (HVN_Send(st, request, rlen_s) < 0) { - THL_Log("http: send() failed"); + HVN_Log("http: send() failed"); return 0; } @@ -513,7 +785,7 @@ static int THL_DoRequest(struct THL_State *st, const char *host, char *hdrend = NULL; while (have < 16384 - 1) { - LONG got = THL_Recv(st, (APTR)(hdr + have), 16384 - 1 - have); + LONG got = HVN_Recv(st, (APTR)(hdr + have), 16384 - 1 - have); if (got <= 0) break; have += got; hdr[have] = 0; @@ -523,7 +795,7 @@ static int THL_DoRequest(struct THL_State *st, const char *host, if (!hdrend) { - THL_Log("http: no header terminator in reply"); + HVN_Log("http: no header terminator in reply"); free(hdr); return 0; } @@ -544,7 +816,7 @@ static int THL_DoRequest(struct THL_State *st, const char *host, } status[n] = 0; snprintf(logbuf, sizeof(logbuf), "http: status=%s", status); - THL_Log(logbuf); + HVN_Log(logbuf); int code = 0; const char *sp = strchr(status, ' '); @@ -576,12 +848,20 @@ static int THL_DoRequest(struct THL_State *st, const char *host, GRAB_HDR("Content-Type:", "content-type:", v_type, sizeof(v_type)); GRAB_HDR("Content-Length:", "content-length:", v_len, sizeof(v_len)); GRAB_HDR("Content-Encoding:","content-encoding:", v_enc, sizeof(v_enc)); - if (v_loc[0]) { snprintf(logbuf, sizeof(logbuf), "http: location=%s", v_loc); THL_Log(logbuf); } - if (v_type[0]) { snprintf(logbuf, sizeof(logbuf), "http: content-type=%s", v_type); THL_Log(logbuf); } - if (v_len[0]) { snprintf(logbuf, sizeof(logbuf), "http: content-length=%s", v_len); THL_Log(logbuf); } - if (v_enc[0]) { snprintf(logbuf, sizeof(logbuf), "http: content-encoding=%s", v_enc); THL_Log(logbuf); } + if (v_loc[0]) { snprintf(logbuf, sizeof(logbuf), "http: location=%s", v_loc); HVN_Log(logbuf); } + if (v_type[0]) { snprintf(logbuf, sizeof(logbuf), "http: content-type=%s", v_type); HVN_Log(logbuf); } + if (v_len[0]) { snprintf(logbuf, sizeof(logbuf), "http: content-length=%s", v_len); HVN_Log(logbuf); } + if (v_enc[0]) { snprintf(logbuf, sizeof(logbuf), "http: content-encoding=%s", v_enc); HVN_Log(logbuf); } #undef GRAB_HDR + /* Stash Content-Length for the cache write path; 0 means "unknown" + and makes the response non-cacheable. */ + { + ULONG v = 0; + for (const char *p = v_len; *p >= '0' && *p <= '9'; p++) v = v*10 + (*p - '0'); + st->content_length = v; + } + /* 3xx + Location => signal caller to reconnect. We don't handle relative redirects yet; the request will fail on next parse if Location isn't a full URL. */ @@ -597,7 +877,7 @@ static int THL_DoRequest(struct THL_State *st, const char *host, if (code != 200) { snprintf(logbuf, sizeof(logbuf), "http: aborting (status=%d)", code); - THL_Log(logbuf); + HVN_Log(logbuf); free(hdr); return 0; } @@ -620,17 +900,17 @@ static int THL_DoRequest(struct THL_State *st, const char *host, snprintf(logbuf, sizeof(logbuf), "http: OK header_len=%lu body_have=%lu chunked=%d tls=%d", header_len, body_have, st->chunked, st->use_tls); - THL_Log(logbuf); + HVN_Log(logbuf); if (body_have > 0) { UBYTE *bp = hdr + header_len; ULONG show = body_have < 16 ? body_have : 16; char hex[96]; - int off = sprintf(hex, "http: body[0..%lu]=", show); - for (ULONG i = 0; i < show; i++) - off += sprintf(hex + off, "%02x ", bp[i]); - THL_Log(hex); + int off = snprintf(hex, sizeof(hex), "http: body[0..%lu]=", show); + for (ULONG i = 0; i < show && off < (int)sizeof(hex) - 4; i++) + off += snprintf(hex + off, sizeof(hex) - off, "%02x ", bp[i]); + HVN_Log(hex); } free(hdr); @@ -639,9 +919,22 @@ static int THL_DoRequest(struct THL_State *st, const char *host, /* Entry point: opens a connection and reads headers, following up to 5 redirects across http / https. Returns 1 on success, 0 on any error. */ -static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) +static LONG HVN_HttpOpen(CONST_STRPTR url, struct HVN_State *st) { char logbuf[384]; + + /* Cache read path: if a fresh entry is on disk, serve it without + touching the network. We store the BPTR in st->file; the existing + Read handler picks it up transparently. */ + { + BPTR cached = HVN_CacheTryOpen((const char *)url); + if (cached) + { + st->file = cached; + return 1; + } + } + char current[1024]; strncpy(current, url, sizeof(current) - 1); current[sizeof(current) - 1] = 0; @@ -651,44 +944,97 @@ static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) char host[256], path[1024]; ULONG port; int is_https; - if (!THL_ParseUrl(current, host, sizeof(host), + if (!HVN_ParseUrl(current, host, sizeof(host), path, sizeof(path), &port, &is_https)) { snprintf(logbuf, sizeof(logbuf), "http: unparseable URL '%s'", current); - THL_Log(logbuf); + HVN_Log(logbuf); return 0; } snprintf(logbuf, sizeof(logbuf), "http: [hop %d] %s://%s:%lu%s", hop, is_https ? "https" : "http", host, port, path); - THL_Log(logbuf); + HVN_Log(logbuf); - if (!THL_Connect(st, host, port, is_https)) + if (!HVN_Connect(st, host, port, is_https)) { - THL_Disconnect(st); + HVN_Disconnect(st); return 0; } char next[1024]; - int r = THL_DoRequest(st, host, path, next, sizeof(next)); - if (r == 1) return 1; - if (r == 0) { THL_Disconnect(st); return 0; } + int r = HVN_DoRequest(st, host, path, next, sizeof(next)); + if (r == 1) + { + /* Cache write path: drain the whole response into st->buffer + if it's within budget, mirror to disk, then close the + socket so Read() replays from memory. Skipped when the + response is chunked or has no Content-Length (we can't + bound memory without Content-Length, and extending this to + chunked streams is a follow-up). */ + if (HVN_CacheDir[0] && HVN_CacheTTL > 0 && !st->chunked && + st->content_length > 0 && st->content_length <= HVN_CACHE_MAX_BYTES) + { + ULONG total = st->content_length; + UBYTE *full = (UBYTE *)malloc(total); + if (full) + { + ULONG have = 0; + if (st->buffer && st->buflen) + { + ULONG copy = st->buflen - st->bufpos; + if (copy > total) copy = total; + memcpy(full, st->buffer + st->bufpos, copy); + have = copy; + free(st->buffer); + st->buffer = NULL; st->buflen = st->bufpos = 0; + } + while (have < total) + { + LONG got = HVN_Recv(st, full + have, (LONG)(total - have)); + if (got <= 0) break; + have += (ULONG)got; + } + if (have == total) + { + HVN_CacheStore(current, full, total, HVN_CacheTTL); + /* Disconnect first -- it frees st->buffer (which + we already freed and NULLed above) and clears + the socket. Then install `full` as the replay + buffer so Read() serves from memory. */ + HVN_Disconnect(st); + st->buffer = full; + st->buflen = total; + st->bufpos = 0; + } + else + { + HVN_Log("cache: incomplete drain, skipping store"); + st->buffer = full; + st->buflen = have; + st->bufpos = 0; + } + } + } + return 1; + } + if (r == 0) { HVN_Disconnect(st); return 0; } /* 3xx -- tear down and try the new URL. */ - THL_Disconnect(st); + HVN_Disconnect(st); strncpy(current, next, sizeof(current) - 1); current[sizeof(current) - 1] = 0; } - THL_Log("http: too many redirects"); - THL_Disconnect(st); + HVN_Log("http: too many redirects"); + HVN_Disconnect(st); return 0; } /* --- the hook function ------------------------------------------------- */ -static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, - struct HTMLview_LoadMsg *msg) +ULONG HTMLviewNet_HookFunc(struct Hook *hook, APTR obj, + struct HTMLview_LoadMsg *msg) { (void)hook; (void)obj; @@ -701,7 +1047,7 @@ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, CONST_STRPTR url = msg->lm_Params.lm_Open.URL; if (!url) return 0; - struct THL_State *st = (struct THL_State *)calloc(1, sizeof(*st)); + struct HVN_State *st = (struct HVN_State *)calloc(1, sizeof(*st)); if (!st) return 0; st->socket = -1; msg->lm_Userdata = st; @@ -709,7 +1055,7 @@ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, if (strncmp(url, "http://", 7) == 0 || strncmp(url, "https://", 8) == 0) { - if (!THL_HttpOpen(url, st)) + if (!HVN_HttpOpen(url, st)) { free(st); msg->lm_Userdata = NULL; return 0; } @@ -727,7 +1073,7 @@ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, case HTMLview_Read: { - struct THL_State *st = (struct THL_State *)msg->lm_Userdata; + struct HVN_State *st = (struct HVN_State *)msg->lm_Userdata; if (!st) return 0; STRPTR out = msg->lm_Params.lm_Read.Buffer; @@ -758,15 +1104,15 @@ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, if (st->socket >= 0) { LONG rd = st->chunked - ? THL_ReadChunked(st, (UBYTE *)out, len) - : THL_Recv(st, out, len); + ? HVN_ReadChunked(st, (UBYTE *)out, len) + : HVN_Recv(st, out, len); { char logbuf[96]; snprintf(logbuf, sizeof(logbuf), "read: recv(sock=%ld want=%ld) => %ld%s%s", st->socket, len, rd, st->chunked ? " (chunked)" : "", st->use_tls ? " (tls)" : ""); - THL_Log(logbuf); + HVN_Log(logbuf); } return rd > 0 ? rd : 0; } @@ -779,11 +1125,11 @@ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, case HTMLview_Close: { - struct THL_State *st = (struct THL_State *)msg->lm_Userdata; + struct HVN_State *st = (struct HVN_State *)msg->lm_Userdata; if (st) { if (st->file) Close(st->file); - THL_Disconnect(st); + HVN_Disconnect(st); free(st); msg->lm_Userdata = NULL; } @@ -793,4 +1139,23 @@ static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, return 0; } -#endif /* HTMLVIEW_TEST_IMAGE_HOOK_H */ +/* --- public wiring helper --------------------------------------------- */ + +void HTMLviewNet_InitHook(struct Hook *hook) +{ + if (!hook) return; + + /* CallHookPkt on m68k and MorphOS passes args in registers (a0=hook, + a2=obj, a1=msg); our plain-C hook function expects stack args. + HookEntry is the amiga.lib trampoline that does the register->stack + conversion. On OS4 hooks are already called with stack args, and + HookEntry is not declared, so we assign directly. */ +#if defined(__amigaos4__) + hook->h_Entry = (HOOKFUNC)HTMLviewNet_HookFunc; + hook->h_SubEntry = NULL; +#else + hook->h_Entry = (HOOKFUNC)HookEntry; + hook->h_SubEntry = (HOOKFUNC)HTMLviewNet_HookFunc; +#endif + hook->h_Data = NULL; +} diff --git a/mcc/net_hook/htmlview_nethook.h b/mcc/net_hook/htmlview_nethook.h new file mode 100644 index 0000000..24766b3 --- /dev/null +++ b/mcc/net_hook/htmlview_nethook.h @@ -0,0 +1,117 @@ +/*************************************************************************** + + HTMLview.mcc - HTMLview MUI Custom Class + Copyright (C) 1997-2000 Allan Odgaard + Copyright (C) 2005-2026 by HTMLview.mcc Open Source Team + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + +***************************************************************************/ + +/* + * libhtmlview_nethook -- reference HTMLview.mcc image/content load hook. + * + * Resolves these URL schemes: + * - PROGDIR:, DH0:, etc. -- direct AmigaDOS Open() + * - file:// -- stripped then Open() + * - http://host[:port]/p -- bsdsocket.library, chunked-aware, follows 3xx + * - https://host[:port]/p -- AmiSSL-wrapped (when compiled with -DHAVE_AMISSL) + * + * Usage (consumer side): + * + * struct Hook net_hook; + * HTMLviewNet_InitHook(&net_hook); + * ... + * HTMLviewObject, + * MUIA_HTMLview_LoadHook, &net_hook, + * MUIA_HTMLview_ImageLoadHook, &net_hook, + * ... + * + * The hook opens its own task-local bsdsocket / amissl bases; the host + * program does NOT need to OpenLibrary("bsdsocket.library") itself. + * + * Up to 5 consecutive 3xx redirects are followed (absolute URLs only). + */ + +#ifndef HTMLVIEW_NETHOOK_H +#define HTMLVIEW_NETHOOK_H + +#include +#include + +struct HTMLview_LoadMsg; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Hook entry point. Normally consumers invoke this indirectly by passing + * an initialised struct Hook to HTMLview.mcc; this prototype is exported + * so HTMLviewNet_InitHook() can wire it into h_Entry/h_SubEntry. + */ +ULONG HTMLviewNet_HookFunc(struct Hook *hook, APTR obj, + struct HTMLview_LoadMsg *msg); + +/* + * Populate `hook` with the right h_Entry / h_SubEntry for the current + * platform. Safe to call multiple times; idempotent. The hook keeps no + * per-instance state of its own, so one Hook can serve many HTMLview + * objects concurrently. + */ +void HTMLviewNet_InitHook(struct Hook *hook); + +/* --- TLS verification (HAVE_AMISSL builds) ---------------------------- */ + +/* + * Verification modes for HTTPS fetches. Default is AUTO: verification is + * enabled if a CA bundle is discovered, otherwise disabled with a single + * warning line in T:htmlview_hook.log. The verify decision is taken per + * request, so installing the bundle later "just works". + */ +#define HTMLVIEWNET_VERIFY_AUTO 0 /* peer-verify if bundle present */ +#define HTMLVIEWNET_VERIFY_NONE 1 /* never verify (pre-Phase-2 behaviour) */ +#define HTMLVIEWNET_VERIFY_PEER 2 /* always verify; fail if no bundle */ + +/* + * Override the CA-bundle path. Pass NULL to clear and restore auto- + * discovery. Path is copied into an internal buffer; the caller owns its + * storage. Typical use: host app knows where it ships certificates and + * wants to point the hook at that location directly. + */ +void HTMLviewNet_SetCABundle(const char *path); + +/* + * Override verify mode. Default HTMLVIEWNET_VERIFY_AUTO. + */ +void HTMLviewNet_SetVerifyMode(int mode); + +/* --- On-disk response cache ------------------------------------------ */ + +/* + * Enable / disable a simple on-disk response cache. Pass a directory path + * to enable (the directory must exist; the hook will not create it); pass + * NULL or an empty string to disable. Disabled by default so the hook's + * behaviour without opt-in matches pre-cache versions. + * + * Cache keys are derived from the URL; entries live as `.body` and + * `.meta` file pairs. There is no eviction policy in this version -- + * stale entries accumulate until the user cleans the directory. + */ +void HTMLviewNet_SetCacheDir(const char *path); + +/* + * Set the fallback TTL in seconds used when a response carries no + * Cache-Control: max-age / Expires hint. Default 3600 (1 hour). Pass 0 to + * make unhinted responses non-cacheable. + */ +void HTMLviewNet_SetCacheTTL(ULONG seconds); + +#ifdef __cplusplus +} +#endif + +#endif /* HTMLVIEW_NETHOOK_H */ From 9f8685797080efbfb5ea16174aabec2c3c4bb4a1 Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Mon, 20 Apr 2026 19:51:20 +0200 Subject: [PATCH 24/32] ci: verify build products and fail loud on missing artifacts The 13.6 release failed at the `cp` step because the preceding build silently produced no `.mcc` binary (Makefile default-goal bug, fixed in the companion commit). `upload-artifact`'s default `warn` mode made the empty slot invisible until release-packaging time. - Add `Verify build products` steps that `ls` each expected binary (`HTMLview.mcc`, `HTMLview.mcp`, `SimpleTest`, `LibLoad_Test`) so a missing product fails the build job itself. - Set `if-no-files-found: error` on every `upload-artifact` so any future glob-match regression also fails at upload, not at release. --- .github/workflows/makefile.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 13294f9..85fa433 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -25,10 +25,18 @@ jobs: uses: docker://sacredbanana/amiga-compiler:m68k-amigaos with: args: sh -c "cd mcp && make clean && make OS=os3" + - name: Verify build products + run: | + set -e + ls mcc/bin_os3/HTMLview.mcc + ls mcc/bin_os3/SimpleTest + ls mcc/bin_os3/LibLoad_Test + ls mcp/bin_os3/HTMLview.mcp - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: htmlview-os3-${{ github.sha }} + if-no-files-found: error path: | mcc/bin_os3/*.mcc mcc/bin_os3/SimpleTest @@ -47,10 +55,18 @@ jobs: uses: docker://sacredbanana/amiga-compiler:ppc-amigaos with: args: sh -c "cd mcp && make clean && make OS=os4" + - name: Verify build products + run: | + set -e + ls mcc/bin_os4/HTMLview.mcc + ls mcc/bin_os4/SimpleTest + ls mcc/bin_os4/LibLoad_Test + ls mcp/bin_os4/HTMLview.mcp - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: htmlview-os4-${{ github.sha }} + if-no-files-found: error path: | mcc/bin_os4/*.mcc mcc/bin_os4/SimpleTest @@ -69,10 +85,18 @@ jobs: uses: docker://sacredbanana/amiga-compiler:ppc-morphos with: args: sh -c "cd mcp && make clean && make OS=mos" + - name: Verify build products + run: | + set -e + ls mcc/bin_mos/HTMLview.mcc + ls mcc/bin_mos/SimpleTest + ls mcc/bin_mos/LibLoad_Test + ls mcp/bin_mos/HTMLview.mcp - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: htmlview-morphos-${{ github.sha }} + if-no-files-found: error path: | mcc/bin_mos/*.mcc mcc/bin_mos/SimpleTest From 0912313a08d249e8e5f1794a8f77e5e51942d58b Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Mon, 20 Apr 2026 19:51:33 +0200 Subject: [PATCH 25/32] docs: modernise README/TODO/.readme + add IMPROVEMENTS roadmap - README: drop SourceForge + SVN references, point at the GitHub repo, document the Docker cross-build flow for OS3 / OS4 / MorphOS, describe linking `libhtmlview_nethook.a` with its optional runtime knobs. - TODO: replace the placeholder `* ????` line with a short list of the pending buckets, pointing readers at IMPROVEMENTS.md for details. - doc/MCC_HTMLview.readme: bump to 13.6, drop the "prototype" and v13- snapshot text, swap the SourceForge URL for GitHub, and cross- reference each long-standing limitation to its IMPROVEMENTS phase. - IMPROVEMENTS.md: multi-session roadmap covering the whole backlog (phases 1-13). Phases 1, 2, 3, and 13 are marked delivered. --- IMPROVEMENTS.md | 284 ++++++++++++++++++++++++++++++++++++++++ README | 111 ++++++++++------ TODO | 56 ++++---- doc/MCC_HTMLview.readme | 150 +++++++++------------ 4 files changed, 452 insertions(+), 149 deletions(-) create mode 100644 IMPROVEMENTS.md diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md new file mode 100644 index 0000000..b14b032 --- /dev/null +++ b/IMPROVEMENTS.md @@ -0,0 +1,284 @@ +# HTMLview.mcc Improvement Plan + +Multi-session plan. Each phase is independently mergeable. Work top-down +unless a later phase is explicitly marked as dependency-free. + +**Status legend:** `[ ]` not started · `[~]` in progress · `[x]` done + +--- + +## Phase 1 — Extract net hook into reusable static library `[x]` + +**Goal.** Every consumer currently has to copy the ~600-line hook out of +`mcc/test_image_hook.h`. Move it into a proper library so bug fixes +propagate and later phases (TLS verify, cache, gzip, cookies) land in one +place. + +**Touches.** +- New: `mcc/net_hook/htmlview_nethook.h` — public API. +- New: `mcc/net_hook/htmlview_nethook.c` — implementation (code moved + from `test_image_hook.h`, no behavioural change). +- `mcc/Makefile` — add `libhtmlview_nethook.a` per-OS; link test programs + against it. +- `mcc/SimpleTest.c`, `mcc/LibLoad_Test.c` — drop `#include + "test_image_hook.h"`, call the new API. +- Delete `mcc/test_image_hook.h`. + +**Public API (initial).** +```c +ULONG HTMLviewNet_HookFunc(struct Hook *hook, APTR obj, + struct HTMLview_LoadMsg *msg); +void HTMLviewNet_InitHook(struct Hook *hook); /* sets h_Entry per-platform */ +``` + +**Acceptance.** +- OS3, OS4, MorphOS build clean via the Docker images in `MEMORY.md`. +- SimpleTest + LibLoad_Test still load local images, plain HTTP, HTTPS on + OS3/OS4 (MorphOS: HTTP only, unchanged). +- `libhtmlview_nethook.a` present in `bin_/` after a build. + +**Notes for the next session.** +- The hook already uses task-local bsdsocket bases; the host task never + needs to open bsdsocket. Drop those `OpenLibrary("bsdsocket.library")` + calls from both test programs. +- `HAVE_AMISSL` should be a per-object compile flag on the lib's `.o`, not + on every caller. Keep `AMISSL_SDK_READY` depending on the lib. + +--- + +## Phase 2 — Certificate verification `[x]` + +**Depends on:** Phase 1. + +**Goal.** Current TLS uses `SSL_VERIFY_NONE`. Anyone on the network can +MITM. Wire a CA bundle and flip to `VERIFY_PEER`. + +**Delivered.** +- `HTMLviewNet_SetCABundle(path)` — NULL restores auto-discovery. +- `HTMLviewNet_SetVerifyMode(mode)` — `AUTO` (default), `NONE`, `PEER`. +- AUTO discovers from: user override → `AmiSSL:Certs/curl-ca-bundle.crt` + → `ENVARC:AmiSSL/Certs/...` → `ENV:AmiSSL/Certs/...` + → `SYS:Storage/AmiSSL/Certs/...`. +- When verifying: `SSL_CTX_load_verify_locations` + `SSL_VERIFY_PEER` + and hostname check via `SSL_set1_host` (RFC 6125). +- Failure diagnostics log `X509_verify_cert_error_string()` so the user + can tell a cert issue from a handshake issue. +- Setters compile in non-AmiSSL builds too (MorphOS) to keep + cross-platform callers link-clean. + +**Notes for next session.** +- Cert store is re-loaded per-request (each transfer builds its own + SSL_CTX). Cheap enough for now; revisit if latency matters. +- `T:htmlview_hook.log` carries the verify decision ("CA bundle loaded + from …" or "no CA bundle found, continuing without verification"). + +--- + +## Phase 3 — On-disk image/page cache `[x]` (v1) + +**Depends on:** Phase 1. + +**Goal.** The hook refetches every image on every reload. On Amiga +networking, this is painful; a tiny keyed cache gives huge wins. + +**Delivered (v1).** +- `HTMLviewNet_SetCacheDir(path)` / `HTMLviewNet_SetCacheTTL(secs)`. +- Disabled by default; `SetCacheDir(NULL)` clears. +- Key = 16-char hex FNV-1a-64 of URL; entries stored as `.body` + + `.meta` file pairs in the configured directory. +- Meta format: `URL=\nExpires=\n`. +- Read path: if body+meta exist and `now < Expires`, serve the body file + directly (opened as `st->file`) with no network I/O. +- Write path: on 200 with Content-Length <= 8 MB and not chunked, drain + the whole body, persist body+meta with TTL = `HVN_CacheTTL`. +- Log entries: `cache: HIT`, `cache: STORE`, size-skip reasons. + +**Deferred to v2.** +- `ETag` / `Last-Modified` capture + `If-None-Match`/`If-Modified-Since` + revalidation (serve 304 cheaply). +- `Cache-Control: max-age=` + `Expires` server hints. +- Chunked-transfer caching. +- Eviction policy (cache dir currently grows unbounded). + +**Acceptance.** `SetCacheDir("T:htmlcache/")`, reload a page, look for +`cache: HIT` in `T:htmlview_hook.log` on the second fetch. + +--- + +## Phase 4 — UTF-8 / charset handling `[ ]` + +**Goal.** Pages with `` or HTTP `Content-Type: +text/html; charset=utf-8` currently render mojibake. Minimum viable: +detect charset, transliterate UTF-8 → Latin-1 (replacing unrepresentable +codepoints with `?` or the nearest entity). + +**Touches.** +- `mcc/Entities.cpp`, `mcc/ParseMessage.cpp` most likely. +- Possibly new `mcc/Charset.cpp` for the transliteration table. +- Hook contract gains: net hook passes the `Content-Type` charset hint + into the parser (either via `HTMLview_LoadMsg` extension or side-channel + tag — needs a small design call). + +**Acceptance.** A UTF-8 page with `© é ñ` renders them as the Latin-1 +byte equivalents, not as two-byte sequences. + +--- + +## Phase 5 — CSS subset `[ ]` + +**Goal.** Stop dropping `style=` on the floor. Scope: inline + `