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 <dos.h>):

  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 <pc.h>, <dos.h> 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 <go32.h>
   
  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 <dpmi.h>):

  _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 <go32.h>

  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.
