#Direct from PopCap versions of their games

1 messages · Page 1 of 1 (latest)

clever swan
#

PopCap sold their games on their own site, as well as through Steam and other retail channels. These versions have their own DRM that monitors and stops play after the 60 minute "trial period", in which you're told to register:
https://i.ibb.co/HfL6h10k/Screenshot-2025-08-13-194930.png
https://i.ibb.co/x8s7bYxF/image.png
Of course, the servers to register these games and unlock them are long gone.
https://i.ibb.co/0yBKtFrn/image.png

It would be cool to see how these games are 'unlocked' internally.

upper gust
#

Did you try a tool like Fiddler or Wireshark to see what kind of network traffic goes out when you enter a registration code? Unpacking PopCap games used to be really easy and the fact that a trial runs means you can likely dump it

upper gust
#

I found an ancient (2011) unpacker I wrote for popcap games. It was decompiled with IDA, don't have the source code anymore.

CHAR *FindPopcapgame1Process()
{
  HANDLE Toolhelp32Snapshot; // eax
  void *v1; // ebx

  while ( 1 )
  {
    Toolhelp32Snapshot = CreateToolhelp32Snapshot(2u, 0);
    v1 = Toolhelp32Snapshot;
    if ( Toolhelp32Snapshot != (HANDLE)-1 )
    {
      pe.dwSize = 296;
      if ( Process32First(Toolhelp32Snapshot, &pe) )
        break;
    }
LABEL_28:
    Sleep(0x64u);
  }
  while ( stricmp(pe.szExeFile, "popcapgame1.exe") )
  {
    if ( (pe.szExeFile[0] == 'p' || pe.szExeFile[0] == 'P')
      && (pe.szExeFile[1] == 'o' || pe.szExeFile[1] == 'O')
      && (pe.szExeFile[2] == 'p' || pe.szExeFile[2] == 'P')
      && (pe.szExeFile[3] == 'c' || pe.szExeFile[3] == 'C')
      && (pe.szExeFile[4] == 'a' || pe.szExeFile[4] == 'A')
      && (pe.szExeFile[5] == 'p' || pe.szExeFile[5] == 'P')
      && (pe.szExeFile[6] == 'g' || pe.szExeFile[6] == 'G')
      && (pe.szExeFile[7] == 'a' || pe.szExeFile[7] == 'A')
      && (pe.szExeFile[8] == 'm' || pe.szExeFile[8] == 'M')
      && (pe.szExeFile[9] == 'e' || pe.szExeFile[9] == 'E') )
    {
      CloseHandle(v1);
      break;
    }
    if ( !Process32Next(v1, &pe) )
      goto LABEL_28;
  }
  dwProcessId = pe.th32ProcessID;
  return pe.szExeFile;
}

int __cdecl Unpack(LPCSTR workingDirectory, LPCSTR applicationPath)
{
  CHAR *popcapgame1_exe_path; // eax
  HANDLE v4; // eax
  char Destination[4]; // [esp+39h] [ebp-26Fh] BYREF
  CHAR v6[256]; // [esp+3Dh] [ebp-26Bh] BYREF
  CHAR NewFileName[4]; // [esp+13Dh] [ebp-16Bh] BYREF
  _BYTE v8[251]; // [esp+141h] [ebp-167h] BYREF
  struct _STARTUPINFOA StartupInfo; // [esp+23Ch] [ebp-6Ch] BYREF
  struct _PROCESS_INFORMATION ProcessInformation; // [esp+280h] [ebp-28h] BYREF

  *(_DWORD *)NewFileName = 0;
  memset(v8, 0, sizeof(v8));
  *(_DWORD *)Destination = 0;
  memset(v6, 0, sizeof(v6));
  SetCurrentDirectoryA(workingDirectory);
  if ( !CreateProcessA(applicationPath, 0, 0, 0, 1, CREATE_DEFAULT_ERROR_MODE, 0, 0, &StartupInfo, &ProcessInformation) )
    return 0;
  popcapgame1_exe_path = FindPopcapgame1Process();
  strcpy(::Destination, popcapgame1_exe_path);
  wsprintfA(NewFileName, "%s.bak", applicationPath);
  strcpy(Destination, applicationPath);
  v4 = OpenProcess(1u, 0, dwProcessId);
  if ( !TerminateProcess(v4, 0) )
    return 0;
  Sleep(0x64u);
  if ( !TerminateProcess(ProcessInformation.hProcess, 0) )
    return 0;
  Sleep(0x64u);
  if ( !SetFileAttributesA(::Destination, 0x80u) )
    return 0;
  Sleep(0x64u);
  if ( !CopyFileA(applicationPath, NewFileName, 0) )
    return 0;
  Sleep(0x64u);
  if ( !DeleteFileA(applicationPath) )
    return 0;
  Sleep(0x64u);
  if ( !CopyFileA(::Destination, Destination, 0) )
    return 0;
  Sleep(0x64u);
  if ( !DeleteFileA(::Destination) )
    return 0;
  CloseHandle(ProcessInformation.hProcess);
  CloseHandle(ProcessInformation.hThread);
  return 1;
}

It looks like a subprocess popcapgame1.exe is created when the game runs. The unpacker simply copies that executable to 'unpack' it.