From 8863df4b46908eeedba752ac44a06d11ad5ad6fc Mon Sep 17 00:00:00 2001 From: Florian Sonnenberger Date: Sat, 6 Jun 2015 23:42:55 +0200 Subject: [PATCH] Now injects a branch instruction and sets the value elsewhere. This allows for 32 bit values instead of the previous 16 bit limit. Since I could not find any suitable chunks of unused bytes near the original function I had to introduce a detour into the normal control flow. The original function's 16 bit immediate move instruction is replaced by a unconditional branch to a function fragment at the top of flash rom. The new value is loaded from the last 32 bit word of flash and control flow proceeds in the original function after the replaced instruction. --- DK2_LS_FW.d | 209 ++++++++++++++++++++++++++++++++++++------------ DK2_LS_FW2_12.d | 68 ++++++++-------- readme.md | 20 +++++ 3 files changed, 212 insertions(+), 85 deletions(-) create mode 100644 readme.md diff --git a/DK2_LS_FW.d b/DK2_LS_FW.d index 24b1d69..d4ce095 100644 --- a/DK2_LS_FW.d +++ b/DK2_LS_FW.d @@ -13,9 +13,18 @@ import std.stdio : writeln, writefln; import std.file : exists, read, write; import std.conv : parse; import std.typecons : tuple; -import std.bitmanip : littleEndianToNative; +import std.bitmanip : littleEndianToNative, nativeToLittleEndian; import std.digest.crc : CRC32; import std.path : stripExtension; +import std.algorithm : any; +import std.math: abs; + +enum fileHeaderLength = 23; +enum imageHeaderLength = 10; +enum firmwareFileOffset = fileHeaderLength + imageHeaderLength; + +enum baseAddressFlash = 0x08004000; +enum defaultLensSeparation = 63500; void main( string[] args ) { @@ -26,18 +35,12 @@ void main( string[] args ) return; } - uint lensSeperation = args[1].parse!uint; - if( lensSeperation > ushort.max ) - { - writeln("Lens separation too big (max. 65535)"); - return; - } - ushort newValue = lensSeperation & ushort.max; + auto lensSeparation = args[1].parse!uint; string firmwarePath = args[2]; if( firmwarePath.exists() == false ) { - writeln("Firmware path incorrect"); + writeln("Error: Firmware path incorrect"); return; } @@ -45,33 +48,34 @@ void main( string[] args ) auto buf = cast(ubyte[]) firmwarePath.read(); // Very simple check. I should do more validation with the data in the header... - if( buf[0..4] != [0x4F, 0x56, 0x52, 0x46] ) + if( buf[0..4] != ['O', 'V', 'R', 'F'] ) { - writeln("This is not a valid Oculus firmware file."); + writeln("Error: This is not a valid Oculus firmware file."); return; } - // This assumes that there is only one firmware image in the file and that the first one is the + // This assumes that there is only one firmware image in the file and that it is for the DK2 auto firmwareLength = buf[0x1D .. 0x21].littleEndianToNative!uint; - if( firmwareLength + 33 > buf.length ) + if( firmwareLength + firmwareFileOffset > buf.length ) { - writeln("Image size in header wrong or file incomplete!"); + writeln("Error: Image size in header wrong or file incomplete!"); return; } + // Get the slice of the file that only contains the firmware image auto fw = buf[0x21 .. 0x21+firmwareLength]; - writeln("Searching the default value..."); + writeln("Searching the default lens separation value in the file..."); - auto offsets = fw.findOffsetsForMOVimm16( 63500 ); + auto offsets = fw.findOffsetsFor_MOV_imm_T3( defaultLensSeparation ); if( offsets.length == 0 ) { - writeln("Could not find the default lens separation value in the firmware!"); + writeln("Error: Could not find the default lens separation value in the firmware!"); return; } if( offsets.length > 1 ) { - writeln("Found more than one possible addresses to patch:"); + writeln("Error: Found more than one possible addresses to patch:"); foreach( offset; offsets ) { writefln( "0x%.8X", offset ); @@ -79,21 +83,86 @@ void main( string[] args ) return; } - auto offset = offsets[0]; - auto mov = fw[offset..offset+4].decodeMOVimm16(); - writefln("Found default value at address 0x%.8X (register=%d)", offsets[0], mov.register); + auto originalOffset = offsets[0]; + auto mov = fw[originalOffset..originalOffset+4].decode_MOV_imm_T3(); + writefln("Found at file offset 0x%.8X (address 0x%.8X): MOVW R%d, #%d", + firmwareFileOffset+originalOffset, baseAddressFlash+originalOffset, + mov.register, mov.value); + + writeln(); + writeln("Checking if there is free space for the detour function..."); + if( fw[$-256..$].any() ) + { + writefln("Error: There is data or code in the last 256 bytes! Too risky to patch."); + return; + } + writeln("The last 256 bytes contain only zeros. They are assumed to be free space."); + writeln(); + writefln("Changing lens separation to %d micrometers (0x%.8X)...", lensSeparation, lensSeparation); + writeln(); + writeln("The following changes have been made:"); + writeln(); + writeln("File Offset | Address | Data | Decoded Data"); + writeln("------------+----------+-------------+------------------"); + + auto detourFuncOffset = fw.length - 12; // Detour function will be in the last 12 bytes of flash + auto detourFuncAddress = baseAddressFlash + detourFuncOffset; - writefln("Changing lens separation value to %d (0x%.4X)", newValue, newValue); - fw[offset..offset+4] = encodeMOVimm16(mov.register, newValue); + // Replace the 32 bit MOV instruction with a unconditional branch (jump) to the detour + // function that will be written later to the very top of flash memory. + auto branchForwardDifference = detourFuncOffset - (originalOffset+4); + auto branchForwardInstruction = encode_B_T4( branchForwardDifference ); + fw[originalOffset..originalOffset+4] = branchForwardInstruction[]; + writefln(" %.8X | %.8X | %.4X %.4X | B.W %.8X", + firmwareFileOffset+originalOffset, + baseAddressFlash+originalOffset, + branchForwardInstruction[0..2].littleEndianToNative!ushort, + branchForwardInstruction[2..4].littleEndianToNative!ushort, + detourFuncAddress); - writeln("Recalculating CRC32..."); + // First detour function instruction: Load the 32 bit lens separation value + // The parameter addressDifference (4) means that the data is loaded from (PC+4). + // PC (program counter) is the address of the next instruction. + auto detourFunc = fw[detourFuncOffset .. $]; // Get a slice of the last 12 bytes of flash memory + detourFunc[0..4] = encode_LDR_literal_T2( mov.register, 4); // Instr: Load word from (here + 8) + writefln(" %.8X | %.8X | %.4X %.4X | LDR.W R%d, [PC,#%d]", + firmwareFileOffset+detourFuncOffset, + detourFuncAddress, + detourFunc[0..2].littleEndianToNative!ushort, + detourFunc[2..4].littleEndianToNative!ushort, + mov.register, 4); + + // Second detour function instruction: Branch (jump) back into the original function + // Destination is the next instruction after the MOV (that has been replaced by our branch) + auto branchBackDifference = (originalOffset+4) - (detourFuncOffset+8); + detourFunc[4..8] = encode_B_T4( branchBackDifference ); // Instr: Jump to next original instruction + writefln(" %.8X | %.8X | %.4X %.4X | B.W %.8X", + firmwareFileOffset+detourFuncOffset+4, + detourFuncAddress+4, + detourFunc[4..6].littleEndianToNative!ushort, + detourFunc[6..8].littleEndianToNative!ushort, + baseAddressFlash+originalOffset+4); + + // Lens separation value, 32 bit, unsigned(?), little-endian (LSB first) + // This will be loaded by the first detour function instruction + detourFunc[8..12] = lensSeparation.nativeToLittleEndian; + writefln(" %.8X | %.8X | %.8X | Lens separation value: %d", + firmwareFileOffset+detourFuncOffset+8, + detourFuncAddress+8, + detourFunc[8..12].littleEndianToNative!uint, + detourFunc[8..12].littleEndianToNative!uint); + + // Update the firmware header + image CRC32 value CRC32 crc; crc.put( buf[0x1B..0x21+firmwareLength] ); auto crcResult = crc.finish(); buf[0x17..0x1B] = crcResult; - writefln("New CRC32 is %(%.2X%)", crcResult); + writefln(" %.8X | n/a | %.2X %.2X %.2X %.2X | New firmware CRC32 value", + 0x17, buf[0x17], buf[0x18], buf[0x19], buf[0x1A]); - string newFile = firmwarePath.stripExtension() ~ "_Fixed_Lens_Seperation.ovrf"; + writeln(); + + string newFile = firmwarePath.stripExtension() ~ ".patched.ovrf"; writeln("Writing patched firmware image to ", newFile); write(newFile, buf); @@ -105,7 +174,7 @@ void main( string[] args ) writeln(); } -bool isMOVimm16( const ubyte[] instruction ) +bool is_MOV_imm_T3( const ubyte[] instruction ) { // See https://web.eecs.umich.edu/~prabal/teaching/eecs373-f11/readings/ARMv7-M_ARM.pdf (p. 347) return ((instruction[0] & 0b11110000) == 0b01000000) && @@ -113,23 +182,7 @@ bool isMOVimm16( const ubyte[] instruction ) ((instruction[3] & 0b10000000) == 0b00000000); } -ubyte[4] encodeMOVimm16( ubyte register, ushort value ) -{ - // See https://web.eecs.umich.edu/~prabal/teaching/eecs373-f11/readings/ARMv7-M_ARM.pdf (p. 347) - auto imm4 = (0b1111000000000000 & value) >> 12; - auto i = (0b0000100000000000 & value) >> 11; - auto imm3 = (0b0000011100000000 & value) >> 8; - auto imm8 = (0b0000000011111111 & value); - - ubyte[4] result; - result[0] = 0xFF & (0b01000000 | imm4); - result[1] = 0xFF & (0b11110010 | (i << 2)); - result[2] = 0xFF & imm8; - result[3] = 0xFF & ((imm3 << 4) | (register & 0b00001111)); - return result; -} - -auto decodeMOVimm16( const ubyte[] instruction ) +auto decode_MOV_imm_T3( const ubyte[] instruction ) { // See https://web.eecs.umich.edu/~prabal/teaching/eecs373-f11/readings/ARMv7-M_ARM.pdf (p. 347) auto imm4 = (0b00001111 & instruction[0]); @@ -142,10 +195,9 @@ auto decodeMOVimm16( const ubyte[] instruction ) return tuple!("register", "value")(reg, val); } - // Find all offsets of MOV instructions with given 16-bit immediate operand // Assumes the buffer is a firmware image and the image starts at a 2 byte aligned target address -uint[] findOffsetsForMOVimm16( const ubyte[] buffer, ushort operand ) +uint[] findOffsetsFor_MOV_imm_T3( const ubyte[] buffer, ushort operand ) { uint[] results = []; @@ -153,9 +205,9 @@ uint[] findOffsetsForMOVimm16( const ubyte[] buffer, ushort operand ) { auto slice = buffer[offset .. offset+4]; - if( slice.isMOVimm16() ) + if( slice.is_MOV_imm_T3() ) { - auto mov = decodeMOVimm16( slice ); + auto mov = decode_MOV_imm_T3( slice ); if( mov.value == operand ) { results.length = results.length + 1; @@ -164,4 +216,63 @@ uint[] findOffsetsForMOVimm16( const ubyte[] buffer, ushort operand ) } } return results; -} \ No newline at end of file +} + +ubyte[4] encode_B_T4( int addressDifference ) +{ + // See https://web.eecs.umich.edu/~prabal/teaching/eecs373-f11/readings/ARMv7-M_ARM.pdf (p. 239) + if( addressDifference % 2 != 0 ) + { + throw new Exception("encode_B_T4: Address difference LSB set! (Instruction or target unaligned)"); + } + enum MIN = -(2^^24) ; // -16777216 + enum MAX = (2^^24)-1; // 16777215 + + if( addressDifference > MAX || addressDifference < MIN ) + { + throw new Exception("encode_B_T4: Address difference too big to encode in T4!"); + } + + auto imm24 = cast(uint) addressDifference; + imm24 = (imm24 >> 1) & 0x00FFFFFF; + + auto S = (0b10000000_00000000_00000000 & imm24) >> 23; + auto I1 = (0b01000000_00000000_00000000 & imm24) >> 22; + auto I2 = (0b00100000_00000000_00000000 & imm24) >> 21; + auto imm10 = (0b00011111_11111000_00000000 & imm24) >> 11; + auto imm11 = (0b00000000_00000111_11111111 & imm24); + + auto J1 = ~(I1 ^ S) & 0b00000001; + auto J2 = ~(I2 ^ S) & 0b00000001; + + ubyte[4] result; + result[0] = 0xFF & imm10; + result[1] = 0xFF & (0b11110000 | (S << 2) | (imm10 >> 8)); + result[2] = 0xFF & imm11; + result[3] = 0xFF & (0b10010000 | (J1 << 5) | (J2 << 3) | (imm11 >> 8)); + + return result; +} + +ubyte[4] encode_LDR_literal_T2( ubyte register, short addressDifference ) +{ + // See https://web.eecs.umich.edu/~prabal/teaching/eecs373-f11/readings/ARMv7-M_ARM.pdf (p. 289) + if( register > 0b1111 ) + { + throw new Exception("encode_LDR_literal_T2: register invalid"); + } + + auto offset = abs( addressDifference ); + if( offset > 0xFFF ) + { + throw new Exception("encode_LDR_literal_T2: addressDifference too big (max = +-4095)"); + } + + ubyte[4] result; + result[0] = (addressDifference >= 0) ? 0b11011111 : 0b01011111; + result[1] = 0b11111000; + result[2] = 0xFF & offset; + result[3] = 0xFF & (( register << 4) | (offset >> 8)); + + return result; +} diff --git a/DK2_LS_FW2_12.d b/DK2_LS_FW2_12.d index 681cb21..102d423 100644 --- a/DK2_LS_FW2_12.d +++ b/DK2_LS_FW2_12.d @@ -9,40 +9,36 @@ // Open a cmd window in the folder where this file is. (e.g. in Explorer SHIFT+Right-click => "Open cmd window here") // Command: dmd -import std.stdio : writeln; +import std.stdio : writeln, readf; import std.conv : parse; import std.file : read, write; import std.digest.crc : CRC32; - -enum offset = 0x0000B904; // ONLY FOR DK2 FIRMWARE VERSION 2.12 !!! -enum register = 0x0000; // ONLY FOR DK2 FIRMWARE VERSION 2.12 !!! +import std.bitmanip : littleEndianToNative, nativeToLittleEndian; void main( string[] args ) { + uint lensSeparation = 63500; + if( args.length < 2 ) { - writeln("\nNo lens separation specified on command line! (default=63500 | max=65535)\n"); - return; + writeln("\nPlease enter the new lens separation in micrometers: (default=63500)"); + readf("%s", &lensSeparation); } - auto maybeNewValue = args[1].parse!uint; - if( maybeNewValue > ushort.max ) + else { - writeln("\nLens separation value too big! (max=65535)\n"); - return; + lensSeparation = args[1].parse!uint(); } - ushort newValue = maybeNewValue & ushort.max; - - // Read the whole file into a buffer - auto buf = cast(ubyte[]) read( "DK2Firmware_2_12.ovrf" ); - // Use slicing to get only the firmware image without headers (starting at file offset 0x21) - auto fw = buf[0x21 .. $]; +/* HERE COMES THE IMPORTANT PART ... */ - // Calculate the ARM instruction that replaces the default "MOVW R0, #63500" - auto newInstruction = encodeMOVimm16(register, newValue); + // Read the whole file into a buffer + auto buf = cast(ubyte[]) read( "DK2Firmware_2_12.ovrf" ); - // Replace the old instruction with the new one - fw[offset..offset+4] = newInstruction; + // Change the code and add the lens separation data + buf[0x00B925..0x00B929] = [0x10, 0xF0, 0x76, 0xBB]; // B.W 0801FFF4 + buf[0x01C015..0x01C019] = [0xDF, 0xF8, 0x04, 0x00]; // LDR.W R0, [PC,#4] + buf[0x01C019..0x01C01D] = [0xEF, 0xF7, 0x86, 0xBC]; // B.W 0800F908 + buf[0x01C01D..0x01C021] = lensSeparation.nativeToLittleEndian(); // Calculate the CRC32 of the firmware image and its header // Start at file offset 0x1B and stop after the last byte of the file @@ -51,24 +47,24 @@ void main( string[] args ) auto crcResult = crc.finish(); // Replace the old CRC32 value (at file offset 0x17) with the new one - buf[0x17..0x1B] = crcResult; + buf[0x000017..0x00001B] = crcResult[]; // Save the new file write("DK2Firmware_2_12.patched.ovrf", buf); -} - -ubyte[4] encodeMOVimm16( ubyte register, ushort value ) -{ - // See https://web.eecs.umich.edu/~prabal/teaching/eecs373-f11/readings/ARMv7-M_ARM.pdf (p. 347) - auto imm4 = (0b1111000000000000 & value) >> 12; - auto i = (0b0000100000000000 & value) >> 11; - auto imm3 = (0b0000011100000000 & value) >> 8; - auto imm8 = (0b0000000011111111 & value); - ubyte[4] result; - result[0] = 0xFF & (0b01000000 | imm4); - result[1] = 0xFF & (0b11110010 | (i << 2)); - result[2] = 0xFF & imm8; - result[3] = 0xFF & ((imm3 << 4) | (register & 0b00001111)); - return result; +/* ... END OF THE IMPORTANT PART */ + + writeln(); + writeln("Lens separation has been changed to ", + buf[0x01C01D..0x01C021].littleEndianToNative!uint, + " micrometers."); + writeln(); + writeln("Patched firmware was saved as DK2Firmware_2_12.patched.ovrf"); + writeln(); + writeln("Use the official Oculus configuration tool to upload the new"); + writeln(" firmware file to your DK2."); + writeln(); + writeln("USE THIS NEW FIRMWARE AT YOUR OWN RISK! Oculus does not support custom"); + writeln(" firmware on your device. If it breaks, you will have to fix it yourself."); + writeln(); } diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..6e5df76 --- /dev/null +++ b/readme.md @@ -0,0 +1,20 @@ +# Lens Separation Adjuster for DK2 Firmware + +**I do NOT own a DK2 and have NOT tested the firmware files that this program produces. Use at your own risk!** +I **have** checked firmware files produced by this tool in a disassembler and they look good. + +There are two programs: **DK2_LS_FW** and **DK2_LS_FW2_12** + +If you want to understand how patching the firmware works and how patching a newer firmware version than 2.12 would work you should have a look at **DK2_LS_FW**. This program tries to find the CPU instruction that sets the lens separation value and modifies it by injecting own instructions. + +If you just want to change the lens separation value in the original DK2 firmware 2.12 you should use **DK2_LS_FW2_12**. It's the quick and dirty version of the other program and works just for this specific firmware version. + +## Command line arguments for DK2_LS_FW: + DK2_LS_FW + +## Command line arguments for DK2_LS_FW2_12: + DK2_LS_FW [Lens separation value in micrometers] +If you don't specify the lens separation on the command line, the program will ask for it. +The program must be in the same directory as *DK2Firmware_2_12.ovrf*. + +## [Download links](https://github.com/nairol/DK2-Firmware-Patcher/releases) \ No newline at end of file