"""File format classes for binary patches.
Sources:
IPS
https://zerosoft.zophar.net/ips.php
UPS
http://individual.utoronto.ca/dmeunier/ups-spec.pdf
"""
from __future__ import annotations
from mrcrowbar import models as mrc
from mrcrowbar import utils
[docs]class IPSRecord( mrc.Block ):
offset_maj = mrc.UInt8( 0x00 )
offset_min = mrc.UInt16_BE( 0x01 )
size = mrc.UInt16_BE( 0x03 )
data = mrc.Bytes( 0x05, length=mrc.Ref( "size" ) )
@property
def offset( self ):
return (self.offset_maj << 16) + self.offset_min
@offset.setter
def offset( self, value ):
self.offset_maj = (value & 0xff0000) >> 16
self.offset_min = value & 0x00ffff
@property
def repr( self ):
return f"offset: 0x{self.offset:06x}, size: 0x{self.size:04x}"
[docs]class IPS( mrc.Block ):
magic = mrc.Const( mrc.Bytes( 0x00, length=5 ), b"PATCH" )
records = mrc.BlockField( IPSRecord, 0x05, stream=True, stream_end=b"EOF" )
@property
def repr( self ):
return f"records: {len( self.records )}"
[docs] def create( self, source, target ):
pass
[docs] def patch( self, source ):
return source
[docs]class UIntVLV( mrc.Field ):
def __init__( self, offset, default=0, **kwargs ):
super().__init__( default=default, **kwargs )
self.offset = offset
[docs] def get_from_buffer( self, buffer, parent=None ):
assert utils.is_bytes( buffer )
offset = mrc.property_get( self.offset, parent )
pointer = offset
total = 0
shift = 0
while pointer < len( buffer ):
test = buffer[pointer]
pointer += 1
total += (test & 0x7f) << shift
shift += 7
if test & 0x80:
break
total += 1 << shift
return total
[docs] def update_buffer_with_value( self, value, buffer, parent=None ):
super().update_buffer_with_value( value, buffer, parent )
offset = mrc.property_get( self.offset, parent )
length = self.get_size( value, parent )
remainder = value
if len( buffer ) < offset + length:
buffer.extend( b"\x00" * (offset + length - len( buffer )) )
for i in range( length ):
buffer[offset + i] = remainder & 0x7f
remainder >>= 7
if remainder == 0:
buffer[offset + i] |= 0x80
break
remainder -= 1
return
[docs] def get_start_offset( self, value, parent=None, index=None ):
assert index is None
offset = mrc.property_get( self.offset, parent )
return offset
[docs] def get_size( self, value, parent=None, index=None ):
assert index is None
test = value
count = 1
test >>= 7
while test > 0:
count += 1
test >>= 7
if test == 0:
break
test -= 1
return count
[docs] def validate( self, value, parent=None ):
if type( value ) != int:
raise mrc.FieldValidationError(
f"Expecting type {self.format_type}, not {type( value[i] )}"
)
if value < 0:
raise mrc.FieldValidationError( "Value must be unsigned" )
return
[docs]class XORData( mrc.Bytes ):
def __init__( self, offset, *args, **kwargs ):
super().__init__( offset, stream_end=b"\x00", *args, **kwargs )
[docs] def validate( self, value, parent=None ):
super().validate( value, parent )
if value.find( b"\x00" ) != -1:
raise mrc.FieldValidationError( "XOR data can't contain a null character" )
return
[docs]class UPSBlock( mrc.Block ):
rel_offset = UIntVLV( 0x00 )
xor_data = XORData( mrc.EndOffset( "rel_offset" ) )
@property
def repr( self ):
return f"rel_offset: 0x{self.rel_offset:x}, size: {len( self.xor_data )}"
[docs]class UPS( mrc.Block ):
STOP_CHECK = lambda buffer, pointer: pointer >= len( buffer ) - 12
magic = mrc.Const( mrc.Bytes( 0x00, length=4 ), b"UPS1" )
input_size = UIntVLV( 0x04 )
output_size = UIntVLV( mrc.EndOffset( "input_size" ) )
blocks = mrc.BlockField(
UPSBlock, mrc.EndOffset( "output_size" ), stream=True, stop_check=STOP_CHECK
)
input_crc32 = mrc.UInt32_LE( mrc.EndOffset( "blocks" ) )
output_crc32 = mrc.UInt32_LE( mrc.EndOffset( "input_crc32" ) )
patch_crc32 = mrc.UInt32_LE( mrc.EndOffset( "output_crc32" ) )