This page documents a lot of my own findings and tricks I've discovered myself.
All sorted in divided sections and with this nifty list for a quick jump.
Don't forget to make any backups where applicable.
Grab all the text of any standard message box:
You can easily copy the contents of any standard message box into the clipboard by pressing Ctrl+C. Win2000 is the first version to support this feature.
Type a Invisible character:
The Numpad code ALT+0160 should produce a invisible character that will not be treated as whitespace.
How to install/update HTML Help (hh.exe) without updating IE:
My 486 with Win95b still has only IE3 installed and this trick worked for any .CHM files I've tried so far.
The HTML is rendered with the IE3 engine, so things are very likely to look wrong.
You may want to change the default background color from grey to white in the Internet properties.
Customizing hardcoded Toolbar buttons in the (IE5) enhanced Win9x Explorer:
The file version was 4.72.3812.634, I don't know if any of that applies to other versions.
Hacking (IE5) enhanced Win9x Explorer menus and keyboard shortcuts (Accelerators):
These are contained in Browselc.dll, you can use resource hacker to change these.
Menu 263 is used for Win98 explorer, 266 for Win95 (I guess) and 267 is used for IE.
Accelerator 256 is applied to both Windows and Internet Explorer.
Putting (IE5) enhanced (Win9x) Explorer Links Toolbar to good use:
There's no rule about that everything in there must be a URL file pointing to an Internet page.
You can put shortcuts to other applications or even AHK scripts in there.
Also you can change the name of that by altering REG_SZ value LinksFolderName in:
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Toolbar
(for blank name use ALT+0160)
However it also changes the path of the folder inside the favorites directory, so give it the same name.
Also to hide it from the favorites menu: Tick the "Hidden" checkbox in the properties of the directory.
Better MIDI sound on Windows NT4SP6 and onwards:
Use *VST MIDI Driver from Falcomod* with special DLLs that follow the VSTi specification.
Then get some nice and old VSTi like the *Yamaha S-YXG50* or Yamaha S-YXG2006LE (SGP2.DLL).
It is possible to obtain SGP2.DLL from MidRadio Player V7.2, extract the installer with WinRAR (or UniExtract) and then you just need to patch the one byte 0x74 at 0x2AAE to 0xEB.
Also beware that it might need a additional GM reset to sound correctly (or else percussion is missing).
Of course there are more different VSTi to choose from like OPL/OPN emulators for that oldschool FM Synthesizer output that sounds like a real classic SoundBlaster/Adlib card or Sega Megadrive/Genesis.
The official *Roland SoundCanvas VA* is also a neat option that requires XP or newer but comes with an high price tag attached and questionable "cloud activation", also it got discontinued in 2024.
It isn't that bad but two DLLs with the combined size of 41MB is quite heavy and takes long time to load, it also tends to crash if some songs send a 'Program Change' command before every single note.
Hopefully someone converts *Nuked-SC55* to a lightweight VSTi DLL that loads much quicker.
(That hopefully isn't linked to >Win10 function calls by a modern crap compiler runtime).
Workaround for special file extensions and 'nameless' files:
The Windows Explorer has limitations on (re)naming files
.LNK;.PIF;.URL;.SCF always have their extension hidden. (Which is good for aesthetic reasons)
It is also possible to have only a extension but no proper name like just ".txt".
The filesystem allows that but the Windows Explorer refuses to name a file like that.
Use the command prompt to work around these limits:
If you want to rename a shortcut (.lnk file) named "Application", then you type in the prompt:
REN Application.lnk Application.bin
And the same with the other idea:
REN RemoveMyName.txt .txt
My remarks about using OpenCBM with PCI LPT cards:
OpenCBM is perfectly capable accessing the address ranges in the PCI space and can even get the correct port addresses from the Config Manager.
The fact is that the driver refuses to start at all if there's no PARPORT.sys loaded and running, which always the case if Windows has installed the drivers for motherboard or ISA card ports.
Drivers for those PCI cards don't use PARPORT.sys but have their own files with different names that do the pretty much the same thing. (My cards use NmPar.sys)
A workaround over this flaw is to manually add a LPT port within the ISA space (like the good old 0x378).
EVEN IF YOUR SYSTEM DOESN'T HAVE AN ACTUAL LPT PORT THERE.
WINDOWS XP DID NOT COMPLAINED THAT NO LPT PORT WAS FOUND AT THAT ADDRESS!
I've added a fake LPT5 (yes I have four real ones installed) to my setup ages ago with the "add new hardware" wizard and since then OpenCBM perfectly works fine with my PCI-E card.
(And my XA1541 cable uses run of the mill 2N3904 transistors and not those silly BSV52 in their tiny SMD packages)
General programming tips for targeting old Windows versions:
The most useful reference for the WinAPI would be MSDN but the online version hosted by the modern M$ of today is very dishonest over the minimum supported version.
Old functions that existed since the mid-1980s are now claimed to require Windows 2000 now.
The best solution would be to locally install *MSDN Oct99*. This one at least is more correct about those requirements and top of that it is off-line, so no need for a bloated JS-heavy web browser to open it.
However you better watch out about mistakes and plain wrong keywords, it sure did happened enough that something like the bogus DISP_CHANGE_BADESC in ChangeDisplaySettingsEx() tripped me up.
Also for targeting Win16, you can check out WIN31WH.HLP in the *Windows3.1 SDK* or run a quick grep for the function on the Win16 include headers to search if it's referenced there.
The most important tips if you're programming GDI for Win9x or Win16:
USE AS FEW OBJECTS AS POSSIBLE!
(but keep the most used ones in memory as recreating new ones at high frequency could slow down the system)
ALWAYS STORE AND SELECT THE OLD OBJECT BACK!
(even if you think it doesn't matter as you delete a memory DC, non-NT systems are highly sensitive about that)
ALWAYS DELETE ALL THE CREATED OBJECTS AND MAKE (damn) SURE TO CHECK THAT!
It would be wise to do that on WM_DESTROY. (And it wouldn't hurt to 'delete' a NULL pointer also)
The easiest way to check for GDI leakage on Win3 is to look at the shutdown dialog. If it's missing the icon then you screwed up, also start and close your program a few times to make sure to catch it.
Global hooks in Windows have limitations that are not that well documented:
WH_MOUSE (not the low-level one) is not able to prevent deactivation of the active window should the user click a window that belongs to a different process/thread.
It is impossible to tell in which direction WM_MOUSEWHEEL scrolls (NT4/98 affected) nor which exact sidebutton was pressed/released in WM_XBUTTON messages (on 2k and newer).
However MSWHEEL.DLL (from IntelliPoint running on Win95/NT3.51) uses this hook to convert special WM_MBUTTONUP events that have an 0x4001 in the HIWORD in dwExtraInfo and the wheel movement in the LOWORD to a registered message "MSWHEEL_ROLLMSG" and post that to the active window.
WH_GETMESSAGE is about POSTed messages (it's to easy forget that) and won't catch all messages like:
Also, beware if a global hook uses OutputDebugString() then it might lock up DebugView or even worse clog up the message queue for the entire system which may force you to reset the computer.
(this race condition occurred for me mostly on my XP system, it could be multicore related and Win9x was NOT affected)
So it's better to think of an way to pass the debug string to your test/control application and do the actual OutputDebugString() from there.
Certain system applications like the command prompt in WinNT3-XP don't allow any hooking at all, but somehow it's okay and allowed in Win7 (or perhaps Vista) for some reason.
Win16-Win32 programming tips and pitfalls for early Windows versions:
My Tools configuration for CREdit for one-click compile/assemble:
Correct all paths starting with ??? to where ever the software is actually installed.
Tick all checkboxes except for "Prompt for arguments" and tick the "Active file only" option.
Initial directory for all is: $(FileDir)
The debug builds are useful for the Watcom Disassembler,
just run "wdis program.dbo -s=program.c" and the source code lines are (somewhat) above the corresponding machine code.
Assemble AVR:
Command: ???\Atmel Toolchain\AVR Assembler\Native\2.1.1117\avrassembler\avrasm2.exe
Arguments: $(FileName).$(FileExt) -fI -o $(FileName).hex -e $(FileName).eep -l $(FileName).lst
Assemble PIC (extra batch file needed, for more useful output):
Command: ???\Microchip\MPLABX\v3.65\mpasmx\mpasm.bat
Arguments: $(FileName)
Contents of mpasm.bat:
@echo off
???\Microchip\MPLABX\v3.65\mpasmx\mpasmx.exe /s- %1.asm
if ERRORLEVEL 1 goto listerrors
echo %1.asm assembled without errors.
exit
:listerrors
type %1.err
Compile for Win32-Char:
Command: ???\WATCOM\binnt\wcl386.exe
Arguments: /d0 $(FileName).$(FileExt) -i="???\WATCOM/h" -bt=NT -fm
Compile for Win32-Char (Debug):
Command: ???\WATCOM\binnt\wcl386.exe
Arguments: /d1 $(FileName).$(FileExt) -i="???\WATCOM/h" -bt=NT -fe=$(FileName)_debug /fo=.dbo -fm
Compile for DOS 16-bit:
Command: ???\WATCOM\binnt\wcl.exe
Arguments: /d0 $(FileName).$(FileExt) -i="???\WATCOM/h" -bt=DOS -fe=$(FileName)16 /fo=$(FileName)16 -fm
Compile for DOS 16-bit (Debug):
Command: ???\WATCOM\binnt\wcl.exe
Arguments: /d1 $(FileName).$(FileExt) -i="???\WATCOM/h" -bt=DOS -fe=$(FileName)16_debug /fo=$(FileName)16.dbo -fm
Compile for DOS PMODE/W:
Command: ???\WATCOM\binnt\wcl386.exe
Arguments: /d0 $(FileName).$(FileExt) -i="???\WATCOM/h" -bt=dos -bcl=pmodew -mf -fe=$(FileName)32 /fo=$(FileName)32 -fm
Compile for DOS PMODE/W (Debug):
Command: ???\WATCOM\binnt\wcl386.exe
Arguments: /d1 $(FileName).$(FileExt) -i="???\WATCOM/h" -bt=dos -bcl=pmodew -mf -fe=$(FileName)_debug -fe=$(FileName)32_debug /fo=$(FileName)32.dbo -fm
This one is a bit complex as the WATCOM files need to be altered but allows for very tiny .COM files.
Assemble DOS .COM:
Command: ???\WATCOM\binnt\wcl.exe
Arguments: $(FileName).$(FileExt) -bcl=asmcom
You must add this to WATCOM\binw\wlsystem.lnk:
system begin asmcom
option osname='ASM DOS .COM'
option start=START
format dos com
end
A Hello-World sample (resulting .COM filesize is 28 bytes!):
_CODE SEGMENT 'CODE'
ORG 0x100
PUBLIC START
START:
PUSH CS
POP DS
MOV DX, MSG
MOV AH, 0x09
INT 0x21
MOV AX, 0x4C01
INT 0x21
MSG:
DB "HELLO WORLD",0x0D,0x0A,'$'
_CODE ENDS
END
Odd visual glitches caused by UXTHEME.dll and Stardock Windowblinds on WinXP:
Very few programs and the Wireless Networks dialog in WinXP (SP2) may look quite messed up if Windowblinds 4.5.2 is enabled, often it's black but sometimes parts of scrollbars are rendered instead.
For some bizarre reason the draw functions of uxtheme.dll misbehave there if WB is enabled.
It might be worth a try to patch calls to IsAppThemed() or IsThemeActive() to always return FALSE.
(Alias hack the branch on zero/nonzero instructions after those calls)
These functions are commonly called when processing the WM_ERASEBKGND message.
This may result in a solid color background that may could look ugly depending on the theme.
How to detect if a Window Handle (HWND) belongs to a Win16 process:
It's not rocket science but the method differs between WinNT and Win9x.
Win9x: GetWindowLong() with GWL_HINSTANCE (-6) will return values smaller than 0x10000 (16-bit).
WinNT:
Check if the name of the process is ntvdm.exe (or otvdmw.exe on
Win64)
One way to do so would be do GetWindowThreadProcessId() then OpenProcess() followed by GetModuleBaseName() and don't forget a CloseHandle() at the end.
Alternative way would be to use GetProcessImageFileName() on WinXP and newer.
Disassemble NE/LE executables or VXD drivers with IDA Pro Free:
IDA Pro Free 4.9 doesn't properly support these types of binaries. (It won't disassemble the actual code)
You can use IDA Pro Free 4.1 (for DOS) for that, then load the saved .IDB file into IDA 4.9.
(special thanks to *M-HT at Vogons*)
Fixing CHS trouble in big disks (>8.4GB) that causes errors in SCANDISK:
If SCANDISK complains about being unable to access last cluster on big (LBA) drive,
then the culprit is the wrong partition type set for CHS adressing like 0x06 for FAT16 or 0x0B for FAT32.
I don't know a good and easy to use program to fix that but you can just open the drive with an hex editor like HxD (physical mode) and change that byte manually.
Partition table starts at 0x1BE and the type value is at offset 0x4 of the entry.
Change that byte to 0x0C for FAT32 or 0x0E for FAT16.
If EZ-Drive is installed then you might find that on the next sector instead.
My own experience of Thermal paste Vs pad:
The paste is better than pads but if the device was designed for (thick) thermal pads thus has a bit of clearance between the heatsink and heatsource then ALWAYS REPLACE THAT WITH A THERMAL PAD.
DON'T EVER PUT ANY THERMAL PASTE ON THE PADS!
I've learned that the hard way, back then I've thought it would improve it without measuring it for real.
Also if a plastic or rubber insulator is used (to prevent electrical contact) then don't put any paste there either.
Only replace a (failed/cracked) plastic insulator between IC/Transistor and heatsink with a rubber insulator.
Removing hotglue the easy way:
Spraying a tiny bit of contact spray right between the edge of the hotglue
and the bonded surface.
Pry it off at that spot with a flat screwdriver (or similar). The fluid will quickly seep under it and loosen up the bond and it will likely come off as an entire piece.
Using old expired but never used Ni-MH batteries:
Just because the positive contact is covered in nasty white crust doesn't mean you couldn't just clean it off (with a Fiberglas pen) and recharge it.
If a current limited lab supply of 1A at 1.2V (within 20 seconds) can revive it from its overdischarged state then it might charge fine and possibly have a good low and usable impedance with about 90% capacity.
Just don't leave it in the device for over a day or better yet remove it immediately after using it.
Expect a high amount of self-discharge, usually it won't go below 1.2V after months but the device may shut off way quicker than it should.
(I found five 14M145 that are GP branded and made in around 2002 judging by the bright orange and dark green wrapper and were sealed in their packaging for ~20 years, I used those on a Sony Walkman WM-EX674)
Using cheatcodes on "B3313 Unabandoned A2 Anniversary Edition" running on real HW:
Cheatcodes doesn't work due to the cheat engine (injected program code) being placed by default at end of the 8MB RDRAM which conflicts with the boot process.
Use FF1CD800 0000 to relocate the cheat engine in a free spot under the 4MB mark.
This MAY cause crashes/lockups during door transitions but the chance of that is very low, nearby RAM areas did that more often. Also keep the amount of active codes to a minimum to prevent further issues.
Settings oddities and DirectX errors in "Bubsy the Woolies Strike Back":
I'm not sure what went wrong in the development but the GOG Config/Launcher is weird and only shows the current resolution in the combobox but that is the least annoying issue.
The real screwed up part is that it sets the variables bits_per_pixel and refresh_rate in config.ini but the actual game doesn't read these two values.
By default the game always requests 24/32bpp for the D3D surface no matter what and the refresh rate is hardcoded to 60hz (only applies to fullscreen mode).
Which caused this wonderful error message to appear on my system if it's set for fullscreen:
...\sbrenderer\vision\../RenderApp/SBRenderApp_DX9.inl::SBRenderApp::Initialize(70)
Could not create D3D Device: 0x8876086c
I think it happened because the resolution was frequency locked to much higher value with RefreshForce.
Now to fix it, search these bytes in the executable with a hex editor:
C746183C000000 //mov dword [esi+$18],$3C
Replace the 3C (60 in hexadecimal) with your desired refresh rate.
Playback MSU-1 PCM files with vgmstream (via txth file):
Create a textfile with this content:
codec = PCM16LE
sample_rate = 44100
channels = 2
interleave = 2
start_offset = 8
num_samples = data_size
loop_flag = auto
loop_start_sample = @0x4
loop_end_sample = data_size
and name it ".pcm.txth" and place it same directory as the .pcm files.
Make sure use a version of vgmstream that supports .txth files. (Winamp plugin from Jan 16 2019 works on XP)
->Follow this tip if you're on Windows
Making a custom linker file for WATCOM projects and other related tips:
This seems to be the only method to get a shared memory section for an Windows hooking DLL:
You could also add the line into wlsystem.lnk itself but editing files of the compiler isn't very portable.
You can also define the PE (Win32) ImageBase address in a similar fashion with:
OPTION OFFSET=0xXXXXXXXX
(values other than the default 0x400000 is recommended for compiling DLLs)
You can find more such options in the "Linker Guide".
The easiest way to add import libraries in WATCOM is to put something like this into your code:
#pragma comment( lib, "setupapi" );
My way of fixing cracked plastic with acetone:
I pour a very little bit into a small glass and use tiny pieces of toilet paper (around 1x1cm) and fold that like 4-5 times into a thin strip, then hold that with tweezers and dip that into the glass with acetone.
And use it like a brush as that doesn't drip as much as regular plain brush.
Usually it's enough to just barely touch the cracked surface get it wet enough then carefully push it against the other piece till it holds itself in place.
Keep in mind that not every type of plastic dissolves with acetone.
The better way to spool a cassette tape for recording:
Pay attention to the distance between the erase and record heads.
Most people (and manuals) wind the start of tape to the middle center, which seems a bad practice to me.
It's always better to spool the start of the magnetic tape right before the erase head.
Or else the tape starts with a bit of the unerased content (as that never moved past the erase head).
On the typical compact cassette the delay between erase and record head is bit over a half second. (Around 2,6cm at 4,7625cm/s are about 546ms)