August 26th, 2013, 22:12 Posted By: wraggster
For the past week I have been working on implementing various missing features and fixing various bugs in zerox86. I also got a new 4GB SDHC card where I could fit all my DOS test programs (of which I have about 3GB's worth). I put that card into my GCW-Zero and thus now have easy access to all my DOS test programs. I then began to go thru my test programs (mostly alphabetically) and checking the problems in them.
Duke Nukem 2
I had left the Sound Blaster ADPCM sound emulation so far unimplemented, so I started my improvements by implementing that. It took a couple of days, mostly because I had all sorts of other activities after work during the first half of the last week. I ported the ADPCM code from the rpix86 version, converting the ASM code from ARM to MIPS. It now plays something at least resembling the correct audio, and since very few programs actually use ADPCM audio, I think it is now sufficient. Duke Nukem 2 actually uses all three ADPCM formats, 4-bit, 2-bit and also the slightly weird 2.6-bit (8 divided by 3) version. Thus it is simple to test the ADPCM code using a single game.
Heimdall is one of the few games that read data directly from disk to graphics VRAM. Jazz Jackrabbit does that into Mode-X VRAM, while Heimdall uses EGA 16-color graphics mode. I had thought that this was already working, but I decided to test it just to be sure. To my surprise it did not work, and after a little bit of debugging I realized that my EGA version did not restore the GP register properly when calling from C routine to my ASM routine.
Back when coding DS2x86 I noticed that the DSTwo firmware did not use the GP (global pointer) register for anything. MIPS convention is to use the GP register as a pointer into the middle of a 64KB "near data" block. This is because MIPS has very simple memory addressing system, you can only use a register with a 16-bit signed offset to address memory. By having the GP register always pointing to the most commonly used data area, you can avoid calculating memory addresses into temporary registers most of the time. Thus, in my DS2x86 ASM code I used the GP register to point into my data area, and also made the actual emulation address calculations take advantage of the GP register pointing to a certain address which was also aligned in a certain way.
In the GCW-Zero environment, however, all the libraries are linked in a way that has the linker decide the suitable value for the GP register, which differs from my needs. It would have slowed my emulation down quite a bit if I would have had to change my code to not rely on the GP register value, so I decided to let the C code use the linker's GP value, and have my ASM code use different value that suits my purposes better. In my ASM code I load the GP register with the value I want after every call to or return from C code (which does not happen all that often, luckily). For the linker to allow that, I needed to have my ASM code use the -mno-abicalls compiler flag. This will make the linker give a lot of linking abicalls files with non-abicalls files warnings, but as long as I remember to handle the differing GP register values this does not seem to cause any problems. Not using ABI calls also has the advantage that I can call my own ASM subroutines using the simple JAL address (jump and link) opcode, instead of the complex way that the C code uses by first calculating the target address into t9 register and finally calling the address using JALR t9 (jump and link register) opcode.
After those fixes, I then began going thru my games alphabetically. The first game I tested was Mahjong Fantasia, which for some reason I have in a directory called 98mj2. It is the only game I have encountered that goes to the EGA 640x200 mode, and then changes the EGA registers so that the actual graphics screen goes to 640x400 resolution. I noticed that I had not taken this register change into account in my EGA graphics blitting routines, and fixed that problem. This made the game start up and look correct.
The next game I tested was A-Train. It seemed to work fine, except that the main game screen was mostly monochrome. I thought that the problem seemed somehow familiar, so I went through my old DSx86 blog posts, and found a DSx86 blog June 27th, 2010 entry where I fixed the exact same problem in DSx86. The problem was the EGA Register Interface Library handling. I checked my code in zerox86, and realized that when I had copied the C code from rpix86, I had not changed the call parameters to match the way my MIPS ASM code differs from the ARM ASM code. I fixed the parameters, and the game began to look correct.
Next I checked Alien Legacy. It seemed to hang with a black screen, which has usually been a difficult situation to track down. I can attach the gdb debugger to the running zerox86 process (which is a huge step forward from the DS2x86 times), but since it is much more likely that the x86 code is the one running in a loop instead of my emulation code, stopping the emulation code does not get me very close to the root problem. Since I am already using signals to handle various interrupts in my code, I thought that it might be a good idea to have a signal that would print a disassembly of the currently executing x86 code to the standard output. This way I could send that signal repeatedly to my zerox86, and see if the x86 code is running in a tight loop.
The problem with this in zerox86 is that my emulation code keeps the x86 registers in MIPS registers and not in any memory address, so when the code gets the signal, the x86 registers (including the program counter) are not in any variable that I could simply examine. But since the signal handler needs to return to the code that was running at the time of the signal, all the register values need to have been pushed on the stack.
Luckily I already had a memory variable that tells the stack pointer value of my emulation core, so I began experimenting with this by coding a SIGUSR1 handler that simply printed the current stack pointer value and my emulation stack pointer value. The difference in Alien Legacy hang situation seemed to be a bit over 700 bytes, which means around 175 words had been pushed on the stack. The problem was just to find where in that area the signal handler has pushed which register.
I added code into my signal handler to print out all the stack values, and then used gdb to break when the signal handler is started, and then again when the code returns back to my emulation loop. Many of the register values only appeared one time in the stack, so using those addresses together with the known GP register value (which the signal handler also needs to push into stack) I was able to make an educated guess as to how the registers are pushed. I added code to my signal handler that looks for the GP value, and gets the register values from the certain offsets in the stack relative to the GP value. These register values are then saved into the memory variables that my disassembly routine uses. After having my signal handler print the disassembly, I managed to find out where Alien Legacy hangs:
0180:001B03DA D9F8 fprem
0180:001B03DC 9B fwait
0180:001B03DD DFE0 fstsw ax
0180:001B03DF 66A90004 test ax,0400
0180:001B03E3 75F5 jne 001B03DA ($-b)
Alien legacy uses the floating point fprem (remainder) opcode, and then checks the floating point flags to see if the C2 flag (value 0x400) is clear (meaning the remainder operation was finished), and runs the remainder again if not. I had not coded proper FPU support into zerox86 yet, so this test always failed causing a never-ending loop.
So, I decided to finally start implementing the FPU operations, as it has been on my TODO list for a while. Since the MIPS processor in the GCW-Zero has proper hardware floating point support, I needed to look into how the floating point parameters are handled in function calls and so on. After some studying I realized that the floating point parameter passing convention is pretty easy to understand and suits my existing FPU emulation framework quite easily, so it only took a couple of hours to implement the missing floating point operations. After that Alien Legacy progressed to the main game screen quite fine. It seems to need mouse, though, so I think I need to implement mouse emulation next.
Thanks for your interest again, I will continue working on zerox86, adding the missing features and testing various games. There are still a lot of misbehaving games that I need to debug, but I hope at least some more games (including the ones mentioned above) will run better in the next version.
For more information and downloads, click here!
There are 0 comments - Join In and Discuss Here