#ifndef UTILS_H
#define UTILS_H

#include <windows.h>
#include <stdint.h>
#include "xbe_cert.h"

#define SECTORS_PER_READ 128 // 128 * 2048 = 256KB (The 8163B has a 256KB hardware cache.)
#define MAX_RETRIES 3        // How many times to retry a failed block

// Xbox DVD layout constants
//
// REDUMP_SECTORS is the unlocked game/XISO view length reported by the
// GDR-8050L after successful authentication.  It is not the full Redump-style
// reconstructed disc-image length.
#define REDUMP_SECTORS 3431264

// Full Original Xbox/XGD1 Redump-style 2048-byte sector image length.
// This includes video L0, pre-game padding, the full unlocked game/XISO view,
// post-game padding, and video L1.  Non-LBA structures such as PFI/DMI/lead-in
// remain sidecar/profile data, not bytes inside the ISO stream.
#define XGD1_FULL_REDUMP_SECTORS 3820880
#define XGD1_GAME_OUTPUT_START_LBA 198144
#define XGD1_XISO_LEADIN_SECTORS 32
#define XGD1_GAME_SOURCE_START_LBA 32
#define XGD1_GAME_SOURCE_SECTORS (REDUMP_SECTORS - XGD1_XISO_LEADIN_SECTORS)
#define XGD1_VIDEO_TOTAL_SECTORS 6992
#define XGD1_VIDEO_L0_SECTORS 6832
#define XGD1_VIDEO_L1_SECTORS 160
#define XGD1_VIDEO_L1_OUTPUT_START_LBA (XGD1_FULL_REDUMP_SECTORS - XGD1_VIDEO_L1_SECTORS)

// Redump/XGD1 physical layer-break from the Redump reference page.
// This is metadata for the reconstructed 2048-byte-sector ISO image.
#define XGD1_REDUMP_LAYER_BREAK_LBA 1913776

// Compatibility alias used by existing code paths that refer to the output
// layer break.  Do not confuse this with the GDR-8050L unlocked game-view
// sector count (3431264).
#define LAYER_BREAK XGD1_REDUMP_LAYER_BREAK_LBA
#define LAYER_THRESHOLD 2000000

// Legacy contiguous/hybrid layout start used by earlier experiments.
#define START_LBA_MAGIC 306112

// Handle cases where stdbool.h isn't available
#ifndef __cplusplus
typedef int bool;
#define true 1
#define false 0
#endif

#pragma pack(push, 1)
typedef struct _XDFS_SUPERBLOCK
{
    char Magic[20];         // Should be "Microsoft Xbox Disk"
    uint32_t RootDirSector; // Sector where the root directory starts
    uint32_t RootDirSize;   // Size of the root directory in bytes
    FILETIME ImageTime;     // Creation time
    char Unused[0x7C8];     // Padding to 2048 bytes
} XDFS_SUPERBLOCK;

typedef struct _XDFS_DIRENTRY
{
    uint16_t LeftEntry;  // Offset to left node (it's a B-Tree)
    uint16_t RightEntry; // Offset to right node
    uint32_t Sector;     // Starting sector of the file/dir
    uint32_t Size;       // Size of the file/dir
    uint8_t Attributes;  // Directory, Hidden, etc.
    uint8_t NameLength;  // Length of name
    char Name[1];        // The name (variable length)
} XDFS_DIRENTRY;
#pragma pack(pop)

#pragma pack(push, 1) // Ensure no padding is added by the compiler
typedef struct
{
    // Offset: 0x00 (0)
    char     Identifier[20];   // "MICROSOFT*XBOX*MEDIA"
    
    // Offset: 0x14 (20)
    uint32_t RootLBA;          // LBA of the Root Directory Table (Relative to Partition Start)
    
    // Offset: 0x18 (24)
    uint32_t RootSize;         // Size of the Root Directory Table in bytes
    
    // Offset: 0x1C (28)
    uint32_t VolumeSize;       // Total Number of Sectors in this Partition (The 2.11GB value)
    
    // Offset: 0x20 (32)
    uint64_t VolumeTimestamp;  // 64-bit Windows FileTime
    
    // Offset: 0x28 (40)
    // The remainder of the 2048-byte sector is reserved/padding (2048 - 40 = 2008)
    uint8_t  Reserved[2008];  
} XDFS_VOLUME_DESCRIPTOR;
#pragma pack(pop)

// XDFS Directory Entry Structure
typedef struct
{
    uint16_t LeftChild;  // Offset / 4
    uint16_t RightChild; // Offset / 4
    uint32_t StartLBA;   // File Data LBA
    uint32_t FileSize;   // File Size in Bytes
    uint8_t Attributes;
    uint8_t FileNameLength;
    char FileName[1]; // Variable length
} XDFS_DIR_ENTRY;

HANDLE OpenDrive(char driveLetter);
void CloseDrive(HANDLE hDevice);
int IsDiscPresent(HANDLE hDevice);
void ControlTray(HANDLE hDevice, BOOL eject);
BOOL TestUnitReady(HANDLE hDevice);
BOOL StartDriveUnit(HANDLE hDevice);
BOOL EnsureDriveReady(HANDLE hDevice, DWORD timeoutMs);
BOOL StopDriveUnit(HANDLE hDevice);
void AutomateTrayCycle(HANDLE hDevice);
BOOL SetDriveSpeedMax(HANDLE hDevice);
void ForceMediaRefresh(HANDLE hDevice);
void HexDump(unsigned char* buffer, uint32_t size);
void outputdata(const uint8_t* buf, uint32_t lines);
uint8_t chksum8(const unsigned char *buff, size_t len);
void FormatElapsedTime(DWORD dwMilliseconds, char* outStr);
void PrintFormattedCapacity(unsigned char* scsibuffer);
void ListOpticalDrives(void);
uint32_t GetTotalSectors(HANDLE hDevice);
uint32_t GetXboxPhysicalSectors(HANDLE hDevice);
void RefreshVolume(HANDLE hDevice);
void ListDirectoryRecursive(HANDLE hDevice, uint32_t lba, uint32_t size, int level);
void ReadXboxGameDir(HANDLE hDevice);
void SanitizeFilename(char* filename);
BOOL ScsiReadSectors(HANDLE hDevice, uint32_t lba, uint16_t count, unsigned char* buffer);
XboxGameInfo GetXboxGameInfo(HANDLE hDevice);
void DisplayXboxGameInfo(XboxGameInfo info);
void DisplayXboxRating(uint32_t ratings);
void GetMediaID(HANDLE hDevice, char* outMediaId);
void PrintGamePartitionHash(const char* filename);
void GetDiscMetadata(HANDLE hDevice, uint32_t* totalSectors, bool* isDualLayer, XboxGameInfo *gameInfo);
uint32_t GetGamePartitionSize(HANDLE hDevice, uint32_t totalDiscSectors, XDFS_VOLUME_DESCRIPTOR *vol);
struct xbox_ref_dump_result_s;
BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat,
                      uint32_t totalDiscSectors, bool isDualLayer,
                      bool EjectOnSuccess,
                      int (*cancel)(void *cancel_data),
                      void *cancel_data,
                      struct xbox_ref_dump_result_s *result);

#endif
