Closed MX9000 closed 2 years ago
This game may have been written for x86 processors before protected mode and the segment limit exception.
Try adding:
[cpu] segment limits=false
Another option is to use a cputype of a processor that doesn't have a segment limit exception, which is anything prior to the 286.
cputype=8086 and cputype=80186 should resolve this issue.
Theoretically it's also possible this issue would never occur on a 386 or higher if HIMEM.SYS is loaded because of the way that HIMEM.SYS accesses extended memory: flat real mode.
HIMEM.SYS can avoid switching to protected mode whenever extended memory is read or written by just programming flat real mode and leaving the CPU in that state, which is why code can just use [esi] to access extended memory when the processor is supposed to throw a fault. At least when I experimented with 386 assembly language under real mode Windows 95 way back in the day.
Virtual 8086 mode however has 64K limits that cannot be overridden, so this might crash if EMM386.EXE is loaded and "active" aka running the DOS system in virtual 8086 mode.
This makes me wonder if it was able to run on a 286 without crashing. Mobygames says this game came out in 1989, so the IBM PS/2 was out there with the 286 processor and EGA/VGA graphics. You could of course put EGA on an 8086, and maybe that's all they tested.
Could it be possible that the 286 does not check segment limits in real mode?
It is known that 386 and higher processors always check segment limits, even in real mode. While they seem to default to 64KB, that is also commonly exploited deliberately to make "flat real mode" in which it becomes possible to address the full 4GB range in real mode.
Segment limit hacks going from protected to real mode are not possible on the 286 because the 286 can only enter protected mode, it cannot leave protected mode without a reset (which is why IBM added the "reset vector" hack in the first place). Additionally the 286 cannot go beyond 64KB segments, so, there's no point hacking segment limits.
Something to test on real hardware.
When it happens, it's during a REP MOVSW with EDI=FFFF with CX at some words to go.
EAX=0000FFFF ESI=0000011E DS=1A70 ES=A200 FS=0000 GS=0000 SS=0826 Real
EBX=000001FE EDI=0000FFFF CS=382F EIP=00009FE6 C0 Z0 S0 O1 A1 P1 D0 I1 T0
ECX=0000096C EBP=000002AE NOPG IOPL0 CPL0
EDX=000003CF ESP=000000EC 27
ST0=00000.00 ST1=00000.00 ST2=00000.00 ST3=00000.00
ST4=00000.00 ST5=00000.00 ST6=00000.00 ST7=00000.00
382F:00009FE6 F3AB repe stosw
382F:00009FE8 13C9 adc cx,cx
382F:00009FEA F3AA repe stosb
Do you suppose the 286 has a bug where a segment limit of FFFFh never catches the WORD access because it probably does a check like this for 16-bit memory access:
uint16_t check_offset = offsetvalue + 1
if (check_offset > segment_limit) exception()
FFFFh + 1 == 0000h because 16-bit rollover, so either BYTE or WORD access at FFFFh never triggers the segment limit exception. Which would explain how such a game could work regardless on a 286.
If that's true, then technically you can exploit that to read one byte past the segment limit provided by the OS in protected mode :)
I should drag out the PS/2 model 30 (286 processor and VGA) and see if I'm right!
Thank you for your in-depth technical explanations! I will try your suggestions as soon as possible and give you feedback. In the meantime, I attach a page from the game manual (https://www.gamesdatabase.org/) where they explicitly mention the compatible systems, including the IBM PS/2 80286: Vette! Hardware Requirements.pdf In the rest of the manual they also mention 80386 CPU and on page 34 they specify that the EGA graphics card must have 256K of VRAM because the model with only 64K is not able to display the 640 x 200 resolution with 16 colors: I don't know if this information is relevant.
That's not entirely true, one "page" of 640x200 16-color is just below 16KB, unless they're relying on page flipping or any other trick.
In the planar design of the EGA, 64KB would mean you can access each bitplane out to 16KB. As far as I know the 64KB memory configuration doesn't change how 16-color modes are mapped to RAM other than 640x350 4-color mode which is theoretically a hack involving the "odd/even mode" that ties together two bitplanes for 4 colors.
Here are the test results:
Thank you.
@MX9000 Same here, I noticed that occasionally it seems to floodfill the entire screen with a single color. Then the controls down below flash which tells me it's using page flipping.
Regarding the 286 segment limit overflow theory... does that also apply to the 386, where a segment limit of FFFFFFFFh (4GB - 1) would cause an overflow when checking WORD and DWORD access, and therefore a WORD/DWORD read from FFFFFFFFh would also not cause a fault?
If I set the limit to FFFEh on a 286 and FFFFEFFFh though (granularity for a 32-bit segment is 4KB), it should fault for FFFFh and WORD read at FFFEh, and WORD/DWORD at FFFFF000h.
Looks like DOSLIB is going to get some new test code.
I've updated DOSLIB's "flat real mode" library to include a program that tests various memory addresses vs various 32-bit/16-bit segment limits.
I'm at work, so I don't have the old hardware to test against right now, but VirtualBox (with and without VT-x enabled) seems to indicate that the overflow I described does not apply, segment limits checks always occur, BUT segment limit checks are disabled entirely if the limit is FFFFFFFF. Meaning, a segment limit of FFFFFFFF does not throw exceptions if you WORD/DWORD read at FFFFFFFF. But if a segment limit of FFFFEFFF (4GB - 4KB) is in effect and will fault a DWORD read at FFFFEFFD (because FFFFEFFD + 4 - 1 = FFFFF000, and FFFFF000 > FFFFEFFF). This is on a quad core Intel laptop (Intel Core i7-1165G7).
I also corrected a strange bug in the segment limit check in GetEADirect() that would fault a DWORD read at FFFFFFFC even though logically that is within range. Typecasting solved it.
Apparently Vette! relies heavily on the divide overflow exception handler for it's math. The handler reads the return address from the stack and reads the first opcode byte that the CPU will return to after IRET. If the opcode byte is 0xC6 or 0xC7 it adds 2 to the offset (instruction pointer) to skip over the DIV instruction, otherwise it doesn't modify it. It then emits an outp(0x20,0x20) (to clear pending interrupts??) and sets AX=0x7FFF so that the result of the divide is clipped to the maximum value.
So that means the game was written for the 8086, then the divide handler was updated to "fix" it for the 80186 and 286.
Ewwwww...... :nauseated_face:
It means that likely whatever causes the game to fill the screen with a solid color is probably something to do with the divide instruction.
The latest commit by the way fixes a few bugs with segment limit checks. According to some real modern Intel hardware, a segment limit of FFFFFFFF effectively disables all segment limit checks, so it follows that a segment limit of FFFF on a 286 does the same, since that is the segment limit in real mode. That fixes the crashes, the only remaining issue is the filling of the screen with a solid color.
I guess @Allofich was right, the extra checks in IDIV were not necessary, and in fact were causing overflow errors in Vette! Removing them fixed the bug with filling the screen with a solid color, even for cputype=80186 and cputype=286.
Also if the game detects a 286 it will enable the "rear view mirror" in the corner of the screen, apparently.
I am checking to make sure that Microsoft Flight Simulator 1.0 (the original need to fix IDIV for 8086 emulation) isn't broken by the change.
EDIT: Yup, no problems.
I guess @Allofich was right, the extra checks in IDIV were not necessary, and in fact were causing overflow errors in Vette! Removing them fixed the bug with filling the screen with a solid color, even for cputype=80186 and cputype=286.
Also if the game detects a 286 it will enable the "rear view mirror" in the corner of the screen, apparently.
Perhaps the game has some CPU type detection mechanism. On page 35 of the manual there is this matrix:
Is there a separate executable for CGA? The copy I found has only EGA, and the EGA version assumes the EGA mode is available and will draw junk on the screen if you run it with machine=cga.
I read that there were both CGA and EGA versions in the game box on different floppy disks. Here are the ones I found in the Total DOS Collection #17: Vette! v1.01 (CGA) (1989)(Spectrum HoloByte, Inc.) [Racing - Driving, Simulation].zip Vette! v1.01 (EGA) (1989)(Spectrum HoloByte, Inc.) [Racing - Driving, Simulation].zip Vette! v1.1 (CGA-Herc) (1989)(Spectrum HoloByte, Inc.) [Racing - Driving, Simulation].zip Vette! v1.1 (EGA) (1989)(Spectrum HoloByte, Inc.) [Racing - Driving, Simulation].zip Vette! v1.11 (1989)(Spectrum HoloByte, Inc.) [Racing - Driving, Simulation].zip Vette! (1989)(Spectrum HoloByte, Inc.) [Racing - Driving, Simulation].zip Vette! v1.01 (1989)(Spectrum HoloByte, Inc.) [Racing - Driving, Simulation].zip
Extracted versions found in The Good Old Days collection of floppy images v1.0-CGA.zip v1.02-CGA.zip v1.02-EGA.zip v1.11-EGA.zip
Fixed in release 0.83.21. Thank you.
Code of Conduct & Contributing Guidelines
Have you checked that no other similar bug report(s) already exists?
What operating system(s) this bug have occurred on?
Windows 10 Pro 21H2 64 bit
What version(s) of DOSBox-X have this bug?
0.83.20 SDL2 64 bit VSbuild
Describe the bug
The EGA version of Vette! game crashes shortly after the start of the race. The graphics get corrupted and the following message appears in the log: Segment limit violation - Limit check ffff+2-1 = 10000 > ffff ES:DI
I also tried to launch the game with LOADFIX, reduce the memsize parameter and disable the extended memory with xms = false.
Expected behavior
In DOSBox 0.74-3 the game seems to work correctly.
Steps to reproduce the behaviour
Used configuration
Emulator log
Additional context
No response