#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <winuser.h>
#include <commctrl.h>
#include "protopat.h"
/********************************************/
/* Disable warnings for precompiled headers */
/********************************************/
#pragma warn -pch
#include "PodRacer.h"

BOOL 			CALLBACK DlgMain (HWND, UINT, WPARAM, LPARAM) ;
BOOL 			CALLBACK DlgAbout (HWND, UINT, WPARAM, LPARAM) ;
HTREEITEM 	AddItemToTree(HWND, LPSTR, HTREEITEM, int);
LPSTR 		StripBraces(LPSTR, LPSTR);
int			ApplyPatch(char);
int			GetSubDirs(HTREEITEM);
int		 	ExpandTreeView(HTREEITEM);
void 			WritePatch(FILE *, uChar *, long);

HINSTANCE 	hInst ;
HWND			hWndPB;
HWND			hWndSB;
HWND 			hWndTV;
char			szTmpBuffer1[(MAX_PATH > 512 ? MAX_PATH : 512)];
char			szTmpBuffer2[(MAX_PATH > 512 ? MAX_PATH : 512)];
char			szFullPath[MAX_PATH];
char			szAppTitle[128];
char        szAppInfo[128];

#pragma argsused
//-------------------------------------------------------------------
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpszCmdLine, int cmdShow)
{
	hInst = hInstance ;

   InitCommonControls();

   DialogBox (hInstance, MAKEINTRESOURCE (IDD_MAIN), NULL, DlgMain) ;

   return 0 ;
}

BOOL CALLBACK DlgMain (HWND hWnd, UINT mMsg, WPARAM wParam, LPARAM lParam)
{
   static	HWND hWndPatch;
	static	HWND hWndUndo;
   char	 	szBuffer[20];
   int	 	c, x;

	switch (mMsg)
   {
   	case WM_INITDIALOG :
      {
         HIMAGELIST	himl;

         sprintf(szAppTitle,"%s V%s Patcher by Mystique", APPLICATION, VERSION);
         sprintf(szAppInfo,"Please select the directory where %s is installed", APPLICATION);

      	// We will use this quite often, so save it permanent
         hWndTV = GetDlgItem(hWnd, IDC_TREEVIEW);
         hWndPB = GetDlgItem(hWnd, IDC_PROGRESSBAR);
         hWndSB = GetDlgItem(hWnd, IDC_STATUSBAR);
         hWndPatch = GetDlgItem(hWnd, IDC_PATCH);
         hWndUndo = GetDlgItem(hWnd, IDC_UNDO);

         // Set Windows title and Status Bar
         SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)(LPCTSTR) szAppTitle);
         SendMessage(hWndSB, WM_SETTEXT, 0, (LPARAM)(LPCTSTR) szAppInfo);

         // Create the image list
         himl = ImageList_Create(16, 16, ILC_COLOR24, 0, 5);
         ImageList_AddIcon(himl, LoadIcon (hInst, MAKEINTRESOURCE (IDI_FLOPPY)));
         ImageList_AddIcon(himl, LoadIcon (hInst, MAKEINTRESOURCE (IDI_DRIVE)));
         ImageList_AddIcon(himl, LoadIcon (hInst, MAKEINTRESOURCE (IDI_CDROM)));
         ImageList_AddIcon(himl, LoadIcon (hInst, MAKEINTRESOURCE (IDI_NETWORK)));
         ImageList_AddIcon(himl, LoadIcon (hInst, MAKEINTRESOURCE (IDI_RAMDISK)));
         ImageList_AddIcon(himl, LoadIcon (hInst, MAKEINTRESOURCE (IDI_FOLDER)));
         SendMessageA(hWndTV, TVM_SETIMAGELIST, TVSIL_NORMAL, (LPARAM) (HIMAGELIST) himl);

         // Get a list of all the logical drives on the system (We ain't fussy)
      	if(GetLogicalDriveStrings(512, &szTmpBuffer1) > 512)
         {
            PostQuitMessage(TRUE);
            return FALSE;
         }

         c=0;
         while(szTmpBuffer1[c])
         {
         	szBuffer[0] = '(';
            for(x=0;szTmpBuffer1[x+c];x++)
            	szBuffer[x+1] = szTmpBuffer1[x+c];
            szBuffer[x] = ')';
            szBuffer[x+1] = 0;
            AddItemToTree(hWndTV, szBuffer, 0, 0);
            c += (x+1);
         }
         return TRUE;
      }
   	case WM_NOTIFY:
   	{
    		if((LOWORD (wParam)) == IDC_TREEVIEW)
      	{
           	NM_TREEVIEW *pnmtv = (NM_TREEVIEW FAR *) lParam;
	  			switch(pnmtv->hdr.code)
    			{
     				case TVN_SELCHANGING:
					{
		   			TV_ITEM	 hTVI;

						SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT)));

						if(!pnmtv->itemNew.hItem)
                  	break;

						if(!GetSubDirs(pnmtv->itemNew.hItem))
		              	break;
//	               ExpandTreeView(pnmtv->itemNew.hItem);

		   			hTVI.hItem = pnmtv->itemNew.hItem;
		   			hTVI.mask = 0;
  						hTVI.stateMask = TVIF_PARAM;
		   			TreeView_GetItem(hWndTV, &hTVI);
                  strcpy(szFullPath, (LPSTR) hTVI.lParam);

           			EnableWindow(hWndPatch, FALSE);
           			EnableWindow(hWndUndo, FALSE);
           			for(c=0;PatchData[c].FileName;c++)
           			{
           				sprintf(szTmpBuffer1,"%s%s", (LPSTR) hTVI.lParam, PatchData[c].FileName);
           				if(_rtl_chmod(szTmpBuffer1, 0) != -1)
                     {
                 			EnableWindow(hWndPatch, TRUE);
                 			EnableWindow(hWndUndo, TRUE);
                     }
           			}
                  break;
        			}
               case TVN_ITEMEXPANDING:
               {
						SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT)));

						if(pnmtv->itemNew.hItem)
	                  ExpandTreeView(pnmtv->itemNew.hItem);
                  break;
               }
               default:
               	return TRUE;
        		}
            SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
         } else {
         	return FALSE;
         }
         break;
      }
      case WM_COMMAND:
      {
			switch(LOWORD(wParam))
         {
				case IDC_FORCE:
            {
            	switch(IsDlgButtonChecked(hWnd, IDC_FORCE))
               {
               	case BST_UNCHECKED:
               		CheckDlgButton(hWnd, IDC_FORCE, BST_CHECKED); //BST_INDETERMINATE);
	               	break;
               	case BST_INDETERMINATE:
               		CheckDlgButton(hWnd, IDC_FORCE, BST_CHECKED);
	               	break;
               	case BST_CHECKED:
               		CheckDlgButton(hWnd, IDC_FORCE, BST_UNCHECKED);
	               	break;
               }
               break;
            }
  				case IDC_PATCH:
         	{
        			if(HIWORD(wParam) == BN_CLICKED)
       				SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT)));
           			if((LOWORD(ApplyPatch(CHECK_PATCH)) == PATCH_TOTAL) || \
		                  (IsDlgButtonChecked(hWnd, IDC_FORCE) == BST_CHECKED))
                  	ApplyPatch(APPLY_PATCH);
						SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
            	break;
         	}
         	case IDC_UNDO:
         	{
        			if(HIWORD(wParam) == BN_CLICKED)
       				SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT)));
           			if((HIWORD(ApplyPatch(CHECK_PATCH)) == PATCH_TOTAL) || \
		                  (IsDlgButtonChecked(hWnd, IDC_FORCE) == BST_CHECKED))
                  	ApplyPatch(UNDO_PATCH);
						SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
            	break;
         	}
            case IDC_ABOUT:
            {
				   DialogBox (hInst, MAKEINTRESOURCE(IDD_ABOUT), hWnd, DlgAbout) ;
               break;
            }
            case IDC_HELP:
            {
				   DialogBox (hInst, MAKEINTRESOURCE(IDD_HELP), hWnd, DlgAbout) ;
               break;
            }
           	case IDC_EXIT:
            	if(HIWORD(wParam) != BN_CLICKED)
            		break;
   	     	case WM_DESTROY:
      	     	PostQuitMessage(TRUE);
         	   return TRUE;
         }
		}
   }
   return FALSE;
}

#pragma argsused
BOOL CALLBACK DlgAbout (HWND hWnd, UINT mMsg, WPARAM wParam, LPARAM lParam)
{

	if((mMsg == WM_COMMAND) && (LOWORD(wParam) == IDC_OK))
   {
   	EndDialog(hWnd, 0);
		return TRUE;
   }
   return FALSE;
}

// AddItemToTree - adds items to a tree-view control.
// Returns the handle of the newly added item.
// hwndTV - handle of the tree-view control
// lpszItem - text of the item to add
// nLevel - level at which to add the item
HTREEITEM AddItemToTree(HWND hWndTV, LPSTR lpszItem, HTREEITEM hPrev, int nLevel)
{
   TV_ITEM hTVI, hpTVI;
   TV_INSERTSTRUCT tvins;

	hTVI.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
 	hTVI.pszText = lpszItem;
	hTVI.cchTextMax = lstrlen(lpszItem);

   if(nLevel == 0)
   {
   	sprintf(szTmpBuffer2, "%c:\\", lpszItem[1]);
		hTVI.lParam = (LPARAM) strdup(szTmpBuffer2);
    	switch(GetDriveType(szTmpBuffer2))
      {
      	case DRIVE_REMOVABLE:
				hTVI.iImage = 0;
    			hTVI.iSelectedImage = 0;
            break;
         case DRIVE_FIXED:
				hTVI.iImage = 1;
    			hTVI.iSelectedImage = 1;
            break;
         case DRIVE_CDROM:
				hTVI.iImage = 2;
    			hTVI.iSelectedImage = 2;
            break;
         case DRIVE_REMOTE:
				hTVI.iImage = 3;
    			hTVI.iSelectedImage = 3;
            break;
         case DRIVE_RAMDISK:
				hTVI.iImage = 4;
    			hTVI.iSelectedImage = 4;
            break;
         default:
				hTVI.iImage = 5;
    			hTVI.iSelectedImage = 5;
    	}
	}  else {
	   hpTVI.hItem = hPrev;
   	hpTVI.mask = TVIF_STATE;
   	hpTVI.stateMask = TVIF_PARAM;
   	TreeView_GetItem(hWndTV, &hpTVI);

      sprintf(szTmpBuffer2, "%s%s\\", (LPSTR) hpTVI.lParam, lpszItem);
		hTVI.lParam = (LPARAM) strdup(szTmpBuffer2);
		hTVI.iImage = 5;
      hTVI.iSelectedImage = 5;
   }

    tvins.item = hTVI;
    tvins.hInsertAfter = 0;
    tvins.hParent = hPrev;

    // Add the item to the tree-view control.
    hPrev = (HTREEITEM) SendMessage(hWndTV, TVM_INSERTITEM, 0,
         (LPARAM) (LPTV_INSERTSTRUCT) &tvins);

	return hPrev;
}

int ExpandTreeView(HTREEITEM hpItem)
{
   HTREEITEM	hcItem;
   int			c=0;

   hcItem = TreeView_GetChild(hWndTV, hpItem);
	while(hcItem)
   {
   	c++;
   	GetSubDirs(hcItem);
      hcItem = TreeView_GetNextSibling(hWndTV, hcItem);
   }

	return c;
}

// **********************************************************************
// * Returns -1 for having been read once already, zero for no files or *
// * directories found, or if successful, the count of files and dirs.  *
// **********************************************************************
static	HTREEITEM	LastItem=0;
static	HTREEITEM	ThisItem=0;
int GetSubDirs(HTREEITEM hpItem)
{
   WIN32_FIND_DATA 	lpFindFileData;
	HANDLE		hFindFile;
   TV_ITEM		hTVI;
   int			c=0;

   hTVI.hItem = hpItem;
   hTVI.lParam = szTmpBuffer2;
   hTVI.mask = TVIF_PARAM | TVIF_CHILDREN;
   hTVI.stateMask = TVIF_CHILDREN | TVIF_PARAM;

   // Get the item data
   TreeView_GetItem(hWndTV, &hTVI);

   // Save This / Last selection
   LastItem = ThisItem;
   ThisItem = hpItem;

   // Crude hack to skip reading blank floppies!
   if(!LastItem)
   	return 0;

	// If we have already read the directory then return
 	if(hTVI.cChildren)
   	return -1;

   // Set up the path for our directory search
	sprintf(szTmpBuffer1, "%s*.*", (LPSTR) hTVI.lParam);
 	if((hFindFile = FindFirstFile(szTmpBuffer1, &lpFindFileData)) != INVALID_HANDLE_VALUE)
   {
		do
      {
      	c++;
      	if(lpFindFileData.dwFileAttributes &= FILE_ATTRIBUTE_DIRECTORY)
		      if(strncmp(lpFindFileData.cFileName, "..", 2) && strncmp(lpFindFileData.cFileName, ".", 1))
	            AddItemToTree(hWndTV, lpFindFileData.cFileName, hpItem, 1);
      }
   	while((FindNextFile(hFindFile, &lpFindFileData)));

   	FindClose(hFindFile);
      TreeView_SortChildren(hWndTV, hpItem, 0);
   }

   return c;
}

int ApplyPatch(char patch)
{
   FILE		*fp;
   uChar		*sdata;
   uChar		*rdata;
	char		sfound = 0;
   char		rfound = 0;
   int 		fcur, f, c, x;
   int		lper=-1, cper;
   long		fsize, fpos = 0;

	for(f=0;PatchData[f].FileName;f++)
   {
   	lper = -1;
      fpos = 0;

		// ******************************************************************
		// * Display what kind of action we are taking							  *
		// ******************************************************************
   	if(!patch)
	   	sprintf(szTmpBuffer1, "Checking: %s%s", szFullPath, PatchData[f].FileName);
      else
	   	sprintf(szTmpBuffer1, "Patching: %s%s", szFullPath, PatchData[f].FileName);
   	SendMessage(hWndSB, WM_SETTEXT, 0, (LPARAM) szTmpBuffer1);
		// ******************************************************************

      sprintf(szTmpBuffer1, "%s%s", szFullPath, PatchData[f].FileName);

      // Set our file to read/write
      _rtl_chmod(szTmpBuffer1, 1, 0);
      // Open our file for read/write
      if(!(int)(fp = fopen(szTmpBuffer1, "r+b")))
      	break;

      // Get the file length
      fseek(fp, 0, SEEK_END);
      fsize = ftell(fp);
      fseek(fp, 0, SEEK_SET);

   	// We will do the search and replace in 512 byte increments
      while((fcur = fread(szTmpBuffer1, 1, 512, fp))>0)
      {
         for(c=0;c<fcur;c++)
         {
            for(x=0;PatchData[f].pArray[x];x += 2)
            {
	       		// Let the user know our current position
  					cper = (((fpos+c) * 100)/fsize);
               if(lper != cper)
               {
	               SendMessage(hWndPB, PBM_SETPOS, cper, 0);
                  lper = cper;
               }

               // Set up the search/replace strings
      			sdata = (uChar *)PatchData[f].pArray[x];
      			rdata = (uChar *)PatchData[f].pArray[x+1];
               // ****************************************************
					// *  Check if we have a match on the search string   *
               // ****************************************************

            	if(sdata[sdata[0]] == (uChar)szTmpBuffer1[c])
            	{
            		// Move to the next comparison byte
            		sdata[0] += (char) 2;

               	// Check if we have matched the whole search string
               	if((sdata[sdata[0]-1] == 0xff) && (sdata[sdata[0]] == 0xfe))
               	{
               		// Replace the search string with the new string
                     if(patch == APPLY_PATCH)
                     	WritePatch(fp, rdata, (fpos + c) - ((sdata[0] -2)/2));

                     // Update the count of found search strings
                  	sfound++;
               	}
               } else {
               	// Reset the search to the beginning of the string
               	sdata[0] = 2;
               }

               // ****************************************************
					// *  Check if we have a match on the replace string  *
               // *  So we can notify that the patch already exists  *
               // ****************************************************
            	if((rdata[rdata[0]] == (uChar)szTmpBuffer1[c]) || \
               	((rdata[rdata[0]-1] == 0xff) && \
                  (sdata[rdata[0]] == (uChar)szTmpBuffer1[c])))
            	{
            		// Move to the next comparison byte
            		rdata[0] += (char) 2;

               	// Check if we have matched the whole search string
               	if((rdata[rdata[0]-1] == 0xff) && (rdata[rdata[0]] == 0xfe))
               	{
               		// Replace the search string with the old string
                     if(patch == UNDO_PATCH)
                     	WritePatch(fp, sdata, (fpos + c) - ((rdata[0] -2)/2));

               		// Update the count of found applied patches
                  	rfound++;
               	}
               } else {
               	// Reset the search to the beginning of the string
               	rdata[0] = 2;
               }
            }
         }
         // Update our internal counter
         fpos += fcur;

         // Reset the seek position in case it has been modified by a replace
         fseek(fp, fpos, SEEK_SET);
      }
      SendMessage(hWndPB, PBM_SETPOS, 0, 0);

      fclose(fp);
   }

   // Display the results of our search
   switch (patch)
   {
      case APPLY_PATCH:
 		if(sfound)
 	    	sprintf(szTmpBuffer1, "%d patch%s were applied.", sfound, ((sfound > 1) ? "es" : ""));
      else
        	sprintf(szTmpBuffer1, "Nothing to do.");
     	break;
      case UNDO_PATCH:
		if(rfound)
	   	sprintf(szTmpBuffer1, "%d patch%s were removed.", rfound, ((rfound > 1) ? "es" : ""));
      else
         sprintf(szTmpBuffer1, "Nothing to do.");
         break;
      case CHECK_PATCH:
		{
        	if(!sfound && !rfound) {
			  	sprintf(szTmpBuffer1, "Invalid or incorrect version");
			} else {
	        	sprintf(szTmpBuffer1, "%d applied patch%s & %d unapplied patch%s found.", \
           		rfound, ((rfound > 1 || !rfound) ? "es" : ""), \
           		sfound, ((sfound > 1 || !sfound) ? "es" : ""));
         }
      }
   }
 	SendMessage(hWndSB, WM_SETTEXT, 0, (LPARAM)szTmpBuffer1);

 	return((rfound << 16) | sfound);
}

void WritePatch(FILE *fp, uChar *rdata, long fpos)
{
	long	opos;
   int	c = 2;

   // Save the current seek position
   opos = ftell(fp);

   // Seek to the start addres of where to apply the patch
	if(fseek(fp, (fpos +1), SEEK_SET))
   	return;

   while((rdata[c-1] != 0xff) || (rdata[c] != 0xfe))
   {
   	if(rdata[c-1] == 0xff)
      	fseek(fp, 1, SEEK_CUR);
      else
      	fputc(rdata[c], fp);
    	c += 2;
   }

   // Reset the seek position
   fseek(fp, opos, SEEK_SET);
}

