/*++

Copyright (c) 1990-2002  Microsoft Corporation

Module Name:

    pathmisc.c

Abstract:

    Win32 miscellaneous path functions

--*/

#include "basedll.h"
#include <xboxp.h>
#include <xdisk.h>
#include <xconfig.h>
#include "xmeta.h"
#include "..\..\fatx\fat.h"

static const OBJECT_STRING ZDrive      = CONSTANT_OBJECT_STRING( OTEXT("\\??\\Z:") );
static const OCHAR CacheDriveFormat[]  = OTEXT("\\Device\\Harddisk0\\Partition%d\\");
static COBJECT_STRING WDrive           = CONSTANT_OBJECT_STRING( OTEXT("\\??\\W:") );
static COBJECT_STRING XDrive           = CONSTANT_OBJECT_STRING( OTEXT("\\??\\X:") );

#ifdef XAPILIBP

extern XAPI_MU_INFO XapiMuInfo;

#else  // XAPILIBP

XAPI_MU_INFO XapiMuInfo = {0};
#if DBG
BOOL g_fMountedUtilityDrive = FALSE;
#endif // DBG

#endif // XAPILIBP

//
//  Define the FAT32 X-Box cache db sector
//

typedef struct _XBOX_CACHE_DB_SECTOR {
    ULONG SectorBeginSignature;                     // offset = 0x000   0
    ULONG Version;                                  // offset = 0x004   4
    UCHAR Data[496];                                // offset = 0x008   8
    ULONG SectorEndSignature;                       // offset = 0x1fc 508
} XBOX_CACHE_DB_SECTOR, *PXBOX_CACHE_DB_SECTOR;

#define XBOX_HD_SECTOR_SIZE                    512

#define XBOX_CACHE_DB_DATA_SIZE                (sizeof(((PXBOX_CACHE_DB_SECTOR) 0)->Data))

#define XBOX_CACHE_DB_SECTOR_BEGIN_SIGNATURE   0x97315286
#define XBOX_CACHE_DB_SECTOR_END_SIGNATURE     0xAA550000
#define XBOX_CACHE_DB_CUR_VERSION              0x00000002
#define XBOX_CACHE_DB_MAX_ENTRY_COUNT          (XBOX_CACHE_DB_DATA_SIZE / sizeof(X_CACHE_DB_ENTRY))

#ifndef XAPILIBP

WINBASEAPI
BOOL
WINAPI
GetDiskFreeSpaceEx(
    PCOSTR lpDirectoryName,
    PULARGE_INTEGER lpFreeBytesAvailableToCaller,
    PULARGE_INTEGER lpTotalNumberOfBytes,
    PULARGE_INTEGER lpTotalNumberOfFreeBytes
    )
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES Obja;
    HANDLE Handle;
    OBJECT_STRING FileName;
    IO_STATUS_BLOCK IoStatusBlock;
    FILE_FS_SIZE_INFORMATION NormalSizeInfo;

    ULARGE_INTEGER BytesPerAllocationUnit;
    ULARGE_INTEGER FreeBytesAvailableToCaller;
    ULARGE_INTEGER TotalNumberOfBytes;

    RIP_ON_NOT_TRUE("GetDiskFreeSpaceEx()", ARGUMENT_PRESENT(lpDirectoryName));

    RtlInitObjectString(&FileName, lpDirectoryName);

    InitializeObjectAttributes(
        &Obja,
        &FileName,
        OBJ_CASE_INSENSITIVE,
        ObDosDevicesDirectory(),
        NULL
        );

    //
    // Open the file
    //

    Status = NtOpenFile(
                &Handle,
                (ACCESS_MASK)FILE_LIST_DIRECTORY | SYNCHRONIZE,
                &Obja,
                &IoStatusBlock,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE | FILE_OPEN_FOR_FREE_SPACE_QUERY
                );
    if ( !NT_SUCCESS(Status) ) {
        XapiSetLastNTError(Status);
        if ( GetLastError() == ERROR_FILE_NOT_FOUND ) {
            SetLastError(ERROR_PATH_NOT_FOUND);
            }
        return FALSE;
        }

    //
    // Determine the size parameters of the volume.
    //

    Status = NtQueryVolumeInformationFile(
                Handle,
                &IoStatusBlock,
                &NormalSizeInfo,
                sizeof(NormalSizeInfo),
                FileFsSizeInformation
                );
    NtClose(Handle);
    if ( !NT_SUCCESS(Status) ) {
        XapiSetLastNTError(Status);
        return FALSE;
        }

    BytesPerAllocationUnit.QuadPart =
        NormalSizeInfo.BytesPerSector * NormalSizeInfo.SectorsPerAllocationUnit;

    FreeBytesAvailableToCaller.QuadPart =
        BytesPerAllocationUnit.QuadPart * NormalSizeInfo.AvailableAllocationUnits.QuadPart;

    TotalNumberOfBytes.QuadPart =
        BytesPerAllocationUnit.QuadPart * NormalSizeInfo.TotalAllocationUnits.QuadPart;

    if ( ARGUMENT_PRESENT(lpFreeBytesAvailableToCaller) ) {
        lpFreeBytesAvailableToCaller->QuadPart = FreeBytesAvailableToCaller.QuadPart;
        }
    if ( ARGUMENT_PRESENT(lpTotalNumberOfBytes) ) {
        lpTotalNumberOfBytes->QuadPart = TotalNumberOfBytes.QuadPart;
        }
    if ( ARGUMENT_PRESENT(lpTotalNumberOfFreeBytes) ) {
        lpTotalNumberOfFreeBytes->QuadPart = FreeBytesAvailableToCaller.QuadPart;
        }

    return TRUE;
}


BOOL
APIENTRY
GetVolumeInformation(
    PCOSTR lpRootPathName,
    POSTR lpVolumeNameBuffer,
    DWORD nVolumeNameSize,
    LPDWORD lpVolumeSerialNumber,
    LPDWORD lpMaximumComponentLength,
    LPDWORD lpFileSystemFlags,
    POSTR lpFileSystemNameBuffer,
    DWORD nFileSystemNameSize
    )

/*++

Routine Description:

    This function returns information about the file system whose root
    directory is specified.

Arguments:

    lpRootPathName - An optional parameter, that if specified, supplies
        the root directory of the file system that information is to be
        returned about.  If this parameter is not specified, then the
        root of the current directory is used.

    lpVolumeNameBuffer - An optional parameter that if specified returns
        the name of the specified volume.

    nVolumeNameSize - Supplies the length of the volume name buffer.
        This parameter is ignored if the volume name buffer is not
        supplied.

    lpVolumeSerialNumber - An optional parameter that if specified
        points to a DWORD.  The DWORD contains the 32-bit of the volume
        serial number.

    lpMaximumComponentLength - An optional parameter that if specified
        returns the maximum length of a filename component supported by
        the specified file system.  A filename component is that portion
        of a filename between pathname seperators.

    lpFileSystemFlags - An optional parameter that if specified returns
        flags associated with the specified file system.

        lpFileSystemFlags Flags:

            FS_CASE_IS_PRESERVED - Indicates that the case of file names
                is preserved when the name is placed on disk.

            FS_CASE_SENSITIVE - Indicates that the file system supports
                case sensitive file name lookup.

            FS_UNICODE_STORED_ON_DISK - Indicates that the file system
                supports unicode in file names as they appear on disk.

    lpFileSystemNameBuffer - An optional parameter that if specified returns
        the name for the specified file system (e.g. FAT, HPFS...).

    nFileSystemNameSize - Supplies the length of the file system name
        buffer.  This parameter is ignored if the file system name
        buffer is not supplied.

Return Value:

    TRUE - The operation was successful.

    FALSE/NULL - The operation failed. Extended error status is available
        using GetLastError.

--*/

{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES Obja;
    HANDLE Handle;
    OBJECT_STRING FileName;
    IO_STATUS_BLOCK IoStatusBlock;
    PFILE_FS_ATTRIBUTE_INFORMATION AttributeInfo;
    PFILE_FS_VOLUME_INFORMATION VolumeInfo;
    ULONG AttributeInfoLength;
    ULONG VolumeInfoLength;
    BOOL rv;

    rv = FALSE;

    nVolumeNameSize *= 2;
    nFileSystemNameSize *= 2;

    RIP_ON_NOT_TRUE("GetVolumeInformation()", ARGUMENT_PRESENT(lpRootPathName));

    RtlInitObjectString(&FileName, lpRootPathName);

    InitializeObjectAttributes(
        &Obja,
        &FileName,
        OBJ_CASE_INSENSITIVE,
        ObDosDevicesDirectory(),
        NULL
        );

    AttributeInfo = NULL;
    VolumeInfo = NULL;

    //
    // Open the file
    //

    Status = NtOpenFile(
                &Handle,
                (ACCESS_MASK)FILE_LIST_DIRECTORY | SYNCHRONIZE,
                &Obja,
                &IoStatusBlock,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT
                );
    if ( !NT_SUCCESS(Status) ) {
        XapiSetLastNTError(Status);
        return FALSE;
        }

    if ( ARGUMENT_PRESENT(lpVolumeNameBuffer) ||
         ARGUMENT_PRESENT(lpVolumeSerialNumber) ) {
        if ( ARGUMENT_PRESENT(lpVolumeNameBuffer) ) {
            VolumeInfoLength = sizeof(*VolumeInfo)+nVolumeNameSize;
            }
        else {
            VolumeInfoLength = sizeof(*VolumeInfo)+MAX_PATH;
            }
        VolumeInfo = RtlAllocateHeap(XapiProcessHeap, 0, VolumeInfoLength);

        if ( !VolumeInfo ) {
            NtClose(Handle);
            XapiSetLastNTError(STATUS_NO_MEMORY);
            return FALSE;
            }
        }

    if ( ARGUMENT_PRESENT(lpFileSystemNameBuffer) ||
         ARGUMENT_PRESENT(lpMaximumComponentLength) ||
         ARGUMENT_PRESENT(lpFileSystemFlags) ) {
        if ( ARGUMENT_PRESENT(lpFileSystemNameBuffer) ) {
            AttributeInfoLength = sizeof(*AttributeInfo) + nFileSystemNameSize;
            }
        else {
            AttributeInfoLength = sizeof(*AttributeInfo) + MAX_PATH;
            }
        AttributeInfo = RtlAllocateHeap(XapiProcessHeap, 0, AttributeInfoLength);
        if ( !AttributeInfo ) {
            NtClose(Handle);
            if ( VolumeInfo ) {
                RtlFreeHeap(XapiProcessHeap, 0,VolumeInfo);
                }
            XapiSetLastNTError(STATUS_NO_MEMORY);
            return FALSE;
            }
        }

    try {
        if ( VolumeInfo ) {
            Status = NtQueryVolumeInformationFile(
                        Handle,
                        &IoStatusBlock,
                        VolumeInfo,
                        VolumeInfoLength,
                        FileFsVolumeInformation
                        );
            if ( !NT_SUCCESS(Status) ) {
                XapiSetLastNTError(Status);
                rv = FALSE;
                goto finally_exit;
                }
            }

        if ( AttributeInfo ) {
            Status = NtQueryVolumeInformationFile(
                        Handle,
                        &IoStatusBlock,
                        AttributeInfo,
                        AttributeInfoLength,
                        FileFsAttributeInformation
                        );
            if ( !NT_SUCCESS(Status) ) {
                XapiSetLastNTError(Status);
                rv = FALSE;
                goto finally_exit;
                }
            }
        try {

            if ( ARGUMENT_PRESENT(lpVolumeNameBuffer) ) {
                if ( VolumeInfo->VolumeLabelLength >= nVolumeNameSize ) {
                    SetLastError(ERROR_BAD_LENGTH);
                    rv = FALSE;
                    goto finally_exit;
                    }
                else {
                    RtlMoveMemory( lpVolumeNameBuffer,
                                   VolumeInfo->VolumeLabel,
                                   VolumeInfo->VolumeLabelLength );

                    *(lpVolumeNameBuffer + (VolumeInfo->VolumeLabelLength / sizeof(OCHAR))) = OBJECT_NULL;
                    }
                }

            if ( ARGUMENT_PRESENT(lpVolumeSerialNumber) ) {
                *lpVolumeSerialNumber = VolumeInfo->VolumeSerialNumber;
                }

            if ( ARGUMENT_PRESENT(lpFileSystemNameBuffer) ) {

                if ( AttributeInfo->FileSystemNameLength >= nFileSystemNameSize ) {
                    SetLastError(ERROR_BAD_LENGTH);
                    rv = FALSE;
                    goto finally_exit;
                    }
                else {
                    RtlMoveMemory( lpFileSystemNameBuffer,
                                   AttributeInfo->FileSystemName,
                                   AttributeInfo->FileSystemNameLength );

                    *(lpFileSystemNameBuffer + (AttributeInfo->FileSystemNameLength / sizeof(OCHAR))) = OBJECT_NULL;
                    }
                }

            if ( ARGUMENT_PRESENT(lpMaximumComponentLength) ) {
                *lpMaximumComponentLength = AttributeInfo->MaximumComponentNameLength;
                }

            if ( ARGUMENT_PRESENT(lpFileSystemFlags) ) {
                *lpFileSystemFlags = AttributeInfo->FileSystemAttributes;
                }
            }
        except (EXCEPTION_EXECUTE_HANDLER) {
            XapiSetLastNTError(STATUS_ACCESS_VIOLATION);
            return FALSE;
            }
        rv = TRUE;
finally_exit:;
        }
    finally {
        NtClose(Handle);
        if ( VolumeInfo ) {
            RtlFreeHeap(XapiProcessHeap, 0,VolumeInfo);
            }
        if ( AttributeInfo ) {
            RtlFreeHeap(XapiProcessHeap, 0,AttributeInfo);
            }
        }
    return rv;
}

NTSTATUS
XapiSelectCachePartition(
    IN BOOL fAlwaysFormat,
    OUT PULONG pnCachePartition,
    OUT PBOOL pfForceFormat
    )
{
    OBJECT_ATTRIBUTES oa;
    NTSTATUS          Status;
    IO_STATUS_BLOCK   statusBlock;
    HANDLE            hVolume;
    DWORD             dwTitleId = XeImageHeader()->Certificate->TitleID;
    ULONG             CachePartitionCount;
    ULONG             nCachePartition;

    ASSERT(pnCachePartition && pfForceFormat);

    InitializeObjectAttributes(&oa,
                               (POBJECT_STRING) &XapiHardDisk,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);

    Status = NtOpenFile(&hVolume,
                        SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
                        &oa,
                        &statusBlock,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        FILE_SYNCHRONOUS_IO_ALERT);

    if (NT_SUCCESS(Status))
    {
        UCHAR rgbSectorBuffer[XBOX_HD_SECTOR_SIZE];
        LARGE_INTEGER byteOffset;

        //
        // Read sector 4 (XBOX_CACHE_DB_SECTOR_INDEX)
        //

        byteOffset.QuadPart = XBOX_CACHE_DB_SECTOR_INDEX * XBOX_HD_SECTOR_SIZE;

        Status = NtReadFile(hVolume,
                            0,
                            NULL,
                            NULL,
                            &statusBlock,
                            rgbSectorBuffer,
                            sizeof(rgbSectorBuffer),
                            &byteOffset);

        if (NT_SUCCESS(Status))
        {
            PXBOX_CACHE_DB_SECTOR pCacheDBSec = (PXBOX_CACHE_DB_SECTOR) rgbSectorBuffer;
            PX_CACHE_DB_ENTRY pCacheDB = (PX_CACHE_DB_ENTRY) pCacheDBSec->Data;
            ULONG i;
            ULONG iPrevDBIndex = (*HalDiskCachePartitionCount - 1);
            ULONG iNewDBIndex;

            if ((XBOX_CACHE_DB_SECTOR_BEGIN_SIGNATURE != pCacheDBSec->SectorBeginSignature) ||
                (XBOX_CACHE_DB_SECTOR_END_SIGNATURE != pCacheDBSec->SectorEndSignature) ||
                (XBOX_CACHE_DB_CUR_VERSION != pCacheDBSec->Version))
            {
                RtlZeroMemory(rgbSectorBuffer, sizeof(rgbSectorBuffer));

                pCacheDBSec->SectorBeginSignature = XBOX_CACHE_DB_SECTOR_BEGIN_SIGNATURE;
                pCacheDBSec->Version = XBOX_CACHE_DB_CUR_VERSION;
                pCacheDBSec->SectorEndSignature = XBOX_CACHE_DB_SECTOR_END_SIGNATURE;
            }

            //
            // Assume that we're going to force the partition to be formatted unless
            // we find out otherwise
            //

            *pfForceFormat = TRUE;

            //
            // Obtain the number of cache partitions from the HAL.  The HAL
            // won't boot with a drive too small to contain one cache
            // partition, but we do need to limit the cache partition count
            // to the number that we can describe in the cache partition
            // database.
            //

            CachePartitionCount = *HalDiskCachePartitionCount;

            ASSERT(CachePartitionCount > 0);

            if (CachePartitionCount > XBOX_CACHE_DB_MAX_ENTRY_COUNT)
            {
                CachePartitionCount = XBOX_CACHE_DB_MAX_ENTRY_COUNT;
            }

            nCachePartition = 0;

            //
            // Search for a cache partition already allocated by this title
            //
            
            for (i = 0; i < CachePartitionCount; i++)
            {
                if ((dwTitleId == pCacheDB[i].dwTitleId) && pCacheDB[i].fUsed)
                {
                    nCachePartition = pCacheDB[i].nCacheIndex + XDISK_FIRST_CACHE_PARTITION;
                    iPrevDBIndex = i;
                    
                    //
                    // We found an existing cache partition, there is no longer a
                    // requirement that the partition be formatted.
                    //

                    *pfForceFormat = FALSE;
                    
                    break;
                }
            }

            //
            // If that search failed, search for a cache partition that is not in use
            // using ugly n-squared algorithm (fortunately, CachePartitionCount is small)
            //
            
            if (0 == nCachePartition)
            {
                UINT j;
                for (j = 0; j < CachePartitionCount; j++)
                {
                    for (i = 0; i < CachePartitionCount; i++)
                    {
                        if ((pCacheDB[i].fUsed) && (pCacheDB[i].nCacheIndex == j))
                        {
                            break;
                        }
                    }

                    //
                    // If we made it through the loop without a match, then this
                    // cache partition (index stored in the j variable) is available
                    //
                    
                    if (i == CachePartitionCount)
                    {
                        nCachePartition = j + XDISK_FIRST_CACHE_PARTITION;
                    }
                }
            }
                
            //
            // If that search failed, grab the oldest cache partition
            //
            // The Cache DB is stored in MRU order - the first entry was the most recently
            // used and the last entry was the least recently used
            //
            
            if (0 == nCachePartition)
            {
                nCachePartition = pCacheDB[CachePartitionCount - 1].nCacheIndex + XDISK_FIRST_CACHE_PARTITION;
            }

            //
            // If the value we've chosen is too large for some reason, pull it back and give
            // it a reasonable value
            //
            
            if (nCachePartition >= CachePartitionCount + XDISK_FIRST_CACHE_PARTITION)
            {
                nCachePartition = (CachePartitionCount - 1) + XDISK_FIRST_CACHE_PARTITION;
            }

            ASSERT(nCachePartition != 0);
            *pnCachePartition = nCachePartition;

            //
            // Normally, we bump this to the top of the cache db because that is how we
            // indicate that it was most recently used.  When fAlwaysFormat is set, we
            // always put it at the end of the list because we want it to be reclaimed
            // when the next title asks for a cache partition
            //
            
            iNewDBIndex = fAlwaysFormat ? (CachePartitionCount - 1) : 0;

            if (!fAlwaysFormat && (0 != iPrevDBIndex))
            {
                //
                // Modify the cache db - slide everything down and make room for this
                // entry at the top of the list
                //

                ASSERT(iPrevDBIndex < CachePartitionCount);
                
                RtlMoveMemory(&(pCacheDB[1]),
                              &(pCacheDB[0]),
                              iPrevDBIndex * sizeof(X_CACHE_DB_ENTRY));
            }
            
            //
            // Write this entry into the new index of the cache db
            //
            // Note that if this function was called with fAlwaysFormat set to TRUE,
            // the entry will be marked with fUsed == FALSE, so that it will be chosen
            // first the next time a title needs to allocate a new partition
            //

            pCacheDB[iNewDBIndex].dwTitleId = dwTitleId;
            pCacheDB[iNewDBIndex].nCacheIndex = (nCachePartition - XDISK_FIRST_CACHE_PARTITION);
            pCacheDB[iNewDBIndex].fUsed = (!fAlwaysFormat);

            //
            // Ignore status result
            //

            NtWriteFile(hVolume,
                        0,
                        NULL,
                        NULL,
                        &statusBlock,
                        rgbSectorBuffer,
                        sizeof(rgbSectorBuffer),
                        &byteOffset);
        }

        NtClose(hVolume);
    }

    return Status;
}


BOOL
WINAPI
XMountUtilityDrive(
    BOOL fFormatClean
    )
{
    BOOL fRet = TRUE;
    BOOL fForceFormat;
    ULONG nPartition;
    NTSTATUS Status;

#if DBG
    if (g_fMountedUtilityDrive)
    {
        RIP("XMountUtilityDrive(): Utility Drive has already been mounted");
    }
#endif // DBG

    Status = XapiSelectCachePartition(fFormatClean, &nPartition, &fForceFormat);

    if (NT_SUCCESS(Status))
    {
        OCHAR szCacheDrive[MAX_PATH];
        OBJECT_STRING VolString, DriveString;
        BOOL fDoFormat = (fFormatClean || fForceFormat);
        ULONG BytesPerCluster = XeUtilityDriveClusterSize();

        _snoprintf(szCacheDrive,
                   ARRAYSIZE(szCacheDrive),
                   CacheDriveFormat,
                   nPartition);

        RtlInitObjectString(&VolString, szCacheDrive);

        //
        // The DriveString should not end in a backslash, so init from the same
        // string, but subtract a character on the Length member.
        //

        RtlInitObjectString(&DriveString, szCacheDrive);
        DriveString.Length -= sizeof(OCHAR);

        if (fDoFormat)
        {
            fRet = XapiFormatFATVolumeEx(&DriveString, BytesPerCluster);
        }

        if (fRet)
        {
            Status = XapiValidateDiskPartitionEx(&VolString, BytesPerCluster);

            if (!NT_SUCCESS(Status) && !fDoFormat)
            {
                //
                // If the validate failed for some reason and we didn't just format
                // the partition, go ahead and format it now (make the system more
                // self-healing)
                //

                if (XapiFormatFATVolumeEx(&DriveString, BytesPerCluster))
                {
                    Status = XapiValidateDiskPartitionEx(&VolString, BytesPerCluster);
                }
            }

            if (NT_SUCCESS(Status))
            {
                // Give the cache partition a drive letter
                Status = IoCreateSymbolicLink((POBJECT_STRING) &ZDrive, &DriveString);
            }

            fRet = NT_SUCCESS(Status);

            if (!fRet)
            {
                XapiSetLastNTError(Status);
            }
        }
    }
    else
    {
        fRet = FALSE;
        XapiSetLastNTError(Status);
    }

#if DBG
    if (fRet)
    {
        g_fMountedUtilityDrive = TRUE;
    }
#endif // DBG

    return fRet;
}


BOOL
WINAPI
XFormatUtilityDrive(
    VOID
    )
{
    NTSTATUS status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    CHAR Target[MAX_PATH];
    ULONG TargetLength;
    OBJECT_STRING ObjectTarget;
    HANDLE Handle;

#if DBG
    if (!g_fMountedUtilityDrive)
    {
        RIP("XFormatUtilityDrive(): Utility Drive has not been mounted");
    }
#endif // DBG

    InitializeObjectAttributes(&ObjectAttributes,
                               (POBJECT_STRING) &ZDrive,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);

    status = NtOpenSymbolicLinkObject(&Handle, &ObjectAttributes);

    if (!NT_SUCCESS(status))
    {
        XapiSetLastNTError(status);
        return FALSE;
    }

    ObjectTarget.Buffer = Target;
    ObjectTarget.MaximumLength = sizeof(Target);

    status = NtQuerySymbolicLinkObject(Handle, &ObjectTarget, &TargetLength);

    NtClose(Handle);

    if (!NT_SUCCESS(status))
    {
        XapiSetLastNTError(status);
        return FALSE;
    }

    return XapiFormatFATVolumeEx(&ObjectTarget, XeUtilityDriveClusterSize());
}


DWORD
WINAPI
XMountAlternateTitle(
    IN PCOSTR lpRootPath,
    IN DWORD dwAltTitleId,
    OUT POCHAR pchDrive
    )
{
    NTSTATUS status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    OBJECT_STRING ObjectName;
    OCHAR szDosDevice[MAX_PATH];
    OCHAR Target[MAX_PATH];
    ULONG TargetLength;
    OBJECT_STRING ObjectTarget;
    OCHAR AltTitleId[CCHMAX_HEX_DWORD];
    HANDLE Handle;
    OCHAR chDrive;
    BOOL fTData;
    PXBEIMAGE_CERTIFICATE Certificate = XeImageHeader()->Certificate;
    int i;

    RIP_ON_NOT_TRUE(XMountAlternateTitle, (lpRootPath != NULL));
    RIP_ON_NOT_TRUE(XMountAlternateTitle, (lpRootPath[0] != '\0'));
    RIP_ON_NOT_TRUE(XMountAlternateTitle, (lpRootPath[1] == ':'));
    RIP_ON_NOT_TRUE(XMountAlternateTitle, (lpRootPath[2] == '\\'));
    RIP_ON_NOT_TRUE(XMountAlternateTitle, (lpRootPath[3] == '\0'));
    RIP_ON_NOT_TRUE(XMountAlternateTitle, (pchDrive != NULL));

    //
    // Removing the 0x20 bit will make lower case characters uppercase
    //

    chDrive = lpRootPath[0] & (~0x20);
    fTData = (HD_TDATA_DRIVE == chDrive);

#if DBG
    if (((chDrive < MU_FIRST_DRIVE) || (chDrive > MU_LAST_DRIVE)) &&
        (HD_UDATA_DRIVE != chDrive) &&
        (!fTData))
    {
        RIP("XFindFirstSaveGame() invalid drive letter parameter");
    }
#endif // DBG

    for (i = 0; i < ARRAYSIZE(Certificate->AlternateTitleIDs); i++)
    {
        if (0 == Certificate->AlternateTitleIDs[i])
        {
            return ERROR_ACCESS_DENIED;
        }

        if (dwAltTitleId == Certificate->AlternateTitleIDs[i])
        {
            break;
        }
    }

    if (i >= sizeof(Certificate->AlternateTitleIDs))
    {
        return ERROR_ACCESS_DENIED;
    }

    soprintf(szDosDevice, OTEXT("\\??\\%c:"), lpRootPath[0]);

    RtlInitObjectString(&ObjectName, szDosDevice);

    InitializeObjectAttributes(&ObjectAttributes,
                               &ObjectName,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);

    status = NtOpenSymbolicLinkObject(&Handle, &ObjectAttributes);

    if (!NT_SUCCESS(status))
    {
        return RtlNtStatusToDosError(status);
    }

    ObjectTarget.Buffer = Target;
    ObjectTarget.MaximumLength = sizeof(Target);

    status = NtQuerySymbolicLinkObject(Handle, &ObjectTarget, &TargetLength);

    NtClose(Handle);

    if (!NT_SUCCESS(status))
    {
        return RtlNtStatusToDosError(status);
    }

    if ((TargetLength < CCHMAX_HEX_DWORD) ||
        ('\\' != Target[TargetLength - (CCHMAX_HEX_DWORD)]))
    {
        return ERROR_INVALID_DRIVE;
    }

    //
    // Remove the existing title id from the end of the string so we can
    // reuse ObjectTarget below in XapiMapLetterToDirectory()
    //

    ObjectTarget.Length -= CCHMAX_HEX_DWORD;

    DwordToStringO(dwAltTitleId, AltTitleId);

    status = XapiMapLetterToDirectory(fTData ? &WDrive : &XDrive,
                                      (PCOBJECT_STRING) &ObjectTarget,
                                      AltTitleId,
                                      FALSE,
                                      NULL,
                                      FALSE);

    if (NT_SUCCESS(status))
    {
        *pchDrive = fTData ? HD_ALT_TDATA_DRIVE : HD_ALT_UDATA_DRIVE;

        if (!fTData && (HD_UDATA_DRIVE != chDrive))
        {
            //
            // Remember that we've mapped an alternate drive letter to this MU
            // drive so that we can unmount the alternate drive automatically
            // if the "real" MU drive is unmounted later using XUnmountMU()
            //

            ASSERT(OBJECT_NULL == XapiMuInfo.DriveWithAltDriveMapped);
            XapiMuInfo.DriveWithAltDriveMapped = chDrive;
        }
    }

    return RtlNtStatusToDosError(status);
}

DWORD
WINAPI
XUnmountAlternateTitle(
    IN OCHAR chDrive
    )
{
    NTSTATUS Status;
    OCHAR szDosDevice[MAX_PATH];
    OBJECT_STRING DosDevice;

    //
    // Removing the 0x20 bit will make lower case characters uppercase
    //

    chDrive &= (~0x20);

#if DBG
    switch (chDrive)
    {
        case HD_ALT_TDATA_DRIVE:
        case HD_ALT_UDATA_DRIVE:
            break;

        default:
            RIP("XUnmountAlternateTitle() - invalid chDrive parameter");
    }
#endif // DBG

    soprintf(szDosDevice, OTEXT("\\??\\%c:"), chDrive);

    RtlInitObjectString(&DosDevice, szDosDevice);

    //
    // BUGBUG: Do more than remove the symbolic link - we need to unmount
    // the filesystem here.
    //

    Status = IoDeleteSymbolicLink(&DosDevice);

    if ((HD_ALT_UDATA_DRIVE == chDrive) && NT_SUCCESS(Status))
    {
        XapiMuInfo.DriveWithAltDriveMapped = OBJECT_NULL;
    }

    return RtlNtStatusToDosError(Status);
}

DWORD
WINAPI
XGetDiskClusterSize(
    PCOSTR lpRootPathName
    )
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES Obja;
    HANDLE Handle;
    OBJECT_STRING FileName;
    IO_STATUS_BLOCK IoStatusBlock;
    FILE_FS_SIZE_INFORMATION NormalSizeInfo;

    RIP_ON_NOT_TRUE("XGetDiskClusterSize()", ARGUMENT_PRESENT(lpRootPathName));

    RtlInitObjectString(&FileName, lpRootPathName);

    InitializeObjectAttributes(
        &Obja,
        &FileName,
        OBJ_CASE_INSENSITIVE,
        ObDosDevicesDirectory(),
        NULL
        );

    //
    // Open the file
    //

    Status = NtOpenFile(
                &Handle,
                (ACCESS_MASK)FILE_LIST_DIRECTORY | SYNCHRONIZE,
                &Obja,
                &IoStatusBlock,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE | FILE_OPEN_FOR_FREE_SPACE_QUERY
                );
    if ( !NT_SUCCESS(Status) ) {
        XapiSetLastNTError(Status);
        if ( GetLastError() == ERROR_FILE_NOT_FOUND ) {
            SetLastError(ERROR_PATH_NOT_FOUND);
            }
        return 0;
        }

    //
    // Determine the size parameters of the volume.
    //

    Status = NtQueryVolumeInformationFile(
                Handle,
                &IoStatusBlock,
                &NormalSizeInfo,
                sizeof(NormalSizeInfo),
                FileFsSizeInformation
                );
    NtClose(Handle);
    if ( !NT_SUCCESS(Status) ) {
        XapiSetLastNTError(Status);
        return 0;
        }

    ASSERT((0 != NormalSizeInfo.BytesPerSector) && (0 != NormalSizeInfo.SectorsPerAllocationUnit));

    return (ULONG) (NormalSizeInfo.BytesPerSector * NormalSizeInfo.SectorsPerAllocationUnit);
}

DWORD
WINAPI
XGetDiskSectorSize(
    PCOSTR lpRootPathName
    )
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES Obja;
    HANDLE Handle;
    OBJECT_STRING FileName;
    IO_STATUS_BLOCK IoStatusBlock;
    FILE_FS_SIZE_INFORMATION NormalSizeInfo;

    RIP_ON_NOT_TRUE("XGetDiskSectorSize()", ARGUMENT_PRESENT(lpRootPathName));

    RtlInitObjectString(&FileName, lpRootPathName);

    InitializeObjectAttributes(
        &Obja,
        &FileName,
        OBJ_CASE_INSENSITIVE,
        ObDosDevicesDirectory(),
        NULL
        );

    //
    // Open the file
    //

    Status = NtOpenFile(
                &Handle,
                (ACCESS_MASK)FILE_LIST_DIRECTORY | SYNCHRONIZE,
                &Obja,
                &IoStatusBlock,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE | FILE_OPEN_FOR_FREE_SPACE_QUERY
                );
    if ( !NT_SUCCESS(Status) ) {
        XapiSetLastNTError(Status);
        if ( GetLastError() == ERROR_FILE_NOT_FOUND ) {
            SetLastError(ERROR_PATH_NOT_FOUND);
            }
        return 0;
        }

    //
    // Determine the size parameters of the volume.
    //

    Status = NtQueryVolumeInformationFile(
                Handle,
                &IoStatusBlock,
                &NormalSizeInfo,
                sizeof(NormalSizeInfo),
                FileFsSizeInformation
                );
    NtClose(Handle);
    if ( !NT_SUCCESS(Status) ) {
        XapiSetLastNTError(Status);
        return 0;
        }

    ASSERT(0 != NormalSizeInfo.BytesPerSector);

    return NormalSizeInfo.BytesPerSector;
}


DWORD
WINAPI
XMUNameFromDriveLetter(
    IN CHAR chDrive,
    OUT LPWSTR lpName,
    IN UINT cchName
    )
{
    NTSTATUS Status;
    OCHAR szDosDevice[8];
    OBJECT_STRING DosDevice;
    OBJECT_ATTRIBUTES Obja;
    HANDLE Handle;
    IO_STATUS_BLOCK IoStatusBlock;
    FSCTL_VOLUME_METADATA VolumeMetadata;
    WCHAR VolumeName[FAT_VOLUME_NAME_LENGTH];

    //
    // Fail if the device is not already mounted.
    //
    if (!MU_IS_MOUNTED(chDrive))
    {
        XDBGERR("XAPI", "XMUNameFromDriveLetter() MU %c: is not mounted", chDrive);
        return ERROR_INVALID_DRIVE;
    }

    //
    // Open a handle to the volume or directory of the drive.
    //
    soprintf(szDosDevice, OTEXT("\\??\\%c:"), chDrive);
    RtlInitObjectString(&DosDevice, szDosDevice);

    InitializeObjectAttributes(
        &Obja,
        (POBJECT_STRING) &DosDevice,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    Status = NtOpenFile(&Handle,
                        SYNCHRONIZE | GENERIC_READ,
                        &Obja,
                        &IoStatusBlock,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        FILE_SYNCHRONOUS_IO_ALERT);

    if (NT_SUCCESS(Status))
    {
        VolumeMetadata.ByteOffset = FIELD_OFFSET(FAT_VOLUME_METADATA, VolumeName);
        VolumeMetadata.TransferLength = sizeof(VolumeName);
        VolumeMetadata.TransferBuffer = VolumeName;

        Status = NtFsControlFile(Handle,
                                 NULL,
                                 NULL,
                                 NULL,
                                 &IoStatusBlock,
                                 FSCTL_READ_VOLUME_METADATA,
                                 &VolumeMetadata,
                                 sizeof(VolumeMetadata),
                                 NULL,
                                 0);

        if (NT_SUCCESS(Status))
        {
            lstrcpynW(lpName, VolumeName, min(cchName, FAT_VOLUME_NAME_LENGTH));
        }

        NtClose(Handle);
    }

    return RtlNtStatusToDosError(Status);
}

#endif // ! XAPILIBP

#ifdef XAPILIBP

VOID
XapiDeleteCachePartition(
    IN DWORD dwTitleId
    )
{
    OBJECT_ATTRIBUTES oa;
    NTSTATUS          Status;
    IO_STATUS_BLOCK   statusBlock;
    HANDLE            hVolume;
    ULONG             CachePartitionCount;

    InitializeObjectAttributes(&oa,
                               (POBJECT_STRING) &XapiHardDisk,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);

    Status = NtOpenFile(&hVolume,
                        SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
                        &oa,
                        &statusBlock,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        FILE_SYNCHRONOUS_IO_ALERT);

    if (NT_SUCCESS(Status))
    {
        UCHAR rgbSectorBuffer[XBOX_HD_SECTOR_SIZE];
        LARGE_INTEGER byteOffset;

        //
        // Read sector 4 (XBOX_CACHE_DB_SECTOR_INDEX)
        //

        byteOffset.QuadPart = XBOX_CACHE_DB_SECTOR_INDEX * XBOX_HD_SECTOR_SIZE;

        Status = NtReadFile(hVolume,
                            0,
                            NULL,
                            NULL,
                            &statusBlock,
                            rgbSectorBuffer,
                            sizeof(rgbSectorBuffer),
                            &byteOffset);

        if (NT_SUCCESS(Status))
        {
            PXBOX_CACHE_DB_SECTOR pCacheDBSec = (PXBOX_CACHE_DB_SECTOR) rgbSectorBuffer;
            PX_CACHE_DB_ENTRY pCacheDB = (PX_CACHE_DB_ENTRY) pCacheDBSec->Data;
            ULONG i;

            if ((XBOX_CACHE_DB_SECTOR_BEGIN_SIGNATURE != pCacheDBSec->SectorBeginSignature) ||
                (XBOX_CACHE_DB_SECTOR_END_SIGNATURE != pCacheDBSec->SectorEndSignature) ||
                (XBOX_CACHE_DB_CUR_VERSION != pCacheDBSec->Version))
            {
                NtClose(hVolume);

                return;
            }

            //
            // Obtain the number of cache partitions from the HAL.  The HAL
            // won't boot with a drive too small to contain one cache
            // partition, but we do need to limit the cache partition count
            // to the number that we can describe in the cache partition
            // database.
            //

            CachePartitionCount = *HalDiskCachePartitionCount;

            ASSERT(CachePartitionCount > 0);

            if (CachePartitionCount > XBOX_CACHE_DB_MAX_ENTRY_COUNT)
            {
                CachePartitionCount = XBOX_CACHE_DB_MAX_ENTRY_COUNT;
            }

            //
            // Search the cache partition database for a matching title ID
            // If found, clear the title ID so we can write it back to the sector
            //

            for (i = 0; i < CachePartitionCount; i++)
            {
                if (dwTitleId == pCacheDB[i].dwTitleId)
                {
                    pCacheDB[i].dwTitleId = 0;
                    pCacheDB[i].fUsed = FALSE;

                    break;
                }
            }

            //
            // If we picked this partition because it is the oldest and not because
            // we matched a TitleId, then we must format the cache partition before
            // giving it to the title
            //

            //
            // Write back into the cache db
            //

            //
            // Ignore status result
            //

            Status = NtWriteFile(hVolume,
                                 0,
                                 NULL,
                                 NULL,
                                 &statusBlock,
                                 rgbSectorBuffer,
                                 sizeof(rgbSectorBuffer),
                                 &byteOffset);
        }

        NtClose(hVolume);
    }
}


NTSTATUS
XapiGetCachePartitions(
    IN PX_CACHE_DB_ENTRY pCacheEntriesBuffer,
    IN UINT cbBufferSize,
    OUT PDWORD pdwNumCacheEntries )
{
    OBJECT_ATTRIBUTES oa;
    NTSTATUS          Status;
    IO_STATUS_BLOCK   statusBlock;
    HANDLE            hVolume;
    ULONG             CachePartitionCount;

    ASSERT(pdwNumCacheEntries);

    //
    // Set the number of entries written to 0, in case of failure
    //

    *pdwNumCacheEntries = 0;

    InitializeObjectAttributes(&oa,
                               (POBJECT_STRING) &XapiHardDisk,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);

    Status = NtOpenFile(&hVolume,
                        SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
                        &oa,
                        &statusBlock,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        FILE_SYNCHRONOUS_IO_ALERT);

    if (NT_SUCCESS(Status))
    {
        UCHAR rgbSectorBuffer[XBOX_HD_SECTOR_SIZE];
        LARGE_INTEGER byteOffset;

        //
        // Read sector 4 (XBOX_CACHE_DB_SECTOR_INDEX)
        //

        byteOffset.QuadPart = XBOX_CACHE_DB_SECTOR_INDEX * XBOX_HD_SECTOR_SIZE;

        Status = NtReadFile(hVolume,
                            0,
                            NULL,
                            NULL,
                            &statusBlock,
                            rgbSectorBuffer,
                            sizeof(rgbSectorBuffer),
                            &byteOffset);

        if (NT_SUCCESS(Status))
        {
            PXBOX_CACHE_DB_SECTOR pCacheDBSec = (PXBOX_CACHE_DB_SECTOR) rgbSectorBuffer;
            PX_CACHE_DB_ENTRY pCacheDB = (PX_CACHE_DB_ENTRY) pCacheDBSec->Data;
            ULONG i;

            if ((XBOX_CACHE_DB_SECTOR_BEGIN_SIGNATURE != pCacheDBSec->SectorBeginSignature) ||
                (XBOX_CACHE_DB_SECTOR_END_SIGNATURE != pCacheDBSec->SectorEndSignature) ||
                (XBOX_CACHE_DB_CUR_VERSION != pCacheDBSec->Version))
            {
                RtlZeroMemory(rgbSectorBuffer, sizeof(rgbSectorBuffer));
            }

            //
            // Obtain the number of cache partitions from the HAL.  The HAL
            // won't boot with a drive too small to contain one cache
            // partition, but we do need to limit the cache partition count
            // to the number that we can describe in the cache partition
            // database.
            //

            CachePartitionCount = *HalDiskCachePartitionCount;

            ASSERT(CachePartitionCount > 0);

            if (CachePartitionCount > XBOX_CACHE_DB_MAX_ENTRY_COUNT)
            {
                CachePartitionCount = XBOX_CACHE_DB_MAX_ENTRY_COUNT;
            }

            //
            // Search the cache partition database for a matching title ID
            //

            for (i = 0; i < CachePartitionCount; i++)
            {
                if( 0 != pCacheDB[i].dwTitleId )
                {
                    if( ( pCacheEntriesBuffer != NULL ) && ( ( sizeof( X_CACHE_DB_ENTRY ) * (*pdwNumCacheEntries + 1) ) <= cbBufferSize ) )
                    {
                        RtlCopyMemory(&(pCacheEntriesBuffer[*pdwNumCacheEntries]),
                                      &(pCacheDB[i]),
                                      sizeof(X_CACHE_DB_ENTRY));
                    }

                    *pdwNumCacheEntries += 1;
                }
            }
        }

        NtClose(hVolume);
    }

    return Status;
}


DWORD
WINAPI
XMUWriteNameToDriveLetter(
    IN CHAR chDrive,
    IN LPCWSTR lpName
    )
{
    NTSTATUS Status;
    OCHAR szDosDevice[8];
    OBJECT_STRING DosDevice;
    OBJECT_ATTRIBUTES Obja;
    HANDLE Handle;
    IO_STATUS_BLOCK IoStatusBlock;
    FSCTL_VOLUME_METADATA VolumeMetadata;
    WCHAR VolumeName[FAT_VOLUME_NAME_LENGTH];

    //
    //  Fail if the device is not already mounted.
    //
    if (!MU_IS_MOUNTED(chDrive))
    {
        XDBGERR("XAPI", "XMUNameFromDriveLetter() MU %c: is not mounted", chDrive);
        return ERROR_INVALID_DRIVE;
    }

    ASSERT(wcslen(lpName) < MAX_MUNAME);

    //
    // Open a handle to the volume or directory of the drive.
    //
    soprintf(szDosDevice, OTEXT("\\??\\%c:"), chDrive);
    RtlInitObjectString(&DosDevice, szDosDevice);

    InitializeObjectAttributes(
        &Obja,
        (POBJECT_STRING) &DosDevice,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    Status = NtOpenFile(&Handle,
                        SYNCHRONIZE | GENERIC_WRITE,
                        &Obja,
                        &IoStatusBlock,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        FILE_SYNCHRONOUS_IO_ALERT);

    if (NT_SUCCESS(Status))
    {
        lstrcpynW(VolumeName, lpName, FAT_VOLUME_NAME_LENGTH);

        VolumeMetadata.ByteOffset = FIELD_OFFSET(FAT_VOLUME_METADATA, VolumeName);
        VolumeMetadata.TransferLength = sizeof(VolumeName);
        VolumeMetadata.TransferBuffer = VolumeName;

        Status = NtFsControlFile(Handle,
                                 NULL,
                                 NULL,
                                 NULL,
                                 &IoStatusBlock,
                                 FSCTL_WRITE_VOLUME_METADATA,
                                 &VolumeMetadata,
                                 sizeof(VolumeMetadata),
                                 NULL,
                                 0);

        NtClose(Handle);
    }

    return RtlNtStatusToDosError(Status);
}

#endif // XAPILIBP
