From a7b510386aa339b77b5a08e7a77e56cde6d4f286 Mon Sep 17 00:00:00 2001 From: disinvite Date: Tue, 27 Jun 2023 23:24:19 -0400 Subject: [PATCH] Implementation of MXIOINFO. Not a 100% match, but we are very close. I don't wanna wrangle with this one any more, so I figured I would open it up for review in case anyone else has ideas. **Known problems:** - The Open function uses a `movzx` instruction on the value of parameter `fdwOpen` before pushing to OpenFile from the kernel. You can force this to appear by casting to `unsigned short`, but this disturbs the instructions for the rest of the file. To get the "best" overall match I decided to leave this out. - Flush, Advance, and Descend differ only in the order of operands on a `cmp` instruction. - This entire file is honestly pretty ugly. The main reason is all the nested ifs; we are constrained by returning a result value from each function, but only at the very end instead of bailing out with a `return`. By far the worst offender is the do/while loop in the Descend function. **Design considerations:** - We are casting the file handle from MMIOINFO to `HFILE` everywhere it is used, so I decided to just change the type. While doing that, I figured I might as well just pull out the members from the struct so we don't have `m_info` all over the place. - Without using a struct member, we have the issue of the obvious `memset` used to zero out the values in the constructor. I changed this to work on the object itself, which would not be valid in most cases, but seems fine here since we have no virtual methods. There is a lot of repeated code here, namely the call to `_llseek` to reset `m_lDiskOffset` based on the current file position. You could move this to an inline function, but maybe that's not appropriate. There are probably strides to be made on code clarity and comments (if needed or wanted) here. I'm open to any suggestions. --- LEGO1/mxioinfo.cpp | 413 +++++++++++++++++++++++++++++++++++++++++++-- LEGO1/mxioinfo.h | 34 +++- 2 files changed, 433 insertions(+), 14 deletions(-) diff --git a/LEGO1/mxioinfo.cpp b/LEGO1/mxioinfo.cpp index ebe2a480..4a3fdf25 100644 --- a/LEGO1/mxioinfo.cpp +++ b/LEGO1/mxioinfo.cpp @@ -3,7 +3,9 @@ // OFFSET: LEGO1 0x100cc800 MXIOINFO::MXIOINFO() { - memset(&m_info, 0, sizeof(MMIOINFO)); + // This is not good practice, but shouldn't damage anything + // because MXIOINFO has no vtable. + memset(this, 0, sizeof(*this)); } // OFFSET: LEGO1 0x100cc820 @@ -15,35 +17,426 @@ MXIOINFO::~MXIOINFO() // OFFSET: LEGO1 0x100cc830 unsigned short MXIOINFO::Open(const char *filename, DWORD fdwOpen) { - return 0; + OFSTRUCT _unused; + unsigned short result = 0; + + m_lBufOffset = 0; + m_lDiskOffset = 0; + + // Cast of fdWOpen to u16 forces the `movzx` instruction + //m_hmmio = OpenFile(filename, &_unused, (unsigned short)fdwOpen); + m_hmmio = OpenFile(filename, &_unused, fdwOpen); + + if (m_hmmio != HFILE_ERROR) { + m_dwFlags = fdwOpen; + if (fdwOpen & MMIO_ALLOCBUF) { + + // Default buffer length of 8k if none specified + int len = m_cchBuffer ? m_cchBuffer : 8192; + HPSTR buf = new char[len]; + + if (!buf) { + result = MMIOERR_OUTOFMEMORY; + m_cchBuffer = 0; + m_dwFlags &= ~MMIO_ALLOCBUF; + m_pchBuffer = 0; + } else { + m_pchBuffer = buf; + m_cchBuffer = len; + } + + m_pchEndRead = m_pchBuffer; + m_pchNext = m_pchBuffer; + m_pchEndWrite = m_pchBuffer + m_cchBuffer; + } + } else { + result = MMIOERR_CANNOTOPEN; + } + + return result; } // OFFSET: LEGO1 0x100cc8e0 -void MXIOINFO::Close(long arg) +unsigned short MXIOINFO::Close(long arg) { - + unsigned short result = 0; + + if (m_hmmio) { + result = Flush(0); + _lclose(m_hmmio); + m_hmmio = NULL; + + if (m_dwFlags & MMIO_ALLOCBUF) + delete[] m_pchBuffer; + + m_pchEndWrite = 0; + m_pchEndRead = 0; + m_pchBuffer = 0; + m_dwFlags = 0; + } + + return result; } // OFFSET: LEGO1 0x100cc930 unsigned long MXIOINFO::Read(HPSTR pch, LONG cch) { - return 0; + unsigned long bytes_read = 0; + + if (m_pchBuffer) { + + int bytes_left = m_pchEndRead - m_pchNext; + while (cch > 0) { + + if (bytes_left > 0) { + if (cch < bytes_left) + bytes_left = cch; + + memcpy(pch, m_pchNext, bytes_left); + cch -= bytes_left; + + m_pchNext += bytes_left; + bytes_read += bytes_left; + } + + if (cch <= 0 || Advance(0)) + break; + + bytes_left = m_pchEndRead - m_pchNext; + if (bytes_left <= 0) + break; + } + } else if (m_hmmio && cch > 0) { + bytes_read = _hread(m_hmmio, pch, cch); + + if (bytes_read == -1) { + bytes_read = 0; + m_lDiskOffset = _llseek(m_hmmio, 0, SEEK_CUR); + } else { + m_lDiskOffset += bytes_read; + } + } + + return bytes_read; } // OFFSET: LEGO1 0x100cca00 LONG MXIOINFO::Seek(LONG lOffset, int iOrigin) { - return 0; + LONG result = -1; + + // If buffered I/O + if (m_pchBuffer) { + if (iOrigin == SEEK_CUR) { + if (!lOffset) { + // don't seek at all and just return where we are. + return m_lBufOffset + (m_pchNext - m_pchBuffer); + } else { + // With SEEK_CUR, lOffset is a relative offset. + // Get the absolute position instead and use SEEK_SET. + lOffset += m_lBufOffset + (m_pchNext - m_pchBuffer); + iOrigin = SEEK_SET; + } + } else if (iOrigin == SEEK_END) { + // not possible with buffered I/O + return -1; + } + + // else iOrigin == SEEK_SET. + + // is lOffset between the start and end of the buffer? + // i.e. can we do the seek without reading more from disk? + if (lOffset >= m_lBufOffset && lOffset < m_lBufOffset + m_cchBuffer) { + m_pchNext = m_pchBuffer + (lOffset - m_lBufOffset); + result = lOffset; + } else { + // we have to read another chunk from disk. + if (m_hmmio && !Flush(0)) { + m_lDiskOffset = _llseek(m_hmmio, lOffset, iOrigin); + + if (m_lDiskOffset == -1) { + m_lDiskOffset = _llseek(m_hmmio, 0, SEEK_CUR); + } else { + + // align offset to buffer size + int new_offset = lOffset - (lOffset % m_cchBuffer); + m_lBufOffset = new_offset; + + // do we need to seek again? + // (i.e. are we already aligned to buffer size?) + if (lOffset != new_offset) { + m_lDiskOffset = _llseek(m_hmmio, new_offset, SEEK_SET); + + if (m_lDiskOffset == -1) { + m_lDiskOffset = _llseek(m_hmmio, 0, SEEK_CUR); + } + } + + if (m_lBufOffset == m_lDiskOffset) { + // is the file open for writing only? + if ((m_dwFlags & MMIO_RWMODE) && + ((m_dwFlags & MMIO_RWMODE) != MMIO_READWRITE)) { + + m_pchNext = m_pchBuffer - m_lBufOffset + lOffset; + + result = lOffset; + } else { + // We can read from the file. Fill the buffer. + int bytes_read = _hread(m_hmmio, m_pchBuffer, m_cchBuffer); + + if (bytes_read == -1) { + m_lDiskOffset = _llseek(m_hmmio, 0, SEEK_CUR); + } else { + m_lDiskOffset += bytes_read; + m_pchNext = lOffset - m_lBufOffset + m_pchBuffer; + m_pchEndRead = m_pchBuffer + bytes_read; + + if (m_pchNext < m_pchEndRead) { + result = lOffset; + } + } + } + } + } + } + } + } else { + // No buffer so just seek the file directly (if we have a valid handle) + if (m_hmmio) { + // i.e. if we just want to get the current file position + if (iOrigin == SEEK_CUR && lOffset == 0) { + return m_lDiskOffset; + } else { + m_lDiskOffset = _llseek(m_hmmio, lOffset, iOrigin); + + result = m_lDiskOffset; + + if (result == -1) { + m_lDiskOffset = _llseek(m_hmmio, 0, SEEK_CUR); + } + } + } + } + + return result; } // OFFSET: LEGO1 0x100ccbc0 -void MXIOINFO::SetBuffer(LPSTR pchBuffer, LONG cchBuffer, LONG unk) +unsigned short MXIOINFO::SetBuffer(LPSTR pchBuffer, LONG cchBuffer, LONG unk) { - + unsigned short result = Flush(0); + + if (m_dwFlags & MMIO_ALLOCBUF) { + m_dwFlags &= ~MMIO_ALLOCBUF; + delete[] m_pchBuffer; + } + + m_pchBuffer = pchBuffer; + m_cchBuffer = cchBuffer; + m_pchEndWrite = m_pchBuffer + m_cchBuffer; + m_pchEndRead = m_pchBuffer; + + return result; +} + +// OFFSET: LEGO1 0x100ccc10 +unsigned short MXIOINFO::Flush(UINT fuFlush) +{ + unsigned short result = 0; + + // if buffer is dirty + if (m_dwFlags & MMIO_DIRTY) { + // if we have allocated an IO buffer + if (m_pchBuffer) { + // if we have a file open for writing + if (m_hmmio && (m_dwFlags & MMIO_RWMODE)) { + // (pulling this value out into a variable forces it into EBX) + LONG cchBuffer = m_cchBuffer; + if (cchBuffer > 0) { + if (m_lBufOffset != m_lDiskOffset) { + m_lDiskOffset = _llseek(m_hmmio, m_lBufOffset, SEEK_SET); + } + + // Was the previous seek (if required) successful? + if (m_lBufOffset != m_lDiskOffset) { + result = MMIOERR_CANNOTSEEK; + m_lDiskOffset = _llseek(m_hmmio, 0, SEEK_CUR); + } else { + long bytes_written = _hwrite(m_hmmio, m_pchBuffer, cchBuffer); + + if (bytes_written != -1 && bytes_written == cchBuffer) { + m_lDiskOffset += bytes_written; + m_pchNext = m_pchBuffer; + m_dwFlags &= ~MMIO_DIRTY; + } else { + result = MMIOERR_CANNOTWRITE; + m_lDiskOffset = _llseek(m_hmmio, 0, SEEK_CUR); + } + } + } + } else { + result = MMIOERR_CANNOTWRITE; + } + } else { + result = MMIOERR_UNBUFFERED; + } + } + + return result; +} + +// OFFSET: LEGO1 0x100ccd00 +unsigned short MXIOINFO::Advance(UINT fuAdvance) +{ + unsigned short result = 0; + DWORD rwmode = m_dwFlags & MMIO_RWMODE; + + if (m_pchBuffer) { + long cch = m_cchBuffer; + + // If we can and should write to the file, + // if we are being asked to write to the file, + // and if there is a buffer *to* write: + if ((rwmode == MMIO_WRITE || rwmode == MMIO_READWRITE) && + (m_dwFlags & MMIO_DIRTY) && + ((fuAdvance & MMIO_WRITE) || (rwmode == MMIO_READWRITE)) && + cch > 0) { + + if (m_lBufOffset != m_lDiskOffset) { + m_lDiskOffset = _llseek(m_hmmio, m_lBufOffset, SEEK_SET); + } + + if (m_lBufOffset != m_lDiskOffset) { + result = MMIOERR_CANNOTSEEK; + } else { + long bytes_written = _hwrite(m_hmmio, m_pchBuffer, cch); + + if (bytes_written != -1 && bytes_written == cch) { + m_lDiskOffset += bytes_written; + m_pchNext = m_pchBuffer; + m_pchEndRead = m_pchBuffer; + m_dwFlags &= ~MMIO_DIRTY; + } else { + result = MMIOERR_CANNOTWRITE; + } + } + + m_lDiskOffset = _llseek(m_hmmio, 0, SEEK_CUR); + + } + + m_lBufOffset += cch; + if ((!rwmode || rwmode == MMIO_READWRITE) && cch > 0) { + if (m_lBufOffset != m_lDiskOffset) { + m_lDiskOffset = _llseek(m_hmmio, m_lBufOffset, SEEK_SET); + } + + // if previous seek failed + if (m_lBufOffset != m_lDiskOffset) { + result = MMIOERR_CANNOTSEEK; + m_lDiskOffset = _llseek(m_hmmio, 0, SEEK_CUR); + } else { + int bytes_read = _hread(m_hmmio, m_pchBuffer, cch); + + if (bytes_read == -1) { + result = MMIOERR_CANNOTREAD; + m_lDiskOffset = _llseek(m_hmmio, 0, SEEK_CUR); + } else { + m_lDiskOffset += bytes_read; + m_pchNext = m_pchBuffer; + m_pchEndRead = m_pchBuffer + bytes_read; + } + } + } + } else { + result = MMIOERR_UNBUFFERED; + } + + return result; } // OFFSET: LEGO1 0x100cce60 -unsigned short MXIOINFO::Descend(LPMMCKINFO pmmcki, const MMCKINFO *pmmckiParent, UINT fuDescend) +unsigned short MXIOINFO::Descend(MMCKINFO *pmmcki, const MMCKINFO *pmmckiParent, unsigned short fuDescend) { - return 0; + unsigned short result = 0; + + if (!pmmcki) + return MMIOERR_BASE; // ? + + if (!fuDescend) { + pmmcki->dwFlags = 0; + if (Read((char *)pmmcki, 8) != 8) { + result = MMIOERR_CANNOTREAD; + } else { + if (m_pchBuffer) { + pmmcki->dwDataOffset = m_pchNext - m_pchBuffer + m_lBufOffset; + } else { + pmmcki->dwDataOffset = m_lDiskOffset; + } + + if (pmmcki->ckid == FOURCC_RIFF || pmmcki->ckid == FOURCC_LIST) { + if (Read((char *)&pmmcki->fccType, 4) != 4) { + result = MMIOERR_CANNOTREAD; + } + } + } + } else { + DWORD ofs = MAXLONG; + + if (pmmckiParent) + ofs = pmmckiParent->cksize + pmmckiParent->dwDataOffset; + + BOOL running = TRUE; + MMCKINFO tmp; + BOOL read_ok = FALSE; + tmp.dwFlags = 0; + + // This loop is... something + do { + if (Read((char *)&tmp, 8) != 8) { + // If the first read fails, report read error. Else EOF. + result = read_ok ? MMIOERR_CHUNKNOTFOUND : MMIOERR_CANNOTREAD; + running = FALSE; + } else { + read_ok = TRUE; + if (m_pchBuffer) { + tmp.dwDataOffset = m_pchNext - m_pchBuffer + m_lBufOffset; + } else { + tmp.dwDataOffset = m_lDiskOffset; + } + + if (ofs < tmp.dwDataOffset) { + result = MMIOERR_CHUNKNOTFOUND; + running = FALSE; + } else { + if ((fuDescend == MMIO_FINDLIST && tmp.ckid == FOURCC_LIST) || + (fuDescend == MMIO_FINDRIFF && tmp.ckid == FOURCC_RIFF)) { + if (Read((char *)&tmp.fccType, 4) != 4) { + result = MMIOERR_CANNOTREAD; + } else { + if (pmmcki->fccType != tmp.fccType) + continue; + } + running = FALSE; + } else { + if (pmmcki->ckid != tmp.ckid) { + if (Seek((tmp.cksize&1)+tmp.cksize, SEEK_CUR) != -1) { + continue; + } else { + result = MMIOERR_CANNOTSEEK; + } + } + running = FALSE; + } + } + } + + } while (running); + + if (!result) + memcpy(pmmcki, &tmp, sizeof(MMCKINFO)); + + } + + return result; } \ No newline at end of file diff --git a/LEGO1/mxioinfo.h b/LEGO1/mxioinfo.h index c88a55b9..d69d2bf0 100644 --- a/LEGO1/mxioinfo.h +++ b/LEGO1/mxioinfo.h @@ -12,13 +12,39 @@ class MXIOINFO __declspec(dllexport) ~MXIOINFO(); unsigned short Open(const char *filename, DWORD fdwOpen); - void Close(long arg); + unsigned short Close(long arg); LONG Seek(LONG lOffset, int iOrigin); unsigned long Read(HPSTR pch, LONG cch); - void SetBuffer(LPSTR pchBuffer, LONG cchBuffer, LONG unk); - unsigned short Descend(LPMMCKINFO pmmcki, const MMCKINFO *pmmckiParent, UINT fuDescend); + unsigned short SetBuffer(LPSTR pchBuffer, LONG cchBuffer, LONG unk); + unsigned short Flush(UINT); + unsigned short Advance(UINT); + unsigned short Descend(MMCKINFO *pmmcki, const MMCKINFO *pmmckiParent, unsigned short fuDescend); - MMIOINFO m_info; + // The following is the MMIOINFO struct but with h_mmio set to type HFILE. + + /* general fields */ + DWORD m_dwFlags; /* 0 general status flags */ + FOURCC m_fccIOProc; /* 4 pointer to I/O procedure */ + LPMMIOPROC m_pIOProc; /* 8 pointer to I/O procedure */ + UINT m_wErrorRet; /* c place for error to be returned */ + HTASK m_htask; /* 10 alternate local task */ + + /* fields maintained by MMIO functions during buffered I/O */ + LONG m_cchBuffer; /* 14 size of I/O buffer (or 0L) */ + HPSTR m_pchBuffer; /* 18 start of I/O buffer (or NULL) */ + HPSTR m_pchNext; /* 1c pointer to next byte to read/write */ + HPSTR m_pchEndRead; /* 20 pointer to last valid byte to read */ + HPSTR m_pchEndWrite; /* 24 pointer to last byte to write */ + LONG m_lBufOffset; /* 28 disk offset of start of buffer */ + + /* fields maintained by I/O procedure */ + LONG m_lDiskOffset; /* 2c disk offset of next read or write */ + DWORD m_adwInfo[3]; /* 30 data specific to type of MMIOPROC */ + + /* other fields maintained by MMIO */ + DWORD m_dwReserved1; /* 3c reserved for MMIO use */ + DWORD m_dwReserved2; /* 40 reserved for MMIO use */ + HFILE m_hmmio; /* 44 handle to open file */ }; #endif // MXIOINFO_H