Undeleting in Windows 95/98/Me/NT/2000/XP

Environment: Visual C++

Introduction

Undeleting files is necessary if you have accidentally deleted files even from the Recycle Bin or if the files or folders have been purposely tampered with. The undeleting process requires lots of internals of the disk file system. This can be done in Windows 95/98/Me by using a thunking mechanism and in Windows NT/2000/XP by using CreateFile API.

The FAT file system is the 12- and 16-bit file systems, VFAT is the FAT with long file names, and FAT32 is the latest breed of the file system that is handled, starting from Win95 OSR 2 to Windows XP. NT 4 does not handle the FAT32 file system; however, by using the utility that I have provided in this article, it is possible to read even FAT32 in NT.

Reading and Writing Disks in Windows 95+

Many methods exist to read disks in Win 95+. I have chosen the thunking of DLLs method to read the disks. This is nothing but executing the real-mode BIOS Int 13h from within the protected mode. This way, we can read the disks as if we were in real-mode DOS. However, it requires two DLLs, called the thunk DLLs. One is a 32-bit DLL which thunks down to a 16-bit DLL that will communicate to the real-mode BIOS and fetch the required buffer of data from the hard disks. The 32-bit DLL in the protected mode translates the linear pointers and sends them down to the 16-bit DLL, which in turn calls the real-mode BIOS using the DOS Protected Mode Interface (DPMI). We have to set up a part of real-mode memory for fetching the buffer, which is mapped to the memory of the 16-bit DLL in the protected mode, which is copied to the linear memory by the routine that handles reading the buffer. For writing, the process is just the reverse; first, the buffer in the linear memory is copied to the protected mode 16-bit memory that is mapped to the real mode memory which is written to the disk.

However, we need to write a small VxD for reading CMOS memory. This could have also been done as an application DLL but it is wiser to do it as a VxD because, by using this, we would not violate the rule that Kernel-level work is being done on the application level. The CMOS reading is essential because we need to know the number of floppy disks installed in the system. In Windows 95+, there are no restrictions on how the system is being used; the Kernel and Application levels (Ring 0 and Ring 3) are seamlessly integrated where the memories can be allocated by anything and accessed by anything.

Reading and Writing Disks in Windows NT+

In NT, the reading and writing mechanisms are entirely different. Because of the security restrictions, we cannot call the BIOS or the real-mode interrupts, but only access the disks using the APIs that NT offers. The API for accessing the disks is CreateFile, which opens a disk as if it were a file, and then the disk is read or written as if it were a file except that it can be done only in chunks of sectors. The disk’s reads and writes are treated just like files except that it does direct disk IO.

Int 13h Extensions

BIOS offers Int 13h extensions in the latest motherboards, starting some years back. This is necessary because in the old BIOS it is not possible to read beyond 8 GB of hard disk space because of register restrictions, which is the mechanism for reading and writing disks. I have handled Int 13h extensions in the thunk 16-bit DLL. Certain BIOS interrupts are added; this allows up to two-power, 64-bit number of sectors to be accessed. This is way beyond one’s imagination and can be used for decades without altering BIOS any more.

NT internally handles Int 13h extensions, so there is no need to handle the extensions separately.

Disk Organization in the FAT File System

In the FAT file system, disk organization is straightforward and simple. The organization of the files ensures that the files are accessible through a simple walkthrough of pointers in a random fashion. This way, any file anywhere in the disk can be located with a simple mechanism. FAT12, or 12-bit FAT, is the lowest member of the FAT family, where the file pointers are just 1.5 bytes. FAT, or 16-bit FAT, is the most widely used in hard disks that have 16-bits or 2 bytes per pointer. VFAT is used in Win95 onwards; it is the FAT with long file name support. FAT32, or 32-bit FAT, is the highest member; it can handle huge hard disks as a single partition, and it is used from Win95 OSR 2 onwards. NT 4 does not manipulate FAT32; hence, FAT is the lowest common member of the FAT file system. Starting from Windows 2000, the OS handles FAT32 and hence we manipulate this file system also.

The boot sector comes first in FAT, followed by hidden and reserved sectors. Then come the FAT tables. The FAT tables consist of pointers to the next cluster occupied by a file. The pointers point to the next clusters, finally terminated by a word or a double word, depending on the file system. The bad clusters are marked with a unique word or dword; thus, they are skipped during the assigning process. Two copies of the FATs are stored for security reasons because this is the most important part of the file system. If any FAT sectors become unusable, then they are skipped and automatically the second copy is used. The FAT is present in a fixed location of the disk at the start of the partition. Then, after the two copies of FAT, comes the root directory. This is a 32-byte packet with file information coded in it. The Root directory is fixed in FAT12 and FAT16 but is just another sub-directory in FAT32. If they are fixed, only a certain amount of files can be stored in the directory; otherwise, as in FAT32, they can store enormous amounts of files. Long file names are stored as packed 32-byte unicode strings. We will soon see the routine that manipulates the Long File Names in the program.

Then come the files and sub-directories that are organized only with the help of the FAT tables. The starting cluster number is stored in the directory entry and it points to the next cluster in FAT table that is occupied by the file. This phenomenon continues until the end of file mark is reached.

Utility Program Overview to Undeleting Files

The program for manipulating the sectors of the disk looks like the following figure. The program has two views, like Explorer. The left portion of the screen is the disks that can be undeleted, the right contains the sectors or clusters. There are special features; for example, you can go to a particular sector. The status line displays the current sector and cluster. The first pane in the status bar displays if the program is performing a calculation.

The FAT, Root Directory, Boot Record, Partition Table, and sub-directories are the files that can be displayed as raw sectors or as decoded entries in the program. The program uses Tree View to display the disks and uses the list view to display the sectors. The tree view stores the whereabouts of the pointers to the disks. The lParam of the tree view stores the pointers. The pointers point to packets of information about the disks. The packets contain various information, as will be described later. If you click on a deleted file marked “Deleted” and then issue the undelete command, the file will be undeleted from the disk if it is possible and the file is not fragmented. The editing feature is available in the NTFS disk. The partition will be displayed as an Unknown partition but editing the disk will work in that also. You can inspect the raw sector layout of the NTFS partition using this utility software.

Inside Programming

The utility was developed using Visual C++ 6 and DDK. It also uses some programs such as THUNK.EXE from the Platform SDK for compiling the thunk script. The thunk DLLs can be used without changing them in any programs. There is also a device driver for reading CMOS memory to know the number of floppy disks in the system. I used BC++ 3.1 for compiling the 16-bit thunk DLL. The main project uses MFC and was compiled using VC++ 6.

First I would like to give the structure of the boot record completely, including the FAT32 version of it. I give the structure of the boot record and explain its fields:

typedef struct{
  char Jmp[3];
  char OEM_Name[8];
  WORD BytesPerSector;
  unsigned char SectorsPerCluster;
  WORD ReservedSectors;
  unsigned char NumFATs;
  WORD NumRoot;
  WORD TotalSectors;
  unsigned char MediaDB;
  WORD SectorsPerFAT;
  WORD SectorsPerTrack;
  WORD NumHeads;
  DWORD NumHidSect;
  DWORD ExtendedTotalSectors;
  DWORD ExtendedSectorsPerFAT;
  WORD Flags;
  WORD Version;
  DWORD RootStartCluster;
  WORD FSInfoSec;
  WORD BkUpBootSec;
  WORD Reserved;
}BOOT;

The RootStartCluster is new to the boot record. This field contains a 32-bit, starting cluster number of the root directory of the FAT32 drive. This is because the root directory is just another sub-directory in FAT32. There is also a backup boot sector in FAT32, which is denoted in BkUpBootSec.

The partition table is the first sector in a disk and contains information on the whereabouts of the disks in the system. It can have four partitions; the value of 0x80 in a field signifies the active partition. I will explain about the organization of the disk after showing the partition table:

typedef struct{
  BYTE BootInd;
  BYTE Head;
  BYTE Sector;
  BYTE Cylinder;
  BYTE SysInd;
  BYTE LastHead;
  BYTE LastSector;
  BYTE LastCylinder;
  DWORD RelativeSector;
  DWORD NumberSectors;
}PARTITION;

The BootInd at the top should be 0x80 to denote the active partition. For other partitions it should be 0. The SysInd has the following values and meanings:

#define PART_UNKNOWN 0x00        //Unknown.
#define PART_DOS2_FAT 0x01       //12-bit FAT.
#define PART_DOS3_FAT 0x04       //16-bit FAT. Partition smaller
                                 //than 32MB.
#define PART_EXTENDED 0x05       //Extended MS-DOS Partition.
#define PART_DOS4_FAT 0x06       //16-bit FAT. Partition larger than
                                 //or equal to 32MB.
#define PART_DOS32 0x0B          //32-bit FAT. Partition up to 2047GB.
#define PART_DOS32X 0x0C         //Same as PART_DOS32(0Bh), but uses
                                 //Logical Block Address Int 13h
                                 //extensions.
#define PART_DOSX13 0x0E         //Same as PART_DOS4_FAT(06h), but
                                 //uses Logical Block Address
                                 //Int 13h extensions.
#define PART_DOSX13X 0x0F        //Same as PART_EXTENDED(05h),
                                 //but uses Logical Block Address
                                 //Int 13h extensions.

The NTFS currently has a value of 7, but I am not dealing with it in this article.

The Head, Sector, and Cylinder have the values of the start of the partition. Note that these are byte values; hence, for disks greater than 8 GB, these values cannot be trusted and only the RelativeSector signifies the right values. The Last values signify the end of the partition. NumberSectors is the number of sectors for a partition belonging to this structure definition.

The partitions have been extended to handle many drives in the partition. This is accomplished by the following method:

The partition points to a Extended partition table that has the same structure but with an arrangement like linked lists. There must be one Primary partition and an extended partition which has a drive and pointer to another (extended) partition table. This setup continues until there are no more extended partitions. I have given you the deciphering of this setup in the code. Full working MFC-based source code to Undelete is provided online; you can download it from the Windows Developer Magazine Web site. The program stores the item’s data of the tree view in the memory as pointers to a structure which is described below.

typedef struct{
  WORD Drive;
  WORD Cylinder;
  WORD Head;
  WORD Sector;
  DWORD NumSectors;
  WORD Type;
  WORD FatType;
  WORD FatRelativeSector;
  DWORD NumFatSectors;
  char *Lfn;
  DWORD RelativeSector;
  WORD TotalHeads;
  DWORD StartCluster;
  WORD SectorsPerTrack;
  WORD SectorsPerCluster;
  WORD Attributes;
  DWORD TotalSectors;
  DWORD NTRelativeSector;
  DWORD DataAreaSector;
  BOOL Flag;
}DRIVEPACKET;

A large chunk of memory is allocated during the init phase of the program. This memory is used to store all the packets of information.

Drive is the drive number that this packet belongs to. The Cylinder, Head, and Sector point to the drive’s starting location that this packet belongs to; NumSectors is the number of sectors. Type points to one of the following values:

#define PART_TABLE 0
#define BOOT_RECORD 1
#define EXTENDED_PART 2
#define FAT16 3
#define FAT12 4
#define ROOT_DIR 5
#define FILES_FOLDERS 6
#define SECTORS_DUMP 7
#define RAW_DUMP 8
#define FAT32 9
#define SUB_DIR 10
#define UNKNOWN 11

FatType points to FAT12, FAT16, or FAT32, depending on the FAT type. FatRelativeSector points to the start of the FAT table within the partition. NumFatSectors is the number of FAT sectors. The *Lfn points to the full name of the Long File Name that this packet points to. Note that not all packets that belong to different catagories have all the fields pointing to some values. For some packets the values are significant and for others it is not. For example, in a packet pointing to PART_TABLE, the Lfn is not pointing to anything and the value is undefined. The RelativeSector points to a sector within the partition that this packet points to. This value is offset from the start of the partition pointed to by Cylinder, Head, and Sector. NTRelativeSector points to the relative sector from the start of the disk. It is for dealing with the disks in NT/2000/XP. However, this value comes in handy when dealing with disks bigger than 8 GB, in which case the Cylinder, Head, and Sector values are neglected and this value is used throughout. Flag is the flag that signifies whether extension to Int 0x13 is available in the system. This value is used by the 16-bit DLL to know whether NTRelativeSector is to be used or the Cylinder, Head, and Sector are to be used.

Now we come to the real program that is written in MFC. I will display the important portions of it and explain them. First, the program initializes the packets at the start, using the following routine. This routine is called if the program refreshes the drives.

void EnumTreeRoot(void)
{
  char TmpStr1[1000];
  LocalUnlock(hMemPackets);
  LocalUnlock(hMemDrivePacket);
  Packets = (DRIVEPACKET *) LocalLock(hMemPackets);
  DrivePacket = (DRIVEPACKET *) LocalLock(hMemDrivePacket);
  m_wndStatusBarCtrl->SetText("Wait....", 0, 0);
  for(DWORD i=0; i<NumFloppies; i++){
    wsprintf(TmpStr, "Floppy Disk %lu", i+1);
    TvI.mask = TVIF_CHILDREN | TVIF_TEXT | TVIF_PARAM;
    TvI.pszText = TmpStr;
    TvI.cChildren = 1;
    TvI.lParam = (long)DrivePacket;
    DrivePacket->Drive = (WORD) i;
    DrivePacket->Cylinder = 0;
    DrivePacket->Head = 0;
    DrivePacket->Sector = 1;
    DrivePacket->NumSectors = 1;
    DrivePacket->Type = BOOT_RECORD;
    DrivePacket->RelativeSector = 0;
    DrivePacket->NTRelativeSector = 0;
    DrivePacket->Flag = 0;

    TvIns.item = TvI;
    TvIns.hParent = TVI_ROOT;
    TvIns.hInsertAfter = TVI_LAST;
    DiskTree->InsertItem((LPTVINSERTSTRUCT) &TvIns);
    DrivePacket++;
  }
  for(i=0;;i++){
    if(gOSWin95){
      if(!(DllThunk32)(i+0x80, 0, 0, 1, 1,
                       (LPBYTE) TmpStr1, 0, FALSE))
        break;
    }
    else{
      if(hDisk[i+0x80])
        CloseHandle(hDisk[i+0x80]);
      char TmpStr[26] = "\\\\.\\PHYSICALDRIVE0";
      TmpStr[17] = i+'0';
      if((hDisk[i+0x80]=CreateFile(TmpStr,
                                   GENERIC_READ|GENERIC_WRITE,
                                   FILE_SHARE_READ,
                                   NULL,
                                   OPEN_EXISTING,
                                   FILE_FLAG_NO_BUFFERING,
                                   NULL))==INVALID_HANDLE_VALUE)
        break;
    }
    wsprintf(TmpStr, "Hard Disk %lu", i+1);
    TvI.mask = TVIF_CHILDREN | TVIF_TEXT | TVIF_PARAM;
    TvI.pszText = TmpStr;
    TvI.cChildren = 1;
    TvI.lParam = (long)DrivePacket;
    DrivePacket->Drive = 0x80+i;
    DrivePacket->Cylinder = 0;
    DrivePacket->Head = 0;
    DrivePacket->Sector = 1;
    DrivePacket->NumSectors = 1;
    DrivePacket->Type = PART_TABLE;
    DrivePacket->RelativeSector = 0;
    DrivePacket->NTRelativeSector = 0;
    if(gOSWin95)
      DrivePacket->Flag = (DllCheckInt13Extension)(0x80+i);
    else
      DrivePacket->Flag = 0;

    NumHards++;

    TvIns.item = TvI;
    TvIns.hParent = TVI_ROOT;
    TvIns.hInsertAfter = TVI_LAST;
    hPrevHD = DiskTree->InsertItem((LPTVINSERTSTRUCT) &TvIns);
    DrivePacket++;
  }
  m_wndStatusBarCtrl->SetText("", 0, 0);
}

The 95 and NT portions are separated by the variable gOSWin95, which is TRUE if the OS is Win95/98/Me; otherwise, it should be NT. The disk is handled using the thunk if the OS is Win95/98/Me; otherwise, it is handled using CreateFile, which is a built-in way of opening the disk in NT/2000/XP. The tree items are inserted as needed for the root items.

Then comes the item-expanding message handler. This is called if the item in the tree view is expanded by clicking on the +. The routine is described in part below:

void CLeftView::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult)
{
  PARTITION *PartitionTable;
  BOOL UnknownPart=FALSE;
  int i;
  NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
  hPrev = 0;
  // TODO: Add your control notification handler code here
  m_wndStatusBarCtrl->SetText("", 0, 0);
  *pResult = 0;
  if(pNMTreeView->itemNew.state & TVIS_EXPANDEDONCE)
    return;
  DRIVEPACKET *DrivePacket =
         (DRIVEPACKET *) pNMTreeView->itemNew.lParam;
  memcpy(&gDrivePacket, DrivePacket, sizeof(DRIVEPACKET));
  memcpy(&ParentDrivePacket, DrivePacket, sizeof(DRIVEPACKET));

  switch(DrivePacket->Type)
  {
  case PART_TABLE:{
    PrevRelSector = MainPrevRelSector = 0;
    hMemBufferDir = LocalAlloc(LMEM_MOVEABLE|LMEM_DISCARDABLE|
                        LMEM_ZEROINIT,
                        DrivePacket->NumSectors * 512);
    if(hMemBufferDir == NULL){
      AfxMessageBox("Fatal Error!  Not able to allocate a Buffer!");
      return;
    }
    BufferDir = (LPBYTE) LocalLock(hMemBufferDir);
    if(gOSWin95){
      if(!(DllThunk32)(DrivePacket->Drive, DrivePacket->Cylinder,
     DrivePacket->Head,
          DrivePacket->Sector, DrivePacket->NumSectors,
     (LPBYTE)BufferDir, DrivePacket->NTRelativeSector,
      DrivePacket->Flag)){
        AfxMessageBox("Partition table not accessible!");
        return;
      }
    }
    else{
      __int64 Tmp64 =
            ((__int64) DrivePacket->NTRelativeSector) * 512;
      long TmpVal = Tmp64 & 0xFFFFFFFF;
      long TmpValHi = (Tmp64 >> 32);
      SetFilePointer(hDisk[DrivePacket->Drive],
                     TmpVal,
                     &TmpValHi,
    FILE_BEGIN);
      TmpVal = 0;
      ReadFile(hDisk[DrivePacket->Drive],
               BufferDir,
               DrivePacket->NumSectors * 512,
               (DWORD *) &TmpVal,
               NULL);
      if(TmpVal != (DrivePacket->NumSectors * 512)){
        AfxMessageBox("Partition table not accessible!");
        return;
      }
    }
    PartitionTable = (PARTITION *) (BufferDir+0x1BE);
    UnknownPart = FALSE;
    for(i=0; i<4; i++){
      switch(PartitionTable->SysInd)
      {
      case 0:
        continue;
        break;
      case PART_DOS2_FAT:
        strcpy(TmpStr, "12-Bit FAT Disk");
        break;
      case PART_DOS3_FAT:
        strcpy(TmpStr, "16-Bit FAT Disk");
        break;
      case PART_EXTENDED:
        strcpy(TmpStr, "Extended DOS Partition");
        break;
      case PART_DOS4_FAT:
        strcpy(TmpStr, "16-Bit FAT Disk");
        break;
      case PART_DOS32:
        strcpy(TmpStr, "32-Bit FAT Disk");      //Normal FAT32
        break;
      case PART_DOS32X:
        strcpy(TmpStr, "32-Bit FAT Disk");      //FAT32 with int
                                                //13 extension
        break;
      case PART_DOSX13:
        strcpy(TmpStr, "16-Bit FAT Disk");      //Extended int 13
        break;
      case PART_DOSX13X:
        strcpy(TmpStr, "Extended 32-bit Partition");
                      //Extended DOS partition in int 13 extension
        break;
      default:
        UnknownPart = TRUE;
        strcpy(TmpStr, "Unknown");
        break;
      }
      Packets->Cylinder = PartitionTable->Cylinder;
      Packets->Drive = DrivePacket->Drive;
      Packets->Flag = DrivePacket->Flag;
      Packets->Head = PartitionTable->Head;
      Packets->Sector = PartitionTable->Sector;
      Packets->NumSectors = 1;
      Packets->Type =
           ((PartitionTable->SysInd == PART_EXTENDED) ||
            (PartitionTable->SysInd == PART_DOSX13X)) ?
    EXTENDED_PART:BOOT_RECORD;
      if(UnknownPart)
        Packets->Type = UNKNOWN;
      Packets->FatType =
           ((PartitionTable->SysInd == PART_DOS32) ||
            (PartitionTable->SysInd == PART_DOS32X)) ? FAT32:0;
      if((PartitionTable->SysInd == PART_EXTENDED) ||
         (PartitionTable->SysInd == PART_DOSX13X)){
        MainPrevRelSector = PartitionTable->RelativeSector;
        Packets->NTRelativeSector = MainPrevRelSector;
      }
      else{
        Packets->NTRelativeSector = MainPrevRelSector +
     PartitionTable->RelativeSector;
      }

      TvI.mask = TVIF_CHILDREN | TVIF_TEXT | TVIF_PARAM;
      TvI.pszText = TmpStr;
      if(UnknownPart){
//        Packets->NumSectors = 0xFFFFFFFF;
          Packets->RelativeSector = 1;
          TvI.cChildren = 0;
      }
      else{
        Packets->RelativeSector = 0;
        TvI.cChildren = 1;
      }
      TvI.lParam = (long)Packets++;

      TvIns.item = TvI;
      TvIns.hParent = (HTREEITEM) pNMTreeView->itemNew.hItem;
      TvIns.hInsertAfter = hPrev;
      hPrev = DiskTree->InsertItem((LPTVINSERTSTRUCT) &TvIns);
      PartitionTable++;
    }
    LocalUnlock(hMemBufferDir);
    LocalFree(hMemBufferDir);
    break;
    }

The partition table expansion is explained here. The partition table sector is loaded in the memory using the thunk DLLs if it is Windows 95/98/Me, and the CreateFile if it is Windows NT/2000/XP. Note that the disk is loaded in the memory as if the disk were a file using ReadFile in NT. Then it is checked for the various partition types and action taken accordingly to display it. Finally, the packet is prepared to point to the child items and initialized accordingly. The other items of the expansion are handled similarly.

Undeleting Files and Folders

The undeleting of files and folders is done in the following sub-routine. The FAT entries contain 0 in the empty areas and contain a sequencer in the other areas of it. If it is a 32-bit FAT, it contains 4 bytes per pointer. This pointer sequences to the next entry, which continues until the end is reached; this is signified by 0xFFFFFFFF. This is the end-of-file marker. When the files are being deleted, the OS simply leaves the directory entry intact except for the first byte, which will be 0xE5. Start Cluster, which is a pointer to the FAT entry and the sequencing beginner, is left intact. It is because of this that we are able to undelete the files. Using this information, it is possible to track down all the FAT entries to find the whereabouts of the file or folder which had been deleted. Using the length it is possible to find out about how many entries have to be fixed. Once this process is complete, the file undeletion is completed.

void CMainFrame::OnUndelete()
{
  // TODO: Add your command handler code here
  DWORD NumFatEntries;
  HLOCAL hMemTempClust;
  if((gListViewType != FILES_FOLDERS) &&
     (gListViewType != ROOT_DIR) &&
    (gListViewType != SUB_DIR))
    return;
  if(ItsFile)
    return;
  POSITION pos = ListView->GetFirstSelectedItemPosition();
  if(pos == NULL)
    return;
  DRIVEPACKET TmpDP;
  memcpy(&TmpDP, &ParentDrivePacket, sizeof(TmpDP));
  HLOCAL hFat = LocalAlloc(LMEM_MOVEABLE|LMEM_DISCARDABLE,
    TmpDP.NumFatSectors*512);
  LPBYTE pMemFat = (LPBYTE) LocalLock(hFat);
  TmpDP.NumSectors = TmpDP.NumFatSectors;
  TmpDP.RelativeSector = TmpDP.FatRelativeSector;
  if(!LoadSectors(&TmpDP, &CylHeadSect, pMemFat)){
    TmpDP.RelativeSector += TmpDP.NumFatSectors;
    if(!LoadSectors(&TmpDP, &CylHeadSect, pMemFat)){
      AfxMessageBox("FATs cannot be read successfully!");
      return;
    }
  }
  m_wndStatusBarCtrl->SetText("Undeleting: Wait....", 0, 0);
  while(pos){
    int IndexOfItem = ListView->GetNextSelectedItem(pos);
    FILEPACKET *FilePacket = (FILEPACKET *)
    ListView->GetItemData(IndexOfItem);
    if((FilePacket->StartCluster == 0) ||
    (FilePacket->StartCluster == 0xFFFFFFFF))
      continue;
    DWORD FatCluster = GetNextCluster(FilePacket->StartCluster,
                                      TmpDP.FatType, pMemFat);
    if(((BYTE)FilePacket->Name[0] != 0xE5)  &&
       (FilePacket->Name[0] != 0x05))
      continue;
    if(FatCluster != 0){
      AfxMessageBox("File Cluster already occupied by another file!!
     Undelete not possible!");
      continue;
    }
    DWORD NumFileClust=0, NextCluster=0;
    if(FilePacket->Attributes & ATTR_SUBDIR){
      LocalUnlock(hMemTempClust);
      LocalFree(hMemTempClust);
      hMemTempClust = LocalAlloc(LMEM_MOVEABLE|LMEM_DISCARDABLE|
     LMEM_ZEROINIT, ParentDrivePacket.SectorsPerCluster * 512);
      DIRECTORY *pMemTempClust =
               (DIRECTORY *) LocalLock(hMemTempClust);
      NextCluster = FilePacket->StartCluster;
      DWORD i=0;
      if(!NextCluster)
        continue;
      while(i == 0){
        DRIVEPACKET TmpDrivePacket;
        DWORD TmpVal=0;
        memcpy(&TmpDrivePacket,
               &ParentDrivePacket,
               sizeof(DRIVEPACKET));
        TmpDrivePacket.RelativeSector =
              ParentDrivePacket.DataAreaSector +
              ParentDrivePacket.SectorsPerCluster *
              (NextCluster-2);
        TmpDrivePacket.NumSectors =
              ParentDrivePacket.SectorsPerCluster;
        if(!LoadSectors(&TmpDrivePacket,
                        &CylHeadSect,
                        (PUCHAR) pMemTempClust))
          AfxMessageBox("Directory cluster not loadable!");
        i = GetNextCluster(++NextCluster,
                           ParentDrivePacket.FatType,
                           pMemFat);
        NumFileClust++;
        for(int j=0; j<(ParentDrivePacket.SectorsPerCluster*512);
            j+=32){
          if(((BYTE)pMemTempClust->Name[0] == 0xE5) ||
      (pMemTempClust->Name[0] == 0x05) ||
      (pMemTempClust->Name[0] == '.'))
            pMemTempClust++;
          else{
            i=1;        //double break trick
            NumFileClust--;
            break;
          }
        }
      }
  }
    else{
      NumFileClust = FilePacket->Size /
                     (TmpDP.SectorsPerCluster * 512);
      if(NumFileClust && (FilePacket->Size %
     (TmpDP.SectorsPerCluster * 512)))
        NumFileClust++;
    }
    NumFileClust--;
    NextCluster = FilePacket->StartCluster;
    int i=0;
    FatCluster = GetNextCluster(NextCluster,
                                TmpDP.FatType,
                                pMemFat);
    do{
      if(!NumFileClust)
        break;
      if(FatCluster !=0){
        NextCluster--;
        break;
      }
      OccupyFATCluster(NextCluster+1, NextCluster,
                       TmpDP.FatType, pMemFat);
      NextCluster++;
      FatCluster = GetNextCluster(NextCluster,
                                  TmpDP.FatType,
                                  pMemFat);
    }while(++i<NumFileClust);
    OccupyFATCluster(0xFFFFFFFF, NextCluster,
                     TmpDP.FatType, pMemFat);
    DRIVEPACKET TmpDrivePacket;
    memcpy(&TmpDrivePacket,
           &ParentDrivePacket,
           sizeof(DRIVEPACKET));
    LocalUnlock(hMemBuffer);
    LocalFree(hMemBuffer);
    LocalUnlock(hMemBufferFatEntries);
    LocalFree(hMemBufferFatEntries);
    if(gListViewType != ROOT_DIR){
      NumFatEntries = CountFatEntries(ParentDrivePacket.StartCluster,
                                      ParentDrivePacket.FatType,
                                      pMemFat);
      if(!NumFatEntries)
        continue;
      hMemBuffer =
          LocalAlloc(LMEM_MOVEABLE|LMEM_DISCARDABLE|LMEM_ZEROINIT,
          NumFatEntries * ParentDrivePacket.SectorsPerCluster * 512);
      if(hMemBuffer == NULL){
        AfxMessageBox("Fatal Error! Not able to allocate a buffer!");
        LocalUnlock(hMemBufferFatEntries);
        LocalFree(hMemBufferFatEntries);
        return;
      }
      HLOCAL hMemBufferFatEntries= LocalAlloc(LMEM_MOVEABLE|
    LMEM_DISCARDABLE|LMEM_ZEROINIT, NumFatEntries*4);
      if(hMemBufferFatEntries == NULL){
        AfxMessageBox("Fatal Error!  Not able to allocate a
        buffer for FAT Entries!");
        return;
      }
      BufferFatEntries = (LPDWORD) LocalLock(hMemBufferFatEntries);
      StoreFatEntries(ParentDrivePacket.StartCluster,
     ParentDrivePacket.FatType, BufferFatEntries, NumFatEntries,
     Buffer);
      if(!ReadDirBuffer(BufferFatEntries,
                        &TmpDrivePacket,
                        NumFatEntries,
     Buffer)){
        AfxMessageBox("Not able to fully load the sub-directory");
      }
      LocalUnlock(hMemBuffer);
      Buffer = (LPBYTE) LocalLock(hMemBuffer);
    }
    else{
      hMemBuffer =
         LocalAlloc(LMEM_MOVEABLE|LMEM_DISCARDABLE|LMEM_ZEROINIT,
                    ParentDrivePacket.NumSectors * 512);
      if(hMemBuffer == NULL){
        AfxMessageBox(
"Fatal Error! Not able to allocate a buffer of memory for root dir!");
        return;
      }
      Buffer = (LPBYTE) LocalLock(hMemBuffer);
      if(!LoadSectors(&TmpDrivePacket, &CylHeadSect, Buffer)){
        AfxMessageBox("Root Directory not fully accessible!");
      }
    }
    DIRECTORY *Direc = (DIRECTORY *) Buffer;
    for(i=0; i<=IndexOfItem; i++, Direc++){
      while((Direc->Attributes == 0x0F) || (Direc->Name[0] == '.'))
        Direc++;
    }
    Direc--;
    Direc->Name[0] = '!';
    if(gListViewType == ROOT_DIR){
      if(!WriteSectors(&TmpDrivePacket, &CylHeadSect, Buffer)){
        AfxMessageBox("Root Directory not fully accessible!");
      }
    }
    else
      if(!WriteDirBuffer(BufferFatEntries,
                         &TmpDrivePacket,
                         NumFatEntries,
                         Buffer))
        AfxMessageBox("Not able to fully write the sub-directory");

  }
  TmpDP.NumSectors = TmpDP.NumFatSectors;
  TmpDP.RelativeSector = TmpDP.FatRelativeSector;
  if(!WriteSectors(&TmpDP, &CylHeadSect, pMemFat)){
    AfxMessageBox("FAT 1 cannot be written successfully!");
  }
  TmpDP.RelativeSector += TmpDP.NumFatSectors;
  if(!WriteSectors(&TmpDP, &CylHeadSect, pMemFat)){
    AfxMessageBox("FAT 2 cannot be written successfully!");
  }
  AfxMessageBox("File(s) undeletion completed!");
  m_wndStatusBarCtrl->SetText("", 0, 0);
  LocalUnlock(hFat);
  LocalFree(hFat);
  LocalUnlock(hMemBuffer);
  LocalFree(hMemBuffer);
  LocalUnlock(hMemBufferFatEntries);
  LocalFree(hMemBufferFatEntries);
}

Renaming the Undeleted Files

After the files or folders have been undeleted, the first character is replaced with a “!” mark. This can be renamed using the program itself. Otherwise, if two programs that differed only by the first character were undeleted, then both will have the same name and Explorer cannot find the next occuring file or folder. The code that implements the renaming comes as follows:

void CMainFrame::OnRename()
{
  // TODO: Add your command handler code here
  CRename Crn;
  if(ListView->GetSelectedCount() != 1){
    AfxMessageBox("Only one file can be selected!");
    return;
  }
  if(Crn.DoModal()==IDCANCEL)
    return;
  DWORD NumFatEntries;
  POSITION pos = ListView->GetFirstSelectedItemPosition();
  if(pos == NULL)
    return;
  DRIVEPACKET TmpDP;
  memcpy(&TmpDP, &ParentDrivePacket, sizeof(TmpDP));
  HLOCAL hFat = LocalAlloc(LMEM_MOVEABLE|LMEM_DISCARDABLE,
	TmpDP.NumFatSectors*512);
  LPBYTE pMemFat = (LPBYTE) LocalLock(hFat);
  TmpDP.NumSectors = TmpDP.NumFatSectors;
  TmpDP.RelativeSector = TmpDP.FatRelativeSector;
  if(!LoadSectors(&TmpDP, &CylHeadSect, pMemFat)){
    TmpDP.RelativeSector += TmpDP.NumFatSectors;
    if(!LoadSectors(&TmpDP, &CylHeadSect, pMemFat)){
      AfxMessageBox("FATs cannot be read successfully!");
    }
  }
  int IndexOfItem = ListView->GetNextSelectedItem(pos);
  FILEPACKET *FilePacket = (FILEPACKET *)
	ListView->GetItemData(IndexOfItem);
  DRIVEPACKET TmpDrivePacket;
  memcpy(&TmpDrivePacket,
         &ParentDrivePacket,
         sizeof(DRIVEPACKET));
  LocalUnlock(hMemBuffer);
  LocalFree(hMemBuffer);
  LocalUnlock(hMemBufferFatEntries);
  LocalFree(hMemBufferFatEntries);
  if(gListViewType != ROOT_DIR){
    NumFatEntries =CountFatEntries(ParentDrivePacket.StartCluster,
    ParentDrivePacket.FatType, pMemFat);
    if(!NumFatEntries)
      return;
    hMemBuffer =
       LocalAlloc(LMEM_MOVEABLE|LMEM_DISCARDABLE|LMEM_ZEROINIT,
                  NumFatEntries * ParentDrivePacket.SectorsPerCluster
                  * 512);
    if(hMemBuffer == NULL){
      AfxMessageBox("Fatal Error!  Not able to allocate a buffer!");
      LocalUnlock(hMemBufferFatEntries);
      LocalFree(hMemBufferFatEntries);
      return;
    }
    HLOCAL hMemBufferFatEntries =
         LocalAlloc(LMEM_MOVEABLE|LMEM_DISCARDABLE|
                    LMEM_ZEROINIT, NumFatEntries*4);
    if(hMemBufferFatEntries == NULL){
      AfxMessageBox(
 "Fatal Error!  Not able to allocate a buffer for FAT Entries!");
      return;
    }
    BufferFatEntries = (LPDWORD) LocalLock(hMemBufferFatEntries);
    StoreFatEntries(ParentDrivePacket.StartCluster,
       ParentDrivePacket.FatType, BufferFatEntries,
       NumFatEntries, Buffer);
    if(!ReadDirBuffer(BufferFatEntries,
                      &TmpDrivePacket,
                      NumFatEntries,
                      Buffer)){
      AfxMessageBox("Not able to fully load the sub-directory");
    }
    LocalUnlock(hMemBuffer);
    Buffer = (LPBYTE) LocalLock(hMemBuffer);
  }
  else{
    hMemBuffer =
        LocalAlloc(LMEM_MOVEABLE|LMEM_DISCARDABLE|LMEM_ZEROINIT,
        ParentDrivePacket.NumSectors * 512);
    if(hMemBuffer == NULL){
      AfxMessageBox(
"Fatal Error! Not able to allocate a buffer of memory for root dir!");
      return;
    }
    Buffer = (LPBYTE) LocalLock(hMemBuffer);
    if(!LoadSectors(&TmpDrivePacket, &CylHeadSect, Buffer)){
      AfxMessageBox("Root Directory not fully accessible!");
    }
  }
  DIRECTORY *Direc = (DIRECTORY *) Buffer;
  for(int i=0; i<=IndexOfItem; i++, Direc++){
    while((Direc->Attributes == 0x0F) ||
          (Direc->Name[0] == '.'))
      Direc++;
  }
  Direc--;
  memset(Direc->Name, ' ', 11);
  strcpy(Direc->Name, RenFileName);
  Direc->Name[strlen(RenFileName)] = ' ';
  Direc->Extension[0] = RenFileExt[0];
  Direc->Extension[1] = (RenFileExt[1] == 0) ? ' ':RenFileExt[1];
  Direc->Extension[2] = (RenFileExt[2] == 0) ? ' ':RenFileExt[2];
  if(gListViewType == ROOT_DIR){
  	if(!WriteSectors(&TmpDrivePacket, &CylHeadSect, Buffer)){
      AfxMessageBox("Root Directory not fully accessible!");
    }
  }
  else
    if(!WriteDirBuffer( BufferFatEntries,
                        &TmpDrivePacket,
                        NumFatEntries,
                        Buffer))
      AfxMessageBox("Not able to fully write the sub-directory");

}

Conclusion

All in all, this utility will be very useful for exploring the internals of the NTFS disks and for undeleting file and folders in Windows 95/98/Me/NT/2000/XP.

Downloads

Not available

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read