Microsoft DirectX 8.1 (C++)

DVD Region Change Support in Windows

Introduction

This document explains how the DVD region selection process works under Microsoft® Windows® 9x and Windows 2000. The first section describes the three sources of DVD Region Information. The second section describes how PC system manufacturers can set a system's initial DVD region. The third section explains how the DVD Navigator determines region match between the drive, decoder and disc. The last section shows how application and decoder developers can implement correct DVD region selection support in their code.

Sources of DVD Region Information

There are three sources of DVD region information that work together to determine the region for DVD playback on a Windows PC.

How to Set the Initial Region in Windows 98 and Windows 2000

It is the responsibility of the system manufacturer to select an initial DVD region for the DVD drive in their PCs.

Important   Only encrypted discs can be used to set the region.

If the manufacturer does not set a region, on first boot the Windows 98 Operating System base components will pick a region based on the Operating System language and the time zone and set the drive to that region. The system manufacturer is free to set the region on the drive and if that is not done during the manufacturing process, it is up to Windows to select the drive's region based on the best guess it can make.

For RPC2 drives, if during the setup process Windows 2000 finds that the drive does not have any region set (i.e., fresh drive), it will try to pick a region as above, but only if there is a disc in the drive. (RPC1 drives will choose the region without any disc in drive). Once a region is set for RPC2 drives, it is not auto-changed by either a re-installation or a clean installation of the Operating System.

The registry key on Windows 2000 systems is HKLM\System\CurrentControlSet\Control\Class\<CDROM GUID>\ <instance number>\DefaultDVDRegion(binary) .

What Microsoft DVD Navigator Does

The Microsoft DVD Navigator uses the following method to determine region match while playing DVD discs on a PC:

The DVD Navigator gets the disc's region, drive's region and decoder's region. If the disc region is "all regions" then the DVD Navigator plays the disc. If the disc is not marked for all regions, the DVD Navigator checks whether the decoder has a preset region. If the decoder has a preset region and it does not match the disc's region, the DVD Navigator will return an error indicating that it cannot play the disc on the current DVD configuration. If the decoder's region is the same as the disc's region, the DVD Navigator will then check whether the drive's region is the same as the disc's region. If they are the same, the DVD Navigator will play the title. If they are not the same, then the DVD Navigator invokes the code to change the drive's region. If the allowed number of region changes has been exhausted, then the region change attempt fails and the title can't be played on that system.

Subsequent DVD region change

On Windows 9x, when the DVD Navigator detects that a region change on the DVD drive is necessary, it uses DVDRgn.exe to request a region change on the drive. This application is regionalized along with other components of the operating system. This application is not installed by default under Windows 98. A method of installing it on the target system is demonstrated below.

In Windows 2000, the DVD Navigator invokes a property page on the DVD-ROM drive device in the Device Manager. This property page is also brought up by the DVDPlay.exe application when a region needs to be changed. The UI of this property page is very similar to that of DVDRgn.exe and it is regionalized in the operating system.

For RPC1 drives, the Windows Operating System components manage region changes. RPC2 drives maintain the region setting in their own hardware. When the number of allowed changes are exhausted on a RPC2 drive, the drive will fail the call to change the region and the region-change component in the operating system will indicate that to the user.

Installing the Windows Region Change Support Components

Application developers can utilize the DVD region change support built into Windows to change region inside the DVD playback applications. There are two different ways of doing this for the Windows 9x and Windows 2000 platforms.

Integrating Region-Change Support into an Application

The following functions are provided for developers who wish to integrate region-change support into their decoders or DVD applications.

/////////////////////////////////////////////////////////////////////
// ChangeDVDRegion :  Function to change the DVD drive region.
//
// Parameters:
//  In:  hWnd - window handle of the application 
//              that calls this function
//  Out: none.
//
// Returns:
//    TRUE: if the drive region change is successful
//    FALSE: if drive region change fails (probably because 
//           no drive was found with a valid DVD-V disc).
/////////////////////////////////////////////////////////////////////
BOOL ChangeDVDRegion(HWND hWnd)
{
  typedef BOOL (APIENTRY *DVDPPLAUNCHER) (HWND hWnd, CHAR DriveLetter);
  BOOL   bRegionChanged = FALSE;
  TCHAR   szCmdLine[MAX_PATH];

  // First find out which drive is a DVD drive with a valid DVD-V disc.

  TCHAR     szDVDDrive[4];
  if (! GetDriveLetter (szDVDDrive) )
  {
    MessageBox(hWnd, 
    TEXT("No DVD drive was found with DVD-V disc.")
    TEXT("\nCannot change DVD region of the drive."), 
    TEXT("Error"), MB_OK | MB_INFORMATION) ;
    return FALSE;
  }

  // Detect which OS we are running on. For Windows NT, use the storprop.dll,
  // while for Windows 9x use the DVDRgn.exe application to change the region.

  OSVERSIONINFO   ver;
  ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&ver);
  if (VER_PLATFORM_WIN32_NT  == ver.dwPlatformId)
  {
    // Windows NT platform
    HINSTANCE       hInstDLL;
    DVDPPLAUNCHER    dvdPPLauncher;
    CHAR         szDVDDriveA[4];

  #ifdef UNICODE
    WideCharToMultiByte(0, 0, szDVDDrive, -1,
              szDVDDriveA, sizeof(szDVDDriveA),
              NULL, NULL );
  #else
    strcpy(szDVDDriveA, szDVDDrive);
  #endif  // UNICODE

    GetSystemDirectory(szCmdLine, MAX_PATH);
    lstrcat(szCmdLine, TEXT("\\storprop.dll"));
    
    hInstDLL = LoadLibrary (szCmdLine);
    if (hInstDLL)
    {
      dvdPPLauncher = (DVDPPLAUNCHER) GetProcAddress(hInstDLL,
                                            "DvdLauncher");
      if (dvdPPLauncher)
      {
        bRegionChanged = dvdPPLauncher(hWnd, szDVDDriveA[0]);
      }
      FreeLibrary(hInstDLL);
    }
  }  // end of region change code for Windows NT platform
  else   // Windows 9x platform
  {
    // Get path of \windows\dvdrgn.exe for command line string
    GetWindowsDirectory(szCmdLine, MAX_PATH);
    lstrcat(szCmdLine, TEXT("\\DVDRgn.exe "));

    // Add only the drive letter as command line parameter
    lstrncat(szCmdLine, szDVDDrive, 1);
    
    // Prepare to execute DVDRgn.exe 
    STARTUPINFO               StartupInfo;
    PROCESS_INFORMATION       ProcessInfo;
    StartupInfo.cb            = sizeof(StartupInfo);
    StartupInfo.dwFlags       = STARTF_USESHOWWINDOW;
    StartupInfo.wShowWindow   = SW_SHOWNORMAL;
    StartupInfo.lpReserved    = NULL;
    StartupInfo.lpDesktop     = NULL;
    StartupInfo.lpTitle       = NULL;
    StartupInfo.cbReserved2   = 0;
    StartupInfo.lpReserved2   = NULL;
    if (CreateProcess(csModuleName, szCmdLine, 
                    NULL, NULL, TRUE, 
                    NORMAL_PRIORITY_CLASS,
                    NULL, NULL, &StartupInfo, &ProcessInfo) )
    {
      // Wait until DVDRgn.exe finishes
      WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
      DWORD dwRet = 1;
      BOOL bRet = GetExitCodeProcess(ProcessInfo.hProcess,
                           &dwRet);
      // If the user changed the drive region 
      // successfully, the exit code of DVDRgn.exe is 0.
      if (bRet && 0 == dwRet)  
      {
        bRegionChanged = TRUE;
      }
    }
  }  // end of region change code for Windows 9x platform

  if (bRegionChanged)   // if region changed successfully
  {
    // Delete old filter graph and create new filter graph
    // via IDvdGraphBuilder::RenderDvdVideoVolume().
  }
  else   // DVD region didn't happen
  {
    MessageBox(hWnd, 
    TEXT("DVD drive region could not be changed.")
    TEXT("Error"), MB_OK | MB_INFORMATION) ;
  }
} //End function ChangeDVDRegion

/////////////////////////////////////////////////////////////////////
// GetDriveLetter :  Function to get the drive letter of the currently 
//           active DVD drive
// Parameters:
//  In:    pDvdC - pointer to IDvdControl interface of 
//                DVDNavigator filter..
//  Out:   pszDrive - the first DVD drive that is found
//                   to have a valid DVD-V disc.
//
// Returns:
//     TRUE:   if a DVD drive is found (with valid disc)
//    FALSE:   if no DVD drive was found with valid DVD-V disc.
/////////////////////////////////////////////////////////////////////
BOOL GetDriveLetter(IDvdControl *pDvdC, TCHAR *pszDrive) 
{
  CHAR   szPathA[MAX_PATH];
  TCHAR   szPath[MAX_PATH];
  ULONG   ulActualSize;
  pszDrive[0] = pszDrive[3] = 0;

  // Get the current root directory
  if (pDvdC ->GetRoot(szPathA, MAX_PATH, &ulActualSize))
  {
    #ifdef UNICODE
      MultiByteToWideChar(CP_ACP, 0, szPathA, 0, szPath, MAX_PATH);
    #else
      lstrcpy(szPath, szPathA);
    #endif  // UNICODE
    lstrcpyn(pszDrive, szPath, 3);
    if (DRIVE_CDROM == GetDriveType(pszDrive)) // could be a DVD drive
      return TRUE;
  }

  // Now loop through all the valid drives to detect which one
  // is a DVD drive with a valid DVD-V disc in it.

  // Get all valid drives
  DWORD dwLeng = GetLogicalDriveStrings(MAX_PATH, szPath);
  TCHAR  *pszTemp = szPath;
  // Try all drives one by one
  for (DWORD dw = 0; dw < dwLeng; dw += 4) 
  {
    // Look only for CD-ROM drives that has a disc with required (.ifo) files
    if (DRIVE_CDROM  == GetDriveType(pszTemp)) 
      {
        TCHAR   szDVDPath1[MAX_PATH]
        TCHAR   szDVDPath2[MAX_PATH];
        lstrcpyn(szDVDPath1, pszTemp, 4);
        lstrcpyn(szDVDPath 2, pszTemp, 4);
        lstrcat(szDVDPath1, TEXT("Video_ts\\Video_ts.ifo"));
        lstrcat(szDVDPath2, TEXT("Video_ts\\Vts_01_0.ifo"));

        // If the .ifo files exist on this drive then it has a valid DVD-V disc
        if (DoesFileExist(achDVDPath1) && DoesFileExist(achDVDPath2))    
        {
          lstrcpyn(pszDrive, pszTemp, 3);
          return TRUE;   // return the first drive that has a valid DVD-V disc
        }
      }
    pszTemp += 4; 
  }

  return FALSE ;   // didn't find any DVD drive (with DVD-V content)
} //End function GetDriveLetter

/////////////////////////////////////////////////////////////////////
// DoesFileExist : Function to determine if the given filename is an existing file.
//
// Parameters:
//  In:    pszFile - filename string to check for existence.
//  Out:   none.
//
// Returns:
//     TRUE:   if the specified file is found.
//      FALSE:   if the specified file is not found.
/////////////////////////////////////////////////////////////////////
BOOL DoesFileExist(LPTSTR pszFile)
{
  HANDLE    hFile = NULL ;
  // We don't want any error message box to pop up when we try to test
  // if the required file is available to open for read.

  UINT uErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | 
  SEM_NOOPENFILEERRORBOX);
  hFile = CreateFile(pszFile, GENERIC_READ, 
                    FILE_SHARE_READ, NULL, 
                    OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN,
                    NULL);
  SetErrorMode(uErrorMode);  // restore error mode
  If (INVALID_HANDLE_VALUE  == hFile) 
    return FALSE ;    

  CloseHandle(hFile);
  return TRUE;
} //End function DoesFileExist