VOGONS


First post, by carlostex

User metadata
Rank l33t
Rank
l33t

Hi there!

Tandy 3 voice sound on an AT class computer (286+) always faced the second DMA controller problem. As you know, on the IBM AT and following clones a second Intel 8237 DMA controller was placed on port 0C0h, right where IBM PC/Jr and Tandy 1000 PC's ha the 3 voice chip. So AT users with homebrew Tandy cards, had commonly their cards set to port 2C0h. Port 1E0h, as with later Tandy PC's, which the Tandy 1000 RSX and the 2500XL, kept the PSSJ but relocated to port 1E0h. Later Tandy PC's thus had their compatibility broken with older software.

Enter the software solution. Through the years we got redirection utilities, for both the ISA cards and the TNDLPT device. Recently, i was discussing here on VOGONS about repatching for the INT C0h trick. This is a better approach than the TSR method as port trapping is made completely unnecessary, no need for QEMM or EMM386! However, i started to be a bit more ambitious... Why not patch for a 2 byte port? Directly to port 2C0h... Possible? Why is this difficult?

As some of you might know with only 2 bytes you can do this:

E6 C0 -> OUT C0, AL

With only 2 bytes you spit out the data that is on AL right directly to the Tandy chip I/O port.
Now for a 2 byte port like 2c0h?

52 -> PUSH DX
BAC002 -> MOV DX, 2c0
EE -> OUT DX, AL
5A -> POP DX

Ouch... that's 6 bytes... One can try and remove saving DX into the stack but it's still 4 bytes and there's situations you can't simply destroy what's on DX, as it might be needed later. So the options:

Option 1:
Find some empty space within the segment where we can jump and create our patch code. Short jumps are 2 bytes long which fit but 99% of the time there's no space around the +- 128 bytes. So better do a CALL, but that's 3 bytes. So that requires this, like the following example:

8AC4
E6C0

We now have 4 bytes to do a CALL and there will be a NOP left over:

E8 xx xx -> CALL to .... (wherever we find the space without effing up)
90

and the patched code will look like this:

8AC4
52
BAC002
EE
5A
C3 -> RETURN BACK to the caller, will land on that stray NOP (which is fine)

Above is fine as long as it's within the same segment, or you're not overwriting reserved space for something else.

Option 2:
Now this one is a LOT more work, but it's ALWAYS better. It's common, (at least a lot of games have them) to find multiple 2 byte SHIFT instructions like so:

D0E0 -> SHL AL, 1
D0E0 -> SHL AL, 1
D0E0 -> SHL AL, 1
D0E0 -> SHL AL, 1

Why not SHL AL, 4?

C0E004 -> SHL AL, 4

and like that we save 5 bytes!!!! OK this will require the 186 instruction set but who cares? XT systems can use port 0C0h anyway....
We're not out of the woods yet, so i need to carefully rewrite ALL instructions realign jumps and calls in between always cross referencing every instruction!
But it works, and soon enough i reach the E6 C0 instruction and i have enough space to patch! Sometimes i have to use a combination of both methods!

Honestly, it's a lot of work...but i don't mind. I'm actually enjoying doing this. A bit masochistic, but it has been an enriching experience. I still face some difficulties though...dreaded ENCRYPTION. Some files are packed/encrypted and it's impossible to patch otherwise. For EA stuff with their LIB files, or DYNAMIX VOL files, SSI with their DAX files, all of those use some kind of compression / encryption that makes it impossible to patch. I have found an unpacking utility for EA LIB files for instance, but the game is expecting to find the packed file during runtime and it completely ignores the unpacked file in the directory. So this would need either some tool to encrypt back the file after i had it patched. These ones are super frustrating because without any encryption it is possible to patch 95% of the time.

So what is the progress at the moment? Well... I have quite a few games patched, i would say around 30 titles or so, which include the classics most everyone likes.

The patches are available at the OLDSKOOL FTP, exactly where my old patches that defeat detection could be found. At the root, you'll now find a folder called ISA with 2 subfolders. One for port 1E0 (great for the AT Tandy PC's) and port 2C0. Some readme's are also included where i thought it would make sense. This is still a WIP, and i guarantee more games will be patched. It is absolutely great to just launch a game not having to depend on any TSR's or redirection tricks, installing INT handlers etc... Just run and go!

I honestly hope that in the future we will see an ISA card that consolidates other similar standards, a single ISA card that would tackle Tandy, CMS, Innovation and Covox Sound Master.

Reply 1 of 17, by carlostex

User metadata
Rank l33t
Rank
l33t

As an addendum, during the patching process i found something i never knew. So LUCASARTS dropped Tandy support for the 256 color version of Indiana Jones and the Last Crusade right?? Er... well they dropped the "ts" command line option from the EXE but the sound routines and the data are intact!!! So there's a patched version on the FTP that allows you to play Indy 256 color with Tandy 3 voice sound! Was just a matter of defeating Adlib detection + making it believe it's running on a Tandy PC and 3 voice Tandy will play!

Reply 2 of 17, by digger

User metadata
Rank Oldbie
Rank
Oldbie

Cool hackery. Thanks for sharing! 🙂

I consider it once of IBM more frustrating decisions how they the broke their own hardware compatibility when they released the PC/AT and just carelessly repurposed that same I/O address for the second DMA controller.

I know IBM always focused on business use cases and stuck to the lame PC speaker just to save maybe a dollar or two in manufacturing costs, but why not keep that official PCjr I/O address free, and pick one of the many other available ports for the second DMA controller? They could then have added the 3-voice sound chip to their official joystick card, in effect making it a "proto sound card" that would in effect add PCjr sound compatibility back in. Didn't it make sense for the same people who bought joystick cards to also want better sound capabilities?

Or better still, why didn't they just equip the PC/AT with the same PCjr-compatible sound chip on the same I/O address? That machine was more high-end and had a higher profit margin anyway.

Maybe if a third-party joystick card manufacturer had come up with that idea and sold enough of such joystick/sound cards before the PC/AT was released , that would have influenced IBM's design decision.

Reply 3 of 17, by carlostex

User metadata
Rank l33t
Rank
l33t

IBM always wanted to keep away from games for their top of the line PC machines, the PC the XT and the AT were to be seen as "serios machines for real work". Machines for games were commony referred as toys, an IBM wanted to avoid that at all costs. In hindsight, yes it would be great if they kept the SN76489 for the AT, but that's just not how corporate suits thought back then.

I had a chance to keep in touch with Rich Heimlich, and one of the stories he told me involved testing a bunch of software, including games for IBM OS/2. I think Rich tells this story in the Bleep webisode about sound, and basically the IBM top brass didn't care that DOS games didn't have any sound when running through OS/2. Rich refused to go along when OS/2 wasn't fully working and Dave Whittle got fired, which he ultimately blamed on Rich. So Rich told me further that none other than Mike Meisner (MR BIOS) found out the issue was with VDMA.SYS driver. Mike hacked the driver, and suddenly everything worked. Who knows what IBM OS/2 could have been if it had perfect compatibility with DOS games and other software?

Reply 4 of 17, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie

maybe do the replace a 2 byte out 0xc0,al with an int call 0xCD 0xXX? thats only two bytes then you can inject a tsr of however many bytes you need..

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--

Reply 5 of 17, by carlostex

User metadata
Rank l33t
Rank
l33t
BloodyCactus wrote on 2025-08-12, 22:13:

maybe do the replace a 2 byte out 0xc0,al with an int call 0xCD 0xXX? thats only two bytes then you can inject a tsr of however many bytes you need..

That defeats the exact purpose of having a binary that does not need a tsr which is all what this is about. It’s nice to have the card in the ISA slot launch the game and it just works, there’s nothing in between.

Besides…encryption is the biggest problem i face, and that makes what I’m doing difficult and it’s just as difficult to patch for INT C0h. So for those games the old port trapping redirection is still the best option, I’m afraid.

Regardless, there’s quite a good number of titles that have been patched already.

Reply 6 of 17, by pdw

User metadata
Rank Newbie
Rank
Newbie

If what annoys you most about a TSR is the memory use, there are ways around that. Your C0h interrupt handler would only be 7 bytes (PUSH DX / MOV DX, 2c0 / OUT DX, AL / POP DX / IRET). There are a lot of spaces where that can be stashed without "using" memory. E.g. two unused interrupt vector slots. Or the "Inter-Application Communication Area" at 0040:00F0.

The nice thing about the INT C0h patches is that it allows for compatibility with any hardware configuration: original hardware, port 2C0h, TNDLPT.

Reply 7 of 17, by carlostex

User metadata
Rank l33t
Rank
l33t
pdw wrote on 2025-08-13, 11:47:

If what annoys you most about a TSR is the memory use, there are ways around that. Your C0h interrupt handler would only be 7 bytes (PUSH DX / MOV DX, 2c0 / OUT DX, AL / POP DX / IRET). There are a lot of spaces where that can be stashed without "using" memory. E.g. two unused interrupt vector slots. Or the "Inter-Application Communication Area" at 0040:00F0.

The nice thing about the INT C0h patches is that it allows for compatibility with any hardware configuration: original hardware, port 2C0h, TNDLPT.

I don't deny that The INT C0h trick isn't without its merits. I do say so in the original post. But the goal here was different. I wanted to challenge myself, because i knew in theory what i wanted to do was possible, so i just rolled my sleeves and started on a game by game basis and things started to progress. Will i be able to patch 100% of games with this method? Of course not, but i was ready for that. I just wanted to have binaries that i could just run and they would work, no need to load any TSR's or reprogram the interrupt handler. So i'm not against it, the more options the better.

Again the biggest issue a project like this isn't even trying to replace and patch instructions, it's encryption. And there are still games in which the Tandy detection eludes and puzzles me. For instance, i have an additional 3 ACCOLADE titles patched for Tandy that i haven't released yet. While these work great in DOSBox set to machine=svga_s3 and tandy=on, they still play with PC Speaker on a real DOS PC. These are Les Manley 2, Elvira: The Jaws of Cerberus and The Games: Winter Challenge. These games seem to share the same type of sound engine, but i couldn't break their detection yet. And this is an issue that will be present for this method or the interrupt handler trick.

Reply 8 of 17, by carlostex

User metadata
Rank l33t
Rank
l33t

Update, here's a PDF converted from my Excel file with the list of currently patched games:

The attachment Tandy 3 Voice patching REDUX.pdf is no longer available

The list doesn't yet include all games supported by John Miles AIL 2, AIL 3 (MSS) and AIL/32. I recompiled the drivers for these libraries, so the actual number of games that now can use a 2 byte port are probably well above 200.

Reply 9 of 17, by matze79

User metadata
Rank l33t
Rank
l33t

That's a whole lot of Stuff !

Reply 10 of 17, by dreamblaster

User metadata
Rank Oldbie
Rank
Oldbie

wohooo do these all work with TNDLPT?

Visit http://www.serdashop.com for retro sound cards, video converters, ...
DreamBlaster X2, S2, S2P, HDD Clicker, ... many projects !
New X2GS SE & X16GS sound card : https://www.serdashop.com/X2GS-SE ,
Thanks for your support !

Reply 11 of 17, by carlostex

User metadata
Rank l33t
Rank
l33t
matze79 wrote on 2025-09-01, 15:05:

That's a whole lot of Stuff !

Yeah it has been quite the journey, unfortunately there's quite a few games with encryption, which i would love to also get patched but it turns out impossible without tools. There's still a few DYNAMIX and EA titles i would love to get patched.

dreamblaster wrote on 2025-09-01, 17:00:

wohooo do these all work with TNDLPT?

They will but would still require redirection in any case. Outputting data to the LPT port is a bit more involved and some games might actually be impossible due to lack of space. But some might be possible. The AIL drivers are possible for sure, and i do intend to work on those. But i would like to tackle all of this first, as ISA is my priority.

Reply 12 of 17, by DaveDDS

User metadata
Rank Oldbie
Rank
Oldbie

A little trick I sometimes use, write a small .COM which makes use of an unused 8086 software interrupt:

- Setup the interrupt vector to point to "handler" in your code
- exec the application .. which you have patched using only the 2-byte INT instruction
- In "handler" do whatever needs to be done, then RTI
- when exec'd program exits back to you - clear/restore the INT vector and exit

.COMs are very small, and you won't need to worry about loading/unloading a TSR.
If you have to, you can rename the patched application to some unique name, and
call the .COM the actual application name (if it needs to be a .EXE, you can make a
tiny model .EXE nearly as small)

If you want to get really "fancy" you can load the application without running, patch
it to call the INT at the beginning, on 1st INT call, patch the app code in memory, set the
INT vector to "handler" and let the code run. Can be tricky if code reloads part of itself
etc... but sometimes you can make an app which does the new thing without having to
keep a special patched one available.

(This or course it assuming DOS where you can patch interrupt vectors and code in memory
without worrying about OS rescrictions)

And ... sometimes if you can figure out the right interrupt that the game calls early-on after
unpacking itself into memory, you just wait on that interrupt. do your patches, restore that
interrupt and chain to it...

Dave ::: https://dunfield.themindfactory.com ::: "Daves Old Computers"->Personal

Reply 13 of 17, by carlostex

User metadata
Rank l33t
Rank
l33t

Thanks for your insight Dave!! There's no doubt that's a neat little trick, but honestly the biggest difficulty with this project, i kid you not, is encryption or weird machine detection schemes. That is the sort of problems that will stop you not only patching the direct way or to use a INT handler by patching a E6 C0 instruction to CD C0.

If it was not for encryption I would have by now more than 90% of games with Tandy support patched for direct output. And that completely eliminates any need for any additional coding or tricks. You just use the patched file and you'll hear music on the Tandy sound adapter. That's the goal here. For instance, I'm sitting on 3 ACCOLADE games here that have a weird machine detection so they don't play Tandy sound on a real DOS PC (play speaker only), but they do play on DOSBox. Those games are Les Manley: Lost in LA, Elvira II: The Jaws of Cerberus and The Games: Winter Challenge. There's another Accolade title called Bar Games, which i actually like a lot, where i've been unable to make the game play Tandy sound without Tandy graphics. Another example of this is Blue Angels.

Then there's encrypted games, which just annoy me to death. DYNAMIX (Heart of China, Willy Beamish, Stellar 7, Nova 9 etc...) and EA (Indianapolis 500, LHX, SU-25, Lakers Vs Celtics, among a couple others) have their games commonly packed and encrypted, which does not allow me to access the Tandy sound routines. Patching for an INT handler is far simpler, but i don't mind doing the direct approach to just be done with it. Considering how far i've done, i don't mind to finish this at all, if possible.

Reply 14 of 17, by DaveDDS

User metadata
Rank Oldbie
Rank
Oldbie
carlostex wrote on Yesterday, 03:15:

Thanks for your insight Dave!! There's no doubt that's a neat little trick, but honestly the biggest difficulty with this project, i kid you not, is encryption or weird machine detection schemes. That is the sort of problems that will stop you not only patching the direct way or to use a INT handler by patching a E6 C0 instruction to CD C0. ...

If the program unpacks/decrypts itself, then you have to use the last method
described and apply the patches after it has unpacked.
- the trick is to find an INT you can trap which will be called by the
program AFTER it has unpacked/decrypted, but BEFORE it performs the operations
you wish to patck.

Somewhere I have a tool I wrote many years ago which traps ALL interrupts, and
reports the first 1k or so INTs called after it launches a program which helped a
lot figuring things like that out. You can tell from the stacked RTI segment
where the program is, and do some memory saves/compares to figure out when it
has unpacked..

The next trick is to figure out where the code you want to patch is located
in the unpacked program - in the same tool, once I had determined that it had
unpacked, I would write the entire 64k code segment to a file on my RamDisk
which I could then look for the various opcodes and disassemble bits around
them as needed to work out the exact patch locations and bytes/

Dave ::: https://dunfield.themindfactory.com ::: "Daves Old Computers"->Personal

Reply 15 of 17, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie

OP was dead set against doing a TSR/Loader when I suggested it. There is a reason we often cracked games this way but whatever...

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--

Reply 16 of 17, by DaveDDS

User metadata
Rank Oldbie
Rank
Oldbie

Oh I got that he is opposed to a TSR, but I don't recall such opposition to a loader, and a loader can be made to appear "invisble", much less overhead on the system,
and he did say it was a "neat little trick". Given the problems with unpacking/decrypting programs, I thought it was worth describing how I've worked around this
in the past using a loader technique.

Dave ::: https://dunfield.themindfactory.com ::: "Daves Old Computers"->Personal

Reply 17 of 17, by Benedikt

User metadata
Rank Oldbie
Rank
Oldbie
BloodyCactus wrote on Yesterday, 17:48:

OP was dead set against doing a TSR/Loader when I suggested it. There is a reason we often cracked games this way but whatever...

Pity.
I was about to write a "companion" TSR that besides redirection handles PSG parameter translation from SN76489 to SAA1099, AY-3-891x and SID to cover Tandy sound cards, CMS / Game Blaster, Covox Sound Master, Mindscape Music Board and Innovation SSI-2001 along with LPT variants and maybe ultimately more, but as you said:
Whatever... ¯\_(ツ)_/¯

P.S.: To address the argumentation: Why would a software interrupt strictly necessitate a TSR, anyway? A software interrupt is just a glorified memory indirect far call!