I've just retried Jazz Jackrabbit on the current release(I've only improved a few things since the last release yesterday, mainly single-step exception faulting better by a little bit(saving registers properly instead of resetting them to pre-instruction state incorrectly), saving CPL and changed segment descriptor cache for restoration on faults(previously only the segment selectors were restored), modifying the TR and it's cache to do the same as the SS, DS,ES,FS and GS descriptors(just use the same pool buffer), small OPL2 phase conversion warning fix and finally some PIQ filling optimization during IPS clocking mode(using less memory checks, as they're now checked(up to 3 memory checks instead of the old up to PIQ size memory checks(e.g. 16)) before fetching as much as can be fetched into the PIQ in said clocking mode only(a full PIQ should suffice for any instruction that the 80486 can handle(more than the maximum PIQ buffer anyway, except during redundant prefixes, in which case the EU will fault when fetching them, which triggers a PIQ refetch anyways).
The entire game seems to work properly, except the text input for inputting the save game (file)name. When inputting said text, it seems to either have some weird key input-based delay(you need to press x more keys before the first keys inputted are shifted into the field) or perhaps some issue that has something to do with it only seeing keys that are different from the previous key inputted somehow? MS-DOS works just fine(when inputting text at least and other required functionality too afaik), so why would Jazz Jackrabbit mess up the key input part? Does it do something special with the keyboard controller, it's key buffer or some internal processing of keys that were inputted? Oddly enough, gameplay seems to be unaffected by said bug?
Hmm, I also had the delayed text input issue at one point. Not sure if it's related, but it went away after I'd rewritten my keyboard controller emulation with the serial timing (so reading the port twice would give the same value, until the next scancode is filled).
Well, UniPCemu already does that? Reading an empty 8042 buffer(poft 0x60) gives the last value read until a new byte is received.
Receiving takes 11 clocks, sending takes 12 clocks(sending has priority).
Sending and receiving is checked each clock. It simply sums 1 to the total clocks each time. When the counter is 11 or higher, 11 is substracted for receiving or 12 for sending. Then counting up resumes like earlier.
When the output buffer is filled, nothing new is received(and the counter keeps filling).
1void update8042(DOUBLE timepassed) //Update 8042 input/output timings! 2{ 3 uint_64 clocks; //Clocks to tick! 4 //Sending costs 12 clocks(1 start bit, 8 data bits, 1 parity bit, 1 stop bit, 1 ACN bit), Receiving costs 11 clocks(1 start bit, 8 data bits, 1 parity bit, 1 stop bit) 5 timing8042 += timepassed; //For ticking! 6 clocks = (uint_64)SAFEDIV(timing8042,timing8042_tick); //How much to tick in whole ticks! 7 timing8042 -= (clocks*timing8042_tick); //Substract the clocks we tick! 8 9 //Now clocks contains the amount of clocks we're to tick! First clock input to the device(sending data to the device has higher priority), then output from the device! 10 clocks8042 += clocks; //Add the clocks we're to tick to the clocks to tick to get the new amount of clocks passed! 11 byte outputpending, inputpending, outputprocessed, inputprocessed; 12 outputpending = (Controller8042.status_buffer & 2); //Output pending to be sent? Takes 12 clocks 13 inputpending = ((Controller8042.status_buffer & 1) == 0); //Input pending to be received? Takes 11 clocks 14 outputprocessed = inputprocessed = 0; //Default: not processed! 15 //Information about the clocks can be found at: http://halicery.com/8042/8042_INTERN_TXT.htm 16 for (;(((clocks8042>=11) && inputpending) || ((clocks8042>=12) && outputpending));) //Enough to tick at at least once? 17 { 18 if (outputpending && (outputprocessed==0)) //Output buffer is full? 19 { 20 if (clocks8042 >= 12) //Are enough clocks ready to send? 21 { 22 if (Controller8042.WritePending) //Write(Input buffer) is pending? 23 { 24 Controller8042.status_buffer &= ~0x2; //Cleared input buffer! 25 if (Controller8042.WritePending==3) //To 8042 command? 26 { 27 Controller8042.WritePending = 0; //Not pending anymore! 28 if (is_XT==0) //We're an AT? 29 { 30 #ifdef LOG8042 31 if (force8042 == 0) //Not forced for initialization? 32 { 33 dolog("8042", "Write port 0x64: %02X", value); 34 } 35 #endif 36 Controller8042.status_high = 0; //Disable high status, we're writing a new command! 37 Controller8042.command = Controller8042.input_buffer; //Set command! 38 commandwritten_8042(); //Written handler! 39 } 40 } 41 else if (Controller8042.WritePending==4) //To first PS/2 Output? 42 { 43 Controller8042.WritePending = 0; //Not pending anymore! 44 Controller8042.output_buffer = Controller8042.input_buffer; //Input to output! 45 Controller8042.status_buffer |= 0x1; //Set output buffer full! 46 Controller8042.status_buffer &= ~0x20; //Clear AUX bit! 47 if (PS2_FIRSTPORTINTERRUPTENABLED(Controller8042)) 48 { 49 lowerirq(12); //Remove the mouse IRQ! 50 acnowledgeIRQrequest(12); //Acnowledge! 51 lowerirq(1); //Remove the keyboard IRQ! 52 raiseirq(1); //Call the interrupt if neccesary! 53 } 54 goto finishwrite; //Abort normal process! 55 } 56 else if (Controller8042.WritePending==5) //To second PS/2 Output? 57 { 58 Controller8042.WritePending = 0; //Not pending anymore! 59 Controller8042.output_buffer = Controller8042.input_buffer; //Input to output! 60 Controller8042.status_buffer |= 0x1; //Set output buffer full!
…Show last 95 lines
61 Controller8042.status_buffer |= 0x20; //Set AUX bit! 62 if (PS2_SECONDPORTINTERRUPTENABLED(Controller8042)) 63 { 64 lowerirq(1); //Remove the keyboard IRQ! 65 acnowledgeIRQrequest(1); //Acnowledge! 66 lowerirq(12); //Remove the mouse IRQ! 67 raiseirq(12); //Call the interrupt if neccesary! 68 } 69 goto finishwrite; //Abort normal process! 70 } 71 else 72 { 73 if (Controller8042.inputtingsecurity) //Inputting security string? 74 { 75 Controller8042.securitychecksum += Controller8042.input_buffer; //Add to the value! 76 if (Controller8042.input_buffer==0) 77 { 78 Controller8042.inputtingsecurity = 0; //Finished inputting? 79 Controller8042.securitykey = Controller8042.securitychecksum; //Set the new security key! 80 } 81 goto finishwrite; //Don't process normally! 82 } 83 if (Controller8042.writeoutputport) //Write the output port? 84 { 85 Controller8042.outputport = Controller8042.input_buffer; //Write the output port directly! 86 refresh_outputport(); //Handle the new output port! 87 Controller8042.writeoutputport = 0; //Not anymore! 88 goto finishwrite; //Don't process normally! 89 } 90 if (Controller8042.Write_RAM) //Write to VRAM byte? 91 { 92 if (Controller8042.Write_RAM == 1) //Might require enabling the ports? 93 { 94 if (((Controller8042.data[0]&0x10)==0x10) && ((Controller8042.input_buffer&0x10)==0)) //Was disabled and is enabled? 95 { 96 if (Controller8042.portenabledhandler[0]) //Enabled handler? 97 { 98 Controller8042.portenabledhandler[0](2); //Handle the hardware being turned on by it resetting! 99 } 100 } 101 else if (((Controller8042.data[0] & 0x10) == 0x00) && ((Controller8042.input_buffer & 0x10) == 0x10)) //Was enabled and is disabled? 102 { 103 if (Controller8042.portenabledhandler[0]) //Enabled handler? 104 { 105 Controller8042.portenabledhandler[0](0x82); //Handle the hardware being turned on by it resetting! 106 } 107 } 108 if (((Controller8042.data[0]&0x20)==0x20) && ((Controller8042.input_buffer&0x20)==0)) //Was disabled and is enabled? 109 { 110 if (Controller8042.portenabledhandler[1]) //Enabled handler? 111 { 112 Controller8042.portenabledhandler[1](2); //Handle the hardware being turned on by it resetting! 113 } 114 } 115 else if (((Controller8042.data[0] & 0x20) == 0x00) && ((Controller8042.input_buffer & 0x20) == 0x20)) //Was enabled and is disabled? 116 { 117 if (Controller8042.portenabledhandler[1]) //Enabled handler? 118 { 119 Controller8042.portenabledhandler[1](0x82); //Handle the hardware being turned on by it resetting! 120 } 121 } 122 } 123 124 Controller8042.RAM[Controller8042.Write_RAM-1] = Controller8042.input_buffer; //Set data in RAM! 125 Controller8042.Write_RAM = 0; //Not anymore! 126 goto finishwrite; //Don't process normally! 127 } 128 Controller8042.portwrite[Controller8042.WritePending-1](Controller8042.input_buffer); //Write data to the specified port! 129 finishwrite: //Not a normal hardware write? 130 Controller8042.WritePending = 0; //Not pending anymore! 131 } 132 clocks8042 -= 12; //Substract the pending data, because we're now processed and sent completely! 133 } 134 } 135 outputprocessed = 1; //We're processed! 136 } 137 else if ((inputpending) && (inputprocessed==0)) //Output buffer is empty? 138 { 139 if (clocks8042 >= 11) //Are enough clocks ready to receive? 140 { 141 if (fill8042_output_buffer(1)) //Have we received something? 142 { 143 clocks8042 -= 11; //Substract from our clocks, because we've received something! 144 } 145 } 146 inputprocessed = 1; //We're processed! 147 } 148 else if ((outputprocessed || (outputpending==0)) && ((inputprocessed) || (inputpending==0))) //Nothing to be done? Both have been checked! 149 break; //Stop: 150 } 151 if (!((inputpending&~inputprocessed)|(outputpending&~outputprocessed))) //Input and output isn't pending? 152 { 153 clocks8042 = 0; //Start counting again! Reset out sending/receiving! 154 } 155}
The connected hardware response itself(keyboard and mouse) are instantaneous when sending/receiving to(fill8042_output_buffer for receiving from peripheral and Controller8042.portwrite[port](byte val) for sending to keyboard/mouse). The only timing they have is some basic response time and packet timeout(mouse) and key rate/delay(keyboard). Buffers aren't filled when they don't have enough space, the state of the input becomes pending in that case(and will be handled once there is enough room).
The signal(the first few rows of the function handle that, where it determines and adds to clocks8042) is ticking at 16.7kHz.
Any games that you know of requiring all 80286 protected mode functionality to work and run properly? So proper interrupts, task switching, gates, segmentation etc.? So essentially everything that's not 32-bit and paging?
Edit: A bit of progress on Windows 3.0. It's finally running in Standard mode! 😁
It has the exact same problem as Windows 3.1 regarding MS-DOS applications being run in that mode.
Everything went fine(ran notepad, clipboard, program manager, setup in parallel perfectly fine, using alt+tab to switch between them.
Now when I try to run the "MS-DOS prompt" shortcut, I see a full-screen MS-DOS 6.22 command.com being ran to a MS-DOS prompt. Then, typing "dir" just shows "r" being typed, then the keyboard is unresponsive(kind of like Windows 3.1 in Standard mode). 3.11 fully crashes to a blinking cursor on black background in top-left corner.
So, there's some real mode or keyboard issue still left somehow? Anyone?
Edit: Windows 3.0 and 3.1 behave the same regarding to MS-DOS applications, except 3.0 seems to have taken a bit of input before not accepting any input anymore. At least in Standard mode, this is the case.
Edit: Just tried Windows 3.0 in Real mode. Everything runs fine as well, until I start the MS-DOS prompt with the shortcut. Then the same issue as with Protected mode occurs. Typing "dir" a few times makes an "r" being typed in the MS-DOS prompt. No other input seems to get through, not even Alt-Tab.
So all modes(3.0 in real and Standard modes, 3.1 in Standard mode) run fine until a MS-DOS application is ran. Windows 3.0 types "r" when typing "dir" and then doesn't respond to keyboard input anymore. Windows 3.1 doesn't repond for any keyboard input.
This happens on UniPCemu's 80386 emulation(80386DX in IPS clocking mode at 3 MIPS speed).
Managed to fix two PS/2 keyboard bugs(command F3(set key delay/rate) not being handled properly since emulating at cycle accuracy and the 8042 output buffer being filled with the escape F0 part of the scancode without setting status bit 0, being changed to fully handled properly again and leaving the last value of the output buffer in the output register until it's fully received and decoded(so F0xx->yy instead of F0xx->F0yy(F0 not setting status bit 0).
Hmmmm... Since Windows 3.x in real and standard mode exhibit the same issue with the PS/2 keyboard, and the PS/2 keyboard is working properly(generating XT scancodes properly) and the MS-DOS prompt doesn't see it in that case, that would mean there's a real mode cpu problem? And since it's built for 8086+, the issue must lie somewhere in the 8086+ opcode emulation?
Edit: Now that the 8042 is properly fixed, as well as the PS/2 keyboard's missing command, now keyboard input works properly for all cases that were going wrong(Jazz Jackrabit, Windows 3.x ) 😁
So it's back to finding bugs in the CPU now. Primarily 80386-related bugs afaik. Didn't find any known bugs with my 80286+ software so far.
Just retried Windows 3.0a with HIMEM and EMM386 from the Windows folder loaded, running it with the /3 switch for 386 Enhanced mode. It seems to hang/crash on a HLT at 0000:0000, previous opcode being at 0048:0868?
Just reinstalled Windows 3.0a and fixed the config.sys drivers to use it properly.
Now, running Windows 3.0 in 386 enhanced mode(with himem.sys and emm386.sys loaded(emm386 reporting 256k memory)), EMM386 crashes with a "EMM386 Exception error #08 - press ENTER to reboot".
So a double fault occurs for some odd reason?
Edit: Either a double fault or timer IRQ0?
Edit: Just found a bug in the addressing of a 32-bit BT instruction(using the same ModR/M offset addition as a 16-bit BT instruction(DIV 16 times 2) instead of it's proper DIV 32 times 4 addition).
Edit: Having fixed those, I no longer see Page faults from Windows 3.0 booting in 386 enhanced mode, but I do see an INT 41h from privilege level 1(which fails due to not having enough rights for the PL0 interrupt gate), after which I eventually see something trying to use the DS segment, which is pointing at a privilege level 1 segment(the same as the original INT 41h fault handler) with only 1 byte of valid space(thus limited to only byte #0, according to the segment descriptor)?
Edit: That INT 41h seems to be correct, according to the logging I have of Windows 3.0 booting in 386 enhanced mode. I see some STI and CLI instructions faulting, which seems normal to me. Then I see DS being loaded with value 0x17D(which has a limit of 0, thus only 1 byte large)? That seems to be causing further trouble down the road, after faulting to kernel mode.
It happens at 0105:56DD. Somehow, I don't think that's supposed to happen when booting Windows 3.1 in 386 enhanced mode?
Edit: It seems I'm right. It doesn't appear anywhere in the win30_3 log file.
Edit: So the problem is somewhere in the program or driver at segment 0105h.
So it's probably something starting at 0105:04CD in protected mode?
Edit: Hmmmm.... Looking for the very first instruction of that segment, it's at 105:7c58 in UniPCemu. The previous opcode seems to have been at a5:7c57? The log also doesn't mention segment a5, but immediately before the jump to segment 105h, it's at segment 05a5 instead?
Edit: I see it diverging paths at 0028:80006c7e, which is a 0xFF opcode JMP to the dword location incorrectly?
This is a log of UniPCemu booting Windows 3.0 in enhanced mode, with MS-DOS 6.22 and it's HIMEM.SYS, Windows' EMM386.SYS(~300KB EMS memory?) and failing drive preload program(can't remember it's name) from the Windows directory(which says invalid MS-DOS version?).
I've executed win without parameters(thus defaulting to 386 enhanced mode).
It looks like SDL2 refuses to somehow go past 4GB?
Edit: Managed to fix a file I/O bug in the common emulator framework. It seems to be a SDL2 RWops problem there(it's erroring out on writes to files that are opened with appending rights). The solution was simple: replace the "ab" flags with "rb+"/"r+b" rights and simply seek to EOF for every write in the wrapper. That has the same effect as appending properly with the "ab" rights, while being able to properly append to the file.
Edit: Perhaps the EBX is invalid to start with? It's FCE4 in win30_3.txt.
Edit: The troube already seems to be there with ESI being a different value at the start of the protected-mode code. It seems to be incorrect in real mode already? So the problem is in the real-mode part?
Just improved the handling of the setting of the Accessed bit of the segment descriptor(only when a descriptor reaches a segment's descriptor cache).
Interestingly enough, Windows 95 now seems to completely hang due to infinite ARPL faults being thrown(or what looks like them anyways)? It's opcode 632D at location C4D8:9a9a.
Anyone knows what that one is?
Edit: Interestingly, the Windows 95 bootlog now becomes very very short:
Since all loading of VXD drivers goes fine until after IFSMGR.VXD(Installable file system and block device manager) loads, the issue must be in IFSMGR.VXD?
Edit: Looking again, I see it triple faulting on trying to raise an interrupt(stack fault) pushing BP on the stack with ESP being 1. Thus SP becomes FFFF for the memory checks, which causes a triple fault in real mode(as is documented)! Then the computer(emulator) reboots.
Edit: Oddly enough, even reverting the code to an older commit(since before I made the latest changes) makes Windows 95 setup crash at that point?
CWSDPMI with engine crashes with a double fault. DPMI32 triple faults and resets the emulator when engine is ran immediately when it(engine)'s ran.
Edit: Reinstalling Windows 95 it loads up to the second ESDI_506.PDR file again(using a normal boot)?
I did see something interesting while Windows 95(a) setup was starting up it's GUI(at least until displaying the welcome to setup screen): it keeps trying to throwing #NP faults for a RETF to segment E0h? Anyone knows anything about that? It doesn't seem to stop throwing them at some point, they just keep coming?
Just tried running Privateer again. It still hangs, but I've noticed something: It's stuck in an infinite (32- bit V86 instructions, except the conditional jump back to the start of the loop) comparision loop comparing DS:[ESI] with some memory location, jumping to the start again when carry isn't set(which it never is). Interrupt flag is cleared, so infinite loop.
Edit: I believe it was at EIP 0x1FC?
Just tried running Windows for Workgroups 3.11 again. It seems to boot successfully, The MS-DOS prompt seems to run quite well, both full screen and windowed 😁
It just went wrong(double fault) when it tried to execute a far call in Virtual 8086 mode(opcode FF), which tried to push data on the stack that wasn't paged in properly?
Edit: Just managed to run Ultima II within a MS-DOS prompt inside a window of Windows 3.11. Then I tried to run Alley Cat inside a second MS-DOS window. Then Windows seems to hang?
Edit: Alley Cat seems to run fine with the minimal config.sys configuration(no drivers) as well as with EMM386(from MS-DOS 6.22) loaded. So the issue is somewhere within Windows 3.11?
Edit: With the 3.11 config.sys and not booted Windows 3.11 it also runs fine, without problems.
Edit: OK. Alley cat is running fine within Windows 3.11. Now to start Ultima II as well(multitasking)...
Edit: Seems to run fine now. It just has the issue it can't terminate, since the Q command won't have effect(It just says: "CMD: Quit or Save (...)"?
Edit: Luckily, Within Windows, pressing Ctrl+Alt+Del twice gives a blue screen which allows the application to be terminated(although incorrectly) 😁
So, Windows for Workgroups 3.11 running without visible errors(both Windows and MS-DOS prompt inside it running) must mean that at least most problems have been solved, if not all of them?
Just tried running Pink Panther: Hocus Pocus Pink inside Windows 3.11. It ran fine until I tried the Sound Test from it's Windows menus, which eventually causes a triple fault during a ADD instruction at logical memory location 80000FFC(it's location on the stack that faults)? It tries to push data on the stack, but the stack is invalid, page faulting again on that, causing a triple fault because of said reason.
Edit: Thinking about it, isn't the high zero page(page number 0x80000) deliberately unmapped, due to processor issues with said memory location incorrectly aliasing to the zeroth page(page 0x00000) on some buggy processors? So perhaps that instruction itself or just the stack is the cause(invalid kernel stack)?
Edit: The problem happens in the kernel at 0x80006ec6. The stack is 80010004, which reaches the invalid area during the page fault, which itself faults on that handling the page fault, thus double faulting and triple faulting because of the invalid stack pointer?
Edit: So the kernel stack is having an overflow. That isn't supposed to happen normally, according to my knowledge? Anybody has an idea what the cause could be? ESP is 80001004 at the time of the triple fault(during it's triggering instruction).
Just gave all Windows versions but Windows for Workgroups 3.1 a go. All of them at least seems to install properly 😁 Although I did notice that the 3.1/3.11 versions (both normal and for Workgroups) seems to take a long time checking the harddisk after analyzing the hardware. Maybe that's just because the C drive is large(2GB)?
It at least is supposed to provide a new base(after MS-DOS) for testing the hardware(since 95 still doesn't run).
Is there any software that tests win32s functionality and checks for any errors in that(seeing as I noticed that component failing combined with 3.11)? Or perhaps some CPU diagnostics?
Edit: Just took a look at AMIDIAG 5.0 again. Ran the CPU Protected Mode Test again(which was previously failing on the LAR instruction(as it said what was failing)). It now no longer reports any errors there and says:
A20 LINE: OK
Protected Mode Instructions: OK
So that's some bugs that were already fixed 😁
Now, what more can I use to test and verify my emulation? Windows 3.11 software is now an option besides MS-DOS 6.22. Anyone got any ideas?
Isn't Windows 3.x supposed to have a TSS for the double fault handling? Since the double fault doesn't cause a task switch(it's a normal interrupt or trap gate), it tries to push information about the fault on the stack, which isn't reloaded(again, because SS:[ESP-4] from the kernel is invalid due to unpaging the zero page due to CPU issues), thus again causing a page fault which becomes a triple fault instead.
So why doesn't the win32s application(compared to MS-DOS apps) have a double fault task gate installed?
Edit: Perhaps the kernel isn't supposed to double fault at all?
Edit: Hmmm... When I answer Yes to the question to change the display resolution to 640x480(it's at 800x600), it faults some times. Eventually, it starts faulting on address x06f40074(opcode 3b4104), which is cmp eax,dword [ecx+04].
That one is recursively faulting, keeps lowering the stack address until eventually double faulting and maybe even triple faulting... Now checking it out... Said fault is occurring on memory location 80007266.
Edit: Interestingly, changing the breakpoint to fault at locations below or at 80000FFF, resolves in the breakpoint triggering on 80006ec6(sounds familiar?), executing instruction 0x0080dc6e0080. That is ADD byte [eax-7fff9124],al. That fault it causes at address 967f7b74 causes a page fault, which causes a page fault on it's stack, that causes a double fault to 0028:80006e78, which uses a normal interrupt gate, which tries to use the same invalid stack, thus triple faulting the CPU.
The "change screen resolution?" option always fails even on real hardware.
Change it to 640x480 256 colors yourself before trying to run it.
Also, you can experiment with having SHARE.EXE loaded or not loaded, and various parameters thereof. You can also try messing with the Windows virtual memory settings.