FH2.exe Source

From Forgotten Hope Wiki

FH2.exe is a workaround for a serious bug in the Battlefield 2 engine. The sourcecode is provided here for the curious/paranoid.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <assert.h>
#include <stdio.h>

#define DEFPATH(who) char who[MAX_PATH] = { '\0' }

static FILE *g_debug = NULL;

/* ----- Helpers ----- */
static void Panic(const char *what) 
{
  DEFPATH(tmpbuf); DEFPATH(errbuf);
  int err = GetLastError();
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, err, 0, errbuf, MAX_PATH-1, 0);
  _snprintf(tmpbuf, MAX_PATH, "%s: %s", what, errbuf);
  MessageBox(0, tmpbuf, "Failed.", MB_ICONEXCLAMATION | MB_OK);
  ExitProcess(err);
}

static void Info(const char *what)
{
  MessageBox(0, what, "Info", MB_ICONINFORMATION | MB_OK);
}

/* ----- Actually useful stuff. ----- */
typedef struct {
  enum { WHERE_NONE = 0, WHERE_MY_DOCUMENTS, WHERE_BF2 } where;
  const char *suffix;
} pathspec;

typedef struct {
  enum { MOVE_NONE = 0, MOVE_DIRECTORY, MOVE_FILE } how;
#define flag_backup 0x1
#define flag_force  0x2
  int flags;
  pathspec from;
  pathspec to;
  const char *why;
} movement;

static movement targets[] = {
  { MOVE_DIRECTORY, 0,           { WHERE_MY_DOCUMENTS, "Battlefield 2/cache"},    { WHERE_MY_DOCUMENTS, "Battlefield 2/cache-vanilla" }, "move aside vanilla shader cache" },
  { MOVE_DIRECTORY, 0,           { WHERE_MY_DOCUMENTS, "Battlefield 2/cache-fh2"},{ WHERE_MY_DOCUMENTS, "Battlefield 2/cache" }, "move in fh2 cache" },
  { MOVE_DIRECTORY, 0,           { WHERE_MY_DOCUMENTS, "Battlefield 2/mods/bf2"}, { WHERE_MY_DOCUMENTS, "Battlefield 2/mods/bf2.tmp" }, "move aside mods/bf2 shader cache" },
  { MOVE_DIRECTORY, 0,           { WHERE_MY_DOCUMENTS, "Battlefield 2/mods/fh2"}, { WHERE_MY_DOCUMENTS, "Battlefield 2/mods/bf2" }, "move in mods/fh2 shader cache" },
  { MOVE_FILE,      0,           { WHERE_BF2, "mods/bf2/shaders_client.zip"},     { WHERE_BF2, "mods/bf2/shaders_client.zip-vanilla" }, "move aside vanilla shaders" },
  { MOVE_FILE,      flag_backup, { WHERE_BF2, "mods/fh2/shaders_client.zip"},     { WHERE_BF2, "mods/bf2/shaders_client.zip" }, "move in fh2 shaders" },
  { MOVE_FILE,      0,           { WHERE_BF2, "mods/fh2/movies/warning.bik"},     { WHERE_BF2, "mods/fh2/movies/warning.bik-disabled" }, "move aside warning video" },
  { MOVE_NONE, }
};

static void GetMyDocs(char *dest)
{
  if (SUCCEEDED(SHGetFolderPath(NULL, 
                CSIDL_PERSONAL|CSIDL_FLAG_CREATE, 
                NULL, 
                0, 
                dest))) {
    return;
  } else {
    Panic("SHGetFolderPath failed");
  }
}

static HANDLE g_mutex = NULL;
static BOOL IsFirstInstance(void)
{
  g_mutex = CreateMutex(NULL, FALSE, "Global\\forgotten hope 2");
  if (g_mutex != NULL && GetLastError() == ERROR_ALREADY_EXISTS)
  {
    CloseHandle(g_mutex);
    g_mutex = NULL;
    return FALSE;
  }
  return TRUE;
}

static void ExecMovement(const movement *m, BOOL reverse)
{
  int e;
  DEFPATH(p_from);
  DEFPATH(p_to);
  DEFPATH(p_from_backup);

  if (m->from.where == WHERE_MY_DOCUMENTS)
      GetMyDocs(p_from);
  PathAppend(p_from, m->from.suffix);
  
  if (m->to.where == WHERE_MY_DOCUMENTS)
      GetMyDocs(p_to);
  PathAppend(p_to, m->to.suffix);

  memcpy(p_from_backup, p_from, MAX_PATH);
  strcat(p_from_backup, ".backup");

  switch (m->how)
  {
    case MOVE_FILE:
        if (!reverse && m->flags & flag_backup)
        {
            if (g_debug) fprintf(g_debug, "Taking a backup of '%s' to '%s'... ", p_from, p_from_backup);
            e = CopyFile(p_from, p_from_backup, FALSE);
            if (g_debug) fprintf(g_debug, "%s.\n", (e) ? "Done" : "Failed");
        }
        break;

    case MOVE_DIRECTORY:
        break;

    default:
    case MOVE_NONE:
      return;
  }

  if (reverse)
  {
    if (g_debug) fprintf(g_debug, "Moving '%s' back to '%s'... ", p_to, p_from);
    e = MoveFileEx(p_to, p_from, MOVEFILE_WRITE_THROUGH);
    if (g_debug) fprintf(g_debug, "%s.\n", (e) ? "Done" : "Failed");
  } else {
    if (g_debug) fprintf(g_debug, "Moving '%s' to '%s'... ", p_from, p_to);
    e = MoveFileEx(p_from, p_to, MOVEFILE_WRITE_THROUGH);
    if (g_debug) fprintf(g_debug, "%s.\n", (e) ? "Done" : "Failed");
  }
}

static void MoveIntoPlace(void)
{
  const movement *m = targets;
  while (m->how != MOVE_NONE)
  {
    if (g_debug) fprintf(g_debug, "MoveIntoPlace: executing task '%s'\n", m->why);
    ExecMovement(m, FALSE);
    m++;
  }
}

static void ResetPlaces(void)
{
  const movement *m = targets;

  while (m->how != MOVE_NONE)
    m++;
  m--;

  while (1)
  {
    if (g_debug) fprintf(g_debug, "MoveIntoPlace: reversing task '%s'\n", m->why);
    ExecMovement(m, TRUE);
    if (m == targets)
      break;
    m--;
  }
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char *lpCmdLine, int nCmdShow)
{
  STARTUPINFO si;
  PROCESS_INFORMATION pi;
  DEFPATH(cmdline);
  DEFPATH(md_cache_bf2);
  DEFPATH(md_cache_bf2tmp);
  DEFPATH(md_cache_fh2);

  BOOL first_instance;
  
  g_debug = fopen("fh2.exe.log", "a+");
  if (g_debug) setvbuf(g_debug, NULL, _IONBF, 0);
  if (g_debug) fprintf(g_debug, "-- fh2.exe -- starting log\n");
  first_instance = IsFirstInstance();
  if (g_debug) fprintf(g_debug, "IsFirstInstance? %s\n", (first_instance)?"Yes":"No");
 
  if (first_instance)
    MoveIntoPlace();
  
  memset(&si, 0, sizeof(si));
  si.cb = sizeof(si);
  memset(&pi, 0, sizeof(pi));
  
  _snprintf(cmdline, MAX_PATH, "bf2.exe +modPath mods/fh2 %s", lpCmdLine);

  SetProcessWorkingSetSize((HANDLE) -1, -1, -1);

  if (g_debug) fprintf(g_debug, "Execing '%s'\n", cmdline);

  // Start the child process.
  if (!CreateProcess(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
    Panic("CreateProcess failed");
  
  if (g_debug) fprintf(g_debug, "Waiting...\n");

  // Wait until child process exits.
  WaitForSingleObject(pi.hProcess, INFINITE);

  if (g_debug) fprintf(g_debug, "bf2.exe finished. Clearing up...\n");

  // Close process and thread handles.
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);

  if (first_instance)
    ResetPlaces();

  if (g_mutex)
    CloseHandle(g_mutex);

  if (g_debug) fprintf(g_debug, "-- fh2.exe -- Exiting.\n");
  if (g_debug)
    fclose(g_debug);

  return 0;
}