Hacking the Xbox 360 DVD drive

Too lazy/busy to read through all this? I've written a summary page conaining all of the how with a lot less of the why (because I'm just that nice).

summary version

Updates:
20th February 2006: WindowsXP and native SATA tests
21st February 2006: Possible firmware code support for bidirectional tray_status
2nd March 2006: A purely software method for inducing modeB (which, among other things, gets the drive working in Windows, Linux and others)
8th March 2006: Mode Select(10) analysis, writing to anywhere in the MN103 address space in software and executing custom code on the drive in software
9th March 2006: Confirmation of bidirectional tray_status line. At last.
15th March 2006: Windows ports of my tools and a less annoying memdump utility for linux and windows (it now accepts hexadecimal input instead of decimal)
4th April 2006: Official LG flasher .exe analysis and a way to bulk upload code to the drive.
6th April 2006: The drive's flash erase/program routines and my broken drive.
9th April 2006: My drive back from the dead and a working Hitachi-LG sector flasher.
17th May 2006: Flashing sectors and executing code via the 0047 Hitachi-LG firmware.
13th June 2006 (20:45 GMT ;-): My reproduction of the Hitachi FW hack. (NEW)


I set out to investigate the communication between the DVD drive and CPU in my xbox 360. The first step was to reverse engineer the DVD power connector pinout and build an adapter that would allow me to power the 360 DVD drive from a PC while I probed it. This should have been an easy task, but it ended up leading me on something of an odyessy. The following notes document my actions and findings to date. Forgive the roughness, I wrote these notes as I went along and haven't had time to tidy them up a great deal, although I have been through and added links and tried to give credit where it is due. As well as adding some important warnings.

WARNING: If you are going to connect your 360 and PC together in *any* way, as I have done several times in the experients below, then you *must* provide the 360 with a path to true earth ground. This is because the 360 has a floating ground and horrible things happen if all connected systems do not agree on the reference voltage. I used a couple of croc clips from the chassis of the 360 to the chassis of my PC to achieve this.

My drive is the Hitachi-LG GDR-3120L (ROM version: 0046DH).

DVD power connector Pin numberings (from motherboard). These were obtained by observing the voltages on the DVD power connector pins in a running xbox while messing with the eject button.

01
23
45
67
89
X

X = unused (+3.3V)
0 = GND
1 = +12V supply
2 = GND
3 = +12V supply
4 = GND
5 = +5V supply
6 = GND
7 = ? (perhaps a +3.3V supply or perhaps unused?)
8a = output : eject (falling edge (0V) closes, rising edge (+3.3V) opens)
8b = output : eject (normally 0V, rising edge of a +3.3V pulse opens/closes tray)
9a = input : tray_status (0V = opening/closing, +3.3V open/closed)
9b = input : tray_status (+3.3V = open, 0V = closed)

Strange behaviour: 8a and 9a were observed with the scope during normal operation of the xbox. I designed a PC power supply to 360 DVD power connector adapter. Circuit diagram below. Using this adapter I observered behaviours 8b and 9b. Why the difference?


(The transistor U2, LED D1 and resistors R1 and R2 were to allow me to observe the tray_status output from the drive)

There is more strangeness. I wrote a simple program to send a couple of common ATAPI commands (technically the first command is an ATA command not ATAPI) to the 360 drive while it was connected to my PC. Here's the code. I am connecting my 360 drive to my PC using a PATA to SATA bridge board as I'm too poor to have SATA ports on my computer.

/*
 * written to test the responsiveness of the xbox360
 * DVD drive to some common ATA/ATAPI commands from
 * issued from a PC.
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 10th Febuary 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <linux/hdreg.h>


void dump_hex(unsigned char *data, unsigned int len, unsigned int offset)
{
	unsigned int i, j, mod;
	char ascii[16];

	for(i = 0; i < len; i++) {
		mod = i % 16;
		if(!(mod)) {
			if(i) {
				for(j = 0; j < 16; j++) {
					if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
						printf("%c", ascii[j]);
					else
						printf(".");
				}
				printf("\n");
			}
			printf("%08X: ", offset + i);
		}
		else if(i && !(i % 8))
			printf("- ");
		
		ascii[mod] = data[i];
		printf("%02X ", data[i]);
	}

	if(!len)
		printf("\n");
	else {
		len = 16 - (len % 16);
		if(len != 16) {
			if(len >= 8)
				printf("  ");
			for(mod++, i = 0; i < len; i++) {
				ascii[mod + i] = ' ';
				printf("   ");
			}
		}
		for(j = 0; j < 16; j++) {
			if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
				printf("%c", ascii[j]);
			else
				printf(".");
		}
		printf("\n");
	}
}

int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	struct hd_drive_cmd_hdr *ch;
	unsigned char data[2048];

	if(argc != 2) {
		printf("usage: dvdprobe device\n");
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY)) == -1) {
		perror("open");
		return 1;
	}

	printf("\nWIN_PIDENTIFY\n=============\n"); // ATA IDENTIFY PACKET DEVICE
	ch = (struct hd_drive_cmd_hdr *)data;
	memset(ch, 0, sizeof(*ch));
	ch->command = WIN_PIDENTIFY;
	ch->sector_count = 1;
	if(ioctl(fd, HDIO_DRIVE_CMD, ch) == -1)
		perror("ioctl");
	else
		dump_hex(&data[sizeof(*ch)], 512, 0);

	
	printf("\n\nGPCMD_INQUIRY\n=============\n"); // ATAPI INQUIRY
	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = GPCMD_INQUIRY;
	cgc.cmd[4] = 36;
	cgc.buffer = data;
	cgc.buflen = 36;
	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_READ;
	cgc.timeout = 15000;
	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else
		dump_hex(data, 36, 0);

	printf("\n\nGPCMD_READ_CDVD_CAPACITY\n=============\n"); // ATAPI READ CAPACITY
	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = GPCMD_READ_CDVD_CAPACITY;
	cgc.buffer = data;
	cgc.buflen = 8;
	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_READ;
	cgc.timeout = 15000;
	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else
		dump_hex(data, 8, 0);

	
	printf("\n\nGPCMD_READ_12\n=============\n"); // ATAPI READ(12)
	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = GPCMD_READ_12;
	cgc.cmd[9] = 1;
	cgc.buffer = data;
	cgc.buflen = 2048;
	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_READ;
	cgc.timeout = 15000;
	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else
		dump_hex(data, 2048, 0);

	printf("\n");

	close(fd);

	return 0;
}

download

When the DVD is powered from xbox:
* READ(12) fails with a vendor specific CHECK CONDITION - 05/81/00
* Inquiry fails with weird CHECK CONDITION 05/55/00 - System Resource Failure.

Here's the program output (powered from xbox)

WIN_PIDENTIFY
=============
00000000: C0 85 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000010: 00 00 00 00 20 20 20 20 - 20 20 20 20 20 20 20 20 ....            
00000020: 20 20 20 20 20 20 20 20 - 00 00 00 00 00 00 30 30         ......00
00000030: 36 34 20 20 20 20 4C 48 - 44 2D 2D 54 54 53 56 44 64    LHD--TTSVD
00000040: 2D 44 4F 52 20 4D 44 47 - 33 52 32 31 4C 30 20 20 -DOR MDG3R21L0  
00000050: 20 20 20 20 20 20 20 20 - 20 20 20 20 20 20 00 00               ..
00000060: 00 00 00 0F 00 00 00 02 - 00 02 06 00 00 00 00 00 ................
00000070: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 07 00 ................
00000080: 03 00 78 00 78 00 78 00 - 78 00 00 00 00 00 00 00 ..x.x.x.x.......
00000090: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000A0: 7C 00 19 00 18 42 00 40 - 00 40 18 42 00 00 00 40 |....B.@.@.B...@
000000B0: 3F 20 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ? ..............
000000C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000F0: 00 00 00 00 00 00 00 00 - 00 00 FE FF FE FF 00 00 ................
00000100: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000110: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000120: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000130: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000140: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000150: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000160: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000170: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000180: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000190: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................


GPCMD_INQUIRY
=============
ioctl: Input/output error
sense: 05/55/00


GPCMD_READ_CDVD_CAPACITY
=============
00000000: 00 00 0D BE 00 00 08 00                           ........        


GPCMD_READ_12
=============
ioctl: Input/output error
sense: 05/81/00

When the DVD is powered from my adapter
* both commands succeed.
* DVD drive spins loudly (with a game disc in) without spinning down.

Here's the program output (powered from PC)

WIN_PIDENTIFY
=============
00000000: C0 85 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000010: 00 00 00 00 20 20 20 20 - 20 20 20 20 20 20 20 20 ....            
00000020: 20 20 20 20 20 20 20 20 - 00 00 00 00 00 00 30 30         ......00
00000030: 36 34 20 20 20 20 4C 48 - 44 2D 2D 54 54 53 56 44 64    LHD--TTSVD
00000040: 2D 44 4F 52 20 4D 44 47 - 33 52 32 31 4C 30 20 20 -DOR MDG3R21L0  
00000050: 20 20 20 20 20 20 20 20 - 20 20 20 20 20 20 00 00               ..
00000060: 00 00 00 0F 00 00 00 02 - 00 02 06 00 00 00 00 00 ................
00000070: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 07 04 ................
00000080: 03 00 78 00 78 00 78 00 - 78 00 00 00 00 00 00 00 ..x.x.x.x.......
00000090: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000A0: 7C 00 19 00 18 42 00 40 - 00 40 18 42 00 00 00 40 |....B.@.@.B...@
000000B0: 3F 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ?...............
000000C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000F0: 00 00 00 00 00 00 00 00 - 00 00 FE FF FE FF 00 00 ................
00000100: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000110: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000120: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000130: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000140: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000150: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000160: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000170: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000180: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000190: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................


GPCMD_INQUIRY
=============
00000000: 05 80 00 32 5B 00 00 00 - 48 4C 2D 44 54 2D 53 54 ...2[...HL-DT-ST
00000010: 44 56 44 2D 52 4F 4D 20 - 47 44 52 33 31 32 30 4C DVD-ROM GDR3120L
00000020: 30 30 34 36                                       0046            


GPCMD_READ_CDVD_CAPACITY
=============
00000000: 00 00 0D BE 00 00 08 00                           ........        


GPCMD_READ_12
=============
00000000: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000010: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000030: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000040: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000050: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000060: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000070: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000080: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000090: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000000F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000100: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000110: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000120: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000130: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000140: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000150: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000160: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000170: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000180: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000190: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000001F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000200: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000210: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000220: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000230: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000240: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000250: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000260: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000270: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000280: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000290: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000002A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000002B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000002C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000002D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000002E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000002F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000300: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000310: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000320: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000330: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000340: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000350: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000360: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000370: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000380: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000390: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000003A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000003B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000003C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000003D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000003E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000003F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000400: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000410: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000420: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000430: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000440: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000450: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000460: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000470: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000480: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000490: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000004A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000004B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000004C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000004D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000004E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000004F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000500: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000510: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000520: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000530: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000540: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000550: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000560: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000570: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000580: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000590: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000005A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000005B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000005C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000005D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000005E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000005F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000600: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000610: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000620: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000630: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000640: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000650: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000660: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000670: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000680: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000690: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000006A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000006B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000006C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000006D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000006E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000006F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000700: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000710: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000720: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000730: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000740: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000750: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000760: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000770: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000780: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000790: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000007A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000007B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000007C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000007D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000007E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
000007F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................

I was able to mount and browse the small DVD structure that exists on 360 game discs in Linux (and windows) when using my adapter. Here are the directory listings.

# mount /dev/hdc /mnt/hdc
mount: block device /dev/hdc is write-protected, mounting read-only
# ls /mnt/hdc -alF
total 10
dr-xr-xr-x      4 4294967295 4294967295  136 2005-10-07 20:18 ./
drwxr-xr-x      8 root       root       4096 2006-02-09 19:39 ../
dr-xr-xr-x  65536 4294967295 4294967295   40 2005-10-07 20:17 AUDIO_TS/
dr-xr-xr-x  65536 4294967295 4294967295  352 2005-10-07 20:17 VIDEO_TS/
# ls /mnt/hdc/AUDIO_TS -alF
total 4
dr-xr-xr-x  65536 4294967295 4294967295  40 2005-10-07 20:17 ./
dr-xr-xr-x      4 4294967295 4294967295 136 2005-10-07 20:18 ../
# ls /mnt/hdc/VIDEO_TS -alF
total 6476
dr-xr-xr-x  65536 4294967295 4294967295     352 2005-10-07 20:17 ./
dr-xr-xr-x      4 4294967295 4294967295     136 2005-10-07 20:18 ../
-r--r--r--      1 4294967295 4294967295   12288 2005-10-07 20:08 VIDEO_TS.BUP
-r--r--r--      1 4294967295 4294967295   12288 2005-10-07 20:08 VIDEO_TS.IFO
-r--r--r--      1 4294967295 4294967295 3289088 2005-10-07 20:08 VIDEO_TS.VOB
-r--r--r--      1 4294967295 4294967295   12288 2005-10-07 20:08 VTS_01_0.BUP
-r--r--r--      1 4294967295 4294967295   12288 2005-10-07 20:08 VTS_01_0.IFO
-r--r--r--      1 4294967295 4294967295 3289088 2005-10-07 20:08 VTS_01_1.VOB
# 

Let's call behaviours 8a and 9a with INQUIRY and READ(12) failing and the drive being quiet modeA and behaviours 8b and 9b with INQUIRY and READ(12) working and the drive spinning loudly modeB.

If I remove the connection between the DVD drive and the tray_status input (the input to the base of the transistor) on my adapter board, then the drive enters modeA.

If I remove the connection between the DVD drive and the tray_status input on my adapter board while the drive powers up (from the PC of course) and then connect it after power up, then the still drive behaves as modeA.

Discovery: shorting tray_status to GND during DVD drive power up causes modeB (tested in the 360 and the PC). The 360 works okay in modeB, the eject button doesn't work correctly but it loads a game okay. Leaving tray_status unconnected causes modeA.

There's something going on with tray_status at boot. I've written a very simple parallel port based logic analyser (PPLA) to observe the tray_status line during drive power up. Here's the code.

/*
 * written to observe the tray_status signal of an
 * xbox 360 DVD drive via a PC parallel port (PP).
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 10th Febuary 2006
 * platform: linux
 *
 */


#include <stdio.h>
#include <unistd.h>
#include <sys/io.h>

#define PPBASE 0x378
#define PPSTAT_REG PPBASE + 1
#define PPSTAT_SEL 0x10 // SELECT input (PP pin 13) is connected to tray_status

int main(int argc, char *argv[])
{
	unsigned int i, lasti = 0;
	unsigned char val, lastval = 0xFF;

	if(ioperm(PPSTAT_REG, 1, 1) == -1)
		perror("ioperm");
	else {
		for(i = 0; ; i++) {
			if((val = inb(PPSTAT_REG) & PPSTAT_SEL) != lastval) {
				printf("val: %u cycle: %u diff: %u\n", val >> 4, i, i - lasti);
				lastval = val;
				lasti = i;
			}
			usleep(1); // note: precise timing is not possible in userspace, this delay is for rejecting short glitches and relative approximation only
		}
	}
}

download

Here are the results (powered from 360).

val: 1 cycle: 0 diff: 0
(note: 360 turned on here)
val: 0 cycle: 55 diff: 55
val: 1 cycle: 58 diff: 3
val: 0 cycle: 62 diff: 4
val: 1 cycle: 71 diff: 9
val: 0 cycle: 80 diff: 9
val: 1 cycle: 84 diff: 4

This is confirmed by the scope (approximate pulse timigs aquired from scope)

~100ms ~100ms ~200ms   ~200ms   ~100ms
       _____           _________       _________________________
______|     |_________|         |_____|
      |     |    |    |    |    |     |    |    |    |    |
   0     1    0    0    1     1    0    1    1    1    1

0 = 0V
1 = 3.3V

This signal is observed with and without a game disc in the drive and even when the tray_status wire is cut and the DVD end attached to the scope/LA and the 360 end tied to +3.3V (so that it doesn't get confused). The signal *is* coming from the drive.

Possibly the tray_status line is bidirectional and the high pulses are the drive releasing the line and sampling the input. If the line is driven low externally during these periods then modeB (a debug mode?) is entered, else modeA is entered. This is all just a hypothesis. The pulses could be nothing, just a side effect of drive initialisation.

Interesting LA results when powered from the PC with tray_status disconnected from my adater board. The sccope/LA reading is,

val: 1 cycle: 0 diff: 0
(note: 360 turned on here)
val: 0 cycle: 63 diff: 63
val: 1 cycle: 66 diff: 3
val: 0 cycle: 70 diff: 4
val: 1 cycle: 79 diff: 9
    ~100ms ~200ms
     ____           ____________________________________________
____|    |_________|

No second low pulse. Perhaps the difference is down to what the xbox does with eject during boot (pulsing eject could cause another tray opening/closing low status pulse)?

Confirmed. The 360 pulses the eject line for ~100ms during power up. I rewrote the PPLA to capture both signals (code below).

/*
 * written to observe the tray_status and eject
 * signals of an xbox 360 DVD drive via a PC
 * parallel port (PP).
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 10th Febuary 2006
 * platform: linux
 *
 */


#include <stdio.h>
#include <unistd.h>
#include <sys/io.h>

#define PPBASE 0x378
#define PPSTAT_REG PPBASE + 1
#define PPSTAT_SEL 0x10 // PP select input (PP pin 13) is connected to tray_status
#define PPSTAT_PAPER 0x20 // PP out of paper input (PP pin 12) is connected to eject

int main(int argc, char *argv[])
{
	unsigned int i, lasti = 0, lasti2 = 0;
	unsigned char val, lastval = 0xFF, lastval2 = 0xFF;

	if(ioperm(PPSTAT_REG, 1, 1) == -1)
		perror("ioperm");
	else {
		for(i = 0; ; i++) {
			if((val = inb(PPSTAT_REG) & PPSTAT_SEL) != lastval) {
				printf("tray_status> val: %u cycle: %u diff: %u\n", val >> 4, i, i - lasti);
				lastval = val;
				lasti = i;
			}
			
			if((val = inb(PPSTAT_REG) & PPSTAT_PAPER) != lastval2) {
				printf("eject>       val: %u cycle: %u diff: %u\n", val >> 5, i, i - lasti2);
				lastval2 = val;
				lasti2 = i;
			}
			usleep(1); // note: precise timing is not possible in userspace, this delay is for rejecting glitches and relative approximation only
		}
	}
}

download

Here are the results.

tray_status> val: 1 cycle: 0 diff: 0
eject>       val: 0 cycle: 0 diff: 0
(note: xbox turned on here)
tray_status> val: 0 cycle: 49 diff: 49
tray_status> val: 1 cycle: 52 diff: 3
tray_status> val: 0 cycle: 55 diff: 3
tray_status> val: 1 cycle: 65 diff: 10
tray_status> val: 0 cycle: 74 diff: 9
eject>       val: 1 cycle: 74 diff: 74
tray_status> val: 1 cycle: 78 diff: 4
eject>       val: 0 cycle: 78 diff: 4

This confirms that an eject pulse from the 360 during boot causes the second low pulse on the tray_status line (note the same cycles for rising/falling edge of eject pulse and falling/rising edge of tray_status pulse).

That explains the second low pulse but what about the first?

I disassembled the drive. IC501 (the 8 pin SOIC affair) appears to be a +3.3V output voltage regulator, which means that pin 7 on the DVD power connector is most likely unused and almost certainly not a +3.3V supply.

Voltmeter measurements on pins of IC501

1 = +5V    8 = +5V
2 = +3.3V 7 = 0V
3 = +5V 6 = +3.3V
4 = +5V 5 = +3.3V

tray_status is connected directly to pin ? (I can't be bothered to count) on the MN103 (IC201) which, as previously noted in various other fora, is a 32-bit panasonic microcontroller. This pin is pulled high (+3.3V) by a 10K resistor (R214) to pin 5 (which I am now guessing is the output pin) of IC501 (the voltage regulator). This explains why my adapter board caused modeB. The transistor base resistor (R1) on my adapter and the 10K resistor in the DVD drive (R214) form a potential divider which holds the tray_status line below the threshold for logic level 1.

This is certainly consistent with my hypothesis that the first high pulse is a tray_status release. R214 would pull tray_status high during any release of the mn103 pin which could be overidden by externally driving the tray_status line low.

I removed the microscopic resistor R214 (which I promptly lost :-\), cut the eject line and tied the DVD end to GND (never ejected), cut the tray_status line and tied the 360 end to +3.3V (always closed). I then tied tray_status low with a replacement 10K resistor. By comparing the two tray_status waveforms (tray_status pulled high and low) I can see exactly how the MN103 is driving/releasing the tray_status line. Any periods that differ beween the two waveforms are periods when tray_status is released. Any periods that are the same mean the tray_status line is being driven high/low.

Here are the two waveforms.

Tray status tied low:

	                   ~1S
                    ______________________________
___________________|                              |____________________

tray_status tied high:

    ~100ms ~200ms
     ____           ___________________________________________________
____|    |_________|
  0 |  1 |  0 |  0 | 1  |  1 |  1  |  1 |

As suspected, it looks as though the tray_status line is released during that first high pulse. The waveforms differ after the ~1S pulse too, but that is most likely due to the different behaviours of modeA and modeB.

The sequence: (all times are rough, will tighten up later)

1. DVD drive powered on
2. drive drives tray_status low for 100ms
3. drive releases tray_status (which goes high because of R214) for 100ms
4. drive drives tray_status low for 200ms
5. drive drives tray_status high for 1s
6. drive drives tray_status as required by behaviour 9a/9b

The drive could be sampling the tray_status line between steps 3 and 4. If it's high (default because of R214) then it enters modeA, if it's low (driven externally) then it enters modeB.

WARNING: For my tests I have been shorting the tray_status line to the chassis of my PC briefly to initiate modeB (after croc clipping the floating chassis of the 360 to my PC chassis of course). This works but isn't a very clever idea and could damage the 360 DVD drive. It's fine for the short period while the tray_status pin is released but as soon as the MN103 starts to drive the pin again then you have the MN103 output connected directly to a supply rail, which is *never* good. I have now connected one end of a 10K resistor to the tray_status pin (after removing R214) and the other end of the resistor to a switch between GND and +3.3V. This allows me to safetly select modeA (switch to +3.3V) or modeB (switch to GND).

The firmware should confirm or deny my theory. I've been avoiding the disassembler so far, but it looks like I've gone as far as I can with the hardware.

As already discovered by the hackers working on the DVD firmware at the xboxhacker.net BBS, the firmware is scrambled. If I remember correctly, the scrambling algorithm xors the data with a 32-bit value and swaps the data bits around. Phantasm and Loser did some great work deciphering the bit swapping and Loser wrote a small utility to obfuscate/deobfuscate a firmware image.

Groepaz has written a great IDA module for the MN103 processor. Takes some work to get going with the IDA 4.9 kernel (it was written for 4.7), but nothing that's too much of a problem. Mainly a few functions that previously returned buffers now require the buffer (and it's length) as an argument.

The hackers (too many to name) involved in the xboxhacker.net "hacking DVD firmware" thread have discovered a lot of the mn103's address space mapping. The following quote from MacDennis is a nice summary.

------------------start quote-----------------------
Facts:

    * The DVD-ROM in the X360 can contain a Hitachi-LG GDR-3120L DVD-ROM
    * This model contains the MN103S94 CPU from Panasonic / Matsushita Electric Industrial
    * The purpose of this CPU is being a DVD controller
    * This CPU contains the AM32 core, a third-generation microcontroller core
    * Instructions / code can be run from ROM, RAM, Flash memory and cache memory
    * Data can be stored in RAM and cache memory
    * The CPU is connected to external Flash memory, type: 39SF020A (SST)
    * The CPU is NOT connected to any *external* RAM/DRAM memory (buffer/cache). There simply isn't any on the mainboard. The Philips XBOX1 drive has external DRAM and also the other X360 drive has it, but not this drive which is odd.
    * "One assignment that is common throughout, however, is the location of the reset vector. It is always at 0x40000000."
    * The instruction manual has a picture for the AM32 which shows external memory starting at 0x40000000, so it's not always 0x80000000!! See page 13 of the manual, processor mode (for program in external memory / with cache)
    * In different mn103 firmwares from different dvd devices, the first 0x1C bytes are almost the same!
    * The READ_BUFFER / WRITE_BUFFER commands are used in ATA / ATAPI devices to update the firmware by uploading it to the device by ATA commands, note that Takires mentions that these operations affect the 0x90000000 region. Did you find that out by disassembling the firmware?
    * Noticed this on a forum: "However, FYI a custom version of the MN103S with a ROM mask (and not a flash) is commonly used in DVD drives (like the Pioneer 1x6 for instance) as a secondary controller for low level tasks."

With the above facts in mind, this is my theory, yes speculation but still ..

    * The missing buffer/cache SRAM (which is odd for a high-speed DVD controller) is *in* the CPU package itself, next to the CPU die
    * Reset vector starts at 0x40000000, internal boot rom. Might also contain functions common for a DVD controller. Jumps to 0x90000020. Why 0x20? Then you have room for a (cleartext) header in the firmware.
    * The buffer/cache SRAM starts at 0x80000000
    * The external firmware flash starts at 0x90000000
    * I don't believe this is a custom MS / X360 ASIC, this because of the costs involved.

Opinions anyone?

------------------end quote-----------------------

So, as all the hard work has been done for me, I'll get started on the disassembly.

I've been staring at the disassembly for ages and I can't find anything that looks like it is controlling the tray_status pin. It's a bit of a needle in a haystack. It's also worth noting that any code that releases/reads the tray_status line is quite possibly in the internal memory at 0x40000000 and not in the flash. I'll need to look at that code too.

DaveX, a hacker also working on the firmware has discoved several vendor specific commands for the Hitachi-LG drive (see his post in the xboxhacker.net "firmware hacking" thread). One of these commands (ATAPI command code E7 with a subcommand code of 01) allows you to read data from the drive's address space.

Another hacker (djhuevo) reported success in using DaveX's command to read the internal memory at 0x40000000 (see his post in the xboxhacker.net "firmware hacking" thread). Hopefully I can repeat that success.

I can! Here's the code I used to dump the internal memory. It will dump any range you care to specify, which means it can dump other areas of the mn103 address space too.

/*
 * dumps the a given portion of the address space
 * of the MN103 microcontroller within the 
 * Hitachi-LG Xbox 360 DVD drive. Warning: it can
 * take a while to dump a lot of data.
 *
 * example: dump the internal memory mapped
 *          from 0x40000000 to 0x40020000
 *          with the 360 drive set up as /dev/hdc
 *          to the file image.bin using a block size
 *          of 0x8000.
 *
 * # ./memdump /dev/hdc 32768 4 32768 ./image.bin
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 12th Febuary 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	int fd;
	unsigned int i, cycles, block_size, block_off, block_len;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	unsigned char *data;
	FILE *fptr;

	if(argc != 6) {
		printf("usage: memdump device offset_in_blocks length_in_blocks block_size output_file\n");
		return 1;
	}

	block_off = atoi(argv[2]);
	block_len = atoi(argv[3]);
	block_size = atoi(argv[4]);

	if(!block_size || block_size > 65535) {
		printf("invalid block_size (valid: 1 - 65535)\n");
		return 1;
	}

	if(!(data = malloc(block_size))) {
		printf("malloc() failed\n");
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		free(data);
		perror("open");
		return 1;
	}

	if(!(fptr = fopen(argv[5], "wb"))) {
		free(data);
		close(fd);
		perror("fopen");
		return 1;
	}
	
	for(i = block_off; i < block_off + block_len; i++) {
		memset(data, '0', sizeof(data));
		memset(cgc.cmd, 0, sizeof(cgc.cmd));
		cgc.cmd[0] = 0xE7; // vendor specific command (discovered by DaveX)
		cgc.cmd[1] = 0x48; // H
		cgc.cmd[2] = 0x49; // I
		cgc.cmd[3] = 0x54; // T
		cgc.cmd[4] = 0x01; // read MCU memory sub-command
		cgc.cmd[6] = (unsigned char)(((i * block_size) & 0xFF000000) >> 24); // address MSB
		cgc.cmd[7] = (unsigned char)(((i * block_size) & 0x00FF0000) >> 16); // address
		cgc.cmd[8] = (unsigned char)(((i * block_size) & 0x0000FF00) >> 8); // address
		cgc.cmd[9] = (unsigned char)((i * block_size) & 0x000000FF); // address LSB
		cgc.cmd[10] = (unsigned char)((block_size & 0xFF00) >> 8); // length MSB
		cgc.cmd[11] = (unsigned char)(block_size & 0x00FF); // length LSB

		cgc.buffer = data;
		cgc.buflen = block_size;
		cgc.sense = &sense;
		cgc.data_direction = CGC_DATA_READ;
		cgc.timeout = 15000;

		if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
			perror("ioctl");
			printf("sense: %02X/%02X/%02X (offset 0x%08X)\n", sense.sense_key, sense.asc, sense.ascq, i * block_size);
		}
		else {
			fwrite(data, block_size, 1, fptr);
			if(ferror(fptr))
				printf("fwrite() failed (offset 0x%08X)\n", i * block_size);
		}
	}
	
	printf("\n");

	free(data);
	fclose(fptr);
	close(fd);

	return 0;
}

download

At first I dumped the entire address space from 0x40000000 to 0x80000000 (it took an age to complete). The data cycled around every 0x20000 bytes and so I conclude the same thing as djhuevo, that the internal memory at 0x40000000 is 128KB in size. I used the following command to dump only the first 128KB of memory at 0x40000000.

# ./memdump /dev/hdc 32768 4 32768 ./intmem.bin

According to the mn103 manual the reset vector is at 0x40000000 and the interrupt vector is at 0x40000008. So I expected to see valid code at the beginning of the image and a prompt jump (within the first 8 bytes) to hop over the interrupt service routine. The puzzling thing is that the first 0x10000 bytes are all 0xFF, which is obviously not valid mn103 code :-\ When DaveX presented the vendor specific memory dump command he alluded to range checks. Perhaps the command was blocking access to the boot code from 0x0 - 0x10000 and simply returning 0xFF values. I have decided to confirm/deny this hypothesis before attempting to disassembly the internal memory image.

Another hacker working on the dvd firmware (robinsod) discovered the ATAPI command handler tables (see see his post in the xboxhacker.net "firmware hacking" thread). Using these tables we can easily see that the address of the 0xE7 command handler is 0x90025BC8 (that's offset 0x25BC8 into the flash image).

Here's the disassembly starting at that address. It certainly looks the part (check out the cmp 0xE7 in the first few lines). The comments that I made for myself while running through the code appear along the execution path for the dump memory subcommand (remember that the 0xE7 command can do more than dump memory). Note that code blocks are in execution order, not address order. I've also deleted a lot of code that isn't executed or relevant.

ROM:00025BC8                 add     0xFC, SP ! '³'
ROM:00025BCB                 movbu   (word_5B7+1), D0	; get ATAPI command code (note: packet starts at 5B8)
ROM:00025BCE                 cmp     0xE7, D0 ! 'þ'	; is it 0xE7?
ROM:00025BD2                 beq     loc_25BD6		; yes in our case, so branch to 25BD6

ROM:00025BD6 loc_25BD6:                              ! CODE XREF: ROM:00025BD2 j
ROM:00025BD6                 movbu   (word_5B9), D0	; get 2nd byte of ATAPI packet
ROM:00025BD9                 cmp     0xE, D0		; is it 0xE?
ROM:00025BDB                 bne     loc_25BF3		; not in our case, so branch to 25BF3

ROM:00025BF3 loc_25BF3:                              ! CODE XREF: ROM:00025BDB j
ROM:00025BF3                                         ! ROM:00025BE2 j ...
ROM:00025BF3                 movbu   (word_5B9+1), D0 ; get third byte of ATAPI packet
ROM:00025BF6                 cmp     0x52, D0 ! 'R'   ; is it 'R'?
ROM:00025BF8                 bne     loc_25C2C	      ; not in our case, so brach to 25C2C

ROM:00025C2C loc_25C2C:                              ! CODE XREF: ROM:00025BF8 j
ROM:00025C2C                                         ! ROM:00025BFF j ...
ROM:00025C2C                 movbu   (word_5B9+1), D0 ; get third byte of ATAPI packet
ROM:00025C2F                 cmp     0x49, D0 ! 'I'   ; is it 'I'?
ROM:00025C31                 bne     loc_25C3A	      ; yes in our case, so don't branch
ROM:00025C33                 movbu   (word_5BB), D0   ; get fourth byte of ATAPI packet
ROM:00025C36                 cmp     0x54, D0 ! 'T'   ; is it 'T'?
ROM:00025C38                 beq     loc_25C42        ; yes in our case, so brach to 25C42

ROM:00025C42 loc_25C42:                              ! CODE XREF: ROM:00025C38 j
ROM:00025C42                 movbu   (word_5BB+1), D0 ; get fifth byte of ATAPI packet (subcommand code)
ROM:00025C45                 cmp     1, D0            ; is it 1?
ROM:00025C47                 beq     loc_25CA3        ; yes in our case, so branch to 25CA3

ROM:00025CA3 loc_25CA3:                              ! CODE XREF: ROM:00025C47 j
ROM:00025CA3                 call    sub_25D27, [], 4 ; call command 0xE7 subcommand 0x01 (dump internal memory) handler
ROM:00025CA8                 bra     loc_25D0A

ROM:00025D27 sub_25D27:                              ! CODE XREF: ROM:loc_25CA3 p
ROM:00025D27                 call    sub_25420, [D2,D3], 0xC ; off we go again
ROM:00025D2C                 mov     1, D0
ROM:00025D2E                 ret     [], 4

ROM:00025420 sub_25420:                              ! CODE XREF: sub_25D27 p
ROM:00025420                 mov     0x5C2, A0         ; A0 = address of last two bytes of ATAPI packet, the transfer length input
ROM:00025423                 call    sub_33AD8, [], 8  ; 16bit big endian to little endian conversion
ROM:0002542A                 movhu   D0, (word_8C4)    ; store transfer length input at 8C4
ROM:0002542D                 mov     0x5BE, A0	       ; A0 = address of the 32-bit address input in the ATAPI packet
ROM:00025430                 call    sub_33A84, [], 8  ; 32bit big endian to little endian conversion
ROM:00025437                 mov     D0, A0            ; our target address goes into A0
ROM:00025439                 mov     D0, (word_62C)    ; and gets stored at 62C
ROM:0002543C                 mov     0xFF000000, D2    ; 
ROM:00025442                 mov     word_90000000, D3 ; D3 = 90000000
ROM:00025448                 mov     D0, D1            ; target address into D1
ROM:00025449                 and     D2, D1            ; zero all but MSB of target address
ROM:0002544B                 cmp     D3, D1            ; are we accessing range 90000000 to 90FFFFFF (flash address space)?
ROM:0002544C                 beq     loc_25493         ; yes, so branch to 25493 (this ends the handler)
ROM:0002544E                 movhu   (word_8C4), D0    ; get transfer length
ROM:00025451                 mov     A0, D1            ; get target address
ROM:00025453                 add     D0, D1            ; D1 = address of byte after last byte to be copied
ROM:00025454                 and     D1, D2            ; check that target + length doesn't go into firmware
ROM:00025456                 cmp     D3, D2            ; does it?
ROM:00025457                 beq     loc_25493         ; yes, so bail
ROM:00025459                 mov     0x8002EC00, D2    ; RAM is believed to be mapped at 0x80000000
ROM:0002545F                 cmp     A0, D2            ; are we trying to access above 0x8002EC00?
ROM:00025461                 bhi     loc_2546B         ; no, so branch to 2546B
ROM:00025463                 cmp     0x80037300, A0    ; are we trying to access range 0x8002EC00 - 0x80037300?
ROM:00025469                 bcs     loc_25493         ; yes, so bail.
ROM:0002546B
ROM:0002546B loc_2546B:                              ! CODE XREF: sub_25420+41 j
ROM:0002546B                 cmp     D1, D2            ; does target address + transfer length take us above 0x8002EC00?
ROM:0002546C                 bhi     loc_25476         ; no, so branch to 25476
ROM:0002546E                 cmp     0x80037300, D1    ; does target address + transfer length take us into 0x8002EC00 - 0x80037300?
ROM:00025474                 bcs     loc_25493         ; yes, so bail
ROM:00025476
ROM:00025476 loc_25476:                              ! CODE XREF: sub_25420+4C j
ROM:00025476                 mov     0x8003A000, D2    ; Another check for range 0x8003A000 - 0x8003A300
ROM:0002547C                 cmp     A0, D2
ROM:0002547E                 bhi     loc_25488
ROM:00025480                 cmp     0x8003A300, A0
ROM:00025486                 bcs     loc_25493
ROM:00025488
ROM:00025488 loc_25488:                              ! CODE XREF: sub_25420+5E j
ROM:00025488                 cmp     D1, D2            ; continuing check for range 0x8003A000 - 0x8003A300
ROM:00025489                 bhi     loc_2549B
ROM:0002548B                 cmp     0x8003A300, D1
ROM:00025491                 bcc     loc_2549B         ; if we pass checks, branch to 2549B
ROM:00025493
ROM:00025493 loc_25493:                              ! CODE XREF: sub_25420+2C j
ROM:00025493                                         ! sub_25420+37 j ...
ROM:00025493                 mov     4, D0
ROM:00025495                 movbu   D0, (word_6D8+1)
ROM:00025498                 clr     D0
ROM:00025499                 bra     locret_254F3    ; we failed checks so we're out

ROM:00033AD8 sub_33AD8:                              ! CODE XREF: sub_2506C+3 p
ROM:00033AD8                                         ! sub_250FE+3 p ...
ROM:00033AD8                 mov     SP, A1          ; this function converts the big endian 16 bit
ROM:00033AD9                 mov     A0, (SP)        ; value at (A0) to a little endian 32 bit value
ROM:00033ADB                 mov     (A1), A0        ; and returns it in D0
ROM:00033ADD                 movbu   (A0), D0        ; 
ROM:00033ADF                 movbu   D0, (5,SP)      ; 
ROM:00033AE2                 mov     (A1), D0        ; 
ROM:00033AE3                 inc     D0              ; 
ROM:00033AE4                 mov     D0, (A1)        ; 
ROM:00033AE5                 mov     D0, A0          ; 
ROM:00033AE7                 movbu   (A0), D0        ; 
ROM:00033AE9                 movbu   D0, (4,SP)      ; 
ROM:00033AEC                 movhu   (4,SP), D0      ; 
ROM:00033AEF                 retf    [], 8           ; return to 2542A

ROM:00033A84 sub_33A84:                              ! CODE XREF: sub_1A1D5+32 p
ROM:00033A84                                         ! sub_1A1D5+42 p ...
ROM:00033A84                 mov     SP, A1          ; this function converts the big endian 32 bit
ROM:00033A85                 mov     A0, (SP)        ; value at (A0) to a little endian 32 bit value
ROM:00033A87                 mov     (A1), A0        ; and returns it in D0
ROM:00033A89                 movbu   (A0), D0
ROM:00033A8B                 movbu   D0, (7,SP)
ROM:00033A8E                 mov     (A1), D0
ROM:00033A8F                 inc     D0
ROM:00033A90                 mov     D0, (A1)
ROM:00033A91                 mov     D0, A0
ROM:00033A93                 movbu   (A0), D0
ROM:00033A95                 movbu   D0, (6,SP)
ROM:00033A98                 mov     (A1), D0
ROM:00033A99                 inc     D0
ROM:00033A9A                 mov     D0, (A1)
ROM:00033A9B                 mov     D0, A0
ROM:00033A9D                 movbu   (A0), D0
ROM:00033A9F                 movbu   D0, (5,SP)
ROM:00033AA2                 mov     (A1), D0
ROM:00033AA3                 inc     D0
ROM:00033AA4                 mov     D0, (A1)
ROM:00033AA5                 mov     D0, A0
ROM:00033AA7                 movbu   (A0), D0
ROM:00033AA9                 movbu   D0, (4,SP)
ROM:00033AAC                 mov     (4,SP), D0
ROM:00033AAE                 retf    [], 8        ; return to 25437

ROM:0002549B loc_2549B:                              ! CODE XREF: sub_25420+69 j
ROM:0002549B                                         ! sub_25420+71 j
ROM:0002549B                 movhu   (word_8C4), D1   ; get transfer length
ROM:0002549E                 mov     0x400, D2
ROM:000254A1                 cmp     D2, D1           ; is transfer length greater than 0x400?
ROM:000254A2                 bhi     loc_254C3        ; yes, so branch to 254C3
ROM:000254A4                 mov     unk_3CC00, D0    ; D0 = 3CC00
ROM:000254AA                 call    sub_33C12, [D2,D3,A2,A3], 0x18 ; transfer data to RAM
ROM:000254B1                 mov     unk_3CC00, A0    ; A0 = 3CC00 (offset into RAM of our target data)
ROM:000254B7                 movhu   (word_8C4), D0   ; D0 = transfer length
ROM:000254BA                 call    sub_1B5FE, [], 0 ; set up control variables for ATAPI data transfer?
ROM:000254C1                 bra     loc_254F1        ; and away we go
ROM:000254C3 ! ---------------------------------------------------------------------------
ROM:000254C3
ROM:000254C3 loc_254C3:                              ! CODE XREF: sub_25420+82 j
ROM:000254C3                 mov     D2, D1           ; D1 = 0x400
ROM:000254C4                 sub     D2, D0           ; D0 = 0x400
ROM:000254C6                 movhu   D0, (word_8C4)   ; overwrite tranfer length with 0x400
ROM:000254C9                 mov     unk_3CC00, D0    ; D0 = 3CC00
ROM:000254CF                 call    sub_33C12, [D2,D3,A2,A3], 0x18 ; transfer 0x400 bytes of target data to RAM
ROM:000254D6                 mov     (word_62C), D0   ; D0 = some value in internal RAM
ROM:000254D9                 mov     unk_3CC00, A0    ; A0 = 3CC00 (offset into RAM of our target data)
ROM:000254DF                 add     D2, D0
ROM:000254E0                 mov     D0, (word_62C)
ROM:000254E3                 mov     D2, D0
ROM:000254E4                 mov     unk_900254F6, A1
ROM:000254EA                 call    sub_1B621, [], 0 ; set up control variables for ATAPI data transfer?
ROM:000254F1
ROM:000254F1 loc_254F1:                              ! CODE XREF: sub_25420+A1 j
ROM:000254F1                 mov     1, D0
ROM:000254F3
ROM:000254F3 locret_254F3:                           ! CODE XREF: sub_25420+79 j
ROM:000254F3                 ret     [D2,D3], 0xC    ; back to 25D2C (this concludes the 0xE7 handler)

ROM:00033C12 sub_33C12:                              ! CODE XREF: sub_100FF+2C p
ROM:00033C12                                         ! sub_100FF+51 p ...
ROM:00033C12                 mov     D1, D3          ; D3 = transfer length
ROM:00033C13                 mov     D0, D2          ; D2 = 3CC00
ROM:00033C14                 mov     A0, A2          ; A2 = target address
ROM:00033C15                 mov     SP, A3          ; A3 points to top entry of stack
ROM:00033C16                 inc4    A3              ; A3 points to second to top entry of stack
ROM:00033C17                 mov     A3, A0          ; now so does A0
ROM:00033C18                 call    sub_64E4, [], 0 ; save CPU state
ROM:00033C1F                 and     0xFDFF, PSW     ; clear interrupt mask 1 (IM1)
ROM:00033C23                 nop
ROM:00033C24                 nop
ROM:00033C25                 or      0x80000000, D2  ; D2 = 8003CC00
ROM:00033C2B                 mov     D2, A0          ; so does A0
ROM:00033C2D                 exthu   D3              ; D3 = transfer length
ROM:00033C2E                 clr     D0              ; zero D0
ROM:00033C2F                 cmp     D0, D3          ; is transfer zero or negative?
ROM:00033C30                 ble     loc_33C3D       ; yep, so branch to 33C3D (exit function)
ROM:00033C32                 setlb                   ; setup loop registers
ROM:00033C33                 movbu   (A2), D1        ; D1 = byte at target address
ROM:00033C35                 inc     A2              ; advance source pointer
ROM:00033C36                 movbu   D1, (A0)        ; copy to address 8003CC00 (RAM)
ROM:00033C38                 inc     A0              ; advance destination pointer 
ROM:00033C39                 inc     D0              ; advance counter
ROM:00033C3A                 exthu   D0
ROM:00033C3B                 cmp     D3, D0          ; all bytes transfered?
ROM:00033C3C                 llt                     ; yes, so branch back to 33C33
ROM:00033C3D                                         
ROM:00033C3D loc_33C3D:                              ! CODE XREF: sub_33C12+1E j
ROM:00033C3D                 mov     A3, A0          ; (A3) is where saved PSW is stored
ROM:00033C3E                 call    sub_64F0, [], 0 ; restore CPU state
ROM:00033C45                 ret     [D2,D3,A2,A3], 0x18 ; return to 254B1 or 254D6 (depending on transfer length)

ROM:000064E4 sub_64E4:                               ! CODE XREF: sub_1A1D5+3 p
ROM:000064E4                                         ! sub_1A22C+3 p ...
ROM:000064E4                 movm    [D0,D1,A0,A1,MDR,LIR,LAR], (SP) ; this function saves CPU state
ROM:000064E6                 mov     PSW, D0
ROM:000064E8                 nop
ROM:000064E9                 nop
ROM:000064EA                 movhu   D0, (A0)
ROM:000064EC                 movm    (SP), [D0,D1,A0,A1,MDR,LIR,LAR]
ROM:000064EE                 rets                                     ; return to 33C1F

ROM:000064F0 sub_64F0:                               ! CODE XREF: sub_1A1D5+4D p
ROM:000064F0                                         ! sub_1A22C+39 p ...
ROM:000064F0                 movm    [D0,D1,A0,A1,MDR,LIR,LAR], (SP) ; this function restores CPU state
ROM:000064F2                 movhu   (A0), D0
ROM:000064F4                 mov     D0, PSW
ROM:000064F6                 nop
ROM:000064F7                 nop
ROM:000064F8                 movm    (SP), [D0,D1,A0,A1,MDR,LIR,LAR]
ROM:000064FA                 rets                                    ; return to 33C45

ROM:0001B5FE sub_1B5FE:                              ! CODE XREF: sub_25420+9A p
ROM:0001B5FE                                         ! sub_25781+4F p ...
ROM:0001B5FE                 movhu   D0, (word_634)   ; store transfer length in internal RAM
ROM:0001B601                 mov     A0, D0           ; D0 = 3CC00
ROM:0001B603                 add     0x80000000, D0   ; D0 = 0x8003CC00 (address in RAM of our target data)
ROM:0001B609                 mov     D0, (word_630)   ; store address in internal RAM
ROM:0001B60C                 bclr    2, (word_5A8+1)  ; clear bit 2 of (5A9) in internal RAM
ROM:0001B611                 mov     5, D0
ROM:0001B613                 movbu   D0, (word_6D8+1) ; (6D9) in internal RAM = 5
ROM:0001B616                 clr     D0
ROM:0001B617                 movbu   D0, (word_6DA+1) ; (6DB) in internal RAM = 0
ROM:0001B61A                 bset    8, (word_5A5+1)  ; set bit 8 of (5A6) in internal RAM
ROM:0001B61F                 rets                     ; back to 254C1

ROM:0001B621 sub_1B621:                              ! CODE XREF: sub_25420+CA p
ROM:0001B621                                         ! sub_25781+66 p ...
ROM:0001B621                 movhu   D0, (word_634)   ; this does the same job as sub_1B5FE for transfers over 0x400 bytes
ROM:0001B624                 mov     A0, D0
ROM:0001B626                 add     0x80000000, D0
ROM:0001B62C                 mov     D0, (word_630)
ROM:0001B62F                 bclr    2, (word_5A8+1)
ROM:0001B634                 mov     5, D0
ROM:0001B636                 movbu   D0, (word_6D8+1)
ROM:0001B639                 clr     D0
ROM:0001B63A                 movbu   D0, (word_6DA+1)
ROM:0001B63D                 bclr    8, (word_5A5+1)
ROM:0001B642                 mov     A1, (word_6DC)   ; only difference between sub_1B5FE and this function
ROM:0001B646                 rets                     ; return to 254F1
ROM:0001B646 ! End of function sub_1B621

That's the handler pretty much completely traced. Nothing preventing/corrupting access to 0x40000000. So I'm assuming that the dump I made of the internal memory at 0x40000000 is correct. Which is annoying because there are 0xFF values at the previously assumed reset vector.

The range checks that DaveX mentioned turn out to be very interesting. The checks prevent access to the flash range 0x90000000 - 0x90FFFFFF. The firmware is only 0x40000 (256KB) big which requires 18 address lines (2^18 = 256KB). The external memory address space of the MN103 is much larger than that and so the flash memory may also be mapped into addresses 0x90040000, 0x90080000, etc. More importantly, it may well be mapped into 0x91000000, 0x91040000, etc. These latter addresses will make it through the range check.

Confirmed! You *can* dump the firmware in software using the following command.

# ./memdump /dev/hdc 74240 8 32768 ./firmware.bin

The other two checks are supposed to prevent access to the ranges 0x8002EC00 - 0x80037300 and 0x8003A000 - 0x8003A300. What's so special about those ranges? Perhaps the memory mapping trick will also work on these addresses. To try this I'll first dump way more than I'll need to try and determine the size of the memory.

# ./memdump /dev/hdc 65536 8192 32768 ./ram.bin
ioctl: Input/output error
sense: 00/00/00 (offset 0x80028000)
ioctl: Input/output error
sense: 00/00/00 (offset 0x80030000)

Those errors are the range checks kicking in. Looking at the dump in the hex editor it appears to cycle around every 0x40000 bytes (although it's harder to tell because the RAM can change between reads). So the RAM looks to be 256KB in size. So let's try dumping from address 0x81000000 to 0x81040000. If the mapping trick works this should give us the entire contents of the RAM.

# ./memdump /dev/hdc 66048 8 32768 ./ram.bin

No errors. A good sign. Looking at the image confirms that it has worked.

Deja vu? This trick is identical to the MIST trick that fooled the POKEPCI check on the original xbox.

There's another bug with the latter two checks too. The checks are as follows

if target address is in secret range, then do not allow.
if target address + requested transfer length is in secret range, then do not allow.

Those two rules do not cover all possible address and transfer lengh combinations that can return the secret ranges. If we use the maximum transfer length (0xFFFF) and specify an offset as close as possible to the start of a secret range, then our target address will be below the range and our target address + the transfer length (0xFFFF) will be above the range. The result is that somewhere in the middle of our dump we get the entire secret range. This works because the length of the latter two forbidden ranges are less than the maximum transfer length of 0xFFFF. Here are the commands I used. This won't work to dump the firmware at 0x90000000, that range is too large.

# ./memdump /dev/hdc 32771 1 65535 ./range1.bin
# ./memdump /dev/hdc 32772 1 65535 ./range2.bin

The checks are useless.

At this point I'm no closer to finding any code that selects modeA/modeB based on tray_status. Hopefully I'll have more to add later.

Update: 20th February 2006

Shortly after posting the above notes to the xboxhacker.net forums, Tiros posted details of his own experiences using the drive under windows. Apparently it is only necessary to tie the eject input low during DVD drive power up to get the drive working under windows. It will be wise to look into this before investing any more time into my own ideas.

After some more experimentation on my machine:
a) I have not been able to reproduce the success Tiros has had with grounding eject at drive power up in win98 (the only version of windows that I have, unfortunately).
b) Linux can mount/browse a game disc even in modeA. If I power the drive up from the 360 (all connections intact) connected to the PC through a PATA - SATA bridge board, then it works fine (although Read(12) and Inquiry fail as usual with the usual check conditions for modeA).
c) The drive works in win98/Linux every time in modeB (pulling tray_status low during power up)

After many false assurances that his computer could not be damaged I managed to convince my friend to allow me to run some tests on his windows XP machine with native SATA.

Results from XP:
c) In XP the drive works every time in modeB with both the bridge board and native SATA. Although to get a drive letter assigned (and hence have the drive show up under "my computer") with native SATA, I have to go through "device manager > DVD/CDROM drives > right click on HL-DT-ST DVD-ROM GDR3120L SCSI CdRom Device > Properties > Volumes tab > Populate > OK" (screen shot). After that it works nicely.
d) I still can't reproduce the grounding eject method using a PATA - SATA bridge board or the native SATA.

I'm guessing (could be wrong) that Tiros grounded eject trick will leave the drive in modeA, just as Linux can access the drive in modeA. Still lots of questions. Answers to come hopefully. Although what I have been referring to as "modeB" isn't fully understood, the fact is that (as strange as it may seem) pulling tray_status low during power up gets my drive working in Linux/Win98 and WinXP (with and without native SATA). Hmmm...

Update: 21st February 2006

Inquiry and Read(12) fail for modeA and succeed for modeB. So let's disassemble their handlers and see if we can spot some checks. Any checks can possibly be traced back to the inital modeA/B branch.

Here's the disassembly for the Inquiry handler.

ROM:00024F48                 movm    [D2,A2], (SP)
ROM:00024F4A                 add     0xF8, SP ! '°'
ROM:00024F4D                 btst    3, (word_5B9) ; check1: are bits 0 (EVPD) and 1 (CmdDt) of packet[1] clear?
ROM:00024F52                 beq     loc_24F57     ; yes, so branch
ROM:00024F54                 jmp     loc_25013     ; no, so bail out
ROM:00024F57 ! ---------------------------------------------------------------------------
ROM:00024F57

ROM:00024F57 loc_24F57:                              ! CODE XREF: ROM:00024F52j
ROM:00024F57                 movbu   (word_5B9+1), D0
ROM:00024F5A                 cmp     0, D0             ; check2: is packet[2] (page/operation code) zero?
ROM:00024F5C                 beq     loc_24F61         ; yes, so branch
ROM:00024F5E                 jmp     loc_25013         ; no, so bail out
ROM:00024F61 ! ---------------------------------------------------------------------------
ROM:00024F61

ROM:00024F61 loc_24F61:                              ! CODE XREF: ROM:00024F5Cj
ROM:00024F61                 call    sub_1B53B, [], 0 ; check3: get bit 6 of (598) (internal data RAM) into D0
ROM:00024F68                 extb    D0
ROM:00024F69                 cmp     0, D0            ; was bit 6 zero?
ROM:00024F6B                 bne     loc_24F80        ; no, so branch
ROM:00024F6D                 movbu   (word_5BD), D0   ; yes, D0 = packet[5]
ROM:00024F70                 mov     0xC0, D1 ! '+'
ROM:00024F73                 and     D1, D0           ; set bits 6 and 7 (both vendor specific) of packet[5] (EDIT 16th March 2006: this is a mistake, see below)
ROM:00024F75                 cmp     D1, D0           ; check4: are all the other bits (NACA, flag, link) zero?
ROM:00024F76                 beq     loc_24F80        ; yes, so branch
ROM:00024F78                 mov     0xD, D0          : no, so bail
ROM:00024F7A                 movbu   D0, (word_5D8)
ROM:00024F7D                 jmp     loc_25018

ROM:0001B53B sub_1B53B:                              ! CODE XREF: sub_1A99A+Cp
ROM:0001B53B                                         ! sub_1DE7Bp ...
ROM:0001B53B                 btst    0x40, (word_598) ! '@' ; is bit 6 of (598) clear?
ROM:0001B540                 beq     loc_1B546              ; yes so branch
ROM:0001B542                 mov     1, D0                  ; no, so D0 = 1
ROM:0001B544                 bra     locret_1B547           ; return
ROM:0001B546 ! ---------------------------------------------------------------------------
ROM:0001B546
ROM:0001B546 loc_1B546:                              ! CODE XREF: sub_1B53B+5j
ROM:0001B546                 clr     D0                     ; D0 = 0
ROM:0001B547
ROM:0001B547 locret_1B547:                           ! CODE XREF: sub_1B53B+9j
ROM:0001B547
ROM:0001B547 locret_1B547:                           ! CODE XREF: sub_1B53B+9j
ROM:0001B547                 rets                           ; return to 24F68

ROM:00024F80                 movbu   (word_5BB+1), D2       ; D2 = allocation length
ROM:00024F83                 mov     D2, D1
ROM:00024F84                 exthu   D1                     ; D1 = allocation length
ROM:00024F85                 cmp     0, D1                  ; is alloc length zero?
ROM:00024F87                 bne     loc_24F8C              ; nope, so branch
ROM:00024F89                 jmp     loc_25018              ; yes, bail
ROM:00024F8C ! ---------------------------------------------------------------------------
ROM:00024F8C
ROM:00024F8C loc_24F8C:                              ! CODE XREF: ROM:00024F87j
ROM:00024F8C                 mov     D2, D1                 ; D1 = alloc length
ROM:00024F8D                 exthu   D1
ROM:00024F8E                 cmp     0x60, D1 ! '`'         ; is alloc length greater 0x60?
ROM:00024F90                 ble     loc_24F94              ; no, so branch
ROM:00024F92                 mov     0x60, D2 ! '`'         ; yes, so truncate it to 0x60
ROM:00024F94
ROM:00024F94 loc_24F94:                              ! CODE XREF: ROM:00024F90j
ROM:00024F94                 mov     unk_3CC00, D0          ; D0 = 3CC00
ROM:00024F9A                 mov     0x60, D1 ! '`'         ; D1 = 0x60
ROM:00024F9C                 mov     unk_9003D47C, A0       ; A0 points to flash address 9003D47C (Inquiry response data)
ROM:00024FA2                 call    sub_33C12, [D2,D3,A2,A3], 0x18 ; Transfer 0x60 byts of response data to RAM
ROM:00024FA9                 mov     SP, A2
ROM:00024FAA                 inc4    A2                      ; A2 = top of stack
ROM:00024FAB                 mov     A2, A1		     ; A1 = top of stack
ROM:00024FAC                 mov     unk_9003D4A0, A0        ; points to flash address 9003D5A0 
ROM:00024FB2                 clr     D0                      ; D0 = 0
ROM:00024FB3                 bra     loc_24FBC               ; branch
ROM:00024FB5 ! ---------------------------------------------------------------------------
ROM:00024FB5
ROM:00024FB5 loc_24FB5:                              ! CODE XREF: ROM:00024FBFj
ROM:00024FB5                 movbu   (A0), D1   ; D1 = first byte of inquiry response (device type)
ROM:00024FB7                 inc     A0         ; advance source ptr
ROM:00024FB8                 movbu   D1, (A1)   ; stick device type on stack
ROM:00024FBA                 inc     A1         ; advance destination ptr
ROM:00024FBB                 inc     D0         ; advance counter
ROM:00024FBC
ROM:00024FBC loc_24FBC:                              ! CODE XREF: ROM:00024FB3j
ROM:00024FBC                 exthu   D0
ROM:00024FBD                 cmp     4, D0                  ; is D0 less than 4?
ROM:00024FBF                 blt     loc_24FB5              ; loop copies first for bytes of inquiry data to stack
ROM:00024FC1                 mov     0x41, D0 ! 'A'         ; D0 = 0x41
ROM:00024FC3                 movbu   D0, (7,SP)             ; put 41 on stack
ROM:00024FC6                 mov     unk_3CC24, D0          ; D0 = 3CC24 
ROM:00024FCC                 mov     4, D1                  ; D1 = 4
ROM:00024FCE                 mov     A2, A0                 ; A0 = ptr to the 4 bytes we just put on the stack
ROM:00024FCF                 call    sub_33C12, [D2,D3,A2,A3], 0x18 ; copy those 4 bytes to 3CC24 in RAM (offset 36 into the inquiry response, vendor specific)
ROM:00024FD6                 mov     0x5FFC, A0             ; A0 = 5FFC (address in internal data RAM)
ROM:00024FD9                 call    sub_33A84, [], 8       ; check5: D0 = make_litte_endian((5FFC));
ROM:00024FE0                 cmp     0xEEF8EFFA, D0         ; is the result EEF8EFFA?
ROM:00024FE6                 bne     loc_24FFD              ; no, so branch (skips revision level overwrite)
ROM:00024FE8                 mov     0x5A, D0 ! 'Z'         ; yes, D0 = 5A;
ROM:00024FEA                 movbu   D0, (4,SP)             ; put 5A on stack
ROM:00024FED                 mov     unk_3CC20, D0          ; D0 = 3CC20 (offset 32 into inquiry response, product revision level)
ROM:00024FF3                 mov     1, D1                  ; D1 = 1 (copy one byte)
ROM:00024FF5                 mov     A2, A0                 ; A0 = where we just put 0x5A
ROM:00024FF6                 call    sub_33C12, [D2,D3,A2,A3], 0x18 ; overwrite product revision level in RAM
ROM:00024FFD
ROM:00024FFD loc_24FFD:                              ! CODE XREF: ROM:00024FE6j
ROM:00024FFD                 bclr    0x80, (word_59E) ! 'Ç' ; clear bit 7 of (59E)
ROM:00025002                 mov     unk_3CC00, A0          ; A0 = start of inquiry response in RAM
ROM:00025008                 exthu   D2                     ; D2 = alloc length
ROM:00025009                 mov     D2, D0                 ; D0 = alloc length
ROM:0002500A                 call    sub_1B5FE, [], 0       ; set up control variables for ATAPI transfer?
ROM:00025011                 bra     locret_2501D           ; return (this concludes the Inquiry handler)
ROM:00025013 ! ---------------------------------------------------------------------------
ROM:00025013
ROM:00025013 loc_25013:                              ! CODE XREF: ROM:00024F54j
ROM:00025013                                         ! ROM:00024F5Ej
ROM:00025013                 mov     0xA, D0
ROM:00025015                 movbu   D0, (word_5D8)
ROM:00025018
ROM:00025018 loc_25018:                              ! CODE XREF: ROM:00024F7Dj
ROM:00025018                                         ! ROM:00024F89j
ROM:00025018                 mov     4, D0
ROM:0002501A                 movbu   D0, (word_6D8+1)
ROM:0002501D
ROM:0002501D locret_2501D:                           ! CODE XREF: ROM:00025011j
ROM:0002501D                 ret     [D2,A2], 0x10 ; The concludes the Inquiry handler
ROM:00033A84 sub_33A84:                              ! CODE XREF: sub_1A1D5+32p
ROM:00033A84                                         ! sub_1A1D5+42p ...
ROM:00033A84                 mov     SP, A1          ; A1 = top of stack
ROM:00033A85                 mov     A0, (SP)        ; put 5FFC on stack
ROM:00033A87                 mov     (A1), A0        ; A0 = 5FFC
ROM:00033A89                 movbu   (A0), D0        ; D0 = (5FFC)
ROM:00033A8B                 movbu   D0, (7,SP)      ; store (5FFC) on stack
ROM:00033A8E                 mov     (A1), D0        ; D0 = 5FFC
ROM:00033A8F                 inc     D0              ; D0 = 5FFD
ROM:00033A90                 mov     D0, (A1)        ; store 5FFD on stack
ROM:00033A91                 mov     D0, A0          ; A0 = 5FFD
ROM:00033A93                 movbu   (A0), D0        ; D0 = (5FFD)
ROM:00033A95                 movbu   D0, (6,SP)      ; store (5FFD) on stack
ROM:00033A98                 mov     (A1), D0        ; D0 = 5FFD
ROM:00033A99                 inc     D0              : D0 = 5FFE
ROM:00033A9A                 mov     D0, (A1)        ; blah
ROM:00033A9B                 mov     D0, A0          ; blah
ROM:00033A9D                 movbu   (A0), D0
ROM:00033A9F                 movbu   D0, (5,SP)      ; store (5FFE) on stack
ROM:00033AA2                 mov     (A1), D0        ; blah
ROM:00033AA3                 inc     D0
ROM:00033AA4                 mov     D0, (A1)
ROM:00033AA5                 mov     D0, A0
ROM:00033AA7                 movbu   (A0), D0
ROM:00033AA9                 movbu   D0, (4,SP)     ; store (5FFF) on stack
ROM:00033AAC                 mov     (4,SP), D0     ; D0 = ((5FFC) << 24) | ((5FFD) << 16) | ((5FFE) << 8) | (5FFF);
ROM:00033AAE
ROM:00033AAE locret_33AAE:
ROM:00033AAE                 retf    [], 8          ; return to 24FE0

checks 1,2 and 4 = uninteresting, standard ATAPI field checks.

check5 = interesting. Inquiry returns revision level 5A47 instead of 0047 for product revision level if the little endian version of the word at (5FFC) is EEF8EFFA? I don't want to get sidetracked again, I'll leave this as a mystery for now.

check3 = very interesting. If (598) bit 6 is set, then set a couple of vendor specific bits in packet[5] before continuing. Perhaps (598) bit 6 indicates modeA or modeB. (EDIT 16th March 2006: reading through this stuff again, it appears that I made a mistake here. The handler doesn't set any bits, it just skips over the check for those bits if (598) bit 6 is set. Fortunately this error didn't/doesn't affect any of my conclusions). This could explain why Inquiry works for modeB and not modeA. The first thing to try is sending and Inquiry command to the drive in modeA with these bits set and see if it works. Here's code to do that. From the disassembly, it looks like the drive has 60 bytes of Inquiry data. So there's some vendor specific stuff in the output for sure. My dvdprobe.c only dumped the first (mandatory) 36 bytes, this program dumps all 96 bytes.

/*
 * sends an ATAPI Inquiry command with the 
 * vendor specific bits at offset 5 into
 * the command packet set (as required by the
 * Xbox360 Hitachi-LG DVD drive)
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 21st february 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

void dump_hex(unsigned char *data, unsigned int len, unsigned int offset)
{
	unsigned int i, j, mod;
	char ascii[16];

	for(i = 0; i < len; i++) {
		mod = i % 16;
		if(!(mod)) {
			if(i) {
				for(j = 0; j < 16; j++) {
					if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
						printf("%c", ascii[j]);
					else
						printf(".");
				}
				printf("\n");
			}
			printf("%08X: ", offset + i);
		}
		else if(i && !(i % 8))
			printf("- ");
		
		ascii[mod] = data[i];
		printf("%02X ", data[i]);
	}

	if(!len)
		printf("\n");
	else {
		len = 16 - (len % 16);
		if(len != 16) {
			if(len >= 8)
				printf("  ");
			for(mod++, i = 0; i < len; i++) {
				ascii[mod + i] = ' ';
				printf("   ");
			}
		}
		for(j = 0; j < 16; j++) {
			if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
				printf("%c", ascii[j]);
			else
				printf(".");
		}
		printf("\n");
	}
}

int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	unsigned char data[0x60];

	if(argc != 2) {
		printf("usage: dvdprobe device\n");
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		perror("open");
		return 1;
	}

	printf("\n\nGPCMD_INQUIRY\n=============\n"); // ATAPI INQUIRY
	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = GPCMD_INQUIRY;
	cgc.cmd[4] = 0x60;
	cgc.cmd[5] = 0xC0; // set vendor specific bits 6 and 7
	cgc.buffer = data;
	cgc.buflen = 0x60;
	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_READ;
	cgc.timeout = 15000;
	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else
		dump_hex(data, 0x60, 0);

	printf("\n");

	close(fd);

	return 0;
}

download

Output for modeA:

GPCMD_INQUIRY
=============
00000000: 05 80 00 32 5B 00 00 00 - 48 4C 2D 44 54 2D 53 54 ...2[...HL-DT-ST
00000010: 44 56 44 2D 52 4F 4D 20 - 47 44 52 33 31 32 30 4C DVD-ROM GDR3120L
00000020: 30 30 34 36 30 42 4D 41 - 42 20 20 20 30 35 2F 30 00460BMAB   05/0
00000030: 37 2F 32 37 20 20 20 00 - 01 00 00 00 00 00 00 00 7/27   .........
00000040: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000050: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................

It worked! This is consistant with what Nayr discovered about the Hitachi-LG's inquiry command (long before I did). It's possible that a standard Inquiry works in modeB because the handler sets the required vendor specific bits for you (after check 3) (EDIT 16th March 2006: reading through this stuff again, it appears that I made a mistake here. The handler doesn't set any bits, it just skips over the check for those bits if (598) bit 6 is set. Fortunately this error didn't/doesn't affect any of my conclusions). If this is true, then it would seem that (598) bit 6 specifies modeA/ModeB.

The only writes to (598) bit 6 that I can find in the firmware are at the following locations:

90006662
90006669

Here's the disassembly listing in that general area.

ROM:0000665A                 movbu   (word_D98C+1), D0 ; read (D98D)
ROM:0000665D                 btst    0x10, D0          ; Is bit 4 set?
ROM:00006660                 beq     loc_6669          ; No, branch
ROM:00006662                 bclr    0x40, (word_598)  ; Yes, so (598) bit 6 = 0 (modeA?)
ROM:00006667                 bra     loc_6680
ROM:00006669 ! ---------------------------------------------------------------------------
ROM:00006669
ROM:00006669 loc_6669:                               ! CODE XREF: sub_654A+116j
ROM:00006669                 bset    0x40, (word_598)  ; (598) bit 6 = 1 (modeB?)
ROM:0000666E                 clr     D0
ROM:0000666F                 movbu   D0, (0x8003E6D8)
ROM:00006675                 mov     1, D0
ROM:00006677                 mov     0x64, D1 ! 'd'
ROM:00006679                 call    sub_1EEB3, [D2,D3,A2], 0x10
ROM:00006680
ROM:00006680 loc_6680:
ROM:00006680                 mov     0x20, D0 ! ' '
ROM:00006682                 movbu   D0, (A2)
ROM:00006684                 ret     [D2,A2], 0xC
ROM:00006684 ! End of function sub_654A

We can see that bit 6 of (598) is set or cleared after testing (D98D) bit 4. According to the MN103 manual, (D98D) can be internal data RAM or mapped externally depending on memory configuration.

Occurances of writes to (D98D) in the firmware (the ones I can find at least):

900000E9 (set to zero)

Here's the code around that location

ROM:000000E5                 movbu   D0, (word_D98C) ! D98C (internal/external data RAM) = 20
ROM:000000E8                 clr     D0
ROM:000000E9                 movbu   D0, (word_D98C+1) ! D98C (internal/external data RAM) = 0
ROM:000000EC                 rets

This is very near to the suspected flash entry point (offset 0x20), so this is likely code to initalise (D98C/D). I think (D98C) and (D98D) are related. Look at the code directly above the (D98D) bit 4 check (the check that I suspect selects modeA or modeB).

ROM:0000664E                 mov     0xD98C, A2          ; A2 = D98C
ROM:00006651                 mov     0x30, D0 ! '0'      ; D0 = 0x30
ROM:00006653                 movbu   D0, (A2)            ; set bit 4 and 5 at (D98C)
ROM:00006655                 call    sub_628E, [D2], 4   ; call timer delay function
ROM:0000665A                 movbu   (word_D98C+1), D0   ; read (D98D)
ROM:0000665D                 btst    0x10, D0            ; Is bit 4 set?
ROM:00006660                 beq     loc_6669            ; No, branch
ROM:00006662                 bclr    0x40, (word_598)    ; Yes, so (598) bit 6 = 0 (modeA?)
ROM:00006667                 bra     loc_6680
ROM:00006669 ! ---------------------------------------------------------------------------
ROM:00006669
ROM:00006669 loc_6669:                               ! CODE XREF: sub_654A+116j
ROM:00006669                 bset    0x40, (word_598)  ; (598) bit 6 = 1 (modeB?)
ROM:0000666E                 clr     D0
ROM:0000666F                 movbu   D0, (0x8003E6D8)
ROM:00006675                 mov     1, D0
ROM:00006677                 mov     0x64, D1 ! 'd'
ROM:00006679                 call    sub_1EEB3, [D2,D3,A2], 0x10
ROM:00006680
ROM:00006680 loc_6680:
ROM:00006680                 mov     0x20, D0 ! ' '     ; D0 = 0x20
ROM:00006682                 movbu   D0, (A2)           ; clear bit 4 at (D98C)
ROM:00006684                 ret     [D2,A2], 0xC
ROM:00006684 ! End of function sub_654A

ROM:0000628E sub_628E:                               ! CODE XREF: sub_654A+10Bp
ROM:0000628E                 movhu   (word_DC54), D1 ; (DC54) a timer? D1 = initial timer value
ROM:00006291                 setlb                   ; set up loop registers
ROM:00006292                 movhu   (word_DC54), D0 ; read current timer value
ROM:00006295                 mov     D1, D2
ROM:00006296                 sub     D0, D2          ; calc difference between inital and current timer values
ROM:00006298                 mov     D2, D0
ROM:00006299                 exthu   D0
ROM:0000629A                 cmp     0x6A, D0 ! 'j'
ROM:0000629C                 llt                     ; Loop for 0x6A timer increments
ROM:0000629D                 retf    [D2], 4
ROM:0000629D ! End of function sub_628E

If you trace the disassembly from 0x90000020 (flash entry point), then you can see that the above code is executed very to near to the begining of flash execution. It also appears to be executed only once. The subroutine at 9000628E looks suspiciously like a timer delay routine to me. I could be seeing things but (D98D) looks like an 8 bit I/O port's data register and (D98C) looks like the I/O port's corresponding control register (bits set to 0 for output and 1 for input). Here's my current speculation.

1) Drive powers up
execution hits 0x900000E5
2) Drive initialises I/O control register (D98C) to 0x20 (all I/O lines outputs except for line 5, which is an input, eject perhaps?)
3) Drive also initialises I/O data register (D98D) to 0x00 (send all output lines low)
execution hits 0x9000664E
4) Drive sets I/O control register (D98C) to 0x30 (line 4, tray_status, is now an input). Resistor R214 pulls it high in an out of the box drive.
5) Drive calls the timer delay function at 0x9000628E to give enough time for tray_status input to settle.
6) Drive reads I/O data register (D98D). If bit 4 is set (tray_status high), then it clears (598) bit 6. This cements modeA. If bit 4 is clear (tray_status low), then it sets (598) bit 6. This cements modeB.
execution hits 0x90006680
7) Drive sets I/O control register (D98C) back to 0x20 (line 4, tray_status, is now an output again).

If this speculation is true, then it explains the first part of the waveforms that I have been seeing on tray_status and the behaviour of the Inquiry command that I have been observing. It would also confirm my theory about a bidirectional tray_status line. Finally, it would explain why I have had so much success in Windows with modeB. A standard Inquiry command works in modeB. The non-standard Inquiry seems to be the biggest stumbling block for windows.

The subroutine at 0x9001B53B that returns (598) bit 6 (the possible modeA/B indicator) is referenced a fair amount in the firmware. If the above speculation holds true, then tracing these references will highlight other differences between modeA and modeB. For example, I image we'd find a reference to this function in the READ(12) command handler (which also works in modeB, but not modeA).

Todo: confirm/deny the above speculation.

Update: 2nd March 2006

There exists a debug command in the Hitachi drive that will set (598) bit 6 (see SuperMario's post on xbh.net). Here's the handler code for this command.

ROM:00025FA7 loc_25FA7:                              ! CODE XREF: sub_25D86+C1 j
ROM:00025FA7                 movbu   (word_5BF+1), D0
ROM:00025FAA                 cmp     1, D0
ROM:00025FAC                 bne     loc_25FF4
ROM:00025FAE                 movbu   (word_598), D0
ROM:00025FB1                 mov     0x64, D1 ! 'd'
ROM:00025FB3                 or      0x40, D0 ! '@'
ROM:00025FB6                 or      0x10, D0
ROM:00025FB9                 movbu   D0, (word_598)
ROM:00025FBC                 clr     D0
ROM:00025FBD                 movbu   D0, (0x8003E6D8)
ROM:00025FC3                 mov     1, D0
ROM:00025FC5                 call    sub_1EEB3, [D2,D3,A2], 0x10
ROM:00025FCA                 bra     loc_25FF4

ROM:00025FF4
ROM:00025FF4 loc_25FF4:                              ! CODE XREF: sub_25D86+102 j
ROM:00025FF4                                         ! sub_25D86+10A j ...
ROM:00025FF4                 clr     D0
ROM:00025FF5                 ret     [D2], 8
ROM:00025FF5 ! End of function sub_25D86

This handler is doing all of the same things as the code at 0x90006669 that I posted about in my last update and that I suspected was setting up modeB after sampling the tray_status input. They both set (598) bit 6, clear the byte at 0x8003E6D8 in RAM and call sub_1EEB3 with parameters 0x64 (in D1) and 0x01 (in D0). It certainly looks the part. Here's some code that will send the drive this command.

/*
 * Puts a Hitachi-LG Xbox 360 DVD drive into modeB.
 * In modeB, the drive responds to standard ATAPI
 * commands that it otherwise wouldn't (for example:
 * Read(12), Inquiry, Mode Sense(10)). ModeB also
 * changes, among other things, the behaviour of the
 * drive's eject input and tray_status output.
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 2nd March 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;

	if(argc != 2) {
		printf("usage: modeb device\n");
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		perror("open");
		return 1;
	}

	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = 0xE7; // Hitachi debug command 0xE7
	cgc.cmd[1] = 0x48; // 'H'
	cgc.cmd[2] = 0x49; // 'I'
	cgc.cmd[3] = 0x54; // 'T'
	cgc.cmd[4] = 0x30;
	cgc.cmd[5] = 0x90;
	cgc.cmd[6] = 0x90;
	cgc.cmd[7] = 0xD0;
	cgc.cmd[8] = 0x01;
	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_NONE;
	cgc.timeout = 15000;
	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else
		printf("done\n");

	printf("\n");

	close(fd);

	return 0;
}

download

It works! The drive immediately spins up loudly in the usual modeB style, eject and tray_status adopt their usual modeB behaviours and the standard Inquiry and Read(12) commands succeed. I've also written a win32 version of the above program, code and binary below.

Be warned: This win32 program is the mother of all hacks. It does not use the windows API (DeviceIoControl(), IOCTL_SCSI_DIRECT_OR_WHATEVER_ITS_CALLED, etc.) and instead uses a userspace parallel ATA driver that I wrote back when I was just learning about the ATA spec. The userpace driver writes directly to the ATA controller's I/O ports (which requires Geek Hideout's I/O dll, which I have included in the binary zip). It also requires that you be using an ATA - SATA adapter to connect your 360 drive to your PC, or an SATA controller that has a legacy mode (try the BIOS setup options on your controller). Sorry :-\ If anybody feels like writing a proper win32 version, then please email me and let me know. I have zero patience for the win32 API.

/*
 * Puts a Hitachi-LG Xbox 360 DVD drive into modeB.
 * In modeB, the drive responds to standard ATAPI
 * commands that it otherwise wouldn't (for example:
 * Read(12), Inquiry, Mode Sense(10)). ModeB also
 * changes, among other things, the behaviour of the
 * drive's eject input and tray_status output.
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 2nd March 2006
 * platform: Windows
 *
 */

#include "../ata/ata.h"
#include <windows.h>
#include <string.h>
#include <stdio.h>

int hex_atoi(void *dest, char *src, int dest_len)
{
	int src_last, i, j;
	unsigned char val;

	memset(dest, 0, dest_len);

	src_last = strlen(src) - 1;

	for(i = src_last; i >= 0; i--) {
		if(src[i] >= '0' && src[i] <= '9')
			val = src[i] - 0x30;
		else if(src[i] >= 'a' && src[i] <= 'f')
			val = (src[i] - 0x60) + 9;
		else if(src[i] >= 'A' && src[i] <= 'F')
			val = (src[i] - 0x40) + 9;
		else
			return 1; // invalid hex digit

		j = src_last - i;

		if(j & 1)
			val <<= 4;

#ifdef BIG_ENDIAN		
		j = dest_len - ((j >> 1) + 1);
#else
		j >>= 1;
#endif

		if(j >= dest_len || j < 0)
			break;

		((unsigned char *)dest)[j] |= val;
	}
	
	return 0;
}

int main(int argc, char *argv[])
{
	struct ata_info ai;
	unsigned short comm, ctrl;
	unsigned char pio, packet[12]; 

	LoadIODLL();

	if(argc < 3) {
		printf("usage: modeb_win command_base control_base [pio_mode]\n\ncommand_base: base register of ATA command block in hex (e.g. 1F0 or 170)\ncontrol_base: base register of ATA control block in hex (e.g. 3F6 or 376)\npio_mode: 16 or 32 (default: 32)\n\n");
		return 1;
	}

	if(hex_atoi(&comm, argv[1], sizeof(comm))) {
		printf("error: invalid ATA command block base register\n");
		return 1;
	}
	if(hex_atoi(&ctrl, argv[2], sizeof(ctrl))) {
		printf("error: invalid ATA control block base register\n");
		return 1;
	}
	if(argc > 3) {
		pio = atoi(argv[3]);
		if(pio == 16)
			pio = ATA_PIO_16;
		else if(pio == 32)
			pio = ATA_PIO_32;
		else {
			printf("error: invalid PIO mode (valid: 16 or 32)\n");
			return 1;
		}
	}
	else
		pio = ATA_PIO_16;
		

	ata_init_info(&ai, comm, ctrl, pio, ATA_DEFAULT_TIMEOUT);

	memset(packet, 0, sizeof(packet));
	packet[0] = 0xE7; // Hitachi debug command 0xE7
	packet[1] = 0x48; // 'H'
	packet[2] = 0x49; // 'I'
	packet[3] = 0x54; // 'T'
	packet[4] = 0x30;
	packet[5] = 0x90;
	packet[6] = 0x90;
	packet[7] = 0xD0;
	packet[8] = 0x01;
	if(ata_packet(packet, sizeof(packet), ATA_MASTER, ATA_INTR_ON, &ai))
		ata_dump_err(stdout, &ai.err);
	else
		printf("done\n");

	printf("\n");

	return 0;
}

download source (7th Mar 06: link now fixed)
download binary

usage instructions are in the binary zip. I've tested it only in win98 and I had to restart windows to get the drive recognised after running the program, but it worked and it should work in 2000/XP too. Although I really recommend using the Linux version if at all possible.

Update: 8th March 2006

For the sake of completeness, I'd still like to confirm my theories about tray_status and the I/O port at (D98C)/(D98D). I've already confirmed that the register at (DC54) is a 16-bit counter by dumping it repeatedly (with the 0xE7 memory dump command) and observing that the 16-bit value decends with each read (cycling around to 0xFFFF as it hits 0). To test the I/O registers I'll need to be able to write to those registers as well as read from them.

It will almost certainly be useful to have a purely software method for writing to the MN103 address space for other hacks/investigations. As far as I know no such thing exists yet, or at least nobody is publishing it.

There is a debug command that allows you to poke bytes in memory above 0x80000000 (external RAM) (see djhuevo's post on xbh.net), but unfortunately I need to write to the address space below this limit. There is also a function that allows you to load bulk code/data into RAM starting at 0x80000000 (see SpenZerX's post on xbh.net). Either the poke RAM or bulk load to RAM commands could be used to upload our own MN103 code to RAM and then the Hitachi jump to RAM command (see DaveX's original post on the Hitachi vendor specific commands) can be used to execute this code (which could write to wherever we like).

The problem is that the jump to subroutine in RAM command (handler starts at 0x9002A725) requires (59E) bit 3 to be set or it fails.

ROM:0002A7A2                 btst    8, (word_59E)   ; is (59E) bit 3 set?
ROM:0002A7A7                 beq     loc_2A7BA       ; No, so bail

This bit isn't set by default in modeA or B. This bit is set within the bulk load to RAM command's handler (starting at 0x9002A5A1), perhaps to ensure that code has actually been written to RAM before the drive jumps.

ROM:0002A5DC                 bset    8, (word_59E)   ; set (59E) bit 3

The petty annoyance is that the bulk write to RAM command is protected by a check against (5A5) bit 4 at the very start of the handler. If this bit is clear, then the command fails. again, this isn't set by default in ModeA or B.

ROM:0002A5A1                 btst    0x10, (word_5A5) ; is (5A5) bit 4 set?
ROM:0002A5A6                 beq     loc_2A5CC        ; no, so branch (this exits the handler)

I can see one other place in the firmware where (5A5) bit 4 is set and one other place where (59E) bit 3 is set. Both of these exist within the ATAPI Mode Select(10) command handler. Which I have yet to trace.

Options:
* Exploit a bug in the firmware to de-rail a write or a call/jump/ret to either write to our target location directly or execute our own code that will write to our target location (the code can be uploaded anywhere in RAM a byte at a time using the RAM poke command).
* Figure out the Mode Select(10) commands that will get either (5A5) bit 4 or (59E) bit 3 set.

A Mode Sense(10) dump will be helpful when tracing the Mode Select(10) handler. Here's code to dump the drive's complete Mode Parameter List.

/*
 * dumps the Mode Parameter List containing all
 * Mode Pages of the xbox 360 Hitachi DVD drive.
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 3rd march 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

void dump_hex(unsigned char *data, unsigned int len, unsigned int offset)
{
	unsigned int i, j, mod;
	char ascii[16];

	for(i = 0; i < len; i++) {
		mod = i % 16;
		if(!(mod)) {
			if(i) {
				for(j = 0; j < 16; j++) {
					if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
						printf("%c", ascii[j]);
					else
						printf(".");
				}
				printf("\n");
			}
			printf("%08X: ", offset + i);
		}
		else if(i && !(i % 8))
			printf("- ");
		
		ascii[mod] = data[i];
		printf("%02X ", data[i]);
	}

	if(!len)
		printf("\n");
	else {
		len = 16 - (len % 16);
		if(len != 16) {
			if(len >= 8)
				printf("  ");
			for(mod++, i = 0; i < len; i++) {
				ascii[mod + i] = ' ';
				printf("   ");
			}
		}
		for(j = 0; j < 16; j++) {
			if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
				printf("%c", ascii[j]);
			else
				printf(".");
		}
		printf("\n");
	}
}

int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	unsigned char data[0x96 + 2];

	if(argc != 2) {
		printf("usage: msense device\n");
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		perror("open");
		return 1;
	}
	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = 0x5A; // mode sense command
	cgc.cmd[1] = 0x08; // DBD set to 1
	cgc.cmd[2] = 0x3F; // return current current values for all mode pages
	cgc.cmd[7] = 0xFF; // allocation length MSB
	cgc.cmd[8] = 0xFF; // allocation length LSB
	cgc.buffer = data;
	cgc.buflen = sizeof(data);
	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_READ;
	cgc.timeout = 15000;
	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else
		dump_hex(data, sizeof(data), 0);

	printf("\n");

	close(fd);

	return 0;
}

download

This program only works with the drive in modeB. The drive rejects a standard Mode Sense(10) command under modeA. There are probably vendor specific parts of the packet that need to be set in order for Mode Sense(10) to work in modeA, but I don't know what they are because I haven't looked at the Mode Sense(10) handler yet. Here's the ouput from my drive.

00000000: 00 96 70 00 00 00 00 00 - 01 0A 00 08 14 08 14 00 ..p.............
00000010: 00 00 00 00 20 0A 02 00 - 00 00 00 00 00 00 00 00 .... ...........
00000020: 3B 30 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ;0..............
00000030: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000040: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000050: 00 00 3C 16 00 00 00 00 - 00 00 00 00 00 00 00 00 ..<.............
00000060: 00 00 00 00 00 00 00 00 - 00 00 3D 0A 04 00 00 00 ..........=.....
00000070: 00 00 00 00 00 00 3E 20 - 00 00 00 E1 03 00 00 00 ......> ........
00000080: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
00000090: 00 00 00 00 00 00 00 00                           ........

Here's a breakdown of the above data.

mode param header:

00 96 : mode data length (big endian)
70 : medium type code (70 = no disc, 42 = 360 game) 
00 00 00 : reserved
00 00 : block descriptor length


mode pages:

01 : page code 1 = Read/Write Error Recovery Parameters, ps = false
0A : param length
00 08 14 08 14 00 00 00 00 00 : mode params (see inf-8090)

20 : page code 20 = vendor specific, ps = false
0A : param length
02 00 00 00 00 00 00 00 00 00 : mode params

3B : page code 3B = vendor specific, ps = false
30 : param length
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : mode params

3C : page code 3C = vendor specific, ps = false
16 : param length
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : mode params 

3D : page code 3D = vendor specific, ps = false
0A : param length
04 00 00 00 00 00 00 00 00 00 : mode params

3E : page code 3E = vendor specific, ps = false
20 : param length
00 00 00 E1 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : mode params 

For the record, Mode Select(10) works in ModeA. As a test, I have successfully modified the Read Retry Count parameter of the Read/Write Error Recovery Parameters mode page in both modes (see inf-8090).

Okay, I have started tracing the Mode Select(10) handler (which starts at 0x9002643F). My comments for this handler are pretty indecipherable, so the following pseudo-code describes the top level of the handler.

if(!sub_1B130()) // not looked into this sub routine yet
	exit;

if ((59E) bit 3 is set and packet[3] == 'H' and packet[4] == 'L') // [H]itachi [L]G?
	call 0x80000000; // call subroutine in RAM

if ((59E) bit 4 is set (settable via a debug command)) {
	if(PLL <= 0x400) // PLL = Parameter Length List (from the ATAPI Mode Select packet)
		call sub_1B66D with D0 = PLL, A0 = 0x63C, A1 = 0x9003012A;
	exit;
}

if (59E) bit 3 is set {
	call sub_1B66D with D0 = PLL, A0 = 0x63C, A1 = 0x90026FC7;
	exit;
}
else if(PLL <= 0x78) {
	call sub_1B66D D0 = PLL, A0 = 0x63C, A1 = 0x900265C5;
	enter a loop; // I haven't looked at this yet
	exit;
}

There are 4 main paths execution can take. All of them end up with a call to sub_1B66D. The arguments to sub_1B66D are the PLL (parameter list length), 0x63C (address of input mode parameter list, page code is at (644)) and an address that differs for each of the 4 calls. The 4 locations contain sub-routines and it's a fair assumption that the specified subroutine gets called sometime after the Mode Select(10) handler returns.

Under normal conditions (and with a PLL <= 0x78), execution takes the call to sub_1B66D with A1 set to 0x900265C5 (the final call in the above pseudo code). So let's disassemble the subroutine at 0x900265C5. Right at the start of the routine is the following code.

ROM:000265C8                 call    sub_1A1D5, [A2], 0b1100 ; set (24AC) and (24B4) based on (5A4) bit 4
ROM:000265CF                 call    sub_1A27D, [A2], 0xC    ; set (626) based on (6BF) and set (6BF) to 1
ROM:000265D6                 movbu   (word_643+1), D0        ; D0 = page code
ROM:000265D9                 cmp     9, D0                   ; is page code 9?
ROM:000265DB                 bne     loc_265F2               ; no, so branch
ROM:000265DD                 btst    0x40, (word_598) ! '@'  ; are we in modeB?
ROM:000265E2                 beq     loc_26646               ; no, so bail
ROM:000265E4                 btst    0x10, (word_598)        ; is (598) bit 4 clear?
ROM:000265E9                 bne     loc_26646               ; no, so bail
ROM:000265EB                 bset    0x10, (unk_5A5)         ; if all requirements met, then it set (5A5) bit 4
ROM:000265F0                 bra     loc_2664B               ; return

(5A5) bit 4 is the bit that restricts the bulk write to RAM command. Looks like a Mode Select(10) command with a page code 9 will set this bit for us. But there is a problem, we must be in modeB and (598) bit 4 must be clear. The software method that I wrote about in my last update can get us into modeB, but that handler also *sets* (598) bit 4, which will cause the above code to fail. If we go back and look at the code that I suspect sets modeB based on tray_status (0x90006669) then we can see that it *does not* set (598) bit 4. This is a crucial difference between the software and hardware methods of invoking modeB. Here's code that will send a Mode Select(10) command that should set (5A5) bit 4.

/*
 * Sets bit 4 at address 0x5A5 within the address
 * space of the MN103 microcontroller within the
 * Hitachi-LG xbox 360 dvd drive
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 7th March 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	unsigned char data[10];

	if(argc != 2) {
		printf("usage: set_5A5_4 device\n");
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		perror("open");
		return 1;
	}

	
	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = 0x55; // Mode Select(10)
	cgc.cmd[1] = 0x10;
	cgc.cmd[8] = 0x0A; // Parameter List Length
	memset(data, 0, sizeof(data));
	data[1] = 0x08;
	data[8] = 0x09; // page code
	cgc.buffer = data;
	cgc.buflen = 10;
	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_WRITE;
	cgc.timeout = 15000;
	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else
		printf("done\n");

	printf("\n");

	close(fd);

	return 0;
}

download

After running the above program I examined (5A5) bit 4 using the Hitachi 0xE7 memory dump command. As suspected, it fails in ModeA, fails in software induced modeB and succeeds in hardware induced modeB. Which sucks because the whole idea is that no hardware tinkering should be required to write to the MN103 address space.

Continuing with the disassembly trace at 0x900265F2 (the branch if page code is not 9) we get the following.

ROM:000265F2 loc_265F2:                              ! CODE XREF: sub_265C8+13j
ROM:000265F2                 movbu   (word_643+1), D0 ; D0 = page code
ROM:000265F5                 and     0x3F, D0 ! '?'   ; zero bits 6 and 7 of (644) (PS and reserved bits)
ROM:000265F8                 cmp     1, D0            ; page code 1 = Read/Write Error Recovery Parameters
ROM:000265FA                 beq     loc_26612
ROM:000265FC                 cmp     0x20, D0 ! ' '   ; page code 20 = vendor specific
ROM:000265FE                 beq     loc_2661A
ROM:00026600                 cmp     0x3B, D0 ! ';'   ; page code 3B = vendor specific
ROM:00026602                 beq     loc_26622
ROM:00026604                 cmp     0x3C, D0 ! '<'   ; page code 3C = vendor specific
ROM:00026606                 beq     loc_2662A
ROM:00026608                 cmp     0x3E, D0 ! '>'   ; page code 3E = vendor specific
ROM:0002660A                 beq     loc_26632
ROM:0002660C                 cmp     0x3A, D0 ! ':'   ; page code 3A = vendor specific
ROM:0002660E                 beq     loc_26646
ROM:00026610                 bra     loc_2663A        ; page code does not match any of the above
ROM:00026612 ! ---------------------------------------------------------------------------
ROM:00026612
ROM:00026612 loc_26612:                              ! CODE XREF: sub_265C8+32j
ROM:00026612                 call    sub_26B40, [D2], 8
ROM:00026617                 ret     [], 4
ROM:0002661A ! ---------------------------------------------------------------------------
ROM:0002661A
ROM:0002661A loc_2661A:                              ! CODE XREF: sub_265C8+36j
ROM:0002661A                 call    sub_269BE, [], 4
ROM:0002661F                 ret     [], 4
ROM:00026622 ! ---------------------------------------------------------------------------
ROM:00026622
ROM:00026622 loc_26622:                              ! CODE XREF: sub_265C8+3Aj
ROM:00026622                 call    sub_28239, [D2,D3,A2], 0x10
ROM:00026627                 ret     [], 4
ROM:0002662A ! ---------------------------------------------------------------------------
ROM:0002662A
ROM:0002662A loc_2662A:                              ! CODE XREF: sub_265C8+3Ej
ROM:0002662A                 call    sub_281D6, [], 4
ROM:0002662F                 ret     [], 4
ROM:00026632 ! ---------------------------------------------------------------------------
ROM:00026632
ROM:00026632 loc_26632:                              ! CODE XREF: sub_265C8+42j
ROM:00026632                 call    sub_2709B, [A2], 8
ROM:00026637                 ret     [], 4
ROM:0002663A ! ---------------------------------------------------------------------------
ROM:0002663A
ROM:0002663A loc_2663A:                              ! CODE XREF: sub_265C8+48j
ROM:0002663A                 call    sub_1B53B, [], 0 ! get modeA/B flag
ROM:00026641                 extb    D0
ROM:00026642                 cmp     0, D0           ! Are we in modeB?
ROM:00026644                 bne     loc_26653       ! yes, so branch (modeA returns)
ROM:00026646
ROM:00026646 loc_26646:                              ! CODE XREF: sub_265C8+1Aj
ROM:00026646                                         ! sub_265C8+21j ...
ROM:00026646                 mov     0xB, D0
ROM:00026648                 movbu   D0, (word_5D8)
ROM:0002664B
ROM:0002664B loc_2664B:                              ! CODE XREF: sub_265C8+28j
ROM:0002664B                                         ! sub_265C8+DDj ...
ROM:0002664B                 mov     4, D0
ROM:0002664D                 movbu   D0, (word_6D8+1)
ROM:00026650                 ret     [], 4

This code handles the page codes reported by Mode Sense(10) earlier (1, 20, 3A, 3B, 3C and 3E). If the page code does not match any of them, then the routine returns if we are in modeA. However, if we are in modeB then we branch to the following code.

ROM:00026653 loc_26653:                              ! CODE XREF: sub_265C8+7Cj
ROM:00026653                 movbu   (word_5C0), D0  ; D0 = paramter list length
ROM:00026656                 cmp     8, D0           ; is the PLL 8? (i.e. mode parameter header only, no mode pages)
ROM:00026658                 beq     loc_266A7       ; yes, so branch past the mode page transfer code
ROM:0002665A                 movbu   (word_5C0), D0
ROM:0002665D                 cmp     8, D0
ROM:0002665F                 blt     loc_2669B       ; bail out if PLL is less than 8
ROM:00026661                 movbu   (word_5C0), D0
ROM:00026664                 add     0xF8, D0 ! '°'
ROM:00026666                 movbu   D0, (word_5C0)  ; PLL -= 2 (to account for length field)
ROM:00026669                 clr     D0
ROM:0002666A                 movbu   D0, (word_5F4)
ROM:0002666D                 movbu   D0, (word_5F4+1)
ROM:00026670                 bra     loc_2667D        ; branch into transfer loop
ROM:00026672 ! ---------------------------------------------------------------------------
ROM:00026672
ROM:00026672 loc_26672:                              ! CODE XREF: sub_265C8+BAj
ROM:00026672                 call    sub_266AE, [D2,D3], 0xC ; calls one of nine functions based on the page code
ROM:00026677                 mov     D0, D1
ROM:00026678                 extbu   D1
ROM:00026679                 cmp     0, D1
ROM:0002667B                 bne     loc_26684       ; break out of loop if error occured
ROM:0002667D
ROM:0002667D loc_2667D:                              ; transfer loop entry point
ROM:0002667D                 movbu   (word_5C0), D1  ! D1 = remaining PLL
ROM:00026680                 cmp     0, D1
ROM:00026682                 bne     loc_26672       ; loop until all data tranfered
ROM:00026684
ROM:00026684 loc_26684:                              ; loop drops out here
ROM:00026684                 extbu   D0
ROM:00026685                 cmp     3, D0
ROM:00026687                 beq     loc_26693       ; bail if error
ROM:00026689                 cmp     1, D0
ROM:0002668B                 beq     loc_26697       ; bail if error
ROM:0002668D                 cmp     0, D0
ROM:0002668F                 beq     loc_266A7       ; successful transfer, so branch
ROM:00026691                 bra     loc_2669B       ; bail if error
ROM:00026693 ! ---------------------------------------------------------------------------
ROM:00026693
ROM:00026693 loc_26693:                              ! CODE XREF: sub_265C8+BFj
ROM:00026693                 mov     0x10, D0
ROM:00026695                 bra     loc_2669D
ROM:00026697 ! ---------------------------------------------------------------------------
ROM:00026697
ROM:00026697 loc_26697:                              ! CODE XREF: sub_265C8+C3j
ROM:00026697                 mov     0xA, D0
ROM:00026699                 bra     loc_2669D
ROM:0002669B ! ---------------------------------------------------------------------------
ROM:0002669B
ROM:0002669B loc_2669B:                              ! CODE XREF: sub_265C8+97j
ROM:0002669B                                         ! sub_265C8+C9j
ROM:0002669B                 mov     0xB, D0
ROM:0002669D
ROM:0002669D loc_2669D:                              ! CODE XREF: sub_265C8+CDj
ROM:0002669D                                         ! sub_265C8+D1j
ROM:0002669D                 movbu   D0, (word_5D8)
ROM:000266A0                 bclr    4, (word_59C+1)
ROM:000266A5                 bra     loc_2664B
ROM:000266A7 ! ---------------------------------------------------------------------------
ROM:000266A7
ROM:000266A7 loc_266A7:                              ! CODE XREF: sub_265C8+90j
ROM:000266A7                                         ! sub_265C8+C7j
ROM:000266A7                 call    sub_26C3B, [D2], 8  ; this function sets (59E) bit 3 :-)
ROM:000266AC                 bra     loc_2664B           ; this branch ends the routine

The subroutine sub_266AE() that gets called within each cycle of the transfer loop uses two tables both containing 9 entries. One (at 0x9003D5EC) contains 9 8bit page codes and the other (at 0x9003D5F8) contains 9 32bit subroutine addresses. Each subroutine corresponds to the page code at the same position in the other table. sub_266AE calls the correct handler routine for the specified page code using these tables.

page code table:
ROM:0003D5EC                 .byte    0              ! vendor specific
ROM:0003D5ED                 .byte    1              ! Read/Write error recovery params
ROM:0003D5EE                 .byte  0xD              ! reserved
ROM:0003D5EF                 .byte  0xE              ! CD Audio control
ROM:0003D5F0                 .byte 0x2A ! *          ! C/DVD capabilities and mechanical status
ROM:0003D5F1                 .byte 0x18              ! reserved
ROM:0003D5F2                 .byte 0x1A              ! power condition
ROM:0003D5F3                 .byte 0x1D              ! Timeout and protect
ROM:0003D5F4                 .byte 0x21 ! !          ! vendor specific

corresponding subroutine table:
ROM:0003D5F8                 .long 0x900266E6
ROM:0003D5FC                 .long 0x90026715
ROM:0003D600                 .long 0x90026795
ROM:0003D604                 .long 0x900267E9
ROM:0003D608                 .long 0x90026875
ROM:0003D60C                 .long 0x90026894
ROM:0003D610                 .long 0x900268B3
ROM:0003D614                 .long 0x90026929
ROM:0003D618                 .long 0x90026982

In modeB the drive supports a lot more page codes than Mode Sense(10) would have us believe. Including a lot of the standard ones that you'd expect on C/DVD drive. For future reference here's a list of page codes the Hitachi-LG drive seems to support via Mode Select(10). There may be more, I haven't traced all of the Mode Select(10) handler yet.

In modeA
========
0x01 = Read/Write Error Recovery Parameters
0x20 = vendor specific
0x3A = vendor specific
0x3B = vendor specific
0x3C = vendor specific
0x3E = vendor specific

In modeB only
=============
0x00 = vendor specific
0x09 = reserved (used by the 360 drive to set (5A5) bit 4) [hardware induced modeB only]
0x0D = reserved
0x0E = CD Audio control
0x18 = reserved
0x1A = power condition
0x1D = timeout and protect
0x21 = vendor specific
0x2A = C/DVD capabilities and mechanical status

You'll notice that at the end of the above code sub_26C3B() is called. This routine sets (59E) bit 3, which is the bit that we need to set in order to get the Hitachi jump to RAM command to working. sub26C3B() gets called after the successful transfer of any mode page that isn't 0x01, 0x20, 0x3A, 0x3B, 0x3C, 0x3E or 0x09. It even gets called if we do not supply any mode pages with the Mode Select(10) command. So, in theory, a Mode Select(10) command sent to the drive in modeB followed by an 8 byte mode parameter list (the header only) should set (59E) bit 3. This should even work with software induced modeB. Here's some code to do this.

/*
 * Sets bit 3 at address 0x59E within the address
 * space of the MN103 microcontroller within the
 * Hitachi-LG xbox 360 dvd drive
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 7th March 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	unsigned char data[8];

	if(argc != 2) {
		printf("usage: set_59E_3 device\n");
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		perror("open");
		return 1;
	}

	
	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = 0x55; // Mode Select(10)
	cgc.cmd[1] = 0x10;
	cgc.cmd[8] = 0x08; // Paramete List Length
	memset(data, 0, sizeof(data));
	data[1] = 0x08;
	cgc.buffer = data;
	cgc.buflen = 8;
	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_WRITE;
	cgc.timeout = 15000;
	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else
		printf("done\n");

	printf("\n");

	close(fd);

	return 0;
}

download

It works! Now there's nothing to stop us from writing to anywhere in the MN103 address space without so much as removing the DVD case. The follwing process should achieve this.

1. Put the drive into modeB using the Hitachi 0xE7 command that sets (598) bit 6.
2. Upload our code (which, when executed, will write to anywhere we like) one byte at a time using the 0xE7 RAM poke command.
3. Set (59E) bit 3 using Mode Select(10) as described above.
4. Force the drive to jump to our code using the 0xE7 jump to RAM command (which will succeed because we have set (59E) bit 3)

Hmmm. I'm having trouble with step 4. I know my code is getting uploaded correctly and executed, but the drive is hanging afterwards. I see that the Hitachi jump to RAM command handler expects a 32-bit value at offset 8 in the ATAPI command packet. I have no idea what this is supposed to be, the code that I have so far traced simply checks that this value is between 1 and 0x60000. Here's the dissasembly

ROM:0002A728                 mov     0x5C0, A0          ; A0 = address of 32-bit value at packet[8]
... snip ...
ROM:0002A794                 call    sub_33A84, [], 8   ; D0 = 32 bit value at packet[8] in little endian
ROM:0002A79B                 mov     D0, (word_924)     ; store it at (924)
ROM:0002A79E                 cmp     0, D0              ; is 32-bit value zero?
ROM:0002A7A0                 beq     loc_2A7BF          ; yes, exit (without error)
ROM:0002A7A2                 btst    8, (word_59E)      ; no, so is (59E) bit 3 set?
ROM:0002A7A7                 beq     loc_2A7BA          ; no, so exit (with error)
ROM:0002A7A9                 cmp     0x60000, D0        ; yes, so is 32-bit value > 0x60000?
ROM:0002A7AF                 bhi     loc_2A7BA          ; yes, so exit (with error)
ROM:0002A7B1                 call    sub_2A7C3, [], 4   ; the call to RAM is within this short function
ROM:0002A7B6                 mov     1, D0              
ROM:0002A7B8                 bra     locret_2A7C0       ; all done, return
ROM:0002A7BA ! ---------------------------------------------------------------------------
ROM:0002A7BA
ROM:0002A7BA loc_2A7BA:                              ! CODE XREF: sub_2A725+82 j
ROM:0002A7BA                                         ! sub_2A725+8A j
ROM:0002A7BA                 mov     0xA, D0
ROM:0002A7BC                 movbu   D0, (word_5D8)
ROM:0002A7BF
ROM:0002A7BF loc_2A7BF:                              ! CODE XREF: sub_2A725+7B j
ROM:0002A7BF                 clr     D0
ROM:0002A7C0
ROM:0002A7C0 locret_2A7C0:                           ! CODE XREF: sub_2A725+93 j
ROM:0002A7C0                 ret     [], 4

I think that it is a transfer length field of some kind. This is because the command fails with an error if it is > 0x60000 but completes with no error (although it doesn't jump to RAM) if this value is zero. This is the specified behaviour for many ATAPI transfer length fields, a zero length transfer request is not to be considered an error. But what is to be transfered? Perhaps the custom code uploaded to RAM is supposed to set up this transfer itself to provide completely custom debug data? Who knows? If the drive is expecting a transfer of data after jumping to RAM, then this could explain the hang.

Fortunately there's more than one way to skin this particular cat. If you take another look at my top level Mode Select(10) pseudo-code, then you'll see that it's possible to jump to a routine at 0x80000000 using Mode Select(10). This requires (59E) bit 3 to be set before issuing a Mode Select(10) command with a couple of reserved bytes set to 'H' and 'L' in the ATAPI packet. Both of these things are acheivable. Once our RAM routine has returned, then the Mode Select(10) handler continues to execute, but because (59E) bit 3 is set this time, the execution takes us to the subroutine at 0x90026FC7 instead of the normal one at 0x900265C5 (see the pseudo-code). Here's the start of the routine at 0x90026FC7

ROM:00026FC7                 movm    [D2,D3,A2,A3], (SP)
ROM:00026FC9                 add     0xF0, SP ! '­'
ROM:00026FCC                 btst    0x10, (unk_5A5) ; is (5A5) bit 4 set?
ROM:00026FD1                 bne     loc_26FDB       ; yes, so continue
ROM:00026FD3                 mov     0xB, D0         ; no, so bail out with error
ROM:00026FD5                 movbu   D0, (word_5D8)
ROM:00026FD8                 jmp     loc_27093
... snip ...
ROM:00027093 loc_27093:                              ! CODE XREF: ROM:00026FD8 j
ROM:00027093                                         ! ROM:00026FF9 j ...
ROM:00027093                 mov     4, D0
ROM:00027095                 movbu   D0, (word_6D8+1)
ROM:00027098                 ret     [D2,D3,A2,A3], 0x20 ! ' '

Because (5A5) bit 4 isn't set under normal conditions, the Mode Select(10) call will fail. Fortunately, this is after it has executed the routine in RAM, so it doesn't matter.

The new plan:
1. Put the drive into modeB using the Hitachi 0xE7 command that sets (598) bit 6.
2. Upload our code (which, when executed, will write to anywhere we like) one byte at a time using the 0xE7 RAM poke command.
3. Set (59E) bit 3 using Mode Select(10) as described above.
4. Force the drive to jump to our code using a Mode Select(10) command with 'H' and 'L' set at packet[3] and packet[4]. This will execute the routine in RAM and then fail as described above.

This time it worked! The following code is a Linux app that will peek or poke any byte in the MN103 address space.

/*
 * Reads or writes a single byte from/to
 * anywhere within the address space of
 * the MN103 microcontroller inside of
 * the Hitachi-LG Xbox360 drive.
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 7th March 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define USAGE "usage: pp device address_in_hex peek\n       pp device address_in_hex poke value_in_hex\n\n"
#define MODE_PEEK 0
#define MODE_POKE 1

/*
mov 0x000000??,D1
movbu D1,(0x????????)
rets
*/
unsigned char code[] = {0xFC,0xCD,0x00,0x00,0x00,0x00,0xFC,0x86,0x00,0x00,0x00,0x00,0xF0,0xFC};

int hex_atoi(void *dest, char *src, int dest_len)
{
	int src_last, i, j;
	unsigned char val;

	memset(dest, 0, dest_len);

	src_last = strlen(src) - 1;

	for(i = src_last; i >= 0; i--) {
		if(src[i] >= '0' && src[i] <= '9')
			val = src[i] - 0x30;
		else if(src[i] >= 'a' && src[i] <= 'f')
			val = (src[i] - 0x60) + 9;
		else if(src[i] >= 'A' && src[i] <= 'F')
			val = (src[i] - 0x40) + 9;
		else
			return 1; // invalid hex digit

		j = src_last - i;

		if(j & 1)
			val <<= 4;

		j >>= 1;

		if(j >= dest_len || j < 0)
			break;

		((unsigned char *)dest)[j] |= val;
	}
	
	return 0;
}

int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	unsigned int addr, mode, i;
	unsigned char val, param_list[8];

	if(argc < 4) {
		printf(USAGE);
		return 1;
	}

	if(hex_atoi(&addr, argv[2], sizeof(addr))) {
		printf("error: invalid address %s\n", argv[2]);
		printf(USAGE);
		return 1;
	}

	if(!strcmp(argv[3], "peek")) {
		mode = MODE_PEEK;
		val = 0;
	}
	else if(!strcmp(argv[3], "poke")) {
		if(argc < 5) {
			printf(USAGE);
			return 1;
		}
		mode = MODE_POKE;
		if(hex_atoi(&val, argv[4], sizeof(val))) {
			printf("error: invalid poke value %s\n", argv[4]);
			printf(USAGE);
			return 1;
		}
	}
	else {
		printf("error: invalid mode %s\n", argv[3]);
		printf(USAGE);
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		perror("open");
		return 1;
	}

	if(mode == MODE_PEEK) {
		// Hitachi read memory command
		memset(cgc.cmd, 0, sizeof(cgc.cmd));
		cgc.cmd[0] = 0xE7;
		cgc.cmd[1] = 0x48;
		cgc.cmd[2] = 0x49;
		cgc.cmd[3] = 0x54;
		cgc.cmd[4] = 0x01;
		cgc.cmd[6] = (unsigned char)((addr & 0xFF000000) >> 24); // address MSB
		cgc.cmd[7] = (unsigned char)((addr & 0x00FF0000) >> 16); // address
		cgc.cmd[8] = (unsigned char)((addr & 0x0000FF00) >> 8); // address
		cgc.cmd[9] = (unsigned char)(addr & 0x000000FF); // address LSB
		cgc.cmd[11] = 1; // length LSB

		cgc.buffer = &val;
		cgc.buflen = 1;
		cgc.sense = &sense;
		cgc.data_direction = CGC_DATA_READ;
		cgc.timeout = 15000;

		if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
			perror("ioctl");
			printf("Hitachi read memory command failed (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
		}
		else
			printf("0x%02X\n", val);
	}
	else { // mode == MODE_POKE
		// initiate modeB
		memset(&cgc, 0, sizeof(cgc));
		cgc.cmd[0] = 0xE7;
		cgc.cmd[1] = 0x48;
		cgc.cmd[2] = 0x49;
		cgc.cmd[3] = 0x54;
		cgc.cmd[4] = 0x30;
		cgc.cmd[5] = 0x90;
		cgc.cmd[6] = 0x90;
		cgc.cmd[7] = 0xD0;
		cgc.cmd[8] = 0x01;
		cgc.sense = &sense;
		cgc.data_direction = CGC_DATA_NONE;
		cgc.timeout = 15000;
		if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
			perror("ioctl");
			printf("failed to initiate modeB (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
		}
		else {
			*((unsigned int *)&code[8]) = addr;
			*((unsigned int *)&code[2]) = val;

			// upload code a byte at a time using RAM poke command
			for(i = 0; i < sizeof(code); i++) {
				// Hitachi poke RAM command
				memset(cgc.cmd, 0, sizeof(cgc.cmd));
				cgc.cmd[0] = 0xE7;
				cgc.cmd[1] = 0x48;
				cgc.cmd[2] = 0x49;
				cgc.cmd[3] = 0x54;
				cgc.cmd[4] = 0xCC;
				cgc.cmd[5] = code[i];
				cgc.cmd[8] = (unsigned char)(((0x80000000 + i) & 0xFF000000) >> 24); // address MSB
				cgc.cmd[9] = (unsigned char)(((0x80000000 + i) & 0x00FF0000) >> 16); // address
				cgc.cmd[10] = (unsigned char)(((0x80000000 + i) & 0x0000FF00) >> 8); // address
				cgc.cmd[11] = (unsigned char)((0x80000000 + i) & 0x000000FF); // address LSB
				cgc.sense = &sense;
				cgc.data_direction = CGC_DATA_NONE;
				cgc.timeout = 15000;
				if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
					perror("ioctl");
					printf("Hitachi poke RAM command failed on byte %u (sense: %02X/%02X/%02X)\n", i, sense.sense_key, sense.asc, sense.ascq);
					close(fd);
					return 1;
				}
			}

			// set (59E) bit 3 via Mode Select(10)
			memset(cgc.cmd, 0, sizeof(cgc.cmd));
			cgc.cmd[0] = 0x55;
			cgc.cmd[1] = 0x10;
			cgc.cmd[8] = 0x08;
			memset(param_list, 0, sizeof(param_list));
			param_list[1] = 6;
			cgc.buffer = param_list;
			cgc.buflen = 8;
			cgc.sense = &sense;
			cgc.data_direction = CGC_DATA_WRITE;
			cgc.timeout = 15000;
			if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
				perror("ioctl");
				printf("set (59E) bit 3 via Mode Select(10) failed (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
			}
			else {
				// jump to RAM routine via Mode Select(10)
				memset(cgc.cmd, 0, sizeof(cgc.cmd));
				cgc.cmd[0] = 0x55;
				cgc.cmd[1] = 0x10;
				cgc.cmd[3] = 0x48; // 'H'
				cgc.cmd[4] = 0x4C; // 'L'
				cgc.cmd[8] = 0x08;
				memset(param_list, 0, sizeof(param_list));
				param_list[1] = 6;
				cgc.buffer = param_list;
				cgc.buflen = 8;
				cgc.sense = &sense;
				cgc.data_direction = CGC_DATA_WRITE;
				cgc.timeout = 15000;
				if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1)
					printf("done\n");
				else
					printf("this shouldn't happen\n");
			}
		}
	}

	close(fd);

	printf("\n");

	return 0;
}

download

Another useful tool. The following program will upload and execute any piece of MN103 code you supply.

/*
 * uploads any given MN103 code to
 * the Hitachi-LG Xbox360 drive and
 * causes the drive to execute it.
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 7th March 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define USAGE "usage: execcode device binary_code_file\n\n"

int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	unsigned int i, len;
	unsigned char param_list[8];
	FILE *fptr;

	if(argc < 3) {
		printf(USAGE);
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		perror("open");
		return 1;
	}

	if(!(fptr = fopen(argv[2], "rb"))) {
		close(fd);
		perror("fopen");
		return 1;
	}

	// initiate modeB
	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = 0xE7;
	cgc.cmd[1] = 0x48;
	cgc.cmd[2] = 0x49;
	cgc.cmd[3] = 0x54;
	cgc.cmd[4] = 0x30;
	cgc.cmd[5] = 0x90;
	cgc.cmd[6] = 0x90;
	cgc.cmd[7] = 0xD0;
	cgc.cmd[8] = 0x01;
	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_NONE;
	cgc.timeout = 15000;
	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("failed to initiate modeB (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else {
		if(fseek(fptr, 0, SEEK_END) == -1) {
			close(fd);
			fclose(fptr);
			perror("fseek");
			return 1;
		}
		if((len = ftell(fptr)) == -1) {
			close(fd);
			fclose(fptr);
			perror("ftell");
			return 1;
		}
		if(fseek(fptr, 0, SEEK_SET) == -1) {
			close(fd);
			fclose(fptr);
			perror("fseek");
			return 1;
		}
		// upload code a byte at a time using RAM poke command
		for(i = 0; i < len; i++) {
			// Hitachi poke RAM command
			memset(cgc.cmd, 0, sizeof(cgc.cmd));
			cgc.cmd[0] = 0xE7;
			cgc.cmd[1] = 0x48;
			cgc.cmd[2] = 0x49;
			cgc.cmd[3] = 0x54;
			cgc.cmd[4] = 0xCC;
			cgc.cmd[5] = (unsigned char)getc(fptr);
			cgc.cmd[8] = (unsigned char)(((0x80000000 + i) & 0xFF000000) >> 24); // address MSB
			cgc.cmd[9] = (unsigned char)(((0x80000000 + i) & 0x00FF0000) >> 16); // address
			cgc.cmd[10] = (unsigned char)(((0x80000000 + i) & 0x0000FF00) >> 8); // address
			cgc.cmd[11] = (unsigned char)((0x80000000 + i) & 0x000000FF); // address LSB
			cgc.sense = &sense;
			cgc.data_direction = CGC_DATA_NONE;
			cgc.timeout = 15000;
			if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
				perror("ioctl");
				printf("Hitachi poke RAM command failed on byte %u (sense: %02X/%02X/%02X)\n", i, sense.sense_key, sense.asc, sense.ascq);
				close(fd);
				fclose(fptr);
				return 1;
			}
		}

		// set (59E) bit 3 via Mode Select(10)
		memset(cgc.cmd, 0, sizeof(cgc.cmd));
		cgc.cmd[0] = 0x55;
		cgc.cmd[1] = 0x10;
		cgc.cmd[8] = 0x08;
		memset(param_list, 0, sizeof(param_list));
		param_list[1] = 6;
		cgc.buffer = param_list;
		cgc.buflen = 8;
		cgc.sense = &sense;
		cgc.data_direction = CGC_DATA_WRITE;
		cgc.timeout = 15000;
		if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
			perror("ioctl");
			printf("set (59E) bit 3 via Mode Select(10) failed (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
		}
		else {
			// jump to RAM routine via Mode Select(10)
			memset(cgc.cmd, 0, sizeof(cgc.cmd));
			cgc.cmd[0] = 0x55;
			cgc.cmd[1] = 0x10;
			cgc.cmd[3] = 0x48; // 'H'
			cgc.cmd[4] = 0x4C; // 'L'
			cgc.cmd[8] = 0x08;
			memset(param_list, 0, sizeof(param_list));
			param_list[1] = 6;
			cgc.buffer = param_list;
			cgc.buflen = 8;
			cgc.sense = &sense;
			cgc.data_direction = CGC_DATA_WRITE;
			cgc.timeout = 15000;
			if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1)
				printf("done\n");
			else
				printf("this shouldn't happen\n");
		}
	}

	close(fd);
	fclose(fptr);

	printf("\n");

	return 0;
}

download

WARNING: At the time of writing, both of the above programs are criminally under-tested. The former has only been tested with a few pokes followed by peeks to RAM and a couple of lower memory locations like (5A5). The latter program has only been tested once with a very simple (14 byte) write-to-RAM routine. Also, I do not know enough about all of the drive's state flags to be sure that these programs will leave the drive in a stable state.

The next step is to use the above programs to confirm/deny my speculation regarding tray_status and potential I/O port registers (D98C) and (D98D)...

Update: 9th March 2006

I was nearly right about registers (D98C) and (D98D).

(D98C) bit 0 = tray_status output (1 = +3.3V, 0 = 0V)
(D98C) bit 4 = tray_status direction (1 = input, 0 = output)
(D98D) bit 4 = tray_status input (1 = +3.3V, 0 = 0V)

Examples using the peek poke program that I posted in yesterday's update.

tray_status as an input:
# ./pp /dev/hdc D98C peek
0x20
# ./pp /dev/hdc D98C poke 30
done
# ./pp /dev/hdc D98C peek
0x30
(I pulled tray_status high here)
# ./pp /dev/hdc D98D peek
0x30
(I pulled tray_status low here)
# ./pp /dev/hdc D98D peek
0x20
tray_status as an output:
# ./pp /dev/hdc D98C peek
0x30
# ./pp /dev/hdc D98C poke 20
done
# ./pp /dev/hdc D98C peek
0x20
# ./pp /dev/hdc D98C poke 21
done
(tray_status was measured at +3.3V here)
# ./pp /dev/hdc D98C poke 20
done
(tray_status was measured at 0V here)

So that's the tray_status/modeB mystery finally solved. Now I need a new project.

Update: 15th March 2006

I've had a request from a fellow hacker to port my pp.c and codeexec.c tools to windows. So here they are. I've also ported my memdump.c tool and modified both the windows and linux versions to accept hexadecimal input parameters (the old decimal memdump was annoying).

Note that these tools work via your operating system's driver. So you'll need to get your OS to detect the drive before they will work (Linux users: simply ejecting and closing the tray during startup should get it detected. windows users: If you are using a PATA - SATA bridge board or SATA contoller with a legacy mode, then you can use my modeb_win tool to get your drive detected (see above)).

I can write versions of the following programs that do not require the OS to detect the drive. However, these will require a PATA - SATA bridge board or a legacy mode SATA controller. If demand is high enough or a hacker who has contributed significantly to the community wants them (as was the case with these Windows ports), then I'll code 'em. Otherwise I won't bother, I'm just that lazy.

memdump.c - hex memdump source for Linux
memdump_win.zip - hex memdump binary for Win2000/XP
memdump_win_src.zip - hex memdump source for Win2000/XP

pp_win.zip - peek/poke binary for Win2000/XP
pp_win_src.zip - peek/poke source for Win2000/XP

execcode_win.zip - execcode binary for Win2000/XP
execcode_win_src.zip - execcode source for Win2000/XP

And finally, I've written a new version of my modeb_win util. This version uses the windows driver instead of writing directly to hardware like the old version. This version is a safer and more stable way to put the drive into modeB. However, unlike the previous version, this one requires that windows has detected the drive (so it's not terribly useful).

modeb_win2.zip - modeb_win2 binary for Win2000/XP
modeb_win2_src.zip - modeb_win2 source for Win2000/XP

These apps are even less tested than their linux counterparts. Let me know if you find bugs (fixes would be nice too). Email is at the botom of the page, or catch me on the xbh.net forums and send me a PM.

Update: 4th April 2006

I've been working on a method to flash the Hitachi-LG DVD firmware in software from a PC. The first thing to do is reverse engineer an existing LG flasher application for a similar drive. The application that I have chosen to disassemble is The Dangerous Bothers software flasher for the LG GDR-8164B drive. This flasher was produced (by The Dangerous Brothers) by modifying an official LG flasher, so it will do nicely for my analysis. Since the GDR-8164B (the drive the flasher was designed for) and the GDR-3120L (the Hitachi-LG drive in the 360) are so similar, It is hoped that this app can be modified to work on the 3120L drive by simply replacing the firmware embedded into the .exe with our own 3120L firmware, changing the checksums and changing the drive identification strings (the strings that the application uses to make sure it's flashing the correct type of drive).

Halfway through my own analysis a hacker named MacDennis produced such a modified flasher and I tested it on my 3120L drive. It didn't work. I spent the next day continuing to reverse engineering the flasher app to see what was going on and to find out where it was failing.

The LG flasher .exe performs most of its work in response to WM_TIMER messages within it's progress bar dialog. Each pass through the WM_TIMER handler at 0x401A7D executes a subroutine from a vector table (at 0x41E01C). The table is indexed by a timer counter (TC) maintained at 0x499878. I call the routines in the vector table 'TC handlers'. The following gives an overview of the operations performed by each TC handler. They are executed in order.

Before TC 0
===========
Sends ATAPI commands:
12 00 00 00 3A 00 00 00 00 00 00 00 (Inquiry, to identify all connected drives)

Sends ATAPI command:
E7 48 49 54 30 90 90 E0 00 00 00 00 (???, does not appear to be supported in the 3120L firmware)

Sends ATAPI command:
1B 00 00 00 00 00 00 00 00 00 00 00 (Start/Stop Unit, to stop the disc)

TC 0
====
There is an entry in the vector table, but it doesn't seem to be called by the WM_TIMER handler.

TC 1
====
Displays "Begin to check the target device status"

TC 2
====
Sends ATAPI command:
5A 00 0E 00 00 00 00 00 18 00 00 00 (mode sense)
Mode parameter list:
00 16 70 00 00 00 00 00 02 29 DF DE 80 04 F8 E0 01 F8 92 04 00 F8 92 05

TC 3
====
Sends ATAPI command:
00 00 00 00 00 00 00 00 00 00 00 00

Displayes "The target device status was OK."

TC 4
====
Displays "Start the preparation for the flashup."

Sends ATAPI command:
55 10 00 00 00 00 00 00 10 00 00 01 (mode select)
Mode parameter list:
00 00 00 00 00 00 00 00 00 06 48 4C 00 00 1B E8


TC 5
====
Creates a new thread to execute code at 0x401005 which sends the MN103 flasher code at 498938 to the drive 0x7F8 bytes at a time using a loop of the following ATAPI command

loop
55 10 00 00 00 00 00 08 00 00 00 01 (mode select)
Mode parameter list:
00 00 00 00 00 48 4C 00
followed by a 0x7F8 byte chunk of the MN103 flasher code
end loop

TC 6
====
Displays "start the test flashup."

Creates new thread to execute code at 0x4010F5 which attempts to force the drive to execute the MN103 flasher code using the following ATAPI command (incidently, this is the same execution vector that I used in my execcode.c app)
55 10 00 48 4C 00 01 00 00 00 00 01 (mode select)

Next it uploads the firmware at 0x420A98 to the drive (which is now running the MN103 flasher code) 0x800 bytes at a time using a loop of the following commands.

loop
3B 04 7F 48 4C 07 01 08 00 00 00 01 (write buffer)
followed by an 0x800 byte chunk of the firmware

5A 10 00 48 4C 08 00 00 12 00 00 01 (mode sense)
Mode parameter list:
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 
end loop

TC 7
====
displays "Start the flashup."
starts a thread to execute code at 0x401118 which attempts to upload the firmware at 0x420A98 using a loop of the following commands

loop
3B 04 00 48 4C 07 01 08 00 00 00 00 (write buffer)
followed by an 0x800 byte chunk of the firmware

5A 10 00 48 4C 08 00 00 12 00 00 00 (mode sense)
Mode parameter list:
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 
end loop

After the loop it sends the following ATAPI command an undetermined number of times (possibly just once)
5A 10 00 48 4Ch 09 00 00 00 00 00 00 (mode sense)

Finally it sends.
12 00 00 00 3A 00 00 00 00 00 00 00 (Inquiry, to test the new firmware?)
Recieves 0x3A byte of Inquiry data.

displays "The flashup... end."

TC 8
====
Does nothing. Possibly never executed.

Here's a derived ATAPI log.

ATAPI log (not observed, derived from disassembly)
==================================================
12 00 00 00 3A 00 00 00 00 00 00 00

E7 48 49 54 30 90 90 E0 00 00 00 00

1B 00 00 00 00 00 00 00 00 00 00 00

5A 00 0E 00 00 00 00 00 18 00 00 00
    00 16 70 00 00 00 00 00
    02 29 DF DE 80 04 F8 E0
    01 F8 92 04 00 F8 92 05

00 00 00 00 00 00 00 00 00 00 00 00

55 10 00 00 00 00 00 00 10 00 00 01
    00 00 00 00 00 00 00 00
    00 06 48 4C 00 00 1B E8

55 10 00 00 00 00 00 08 00 00 00 01
    00 00 00 00 48 4C 00 00
    a chunk of the flasher

55 10 00 00 00 00 00 08 00 00 00 01
    00 00 00 00 48 4C 00 00
    a chunk of the flasher

55 10 00 00 00 00 00 08 00 00 00 01
    00 00 00 00 48 4C 00 00
    a chunk of the flasher

55 10 00 00 00 00 00 04 08 00 00 01
    00 00 00 00 48 4C 00 00
    final chunk of the flasher

55 10 00 48 4C 00 01 00 00 00 00 01

3B 04 7F 48 4C 07 01 08 00 00 00 01
    a chunk of firmware

5A 10 00 48 4C 08 00 00 12 00 00 01
    ?? ?? ?? ?? ?? ?? ?? ??
    ?? ?? ?? ?? ?? ?? ?? ??
    ?? ??

... *snip* the above two commands sent 0x80 times in total

3B 04 00 48 4C 07 01 08 00 00 00 00
    a chunk of the firmware

5A 10 00 48 4C 08 00 00 12 00 00 00
    ?? ?? ?? ?? ?? ?? ?? ??
    ?? ?? ?? ?? ?? ?? ?? ??
    ?? ??

... *snip* above two commands sent 0x80 times in total

5A 10 00 48 4Ch 09 00 00 00 00 00 00

... *snip* the above command is possibly send more than once (not sure)

12 00 00 00 3A 00 00 00 00 00 00 00

The drive dies at the start of TC 6 after attempting to executing the MN103 flasher on the drive. Here's the flasher disassembly at this point. This is what all the ATAPI calls look like in the flasher app.

.text:00402B79 push    offset unk_499198  ! unknown arg
.text:00402B7E push    0                  ! ATAPI transfer length
.text:00402B80 lea     ecx, [ebp-0Ch]
.text:00402B83 push    ecx                ! pointer to transfer buffer
.text:00402B84 push    2                  ! transfer direction (0 = write, 1 = read, 2 = none)
.text:00402B86 push    offset unk_484150  ! ATAPI packet
.text:00402B8B mov     dl, byte_49947C
.text:00402B91 push    edx                ! unknown arg
.text:00402B92 call    sub_40110E         ! send ATAPI command function

The function returns (after a timeout I'm guessing) but the drive is dead, this causes the FW transfer loop to immediately drop out after 5 retries (because the drive is dead and not responding to commands I presume). Looks like a runaway %eip in the drive. At first I thought the code was failing to upload, but I put a breakpoint just before the call to sub_40110E above and dumped the drive's RAM, the flasher code was there and completely intact. Then I stepped through sub_40110E and the drive immediately died.

The MN103 code from the official LG flasher app does not work on the 360 drive, almost certainly because of low level interface differences between the PATA drive that the flasher was designed for and the SATA 360 drive. MacDennis has identified some critical low level differences which confirm that the flasher code *will not* work with the Hitachi-LG 3120L 360 drive.

This is a problem.

The flasher app has revealed a way to bulk upload code. Up until now I've been uploading code to the drive a byte at a time which is very slow. Looking at TC 5 we can see that the flasher app uses a series of Mode Select commands to bulk upload data in blocks of up to 0x7F8 bytes. Looking at my derived ATAPI log, we can see from the Parameter List Length fields in the Mode Select packets are 0x800, 0x800, 0x800 and 0x408. These lengths are 8 bytes larger than the actual data blocks because, as can be seen from the log, the command transmits a fixed eight bytes in front of each data block. That makes the length of the flasher code ((0x800 - 8) * 3) + (0x408 - 8) = 0x1BE8 bytes in total. If you take a look at the ATAPI command in TC 4, you'll notice that this value appears at the end of the Mode Parameter List of that Mode Select command. It looks like the command at TC4 sets up the transfer.

I've traced the 3120L Mode Select handler code relevant to the Mode Select command in TC 4. Here's the end result. Note: This command, and hence the entire upload procedure, requires modeB or it fails before reaching this code.

ROM:00026C42                 bset    8, (word_59E)   ; set (59E) bit 3
ROM:00026C47                 mov     0x648, A0       ; mode parameter list starts at 0x63C, so 0x648 is the address of our 32-bit 0x00001BE8 value (the transfer length)
ROM:00026C4A                 call    sub_33A84, [], 8 ; convert transfer length to little endian
ROM:00026C51                 mov     D0, (word_928)  ; (928) = transfer length
ROM:00026C54                 mov     D0, D1
ROM:00026C55                 lsr     0xB, D1
ROM:00026C58                 movbu   D1, (word_8CE)  ; (8CE) = length / 0x800 (number of blocks)
ROM:00026C5B                 btst    0x7FF, D0       ; any remainder (incomplete trailing block)?
ROM:00026C5F                 beq     loc_26C68       ; no, continue
ROM:00026C61                 movbu   (word_8CE), D0
ROM:00026C64                 inc     D0
ROM:00026C65                 movbu   D0, (word_8CE)  ; yes, so add one to number of blocks
ROM:00026C68 loc_26C68:                              ! CODE XREF: sub_26C3B+24j
ROM:00026C68                 clr     D0
ROM:00026C69                 movbu   D0, (word_8CE+1) ; zero the 2nd least significant byte of the number of blocks
ROM:00026C6C                 ret     [D2], 8

This command sets (59E) bit 3 and sets up low memory locations (928) and (8CE) using the 32-bit transfer length field supplied in the Mode Parameter List. It zeros the 2nd least significant byte at (8CE), which is the number of blocks. This suggests an upper transfer limit of 0xFF blocks (0xFF * 0x7F8 = 0x7F008 bytes), which is a lot bigger than RAM anyway, so it should never be a problem. Now let's trace the Mode Select handler code relevant to commnds in TC 5 (the commands that actually transfer the data). Remember that (59E) bit 3 is now set, this takes us on a different route through the top level of the Mode Select handler (see the pseudo-code I posted in the 8th March 2006 update). The bulk of the handler code for this command is shown below.

ROM:00026FFC loc_26FFC:                              ! CODE XREF: ROM:00026FEDj
ROM:00026FFC                 movbu   (word_8CE+1), D0 ; D0 = block counter set to zero in TC 4 (tracks how many blocks transfered in previous commands)
ROM:00026FFF                 movhu   (word_8D0), D2   ; D2 = Parameter List Length
ROM:00027002                 mov     0x7F8, D1
ROM:00027005                 mul     D1, D0           ; D0 = block counter * 0x7F8
ROM:00027007                 add     0xF8, D2 ! '°'  
ROM:00027009                 exthu   D2
ROM:0002700A                 mov     D2, A0           ; A0 = Parameter List Length - 8 (remove the 8 bytes in front of our data)
ROM:0002700C                 mov     unk_3CC08, D1    ; D1 points to our data block in RAM
ROM:00027012                 call    sub_33E38, [D2,D3,A2,A3], 0x18 ; copy the amount of data specified in the Parameter List Length field (minus 8) to 0x8000000 + (block index * 0x7F8)
ROM:00027019                 movbu   (word_8CE+1), D0
ROM:0002701C                 inc     D0
ROM:0002701D                 movbu   D0, (word_8CE+1) ; increment block counter
ROM:00027020                 movbu   (word_8CE), D1
ROM:00027023                 extbu   D0
ROM:00027024                 cmp     D0, D1           ; Have we just transfered the last block?
ROM:00027025                 bne     loc_27093        ; no, so return
ROM:00027027                 mov     SP, A0           ; yes, so calculate final checksum
ROM:00027028                 add     0xC, A0
ROM:0002702A                 mov     A0, (4,SP)
ROM:0002702C                 call    sub_64E4, [], 0  ; save CPU state
ROM:00027033                 and     0xFDFF, PSW      ; set interupt mask level
ROM:00027037                 nop
ROM:00027038                 nop
ROM:00027039                 mov     (word_928), D0   ; D0 = total tranfer length (set in TC 4)
ROM:0002703C                 mov     2, D1
ROM:0002703E                 sub     D1, D0           ; subtract length of checsum value (16-bit)
ROM:00027040                 mov     SP, A0
ROM:00027041                 add     0xE, A0
ROM:00027043                 call    sub_33BDC, [D2,D3,A2,A3], 0x18 ; get 16-bit checksum onto stack
ROM:0002704A                 mov     (word_928), D2   ; D0 = total tranfer length (set in TC 4)
ROM:0002704D                 mov     2, D0
ROM:0002704F                 sub     D0, D2           ; subtract length of checksum value (16-bit) from total length to get offset checksum value
ROM:00027051                 mov     D2, A2
ROM:00027053                 clr     D2
ROM:00027054                 clr     D0
ROM:00027055                 movbu   D0, (8,SP)
ROM:00027058                 clr     D3
ROM:00027059                 mov     SP, A3
ROM:0002705A                 add     8, A3
ROM:0002705C                 bra     loc_27073        ; enter checksum calculation loop
ROM:0002705E ! ---------------------------------------------------------------------------
ROM:0002705E
ROM:0002705E loc_2705E:                              ! CODE XREF: ROM:00027075j
ROM:0002705E                 mov     D3, D0
ROM:0002705F                 mov     A3, A0
ROM:00027060                 mov     1, D1
ROM:00027062                 call    sub_33BDC, [D2,D3,A2,A3], 0x18 ; get one byte from data
ROM:00027069                 movbu   (8,SP), D0
ROM:0002706C                 exthu   D2
ROM:0002706D                 add     D0, D2 ; sum it to total
ROM:0002706E                 inc     D3
ROM:0002706F                 mov     1, D0
ROM:00027071                 sub     D0, A2
ROM:00027073
ROM:00027073 loc_27073:                              ! CODE XREF: ROM:0002705Cj
ROM:00027073                 cmp     0, A2           ; finished calculatin checksum?
ROM:00027075                 bne     loc_2705E       ; no, loop until checksum calculated
ROM:00027077                 mov     (4,SP), A0
ROM:00027079                 call    sub_64F0, [], 0 ; restore CPU state
ROM:00027080                 movhu   (0xE,SP), D1
ROM:00027083                 add     D1, D2
ROM:00027084                 exthu   D2
ROM:00027085                 cmp     0, D2
ROM:00027087                 beq     loc_27093       ; checksum OK, return normally
ROM:00027089                 mov     7, D0           ; checksum failed, fail
ROM:0002708B                 movbu   D0, (word_5D8)
ROM:0002708E                 bclr    8, (word_59E)   ; this flag will prevent corrupt code from being executed
ROM:00027093
ROM:00027093 loc_27093:                              ! CODE XREF: ROM:00026FD8j
ROM:00027093                                         ! ROM:00026FF9j ...
ROM:00027093                 mov     4, D0
ROM:00027095                 movbu   D0, (word_6D8+1)
ROM:00027098                 ret     [D2,D3,A2,A3], 0x20 ! ' '

We can see that we must provide a correct 16-bit checksum value immediately after the data that we upload. The checksum length (2 bytes) must be included in the length field of the transfer prepation Mode Select command at TC 4. If the checksum doesn't match then it prevents execution of the uploaded code by clearing (59E) bit 3. Also, it is my experience that the total transfer length must be aligned on a 4 byte boundary or it fails to upload the last block of data.

Once the code is uploaded to RAM, the flasher executes it with yet another Mode Select command (at the start of TC 6). This command is the same (more or less) as the one that I used in my execcode app. This is the complete procedure.

1) Send the following ATAPI command to prepare the transfer.
Where LL LL LL LL is the total length of the code (including
a trailing 2 byte checksum), this field is big endian.

Packet:
55 10 00 00 00 00 00 00 10 00 00 01
Mode Parameter List:
00 00 00 00 00 00 00 00
00 06 48 4C LL LL LL LL

2) Send as many of the following command as necessary to
transfer your data. Where LL LL is the length of the code
chunk + 8. This field is big endian.

Packet:
55 10 00 00 00 00 00 LL LL 00 00 01
Mode Parameter List:
00 00 00 00 48 4C 00 00
an (0xLLLL - 8) byte chunk of the flasher

3) Execute the code using the following command.

Packet:
55 10 00 48 4C 00 01 00 00 00 00 01

I have rewritten execcode to use this new bulk upload procedure, it will now be a lot faster when uploading large amounts of code. Another problem with the old execcode was that it would crash the drive if there was a disc inserted. You can see from my ATAPI 'log' that the LG flasher stops the disc using a START/STOP UNIT command (the 3rd command in the log). I added this command to execcode, this prevents the crash. Here's the new one.

/*
 * uploads any given MN103 code to
 * the Hitachi-LG Xbox360 drive and
 * causes the drive to execute it.
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 7th March 2006 (last updated: 4th April 2006)
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define USAGE "usage: execcode device binary_code_file\n\n"

int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	unsigned int i, len, reallen, blocks, blklen, sum;
	unsigned char param_list[0x10], *buffer;
	FILE *fptr;
	static unsigned char txblk[0x800];

	if(argc < 3) {
		printf(USAGE);
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		perror("open");
		return 1;
	}
	
	if(!(fptr = fopen(argv[2], "rb"))) {
		perror("fopen");
		close(fd);
		return 1;
	}

	if(fseek(fptr, 0, SEEK_END) == -1) {
		perror("fseek(SEEK_END)");
		close(fd);
		fclose(fptr);
		return 1;
	}
	if((len = ftell(fptr)) == -1) {
		perror("ftell");
		close(fd);
		fclose(fptr);
		return 1;
	}
	if(fseek(fptr, 0, SEEK_SET) == -1) {
		perror("fseek(SEEK_SET)");
		close(fd);
		fclose(fptr);
		return 1;
	}

	reallen = len;
	len += 2; // for checksum

	if(len & 0x03) // pad to nearest 4 byte boundary
		len += 4 - (len & 0x03);

	if(!(buffer = (unsigned char *)malloc(len))) {
		printf("malloc() failed\n");
		close(fd);
		fclose(fptr);
		return 1;
	}

	memset(buffer, 0, len);

	if(fread(buffer, reallen, 1, fptr) < 1)
		printf("fread() failed\n");
	else {
		// Calculate checksum
		for(sum = 0, i = 0; i < reallen; i++)
			sum += buffer[i];
		sum = 0x10000 - (sum & 0x0000FFFF);
		buffer[len - 2] = (unsigned char)(sum & 0x00FF);
		buffer[len - 1] = (unsigned char)((sum & 0xFF00) >> 8);				

		// initiate modeB
		memset(&cgc, 0, sizeof(cgc));
		cgc.cmd[0] = 0xE7;
		cgc.cmd[1] = 0x48;
		cgc.cmd[2] = 0x49;
		cgc.cmd[3] = 0x54;
		cgc.cmd[4] = 0x30;
		cgc.cmd[5] = 0x90;
		cgc.cmd[6] = 0x90;
		cgc.cmd[7] = 0xD0;
		cgc.cmd[8] = 0x01;

		cgc.sense = &sense;
		cgc.data_direction = CGC_DATA_NONE;
		cgc.timeout = 15000;
		
		if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
			perror("ioctl");
			printf("failed to initiate modeB (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
		}		
		else {
			// Stop the disc
			memset(&cgc, 0, sizeof(cgc));
			cgc.cmd[0] = 0x1B;
	
			cgc.sense = &sense;
			cgc.data_direction = CGC_DATA_NONE;
			cgc.timeout = 15000;
			
			if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1 && (sense.sense_key != 0x02 || sense.asc != 0x3A)) {
				perror("ioctl");
				printf("failed to stop the disc (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
			}
			else {
				// <hack>
				// Clear (59E) bit 3 if it's set (this bit will cause us problems if it's left set)
				memset(&cgc, 0, sizeof(cgc));
				cgc.cmd[0] = 0x55;
		
				cgc.sense = &sense;
				cgc.data_direction = CGC_DATA_NONE;
				cgc.timeout = 15000;
				
				ioctl(fd, CDROM_SEND_PACKET, &cgc);
				// </hack>

				// Configure upload (required to upload/execute code)
				memset(&cgc, 0, sizeof(cgc));
				cgc.cmd[0] = 0x55;
				cgc.cmd[1] = 0x10;
				cgc.cmd[8] = 0x10;
				cgc.cmd[11] = 0x01;
						
				memset(param_list, 0, sizeof(param_list));
				param_list[9] = 0x06;
				param_list[10] = 0x48;
				param_list[11] = 0x4C;
				param_list[12] = (unsigned char)((len & 0xFF000000) >> 24);
				param_list[13] = (unsigned char)((len & 0x00FF0000) >> 16);
				param_list[14] = (unsigned char)((len & 0x0000FF00) >> 8);
				param_list[15] = (unsigned char)(len & 0x000000FF);
					
				cgc.buffer = param_list;
				cgc.buflen = 0x10;
				cgc.sense = &sense;
				cgc.data_direction = CGC_DATA_WRITE;
				cgc.timeout = 15000;
				
				if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
					perror("ioctl");
					printf("failed to configure upload (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
				}
				else {
					// Upload our buffer in ~2K blocks
					blocks = len / 0x7F8;
					if(len % 0x7F8)
						blocks++;
					for(sum = 0, i = 0; i < blocks; i++) {
						if(i != blocks - 1)
							blklen = 0x800;
						else
							blklen = (len % 0x7F8) + 8;
			
						memset(&cgc, 0, sizeof(cgc));
						cgc.cmd[0] = 0x55;
						cgc.cmd[1] = 0x10;
						cgc.cmd[7] = (unsigned char)((blklen & 0xFF00) >> 8);
						cgc.cmd[8] = (unsigned char)(blklen & 0x00FF);
						cgc.cmd[11] = 0x01;
			
						memset(txblk, 0, sizeof(txblk));
						txblk[4] = 0x48;
						txblk[5] = 0x4C;
	
						memcpy(&txblk[8], &buffer[i * 0x7F8], blklen - 8);
	
						cgc.buffer = txblk;
						cgc.buflen = blklen;
						cgc.sense = &sense;
						cgc.data_direction = CGC_DATA_WRITE;
						cgc.timeout = 15000;
			
						if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
							perror("ioctl");
							printf("failed to upload buffer block %u (sense: %02X/%02X/%02X)\n", i, sense.sense_key, sense.asc, sense.ascq);
							close(fd);
							fclose(fptr);
							free(buffer);
							return 1;
						}
					}
		
					// Execute code
					memset(&cgc, 0, sizeof(cgc));
					cgc.cmd[0] = 0x55;
					cgc.cmd[1] = 0x10;
					cgc.cmd[3] = 0x48;
					cgc.cmd[4] = 0x4C;
					cgc.cmd[6] = 0x01;
					cgc.cmd[11] = 0x01;
		
					cgc.sense = &sense;
					cgc.data_direction = CGC_DATA_NONE;
					cgc.timeout = 15000;
			
					if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
						perror("ioctl");
						printf("failed to execute flasher (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
					}
					else
						printf("done\n");
				}
			}
		}
	}

	close(fd);
	fclose(fptr);
	free(buffer);

	printf("\n");

	return 0;
}

download

And, for the people yet to realise that it's consistantly the slowest, least secure and most expensive OS on the market, here are the windows versions ;-)

execcode2_win.zip - new execcode binary for Win2000/XP
execcode2_win_src.zip - new execcode source for Win2000/XP

Interestingly, there appears to be a bug in the 3120L firmware. When the handler code for the Mode Select command that prepares the transfer calculates the total number of blocks to be transfered, it divides by 0x800 (a right shift of 0x0B is the same as division by 0x800).

ROM:00026C54                 mov     D0, D1          ; D1 = transfer length
ROM:00026C55                 lsr     0xB, D1         ; D1 length / 0x800
ROM:00026C58                 movbu   D1, (word_8CE)  ; (8CE) = D1

However, the handler code for the Mode Select commands that transfer the data increments the block counter every 0x7F8 bytes. This means that a small number of transfer lengths will fail when they shouldn't. For example: Imagine you want to transfer 0x1000 bytes of data. The drive calculates the total number of blocks as 0x1000 / 0x800 = 2. You send the first 0x7F8 byte block, which works. Then you send the next 0x7F8 byte block. Now, you still have 0x10 bytes to send (including your checksum), but the drive sees that two blocks have been uploaded and so assumes that you've finished. It then tries to match the checksum to whatever 16-bit value happends to be in RAM at 0x80000000 + (0x1000 - 2) = 0x80000FFD. The sum will almost certainly fail and, even if it doesn't, you still have an incomplete code upload. I haven't tested this bug, but I'm sure it exists.

Update: 6th April 2006

Code exists in the 3120L firmware to erase and program the flash chip in the drive. This code is used to change the drive's unique AES key, the key that is used to de/encrypt data during disc authentication. In the firmware, these routines are loaded into RAM and then executed, which makes sense. If you reverse engineer the code that does this (0x9002F46F and 0x9002F4CC in the 0047 revision firmware) and the erase/program routines themselves, then you will find the following (again, for the 0047 firmware, the 0046 firmware may have different offsets)

Erase code block
================
Starts at: 0x9002F8B7
Length: 0x470
Input arguments:
(934) = address of sector to erase
Return values:
(92E) (0x00 = success, 0xFF = error)

Program code block
==================
Starts at: 0x9002FD29
Length: 0x3CC
Input arguments:
(934) = destination address in flash
(938) = source address
(92C) = length of source data
Return values:
(92E) (0x00 = success, 0xFF = error)

It will be possible to program single sectors in flash by contructing a buffer containing the 3120L erase/program code blocks, the new sector data and some control code. This buffer can be uploaded to RAM using the bulk upload procedure I mentioned in my last update and then the control code can be executed. The control code will have to be hand assembled and will use the erase/program routines to flash the sector. This isn't an ideal software flasher by a long way, but it will do as an interim solution.

There is one problem with this plan. The erase and program routines have hard coded writes to RAM locations 0x80001000, 0x80001004, 0x80001020, 0x80001024, 0x80001028 and 0x8000102C. The length of my upload buffer is going to be about 0x470 + 0x3CC + 0x1000 + 2 = 0x183E. This means that those writes will corrupt my buffer. I'll have to change all accesses to those RAM locations to 0x800020XX. This will put them saftely after my buffer.

I just wrote and tested my sector flasher and it killed my drive. I can't even get into recovery mode (which means the boot code and/or recovery code is knackered). I'm going to have to remove and reprogram the chip via a hardware programmer to continue.

I think I know what has happened. If you look at the 3120L erase/program routines, then you'll see that they support a number of different flash chip devices. It uses the chip manufacturer IDs to determine how to erase/program the device. The chip in my drive is the SST39F020A (ManID: 0xBF). For this chip and one other (ManID: 0x9D) the code erases two sectors for the price of one!

ROM:0002FC44                 mov     0xAA, D0 ! '¬'  ; sector erase sequence (for Manufacturer ID 0xBF and 0x9D)
ROM:0002FC47                 movbu   D0, (A0)
ROM:0002FC49                 mov     0x55, D0 ! 'U'
ROM:0002FC4B                 movbu   D0, (A1)
ROM:0002FC4D                 mov     0x80, D0 ! 'Ç'
ROM:0002FC50                 movbu   D0, (A0)
ROM:0002FC52                 mov     0xAA, D0 ! '¬'
ROM:0002FC55                 movbu   D0, (A0)
ROM:0002FC57                 mov     0x55, D0 ! 'U'
ROM:0002FC59                 movbu   D0, (A1)
ROM:0002FC5B                 mov     (word_934), A2
ROM:0002FC5F                 mov     0x30, D0 ! '0'
ROM:0002FC61                 movbu   D0, (A2)
ROM:0002FC63                 add     0xFC, SP ! '³'
ROM:0002FC66                 calls   sub_2FCC7       ; wait for sector erase to complete
ROM:0002FC6C                 add     4, SP
ROM:0002FC6F                 mov     0xAA, D0 ! '¬'  ; another sector erase sequence
ROM:0002FC72                 movbu   D0, (A0)
ROM:0002FC74                 mov     0x55, D0 ! 'U'
ROM:0002FC76                 movbu   D0, (A1)
ROM:0002FC78                 mov     0x80, D0 ! 'Ç'
ROM:0002FC7B                 movbu   D0, (A0)
ROM:0002FC7D                 mov     0xAA, D0 ! '¬'
ROM:0002FC80                 movbu   D0, (A0)
ROM:0002FC82                 mov     0x55, D0 ! 'U'
ROM:0002FC84                 movbu   D0, (A1)
ROM:0002FC86                 mov     (word_934), A2
ROM:0002FC8A                 add     0x1000, A2      ; erase sector input sector + 0x1000
ROM:0002FC8E                 mov     0x30, D0 ! '0'
ROM:0002FC90                 movbu   D0, (A2)
ROM:0002FC92                 add     0xFC, SP ! '³'
ROM:0002FC95                 calls   sub_2FCC7       ; wait for erase to complete
ROM:0002FC9B                 add     4, SP
ROM:0002FC9E                 bra     locret_2FCA0

I think the reason for this might be because the other supported flash devices have 8KB sectors, whereas the 0xBF and 0x9D devices have 4KB sectors. I guess the routines erase two at a time so that all chips can be treated like 8KB sector devices. This keeps everything nice and generic. Unfortunately it killed my drive. I tested my sector flasher on sector 0x0x9003F000 because it isn't included in the firmware's internal checksum (so if it went wrong, I wouldn't be stuck in recovery mode). Sadly for me (but probably pretty funny for everyone else), 0x9003F000 + 0x1000 = 0x90040000 = 0x90000000 = the start of flash. I erased my flash entry point code. This explains why I can't even get recovery mode. Doh!

Update: 9th April 2006

I desoldered, dumped and then reprogrammed my flash chip with a hardware programmer. The drive now lives, ready to be used and abused once more. The dump confirmed that I had erased my flash entry point code. I fixed the erase code to only erase one sector on 4KB sector devices (I just puts nops over the second erase sequence). I also modified my sector flasher to work with both 4KB and 8KB sectors (just in case the Hitachi ever ships with 8KB sector flash chips, as the firmware erase routine suggests it might do). Because I now have to account for uploading an 8KB sector, I had to modify the hard coded RAM accesses in the erase/program routines again. This time from 80002XXX to 80003XXX. This prevents those routines from corrupting the second half of the 8KB sector data that we upload to RAM.

Here's the working sector flasher.

Please read the warnings and instructions on this page before attempting to use this app!

/*
 * Will program single sectors of the
 * firmware flash chip within the
 * Hitachi-LG Xbox 360 DVD drive.
 *
 * example: Imagine you've modified the sector at 0x3F000 in firmware.bin
 *          and you want to flash this new sector to drive /dev/hdc which
 *          has a flash chip organised in 4KB sectors (0x1000 bytes).
 *
 * $ flashsec /dev/hdc firmware.bin 9003F000 1000
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 7th March 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>

// Don't change the size of any of these code blocks, you'll mess up the hard coded offsets

// Control code
/*
2 movm [D2,D3,A2,A3,D0,D1,A0,A1,MDR,LIR,LAR], (SP) // CF F8
4 and 0xFDFF, PSW // FA FC FF FD
6 mov dest_address, D0 // FC CC ?? ?? ?? ??
3 mov D0, (934) // 01 34 09
6 calls erase_code // FC FF 36 00 00 00
3 movbu (92E), D0 // 34 2E 09
2 cmp 0, D0 // A0 00
2 bne exit // C9 23 (disp.)
6 mov dest_address, D0 // FC CC ?? ?? ?? ??
3 mov D0, (934) // 01 34 09
6 mov src_address, D0 // FC CC 81 08 00 80
3 mov D0, (938) // 01 38 09
6 mov sector_size, D0 // FC CC ?? ?? ?? ??
3 mov D0, (92C) // 01 2C 09
6 calls program_code // FC FF 7E 04 00 00
exit:
4 or 0x200, PSW // FA FD 00 02
2 movm (SP), [D2,D3,A2,A3,D0,D1,A0,A1,MDR,LIR,LAR] // CE F8
2 rets // F0 FC
*/
unsigned char control_code[] = {
0xCF,0xF8,0xFA,0xFC,0xFF,0xFD,0xFC,0xCC,0x00,0x00,0x00,0x00,0x01,0x34,0x09,0xFC,
0xFF,0x36,0x00,0x00,0x00,0x34,0x2E,0x09,0xA0,0x00,0xC9,0x23,0xFC,0xCC,0x00,0x00,
0x00,0x00,0x01,0x34,0x09,0xFC,0xCC,0x81,0x08,0x00,0x80,0x01,0x38,0x09,0xFC,0xCC,
0x00,0x00,0x00,0x00,0x01,0x2C,0x09,0xFC,0xFF,0x7E,0x04,0x00,0x00,0xFA,0xFD,0x00,
0x02,0xCE,0xF8,0xF0,0xFC
};

// Flash sector erase code (from Hitchi-LG 3120L firmware revision 0047)
// This code was modified slightly to prevent it from corrupting the
// sector source data in RAM (placed after the code in our buffer) and
// to prevent erasing two sectors at once on 4K sector flash devices.
unsigned char erase_code[] = {
0xCF,0x08,0xCF,0x20,0xF8,0xFE,0xFC,0xFC,0xFF,0x29,0x04,0x00,0x00,0xF8,0xFE,0x04,
0x34,0xA8,0xD9,0x85,0x01,0xF2,0x14,0x02,0xA8,0xD9,0xF8,0xFE,0xFC,0xFC,0xFF,0x76,
0x00,0x00,0x00,0xF8,0xFE,0x04,0xFC,0xA8,0x20,0x30,0x00,0x80,0x2D,0xFF,0x00,0xA1,
0xC8,0x39,0x80,0x05,0xFC,0x81,0x00,0x30,0x00,0x80,0xF8,0xFE,0xFC,0xFC,0xFF,0x80,
0x02,0x00,0x00,0xF8,0xFE,0x04,0xF8,0xFE,0xFC,0xFC,0xFF,0xA2,0x03,0x00,0x00,0xF8,
0xFE,0x04,0x81,0xC8,0x1E,0xFC,0xA4,0x00,0x30,0x00,0x80,0xFC,0xC4,0x01,0x00,0x00,
0x00,0xFC,0x81,0x00,0x30,0x00,0x80,0xC9,0xD3,0x2C,0xFF,0x00,0x02,0x2E,0x09,0xCA,
0x07,0x80,0x00,0x02,0x2E,0x09,0x34,0xA8,0xD9,0x2D,0xFE,0x00,0xF2,0x04,0x02,0xA8,
0xD9,0xF8,0xFE,0xFC,0xFC,0xFF,0xCC,0x03,0x00,0x00,0xF8,0xFE,0x04,0xCE,0x20,0xCE,
0x08,0xF0,0xFC,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x55,0x2C,0xAA,0x00,
0xF0,0x50,0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x2A,0x80,0x55,0xF0,0x51,
0x2C,0x90,0x00,0xF0,0x50,0xCB,0xCB,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x20,
0x30,0x00,0x80,0xFC,0xA8,0x00,0x10,0x00,0x90,0xFC,0x82,0x24,0x30,0x00,0x80,0xFC,
0xA8,0x00,0x20,0x00,0x90,0xFC,0x82,0x28,0x30,0x00,0x80,0xFC,0xA8,0x01,0x00,0x00,
0x90,0xFC,0x82,0x04,0x30,0x00,0x80,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,
0x2C,0xF0,0x00,0xF0,0x50,0xCB,0xCB,0xF8,0xFE,0xFC,0xFC,0xFF,0x16,0x03,0x00,0x00,
0xF8,0xFE,0x04,0x85,0x1F,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x20,0xFC,0xA8,
0x24,0x30,0x00,0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x0E,
0x2C,0x00,0x20,0xFC,0x81,0x2C,0x30,0x00,0x80,0xCC,0x92,0x01,0xFC,0xDC,0x00,0x00,
0x00,0x90,0xFA,0xD0,0x55,0x05,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,0x00,0x00,0x00,
0x90,0xFA,0xD1,0xAA,0x02,0x80,0x55,0xF0,0x51,0x2C,0x90,0x00,0xF0,0x50,0xCB,0xCB,
0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x20,0x30,0x00,0x80,0xFC,0xA8,0x00,0x10,
0x00,0x90,0xFC,0x82,0x24,0x30,0x00,0x80,0xFC,0xA8,0x00,0x20,0x00,0x90,0xFC,0x82,
0x28,0x30,0x00,0x80,0xFC,0xA8,0x01,0x00,0x00,0x90,0xFC,0x82,0x04,0x30,0x00,0x80,
0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,0xF0,0x00,0xF0,0x50,0xCB,0xCB,
0xF8,0xFE,0xFC,0xFC,0xFF,0x7D,0x02,0x00,0x00,0xF8,0xFE,0x04,0x2D,0xC2,0x00,0xFC,
0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x20,0xFC,0xA8,0x24,0x30,0x00,0x80,0xA4,0xC9,
0x17,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x0E,0x2C,0x00,0x20,0xFC,0x81,0x2C,
0x30,0x00,0x80,0xCC,0xF8,0x00,0x2D,0x9D,0x00,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,
0xC9,0x20,0xFC,0xA8,0x24,0x30,0x00,0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x28,0x30,0x00,
0x80,0xA4,0xC9,0x0E,0x2C,0x00,0x10,0xFC,0x81,0x2C,0x30,0x00,0x80,0xCC,0xCE,0x00,
0x85,0x37,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x20,0xFC,0xA8,0x24,0x30,0x00,
0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x0E,0x2C,0x00,0x20,
0xFC,0x81,0x2C,0x30,0x00,0x80,0xCC,0xA5,0x00,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,
0xD0,0x55,0x55,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,
0xAA,0x2A,0x80,0x55,0xF0,0x51,0x2C,0x90,0x00,0xF0,0x50,0xCB,0xCB,0xFC,0xA8,0x00,
0x00,0x00,0x90,0xFC,0x82,0x20,0x30,0x00,0x80,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,
0x82,0x24,0x30,0x00,0x80,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x28,0x30,0x00,
0x80,0xFC,0xA8,0x01,0x00,0x00,0x90,0xFC,0x82,0x04,0x30,0x00,0x80,0x2C,0xAA,0x00,
0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,0xF0,0x00,0xF0,0x50,0xCB,0xCB,0xF8,0xFE,0xFC,
0xFC,0xFF,0x90,0x01,0x00,0x00,0xF8,0xFE,0x04,0x2D,0xBF,0x00,0xFC,0xA8,0x20,0x30,
0x00,0x80,0xA4,0xC9,0x1F,0xFC,0xA8,0x24,0x30,0x00,0x80,0xA4,0xC9,0x16,0xFC,0xA8,
0x28,0x30,0x00,0x80,0xA4,0xC9,0x0D,0x2C,0x00,0x10,0xFC,0x81,0x2C,0x30,0x00,0x80,
0xCA,0x0B,0x2C,0xFF,0x00,0xFC,0x82,0x20,0x30,0x00,0x80,0xF0,0xFC,0xFC,0xA8,0x20,
0x30,0x00,0x80,0x85,0x1F,0xA4,0xC8,0x19,0x2D,0xC2,0x00,0xA4,0xC8,0x55,0x2D,0x9D,
0x00,0xA4,0xC9,0x05,0xCC,0x8F,0x00,0x85,0x37,0xA4,0xC8,0x47,0xCC,0x9D,0x00,0xFC,
0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x55,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,
0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x2A,0x80,0x55,0xF0,0x51,0x2C,0x80,0x00,0xF0,
0x50,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0xFA,0xA0,0x34,0x09,0x80,0x30,
0xF0,0x50,0xF8,0xFE,0xFC,0xFC,0xFF,0xFB,0x00,0x00,0x00,0xF8,0xFE,0x04,0xCC,0xCB,
0x00,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x05,0x2C,0xAA,0x00,0xF0,0x50,
0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x02,0x80,0x55,0xF0,0x51,0x2C,0x80,
0x00,0xF0,0x50,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0xFA,0xA0,0x34,0x09,
0x80,0x30,0xF0,0x50,0xF8,0xFE,0xFC,0xFC,0xFF,0xB9,0x00,0x00,0x00,0xF8,0xFE,0x04,
0xCC,0x89,0x00,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x05,0xFC,0xDD,0x00,
0x00,0x00,0x90,0xFA,0xD1,0xAA,0x02,0xCA,0x16,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,
0xD0,0x55,0x55,0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x2A,0x2C,0xAA,0x00,
0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,0x80,0x00,0xF0,0x50,0x2C,0xAA,0x00,0xF0,0x50,
0x80,0x55,0xF0,0x51,0xFA,0xA2,0x34,0x09,0x80,0x30,0xF0,0x52,0xF8,0xFE,0xFC,0xFC,
0xFF,0x61,0x00,0x00,0x00,0xF8,0xFE,0x04,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,
0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,
0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,
0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCA,0x02,0xF0,0xFC,0xFC,0xA5,0x2C,0x30,0x00,
0x80,0xFA,0xA0,0x34,0x09,0xF0,0x40,0xFA,0xC8,0xFF,0x00,0xC9,0x10,0x41,0xFC,0xC5,
0x01,0x00,0x00,0x00,0xC9,0xF1,0x80,0x00,0xDF,0x00,0x00,0x2C,0xFF,0x00,0xF0,0xFC,
0xFC,0xA8,0x00,0x00,0x00,0x90,0xCB,0xCB,0xFC,0xA9,0x00,0x00,0x00,0x90,0xCB,0xCB,
0xA1,0xC9,0xEF,0xFC,0xA9,0x00,0x00,0x00,0x90,0xCB,0xCB,0xA1,0xC9,0xE4,0xF0,0xFC,
0x34,0x04,0xD9,0xF8,0xE0,0x80,0xC9,0xFA,0xCB,0xCB,0x80,0x01,0x02,0x00,0xDF,0xCB,
0xCB,0x80,0x00,0x02,0x01,0xDF,0xCB,0xCB,0x2C,0x9A,0x00,0x02,0x28,0xDF,0xF0,0xFC,
0x34,0x04,0xD9,0xF8,0xE0,0x80,0xC9,0xFA,0xCB,0xCB,0x80,0x00,0x02,0x00,0xDF,0xCB,
0xCB,0x80,0x00,0x02,0x01,0xDF,0xCB,0xCB,0x2C,0x9A,0x00,0x02,0x28,0xDF,0xF0,0xFC
};

// Flash byte-program code (from Hitchi-LG 3120L firmware revision 0047)
// This code was modified slightly to prevent it from corrupting the
// sector source data in RAM (placed after the code in our buffer).
unsigned char program_code[] = {
0xCF,0x08,0xF8,0xFE,0xFC,0xFC,0xFF,0x87,0x03,0x00,0x00,0xF8,0xFE,0x04,0x34,0xA8,
0xD9,0x85,0x01,0xF2,0x14,0x02,0xA8,0xD9,0xF8,0xFE,0xFC,0xFC,0xFF,0xA3,0x00,0x00,
0x00,0xF8,0xFE,0x04,0xFC,0xA8,0x20,0x30,0x00,0x80,0x2D,0xFF,0x00,0xA1,0xC8,0x42,
0x38,0x2C,0x09,0xFC,0x81,0x2C,0x30,0x00,0x80,0x80,0x0A,0xFC,0x81,0x00,0x30,0x00,
0x80,0xF8,0xFE,0xFC,0xFC,0xFF,0x79,0x02,0x00,0x00,0xF8,0xFE,0x04,0xF8,0xFE,0xFC,
0xFC,0xFF,0x03,0x03,0x00,0x00,0xF8,0xFE,0x04,0x81,0xC8,0x1E,0xFC,0xA4,0x00,0x30,
0x00,0x80,0xFC,0xC4,0x01,0x00,0x00,0x00,0xFC,0x81,0x00,0x30,0x00,0x80,0xC9,0xD3,
0x2C,0xFF,0x00,0x02,0x2E,0x09,0xCA,0x2D,0xFA,0xA0,0x34,0x09,0x41,0xFA,0x80,0x34,
0x09,0xFA,0xA0,0x38,0x09,0x41,0xFA,0x80,0x38,0x09,0xFC,0xA4,0x2C,0x30,0x00,0x80,
0xFC,0xC4,0x01,0x00,0x00,0x00,0xFC,0x81,0x2C,0x30,0x00,0x80,0xC9,0x9D,0x80,0x00,
0x02,0x2E,0x09,0x34,0xA8,0xD9,0x2D,0xFE,0x00,0xF2,0x04,0x02,0xA8,0xD9,0xF8,0xFE,
0xFC,0xFC,0xFF,0xFB,0x02,0x00,0x00,0xF8,0xFE,0x04,0xCE,0x08,0xF0,0xFC,0xFC,0xDC,
0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x55,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,0x00,
0x00,0x00,0x90,0xFA,0xD1,0xAA,0x2A,0x80,0x55,0xF0,0x51,0x2C,0x90,0x00,0xF0,0x50,
0xCB,0xCB,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x20,0x30,0x00,0x80,0xFC,0xA8,
0x00,0x10,0x00,0x90,0xFC,0x82,0x24,0x30,0x00,0x80,0xFC,0xA8,0x00,0x20,0x00,0x90,
0xFC,0x82,0x28,0x30,0x00,0x80,0xFC,0xA8,0x01,0x00,0x00,0x90,0xFC,0x82,0x04,0x30,
0x00,0x80,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,0xF0,0x00,0xF0,0x50,
0xCB,0xCB,0xF8,0xFE,0xFC,0xFC,0xFF,0x47,0x02,0x00,0x00,0xF8,0xFE,0x04,0x85,0x1F,
0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x19,0xFC,0xA8,0x24,0x30,0x00,0x80,0xA4,
0xC9,0x10,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x07,0xC9,0x05,0xCC,0x6E,0x01,
0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x05,0x2C,0xAA,0x00,0xF0,0x50,0xFC,
0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x02,0x80,0x55,0xF0,0x51,0x2C,0x90,0x00,
0xF0,0x50,0xCB,0xCB,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x20,0x30,0x00,0x80,
0xFC,0xA8,0x00,0x10,0x00,0x90,0xFC,0x82,0x24,0x30,0x00,0x80,0xFC,0xA8,0x00,0x20,
0x00,0x90,0xFC,0x82,0x28,0x30,0x00,0x80,0xFC,0xA8,0x01,0x00,0x00,0x90,0xFC,0x82,
0x04,0x30,0x00,0x80,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,0xF0,0x00,
0xF0,0x50,0xCB,0xCB,0xF8,0xFE,0xFC,0xFC,0xFF,0xB5,0x01,0x00,0x00,0xF8,0xFE,0x04,
0x2D,0xC2,0x00,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x24,0x30,
0x00,0x80,0xA4,0xC9,0x0E,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x05,0xCC,0xDD,
0x00,0x2D,0x9D,0x00,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x24,
0x30,0x00,0x80,0xA4,0xC9,0x0E,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x05,0xCC,
0xBC,0x00,0x85,0x37,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x24,
0x30,0x00,0x80,0xA4,0xC9,0x0E,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x05,0xCC,
0x9C,0x00,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x55,0x2C,0xAA,0x00,0xF0,
0x50,0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x2A,0x80,0x55,0xF0,0x51,0x2C,
0x90,0x00,0xF0,0x50,0xCB,0xCB,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x20,0x30,
0x00,0x80,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x24,0x30,0x00,0x80,0xFC,0xA8,
0x00,0x00,0x00,0x90,0xFC,0x82,0x28,0x30,0x00,0x80,0xFC,0xA8,0x01,0x00,0x00,0x90,
0xFC,0x82,0x04,0x30,0x00,0x80,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,
0xF0,0x00,0xF0,0x50,0xCB,0xCB,0xF8,0xFE,0xFC,0xFC,0xFF,0xE3,0x00,0x00,0x00,0xF8,
0xFE,0x04,0x2D,0xBF,0x00,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x16,0xFC,0xA8,
0x24,0x30,0x00,0x80,0xA4,0xC9,0x0D,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x04,
0xC8,0x0B,0x2C,0xFF,0x00,0xFC,0x82,0x20,0x30,0x00,0x80,0xF0,0xFC,0xFC,0xA8,0x20,
0x30,0x00,0x80,0x85,0x1F,0xA4,0xC8,0x15,0x2D,0xC2,0x00,0xA4,0xC8,0x4B,0x2D,0x9D,
0x00,0xA4,0xC8,0x45,0x85,0x37,0xA4,0xC8,0x40,0xCA,0x02,0xFC,0xDC,0x00,0x00,0x00,
0x90,0xFA,0xD0,0x55,0x55,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,0x00,0x00,0x00,0x90,
0xFA,0xD1,0xAA,0x2A,0x80,0x55,0xF0,0x51,0x2C,0xA0,0x00,0xF0,0x50,0xFA,0xA1,0x34,
0x09,0xFA,0xA0,0x38,0x09,0xF0,0x40,0xF0,0x51,0xF8,0xFE,0xFC,0xFC,0xFF,0x60,0x00,
0x00,0x00,0xF8,0xFE,0x04,0xCA,0x3C,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,
0x05,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x02,
0x80,0x55,0xF0,0x51,0x2C,0xA0,0x00,0xF0,0x50,0xFA,0xA1,0x34,0x09,0xFA,0xA0,0x38,
0x09,0xF0,0x40,0xF0,0x51,0xF8,0xFE,0xFC,0xFC,0xFF,0x24,0x00,0x00,0x00,0xF8,0xFE,
0x04,0xF0,0xFC,0xFA,0xA1,0x34,0x09,0xFA,0xA0,0x38,0x09,0xF0,0x40,0xF0,0x45,0xA4,
0xC9,0x07,0x80,0x00,0xDF,0x00,0x00,0x2C,0xFF,0x00,0xF0,0xFC,0xFC,0xA8,0x00,0x00,
0x00,0x90,0xCB,0xCB,0xFC,0xA9,0x00,0x00,0x00,0x90,0xCB,0xCB,0xA1,0xC9,0xEF,0xFC,
0xA9,0x00,0x00,0x00,0x90,0xCB,0xCB,0xA1,0xC9,0xE4,0xF0,0xFC,0x34,0x04,0xD9,0xF8,
0xE0,0x80,0xC9,0xFA,0xCB,0xCB,0x80,0x01,0x02,0x00,0xDF,0xCB,0xCB,0x80,0x00,0x02,
0x01,0xDF,0xCB,0xCB,0x2C,0x9A,0x00,0x02,0x28,0xDF,0xF0,0xFC,0x34,0x04,0xD9,0xF8,
0xE0,0x80,0xC9,0xFA,0xCB,0xCB,0x80,0x00,0x02,0x00,0xDF,0xCB,0xCB,0x80,0x00,0x02,
0x01,0xDF,0xCB,0xCB,0x2C,0x9A,0x00,0x02,0x28,0xDF,0xF0,0xFC
};

unsigned short end = 0x00FF; // endian detection

int hex_atoi(void *dest, char *src, int dest_len)
{
	int src_last, i, j;
	unsigned char val;

	memset(dest, 0, dest_len);

	src_last = strlen(src) - 1;

	for(i = src_last; i >= 0; i--) {
		if(src[i] >= '0' && src[i] <= '9')
			val = src[i] - 0x30;
		else if(src[i] >= 'a' && src[i] <= 'f')
			val = (src[i] - 0x60) + 9;
		else if(src[i] >= 'A' && src[i] <= 'F')
			val = (src[i] - 0x40) + 9;
		else
			return 1; // invalid hex digit

		j = src_last - i;

		if(j & 1)
			val <<= 4;

		if(*((unsigned char *)&end)) // little endian CPU
			j >>= 1;
		else // big endian CPU
			j = dest_len - ((j >> 1) + 1);

		if(j >= dest_len || j < 0)
			break;

		((unsigned char *)dest)[j] |= val;
	}

	return 0;
}


int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	unsigned char param_list[0x10], *buffer, *ptr;
	static unsigned char txblk[0x800];
	unsigned int sum, i, sector, blocks, blklen, buffer_len, sector_size, bytes;
	FILE *fptr;

	if(argc != 5) {
		printf("usage: flashsec dvd_drive_letter_or_device_number firmware_image target_sector_address sector_size\n");
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		perror("open");
		return 1;
	}

	if(!(fptr = fopen(argv[2], "rb"))) {
		perror("fopen");
		close(fd);
		return 1;
	}

	if(hex_atoi(§or_size, argv[4], sizeof(sector_size))) {
		printf("invalid sector size\n");
		close(fd);
		fclose(fptr);
		return 1;
	}

	if(hex_atoi(§or, argv[3], sizeof(sector)) || (sector % sector_size) || sector < 0x90000000) {
		printf("invalid sector address\n");
		close(fd);
		fclose(fptr);
		return 1;
	}

	// Create our flash buffer
	if(fseek(fptr, sector - 0x90000000, SEEK_SET) == -1) {
		perror("fseek");
		close(fd);
		fclose(fptr);
		return 1;
	}

	buffer_len = (sizeof(control_code) + sizeof(erase_code) + sizeof(program_code) + sector_size + 2); // + 2 for 16-bit checksum
	if(buffer_len & 0x03)
		buffer_len += 4 - (buffer_len & 0x03); // round up to 4 byte boundary

	if(!(buffer = (unsigned char *)malloc(buffer_len))) {
		printf("malloc() failed\n");
		close(fd);
		fclose(fptr);
		return 1;
	}

	memset(buffer, 0, buffer_len);

	if(!*((unsigned char *)&end)) { // big endian CPU
		i = sector;
		((unsigned char *)§or)[0] = ((unsigned char *)&i)[3];
		((unsigned char *)§or)[1] = ((unsigned char *)&i)[2];
		((unsigned char *)§or)[2] = ((unsigned char *)&i)[1];
		((unsigned char *)§or)[3] = ((unsigned char *)&i)[0];
		((unsigned char *)&i)[0] = ((unsigned char *)§or_size)[3];
		((unsigned char *)&i)[1] = ((unsigned char *)§or_size)[2];
		((unsigned char *)&i)[2] = ((unsigned char *)§or_size)[1];
		((unsigned char *)&i)[3] = ((unsigned char *)§or_size)[0];
	}
	else
		i = sector_size;

	*((unsigned int *)&control_code[8]) = sector;
	*((unsigned int *)&control_code[30]) = sector;
	*((unsigned int *)&control_code[48]) = i; // little endian sector_size

	memcpy(buffer, control_code, sizeof(control_code));
	ptr = &buffer[sizeof(control_code)];
	memcpy(ptr, erase_code, sizeof(erase_code));
	ptr += sizeof(erase_code);
	memcpy(ptr, program_code, sizeof(program_code));
	ptr += sizeof(program_code);

	if(fread(ptr, sector_size, 1, fptr) != 1) {
		perror("fread");
		close(fd);
		fclose(fptr);
		return 1;
	}

	// Caculate buffer checksum
	for(sum = 0, i = 0; i < buffer_len - 2; i++)
		sum += buffer[i];
	sum = 0x10000 - (sum & 0x0000FFFF);
	buffer[buffer_len - 2] = (unsigned char)(sum & 0x00FF);
	buffer[buffer_len - 1] = (unsigned char)((sum & 0xFF00) >> 8);

	// Initiate modeB (required to configure upload)
	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = 0xE7;
	cgc.cmd[1] = 0x48;
	cgc.cmd[2] = 0x49;
	cgc.cmd[3] = 0x54;
	cgc.cmd[4] = 0x30;
	cgc.cmd[5] = 0x90;
	cgc.cmd[6] = 0x90;
	cgc.cmd[7] = 0xD0;
	cgc.cmd[8] = 0x01;

	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_NONE;
	cgc.timeout = 15000;

	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("failed to initiate modeB (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else {
		// Stop the disc
		memset(&cgc, 0, sizeof(cgc));
		cgc.cmd[0] = 0x1B;

		cgc.sense = &sense;
		cgc.data_direction = CGC_DATA_NONE;
		cgc.timeout = 15000;

		if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1 && (sense.sense_key != 0x02 || sense.asc != 0x3A)) {
			perror("ioctl");
			printf("failed to stop the disc (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
		}
		else {
			// <hack>
			// Clear (59E) bit 3 if it's set (this bit will cause us problems if it's left set)
			memset(&cgc, 0, sizeof(cgc));
			cgc.cmd[0] = 0x55;

			cgc.sense = &sense;
			cgc.data_direction = CGC_DATA_NONE;
			cgc.timeout = 15000;

			ioctl(fd, CDROM_SEND_PACKET, &cgc);
			// </hack>

			// Configure upload (required to upload/execute code)
			memset(&cgc, 0, sizeof(cgc));
			cgc.cmd[0] = 0x55;
			cgc.cmd[1] = 0x10;
			cgc.cmd[8] = 0x10;
			cgc.cmd[11] = 0x01;

			memset(param_list, 0, sizeof(param_list));
			param_list[9] = 0x06;
			param_list[10] = 0x48;
			param_list[11] = 0x4C;
			param_list[14] = (unsigned char)((buffer_len & 0xFF00) >> 8);
			param_list[15] = (unsigned char)(buffer_len & 0x00FF);

			cgc.buffer = param_list;
			cgc.buflen = 0x10;
			cgc.sense = &sense;
			cgc.data_direction = CGC_DATA_WRITE;
			cgc.timeout = 15000;

			if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
				perror("ioctl");
				printf("failed to configure upload (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
			}
			else {
				// Upload our buffer in 0x7F8 byte chunks
				blocks = buffer_len / 0x7F8;
				if(buffer_len % 0x7F8)
				blocks++;
				for(i = 0; i < blocks; i++) {
					if(i != blocks - 1)
						blklen = 0x800;
					else
						blklen = (buffer_len % 0x7F8) + 8;

					memset(&cgc, 0, sizeof(cgc));
					cgc.cmd[0] = 0x55;
					cgc.cmd[1] = 0x10;
					cgc.cmd[7] = (unsigned char)((blklen & 0xFF00) >> 8);
					cgc.cmd[8] = (unsigned char)(blklen & 0x00FF);
					cgc.cmd[11] = 0x01;

					memset(txblk, 0, sizeof(txblk));
					txblk[4] = 0x48;
					txblk[5] = 0x4C;

					memcpy(&txblk[8], &buffer[i * 0x7F8], blklen - 8);

					cgc.buffer = txblk;
					cgc.buflen = blklen;
					cgc.sense = &sense;
					cgc.data_direction = CGC_DATA_WRITE;
					cgc.timeout = 15000;

					if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
						perror("ioctl");
						printf("failed to upload buffer block %u (sense: %02X/%02X/%02X)\n", i, sense.sense_key, sense.asc, sense.ascq);
						close(fd);
						fclose(fptr);
						free(buffer);
						return 1;
					}
				}

				// Execute flasher code
				memset(&cgc, 0, sizeof(cgc));
				cgc.cmd[0] = 0x55;
				cgc.cmd[1] = 0x10;
				cgc.cmd[3] = 0x48;
				cgc.cmd[4] = 0x4C;
				cgc.cmd[6] = 0x01;
				cgc.cmd[11] = 0x01;

				cgc.sense = &sense;
				cgc.data_direction = CGC_DATA_NONE;
				cgc.timeout = 15000;

				if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
					perror("ioctl");
					printf("failed to execute flasher (sense: %02X/%02X/%02X)\n", i, sense.sense_key, sense.asc, sense.ascq);
				}
				else
					printf("done\n");
			}
		}
	}

	close(fd);
	fclose(fptr);
	free(buffer);

	printf("\n");

	return 0;
}

download

Win32 ports.

flashsec_win.zip - flashsec binary for Win2000/XP
flashsec_win_src.zip - flashsec source for Win2000/XP

I can now attempt to reproduce the firmware hack without masses of soldering.

Update: 17th May 2006

It came to my attention recently that flashsec does not work with the 0047 Hitach-LG firmware. The 0047 firmware contains the following code which is absent from the 0046 firmware. This bit check protects the code that is responsible for uploading blocks of data to the drive. It also causes certain mode select commands to fail on 0047 which would pass on 0046 (like setting (93E) bit 3 if it's already set). This causes my flashsec, execcode(2) and pp apps to fail.

ROM:00026FCC                 btst    0x10, (unk_5A5)  ; is (5A5) bit 4 set?
ROM:00026FD1                 bne     loc_26FDB        ; yes, continue
ROM:00026FD3                 mov     0xB, D0
ROM:00026FD5                 movbu   D0, (word_5D8)
ROM:00026FD8                 jmp     loc_27093        ; no, fail

Although my drive is 0046, I have been reverse engineering 0047. I had discussed this check before with a hacker named SpenZerX. I always wondered how my apps worked despite this check. Now I know!

There is a special mode select command that will set (5A5) bit 4 for you, but it requires hardware induced modeB (see my 8th March update for full info). In the end I decided to set this bit by poking a small piece of code, a byte at a time, to RAM and then executing it. This code would set (5A5) bit 4. This works because the RAM poke command isn't protected by a check to (5A5) bit 4. Here's the new code. I don't know if this will work with the 0046 firmware or not. I flashed my drive to 0047, so I don't have any way to test it. My advice is to use the old flashsec if you have a 0046 drive.

/*
 * Will program single sectors of the
 * firmware flash chip within the
 * Hitachi-LG Xbox 360 DVD drive.
 *
 * example: Imagine you've modified the sector at 0x3F000 in firmware.bin
 *          and you want to flash this new sector to drive /dev/hdc which
 *          has a flash chip organised in 4KB sectors (0x1000 bytes).
 *
 * $ flashsec47 /dev/hdc firmware.bin 9003F000 1000
 *
 * author: Kevin East (SeventhSon)
 * email: kev@kev.nu
 * web: http://www.kev.nu/360/
 * date: 16th May 2006
 * platform: linux
 *
 */

#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>

// Don't change the size of any of these code blocks, you'll mess up the hard coded offsets

// Control code
/*
2 movm [D2,D3,A2,A3,D0,D1,A0,A1,MDR,LIR,LAR], (SP) // CF F8
4 and 0xFDFF, PSW // FA FC FF FD
6 mov dest_address, D0 // FC CC ?? ?? ?? ??
3 mov D0, (934) // 01 34 09
6 calls erase_code // FC FF 36 00 00 00
3 movbu (92E), D0 // 34 2E 09
2 cmp 0, D0 // A0 00
2 bne exit // C9 23 (disp.)
6 mov dest_address, D0 // FC CC ?? ?? ?? ??
3 mov D0, (934) // 01 34 09
6 mov src_address, D0 // FC CC 81 08 00 80
3 mov D0, (938) // 01 38 09
6 mov sector_size, D0 // FC CC ?? ?? ?? ??
3 mov D0, (92C) // 01 2C 09
6 calls program_code // FC FF 7E 04 00 00
exit:
4 or 0x200, PSW // FA FD 00 02
2 movm (SP), [D2,D3,A2,A3,D0,D1,A0,A1,MDR,LIR,LAR] // CE F8
2 rets // F0 FC
*/
unsigned char control_code[] = {
0xCF,0xF8,0xFA,0xFC,0xFF,0xFD,0xFC,0xCC,0x00,0x00,0x00,0x00,0x01,0x34,0x09,0xFC,
0xFF,0x36,0x00,0x00,0x00,0x34,0x2E,0x09,0xA0,0x00,0xC9,0x23,0xFC,0xCC,0x00,0x00,
0x00,0x00,0x01,0x34,0x09,0xFC,0xCC,0x81,0x08,0x00,0x80,0x01,0x38,0x09,0xFC,0xCC,
0x00,0x00,0x00,0x00,0x01,0x2C,0x09,0xFC,0xFF,0x7E,0x04,0x00,0x00,0xFA,0xFD,0x00,
0x02,0xCE,0xF8,0xF0,0xFC
};

// Flash sector erase code (from Hitchi-LG 3120L firmware revision 0047)
// This code was modified slightly to prevent it from corrupting the
// sector source data in RAM (placed after the code in our buffer) and
// to prevent erasing two sectors at once on 4K sector flash devices.
unsigned char erase_code[] = {
0xCF,0x08,0xCF,0x20,0xF8,0xFE,0xFC,0xFC,0xFF,0x29,0x04,0x00,0x00,0xF8,0xFE,0x04,
0x34,0xA8,0xD9,0x85,0x01,0xF2,0x14,0x02,0xA8,0xD9,0xF8,0xFE,0xFC,0xFC,0xFF,0x76,
0x00,0x00,0x00,0xF8,0xFE,0x04,0xFC,0xA8,0x20,0x30,0x00,0x80,0x2D,0xFF,0x00,0xA1,
0xC8,0x39,0x80,0x05,0xFC,0x81,0x00,0x30,0x00,0x80,0xF8,0xFE,0xFC,0xFC,0xFF,0x80,
0x02,0x00,0x00,0xF8,0xFE,0x04,0xF8,0xFE,0xFC,0xFC,0xFF,0xA2,0x03,0x00,0x00,0xF8,
0xFE,0x04,0x81,0xC8,0x1E,0xFC,0xA4,0x00,0x30,0x00,0x80,0xFC,0xC4,0x01,0x00,0x00,
0x00,0xFC,0x81,0x00,0x30,0x00,0x80,0xC9,0xD3,0x2C,0xFF,0x00,0x02,0x2E,0x09,0xCA,
0x07,0x80,0x00,0x02,0x2E,0x09,0x34,0xA8,0xD9,0x2D,0xFE,0x00,0xF2,0x04,0x02,0xA8,
0xD9,0xF8,0xFE,0xFC,0xFC,0xFF,0xCC,0x03,0x00,0x00,0xF8,0xFE,0x04,0xCE,0x20,0xCE,
0x08,0xF0,0xFC,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x55,0x2C,0xAA,0x00,
0xF0,0x50,0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x2A,0x80,0x55,0xF0,0x51,
0x2C,0x90,0x00,0xF0,0x50,0xCB,0xCB,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x20,
0x30,0x00,0x80,0xFC,0xA8,0x00,0x10,0x00,0x90,0xFC,0x82,0x24,0x30,0x00,0x80,0xFC,
0xA8,0x00,0x20,0x00,0x90,0xFC,0x82,0x28,0x30,0x00,0x80,0xFC,0xA8,0x01,0x00,0x00,
0x90,0xFC,0x82,0x04,0x30,0x00,0x80,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,
0x2C,0xF0,0x00,0xF0,0x50,0xCB,0xCB,0xF8,0xFE,0xFC,0xFC,0xFF,0x16,0x03,0x00,0x00,
0xF8,0xFE,0x04,0x85,0x1F,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x20,0xFC,0xA8,
0x24,0x30,0x00,0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x0E,
0x2C,0x00,0x20,0xFC,0x81,0x2C,0x30,0x00,0x80,0xCC,0x92,0x01,0xFC,0xDC,0x00,0x00,
0x00,0x90,0xFA,0xD0,0x55,0x05,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,0x00,0x00,0x00,
0x90,0xFA,0xD1,0xAA,0x02,0x80,0x55,0xF0,0x51,0x2C,0x90,0x00,0xF0,0x50,0xCB,0xCB,
0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x20,0x30,0x00,0x80,0xFC,0xA8,0x00,0x10,
0x00,0x90,0xFC,0x82,0x24,0x30,0x00,0x80,0xFC,0xA8,0x00,0x20,0x00,0x90,0xFC,0x82,
0x28,0x30,0x00,0x80,0xFC,0xA8,0x01,0x00,0x00,0x90,0xFC,0x82,0x04,0x30,0x00,0x80,
0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,0xF0,0x00,0xF0,0x50,0xCB,0xCB,
0xF8,0xFE,0xFC,0xFC,0xFF,0x7D,0x02,0x00,0x00,0xF8,0xFE,0x04,0x2D,0xC2,0x00,0xFC,
0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x20,0xFC,0xA8,0x24,0x30,0x00,0x80,0xA4,0xC9,
0x17,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x0E,0x2C,0x00,0x20,0xFC,0x81,0x2C,
0x30,0x00,0x80,0xCC,0xF8,0x00,0x2D,0x9D,0x00,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,
0xC9,0x20,0xFC,0xA8,0x24,0x30,0x00,0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x28,0x30,0x00,
0x80,0xA4,0xC9,0x0E,0x2C,0x00,0x10,0xFC,0x81,0x2C,0x30,0x00,0x80,0xCC,0xCE,0x00,
0x85,0x37,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x20,0xFC,0xA8,0x24,0x30,0x00,
0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x0E,0x2C,0x00,0x20,
0xFC,0x81,0x2C,0x30,0x00,0x80,0xCC,0xA5,0x00,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,
0xD0,0x55,0x55,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,
0xAA,0x2A,0x80,0x55,0xF0,0x51,0x2C,0x90,0x00,0xF0,0x50,0xCB,0xCB,0xFC,0xA8,0x00,
0x00,0x00,0x90,0xFC,0x82,0x20,0x30,0x00,0x80,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,
0x82,0x24,0x30,0x00,0x80,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x28,0x30,0x00,
0x80,0xFC,0xA8,0x01,0x00,0x00,0x90,0xFC,0x82,0x04,0x30,0x00,0x80,0x2C,0xAA,0x00,
0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,0xF0,0x00,0xF0,0x50,0xCB,0xCB,0xF8,0xFE,0xFC,
0xFC,0xFF,0x90,0x01,0x00,0x00,0xF8,0xFE,0x04,0x2D,0xBF,0x00,0xFC,0xA8,0x20,0x30,
0x00,0x80,0xA4,0xC9,0x1F,0xFC,0xA8,0x24,0x30,0x00,0x80,0xA4,0xC9,0x16,0xFC,0xA8,
0x28,0x30,0x00,0x80,0xA4,0xC9,0x0D,0x2C,0x00,0x10,0xFC,0x81,0x2C,0x30,0x00,0x80,
0xCA,0x0B,0x2C,0xFF,0x00,0xFC,0x82,0x20,0x30,0x00,0x80,0xF0,0xFC,0xFC,0xA8,0x20,
0x30,0x00,0x80,0x85,0x1F,0xA4,0xC8,0x19,0x2D,0xC2,0x00,0xA4,0xC8,0x55,0x2D,0x9D,
0x00,0xA4,0xC9,0x05,0xCC,0x8F,0x00,0x85,0x37,0xA4,0xC8,0x47,0xCC,0x9D,0x00,0xFC,
0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x55,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,
0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x2A,0x80,0x55,0xF0,0x51,0x2C,0x80,0x00,0xF0,
0x50,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0xFA,0xA0,0x34,0x09,0x80,0x30,
0xF0,0x50,0xF8,0xFE,0xFC,0xFC,0xFF,0xFB,0x00,0x00,0x00,0xF8,0xFE,0x04,0xCC,0xCB,
0x00,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x05,0x2C,0xAA,0x00,0xF0,0x50,
0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x02,0x80,0x55,0xF0,0x51,0x2C,0x80,
0x00,0xF0,0x50,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0xFA,0xA0,0x34,0x09,
0x80,0x30,0xF0,0x50,0xF8,0xFE,0xFC,0xFC,0xFF,0xB9,0x00,0x00,0x00,0xF8,0xFE,0x04,
0xCC,0x89,0x00,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x05,0xFC,0xDD,0x00,
0x00,0x00,0x90,0xFA,0xD1,0xAA,0x02,0xCA,0x16,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,
0xD0,0x55,0x55,0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x2A,0x2C,0xAA,0x00,
0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,0x80,0x00,0xF0,0x50,0x2C,0xAA,0x00,0xF0,0x50,
0x80,0x55,0xF0,0x51,0xFA,0xA2,0x34,0x09,0x80,0x30,0xF0,0x52,0xF8,0xFE,0xFC,0xFC,
0xFF,0x61,0x00,0x00,0x00,0xF8,0xFE,0x04,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,
0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,
0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,
0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCB,0xCA,0x02,0xF0,0xFC,0xFC,0xA5,0x2C,0x30,0x00,
0x80,0xFA,0xA0,0x34,0x09,0xF0,0x40,0xFA,0xC8,0xFF,0x00,0xC9,0x10,0x41,0xFC,0xC5,
0x01,0x00,0x00,0x00,0xC9,0xF1,0x80,0x00,0xDF,0x00,0x00,0x2C,0xFF,0x00,0xF0,0xFC,
0xFC,0xA8,0x00,0x00,0x00,0x90,0xCB,0xCB,0xFC,0xA9,0x00,0x00,0x00,0x90,0xCB,0xCB,
0xA1,0xC9,0xEF,0xFC,0xA9,0x00,0x00,0x00,0x90,0xCB,0xCB,0xA1,0xC9,0xE4,0xF0,0xFC,
0x34,0x04,0xD9,0xF8,0xE0,0x80,0xC9,0xFA,0xCB,0xCB,0x80,0x01,0x02,0x00,0xDF,0xCB,
0xCB,0x80,0x00,0x02,0x01,0xDF,0xCB,0xCB,0x2C,0x9A,0x00,0x02,0x28,0xDF,0xF0,0xFC,
0x34,0x04,0xD9,0xF8,0xE0,0x80,0xC9,0xFA,0xCB,0xCB,0x80,0x00,0x02,0x00,0xDF,0xCB,
0xCB,0x80,0x00,0x02,0x01,0xDF,0xCB,0xCB,0x2C,0x9A,0x00,0x02,0x28,0xDF,0xF0,0xFC
};

// Flash byte-program code (from Hitchi-LG 3120L firmware revision 0047)
// This code was modified slightly to prevent it from corrupting the
// sector source data in RAM (placed after the code in our buffer).
unsigned char program_code[] = {
0xCF,0x08,0xF8,0xFE,0xFC,0xFC,0xFF,0x87,0x03,0x00,0x00,0xF8,0xFE,0x04,0x34,0xA8,
0xD9,0x85,0x01,0xF2,0x14,0x02,0xA8,0xD9,0xF8,0xFE,0xFC,0xFC,0xFF,0xA3,0x00,0x00,
0x00,0xF8,0xFE,0x04,0xFC,0xA8,0x20,0x30,0x00,0x80,0x2D,0xFF,0x00,0xA1,0xC8,0x42,
0x38,0x2C,0x09,0xFC,0x81,0x2C,0x30,0x00,0x80,0x80,0x0A,0xFC,0x81,0x00,0x30,0x00,
0x80,0xF8,0xFE,0xFC,0xFC,0xFF,0x79,0x02,0x00,0x00,0xF8,0xFE,0x04,0xF8,0xFE,0xFC,
0xFC,0xFF,0x03,0x03,0x00,0x00,0xF8,0xFE,0x04,0x81,0xC8,0x1E,0xFC,0xA4,0x00,0x30,
0x00,0x80,0xFC,0xC4,0x01,0x00,0x00,0x00,0xFC,0x81,0x00,0x30,0x00,0x80,0xC9,0xD3,
0x2C,0xFF,0x00,0x02,0x2E,0x09,0xCA,0x2D,0xFA,0xA0,0x34,0x09,0x41,0xFA,0x80,0x34,
0x09,0xFA,0xA0,0x38,0x09,0x41,0xFA,0x80,0x38,0x09,0xFC,0xA4,0x2C,0x30,0x00,0x80,
0xFC,0xC4,0x01,0x00,0x00,0x00,0xFC,0x81,0x2C,0x30,0x00,0x80,0xC9,0x9D,0x80,0x00,
0x02,0x2E,0x09,0x34,0xA8,0xD9,0x2D,0xFE,0x00,0xF2,0x04,0x02,0xA8,0xD9,0xF8,0xFE,
0xFC,0xFC,0xFF,0xFB,0x02,0x00,0x00,0xF8,0xFE,0x04,0xCE,0x08,0xF0,0xFC,0xFC,0xDC,
0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x55,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,0x00,
0x00,0x00,0x90,0xFA,0xD1,0xAA,0x2A,0x80,0x55,0xF0,0x51,0x2C,0x90,0x00,0xF0,0x50,
0xCB,0xCB,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x20,0x30,0x00,0x80,0xFC,0xA8,
0x00,0x10,0x00,0x90,0xFC,0x82,0x24,0x30,0x00,0x80,0xFC,0xA8,0x00,0x20,0x00,0x90,
0xFC,0x82,0x28,0x30,0x00,0x80,0xFC,0xA8,0x01,0x00,0x00,0x90,0xFC,0x82,0x04,0x30,
0x00,0x80,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,0xF0,0x00,0xF0,0x50,
0xCB,0xCB,0xF8,0xFE,0xFC,0xFC,0xFF,0x47,0x02,0x00,0x00,0xF8,0xFE,0x04,0x85,0x1F,
0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x19,0xFC,0xA8,0x24,0x30,0x00,0x80,0xA4,
0xC9,0x10,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x07,0xC9,0x05,0xCC,0x6E,0x01,
0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x05,0x2C,0xAA,0x00,0xF0,0x50,0xFC,
0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x02,0x80,0x55,0xF0,0x51,0x2C,0x90,0x00,
0xF0,0x50,0xCB,0xCB,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x20,0x30,0x00,0x80,
0xFC,0xA8,0x00,0x10,0x00,0x90,0xFC,0x82,0x24,0x30,0x00,0x80,0xFC,0xA8,0x00,0x20,
0x00,0x90,0xFC,0x82,0x28,0x30,0x00,0x80,0xFC,0xA8,0x01,0x00,0x00,0x90,0xFC,0x82,
0x04,0x30,0x00,0x80,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,0xF0,0x00,
0xF0,0x50,0xCB,0xCB,0xF8,0xFE,0xFC,0xFC,0xFF,0xB5,0x01,0x00,0x00,0xF8,0xFE,0x04,
0x2D,0xC2,0x00,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x24,0x30,
0x00,0x80,0xA4,0xC9,0x0E,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x05,0xCC,0xDD,
0x00,0x2D,0x9D,0x00,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x24,
0x30,0x00,0x80,0xA4,0xC9,0x0E,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x05,0xCC,
0xBC,0x00,0x85,0x37,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x17,0xFC,0xA8,0x24,
0x30,0x00,0x80,0xA4,0xC9,0x0E,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x05,0xCC,
0x9C,0x00,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,0x55,0x2C,0xAA,0x00,0xF0,
0x50,0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x2A,0x80,0x55,0xF0,0x51,0x2C,
0x90,0x00,0xF0,0x50,0xCB,0xCB,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x20,0x30,
0x00,0x80,0xFC,0xA8,0x00,0x00,0x00,0x90,0xFC,0x82,0x24,0x30,0x00,0x80,0xFC,0xA8,
0x00,0x00,0x00,0x90,0xFC,0x82,0x28,0x30,0x00,0x80,0xFC,0xA8,0x01,0x00,0x00,0x90,
0xFC,0x82,0x04,0x30,0x00,0x80,0x2C,0xAA,0x00,0xF0,0x50,0x80,0x55,0xF0,0x51,0x2C,
0xF0,0x00,0xF0,0x50,0xCB,0xCB,0xF8,0xFE,0xFC,0xFC,0xFF,0xE3,0x00,0x00,0x00,0xF8,
0xFE,0x04,0x2D,0xBF,0x00,0xFC,0xA8,0x20,0x30,0x00,0x80,0xA4,0xC9,0x16,0xFC,0xA8,
0x24,0x30,0x00,0x80,0xA4,0xC9,0x0D,0xFC,0xA8,0x28,0x30,0x00,0x80,0xA4,0xC9,0x04,
0xC8,0x0B,0x2C,0xFF,0x00,0xFC,0x82,0x20,0x30,0x00,0x80,0xF0,0xFC,0xFC,0xA8,0x20,
0x30,0x00,0x80,0x85,0x1F,0xA4,0xC8,0x15,0x2D,0xC2,0x00,0xA4,0xC8,0x4B,0x2D,0x9D,
0x00,0xA4,0xC8,0x45,0x85,0x37,0xA4,0xC8,0x40,0xCA,0x02,0xFC,0xDC,0x00,0x00,0x00,
0x90,0xFA,0xD0,0x55,0x55,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,0x00,0x00,0x00,0x90,
0xFA,0xD1,0xAA,0x2A,0x80,0x55,0xF0,0x51,0x2C,0xA0,0x00,0xF0,0x50,0xFA,0xA1,0x34,
0x09,0xFA,0xA0,0x38,0x09,0xF0,0x40,0xF0,0x51,0xF8,0xFE,0xFC,0xFC,0xFF,0x60,0x00,
0x00,0x00,0xF8,0xFE,0x04,0xCA,0x3C,0xFC,0xDC,0x00,0x00,0x00,0x90,0xFA,0xD0,0x55,
0x05,0x2C,0xAA,0x00,0xF0,0x50,0xFC,0xDD,0x00,0x00,0x00,0x90,0xFA,0xD1,0xAA,0x02,
0x80,0x55,0xF0,0x51,0x2C,0xA0,0x00,0xF0,0x50,0xFA,0xA1,0x34,0x09,0xFA,0xA0,0x38,
0x09,0xF0,0x40,0xF0,0x51,0xF8,0xFE,0xFC,0xFC,0xFF,0x24,0x00,0x00,0x00,0xF8,0xFE,
0x04,0xF0,0xFC,0xFA,0xA1,0x34,0x09,0xFA,0xA0,0x38,0x09,0xF0,0x40,0xF0,0x45,0xA4,
0xC9,0x07,0x80,0x00,0xDF,0x00,0x00,0x2C,0xFF,0x00,0xF0,0xFC,0xFC,0xA8,0x00,0x00,
0x00,0x90,0xCB,0xCB,0xFC,0xA9,0x00,0x00,0x00,0x90,0xCB,0xCB,0xA1,0xC9,0xEF,0xFC,
0xA9,0x00,0x00,0x00,0x90,0xCB,0xCB,0xA1,0xC9,0xE4,0xF0,0xFC,0x34,0x04,0xD9,0xF8,
0xE0,0x80,0xC9,0xFA,0xCB,0xCB,0x80,0x01,0x02,0x00,0xDF,0xCB,0xCB,0x80,0x00,0x02,
0x01,0xDF,0xCB,0xCB,0x2C,0x9A,0x00,0x02,0x28,0xDF,0xF0,0xFC,0x34,0x04,0xD9,0xF8,
0xE0,0x80,0xC9,0xFA,0xCB,0xCB,0x80,0x00,0x02,0x00,0xDF,0xCB,0xCB,0x80,0x00,0x02,
0x01,0xDF,0xCB,0xCB,0x2C,0x9A,0x00,0x02,0x28,0xDF,0xF0,0xFC
};

/*
5 bset 0x10,(5A5) // FE 80 A5 05 10
2 rets // F0 FC
*/
unsigned char clr_code[] = 
{
0xFE,0x80,0xA5,0x05,0x10,0xF0,0xFC
};

unsigned short end = 0x00FF; // endian detection

int hex_atoi(void *dest, char *src, int dest_len)
{
	int src_last, i, j;
	unsigned char val;

	memset(dest, 0, dest_len);

	src_last = strlen(src) - 1;

	for(i = src_last; i >= 0; i--) {
		if(src[i] >= '0' && src[i] <= '9')
			val = src[i] - 0x30;
		else if(src[i] >= 'a' && src[i] <= 'f')
			val = (src[i] - 0x60) + 9;
		else if(src[i] >= 'A' && src[i] <= 'F')
			val = (src[i] - 0x40) + 9;
		else
			return 1; // invalid hex digit

		j = src_last - i;

		if(j & 1)
			val <<= 4;

		if(*((unsigned char *)&end)) // little endian CPU
			j >>= 1;
		else // big endian CPU
			j = dest_len - ((j >> 1) + 1);

		if(j >= dest_len || j < 0)
			break;

		((unsigned char *)dest)[j] |= val;
	}

	return 0;
}


int main(int argc, char *argv[])
{
	int fd;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	unsigned char param_list[0x10], *buffer, *ptr;
	static unsigned char txblk[0x800];
	unsigned int sum, i, sector, blocks, blklen, buffer_len, sector_size, bytes;
	FILE *fptr;

	if(argc != 5) {
		printf("usage: flashsec47 device firmware_image target_sector_address sector_size\n");
		return 1;
	}

	if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
		perror("open");
		return 1;
	}

	if(!(fptr = fopen(argv[2], "rb"))) {
		perror("fopen");
		close(fd);
		return 1;
	}

	if(hex_atoi(§or_size, argv[4], sizeof(sector_size))) {
		printf("invalid sector size\n");
		close(fd);
		fclose(fptr);
		return 1;
	}

	if(hex_atoi(§or, argv[3], sizeof(sector)) || (sector % sector_size) || sector < 0x90000000) {
		printf("invalid sector address\n");
		close(fd);
		fclose(fptr);
		return 1;
	}

	// Create our flash buffer
	if(fseek(fptr, sector - 0x90000000, SEEK_SET) == -1) {
		perror("fseek");
		close(fd);
		fclose(fptr);
		return 1;
	}

	buffer_len = (sizeof(control_code) + sizeof(erase_code) + sizeof(program_code) + sector_size + 2); // + 2 for 16-bit checksum
	if(buffer_len & 0x03)
		buffer_len += 4 - (buffer_len & 0x03); // round up to 4 byte boundary

	if(!(buffer = (unsigned char *)malloc(buffer_len))) {
		printf("malloc() failed\n");
		close(fd);
		fclose(fptr);
		return 1;
	}

	memset(buffer, 0, buffer_len);

	if(!*((unsigned char *)&end)) { // big endian CPU
		i = sector;
		((unsigned char *)§or)[0] = ((unsigned char *)&i)[3];
		((unsigned char *)§or)[1] = ((unsigned char *)&i)[2];
		((unsigned char *)§or)[2] = ((unsigned char *)&i)[1];
		((unsigned char *)§or)[3] = ((unsigned char *)&i)[0];
		((unsigned char *)&i)[0] = ((unsigned char *)§or_size)[3];
		((unsigned char *)&i)[1] = ((unsigned char *)§or_size)[2];
		((unsigned char *)&i)[2] = ((unsigned char *)§or_size)[1];
		((unsigned char *)&i)[3] = ((unsigned char *)§or_size)[0];
	}
	else
		i = sector_size;

	*((unsigned int *)&control_code[8]) = sector;
	*((unsigned int *)&control_code[30]) = sector;
	*((unsigned int *)&control_code[48]) = i; // little endian sector_size

	memcpy(buffer, control_code, sizeof(control_code));
	ptr = &buffer[sizeof(control_code)];
	memcpy(ptr, erase_code, sizeof(erase_code));
	ptr += sizeof(erase_code);
	memcpy(ptr, program_code, sizeof(program_code));
	ptr += sizeof(program_code);

	if(fread(ptr, sector_size, 1, fptr) != 1) {
		perror("fread");
		close(fd);
		fclose(fptr);
		return 1;
	}

	// Caculate buffer checksum
	for(sum = 0, i = 0; i < buffer_len - 2; i++)
		sum += buffer[i];
	sum = 0x10000 - (sum & 0x0000FFFF);
	buffer[buffer_len - 2] = (unsigned char)(sum & 0x00FF);
	buffer[buffer_len - 1] = (unsigned char)((sum & 0xFF00) >> 8);

	// Initiate modeB (required to configure upload)
	memset(&cgc, 0, sizeof(cgc));
	cgc.cmd[0] = 0xE7;
	cgc.cmd[1] = 0x48;
	cgc.cmd[2] = 0x49;
	cgc.cmd[3] = 0x54;
	cgc.cmd[4] = 0x30;
	cgc.cmd[5] = 0x90;
	cgc.cmd[6] = 0x90;
	cgc.cmd[7] = 0xD0;
	cgc.cmd[8] = 0x01;

	cgc.sense = &sense;
	cgc.data_direction = CGC_DATA_NONE;
	cgc.timeout = 15000;

	if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
		perror("ioctl");
		printf("failed to initiate modeB (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
	}
	else {
		// Stop the disc
		memset(&cgc, 0, sizeof(cgc));
		cgc.cmd[0] = 0x1B;

		cgc.sense = &sense;
		cgc.data_direction = CGC_DATA_NONE;
		cgc.timeout = 15000;

		if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1 && (sense.sense_key != 0x02 || sense.asc != 0x3A)) {
			perror("ioctl");
			printf("failed to stop the disc (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
		}
		else {
			// Set (5A5) bit 4 (required to upload code on the 0047 firmware)
			for(i = 0; i < sizeof(clr_code); i++) {
				// Hitachi poke RAM command
				memset(&cgc, 0, sizeof(cgc));
				cgc.cmd[0] = 0xE7;
				cgc.cmd[1] = 0x48;
				cgc.cmd[2] = 0x49;
				cgc.cmd[3] = 0x54;
				cgc.cmd[4] = 0xCC;
				cgc.cmd[5] = clr_code[i];
				cgc.cmd[8] = (unsigned char)(((0x80000000 + i) & 0xFF000000) >> 24); // address MSB
				cgc.cmd[9] = (unsigned char)(((0x80000000 + i) & 0x00FF0000) >> 16); // address
				cgc.cmd[10] = (unsigned char)(((0x80000000 + i) & 0x0000FF00) >> 8); // address
				cgc.cmd[11] = (unsigned char)((0x80000000 + i) & 0x000000FF); // address LSB
	
				cgc.sense = &sense;
				cgc.data_direction = CGC_DATA_NONE;
				cgc.timeout = 15000;
				
				if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
					perror("ioctl");
					printf("Hitachi poke RAM command failed on byte %u (sense: %02X/%02X/%02X)\n", i, sense.sense_key, sense.asc, sense.ascq);
					close(fd);
					fclose(fptr);
					free(buffer);
					return 1;
				}
			}

			// set (59E) bit 3, if it's not already set (required to execute code in RAM)
			memset(&cgc, 0, sizeof(cgc));
			cgc.cmd[0] = 0x55;
			cgc.cmd[1] = 0x10;
			cgc.cmd[8] = 0x08;

			memset(param_list, 0, sizeof(param_list));
			param_list[1] = 6;
		
			cgc.buffer = param_list;
			cgc.buflen = 8;
			cgc.sense = &sense;
			cgc.data_direction = CGC_DATA_WRITE;
			cgc.timeout = 15000;

			ioctl(fd, CDROM_SEND_PACKET, &cgc);

			// jump to code in RAM (this code sets (5A5) bit 4)
			memset(&cgc, 0, sizeof(cgc));
			cgc.cmd[0] = 0x55;
			cgc.cmd[1] = 0x10;
			cgc.cmd[3] = 0x48; // 'H'
			cgc.cmd[4] = 0x4C; // 'L'
			cgc.cmd[8] = 0x08;

			memset(param_list, 0, sizeof(param_list));
			param_list[1] = 6;
			
			cgc.buffer = param_list;
			cgc.buflen = 8;
			cgc.sense = &sense;
			cgc.data_direction = CGC_DATA_WRITE;
			cgc.timeout = 15000;

			ioctl(fd, CDROM_SEND_PACKET, &cgc);

			// Clear (59E) bit 3 if it's set (this bit will cause us problems if it's left set)
			memset(&cgc, 0, sizeof(cgc));
			cgc.cmd[0] = 0x55;

			cgc.sense = &sense;
			cgc.data_direction = CGC_DATA_NONE;
			cgc.timeout = 15000;

			ioctl(fd, CDROM_SEND_PACKET, &cgc);

			// Configure upload
			memset(&cgc, 0, sizeof(cgc));
			cgc.cmd[0] = 0x55;
			cgc.cmd[1] = 0x10;
			cgc.cmd[8] = 0x10;
			cgc.cmd[11] = 0x01;

			memset(param_list, 0, sizeof(param_list));
			param_list[9] = 0x06;
			param_list[10] = 0x48;
			param_list[11] = 0x4C;
			param_list[14] = (unsigned char)((buffer_len & 0xFF00) >> 8);
			param_list[15] = (unsigned char)(buffer_len & 0x00FF);

			cgc.buffer = param_list;
			cgc.buflen = 0x10;
			cgc.sense = &sense;
			cgc.data_direction = CGC_DATA_WRITE;
			cgc.timeout = 15000;

			if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
				perror("ioctl");
				printf("failed to configure upload (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
			}
			else {
				// Upload our buffer in 0x7F8 byte chunks
				blocks = buffer_len / 0x7F8;
				if(buffer_len % 0x7F8)
				blocks++;
				for(i = 0; i < blocks; i++) {
					if(i != blocks - 1)
						blklen = 0x800;
					else
						blklen = (buffer_len % 0x7F8) + 8;

					memset(&cgc, 0, sizeof(cgc));
					cgc.cmd[0] = 0x55;
					cgc.cmd[1] = 0x10;
					cgc.cmd[7] = (unsigned char)((blklen & 0xFF00) >> 8);
					cgc.cmd[8] = (unsigned char)(blklen & 0x00FF);
					cgc.cmd[11] = 0x01;

					memset(txblk, 0, sizeof(txblk));
					txblk[4] = 0x48;
					txblk[5] = 0x4C;

					memcpy(&txblk[8], &buffer[i * 0x7F8], blklen - 8);

					cgc.buffer = txblk;
					cgc.buflen = blklen;
					cgc.sense = &sense;
					cgc.data_direction = CGC_DATA_WRITE;
					cgc.timeout = 15000;

					if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
						perror("ioctl");
						printf("failed to upload buffer block %u (sense: %02X/%02X/%02X)\n", i, sense.sense_key, sense.asc, sense.ascq);
						close(fd);
						fclose(fptr);
						free(buffer);
						return 1;
					}
				}

				// Execute flasher code
				memset(&cgc, 0, sizeof(cgc));
				cgc.cmd[0] = 0x55;
				cgc.cmd[1] = 0x10;
				cgc.cmd[3] = 0x48;
				cgc.cmd[4] = 0x4C;
				cgc.cmd[6] = 0x01;
				cgc.cmd[11] = 0x01;

				cgc.sense = &sense;
				cgc.data_direction = CGC_DATA_NONE;
				cgc.timeout = 15000;

				if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
					perror("ioctl");
					printf("failed to execute flasher (sense: %02X/%02X/%02X)\n", i, sense.sense_key, sense.asc, sense.ascq);
				}
				else
					printf("done\n");
			}
		}
	}

	close(fd);
	fclose(fptr);
	free(buffer);

	printf("\n");

	return 0;
}

download

Win32 ports.

flashsec47_win.zip - flashsec47 binary for Win2000/XP
flashsec47_win_src.zip - flashsec47 source for Win2000/XP

I can't be bothered to update execcode(2) and pp.

Update: 13th June 2006

A wise man once said,

"Let's just say that I'm quite sure that somebody like Seventhson could do it in 2-3 weeks :) Maybe even a lot faster. I just read a post where he said that he just started looking at the 360 security, so we'll see soon if I was right" - TheSpecialist

And a mere 12 weeks later, I've just booted my first first copied game on the Hitachi.

Why am I posting this if I'm not releasing a hacked FW? Two reasons

A) To boast
B) To annoy all the morons on foros360 who slated me in Spanish when it was falsely rumoured that I'd hacked the Hitachi but not released it. Please deflate your sense of entitlement.

It goes without saying that I owe a huge debt of gratitude to the original firmware researchers on xbh.net (see the participants in the classic FW thread), especially the 6 who succeeded in hacking it first and posted their analysis (see the credits on the original video). And last but not least Dzgx216, who kick-started me on the PFI routines and risked his drive when I didn't have one. Thanks man.

A new project is required. Preferably one that doesn't involve staring at Panasonic opcodes for hours on end.

kev@kev.nu

Back to 360 index