BATCHFILE = """\
c:\\%s
rem end
"""

def memory_hma_freespace(self):

    if 'FDPP' in self.version:
        self.mkfile("userhook.sys", """DOS=HIGH\n""", newline="\r\n")

    self.mkfile("testit.bat", BATCHFILE % 'hmaspace', newline="\r\n")

    self.mkcom_with_ia16("hmaspace", r"""

#include <i86.h>
#include <stdio.h>
#include <string.h>


int main(int argc, char *argv[])
{
  union REGS r = {};
  struct SREGS rs;
  int ret = 0;

  r.x.ax = 0x4a01;      // get hma free space
  int86x(0x2f, &r, &r, &rs);
  if (r.x.bx == 0) {
    printf("INFO: DOS not using HMA(BX==0)\n");
    ret += 1;
  }
  if (rs.es == 0xffff && r.x.di == 0xffff) {
    printf("INFO: DOS not using HMA(ES:DI==FFFF:FFFF)\n");
    ret += 1;
  }

  if (ret != 0) {
    printf("FAIL: No HMA available\n");
    return ret;
  }

  printf("INFO: HMA free space == 0x%04x\n", r.x.bx);
  printf("INFO: HMA area at %04X:%04X\n", rs.es, r.x.di);
  printf("PASS: HMA available\n");
  return 0;
}

""")

    results = self.runDosemu("testit.bat")

    self.assertIn("PASS:", results)


def memory_hma_alloc(self):

    if 'FDPP' in self.version:
        self.mkfile("userhook.sys", """DOS=HIGH\n""", newline="\r\n")

    self.mkfile("testit.bat", BATCHFILE % 'hmaalloc', newline="\r\n")

    self.mkcom_with_ia16("hmaalloc", r"""

#include <i86.h>
#include <stdio.h>
#include <string.h>


int main(int argc, char *argv[])
{
  union REGS r = {};
  struct SREGS rs;
  int ret = 0;

  r.x.ax = 0x4a02;      // get hma memory block
  r.x.bx = 35;          // alloc 35 bytes, should get rounded up to 48
  int86x(0x2f, &r, &r, &rs);

  printf("INFO: HMA size returned %d\n", r.x.bx);
  printf("INFO: HMA block allocated at %04X:%04X\n", rs.es, r.x.di);

  if (r.x.bx != 48) {
    printf("WARN: HMA returned size unexpected\n");
    ret += 1;
  }

  if (rs.es != 0xffff) {
    printf("WARN: HMA block allocated in wrong segment\n");
    ret += 1;
  }

  if (ret != 0) {
    printf("FAIL: Test failed\n");
    return ret;
  }

  printf("PASS: HMA allocation successful\n");
  return 0;
}

""")

    results = self.runDosemu("testit.bat")

    self.assertIn("PASS:", results)


def memory_hma_alloc3(self):

    if 'FDPP' in self.version:
        self.mkfile("userhook.sys", """DOS=HIGH\n""", newline="\r\n")

    self.mkfile("testit.bat", BATCHFILE % 'hmaalloc', newline="\r\n")

    self.mkcom_with_ia16("hmaalloc", r"""

#include <i86.h>
#include <stdio.h>
#include <string.h>

struct HMCB {
  uint8_t signature[2]; // "MS"
  uint16_t owner;       // 0000=free, 0001=DOS, FF33=IO.SYS, FFFF=MSDOS.SYS
  uint16_t size;        // bytes not including this header
  uint16_t next;        // offset of next memory block in segment FFFFh, or 0000h if last
  uint8_t reserved[8];  // unused (explicitly set to 0 for MS-DOS 7.10)
};

/*
Windows95 - DOS KERNEL - (DE)ALLOCATE HMA MEMORY BLOCK

AX = 4A03h
CX = segment of block's owner to allocate, if 0 then 1 is used
    (MS-DOS v5 AX=4A02h passes INT 2F caller's CS as the owner)
DL = subfunction
  00h allocate block from bottom of largest free block
    BX = number of bytes
    Return:
      DI=FFFFh if unable to allocate
      ES:DI -> allocated block if able to allocate,
      BX = allocated size (rounded up to paragraph)

  01h allocate block from top of largest free block
    BX = number of bytes
    Return:
      DI=FFFFh if unable to allocate
      ES:DI -> allocated block if able to allocate,
      BX = allocated size (rounded up to paragraph)

  02h free block (coalesces prior and subsequent free blocks)
    ES:DI -> block to be freed
    Return:
      Nothing.
*/

int main(int argc, char *argv[])
{
  union REGS r = {};
  struct SREGS rs;
  int ret;
  struct HMCB hmcb;
  struct HMCB __far *phmcb;
  uint16_t hma_seg, hma_off;
  char __far *first;
  char __far *second;

// Alloc subfunction (from bottom of largest free block)
  ret = 0;
  r.x.ax = 0x4a03;              // hma alloc/free memory block
  r.x.bx = 49;                  // alloc 49 bytes, should get rounded up to 64
  r.x.cx = __libi86_get_cs();   // segment of block's owner
  r.h.dl = 0;                   // subfunction 0 - alloc upwards

  printf("INFO: Int2f/4a03, dl=0 alloc upwards\n");
  int86x(0x2f, &r, &r, &rs);

  printf("INFO: HMA size returned %u\n", r.x.bx);
  printf("INFO: HMA block (1) allocated at %04X:%04X\n", rs.es, r.x.di);

  first = MK_FP(rs.es, r.x.di);
  hma_seg = rs.es;
  hma_off = r.x.di;

  // Copy back to near structure to make printfs easier
  memset(&hmcb, 0, sizeof(hmcb));
  _fmemcpy(&hmcb, MK_FP(rs.es, r.x.di - 0x10), sizeof(hmcb));

  printf("INFO: \"%c%c\" // signature\n", hmcb.signature[0], hmcb.signature[1]);
  printf("INFO: %04X // owner, our CS = %04X\n", hmcb.owner, __libi86_get_cs());
  printf("INFO: %04X // size (hex bytes)\n", hmcb.size);
  printf("INFO: %04X // offset of next HMA block\n", hmcb.next);

  if (r.x.di == 0xffff) {
    printf("WARN: HMA returned offset indicates failure (DI == 0xffff)\n");
    ret += 1;
  }

  if (r.x.bx != 64) {
    printf("WARN: HMA returned size unexpected (BX == %u)\n", r.x.bx);
    ret += 1;
  }

  if (r.x.bx != hmcb.size) {
    printf("WARN: hmcb.size != HMA returned size (%u != %u)\n", hmcb.size, r.x.bx);
    ret += 1;
  }

  if (hmcb.next) { /* FDPP allows final block to have next of zero */
    // Try to validate offset of next block
    if (hmcb.next <= hma_off) {
      printf("WARN: hmcb.next wasn't greater than the current block\n");
      ret += 1;
    }
    phmcb = MK_FP(hma_seg, hmcb.next);
    if (phmcb->signature[0] != 'M' || phmcb->signature[1] != 'S') {
      printf("WARN: hmcb.next didn't point to a valid hmcb\n");
      ret += 1;
    }
  } else {
    printf("INFO: hmcb.next was zero assuming we are testing FDPP\n");
  }

  if (ret != 0) {
    printf("FAIL: Alloc (1) test failed\n");
    return ret;
  }

// Free block subfunction
  ret = 0;
  r.x.ax = 0x4a03;              // hma alloc/free memory block
  r.x.cx = __libi86_get_cs();   // segment of block's owner
  r.h.dl = 2;                   // subfunction 2 - free
  rs.es = hma_seg;              // old block seg:off
  r.x.di = hma_off;

  printf("INFO: Int2f/4a03, dl=2 free (%04X:%04X)\n", rs.es, r.x.di);
  int86x(0x2f, &r, &r, &rs);

  // Free doesn't return anything to indicate success, so we can check the
  // requested block to see if it's still marked as ours. However it only
  // really works when the block was allocated in the upward direction.

  // Copy back to near structure to make access easier
  memset(&hmcb, 0, sizeof(hmcb));
  _fmemcpy(&hmcb, MK_FP(rs.es, r.x.di - 0x10), sizeof(hmcb));

  if (hmcb.signature[0] == 'M' && hmcb.signature[1] == 'S' &&
      hmcb.owner == __libi86_get_cs()) {
    printf("WARN: Freed block (1) still marked as ours\n");
    ret += 1;
  }

  if (ret != 0) {
    printf("FAIL: Free (1) test failed\n");
    return ret;
  }

  printf("\n");

// Alloc subfunction (downwards from top of largest free block)
  ret = 0;
  r.x.ax = 0x4a03;              // hma alloc/free memory block
  r.x.bx = 35;                  // alloc 35 bytes, should get rounded up to 48
  r.x.cx = __libi86_get_cs();   // segment of block's owner
  r.h.dl = 1;                   // subfunction 1 - alloc downwards

  printf("INFO: Int2f/4a03, dl=1 alloc downwards\n");
  int86x(0x2f, &r, &r, &rs);

  printf("INFO: HMA size returned %u\n", r.x.bx);
  printf("INFO: HMA block (2) allocated at %04X:%04X\n", rs.es, r.x.di);

  second = MK_FP(rs.es, r.x.di);
  hma_seg = rs.es;
  hma_off = r.x.di;

  // Copy back to near structure to make printfs easier
  memset(&hmcb, 0, sizeof(hmcb));
  _fmemcpy(&hmcb, MK_FP(rs.es, r.x.di - 0x10), sizeof(hmcb));

  printf("INFO: \"%c%c\" // signature\n", hmcb.signature[0], hmcb.signature[1]);
  printf("INFO: %04X // owner, our CS = %04X\n", hmcb.owner, __libi86_get_cs());
  printf("INFO: %04X // size (hex bytes)\n", hmcb.size);
  printf("INFO: %04X // offset of next HMA block\n", hmcb.next);

  if (r.x.di == 0xffff) {
    printf("WARN: HMA returned offset indicates failure (DI == 0xffff)\n");
    ret += 1;
  }

  if (r.x.bx != 48) {
    printf("WARN: HMA returned size unexpected (BX == %u)\n", r.x.bx);
    ret += 1;
  }

  if (r.x.bx != hmcb.size) {
    printf("WARN: hmcb.size != HMA returned size (%u != %u)\n", hmcb.size, r.x.bx);
    ret += 1;
  }

  if (hmcb.next) { /* FDPP allows final block to have next of zero */
    // Try to validate offset of next block
    if (hmcb.next <= hma_off) {
      printf("WARN: hmcb.next wasn't greater than the current block\n");
      ret += 1;
    }
    phmcb = MK_FP(hma_seg, hmcb.next);
    if (phmcb->signature[0] != 'M' || phmcb->signature[1] != 'S') {
      printf("WARN: hmcb.next didn't point to a valid hmcb\n");
      ret += 1;
    }
  } else {
    printf("INFO: hmcb.next was zero assuming we are testing FDPP\n");
  }

  if (ret != 0) {
    printf("FAIL: Alloc (2) test failed\n");
    return ret;
  }

// Free block subfunction
  r.x.ax = 0x4a03;              // hma alloc/free memory block
  r.x.cx = __libi86_get_cs();   // segment of block's owner
  r.h.dl = 2;                   // subfunction 2 - free
  rs.es = hma_seg;              // old block seg:off
  r.x.di = hma_off;

  printf("INFO: Int2f/4a03, dl=2 free (%04X:%04X)\n", rs.es, r.x.di);
  int86x(0x2f, &r, &r, &rs);

  // Free doesn't return anything to indicate success, so previously we checked the
  // requested block to see if it's still marked as ours. However on MS-DOS 7.10
  // that's an unreliable indicator when the block has been allocated in the
  // downward direction, so we just have to assume it worked.

  printf("\n");

  if (second <= first) {
    printf("FAIL: second block address <= first block address\n");
    return 1;
  }

  printf("PASS: HMA alloc fn03 / free successful\n");
  return 0;
}

""")

    results = self.runDosemu("testit.bat")

    self.assertIn("PASS:", results)


def memory_hma_chain(self):

    if 'FDPP' in self.version:
        self.mkfile("userhook.sys", """DOS=HIGH\n""", newline="\r\n")

    self.mkfile("testit.bat", BATCHFILE % 'hmachain', newline="\r\n")

    self.mkcom_with_ia16("hmachain", r"""

#include <i86.h>
#include <stdio.h>
#include <string.h>

struct HMCB {
  uint8_t signature[2]; // "MS"
  uint16_t owner;       // 0000=free, 0001=DOS, FF33=IO.SYS, FFFF=MSDOS.SYS
  uint16_t size;        // bytes not including this header
  uint16_t next;        // offset of next memory block in segment FFFFh, or 0000h if last
  uint8_t reserved[8];  // unused (explicitly set to 0 for MS-DOS 7.10)
};

/*
  INT 2F U - Windows95 - DOS KERNEL - GET START OF HMA MEMORY CHAIN
	AX = 4A04h
  Return: AX = 0000h if function supported
	    ES:DI -> first HMA memory control block (see #02800)
*/

int main(int argc, char *argv[])
{
  union REGS r = {};
  struct SREGS rs;
  int ret;
  struct HMCB hmcb;

// Alloc subfunction
  ret = 0;
  r.x.ax = 0x4a04;              // get start of hma chain

  printf("INFO: Int2f/4a04\n");
  int86x(0x2f, &r, &r, &rs);

  if (r.x.ax != 0) {
    printf("WARN: HMA get chain returned (0x%04x), unsupported\n", r.x.ax);
    ret += 1;
  } else {
    printf("INFO: HMA head at %04X:%04X\n", rs.es, r.x.di);

    // Copy back to near structure to make printfs easier
    memset(&hmcb, 0, sizeof(hmcb));
    _fmemcpy(&hmcb, MK_FP(rs.es, r.x.di), sizeof(hmcb));

    printf("INFO: \"%c%c\" // signature\n", hmcb.signature[0], hmcb.signature[1]);
    printf("INFO: %04X // owner\n", hmcb.owner);
    printf("INFO: %04X // size (hex bytes)\n", hmcb.size);
    printf("INFO: %04X // offset of next HMA block\n", hmcb.next);

    if (hmcb.signature[0] != 'M' || hmcb.signature[1] != 'S') {
      printf("WARN: HMA head signature incorrect\n");
      ret += 1;
    }
  }

  if (ret != 0) {
    printf("FAIL: HMA get chain failed\n");
    return ret;
  }

  printf("PASS: HMA get chain successful\n");
  return 0;
}

""")

    results = self.runDosemu("testit.bat")

    self.assertIn("PASS:", results)


def memory_hma_a20(self):

    if 'FDPP' in self.version:
        self.mkfile("userhook.sys", """DOS=HIGH\n""", newline="\r\n")

    self.mkfile("testit.bat", BATCHFILE % 'hmaa20', newline="\r\n")

    self.mkcom_with_ia16("hmaa20", r"""

#include <i86.h>
#include <stdio.h>
#include <string.h>

#define TSTRING "HELLO THERE"

int main(int argc, char *argv[])
{
  union REGS r = {};
  struct SREGS rs;
  char testbuf[sizeof(TSTRING)];
  char savebuf[sizeof(TSTRING)];
  char __far *hma_location;
  char __far *cnv_location;
  int ret = 0;
  int a20;
  int res1, res2, val1, val2;

  r.x.ax = 0x4a02;      // get hma memory block
  r.x.bx = 35;          // alloc 35 bytes, should get rounded up to 48
  int86x(0x2f, &r, &r, &rs);

  printf("INFO: HMA size returned %d\n", r.x.bx);
  printf("INFO: HMA block allocated at %04X:%04X\n", rs.es, r.x.di);

  if (rs.es != 0xffff) {
    printf("WARN: HMA block allocated in wrong segment\n");
    ret += 1;
  }

  // Note: wrapped address will always be in first segment
  hma_location = MK_FP(rs.es, r.x.di);
  cnv_location = MK_FP(0x0000, ((rs.es << 4) + r.x.di) & 0xffff);

  _disable();

  // Get A20 status
  r.x.ax = 0x2402;
  r.x.cflag = 1;
  int86x(0x15, &r, &r, &rs);
  if (!r.x.cflag) {
    a20 = r.h.al;
  } else {
    a20 = 1; // default to on
  }
  res1 = r.x.cflag;
  val1 = r.h.al;
  val2 = r.h.ah;

  // Save memory
  _fmemcpy(savebuf, cnv_location, sizeof(TSTRING));

  // Write to HMA memory
  _fmemcpy(hma_location, TSTRING, sizeof(TSTRING));

  // Read from the wrapped address
  _fmemcpy(testbuf, cnv_location, sizeof(TSTRING));

  // Restore corrupted memory as soon as possible
  _fmemcpy(cnv_location, savebuf, sizeof(TSTRING));

  _enable();

  if (!res1) {
    printf("INFO: A20 is initially %s\n", val1 == 0 ? "disabled" : "enabled");
  } else {
    printf("WARN: A20 status could not be determined (0x%02x)\n", val2);
    ret += 1;
  }

  // Compare to make sure we are not writing in the wrong place
  if (memcmp(testbuf, TSTRING, sizeof(TSTRING)) == 0) {
    printf("WARN: Wrapping is evident\n");
    ret += 1;
  } else {
    printf("INFO: Wrapping is not evident\n");
  }

  _disable();

  // Disable the A20 to enable the wrapping of memory
  r.x.ax = 0x2400;
  r.x.cflag = 1;
  int86x(0x15, &r, &r, &rs);
  res1 = r.x.cflag;
  if (res1)
    val1 = r.h.ah;

  // Save memory
  _fmemcpy(savebuf, cnv_location, sizeof(TSTRING));

  // Write to HMA memory
  _fmemcpy(hma_location, TSTRING, sizeof(TSTRING));

  // Read from the wrapped address
  _fmemcpy(testbuf, cnv_location, sizeof(TSTRING));

  // Restore corrupted memory as soon as possible
  _fmemcpy(cnv_location, savebuf, sizeof(TSTRING));

  // Maybe reenable the A20 to return to normal
  if (a20) {
    r.x.ax = 0x2401;
    r.x.cflag = 1;
    int86x(0x15, &r, &r, &rs);
    res2 = r.x.cflag;
    val2 = r.h.ah;
  }

  _enable();

  if (!res1) {
    printf("INFO: A20 successfully disabled\n");
  } else {
    printf("WARN: A20 could not be disabled (0x%02x)\n", val1);
    ret += 1;
  }

  if (a20)
    if (!res2) {
      printf("INFO: A20 successfully reenabled\n");
    } else {
      printf("WARN: A20 could not be reenabled\n (0x%02x)\n", val2);
      ret += 1;
    }

  // Compare
  if (memcmp(testbuf, TSTRING, sizeof(TSTRING)) == 0) {
    printf("INFO: Wrapping is evident and expected\n");
  } else {
    printf("WARN: Wrapping is not evident\n");
    ret += 1;
  }

  if (ret != 0) {
    printf("FAIL: Test failed\n");
    return ret;
  }

  printf("PASS: HMA allocation successful\n");
  return 0;
}

""")

    results = self.runDosemu("testit.bat")

    self.assertIn("PASS:", results)
