┌────────────────────────────────────────────────────────────────────────────┐
│░▒▓█ Technical documentation for GrafX 2.00 - Version 1.08 (10/05/1997) █▓▒░│
└────────────────────────────────────────────────────────────────────────────┘

This file deals with:

  - the PKM picture format
  - the values to send to the CRTC to access all the amazing video modes
    available in GrafX 2.00



┌────────────────────────────────────────────────────────────────────────────┐
│            ░▒▓█ The PKM picture format - by Karl Maritaud █▓▒░             │
└────────────────────────────────────────────────────────────────────────────┘


    First of all, I'd like to say that I made this file format some years ago
  when I didn't knew how to load any good format (eg. GIF) and wanted to have
  my own format.
    PKM format was designed to be very simple, easy to encode and decode. Its
  header is very simple (short) and evolutive.
    The only real default I can find in this format is that you can only save
  256-color pictures.
    I know that you will think:
      "Oh no just another fucking format! I'll never use it! Its compression
      is too poor and I prefer GIF!".
    And I'll answer:
      "Yeah! You're right. But if you dunno how to load GIF and want a simple
      format with a quite good compression rate (on simple pictures at least),
      it could be useful."

  So, here comes the format documentation...



The HEADER:
═══════════

  The header is the following 780-byte-structure. (Don't worry about the size.
  That's just because the palette is considered as a part of the header).


  ┌─────┬───────────┬──────┬──────┬──────────────────────────────────────────┐
  │ Pos │ Field     │ Type │ Size │ Description                              │
  ╞═════╪═══════════╪══════╪══════╪══════════════════════════════════════════╡
  │   0 │ Signature │ char │   3  │ Constant string "PKM" (with NO size      │
  │     │           │      │      │ delimitation '\0' or so...)              │
  ├─────┼───────────┼──────┼──────┼──────────────────────────────────────────┤
  │   3 │ Version   │ byte │   1  │ For the moment, it can take only the     │
  │     │           │      │      │ value 0.                                 │
  │     │           │      │      │ Other packing methods may change this    │
  │     │           │      │      │ field but there is only one for now...   │
  ├─────┼───────────┼──────┼──────┼──────────────────────────────────────────┤
  │   4 │ Pack_byte │ byte │   1  │ Value of the recognition byte for color  │
  │     │           │      │      │ repetitions that are coded on 1 byte.    │
  │     │           │      │      │ (See the picture packing section for a   │
  │     │           │      │      │ better explanation)                      │
  ├─────┼───────────┼──────┼──────┼──────────────────────────────────────────┤
  │   5 │ Pack_word │ byte │   1  │ Value of the recognition byte for color  │
  │     │           │      │      │ repetitions that are coded on 2 bytes.   │
  │     │           │      │      │ (See the picture packing section...)     │
  ├─────┼───────────┼──────┼──────┼──────────────────────────────────────────┤
  │   6 │ Width     │ word │   2  │ Picture width  (in pixels)               │
  ├─────┼───────────┼──────┼──────┼──────────────────────────────────────────┤
  │   8 │ Height    │ word │   2  │ Picture height (in pixels)               │
  ├─────┼───────────┼──────┼──────┼──────────────────────────────────────────┤
  │  10 │ Palette   │ byte │ 768  │ RGB palette (RGB RGB ... 256 times) with │
  │     │           │      │      │ values from 0 to 63. I know the standard │
  │     │           │      │      │ in picture files is 0 to 255 but I find  │
  │     │           │      │      │ it stupid! It is really easier to send   │
  │     │           │      │      │ the whole palette in port 3C9h with a    │
  │     │           │      │      │ REP OUTSB without palette convertion.    │
  ├─────┼───────────┼──────┼──────┼──────────────────────────────────────────┤
  │ 778 │ PH_size   │ word │   2  │ Post-header size. This is the number of  │
  │     │           │      │      │ bytes between the header and the picture │
  │     │           │      │      │ data. This value can be equal to 0.      │
  └─────┴───────────┴──────┴──────┴──────────────────────────────────────────┘

  Data of type "word" are stored with Intel conventions: lower byte first.



The POST-HEADER:
════════════════

  The post-header has a variable size. It was designed to support new features
for this file format without changing the whole format.

  It consists in field identifiers followed by their size and their value.
  A field identifier is coded with 1 byte and a field size also.


  These field identifiers are:  (this list may be updated...)
  ────────────────────────────

    0 : Comment on the picture
    1 : Original screen dimensions
    2 : Back color (transparent color)

  If you encounter a field that you don't know just jump over it. But if a
  field tells you to jump to a position that is over the beginning of the
  picture data, there is an error in the file.


  The fields:
  ───────────

    * Comment:

      With this field, artists will be able to comment their pictures.
      Note that GrafX 2 has a comment size limit of 32 chars. But you can
      comment a picture with up to 255 chars if you make your own viewer
      since GrafX 2 will just ignore extra characters.

      Example: [0],[16],[Picture by X-Man]
      This sequence means:
        - the field is a comment
        - the comment takes 16 characters (there is no end-of-string character
          since you know its size)
        - the comment is "Picture by X-Man"

    * Original screen dimensions:

      Since GrafX 2 supplies a huge range of resolutions, it seemed convenient
      to add a field that indicates what were the original screen dimensions.

      Example: [1],[4],[320],[256]
      This sequence means:
        - the field is a screen dimensions descriptor
        - the dimensions are 2 words (so this value must be always equal to 4)
        - the original screen width was 320 pixels
        - the original screen height was 256 pixels

      Note that words stored in fields are written Intel-like. The 90% BETA
      version did not respect this norm. I'm really sorry about this. This is
      not very serious but pictures saved with version 90% and loaded with a
      latest version (91% and more) won't set the right resolution.

    * Back color:

      Saving the back color (transparent color) is especially useful when you
      want to save a brush.
      The size of this field is 1 byte (index of the color between 0 and 255).

      Example: [2],[1],[255]
      This sequence means:
        - the field is a screen dimensions descriptor
        - the value takes 1 byte
        - the transparent color is 255


The PICTURE PACKING METHOD:
═══════════════════════════

  The PKM compression method is some sort of Run-Length-Compression which is
very efficient on pictures with long horizontal color repetitions.
  Actually, the compression is efficient if there are often more than 3 times
the same color consecutively.

  I think that it would be better to give you the algorithm instead of swim-
ming in incomprehensible explanations.


  BEGIN
    /*
      functions:
        Read_byte(File)       reads and returns 1 byte from File
        Draw_pixel(X,Y,Color) draws a pixel of a certain Color at pos. (X,Y)
        File_length(File)     returns the total length in bytes of File

      variables:
        type of Image_size          is dword
        type of Data_size           is dword
        type of Packed_data_counter is dword
        type of Pixels_counter      is dword
        type of Color               is byte
        type of Byte_read           is byte
        type of Word_read           is word
        type of Counter             is word
        type of File                is <binary file>
    */

    /* At this point you've already read the header and post-header. */

    Image_size          <- Header.Width * Header.Height
    Data_size           <- File_length(File) - (780+Header.PH_size)

    Packed_data_counter <- 0
    Pixels_counter      <- 0

    /* Depacking loop: */
    WHILE ((Pixels_counter<Image_size) AND (Packed_data_counter<Data_size)) DO
    {
      Byte_read <- Read_byte(File)

      /* If it is not a packet recognizer, it's a raw pixel */
      IF ((Byte_read<>Header.Pack_byte) AND (Byte_read<>Header.Pack_word))
      THEN
      {
        Draw_pixel(Pixels_counter MOD Header.Width,
                   Pixels_counter DIV Header.Width,
                   Byte_read)

        Pixels_counter      <- Pixels_counter + 1
        Packed_data_counter <- Packed_data_counter + 1
      }
      ELSE /* Is the number of pixels to repeat coded... */
      {    /* ... with 1 byte */
        IF (Byte_read = Header.Pack_byte) THEN
        {
          Color     <- Read_byte(File)
          Byte_read <- Read_byte(File)

          FOR Counter FROM 0 TO (Byte_read-1) STEP +1
            Draw_pixel((Pixels_counter+Counter) MOD Header.Width,
                       (Pixels_counter+Counter) DIV Header.Width,
                       Color)

          Pixels_counter      <- Pixels_counter + Byte_read
          Packed_data_counter <- Packed_data_counter + 3
        }
        ELSE /* ... with 2 bytes */
        {
          Color     <- Read_byte(File)
          Word_read <- (word value) (Read_byte(File) SHL 8)+Read_byte(File)

          FOR Counter FROM 0 TO (Word_read-1) STEP +1
            Draw_pixel((Pixels_counter+Counter) MOD Header.Width,
                       (Pixels_counter+Counter) DIV Header.Width,
                       Color)

          Pixels_counter      <- Pixels_counter + Word_read
          Packed_data_counter <- Packed_data_counter + 4
        }
      }
    }
  END


  For example, the following sequence:
    (we suppose that Pack_byte=01 and Pack_word=02)
    04 03 01 05 06 03 02 00 01 2C
  will be decoded as:
    04 03 05 05 05 05 05 05 03 00 00 00 ... (repeat 0 300 times (012Ch=300))

  Repetitions that fit in a word must be written with their higher byte first.
  I know that it goes against Intel standard but since I read bytes from the
  file thru a buffer (really faster), I don't care about the order (Sorry :)).
  But words in the header and post-header must be written and read Intel-like!


  Packing advices:
  ────────────────

  * As you can see, there could be a problem when you'd want to pack a raw
  pixel with a color equal to Pack_byte or Pack_word. These pixels should
  always be coded as a packet even if there is only one pixel.

    Example: (we suppose that Pack_byte=9)
      9   will be encoded 9,9,1     (The 1st 9 in the encoded...
      9,9 will be encoded 9,9,2     ... sequence is Pack_byte)
      etc...

  * It seems obvious to find values for Pack_byte and Pack_word that are
  (almost) never used. So a small routine that finds the 2 less used colors
  in the image should be called before starting to pack the picture. This can
  be done almost instantaneously in Assembler.

  * When you want to pack a 2-color-sequence, just write these 2 colors one
  after the other (Color,Color) because it only takes 2 bytes instead of 3 if
  you had to write a packet (Pack_byte,Color,2).

  * If you pack a very simple picture which has a sequence of more than 65535
  same consecutive bytes, you must break the sequence and continue with a new
  packet.

    Example: you have to pack 65635 same consecutive bytes (eg. color 0)
      (we suppose that Pack_byte=01 and Pack_word=02)
      You'll write: 02 00 FF FF 01 00 64        (FFFFh=65535, 64h=100)



┌────────────────────────────────────────────────────────────────────────────┐
│                  ░▒▓█ Setting GrafX 2.00 video modes █▓▒░                  │
└────────────────────────────────────────────────────────────────────────────┘


    All set-mode procs are in 386 ASM. Anyway, if you can't understand any
  line of ASM, I really can't see the use you'll have of these procedures.

    They are designed to be used in FLAT memory model. Anyway, it wouldn't
  take too much time for you to adapt them to the model you use since only
  memory indexations can be affected by this (so use DS:SI instead of ESI,
  ES:DI instead of EDI, and beware to the address 0A0000h that will become
  0A000h:0000h).


MCGA: (Standard VGA mode)
═════

    Is there anybody in this world who still don't now how to set the MCGA
  320x200 256 colors mode ??!?
    Well... I hope you are a novice if you read the 2 following lines :)


    mov  ax,0013h
    int  10h



X-Modes: (Extended VGA modes)
════════

    Well... I think the original Mode X was 320x240 but now, many people call
  "X-Modes" (or Modes X, or Tweaked modes) all the VGA modes that use more
  that 64Kb of video memory with the "Unchained" structure.
    Setting a pixel in any X-Mode can be done by one same function (but I
  won't explain to you how to do that. You just have to tell the function what
  the plane width (screen_width/4) is).
    If you can't understand anything about what I say (unchained, planes...),
  just read any good documentation about Mode X.

    We'd like to thank the authors of XLIB2 for saving our time by having made
  this useful function. We slightly optimized it for our needs but the most
  important parts are here.


    mov  ax,13h      ; Yeah! The MCGA mode again! All X-Modes must start from
    int  10h         ; the standard VGA mode, but many things change after.

    mov  dx,3C6h     ; During the initialization, we'll turn the palette into
    xor  al,al       ; black in order to avoid the user to see all our
    out  dx,al       ; manipulations.

    mov  dx,3C4h     ; We will inform the TIMING SEQUENCER register to switch
    mov  ax,0604h    ; in unchained mode (mode-X), without odd/even management
    out  dx,ax       ; and with an access to the 256Kb of the video card.
    mov  ax,0100h    ; Now we will engage the synchronous reset of the TS
    out  dx,ax       ; register because we're about to play with registers.

    mov  al,01h      ; Like with the palette, we ask the video card not to
    out  dx,al       ; peek the memory to display it anymore. Thus, it's
    inc  dx          ; one more way to avoid interferences in the display,
    in   al,dx       ; which happens until the mode is completely initialized
    mov  ah,al       ; and stable. In addition, we can expect that asking a
    mov  al,01h      ; memory reading interruption will turn the system
    push ax          ; faster, and thus speed up the initialization of the
    mov  al,ah       ; graphic mode (hope makes you live :))
    or   al,20h      ;
    out  dx,al       ;

    mov  esi,X_ptr   ; Pointer on the list of constants to send to the CRTC.
    cld

    lodsb            ; This loads in AL a value that will tell what to do
                     ; with the MISCELLANEOUS register, and increases ESI.
                     ; The value is equal to ZERO => Nothing to do
                     ;                    or ELSE => Send AL to MISCELLANEOUS

    or   al,al       ; Shall we modify the basic video mode?
    jz   NoThankYou  ; No?─┐ Actually the answer is always "Yes".
    mov  dx,3C2h     ;     │ Except for a few modes such as
    out  dx,al       ;     │ 320x200 in Mode X
    NoThankYou:      ; <───┘ (but our 320x200 is MCGA...)

    mov  dx,3C4h     ; Manipulations with MISCELLANEOUS register are over, we
    mov  ax,0300h    ; can now disengage the synchronous register of the TS.
    out  dx,ax

    ; Now, what about teasing the CRTC?

    mov  dx,3D4h     ; In the 18th register of the CRTC, we will disengage the
    mov  al,11h      ; protection bit. Without this, the values we would have
    out  dx,al       ; sent to the CRTC registers would have been ignored.
    inc  dx
    in   al,dx
    and  al,7Fh
    out  dx,al

    dec  dx          ; DX points back to the CRTC register entry
    lodsb            ; This loads in AL the number of CRTC registers to modify
    xor  ecx,ecx     ; You must clear ECX before...
    mov  cl,al       ; ... starting to repeat AL (CL) times OUTSW
    rep  outsw       ; Let's send all the CRTC parameters!

    ; Just in case the 20th CRTC register would have been forgotten in the
    ; initialisation table, we can compute it by ourselves (Yeah, we are good
    ; guys).

    mov  ax,Screen_width ; You must tell the routine what the Screen width is
    shr  ax,3
    mov  ah,al
    mov  al,13h
    out  dx,ax

    mov  dx,3C4h     ; Now you have the good resolution but there can be
    mov  ax,0F02h    ; shitty pixels on the screen coming from the uncleared
    out  dx,ax       ; memory areas.
    mov  edi,0A0000h ; So we'll clean memory starting from 0A0000h with the
    xor  eax,eax     ; value 0 (which is the standard black) and on a range
    mov  ecx,4000h   ; of 4000h dwords (256Kb).
    rep  stosd       ; Let's wipe all this out.

    mov  dx,3C4h     ; We can ask the VGA to read again the memory to display
    pop  ax          ; it on the screen...
    out  dx,ax       ;
    mov  dx,3C6h     ; ... and turn on the palette so the picture appears to
    mov  al,0FFh     ; the user.
    out  dx,al       ;



  The table of constants you must send is one of these:
  (These are tables for C but they can be easily used in other languages)

  word X320Y224[] =
    { 0x0BA3, 0x6F06, 0xBA07, 0x0008, 0x4109, 0x0810, 0x8A11, 0xBF12, 0x0014,
      0xC715, 0x0416, 0xE317 };
  word X320Y240[] =
    { 0x0AE3, 0x0D06, 0x3E07, 0x4109, 0xEA10, 0xAC11, 0xDF12, 0x0014, 0xE715,
      0x0616, 0xE317 };
  word X320Y256[] =
    { 0x0CE3, 0x2306, 0xB207, 0x0008, 0x6109, 0x0A10, 0xAC11, 0xFF12, 0x2013,
      0x0014, 0x0715, 0x1A16, 0xE317 };
  word X320Y270[] =
    { 0x0BE7, 0x3006, 0xF007, 0x0008, 0x6109, 0x2010, 0xA911, 0x1B12, 0x0014,
      0x1F15, 0x2F16, 0xE317 };
  word X320Y282[] =
    { 0x0CE3, 0x6206, 0xF007, 0x6109, 0x310F, 0x3710, 0x8911, 0x3312, 0x2F13,
      0x0014, 0x3C15, 0x5C16, 0xE317 };
  word X320Y300[] =
    { 0x0DE3, 0x4606, 0x1F07, 0x0008, 0x4009, 0x3110, 0x8011, 0x2B12, 0x2013,
      0x0014, 0x2F15, 0x4416, 0xE317 };
  word X320Y360[] =
    { 0x09E3, 0x4009, 0x8810, 0x8511, 0x6712, 0x2013, 0x0014, 0x6D15, 0xBA16,
      0xE317 };
  word X320Y400[] =
    { 0x03E3, 0x4009, 0x0014, 0xE317 };
  word X320Y448[] =
    { 0x0BA3, 0x6F06, 0xBA07, 0x0008, 0x4009, 0x0810, 0x8A11, 0xBF12, 0x0014,
      0xC715, 0x0416, 0xE317 };
  word X320Y480[] =
    { 0x0AE3, 0x0D06, 0x3E07, 0x4009, 0xEA10, 0xAC11, 0xDF12, 0x0014, 0xE715,
      0x0616 , 0xE317};
  word X320Y512[] =
    { 0x0CE3, 0x2306, 0xB207, 0x0008, 0x6009, 0x0A10, 0xAC11, 0xFF12, 0x2013,
      0x0014, 0x0715, 0x1A16, 0xE317 };
  word X320Y540[] =
    { 0x0BE7, 0x3006, 0xF007, 0x0008, 0x6009, 0x2010, 0xA911, 0x1B12, 0x0014,
      0x1F15, 0x2F16, 0xE317 };
  word X320Y564[] =
    { 0x0CE7, 0x6206, 0xF007, 0x0008, 0x6009, 0x3E10, 0x8911, 0x3312, 0x2013,
      0x0014, 0x3C15, 0x5C16, 0xE317 };
  word X320Y600[] =
    { 0x0BE7, 0xBE06, 0xF007, 0x0008, 0x6009, 0x7C10, 0x8C11, 0x5712, 0x0014,
      0x5815, 0x7016, 0xE317 };
  word X360Y200[] =
    { 0x09E7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x2D13, 0x0014,
      0xE317 };
  word X360Y224[] =
    { 0x12A7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x6F06, 0xBA07,
      0x0008, 0x4109, 0x0810, 0x8A11, 0xBF12, 0x2D13, 0x0014, 0xC715, 0x0416,
      0xE317 };
  word X360Y240[] =
    { 0x11E7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x0D06, 0x3E07,
      0x4109, 0xEA10, 0xAC11, 0xDF12, 0x2D13, 0x0014, 0xE715, 0x0616, 0xE317 };
  word X360Y256[] =
    { 0x12E7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x2B06, 0xB207,
      0x0008, 0x6109, 0x0E10, 0xAC11, 0xFF12, 0x2D13, 0x0014, 0x0715, 0x1A16,
      0xE317 };
  word X360Y270[] =
    { 0x12E7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x3006, 0xF007,
      0x0008, 0x6109, 0x2010, 0xA911, 0x1B12, 0x2D13, 0x0014, 0x1F15, 0x2F16,
      0xE317 };
  word X360Y282[] =
    { 0x12E7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x6206, 0xF007,
      0x6109, 0x310F, 0x3710, 0x8911, 0x3312, 0x2D13, 0x0014, 0x3C15, 0x5C16,
      0xE317 };
  word X360Y300[] =
    { 0x12E7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x4606, 0x1F07,
      0x0008, 0x4009, 0x3110, 0x8011, 0x2B12, 0x2D13, 0x0014, 0x2F15, 0x4416,
      0xE317 };
  word X360Y360[] =
    { 0x0FE7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x4009, 0x8810,
      0x8511, 0x6712, 0x2D13, 0x0014, 0x6D15, 0xBA16, 0xE317 };
  word X360Y400[] =
    { 0x0AE7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x4009, 0x2D13,
      0x0014, 0xE317 };
  word X360Y448[] =
    { 0x12A7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x6F06, 0xBA07,
      0x0008, 0x4009, 0x0810, 0x8A11, 0xBF12, 0x2D13, 0x0014, 0xC715, 0x0416,
      0xE317 };
  word X360Y480[] =
    { 0x11E7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x0D06, 0x3E07,
      0x4009, 0xEA10, 0xAC11, 0xDF12, 0x2D13, 0x0014, 0xE715, 0x0616, 0xE317 };
  word X360Y512[] =
    { 0x12E7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x2B06, 0xB207,
      0x0008, 0x6009, 0x0E10, 0xAC11, 0xff12, 0x2D13, 0x0014, 0x0715, 0x1A16,
      0xE317 };
  word X360Y540[] =
    { 0x12E7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x3006, 0xF007,
      0x0008, 0x6009, 0x2010, 0xA911, 0x1B12, 0x2D13, 0x0014, 0x1F15, 0x2F16,
      0xE317 };
  word X360Y564[] =
    { 0x12EB, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0x6206, 0xF007,
      0x0008, 0x6009, 0x3E10, 0x8911, 0x3312, 0x2D13, 0x0014, 0x3C15, 0x5C16,
      0xE317 };
  word X360Y600[] =
    { 0x12E7, 0x6B00, 0x5901, 0x5A02, 0x8E03, 0x5E04, 0x8A05, 0xBE06, 0xF007,
      0x0008, 0x6009, 0x7C10, 0x8C11, 0x5712, 0x2D13, 0x0014, 0x5815, 0x7016,
      0xE317 };
  word X400Y200[] =
    { 0x09E7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x3213, 0x0014,
      0xE317 };
  word X400Y224[] =
    { 0x12A7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x6F06, 0xBA07,
      0x0008, 0x4109, 0x0810, 0x8A11, 0xBF12, 0x3213, 0x0014, 0xC715, 0x0416,
      0xE317 };
  word X400Y240[] =
    { 0x12E7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x0D06, 0x3E07,
      0x0008, 0x4109, 0xEA10, 0xAC11, 0xDF12, 0x3213, 0x0014, 0xE715, 0x0616,
      0xE317 };
  word X400Y256[] =
    { 0x12E7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x2B06, 0xB207,
      0x0008, 0x6109, 0x1310, 0xAC11, 0xFF12, 0x3213, 0x0014, 0x0715, 0x1A16,
      0xE317 };
  word X400Y270[] =
    { 0x12E7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x3006, 0xF007,
      0x0008, 0x6109, 0x2010, 0xA911, 0x1B12, 0x3213, 0x0014, 0x1F15, 0x2F16,
      0xE317 };
  word X400Y282[] =
    { 0x12E7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x6206, 0xF007,
      0x6109, 0x310F, 0x3710, 0x8911, 0x3312, 0x3213, 0x0014, 0x3C15, 0x5C16,
      0xE317 };
  word X400Y300[] =
    { 0x12E7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x4606, 0x1F07,
      0x0008, 0x4009, 0x3110, 0x8011, 0x2B12, 0x3213, 0x0014, 0x2F15, 0x4416,
      0xE317 };
  word X400Y360[] =
    { 0x0FE7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x4009, 0x8810,
      0x8511, 0x6712, 0x3213, 0x0014, 0x6D15, 0xBA16, 0xE317 };
  word X400Y400[] =
    { 0x0AE7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x4009, 0x3213,
      0x0014, 0xE317 };
  word X400Y448[] =
    { 0x12A7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x6F06, 0xBA07,
      0x0008, 0x4009, 0x0810, 0x8A11, 0xBF12, 0x3213, 0x0014, 0xC715, 0x0416,
      0xE317 };
  word X400Y480[] =
    { 0x11E7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x0D06, 0x3E07,
      0x4009, 0xEA10, 0xAC11, 0xDF12, 0x3213, 0x0014, 0xE715, 0x0616, 0xE317 };
  word X400Y512[] =
    { 0x12E7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x2B06, 0xB207,
      0x0008, 0x6009, 0x1310, 0xAC11, 0xFF12, 0x3213, 0x0014, 0x0715, 0x1A16,
      0xE317 };
  word X400Y540[] =
    { 0x12E7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x3006, 0xF007,
      0x0008, 0x6009, 0x2010, 0xA911, 0x1B12, 0x3213, 0x0014, 0x1F15, 0x2F16,
      0xE317 };
  word X400Y564[] =
    { 0x12EB, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0x6206, 0xF007,
      0x0008, 0x6009, 0x3E10, 0x8911, 0x3312, 0x3213, 0x0014, 0x3C15, 0x5C16,
      0xE317 };
  word X400Y600[] =
    { 0x12E7, 0x7100, 0x6301, 0x6402, 0x9203, 0x6604, 0x8205, 0xBE06, 0xF007,
      0x0008, 0x6009, 0x7C10, 0x8C11, 0x5712, 0x3213, 0x0014, 0x5815, 0x7016,
      0xE317 };


  The structure: (example)

       ┌────This is the number of values to send to the CRTC registers. This
       │    is actually the number of words in the tables minus 1 (because the
       │    1st word of the table is not sent to the CRTC but contains a value
       │    to send to the MISCELLANEOUS register and the number of values to
       │    send to the CRTC registers ;) ).
       │
       │ ┌──This is the value to send to the MISCEALLANEOUS register (or ZERO
       │ │  if no value must be sent to it).
       │ │
       │ │     ┌───This is a value to send to a register of the CRTC.
       │ │     │
       │ │     │ ┌─This is the index of the CRTC register that will receive
       │ │     │ │ the value.
       ├┐├┐    ├┐├┐
   { 0x0AE3, 0x0D06, 0x3E07, 0x4109, 0xEA10, 0xAC11, 0xDF12, 0x0014, 0xE715,
     0x0616, 0xE317 };

    You can notice that CRTC registers 0 to 5 (and 13h) define the screen
  width while registers 6 to 17h (except 13h) define the screen height.


    We have more modes in our pocket than the "few" :) ones we included in
  GrafX 2.00, but they aren't really useful or stable. But we may decice to
  include them anyway in a next version.
    If some of your favourite modes are missing, just send us the list of
  constants we must shoot at the CRTC just following the structure we use
  above.

  IMPORTANT! The constant values listed above are not supported by every
             monitor or video card.
             We have tested GrafX2 with several different configurations and
             we constated that some modes don't work at all with some video
             cards while some others can be overscanned, out of center, dark,
             too bright, or shrunk.
             But they all work fine with our poor little Tseng Labs ET4000...

  If you already have a good knowledge about CRTC and have different values
  than ours for certain modes, please let us know. We'll use them if they work
  better with a majority of computers.



VESA: (A "pseudo-standard" for Super-VGA modes)
═════

    We use VESA for modes that require a width of 640, 800 or 1024 pixels.
  But there is a way to combine X-Modes height with VESA so it's possible to
  have modes as weird as in X-Mode.


  mov  ax,4F02h
  mov  bx,Video_mode
  int  10h


  256-color-VESA video modes are:
    100h :  640x400
    101h :  640x480
    103h :  800x600
    105h : 1024x768
    107h : 1280x1024 (not available in GrafX2 because only supported with
                     video cards with 2 or more Megabytes of video memory)


  As with X-Modes, you can modify CRTC registers to access "Xtd-VESA" modes!
  (Note that some video cards don't support the modification of the VGA CRTC
  registers in VESA modes.)


  To enter these extended VESA modes, set a standard VESA mode with the right
  width, and then call Modify_CRTC_registers with the proper Height table.

  Example (640x512) :
    VESA_Set_mode(101h)          /* Set a video mode with the same width */
    Modify_CRTC_registers(Y512)  /* Modify height */


  * Height tables:

  word Y224[] =
    { 0x09A3, 0x6F06, 0xBA07, 0x0008, 0x4109, 0x0810, 0x8A11, 0xBF12, 0xC715,
      0x0416 };
  word Y240[] =
    { 0x09E3, 0x0D06, 0x3E07, 0x0008, 0x4109, 0xEA10, 0xAC11, 0xDF12, 0xE715,
      0x0616 };
  word Y256[] =
    { 0x0900, 0x2B06, 0xB207, 0x0008, 0x6109, 0x0A10, 0xAC11, 0xFF12, 0x0715,
      0x1A16 };
  word Y270[] =
    { 0x09E7, 0x3006, 0xF007, 0x0008, 0x6109, 0x2010, 0xA911, 0x1B12, 0x1F15,
      0x2F16 };
  word Y282[] =
    { 0x0AE3, 0x6206, 0xF007, 0x0008, 0x6109, 0x310F, 0x3710, 0x8911, 0x3312,
      0x3C15, 0x5C16 };
  word Y300[] =
    { 0x09E3, 0x4606, 0x1F07, 0x0008, 0x4009, 0x3110, 0x8011, 0x2B12, 0x2F15,
      0x4416 };
  word Y350[] =
    { 0x09A3, 0xBF06, 0x1F07, 0x0008, 0x4009, 0x8310, 0x8511, 0x5D12, 0x6315,
      0xBA16 };
  word Y360[] =
    { 0x07E3, 0x0008, 0x4009, 0x8810, 0x8511, 0x6712, 0x6D15, 0xBA16 };
  word Y400[] =
    { 0x01E3, 0x4009 };
  word Y448[] =
    { 0x09A3, 0x6F06, 0xBA07, 0x0008, 0x4009, 0x0810, 0x8A11, 0xBF12, 0xC715,
      0x0416 };
  word Y480[] =
    { 0x09E3, 0x0D06, 0x3E07, 0x0008, 0x4009, 0xEA10, 0xAC11, 0xDF12, 0xE715,
      0x0616 };
  word Y512[] =
    { 0x0900, 0x2B06, 0xB207, 0x0008, 0x6009, 0x0A10, 0xAC11, 0xFF12, 0x0715,
      0x1A16 };
  word Y540[] =
    { 0x09E7, 0x3006, 0xF007, 0x0008, 0x6009, 0x2010, 0xA911, 0x1B12, 0x1F15,
      0x2F16 };
  word Y564[] =
    { 0x09E7, 0x6206, 0xF007, 0x0008, 0x6009, 0x3E10, 0x8911, 0x3312, 0x3C15,
      0x5C16 };
  word Y600[] =
    { 0x09E7, 0xBE06, 0xF007, 0x0008, 0x6009, 0x7C10, 0x8C11, 0x5712, 0x5815,
      0x7016 };



  Modifying CRTC registers: (inspired by X-Modes init... See above for more
  ─────────────────────────  details or comments)

  mov  esi,XVESA_Ptr
  cld

  lodsb
  or   al,al       ; Shall we modify the basic video mode?
  jz   NoThankYou  ; No?─┐ The answer can be "No" because initialisations
  mov  dx,3C2h     ;     │ of certain VESA modes directly set the right
  out  dx,al       ;     │ value for the Miscellaneous register.
  NoThankYou:      ; <───┘

  mov  dx,3D4h
  mov  al,11h
  out  dx,al
  inc  dx
  in   al,dx
  and  al,7Fh
  out  dx,al

  dec  dx
  lodsb
  xor  ecx,ecx
  mov  cl,al
  rep  outsw



    If you are cunning enough, you'll be able to combine constants used in
  X-Modes to get more "Xtd-VESA" modes such as 640x200, 800x480, etc...
  (but I don't think this will work with 1024x??? because this mode is
  generally interlaced... But who knows?...)
    The most difficult is to find the right value for the MISCELLANEOUS
  register.
