[G4 Q&A, Help & Troubleshooting] xrad:
For the lack of a better place, I want to document here some things I found out about the Download mode (DM)

Before the 20A/20B update, you could use this mode to root an H815 using the Send_Command.exe tool. There was also a Python port, and the R&D thread about QDL. I have then extracted the laf.img partition. In the DL, there are two USB serial connections created. One runs some kind of modem protocol with AT commands (/sbin/usbd daemon), the other runs the /sbin/lafd. The lafd runs an HDLC protocol. The protocol is quite well understood:

7e MSG-DATA FCS 7e

The Frame-Check-Sequence (FCS) is a 16-bit CRC-CCITT.

If the message data contains a 0x7e byte, it is escaped with "0x7d 0x5e".

If the message data contains a 0x7d byte, it is escaped with "0x7d 0x5d".

By disassembling lafd I could determine the following codes:

Command Function
----------------------------------
0x06 NOP
0x0a Reboot phone into normal mode
0x2a Reset DL mode?
0xfa 0x94 0x00 Qfuse stuff?
0xfa 0x94 0x01 Qfuse stuff?
0xfa 0x94 0x02 Qfuse stuff?
0xfa 0x94 0x03 Qfuse stuff?
0xef 0xa0 ??
0xef 0xa1 ??
0xef 0xa2 ??
0xef 0xb0 ??
0xef 0xb1 ??
0xef 0xb2 ??
0xef 0xb5 ??

It seems this is a stripped down/customized version of the original Qualcomm QDL command set. I freely epxerimented with these codes. At one point I almost freaked out because I couldn't leave download mode anymore, even after pulling the battery. Sending the 0xfa 0x94 0x03 command got me out of there, so be careful when you try to experiment with this

Well, so much for that. Now, it appears that before the 20A/20B update, another command 0x3a was supported which changed the lafd from HDLC processing to something internally called LAF Message processing. A LAF message looks like this:

0x00 command one of: WRTE, READ, IOCT, OPEN, CLSE,
UNLK, ERSE, CTRL, HELO, EXEC, RSVD,
INFO, MISC, KILO, DIFF, CRCC
0x04 arg0: 00 00 00 00
0x08 arg1: 00 00 00 00
0x0C arg_opt0: 00 00 00 00
0x10 arg_opt1: 00 00 00 00
0x14 data_length: LL LL LL LL -> Little endian message length
0x18 data_check CS CS 00 00 -> Little endia checksum
0x1C data_magic BA A7 BA BC -> Was: b3 ad ab bc
0x20 DATA...

Send_Command issues the 0x3a command and then uses EXEC messages to execute arbitrary commands on the phone. This 0x3a command does not work anymore, but the complete code for LAF message processing is still there. I could not find out how to activate this mode.

I have not done anything with the modem port, perhaps some of the magic lies there.

Finally here a little perl script which is a mix of several resources I found in various places here on XDA. I want to share it back, it's a good playground for experimenting.

#!/usr/bin/perl
use warnings;
use strict;
use Time::HiRes qw(usleep nanosleep);

# http://blogs.gnome.org/dcbw/2010/04/15/mobile-broadband-and-qualcomm-proprietary-protocols/
# http://cgit.freedesktop.org/ModemManager/ModemManager/tree/libqcdm/src/utils.c

use Device::SerialPort;
use Data::Dump qw(dump);

my @crc_table = (
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
);

sub crc {
my $crc = 0xffff;
foreach my $byte ( split(//, join('',@_)) ) {
$crc = ($crc >> 8) ^ $crc_table[ ($crc ^ ord($byte)) & 0xff ];
}
return $crc ^ 0xffff;
}

my $device = $ARGV[0] || $ENV{DEVICE} || '/dev/ttyUSB2'; # last device! control port!
my $at_qcdmg = $ARGV[1]; # force AT$QCDMG from command-line

my $port = Device::SerialPort->new( $device ) || die "$device: $!";
$port->baudrate(115200) || die "fail $device baudrate";
$port->parity("none") || die "fail $device parity";
$port->databits(8) || die "fail $device databits";
$port->stopbits(1) || die "fail $device stopbits";
$port->handshake("none") || die "fail $device handshake";

$port->write_settings || die "no $device settings";

$port->user_msg(1);
$port->error_msg(1);
$port->debug(1);

$port->read_char_time(3000); # 5 s char timeout
$port->read_const_time(3000); # 1 s read timeout

while ( my $drain = $port->input ) {
warn "# drain $drain\n";
$at_qcdmg = 1;
}

if ( $at_qcdmg ) {
$port->write('AT$QCDMG' . "\r");
warn "DIAG mode: ", $port->read(9 + 3); # echo, OK+cr/lf
}

warn "## using $device\n";

sub as_hex {
my @out;
foreach my $str ( @_ ) {
my $hex = uc unpack( 'H*', $str );
$hex =~ s/(..)/$1 /g if length( $str ) > 2;
$hex =~ s/\s+$//;
push @out, $hex;
}
return join(' | ', @out) . " " . dump(@_);
}

sub diag {
my ($cmd, $message) = @_;

if (not defined $message) {
$message = "";
}

my $data = "$cmd";
$data .= pack('V', 0);
$data .= pack('V', 0);
$data .= pack('V', 0);
$data .= pack('V', 0);
$data .= pack('V', length $message);
$data .= pack('V', 0);
$data .= pack('V', 0xbcabb3ad);
$data .= $message;

my $check = crc($data);

$data = "$cmd";
$data .= pack('V', 0);
$data .= pack('V', 0);
$data .= pack('V', 0);
$data .= pack('V', 0);
$data .= pack('V', length $message);
#$data .= pack('V', crc($message));
$data .= pack('V', $check);
$data .= pack('V', 0xabbcb3ad);
$data .= $message;

print STDERR ">>> $cmd >>> ",as_hex($data),$/;
$port->write($data);
$port->write_drain;

usleep(200000);

block_read();
}

sub block_read {

my $ret = $port->read(1);
while ( my $c = $port->input ) {
print STDERR "<<< ",as_hex($ret . $c), $/;
$ret .= $c;
#	last if $c =~ m/^OK\r/;
}
return $ret;
}

sub escape {
my $buffer = shift;
my @bytes = unpack( 'C*', $buffer );
my @newBytes;
foreach my $byte (@bytes) {
if ( $byte == 0x7e ) {
push @newBytes, 0x7d;
push @newBytes, 0x5e;
}
elsif ( $byte == 0x7d ) {
push @newBytes, 0x7d;
push @newBytes, 0x5d;
}
else {
push @newBytes, $byte;
}
}

return pack( 'C*', @newBytes );
}

sub hdlc_command {
my $cmd = shift;
my $seq = chr($cmd);
$seq .= pack('v', crc($seq));
$seq = escape($seq) . chr(0x7e);
return $seq;
}

sub hdlc_command2 {
my $cmd1 = shift;
my $cmd2 = shift;
my $seq = chr($cmd1) . chr($cmd2);
$seq .= pack('v', crc($seq));
$seq = escape($seq) . chr(0x7e);
return $seq;
}

sub hdlc_command3 {
my $cmd1 = shift;
my $cmd2 = shift;
my $cmd3 = shift;
my $seq = chr($cmd1) . chr($cmd2) . chr($cmd3);
$seq .= pack('v', crc($seq));
$seq = escape($seq) . chr(0x7e);
return $seq;
}

#my $init = hdlc_command(0x06);
#my $init = hdlc_command3(0xfa, 0x94, 0x00); # FA 94 00 FF 00 9A 7F 7E bootloader unlock check?
#my $init = hdlc_command3(0xfa, 0x94, 0x01); # FA 94 01 02 00 36 E9 7E
#my $init = hdlc_command3(0xfa, 0x94, 0x02); # FA 94 02 02 00 52 06 7E
#my $init = hdlc_command3(0xfa, 0x94, 0x03); # FA 94 03 02 00 2B 8F 7E # when stuck in dl mode
#my $init = hdlc_command2(0xef, 0xa0); # EF A0 lots of ids
#my $init = hdlc_command2(0xef, 0xa1); # EF A1 00 00 00 ...
#my $init = hdlc_command2(0xef, 0xa2); # EF A2 00 00 00 ... WebDL?
#my $init = hdlc_command2(0xef, 0xb0);
#my $init = hdlc_command2(0xef, 0xb1);
#my $init = hdlc_command2(0xef, 0xb2);
#my $init = hdlc_command2(0xef, 0xb5);
#my $init = hdlc_command2(0xef, 0x9f);
#print STDERR ">>>> ",as_hex($init), $/;

#my $init = hdlc_command(0x2a); # RESET
#$port->write($init);
#$port->write_drain;
#block_read();

#diag('EXEC', 'echo Hello');
diag('EXEC', 'id');
#diag('HELO');

$port->close || die "can't close port $!";

warn "## $device closed\n";

---------- Post added at 12:40 PM ---------- Previous post was at 12:34 PM ----------

Guys, all this has been discussed in this this thread before. Please search the thread before speculating too much to avoid clutter. The device id encodes CPU specific ID values that cannot be changed. Changing only the IMEI is only half of what would be needed to spoof another phone identity. I don't think this is ever going to be possible.

Perhaps someone knowledgable enough on the lowest Qualcomm chip level could find ways to make the phone accept another aboot.img. But I could imagine this requires tampering with the Qfuses, and might brick a phone completely when done wrong or during development.