// ACFile.cpp
// Implementation of class cACFile

#include "stdafx.h"
#include "DatFile.h"

#define AC_NUMFILELOC 0x03E
#define AC_ROOTDIRPTRLOC 0x148

#define new DEBUG_NEW

cDatFile::cFile::cFile( cDatFile *pSource, BYTE *pFirstSector, DWORD dwSize )
: m_pFirstSector( pFirstSector ),
m_pCurrentSector( pFirstSector ),
m_pCurrentByte( pFirstSector + sizeof( DWORD ) ),
m_dwSize( dwSize ),
m_dwOffset( 0 ),
m_pSource( pSource )
{
}

void cDatFile::cFile::reset()
{
   m_pCurrentSector = m_pFirstSector;
   m_pCurrentByte = m_pFirstSector + sizeof( DWORD );
   m_dwOffset = 0;
}

DWORD cDatFile::cFile::read( BYTE *pbBuffer, DWORD dwSize )
{
   // Check if we can fit
   if( dwSize + m_dwOffset > m_dwSize )
      dwSize = m_dwSize - m_dwOffset;

   DWORD dwRemaining = dwSize;
   while( dwRemaining > 0 )
   {
      if( ( m_pCurrentByte + dwRemaining ) > m_pCurrentSector + m_pSource->m_dwSectorSize )
      {
         // We are reading over a sector boundary, read what we've got and reset for the next sector
         DWORD dwSection = ( m_pCurrentSector + m_pSource->m_dwSectorSize ) - m_pCurrentByte;

         ::memcpy( pbBuffer, m_pCurrentByte, dwSection );
         m_pCurrentSector = m_pSource->m_pData + *reinterpret_cast< DWORD * >( m_pCurrentSector );
         m_pCurrentByte = m_pCurrentSector + sizeof( DWORD );

         dwRemaining -= dwSection;
         pbBuffer += dwSection;
      }
      else
      {
         ::memcpy( pbBuffer, m_pCurrentByte, dwRemaining );
         m_pCurrentByte += dwRemaining;
         dwRemaining = 0;
      }
   }

   m_dwOffset += dwSize;
   return dwSize;
}

DWORD cDatFile::cFile::skip( DWORD dwSize )
{
   // Check if we can fit
   if( dwSize + m_dwOffset > m_dwSize )
      dwSize = m_dwSize - m_dwOffset;

   DWORD dwRemaining = dwSize;
   while( dwRemaining > 0 )
   {
      if( ( m_pCurrentByte + dwRemaining ) > m_pCurrentSector + m_pSource->m_dwSectorSize )
      {
         // We are reading over a sector boundary, read what we've got and reset for the next sector
         DWORD dwSection = ( m_pCurrentSector + m_pSource->m_dwSectorSize ) - m_pCurrentByte;

         m_pCurrentSector = m_pSource->m_pData + *reinterpret_cast< DWORD * >( m_pCurrentSector );
         m_pCurrentByte = m_pCurrentSector + sizeof( DWORD );

         dwRemaining -= dwSection;
      }
      else
      {
         m_pCurrentByte += dwRemaining;
         dwRemaining = 0;
      }
   }

   m_dwOffset += dwSize;
   return dwSize;
}

cDatFile::cDatFile( LPCTSTR szFilename, DWORD dwSectorSize )
: m_hFile( NULL ),
m_hMapping( NULL ),
m_pData( NULL ),
m_dwSectorSize( dwSectorSize )
{
   m_hFile = ::CreateFile( szFilename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL );

   if( m_hFile == INVALID_HANDLE_VALUE )
      throw std::exception();

   // Proceed to create the file mapping
   m_hMapping = ::CreateFileMapping( m_hFile, NULL, PAGE_READONLY | SEC_COMMIT, 0, 0, NULL );
   if( m_hMapping == NULL )
   {
      ::CloseHandle( m_hFile );
      throw std::exception();
   }

   m_pData = reinterpret_cast< BYTE * >( ::MapViewOfFileEx( m_hMapping, FILE_MAP_READ, 0, 0, 0, NULL ) );

   if( m_pData == NULL )
   {
      ::CloseHandle( m_hMapping );
      ::CloseHandle( m_hFile );
      throw std::exception();
   }
}

cDatFile::~cDatFile()
{
   ::UnmapViewOfFile( m_pData );
   ::CloseHandle( m_hMapping );
   ::CloseHandle( m_hFile );
}

cDatFile::cFile cDatFile::getFile( DWORD dwFileNumber )
{
   cDirectory dir;

   // Search for our golden file
   for ( DWORD dwDirStart = *reinterpret_cast< DWORD * >( m_pData + AC_ROOTDIRPTRLOC ); dwDirStart != 0; )
   {
      cFile dir_entry( this, m_pData + dwDirStart, sizeof( cDirectory ) );
      dir_entry.read( reinterpret_cast< BYTE * >( &dir ), sizeof( cDirectory ) );

      // Now, the files are located like triplets, so copy the triplets
      cFileEntry *pEntry = dir.m_files,
         *pEndEntry = pEntry + dir.m_dwFiles;

      for( cFileEntry *pIter = pEntry; pIter != pEndEntry && pIter->m_dwID < dwFileNumber; ++ pIter );

      // We either got an exact match - or we attempt to further narrow down the file
      if( pIter != pEndEntry )
      {
         // We found some sort of match
         if( pIter->m_dwID == dwFileNumber )
            // This is an exact match hooray
            return cFile( this, m_pData + pIter->m_dwOffset, pIter->m_dwSize );
      }

      // We have an inexact match, but now we attempt to recurse
      if( dir.m_subdirs[ 0 ] == 0 )
         // If the first entry in the directory is 0, there are no
         // helpers - we lose, it's not here
         break;

      dwDirStart = dir.m_subdirs[ pIter - pEntry ];
   }

   // If we get here, the file wasn't found - sniff
   throw std::exception();
}
