I've been experimenting with options for writing a DOS installer builder off and on over the last little while (to complete my list of open-source installer builders for every era and use case) and, today, I got carried away.
I'd started poking at Free Pascal but UPX doesn't like the FPC 3.x DPMI output, so I was at a little over 300KiB for the stub before I got any further than using the Crt and unzipper units to write a "draw tiled background" procedure that could print a parody of the Epic Pinball installer's background and then dump a proof-of-concept listing of the appended Zip file when I hit Enter.
The attachment screenshot_fpc32.png is no longer available
Free Pascal's i8086 target doesn't have support for decompression in the standard library and it's still 39KiB after UPX if I rip out the dependencies on the unzipping library to get it building, so it's just printing the background ...so I started considering a second shot at making sense of how to incorporate Info-ZIP's self-extractor stub into a C-based solution. (Almost all my experience with C and C++ is theoretical, so I don't have much practice reading other people's code.)
Despite my dislike of using something with such primitive compile-time checks, my curiosity got the better of me, so I decided yesterday to see how Open Watcom C/C++ would do.
(If nothing else, maybe I could follow the lead of the Microsoft Setup Toolkit for Windows 3.1 and make version 1.0 useful as an installer wizard which is space-efficient and saves the developer from having to hack something together, but doesn't do single-file installers.)
The start was mixed, with graph.h adding something like 30KiB to the base executable size, but things have been getting very promising since then. I wandered my way through the manuals and -help output to see how many optimizations I could turn on (and switched to building .com rather than .exe binaries, to shave off the overhead of the MZ file format). (At the time, I didn't think Open Watcom supported dead code elimination though I'd later discover that it's something you have to pass through to the linker that's not mentioned in the documentation for the higher-level compile driver.)
I decided to try something I'd never done before and wrote some FFI shims for the BIOS's int 10h calls instead. That got me down to about 8KiB after UPX and, as a bonus, made it easier to do text-mode color fills.
Not long after, I remembered from /r/rust/ that things in print! such as float formatting tend to be inherently heavy, so I tried puts() instead of printf(). That dropped a it down to 3KiB, but it still felt too heavy for what was essentially a Hello World, centered on the screen, with a few bytes of assembly opcodes to set some non-standard colors.
The attachment screenshot_ow16.png is no longer available
...so I decided to try to write a wrapper for the int 10h/ah=13h (Draw text at cursor without moving it) call. Cobbling together enough understanding of x86 assembly to pass a string to it was... involved. (Thank goodness that, when I did encounter a bug in the example code I'd built on, which was triggered by building a .exe rather than a .com, I remembered enough of my near-uselessly random hobby reading on the topic from years ago to realize the problem was that the code assumed CS=SS.)
That said, it was worth it. At the moment, I'm sitting on a version of that "Hello World!" in the screenshot which takes up 1212 bytes (1364 bytes if I ask for a .exe instead, and UPX refuses to pack either because the stub is bigger than the savings from compression) and, according to my experiments, 996 bytes of that is the fixed overhead of the Open Watcom C runtime library bootstrap (ie. the stuff which eventually calls main()). (I'm thinking that, at some point, I'll go poking around further in the Open Watcom docs to check whether there are any other options I could use to request a build of the standard library optimized for size.)
In the mean time, I also used Doxygen for the first time. (I can get pretty obsessive about polishing up my API docs.)
The attachment screenshot_apidocs.png is no longer available
The typedefs are both to make the function signatures in the documentation nicer and so that splint -strict can complain if I pass a row where a column is expected or get my integer arguments mixed up in some other way..
Now, I've put in a long obsessive-compulsive Saturday, so I think I'm going to go to sleep.
(I have also been brainstorming on the best way to implement a project definition/control scripting for such an installer, and I've made quite a bit of progress on the idea, but, unless someone is curious, I'll wait until I have an implementation to show off.)