#include <windows.h>
#include <ntddscsi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/*
How to use this for Hybrid Firmware Development
Let's say you figure out the Xbox Challenge logic and write a custom 512-byte routine in MN103S assembly to spoof it.

You compile your routine: mn10300-elf-as spoof.s -o spoof.o

Extract the raw bytes: mn10300-elf-objcopy -O binary spoof.o spoof.bin

Identify an empty cave in the drive's RAM (e.g., 0x4007F000).

Run your tool: pp_win.exe D: 4007F000 patch spoof.bin

You then issue a single poke command to overwrite the master security branch instruction in RAM to jmp 0x4007F000.

You can iteratively tweak, recompile, and patch your custom logic into the drive's live RAM in seconds, completely avoiding the flash memory until your hybrid firmware is 100% perfect.
*/

// Hitachi Backdoor Signatures
#define SIG_H 0x48
#define SIG_I 0x49
#define SIG_T 0x54
#define SIG_L 0x4C

// SPTD Structure with Sense Buffer
typedef struct _sptd_sense {
    SCSI_PASS_THROUGH_DIRECT Sptd;
    ULONG Filler;
    UCHAR SenseBuf[32];
} sptd_sense;

// ---------------------------------------------------------
// CORE SCSI DISPATCHER
// ---------------------------------------------------------
int send_scsi_command(HANDLE fd, sptd_sense* sptd_buf, void* data_buffer, ULONG transfer_length, UCHAR data_dir) {
    DWORD bytes;
    sptd_buf->Sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
    sptd_buf->Sptd.CdbLength = 12;
    sptd_buf->Sptd.SenseInfoLength = 18;
    sptd_buf->Sptd.DataIn = data_dir;
    sptd_buf->Sptd.DataTransferLength = transfer_length;
    sptd_buf->Sptd.TimeOutValue = 15;
    sptd_buf->Sptd.DataBuffer = data_buffer;
    sptd_buf->Sptd.SenseInfoOffset = offsetof(sptd_sense, SenseBuf);

    if(!DeviceIoControl(fd, IOCTL_SCSI_PASS_THROUGH_DIRECT, sptd_buf, sizeof(sptd_sense), sptd_buf, sizeof(sptd_sense), &bytes, NULL)) {
        return 0; // Fail
    }
    return 1; // Success
}

// ---------------------------------------------------------
// PEEK BYTE (Read via 0xE7 'HIT')
// ---------------------------------------------------------
int peek_byte(HANDLE fd, uint32_t addr, unsigned char* out_val) {
    sptd_sense sptd_buf;
    unsigned char val[2] = {0}; // Hack: Windows hates DataTransferLength = 1, read 2
    
    memset(&sptd_buf, 0, sizeof(sptd_sense));
    sptd_buf.Sptd.Cdb[0] = 0xE7;
    sptd_buf.Sptd.Cdb[1] = SIG_H;
    sptd_buf.Sptd.Cdb[2] = SIG_I;
    sptd_buf.Sptd.Cdb[3] = SIG_T;
    sptd_buf.Sptd.Cdb[4] = 0x01;
    sptd_buf.Sptd.Cdb[6] = (unsigned char)((addr & 0xFF000000) >> 24);
    sptd_buf.Sptd.Cdb[7] = (unsigned char)((addr & 0x00FF0000) >> 16);
    sptd_buf.Sptd.Cdb[8] = (unsigned char)((addr & 0x0000FF00) >> 8);
    sptd_buf.Sptd.Cdb[9] = (unsigned char)(addr & 0x000000FF);
    sptd_buf.Sptd.Cdb[11] = 1;

    if (!send_scsi_command(fd, &sptd_buf, val, 2, SCSI_IOCTL_DATA_IN)) {
        printf("[-] Peek failed (sense: %02X/%02X/%02X)\n", 
            sptd_buf.SenseBuf[2] & 0x0F, sptd_buf.SenseBuf[12], sptd_buf.SenseBuf[13]);
        return 0;
    }

    *out_val = val[0];
    return 1;
}

// ---------------------------------------------------------
// POKE BYTE (Write via MN103S RAM Injection)
// ---------------------------------------------------------
int poke_byte(HANDLE fd, uint32_t addr, unsigned char val) {
    sptd_sense sptd_buf;
    unsigned char param_list[8];
    int i;
    
    // MN103S Assembly Payload
    unsigned char code[] = {0xFC,0xCD,0x00,0x00,0x00,0x00,0xFC,0x86,0x00,0x00,0x00,0x00,0xF0,0xFC};
    
    // 1. Initiate ModeB
    memset(&sptd_buf, 0, sizeof(sptd_sense));
    sptd_buf.Sptd.Cdb[0] = 0xE7;
    sptd_buf.Sptd.Cdb[1] = SIG_H;
    sptd_buf.Sptd.Cdb[2] = SIG_I;
    sptd_buf.Sptd.Cdb[3] = SIG_T;
    sptd_buf.Sptd.Cdb[4] = 0x30;
    sptd_buf.Sptd.Cdb[5] = 0x90;
    sptd_buf.Sptd.Cdb[6] = 0x90;
    sptd_buf.Sptd.Cdb[7] = 0xD0;
    sptd_buf.Sptd.Cdb[8] = 0x01;
    
    if (!send_scsi_command(fd, &sptd_buf, NULL, 0, SCSI_IOCTL_DATA_UNSPECIFIED)) {
        printf("[-] Failed to initiate modeB\n");
        return 0;
    }

    // Prepare payload
    *((unsigned int *)&code[8]) = addr;
    *((unsigned int *)&code[2]) = val;

    // 2. Upload code a byte at a time using RAM poke command
    for(i = 0; i < sizeof(code); i++) {
        memset(&sptd_buf, 0, sizeof(sptd_sense));
        sptd_buf.Sptd.Cdb[0] = 0xE7;
        sptd_buf.Sptd.Cdb[1] = SIG_H;
        sptd_buf.Sptd.Cdb[2] = SIG_I;
        sptd_buf.Sptd.Cdb[3] = SIG_T;
        sptd_buf.Sptd.Cdb[4] = 0xCC;
        sptd_buf.Sptd.Cdb[5] = code[i];
        sptd_buf.Sptd.Cdb[8] = (unsigned char)(((0x80000000 + i) & 0xFF000000) >> 24);
        sptd_buf.Sptd.Cdb[9] = (unsigned char)(((0x80000000 + i) & 0x00FF0000) >> 16);
        sptd_buf.Sptd.Cdb[10] = (unsigned char)(((0x80000000 + i) & 0x0000FF00) >> 8);
        sptd_buf.Sptd.Cdb[11] = (unsigned char)((0x80000000 + i) & 0x000000FF);

        if (!send_scsi_command(fd, &sptd_buf, NULL, 0, SCSI_IOCTL_DATA_UNSPECIFIED)) {
            printf("[-] Code upload failed on byte %u\n", i);
            return 0;
        }
    }

    // 3. Set (59E) bit 3 via Mode Select(10)
    memset(&sptd_buf, 0, sizeof(sptd_sense));
    sptd_buf.Sptd.Cdb[0] = 0x55;
    sptd_buf.Sptd.Cdb[1] = 0x10;
    sptd_buf.Sptd.Cdb[8] = 0x08;
    memset(param_list, 0, sizeof(param_list));
    param_list[1] = 6;

    if (!send_scsi_command(fd, &sptd_buf, param_list, 8, SCSI_IOCTL_DATA_OUT)) {
        printf("[-] Mode Select failed to set 59E bit 3\n");
        return 0;
    }

    // 4. Jump to RAM routine via Mode Select(10) 'HL' Backdoor
    memset(&sptd_buf, 0, sizeof(sptd_sense));
    sptd_buf.Sptd.Cdb[0] = 0x55;
    sptd_buf.Sptd.Cdb[1] = 0x10;
    sptd_buf.Sptd.Cdb[3] = SIG_H; // 'H'
    sptd_buf.Sptd.Cdb[4] = SIG_L; // 'L'
    sptd_buf.Sptd.Cdb[8] = 0x08;
    memset(param_list, 0, sizeof(param_list));
    param_list[1] = 6;

    // We execute, ignore the Windows return error as noted in PP_WIN.C hack
    send_scsi_command(fd, &sptd_buf, param_list, 8, SCSI_IOCTL_DATA_OUT);
    
    return 1;
}

// ---------------------------------------------------------
// BATCH FILE PATCHER
// ---------------------------------------------------------
int apply_patch_file(HANDLE fd, uint32_t base_address, const char* filename) {
    FILE* fp = fopen(filename, "rb");
    if (!fp) {
        printf("[-] Could not open patch file: %s\n", filename);
        return 0;
    }

    fseek(fp, 0, SEEK_END);
    long size = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    if (size == 0) {
        printf("[-] Patch file is empty.\n");
        fclose(fp);
        return 0;
    }

    unsigned char* buffer = (unsigned char*)malloc(size);
    if (!buffer) {
        printf("[-] Memory allocation failed.\n");
        fclose(fp);
        return 0;
    }

    fread(buffer, 1, size, fp);
    fclose(fp);

    printf("[*] Loading '%s' (%ld bytes)...\n", filename, size);
    printf("[*] Flashing RAM starting at 0x%08X...\n", base_address);
    
    for (long i = 0; i < size; i++) {
        if (!poke_byte(fd, base_address + i, buffer[i])) {
            printf("[-] Patch aborted at offset 0x%lX (Address 0x%08lX)\n", i, base_address + i);
            free(buffer);
            return 0;
        }
        
        if (i > 0 && i % 64 == 0) {
            printf("    ... %ld bytes written\n", i);
        }
    }

    printf("[+] %ld byte patch applied successfully!\n", size);
    free(buffer);
    return 1;
}

// ---------------------------------------------------------
// ENTRY POINT
// ---------------------------------------------------------
int main(int argc, char* argv[]) {
    if (argc < 4) {
        printf("Hitachi-LG DVD RAM Debugger (PP_WIN Custom)\n");
        printf("Usage:\n");
        printf("  pp_win <drive> <hex_address> peek\n");
        printf("  pp_win <drive> <hex_address> poke <hex_value>\n");
        printf("  pp_win <drive> <hex_address> patch <file.bin>\n\n");
        printf("Examples:\n");
        printf("  pp_win D: 400012E8 peek\n");
        printf("  pp_win D: 400012E8 poke D7\n");
        printf("  pp_win D: 4007F000 patch payload.bin\n");
        return 1;
    }

    char drivePath[32];
    if (strlen(argv[1]) == 2 && argv[1][1] == ':') {
        sprintf(drivePath, "\\\\.\\%s", argv[1]);
    } else {
        strncpy(drivePath, argv[1], sizeof(drivePath) - 1);
    }

    HANDLE fd = CreateFile(drivePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if (fd == INVALID_HANDLE_VALUE) {
        printf("[-] Could not open handle to %s (Error %lu)\n", drivePath, GetLastError());
        printf("[-] Make sure you are running as Administrator.\n");
        return 1;
    }

    uint32_t addr = strtoul(argv[2], NULL, 16);

    if (strcmp(argv[3], "peek") == 0) {
        unsigned char val = 0;
        if (peek_byte(fd, addr, &val)) {
            printf("[+] 0x%02X\n", val);
        }
    } 
    else if (strcmp(argv[3], "poke") == 0 && argc >= 5) {
        unsigned char val = (unsigned char)strtoul(argv[4], NULL, 16);
        if (poke_byte(fd, addr, val)) {
            printf("[+] Poked 0x%02X to 0x%08X\n", val, addr);
        }
    } 
    else if (strcmp(argv[3], "patch") == 0 && argc >= 5) {
        apply_patch_file(fd, addr, argv[4]);
    } 
    else {
        printf("[-] Invalid mode or missing arguments.\n");
    }

    CloseHandle(fd);
    return 0;
}