• Please review our updated Terms and Rules here

Tandy 1000 Detection

Great Hierophant

Veteran Member
Joined
Mar 22, 2006
Messages
1,928
Location
Massachusetts, USA
How do PC games typically auto-detect that they are running on a Tandy 1000 machine? I read somewhere that they look for the string TANDY in the BIOS. This will identify a PC manufactured by Tandy, as the copyright text for all Tandy PCs should have the word TANDY in them. But this is not enough because Tandy released many PC compatible machines like the Tandy 1200, 3000 & 4000 that did not support Tandy graphics and sound.

A Tandy 1000 uses the PC identifier byte at F000:FFFE, FF. It does not use the PCjr. identifier byte, FD. Presumably the program would have to check to see whether Graphics Modes 8, 9 & A are defined in the BIOS. If they are, then it knows it can use Tandy 1000 graphics and the sound chip is always present. If not, then it should assume its is dealing with a CGA card.
 
Taken right from my Paku Paku source code -- My sound auto-detection runs this after checking for Adlib:

Code:
function tandyDetect:boolean; assembler;
{
	Returns:
		False (0x0000) for Non-Tandy/Jr
		True (0xFFFF) for Tandy/Jr
}
asm
	mov  ax,$FFFF
	mov  es,ax
	mov  di,$000E
	cmp  es:[di],$FD    { is $FD only on the Jr }
	jne  @notJr
	ret
@notJr:
	cmp  es:[di],$FF    { is $FF only on tandy }
	jne  @notTandy
	mov  bx,$FC00
	mov  es,bx
	xor  di,di
	cmp  es:[di],$21
	jne  @notTandy
	ret
@notTandy:
	xor  ax,ax
end;
Though this also returns true for the Jr.

My Video routines uses a bit more complex a check:
Code:
const
	videoCard_mda=0;
	videoCard_cga=1;
	videoCard_pcJr=2;
	videoCard_tandy1000=3;
	videoCard_tandySLTL=4;
	videoCard_ega=5;
	videoCard_vga=6;
	videoCard_mcga=7;
	videoCardName:packed array[0..videoCard_mcga] of string[11]=(
		'MDA','CGA','PCJr','Tandy 1000','Tandy TL/SL','EGA','VGA','MCGA'
	);

{
	Detecting which video card is present is kinda tricky...
	but thankfully they did something smart with int $10.
	Calls to unknown subfunctions just RET leaving registers
	intact, so if you call a VGA function that you know changes
	a register, and the register doesn't change, it's not a VGA.
	Call a EGA function ditto, ditto... finally check if we're in
	a monochrome display mode, that's MDA.

	Unfortunately there's no known reliable check for a CGA since
	newer cards pretend to be one -- but if we eliminate
	'everything else' from the list, it must be CGA.
}

function detectCard:byte; assembler;
asm
	mov  ax,$1200
	mov  bl,$32       { VGA only enable video }
	int  $10
	cmp  al,$12       { VGA returns $12, all others leave it unmodified! }
	jne  @notVGA      { not a vga, test for EGA }
	                  { VGA, or is it? test for MCGA }
	xor  bl,bl        { null BL so it's set up for non-PS/2 }
	mov  ax,$1A00
	int  $10
	cmp  bl,$0A       { MCGA returns $0A..$0C }
	jb   @isVGA
	cmp  bl,$0C
	jg   @isVGA
	mov  al,videoCard_mcga
	ret
@isVGA:
	mov  al,videoCard_vga
	ret
@notVGA:           { We eliminated VGA, so an EGA/VGA true must be EGA }
	mov  ah,$12
	mov  bl,$10       { EGA/VGA get configuration info }
	int  $10
	and  bl,$03       { EGA/VGA returns a 0..3 value here }
	jz   @notEGA      { not a VGA, test for MDA }
	mov  al,videoCard_ega
	ret
@notEGA:            { MDA all we need to detect is video mode 7 }
	mov  ah,$0F       { get Video mode }
	int  $10
	cmp  al,$07
	jne  @notMDA
	mov  al,videoCard_mda
	ret
@notMDA:            { not MDA, check for Jr. }
	mov  ax,$FFFF
	mov  es,ax
	mov  di,$000E     { second to last byte PCjr/Tandy BIOS info area }
	cmp  es:[di],$FD { ends up $FD only on the Jr. }
	jne  @notJr
	mov  al,videoCard_pcJr
	ret
@notJr:             { not junior, test for tandy }
	cmp  es:[di],$FF       { all tandy's return $FF here }
	jne  @notTandy
	mov  ax,$FC00
	mov  es,ax
	xor  di,di
	cmp  es:[di],$21
	jne  @notTandy
	mov  ah,$C0       { test for SL/TL }
	int  $15          { Get System Environment }
	jnc  @tandySLTL     { early Tandy's leave the carry bit set, TL/SL does not }
	mov  al,videoCard_tandy1000
	ret
@tandySLTL:
	mov  al,videoCard_tandySLTL
	ret
@notTandy:
	mov  al,videoCard_cga { all other cards eliminated, must be CGA }
end;

Which works quite well telling you exactly what's in there for a video card -- but this will NOT tell you the machine is tandy if a MDA, EGA or VGA is installed.
 
It gets more tricky when you want to have DAC/PSSJ audio support for the later Tandy systems with a 16-bit ISA bus:

Cloudschatze said:
In the PSSJ-bearing Tandy systems with 16-bit ISA slots (1000 RSX, the 2500-series, and that first-model Sensation! again), the 3-voice and DAC ports were moved from the starting address of C0 to 1E0, so as to not conflict with the second DMA controller. This breaks Tandy sound compatibility with most software that I've encountered. Some later software takes these changes into account, so while Sierra's SCI0 and SCI1 games (for example) will work fine on these systems, with the appropriate driver, the older AGI games will not.
 
Taken right from my Paku Paku source code -- My sound auto-detection runs this after checking for Adlib:

Code:
function tandyDetect:boolean; assembler;
{
	Returns:
		False (0x0000) for Non-Tandy/Jr
		True (0xFFFF) for Tandy/Jr
}
asm
	mov  ax,$FFFF
	mov  es,ax
	mov  di,$000E
	cmp  es:[di],$FD    { is $FD only on the Jr }
	jne  @notJr
	ret
@notJr:
	cmp  es:[di],$FF    { is $FF only on tandy }
	jne  @notTandy
	mov  bx,$FC00
	mov  es,bx
	xor  di,di
	cmp  es:[di],$21
	jne  @notTandy
	ret
@notTandy:
	xor  ax,ax
end;
Though this also returns true for the Jr.

My Video routines uses a bit more complex a check:
Code:
const
	videoCard_mda=0;
	videoCard_cga=1;
	videoCard_pcJr=2;
	videoCard_tandy1000=3;
	videoCard_tandySLTL=4;
	videoCard_ega=5;
	videoCard_vga=6;
	videoCard_mcga=7;
	videoCardName:packed array[0..videoCard_mcga] of string[11]=(
		'MDA','CGA','PCJr','Tandy 1000','Tandy TL/SL','EGA','VGA','MCGA'
	);

{
	Detecting which video card is present is kinda tricky...
	but thankfully they did something smart with int $10.
	Calls to unknown subfunctions just RET leaving registers
	intact, so if you call a VGA function that you know changes
	a register, and the register doesn't change, it's not a VGA.
	Call a EGA function ditto, ditto... finally check if we're in
	a monochrome display mode, that's MDA.

	Unfortunately there's no known reliable check for a CGA since
	newer cards pretend to be one -- but if we eliminate
	'everything else' from the list, it must be CGA.
}

function detectCard:byte; assembler;
asm
	mov  ax,$1200
	mov  bl,$32       { VGA only enable video }
	int  $10
	cmp  al,$12       { VGA returns $12, all others leave it unmodified! }
	jne  @notVGA      { not a vga, test for EGA }
	                  { VGA, or is it? test for MCGA }
	xor  bl,bl        { null BL so it's set up for non-PS/2 }
	mov  ax,$1A00
	int  $10
	cmp  bl,$0A       { MCGA returns $0A..$0C }
	jb   @isVGA
	cmp  bl,$0C
	jg   @isVGA
	mov  al,videoCard_mcga
	ret
@isVGA:
	mov  al,videoCard_vga
	ret
@notVGA:           { We eliminated VGA, so an EGA/VGA true must be EGA }
	mov  ah,$12
	mov  bl,$10       { EGA/VGA get configuration info }
	int  $10
	and  bl,$03       { EGA/VGA returns a 0..3 value here }
	jz   @notEGA      { not a VGA, test for MDA }
	mov  al,videoCard_ega
	ret
@notEGA:            { MDA all we need to detect is video mode 7 }
	mov  ah,$0F       { get Video mode }
	int  $10
	cmp  al,$07
	jne  @notMDA
	mov  al,videoCard_mda
	ret
@notMDA:            { not MDA, check for Jr. }
	mov  ax,$FFFF
	mov  es,ax
	mov  di,$000E     { second to last byte PCjr/Tandy BIOS info area }
	cmp  es:[di],$FD { ends up $FD only on the Jr. }
	jne  @notJr
	mov  al,videoCard_pcJr
	ret
@notJr:             { not junior, test for tandy }
	cmp  es:[di],$FF       { all tandy's return $FF here }
	jne  @notTandy
	mov  ax,$FC00
	mov  es,ax
	xor  di,di
	cmp  es:[di],$21
	jne  @notTandy
	mov  ah,$C0       { test for SL/TL }
	int  $15          { Get System Environment }
	jnc  @tandySLTL     { early Tandy's leave the carry bit set, TL/SL does not }
	mov  al,videoCard_tandy1000
	ret
@tandySLTL:
	mov  al,videoCard_tandySLTL
	ret
@notTandy:
	mov  al,videoCard_cga { all other cards eliminated, must be CGA }
end;

Which works quite well telling you exactly what's in there for a video card -- but this will NOT tell you the machine is tandy if a MDA, EGA or VGA is installed.

Very readable code, even for me. Your routine does not test for Hercules graphics, noting that Paku Paku does not support Hercules graphics. If your Tandy SL or TL or later is being used in monochrome mode, it seems like it would look like the routine would assume you have an MDA card in it.

I did not know of the unique Tandy 1000 identifier byte at FC00:0000, which has a 21 in it for all Tandy 1000s and something else for other machines. This is documented in the Tandy 1000 TL and SL Technical References, but I do not see it mentioned in earlier documentation.
 
deathshadow, you have a bug in there and it is kind of funny as it proves a point I made in an earlier discussion we've had.

If you mean this:
Code:
mov  ax,$1A00
	int  $10
	cmp  bl,$0A       { MCGA returns $0A..$0C }
	jb   @isVGA
	cmp  bl,$0C
	jg   @isVGA

1) only way that 'might' fail is if it returns 'unknown' since the 'normal' responses are $00..$0C ... BUT

2) FF 'unknown' would, if handled with sign, still be less than $0A, so it's still "VGA" (which a number of SVGA clones respond with there). When checking both below and greater on a nybble sized range sign doesn't matter... We already tested for VGA or MCGA at that point, so ALL we care about is $0A,$0B and $0C. Any result outside that range is invalid, so sign makes no difference there as it's still invalid; it would simply trip the jb instead of the jg. Result is the same, jumping to "isVGA".


Though if you mean some other bug, feel free to enlighten us all.

Though I'm looking at the 'and 0x03' on the EGA test and going "WTF" - that test would only work on 128K or more EGA cards. 64k EGA should bomb out.
 
If you mean this:

Yes, that's it. My point in the earlier discussion is that signed jcc's should be avoided like the plague* just because it is so easy to get confused. And judging by your reply you're still confused.

Consider this, what happens if the contents of BL is somewhere in the range of 80h to FFh on return from that interrupt?

The first jcc (jb) works with unsigned numbers so it will only jump if BL is in the 0 to 9 range. So the first check falls through.
The second jcc (jg) works with signed numbers so it will only jump if BL is in the 0Dh to 7Fh range. So the second check also falls through.

End result is that the routine reports an MCGA card whenever BL is in the 80h to FFh range. Is that what you intended?

*unless of course you're actually doing something with negative numbers or as part of some kind of optimization involving flags.
 
Wilton pretty much nailed basic graphics card detection in 1987 so I defer to him: https://github.com/MobyGamer/TOPBENCH/blob/master/VIDEOID.ASM

I expanded his code a little with Tandy stuff from Jason and my own 400-line CGA (6300, Compaq PIII) in this code: https://github.com/MobyGamer/TOPBENCH/blob/master/TOPB_DET.PAS (search for "Now check for specific adapters based on various criteria.")

As for detecting a Tandy system (ie. not just the graphics card), the previously-linked code finds it by peeking all over ROM. Paraphrasing:

Code:
if mem[$FFFF:$000E]=$FF 
  then if mem[$F000:$C000]=$21
    then found:='Tandy 1000'
    else found:='IBM PC';
 
I was unable to get that to work on my 386/40 testbed with VGA, which is why I rejected it a couple years ago... apparently if the VGA or system BIOS doesn't trip the PS/2 detection, it bombs out saying it's a MDA...

While the code calls it "FindPS2", it uses Int 10h,1A00h which is not PS/2 specific but rather VGA specific. All VGA-compatible cards must support this function or they are not 100% VGA compatible. If you still have your testbed of a couple of years ago, I'd be curious to see what that code (or TOPBENCH) detects your card as.
 
Back
Top