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).
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;
}
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
}
}
}
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
}
}
}
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;
}
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.
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...
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;
}
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.
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;
}
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.
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;
}
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;
}
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;
}
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;
}
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;
}
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)...
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.
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.
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;
}
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.
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!
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;
}
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.
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;
}
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.
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.