DJGPP DOS GAME PROGRAMMERS FAQ V1.0 ___________________________________ The intention of this FAQ is to provide the basic technical information required to do all the good stuff that you can do with other DOS based compilers. It shows how to access most hardware such as video, sound, controllers, timers, interrupts under various OS's like DOS, Windows, OS/2. (though in a DOS session) This FAQ does not show you how to write games, nor is it a library of functions, though it will hopefully provide enough information for most people with programming experience to convert one of the many existing libraries available for other compilers. I am open to corrections, improvements and expansion of this FAQ. A lot of this information has come from my own experimentation with the compiler and hardware. I hope you find it useful. Let me know how y'all get on! S.Hull@bradford.ac.uk Stu Hull. _______________________________________________________________________ Q. What is DGJPP? DGJPP is a popular port of GNU C by DJ Delorie. It is a full 32-bit compiler with DOS extender with a flat memory model, and can access 128Mb of physical and 128Mb of Virtual (Disk swapped) memory. It is distributed under the GNU Licence agreement. It currently has no development environment (provide your own text editors!) and only a basic debugger, these aside it is a very competent compiler. Q. Where can I get DJGPP? Any SimTel site. Try oak.oakland.edu:/pub/msdos/djgpp. Pull the faq in the directory which will explain the minimum files required, and the basics of operation. Q. How do I install DJGPP? Create a directory (example C:\GCC) and unzip the files required (don't forget the -d switch to build directories), then add the following statement to your autoexec file: SET DJGPP=C:\GCC\DJGPP.ENV and add C:\GCC\BIN to your PATH statement. Q. Can I distribute programs compiled with DJGPP? That would appear to depend upon which libraries you use. As far as I understand it none of the following examples use library code that cannot be distributed, and go32 is public domain, but please check before releasing. If you are making a commercial release it is suggested you make an appropriate donation to the developers. Q. How do I compile/link/include libraries? Briefly, without going into things like makefiles, there are two steps, compile into object (COFF format) and link. A few examples follow: To compile and link (to 'a.out'): gcc [options] {sourcename} [-l{libname}] Compile (to object): gcc [options] {sourcename} -o {objectname} Link (to 'a.out'): ld \gcc\lib\crt0.o -L\gcc\lib {objectname(s)} -lgcc -lc [-l{libname}] Useful compile options include: -Wall All warning messages displayed. -m486 Optimise for 486. Q. How do I execute ('a.out')? go32 a.out [arguments] Q. How do I start the debugger? # go32 -d edebug32 a.out [arguments] (or use ed32-dpmi if running under Windows/OS2 etc). Q. How do I use the inline assembler? As you may know, the format used by the GNU assembler differs from what Intel uses. Inline assembly is encapsulated with 'asm("...")' inside your c code. Follow these rules and you'll be OK: Typical format is: {instruction} {source}, {dest} (NOT {dest}, {source}!) Each instruction is suffixed with the operation size (though this can also be assumed from the register size) such as 'b' for byte, 'w' for word, 'l' for long word. Registers are prefixed with a '%'. Numbers are prefixed with '$' and assumed to be decimal unless they are of the '0x' format. Indirect addressing is done by surrounding the term with brackets (eg '(%esi)', or '%es:(%edi)' with segment prefix). Variables are prefixed with _ (underscore) though they cannot be local to the procedure (ie they must be defined globally). To access external global variables, you must also surround the name with brackets to access it indirectly. Instructions can be separated with newline or ';'. Examples: asm(" pushw %es movw (_DosSeg), %es movl _offset, %edi movl $4, %eax movb %al, %es:(%edi) rep ; movsl moveloop: decl %ecx jnz memloop "); Q. Why are segments registers still used with a flat memory model? Without going into too much detail about protected mode environments, there are no segments as such, but selectors, although they can be treated in a similar fashion. When running in protected mode DOS memory is assigned a selector, and your code another. This means that no matter what address your code tries to access it will not interfere with the DOS memory, or any other memory it hasn't been given access to. This is the mechanism used by a 32-bit OS preventing one from accessing another. This creates problems when accessing Video memory in the DOS segment. 'go32' will try to re-map some area's of memory to appear as the DOS memory, though under a 32-bit OS it cannot do this. There are ways round this though which I will deal with later. Q. How do I select graphics modes? This is the same as real mode, for example to set mode 13h (include ): union REGS in, out; in.x.ax = 0x0013; int86(0x10, &in, &out); Q. How to I set mode-x? Again, this is the same as for real mode. [Set mode 13h, as above] /* Unchain */ outportw(0x3c4, 0x0604); outportw(0x3d4, 0xe317); outportw(0x3d4, 0x0014); /* Enable all planes */ outportw(0x3c4, 0x0f02); Q. How is DOS/video memory accessed? You could use one of the graphics libraries provided which are intended to make it easier to port code written for other systems, unfortunately they do not work in a protected mode environment (Such as Windows and OS2) and are slow. Your code does not automatically have access to DOS memory, though the dosmemput and dosmemget functions will do this for you, example: dosmemput("H.e.l.l.o.", 10, 0xb8000); will put 'Hello' in the top left hand corner of a text screen (don't forget to include , and use add the library -lpc). If you want to access it yourself via asm you need to find out what the DOS selector is (selectors are explained elsewhere), and load this into a segment register. The same example with inline asm: #include static int dos_seg, length; static char *string; main() { dos_seg = _go32_conventional_mem_selector(); string = "H.e.l.l.o."; length = 10; asm (" pushw %es movw _dos_seg, %es movl _string, %esi movl $0xb8000, %edi movw _length, %cx rep ; movsb popw %es "); } Q. How do I detect available physical/virtual memory? The following functions are provided (include ): _go32_dpmi_remaining_physical_memory(); _go32_dpmi_remaining_virtual_memory(); Q. How are floating point numbers handled? By default, DJGPP uses the maths co-processor found on some systems. There is an emulator but you must include it your self. I have yet to evaluate this. Q. How do I chain interrupts? You must provide the offset and selector for the function to be chained. For example, chaining the hardware timer interrupt: #include void Interrupt(void) { /* Timer interrupt handler */ } main() { _go32_dpmi_seginfo old_handler, new_handler; new_handler.pm_offset = (int) Interrupt; new_handler.pm_selector = _go32_my_cs(); _go32_dpmi_get_protected_mode_interrupt(8, &old_handler); _go32_dpmi_chain_protected_mode_interrupt(8, &new_handler); /* Interrupt is chained */ _go32_dpmi_set_protected_mode_interrupt(8, &old_handler); /* Interrupt is unchained */ } NOTE: There is a problem when running under DOS however, when you get the old interrupt offset/selector you get a bogus value. If you then use this to unchain the interrupt, and later another interrupt does occur, you program will crash. I need to find out if there is a fix for this. At the moment, I unchain and immediately exit to prevent a crash if run under DOS. There are also problems under OS/2 and Win which I mention in the sound card DMA example. Either interrupt chaining is buggy or I am doing something wrong. I would like to know. Q. How do I access the FM part of a soundcard? Since all FM access can be done with hardware ports this is the same as in real mode. Q. How do I play digital sounds via DMA? This is probably the least well documented subject in the PC world, so I have included in the examples some code to play mono .WAV files of any length. The following is a description of how it is done: The basic idea goes like this, you set up a DOS memory buffer for the DMA transfer, any size, but tell the sound card it will be the full 64Kb. You then tell the DMA to transfer the buffer in cycle mode, which basically resends the same chunk of memory all the time. When the sound card creates and interrupt to say 64Kb has been transferred, you tell it to take another 64Kb. This way DMA does not have to be set up again, and reduces clicking to almost non existent. To play a sound bigger than the buffer size you need to keep a track of where the DMA transfer is, and while one half is being played, fill the other half with the next part of the sound. The buffer must also be filled with silence in-between samples. The example chains the hardware timer to do this. When writing this code I noticed a problem which I call interrupt miss. Sometimes you don't get a timer interrupt or sound card interrupt in protected mode. The program only seems to get notified of an interrupt if it occurs while the processor is executing your segment. Do a function like printf, which runs in the DOS segment, and you will not get notified. To prevent this being a problem with the DMA code I have done two things, made the buffer large enough to tolerate a timer interrupt miss before the sound becomes looped, and also made the code that reads the DMA count make sure the it has changed since last time, otherwise the DMA transfer is restarted. There was a problem with OS/2 2.1 that prevented the DMA from being in cycle mode, though it would work with the DMA transfer detection above would sound choppy, though this seems to have been put right in OS/2 WARP. Allocation of DOS memory for a DMA buffer: int AllocateBuffer() { unsigned int address, page; _go32_dpmi_seginfo memory; /* Allocate memory, return zero if fails */ memory.size = (DMA_SIZE * 2) / 16; if (_go32_dpmi_allocate_dos_memory(&memory)) return(0); /* What is the 20 bit address? */ address = memory.rm_segment << 4; /* Does it cross a 64K boundary? */ page = address & 0xffff; if ((page + DMA_SIZE) > 0xffff) address = (address - page) + 0x10000; /* Return the address of the buffer */ return(address); } Q. How do I write a keyboard handler? There may be a problem with interrupt miss though I haven't tried this yet. Still in progress. Q. How do I read from mouse? There is a library of functions available, and these may be good for cross platform compatibility, but the usual 'int86()' calls will work fine.