mirror of https://github.com/FOME-Tech/openblt.git
1781 lines
66 KiB
Plaintext
1781 lines
66 KiB
Plaintext
unit FirmwareData;
|
|
//***************************************************************************************
|
|
// Description: Class for managing and manipulating firmware data.
|
|
// File Name: FirmwareData.pas
|
|
//
|
|
//---------------------------------------------------------------------------------------
|
|
// C O P Y R I G H T
|
|
//---------------------------------------------------------------------------------------
|
|
// Copyright (c) 2016 by Feaser http://www.feaser.com All rights reserved
|
|
//
|
|
// This software has been carefully tested, but is not guaranteed for any particular
|
|
// purpose. The author does not offer any warranties and does not guarantee the accuracy,
|
|
// adequacy, or completeness of the software and is not responsible for any errors or
|
|
// omissions or the results obtained from use of the software.
|
|
//
|
|
//---------------------------------------------------------------------------------------
|
|
// L I C E N S E
|
|
//---------------------------------------------------------------------------------------
|
|
// This file is part of OpenBLT. OpenBLT is free software: you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License as published by the Free
|
|
// Software Foundation, either version 3 of the License, or (at your option) any later
|
|
// version.
|
|
//
|
|
// OpenBLT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
// PURPOSE. See the GNU General Public License for more details.
|
|
//
|
|
// You have received a copy of the GNU General Public License along with OpenBLT. It
|
|
// should be located in ".\Doc\license.html". If not, contact Feaser to obtain a copy.
|
|
//
|
|
//***************************************************************************************
|
|
{$IFDEF FPC}
|
|
{$mode objfpc}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
|
|
//***************************************************************************************
|
|
// Includes
|
|
//***************************************************************************************
|
|
uses
|
|
SysUtils, Classes;
|
|
|
|
|
|
//***************************************************************************************
|
|
// Type Definitions
|
|
//***************************************************************************************
|
|
type
|
|
//---------------------------------- TDataSegment -------------------------------------
|
|
TDataSegment = class(TObject)
|
|
private
|
|
// array with actual data bytes of the segment.
|
|
FDataBytes: array of Byte;
|
|
// base memory address for the data of this segment.
|
|
FBaseAddress: Longword;
|
|
// number of data bytes in this segment.
|
|
FDataSize: Integer;
|
|
procedure SetBaseAddress(value: Longword);
|
|
function GetLastAddress: Longword;
|
|
function GetData(index: Integer): Byte;
|
|
procedure GrowDataArray(numOfBytesToAdd: Integer);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
function Add(data: array of Byte; length: Integer; address: Longword): Boolean;
|
|
function Remove(length: Integer; address: Longword): Boolean;
|
|
procedure Dump;
|
|
property Data[index: Integer]: Byte read GetData;
|
|
property Size: Integer read FDataSize;
|
|
property BaseAddress: Longword read FBaseAddress write SetBaseAddress;
|
|
property LastAddress: Longword read GetLastAddress;
|
|
end;
|
|
|
|
//---------------------------------- TDataSegmentList ---------------------------------
|
|
TDataSegmentList=class(TList)
|
|
private
|
|
function Get(Index: Integer): TDataSegment;
|
|
protected
|
|
{ Protected declarations }
|
|
public
|
|
{ Public declarations }
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function Add(segment: TDataSegment): Integer;
|
|
procedure Delete(Index: Integer);
|
|
property Items[Index: Integer]: TDataSegment read Get; default;
|
|
end;
|
|
|
|
//---------------------------------- TFirmwareFileType --------------------------------
|
|
TFirmwareFileType =
|
|
(
|
|
FFT_UNKNOWN,
|
|
FFT_SRECORD,
|
|
FFT_BINARY
|
|
);
|
|
|
|
|
|
//---------------------------------- TFirmwareFileHandler -----------------------------
|
|
TFirmwareFileHandler = class(TObject)
|
|
type
|
|
TFirmwareFileDataReadEvent = procedure(sender: TObject; data: array of Byte; length: Integer; address: Longword) of object;
|
|
protected
|
|
// event handler for when a chunk of data was read from the firmware file
|
|
FOnDataRead: TFirmwareFileDataReadEvent;
|
|
public
|
|
constructor Create; virtual;
|
|
function Load(firmwareFile: String): Boolean; virtual; abstract;
|
|
function Save(firmwareFile: String; segments: TDataSegmentList): Boolean; virtual; abstract;
|
|
property OnDataRead: TFirmwareFileDataReadEvent read FOnDataRead write FOnDataRead;
|
|
end;
|
|
|
|
//---------------------------------- TSRecordFileHandler ------------------------------
|
|
TSRecordFileHandler = class(TFirmwareFileHandler)
|
|
type
|
|
TSRecordLineType = (ltInvalid, ltS0, ltS1, ltS2, ltS3, ltS7, ltS8, ltS9);
|
|
private
|
|
FDataBytesPerLineOnSave: Integer;
|
|
class function GetLineType(line: String): TSRecordLineType; static;
|
|
function GetLineData(line: String; var data: array of Byte; var length: Integer; var address: Longword): Boolean;
|
|
function ConstructLine(data: array of Byte; length: Integer; address: Longword): String;
|
|
public
|
|
constructor Create; override;
|
|
function Load(firmwareFile: String): Boolean; override;
|
|
function Save(firmwareFile: String; segments: TDataSegmentList): Boolean; override;
|
|
class function IsSRecordFile(firmwareFile: String): Boolean; static;
|
|
property DataBytesPerLineOnSave: Integer read FDataBytesPerLineOnSave write FDataBytesPerLineOnSave;
|
|
end;
|
|
|
|
//---------------------------------- TBinaryFileHandler -------------------------------
|
|
TBinaryFileHandler = class(TFirmwareFileHandler)
|
|
private
|
|
public
|
|
constructor Create; override;
|
|
function Load(firmwareFile: String): Boolean; override;
|
|
function Save(firmwareFile: String; segments: TDataSegmentList): Boolean; override;
|
|
end;
|
|
|
|
//---------------------------------- TFirmwareData ------------------------------------
|
|
TFirmwareData = class(TObject)
|
|
private
|
|
// list with data segments of the firmware
|
|
FSegmentList: TDataSegmentList;
|
|
function GetSegmentCount: Integer;
|
|
function GetSegment(index: Integer): TDataSegment;
|
|
procedure SortSegments;
|
|
function FindSegmentIdx(address: Longword): Integer;
|
|
function FindPrevSegmentIdx(address: Longword): Integer;
|
|
function FindNextSegmentIdx(address: Longword): Integer;
|
|
function GetFirmwareFileType(firmwareFile: String): TFirmwareFileType;
|
|
procedure FirmwareFileDataRead(sender: TObject; data: array of Byte; length: Integer; address: Longword);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function AddData(data: array of Byte; length: Integer; address: Longword): Boolean;
|
|
function RemoveData(length: Integer; address: Longword): Boolean;
|
|
procedure ClearData;
|
|
function LoadFromFile(firmwareFile: String; append: Boolean): Boolean;
|
|
function SaveToFile(firmwareFile: String; firmwareFileType: TFirmwareFileType): Boolean;
|
|
procedure Dump;
|
|
property SegmentCount: Integer read GetSegmentCount;
|
|
property Segment[index: Integer]: TDataSegment read GetSegment;
|
|
end;
|
|
|
|
|
|
implementation
|
|
//---------------------------------------------------------------------------------------
|
|
//-------------------------------- TDataSegment -----------------------------------------
|
|
//---------------------------------------------------------------------------------------
|
|
//***************************************************************************************
|
|
// NAME: Create
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Class constructor
|
|
//
|
|
//***************************************************************************************
|
|
constructor TDataSegment.Create;
|
|
begin
|
|
// call inherited constructor
|
|
inherited Create;
|
|
// clear segment contents
|
|
Clear;
|
|
end; //*** end of Create ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Destroy
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Class destructor
|
|
//
|
|
//***************************************************************************************
|
|
destructor TDataSegment.Destroy;
|
|
begin
|
|
// release allocated array memory
|
|
SetLength(FDataBytes, 0);
|
|
// call inherited destructor
|
|
inherited;
|
|
end; //*** end of Destroy ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: SetBaseAddress
|
|
// PARAMETER: value New base address.
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Setter for base address.
|
|
//
|
|
//***************************************************************************************
|
|
procedure TDataSegment.SetBaseAddress(value: Longword);
|
|
begin
|
|
FBaseAddress := value;
|
|
end; //*** end of SetBaseAddress ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: GetLastAddress
|
|
// PARAMETER: none
|
|
// RETURN VALUE: Last address.
|
|
// DESCRIPTION: Getter for last address in the segment.
|
|
//
|
|
//***************************************************************************************
|
|
function TDataSegment.GetLastAddress: Longword;
|
|
begin
|
|
Result := 0;
|
|
if FDataSize > 0 then
|
|
Result := (FBaseAddress + LongWord(FDataSize)) - 1;
|
|
end; //*** end of GetLastAddress ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: GetData
|
|
// PARAMETER: index Index into the data byte array.
|
|
// RETURN VALUE: Byte value.
|
|
// DESCRIPTION: Getter for a byte value from the array at the specified index.
|
|
//
|
|
//***************************************************************************************
|
|
function TDataSegment.GetData(index: Integer): Byte;
|
|
begin
|
|
Result := 0;
|
|
if (index < FDataSize) and (index >= 0) then
|
|
Result := FDataBytes[index];
|
|
end; //*** end of GetData ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Clear
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Allocates more space to the data array if necessary. Allocation is
|
|
// done in chunks of DATA_ARRAY_GROWTH_STEP, because this is more
|
|
// run-time efficient.
|
|
//
|
|
//***************************************************************************************
|
|
procedure TDataSegment.GrowDataArray(numOfBytesToAdd: Integer);
|
|
const
|
|
DATA_ARRAY_GROWTH_STEP: Integer = 1024;
|
|
var
|
|
numOfBytesToGrow: Integer;
|
|
numOfStepsToGrow: Integer;
|
|
desiredArrayLength: Integer;
|
|
begin
|
|
if numOfBytesToAdd > 0 then
|
|
begin
|
|
// check if more space needs to be allocated
|
|
if Length(FDataBytes) < (FDataSize + numOfBytesToAdd) then
|
|
begin
|
|
// determine how many bytes the array needs to grow
|
|
numOfBytesToGrow := (FDataSize + numOfBytesToAdd) - Length(FDataBytes);
|
|
if numOfBytesToGrow > 0 then
|
|
begin
|
|
// determine how many growth steps to add
|
|
numOfStepsToGrow := numOfBytesToGrow div DATA_ARRAY_GROWTH_STEP;
|
|
if (numOfBytesToGrow mod DATA_ARRAY_GROWTH_STEP) > 0 then
|
|
numOfStepsToGrow := numOfStepsToGrow + 1;
|
|
// determine desired new array length
|
|
desiredArrayLength := Length(FDataBytes) + (numOfStepsToGrow * DATA_ARRAY_GROWTH_STEP);
|
|
// grow the array
|
|
SetLength(FDataBytes, desiredArrayLength);
|
|
end;
|
|
end;
|
|
end;
|
|
end; //*** end of GrowDataArray ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Clear
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Clears all databytes from the segment and resets its base address.
|
|
//
|
|
//***************************************************************************************
|
|
procedure TDataSegment.Clear;
|
|
begin
|
|
FBaseAddress := 0;
|
|
FDataSize := 0;
|
|
SetLength(FDataBytes, 0);
|
|
end; //*** end of Clear
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Add
|
|
// PARAMETER: data Array with bytes to add to the segment.
|
|
// length Number of bytes in the array.
|
|
// address Address where to start adding bytes at in the segment.
|
|
// RETURN VALUE: True if the data was added to the segment, False if it couldn't be
|
|
// added. This latter situation happens if the data is not aligned to
|
|
// the data the is already present in the segment.
|
|
// DESCRIPTION: Adds data bytes to the segment starting at the specified address. This
|
|
// function allows a new chunk of data to be added at the front or the
|
|
// rear of the segment, as well as overwriting existing data.
|
|
//
|
|
//***************************************************************************************
|
|
function TDataSegment.Add(data: array of Byte; length: Integer; address: Longword): Boolean;
|
|
var
|
|
byteIdx: Integer;
|
|
numBytesToAppend: Integer;
|
|
begin
|
|
// init result
|
|
Result := False;
|
|
|
|
// check if there is something to add
|
|
if length <= 0 then
|
|
Exit;
|
|
// the following checks assume there is already data in the segment
|
|
if FDataSize > 0 then
|
|
begin
|
|
// check if the new data does not fit at the end
|
|
if address > (GetLastAddress + 1) then
|
|
Exit;
|
|
// check if new data does not fit at the start
|
|
if (address + Longword(length)) < FBaseAddress then
|
|
Exit;
|
|
end;
|
|
|
|
// still here some there is something to add. check if the segment is currently empty
|
|
if (FDataSize = 0) then
|
|
begin
|
|
// make sure enough elements are allocated in the data array
|
|
GrowDataArray(length);
|
|
// set the base address
|
|
FBaseAddress := address;
|
|
// add the data
|
|
for byteIdx := 0 to (length - 1) do
|
|
FDataBytes[byteIdx] := data[byteIdx];
|
|
// set the new size
|
|
FDataSize := length;
|
|
// success
|
|
Result := True;
|
|
end
|
|
// check if all data is for overwriting existing data
|
|
else if (address >= FBaseAddress) and ((address + Longword(length - 1)) <= GetLastAddress) then
|
|
begin
|
|
// overwrite the data
|
|
for byteIdx := 0 to (length - 1) do
|
|
FDataBytes[(address - FBaseAddress) + Longword(byteIdx)] := data[byteIdx];
|
|
// success
|
|
Result := True;
|
|
end
|
|
// check if data should be appended at the end including partial overwrite at the end
|
|
else if (address >= FBaseAddress) and ((address + Longword(length - 1)) > GetLastAddress) then
|
|
begin
|
|
// determine minimal required growth of the array
|
|
numBytesToAppend := (address + Longword(length)) - (FBaseAddress + Longword(FDataSize));
|
|
// make sure enough elements are allocated in the data array
|
|
GrowDataArray(numBytesToAppend);
|
|
// add the data
|
|
for byteIdx := 0 to (length - 1) do
|
|
FDataBytes[(address - FBaseAddress) + Longword(byteIdx)] := data[byteIdx];
|
|
// set the new size
|
|
FDataSize := FDataSize + numBytesToAppend;
|
|
// success
|
|
Result := True;
|
|
end
|
|
// check if data should be appended at the start including partial overwrite at the start
|
|
else if (address < FBaseAddress) and ((address + Longword(length - 1)) <= GetLastAddress) then
|
|
begin
|
|
// determine minimal required growth of the array
|
|
numBytesToAppend := FBaseAddress - address;
|
|
// make sure enough elements are allocated in the data array
|
|
GrowDataArray(numBytesToAppend);
|
|
// set the base address
|
|
FBaseAddress := address;
|
|
// move current contents
|
|
{for byteIdx := 0 to (FDataSize - 1) do
|
|
FDataBytes[numBytesToAppend + byteIdx] := FDataBytes[byteIdx];}
|
|
for byteIdx := (FDataSize - 1) downto 0 do
|
|
FDataBytes[numbytesToAppend + byteIdx] := FDataBytes[byteIdx];
|
|
// add the new data
|
|
for byteIdx := 0 to (length - 1) do
|
|
FDataBytes[byteIdx] := data[byteIdx];
|
|
// set the new size
|
|
FDataSize := FDataSize + numBytesToAppend;
|
|
// success
|
|
Result := True;
|
|
end
|
|
// check if data should be both appended at the start and the end. this is the case when
|
|
// the to be added data is larger then the current segment and overlaps the entire current
|
|
// segment
|
|
else if (address < FBaseAddress) and ((address + Longword(length - 1)) > GetLastAddress) then
|
|
begin
|
|
// set the base address
|
|
FBaseAddress := address;
|
|
// make sure enough elements are allocated in the data array
|
|
GrowDataArray(length);
|
|
// add the new data. no need to first move current contents because they will be
|
|
// fully overwritten anyways
|
|
for byteIdx := 0 to (length - 1) do
|
|
FDataBytes[byteIdx] := data[byteIdx];
|
|
// set the new size
|
|
FDataSize := length;
|
|
// success
|
|
Result := True;
|
|
end;
|
|
end; //*** end of Add ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Remove
|
|
// PARAMETER: length Number of bytes to remove
|
|
// address Address where to start removing data from.
|
|
// RETURN VALUE: True if the data was removed, False if the data could not be removed
|
|
// because this class cannot split a segment.
|
|
// DESCRIPTION: Removes data from the segment. Note that the to be removed data
|
|
// must be aligned to the start or the end of the segment, because this
|
|
// class cannot split a segment.
|
|
//
|
|
//***************************************************************************************
|
|
function TDataSegment.Remove(length: Integer; address: Longword): Boolean;
|
|
var
|
|
numOfBytesToRemove: Integer;
|
|
byteIdx: Integer;
|
|
begin
|
|
Result := True;
|
|
|
|
// if there is nothing to remove then we are done already
|
|
if (length <= 0) or (FDataSize = 0) then
|
|
begin
|
|
Exit;
|
|
end;
|
|
|
|
// if the data is not in this segment the we are also done already
|
|
if (address > GetLastAddress) or ((address + Longword(length - 1)) < FBaseAddress) then
|
|
begin
|
|
Exit;
|
|
end;
|
|
|
|
// check if the to be removed data overlaps with either the end or the start of the
|
|
// segment. if not, then we cannot remove the data because this class cannot split the
|
|
// segment
|
|
if (address > FBaseAddress) and ((address + Longword(length - 1)) < GetLastAddress) then
|
|
begin
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
|
|
// check if the entire segment should be removed
|
|
if (address <= FBaseAddress) and ((address + Longword(length - 1)) >= GetLastAddress) then
|
|
begin
|
|
Clear;
|
|
end
|
|
// check if the to be removed data is at the start of the segment
|
|
else if (address <= FBaseAddress) then
|
|
begin
|
|
numOfBytesToRemove := (address + Longword(length)) - FBaseAddress;
|
|
// move remaining data to the start of the array
|
|
for byteIdx := 0 to (FDataSize - numOfBytesToRemove - 1) do
|
|
FDataBytes[byteIdx] := FDataBytes[byteIdx + numOfBytesToRemove];
|
|
// adjust size and base address
|
|
FDataSize := FDataSize - numOfBytesToRemove;
|
|
FBaseAddress := FBaseAddress + Longword(numOfBytesToRemove);
|
|
end
|
|
// check if the to be removed data is at the end of the segment
|
|
else if (address > FBaseAddress) and ((address + Longword(length - 1)) >= GetLastAddress) then
|
|
begin
|
|
numOfBytesToRemove := GetLastAddress - address + 1;
|
|
FDataSize := FDataSize - numOfBytesToRemove;
|
|
end;
|
|
end; //*** end of Remove ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Dump
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Dumps the segment contents to the standard output for debugging
|
|
// purposes.
|
|
//
|
|
//***************************************************************************************
|
|
procedure TDataSegment.Dump;
|
|
{$IFDEF DEBUG}
|
|
var
|
|
line: String;
|
|
byteCnt: Integer;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF DEBUG}
|
|
// output address and size
|
|
Writeln('Segment base address = $' + Format('%.8X', [BaseAddress]));
|
|
Writeln('Segment data size = ' + IntToStr(Size));
|
|
// output raw data
|
|
Writeln('Segment data contents = ' + sLineBreak);
|
|
line := ' ';
|
|
for byteCnt := 1 to Size do
|
|
begin
|
|
line := line + Format('%.2X ', [Data[byteCnt - 1]]);
|
|
if (byteCnt mod 16) = 0 then
|
|
begin
|
|
Writeln(line);
|
|
line := ' ';
|
|
end;
|
|
end;
|
|
Writeln(line);
|
|
{$ENDIF}
|
|
end; //*** end of Dump
|
|
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
//-------------------------------- TDataSegmentList -------------------------------------
|
|
//---------------------------------------------------------------------------------------
|
|
//***************************************************************************************
|
|
// NAME: Create
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Object constructor. Calls TObject's constructor and initializes
|
|
// the private property variables to their default values.
|
|
//
|
|
//***************************************************************************************
|
|
constructor TDataSegmentList.Create;
|
|
begin
|
|
// call inherited constructor
|
|
inherited Create;
|
|
end; //*** end of Create ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Destroy
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Component destructor.
|
|
//
|
|
//***************************************************************************************
|
|
destructor TDataSegmentList.Destroy;
|
|
var
|
|
idx: Integer;
|
|
begin
|
|
// release allocated heap memory
|
|
for idx := 0 to Count - 1 do
|
|
TDataSegment(Items[idx]).Free;
|
|
inherited;
|
|
end; //*** end of Destroy ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Get
|
|
// PARAMETER: Index Index in the list
|
|
// RETURN VALUE: List item.
|
|
// DESCRIPTION: Obtains an element from the list.
|
|
//
|
|
//***************************************************************************************
|
|
function TDataSegmentList.Get(Index: Integer): TDataSegment;
|
|
begin
|
|
Result := TDataSegment(inherited Get(Index));
|
|
end; //*** end of Get ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Add
|
|
// PARAMETER: segment The data segment to add.
|
|
// RETURN VALUE: Index of the newly added segment in the list if successful, -1
|
|
// otherwise.
|
|
// DESCRIPTION: Adds an element to the list.
|
|
//
|
|
//***************************************************************************************
|
|
function TDataSegmentList.Add(segment: TDataSegment): Integer;
|
|
begin
|
|
// add the entry to the list
|
|
Result := inherited Add(segment);
|
|
// set correct value for error situation
|
|
if Result < 0 then
|
|
Result := -1;
|
|
end; //*** end of Add ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Delete
|
|
// PARAMETER: Index Index in the list.
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Remove an element to the list as the specified index. It is automa-
|
|
// tically freed as well.
|
|
//
|
|
//***************************************************************************************
|
|
procedure TDataSegmentList.Delete(Index: Integer);
|
|
var
|
|
segment: TDataSegment;
|
|
begin
|
|
// only continue if the index is valid
|
|
if (Index >= 0) and (Index < Count) then
|
|
begin
|
|
// obtain object first so we can free it afterwards
|
|
segment := Get(Index);
|
|
// delete it from the list
|
|
inherited Delete(Index);
|
|
// now free it
|
|
segment.Free
|
|
end;
|
|
end; //*** end of Delete ***
|
|
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
//-------------------------------- TFirmwareFileHandler ---------------------------------
|
|
//---------------------------------------------------------------------------------------
|
|
//***************************************************************************************
|
|
// NAME: Create
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Class constructor
|
|
//
|
|
//***************************************************************************************
|
|
constructor TFirmwareFileHandler.Create;
|
|
begin
|
|
// call inherited constructor
|
|
inherited Create;
|
|
// init fields
|
|
FOnDataRead := nil;
|
|
end; //*** end of Create ***
|
|
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
//-------------------------------- TSRecordFileHandler ----------------------------------
|
|
//---------------------------------------------------------------------------------------
|
|
//***************************************************************************************
|
|
// NAME: Create
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Class constructor
|
|
//
|
|
//***************************************************************************************
|
|
constructor TSRecordFileHandler.Create;
|
|
begin
|
|
// call inherited constructor
|
|
inherited Create;
|
|
// set default number of data bytes to add to a line when saving an s-record
|
|
FDataBytesPerLineOnSave := 16;
|
|
end; //*** end of Create ***
|
|
|
|
//***************************************************************************************
|
|
// NAME: Load
|
|
// PARAMETER: firmwareFile Filename with path of the file to load.
|
|
// RETURN VALUE: True is successful, False otherwise.
|
|
// DESCRIPTION: Loads the data in the specified firmware file. The OnDataRead event
|
|
// handler is called each time a chunk of data was read from the file.
|
|
//
|
|
//***************************************************************************************
|
|
function TSRecordFileHandler.Load(firmwareFile: String): Boolean;
|
|
var
|
|
srecordFile: TextFile;
|
|
line: String;
|
|
lineData: array of Byte;
|
|
lineLength: Integer;
|
|
lineAddr: Longword;
|
|
begin
|
|
// init result value and locals
|
|
Result := True;
|
|
|
|
// first check if the file actually exists
|
|
if not FileExists(firmwareFile) then
|
|
begin
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
|
|
// check if the event handler is configured, otherwise it is pointless to go through
|
|
// the file
|
|
if not Assigned(FOnDataRead) then
|
|
begin
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
|
|
// create array with sufficient length
|
|
SetLength(lineData, 1024);
|
|
// go through the lines in the file to try and detect a line that is formatted as an
|
|
// S-record. start by getting the file handle and going to the start of the file
|
|
AssignFile(srecordFile, firmwareFile);
|
|
Reset(srecordFile);
|
|
// loop through the lines
|
|
while not Eof(srecordFile) do
|
|
begin
|
|
// read the next line from the file
|
|
ReadLn(srecordFile, line);
|
|
// parse the line to extract the data bytes and address info
|
|
if GetLineData(line, lineData, lineLength, lineAddr) then
|
|
begin
|
|
// invoke the event handler to inform about the new data
|
|
FOnDataRead(Self, lineData, lineLength, lineAddr);
|
|
end;
|
|
end;
|
|
// close the file
|
|
CloseFile(srecordFile);
|
|
// release array
|
|
SetLength(lineData, 0);
|
|
end; //*** end of Load ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Save
|
|
// PARAMETER: firmwareFile Filename with path of the file to save.
|
|
// segments List with data segments that need to be saved.
|
|
// RETURN VALUE: True is successful, False otherwise.
|
|
// DESCRIPTION: Saves the firmware data to the specified firmware file.
|
|
//
|
|
//***************************************************************************************
|
|
function TSRecordFileHandler.Save(firmwareFile: String; segments: TDataSegmentList): Boolean;
|
|
var
|
|
srecordFile: TextFile;
|
|
segmentIdx: Integer;
|
|
byteIdx: Integer;
|
|
line: String;
|
|
programData: array of Byte;
|
|
currentAddress: Longword;
|
|
currentByteCnt: Integer;
|
|
firmwareFileBytes: TBytes;
|
|
headerByteCount: Integer;
|
|
checksumCalc: Byte;
|
|
addrByteCnt: Integer;
|
|
charIdx: Integer;
|
|
begin
|
|
// init result
|
|
Result := True;
|
|
|
|
// check if there is actually something to write
|
|
if segments.Count <= 0 then
|
|
begin
|
|
// no program data to write
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
|
|
// open the firmware file for writing
|
|
AssignFile(srecordFile, firmwareFile);
|
|
ReWrite(srecordFile);
|
|
|
|
// ---- add the S0 header line that contains the filename ----
|
|
SetLength(firmwareFileBytes, Length(firmwareFile));
|
|
for charIdx := 1 to Length(firmwareFile) do
|
|
firmwareFileBytes[charIdx - 1] := Ord(firmwareFile[charIdx]);
|
|
headerByteCount := 3 + Length(firmwareFileBytes);
|
|
line := 'S0' + Format('%.2X', [headerByteCount]) + '0000';
|
|
for byteIdx := 0 to (Length(firmwareFileBytes) - 1) do
|
|
begin
|
|
line := line + Format('%.2X', [firmwareFileBytes[byteIdx]]);
|
|
end;
|
|
// compute checksum
|
|
checksumCalc := 0;
|
|
for byteIdx := 0 to (headerByteCount - 1) do
|
|
begin
|
|
checksumCalc := checksumCalc + StrToInt('$' + Copy(line, 3+(byteIdx*2), 2));
|
|
end;
|
|
// convert to one's complement and add it
|
|
checksumCalc := not checksumCalc;
|
|
line := line + Format('%.2X', [checksumCalc]);
|
|
// add it to the file
|
|
WriteLn(srecordFile, line);
|
|
|
|
// ---- add the program data lines ----
|
|
// init program data array
|
|
SetLength(programData, DataBytesPerLineOnSave);
|
|
// loop through all segments
|
|
for segmentIdx := 0 to (segments.Count - 1) do
|
|
begin
|
|
// set current address and byte count
|
|
currentAddress := segments[segmentIdx].BaseAddress;
|
|
currentByteCnt := 0;
|
|
// progress the data
|
|
for byteIdx := 0 to (segments[segmentIdx].Size - 1) do
|
|
begin
|
|
// add the program data byte
|
|
programData[currentByteCnt] := segments[segmentIdx].Data[byteIdx];
|
|
currentByteCnt := currentByteCnt + 1;
|
|
// check if desired program data bytes per line is reached
|
|
if currentByteCnt = DataBytesPerLineOnSave then
|
|
begin
|
|
// construct the s-record line and add it to the file
|
|
line := ConstructLine(programData, currentByteCnt, currentAddress);
|
|
WriteLn(srecordFile, line);
|
|
// refresh loop variables
|
|
currentAddress := currentAddress + Longword(currentByteCnt);
|
|
currentByteCnt := 0;
|
|
end;
|
|
end;
|
|
// check if there are still bytes left to write to the file
|
|
if currentByteCnt > 0 then
|
|
begin
|
|
// construct the s-record line and add it to the file
|
|
line := ConstructLine(programData, currentByteCnt, currentAddress);
|
|
WriteLn(srecordFile, line);
|
|
end;
|
|
end;
|
|
|
|
// ---- add the termination line ----
|
|
// determine the line type to use
|
|
if segments[0].BaseAddress >= $FFFFFF then
|
|
begin
|
|
addrByteCnt := 4;
|
|
line := 'S705' + Format('%.8X', [segments[0].BaseAddress]);
|
|
end
|
|
else if segments[0].BaseAddress >= $FFFF then
|
|
begin
|
|
addrByteCnt := 3;
|
|
line := 'S804' + Format('%.6X', [segments[0].BaseAddress]);
|
|
end
|
|
else
|
|
begin
|
|
addrByteCnt := 2;
|
|
line := 'S903' + Format('%.4X', [segments[0].BaseAddress]);
|
|
end;
|
|
// compute checksum
|
|
checksumCalc := 0;
|
|
for byteIdx := 0 to addrByteCnt do
|
|
begin
|
|
checksumCalc := checksumCalc + StrToInt('$' + Copy(line, 3+(byteIdx*2), 2));
|
|
end;
|
|
// convert to one's complement and add it
|
|
checksumCalc := not checksumCalc;
|
|
line := line + Format('%.2X', [checksumCalc]);
|
|
WriteLn(srecordFile, line);
|
|
|
|
// close the file
|
|
CloseFile(srecordFile);
|
|
end; //*** end of Save ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: IsSRecordFile
|
|
// PARAMETER: firmwareFile Filename with path of the file to check.
|
|
// RETURN VALUE: True is the file has the S-Record format, False otherwise.
|
|
// DESCRIPTION: Checks if the file contains data formatted as an S-Record.
|
|
//
|
|
//***************************************************************************************
|
|
class function TSRecordFileHandler.IsSRecordFile(firmwareFile: String): Boolean;
|
|
var
|
|
srecordFile: TextFile;
|
|
line: String;
|
|
begin
|
|
// init result value and locals
|
|
Result := False;
|
|
|
|
// first check if the file actually exists
|
|
if not FileExists(firmwareFile) then
|
|
Exit;
|
|
|
|
// go through the lines in the file to try and detect a line that is formatted as an
|
|
// S-record. start by getting the file handle and going to the start of the file
|
|
AssignFile(srecordFile, firmwareFile);
|
|
Reset(srecordFile);
|
|
// loop through the lines
|
|
while not Eof(srecordFile) do
|
|
begin
|
|
ReadLn(srecordFile, line); // read line from file
|
|
if (TSRecordFileHandler.GetLineType(line) = ltS1) or
|
|
(TSRecordFileHandler.GetLineType(line) = ltS2) or
|
|
(TSRecordFileHandler.GetLineType(line) = ltS3) then
|
|
begin
|
|
// valid S-Record
|
|
Result := true;
|
|
// no need to continue looping
|
|
Break;
|
|
end;
|
|
end;
|
|
// close the file
|
|
CloseFile(srecordFile);
|
|
end; //*** end of IsSRecordFile ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: GetLineType
|
|
// PARAMETER: Line from S-Record
|
|
// RETURN VALUE: line type
|
|
// DESCRIPTION: Determines what type of S-Record line we're dealing with.
|
|
//
|
|
//***************************************************************************************
|
|
class function TSRecordFileHandler.GetLineType(line: String): TSRecordLineType;
|
|
begin
|
|
Result := ltInvalid;
|
|
|
|
if Pos('S0', UpperCase(line)) > 0 then
|
|
begin
|
|
Result := ltS0;
|
|
Exit;
|
|
end;
|
|
|
|
if Pos('S1', UpperCase(line)) > 0 then
|
|
begin
|
|
Result := ltS1;
|
|
Exit;
|
|
end;
|
|
|
|
if Pos('S2', UpperCase(line)) > 0 then
|
|
begin
|
|
Result := ltS2;
|
|
Exit;
|
|
end;
|
|
|
|
if Pos('S3', UpperCase(line)) > 0 then
|
|
begin
|
|
Result := ltS3;
|
|
Exit;
|
|
end;
|
|
|
|
if Pos('S7', UpperCase(line)) > 0 then
|
|
begin
|
|
Result := ltS7;
|
|
Exit;
|
|
end;
|
|
|
|
if Pos('S8', UpperCase(line)) > 0 then
|
|
begin
|
|
Result := ltS8;
|
|
Exit;
|
|
end;
|
|
|
|
if Pos('S9', UpperCase(line)) > 0 then
|
|
begin
|
|
Result := ltS9;
|
|
Exit;
|
|
end;
|
|
end; //*** end of GetLineType ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: GetLineData
|
|
// PARAMETER: line Line from S-Record.
|
|
// data Array where the data bytes are to be stored.
|
|
// length Storage for number of bytes that were read.
|
|
// address Storage for the address found on the S-Record line.
|
|
// RETURN VALUE: True is successful, False otherwise
|
|
// DESCRIPTION: Extracts the data bytes and address from the S-Record line.
|
|
//
|
|
//***************************************************************************************
|
|
function TSRecordFileHandler.GetLineData(line: String; var data: array of Byte; var length: Integer; var address: Longword): Boolean;
|
|
var
|
|
lineType: TSRecordLineType;
|
|
byteCount: Integer;
|
|
byteIdx: Integer;
|
|
checksumRead: Byte;
|
|
checksumCalc: Byte;
|
|
addrByteCnt: Integer;
|
|
begin
|
|
// init result
|
|
Result := True;
|
|
// read out the line type
|
|
lineType := TSRecordFileHandler.GetLineType(line);
|
|
// set line type specific settings
|
|
case lineType of
|
|
ltS1:
|
|
begin
|
|
addrByteCnt := 2;
|
|
end;
|
|
ltS2:
|
|
begin
|
|
addrByteCnt := 3;
|
|
end;
|
|
ltS3:
|
|
begin
|
|
addrByteCnt := 4;
|
|
end;
|
|
else
|
|
// line does not contain program data
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
|
|
// extract count value from the line
|
|
byteCount := StrToInt('$' + Copy(line, 3, 2));
|
|
// extract address
|
|
address := StrToInt('$' + Copy(line, 5, addrByteCnt*2));
|
|
// determine number of data bytes = total bytes - address - checksum
|
|
length := byteCount - addrByteCnt - 1;
|
|
// read the checksum
|
|
checksumRead := StrToInt('$' + Copy(line, (5+(addrByteCnt*2))+(length*2), 2));
|
|
// compute checksum
|
|
checksumCalc := 0;
|
|
for byteIdx := 0 to (byteCount - 1) do
|
|
begin
|
|
checksumCalc := checksumCalc + StrToInt('$' + Copy(line, 3+(byteIdx*2), 2));
|
|
end;
|
|
// convert to one's complement
|
|
checksumCalc := not checksumCalc;
|
|
// validate checksum
|
|
if checksumCalc <> checksumRead then
|
|
begin
|
|
// line contains an invalid checksum
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
// read all the data bytes
|
|
for byteIdx := 0 to (length - 1) do
|
|
begin
|
|
data[byteIdx] := StrToInt('$' + Copy(line, (5+(addrByteCnt*2))+(byteIdx*2), 2));
|
|
end;
|
|
end; //*** end of GetLineData ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: ConstructLine
|
|
// PARAMETER: data Array with data bytes.
|
|
// length Number of bytes in the array.
|
|
// address Base address of the data.
|
|
// RETURN VALUE: The constructed line if successful, '' otherwise.
|
|
// DESCRIPTION: Constructs an S-record line with program data.
|
|
//
|
|
//***************************************************************************************
|
|
function TSRecordFileHandler.ConstructLine(data: array of Byte; length: Integer; address: Longword): String;
|
|
var
|
|
addrByteCnt: Integer;
|
|
byteCount: Integer;
|
|
addressStr: String;
|
|
byteIdx: Integer;
|
|
checksumCalc: Byte;
|
|
begin
|
|
// determine the line type to use
|
|
if address >= $FFFFFF then
|
|
begin
|
|
addrByteCnt := 4;
|
|
addressStr := Format('%.8X', [address]);
|
|
Result := 'S3';
|
|
end
|
|
else if address >= $FFFF then
|
|
begin
|
|
addrByteCnt := 3;
|
|
addressStr := Format('%.6X', [address]);
|
|
Result := 'S2';
|
|
end
|
|
else
|
|
begin
|
|
addrByteCnt := 2;
|
|
addressStr := Format('%.4X', [address]);
|
|
Result := 'S1';
|
|
end;
|
|
// determine number of bytes after the Sx, excluding checksum
|
|
byteCount := addrByteCnt + length + 1;
|
|
// add the count and address
|
|
Result := Result + Format('%.2X', [byteCount]) + addressStr;
|
|
// add all the data bytes
|
|
for byteIdx := 0 to (length - 1) do
|
|
begin
|
|
Result := Result + Format('%.2X', [data[byteIdx]]);
|
|
end;
|
|
// compute checksum
|
|
checksumCalc := 0;
|
|
for byteIdx := 0 to (byteCount - 1) do
|
|
begin
|
|
checksumCalc := checksumCalc + StrToInt('$' + Copy(Result, 3+(byteIdx*2), 2));
|
|
end;
|
|
// convert to one's complement
|
|
checksumCalc := not checksumCalc;
|
|
// add the checksum
|
|
Result := Result + Format('%.2X', [checksumCalc]);
|
|
end; //*** end of ConstructLine ***/
|
|
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
//-------------------------------- TBinaryFileHandler -----------------------------------
|
|
//---------------------------------------------------------------------------------------
|
|
//***************************************************************************************
|
|
// NAME: Create
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Class constructor
|
|
//
|
|
//***************************************************************************************
|
|
constructor TBinaryFileHandler.Create;
|
|
begin
|
|
// call inherited constructor
|
|
inherited Create;
|
|
end; //*** end of Create ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Load
|
|
// PARAMETER: firmwareFile Filename with path of the file to load.
|
|
// RETURN VALUE: True is successful, False otherwise.
|
|
// DESCRIPTION: Loads the data in the specified firmware file. The OnDataRead event
|
|
// handler is called each time a chunk of data was read from the file.
|
|
//
|
|
//***************************************************************************************
|
|
function TBinaryFileHandler.Load(firmwareFile: String): Boolean;
|
|
begin
|
|
// loading from a binary file is not yet supported
|
|
Result := False;
|
|
end; //*** end of Load ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Save
|
|
// PARAMETER: firmwareFile Filename with path of the file to save.
|
|
// segments List with data segments that need to be saved.
|
|
// RETURN VALUE: True is successful, False otherwise.
|
|
// DESCRIPTION: Saves the firmware data to the specified firmware file.
|
|
//
|
|
//***************************************************************************************
|
|
function TBinaryFileHandler.Save(firmwareFile: String; segments: TDataSegmentList): Boolean;
|
|
var
|
|
startAddr: Longword;
|
|
endAddr: Longword;
|
|
segmentIdx: Integer;
|
|
progData: array of Byte;
|
|
progLen: Longword;
|
|
byteIdx: Longword;
|
|
binaryFile: File;
|
|
begin
|
|
// init result and locals
|
|
Result := False;
|
|
startAddr := $FFFFFFFF;
|
|
endAddr := 0;
|
|
|
|
// first need to determine the start and end addresses for the firmware data
|
|
for segmentIdx := 0 to (segments.Count - 1) do
|
|
begin
|
|
if segments[segmentIdx].BaseAddress < startAddr then
|
|
startAddr := segments[segmentIdx].BaseAddress;
|
|
if segments[segmentIdx].LastAddress > endAddr then
|
|
endAddr := segments[segmentIdx].LastAddress;
|
|
end;
|
|
|
|
// plausibility check
|
|
if startAddr > endAddr then
|
|
Exit;
|
|
|
|
// calculate program length
|
|
progLen := endAddr - startAddr + 1;
|
|
|
|
// init array size such that it can hold all program data, including filler bytes
|
|
// for possible
|
|
SetLength(progData, progLen);
|
|
// fill it completely with filler bytes
|
|
for byteIdx := 0 to (progLen - 1) do
|
|
progData[byteIdx] := $FF;
|
|
|
|
// add the segment data to the program data array
|
|
for segmentIdx := 0 to (segments.Count - 1) do
|
|
begin
|
|
// loop through segment data bytes one-by-one
|
|
for byteIdx := 0 to (segments[segmentIdx].Size - 1) do
|
|
begin
|
|
// at the byte at the correct index
|
|
progData[(segments[segmentIdx].BaseAddress - startAddr) + byteIdx] := segments[segmentIdx].Data[byteIdx];
|
|
end;
|
|
end;
|
|
|
|
// open the firmware file for writing
|
|
AssignFile(binaryFile, firmwareFile);
|
|
// define a record to be of size 1 byte.
|
|
ReWrite(binaryFile, 1);
|
|
|
|
// write all program bytes one-by-one to the file
|
|
for byteIdx := 0 to (progLen - 1) do
|
|
begin
|
|
BlockWrite(binaryFile, progData[byteIdx], 1);
|
|
end;
|
|
|
|
// clean up
|
|
CloseFile(binaryFile);
|
|
Result := True;
|
|
end; //*** end of Save ***
|
|
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
//-------------------------------- TFirmwareData ----------------------------------------
|
|
//---------------------------------------------------------------------------------------
|
|
//***************************************************************************************
|
|
// NAME: Create
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Class constructor
|
|
//
|
|
//***************************************************************************************
|
|
constructor TFirmwareData.Create;
|
|
begin
|
|
// call inherited constructor
|
|
inherited Create;
|
|
// create empty data segments list
|
|
FSegmentList := TDataSegmentList.Create();
|
|
end; //*** end of Create ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Destroy
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Class destructor
|
|
//
|
|
//***************************************************************************************
|
|
destructor TFirmwareData.Destroy;
|
|
begin
|
|
// release the data segments list
|
|
FSegmentList.Free;
|
|
// call inherited destructor
|
|
inherited;
|
|
end; //*** end of Destroy ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: GetSegmentCount
|
|
// PARAMETER: none
|
|
// RETURN VALUE: Count of data segments.
|
|
// DESCRIPTION: Getter for the count of data segments with firmware data.
|
|
//
|
|
//***************************************************************************************
|
|
function TFirmwareData.GetSegmentCount: Integer;
|
|
begin
|
|
Result := FSegmentList.Count;
|
|
end; //*** end of GetSegmentCount ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: GetSegment
|
|
// PARAMETER: index Index of the data segment to get.
|
|
// RETURN VALUE: Data segment if successful, nil otherwise.
|
|
// DESCRIPTION: Getter for a data segment at the specified index.
|
|
//
|
|
//***************************************************************************************
|
|
function TFirmwareData.GetSegment(index: Integer): TDataSegment;
|
|
begin
|
|
Result := nil;
|
|
if (index >= 0) and (index < FSegmentList.Count) then
|
|
Result := FSegmentList[index];
|
|
end; //*** end of GetSegment ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: FirmwareDataCompareSegments
|
|
// PARAMETER: Item1 First item for the comparison.
|
|
// Item2 Second item for the comparison.
|
|
// RETURN VALUE: 1 if Item1's identifier is larger, -1 if Item1's identifier is
|
|
// smaller, 0 if the identifiers are equal.
|
|
// DESCRIPTION: Custom sorting routine for the entries in filter.
|
|
//
|
|
//***************************************************************************************
|
|
function FirmwareDataCompareSegments(Item1, Item2: Pointer): Integer;
|
|
begin
|
|
Result := TDataSegment(Item1).BaseAddress - TDataSegment(Item2).BaseAddress;
|
|
end; //*** end of FirmwareDataCompareSegments ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: SortSegments
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Sorts the segments based on the base address of the segment.
|
|
//
|
|
//***************************************************************************************
|
|
procedure TFirmwareData.SortSegments;
|
|
begin
|
|
FSegmentList.Sort(@FirmwareDataCompareSegments);
|
|
end; //*** end of SortSegments ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: FindSegmentIdx
|
|
// PARAMETER: address Address to match
|
|
// RETURN VALUE: Segment index if found, -1 otherwise.
|
|
// DESCRIPTION: Searches for a segment that contains the specified address.
|
|
//
|
|
//***************************************************************************************
|
|
function TFirmwareData.FindSegmentIdx(address: Longword): Integer;
|
|
var
|
|
segmentIdx: Integer;
|
|
begin
|
|
Result := -1;
|
|
// loop through segments
|
|
for segmentIdx := 0 to (GetSegmentCount - 1) do
|
|
begin
|
|
// does this address fall into this segment?
|
|
if (address >= FSegmentList[segmentIdx].BaseAddress) and (address <= FSegmentList[segmentIdx].LastAddress) then
|
|
begin
|
|
// match found
|
|
Result := segmentIdx;
|
|
// no need to continue loop
|
|
Break;
|
|
end;
|
|
end;
|
|
end; //*** end of FindSegmentIdx ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: FindPrevSegmentIdx
|
|
// PARAMETER: address Address to match
|
|
// RETURN VALUE: Segment index if found, -1 otherwise.
|
|
// DESCRIPTION: Searches for the previous segment. So a segment who's lastaddress is
|
|
// closest to the specified address.
|
|
//
|
|
//***************************************************************************************
|
|
function TFirmwareData.FindPrevSegmentIdx(address: Longword): Integer;
|
|
var
|
|
segmentIdx: Integer;
|
|
begin
|
|
Result := -1;
|
|
// loop through segments and keep in mind that they are ordered by increasing memory
|
|
// addresses
|
|
for segmentIdx := (GetSegmentCount - 1) downto 0 do
|
|
begin
|
|
if FSegmentList[segmentIdx].LastAddress < address then
|
|
begin
|
|
// match found
|
|
Result := segmentIdx;
|
|
Break;
|
|
end;
|
|
end;
|
|
end; //*** end of FindPrevSegmentIdx ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: FindNextSegmentIdx
|
|
// PARAMETER: address Address to match
|
|
// RETURN VALUE: Segment index if found, -1 otherwise.
|
|
// DESCRIPTION: Searches for the next segment. So a segment who's baseaddress is
|
|
// closest to the specified address.
|
|
//
|
|
//***************************************************************************************
|
|
function TFirmwareData.FindNextSegmentIdx(address: Longword): Integer;
|
|
var
|
|
segmentIdx: Integer;
|
|
begin
|
|
Result := -1;
|
|
// loop through segments and keep in mind that they are ordered by increasing memory
|
|
// addresses
|
|
for segmentIdx := 0 to (GetSegmentCount - 1) do
|
|
begin
|
|
if FSegmentList[segmentIdx].BaseAddress > address then
|
|
begin
|
|
// match found
|
|
Result := segmentIdx;
|
|
Break;
|
|
end;
|
|
end;
|
|
end; //*** end of FindNextSegmentIdx ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: GetFirmwareFileType
|
|
// PARAMETER: firmwareFile Filename with path of the file to check.
|
|
// RETURN VALUE: The type of the firmware file.
|
|
// DESCRIPTION: Determines the type of the firmware file.
|
|
//
|
|
//***************************************************************************************
|
|
function TFirmwareData.GetFirmwareFileType(firmwareFile: String): TFirmwareFileType;
|
|
begin
|
|
// init result to unknown file type
|
|
Result := FFT_UNKNOWN;
|
|
|
|
// check if the file is formatted as an S-Record
|
|
if TSRecordFileHandler.IsSRecordFile(firmwareFile) then
|
|
Result := FFT_SRECORD;
|
|
end; //*** end of GetFirmwareFileType ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: FirmwareFileDataRead
|
|
// PARAMETER: sender Object that triggered the event
|
|
// data Array with data bytes that were read.
|
|
// length Number of data bytes that were read.
|
|
// address Start memory address that the bytes belong to.
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Callback for when data was read from a firmware file during loading.
|
|
//
|
|
//***************************************************************************************
|
|
procedure TFirmwareData.FirmwareFileDataRead(sender: TObject; data: array of Byte; length: Integer; address: Longword);
|
|
begin
|
|
// add the newly read firmware data
|
|
AddData(data, length, address);
|
|
end; //*** end of FirmwareFileDataRead ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: AddData
|
|
// PARAMETER: data Array with bytes to add.
|
|
// length Number of bytes in the array.
|
|
// address Address where to start adding bytes.
|
|
// RETURN VALUE: True is successful, False otherwise.
|
|
// DESCRIPTION: Adds firmware data to the data segments. Segments are automatically
|
|
// created and joined where needed.
|
|
//
|
|
//***************************************************************************************
|
|
function TFirmwareData.AddData(data: array of Byte; length: Integer; address: Longword): Boolean;
|
|
var
|
|
firstSegmentIdx: Integer;
|
|
lastSegmentIdx: Integer;
|
|
segmentIdx: Integer;
|
|
joinedData: array of Byte;
|
|
joinedSize: Integer;
|
|
byteIdx: Integer;
|
|
begin
|
|
Result := True;
|
|
|
|
// find the starting and ending segment index
|
|
firstSegmentIdx := FindSegmentIdx(address);
|
|
lastSegmentIdx := FindSegmentIdx(address + Longword(length) - 1);
|
|
|
|
// try to snap segments if they are directly next to another one
|
|
if firstSegmentIdx = -1 then
|
|
begin
|
|
segmentIdx := FindPrevSegmentIdx(address);
|
|
if segmentIdx <> - 1 then
|
|
begin
|
|
if address = (FSegmentList[segmentIdx].LastAddress + 1) then
|
|
firstSegmentIdx := segmentIdx;
|
|
end;
|
|
end;
|
|
if lastSegmentIdx = -1 then
|
|
begin
|
|
segmentIdx := FindNextSegmentIdx(address + Longword(length) - 1);
|
|
if segmentIdx <> - 1 then
|
|
begin
|
|
if (address + Longword(length)) = FSegmentList[segmentIdx].BaseAddress then
|
|
lastSegmentIdx := segmentIdx;
|
|
end;
|
|
end;
|
|
|
|
// begin and end belongs to existing segments?
|
|
if (firstSegmentIdx <> -1) and (lastSegmentIdx <> -1) then
|
|
begin
|
|
// create new data array with a copy of the first segment at the start and a copy
|
|
// of the last segment at the end.
|
|
joinedSize := (FSegmentList[lastSegmentIdx].LastAddress + 1) - FSegmentList[firstSegmentIdx].BaseAddress;
|
|
SetLength(joinedData, joinedSize);
|
|
for byteIdx := 0 to (FSegmentList[firstSegmentIdx].Size - 1) do
|
|
joinedData[byteIdx] := FSegmentList[firstSegmentIdx].Data[byteIdx];
|
|
for byteIdx := 0 to (FSegmentList[lastSegmentIdx].Size - 1) do
|
|
joinedData[(joinedSize - FSegmentList[lastSegmentIdx].Size) + byteIdx] := FSegmentList[lastSegmentIdx].Data[byteIdx];
|
|
// now remove the affected segments in preparation to replace them with 1 big new one
|
|
// but not the first one, because this one will be resized to be a big one that holds
|
|
// all the data. keep in mind that the indexes change after deleting a segment, so
|
|
// the to be deleted segment is always at index firstSegmentIdx + 1
|
|
for segmentIdx := (firstSegmentIdx + 1) to lastSegmentIdx do
|
|
begin
|
|
FSegmentList.Delete(firstSegmentIdx + 1);
|
|
end;
|
|
// add the backed up data to the first segment, which will automatically be expanded
|
|
Result := FSegmentList[firstSegmentIdx].Add(joinedData, joinedSize, FSegmentList[firstSegmentIdx].BaseAddress);
|
|
// now add the actual data
|
|
if Result then
|
|
Result := FSegmentList[firstSegmentIdx].Add(data, length, address);
|
|
// release array
|
|
SetLength(joinedData, 0);
|
|
// make sure segments are properly sorted
|
|
SortSegments;
|
|
// all done
|
|
Exit;
|
|
end;
|
|
|
|
// begin and end do not belong to existing segments
|
|
if (firstSegmentIdx = -1) and (lastSegmentIdx = -1) then
|
|
begin
|
|
// it could be there there are existing segments between the range that should be
|
|
// removed. try to match the first and last segment index to snap to these.
|
|
firstSegmentIdx := FindNextSegmentIdx(address);
|
|
lastSegmentIdx := FindPrevSegmentIdx(address + Longword(length) - 1);
|
|
// if these are both valid values, then there are segments in between that should
|
|
// be removed
|
|
if (firstSegmentIdx <> -1) and (lastSegmentIdx <> -1) then
|
|
begin
|
|
// remove the segments. keep in mind that the indexes change after deleting a
|
|
// segment, so the to be deleted segment is always at index firstSegmentIdx
|
|
for segmentIdx := firstSegmentIdx to lastSegmentIdx do
|
|
begin
|
|
FSegmentList.Delete(firstSegmentIdx);
|
|
end;
|
|
end;
|
|
// now add the data as a new segment
|
|
segmentIdx := FSegmentList.Add(TDataSegment.Create);
|
|
if segmentIdx >= 0 then
|
|
Result := FSegmentList[segmentIdx].Add(data, length, address)
|
|
else
|
|
Result := False;
|
|
// make sure segments are properly sorted
|
|
SortSegments;
|
|
// all done
|
|
Exit;
|
|
end;
|
|
|
|
// begin belongs to existing segments but the end does not?
|
|
if (firstSegmentIdx <> -1) and (lastSegmentIdx = -1) then
|
|
begin
|
|
// snap last segment to the closest known one
|
|
lastSegmentIdx := FindPrevSegmentIdx(address + Longword(length) - 1);
|
|
// remove the overlapping segments, excluding the first one. keep in mind that the
|
|
// indexes change after deleting a segment, so the to be deleted segment is always at
|
|
// index firstSegmentIdx + 1
|
|
for segmentIdx := (firstSegmentIdx + 1) to lastSegmentIdx do
|
|
begin
|
|
FSegmentList.Delete(firstSegmentIdx + 1);
|
|
end;
|
|
// now add the data to the first segment, which will automatically expand it
|
|
Result := FSegmentList[firstSegmentIdx].Add(data, length, address);
|
|
// make sure segments are properly sorted
|
|
SortSegments;
|
|
// all done
|
|
Exit;
|
|
end;
|
|
|
|
// begin does not belong to an existing segment but the end does
|
|
if (firstSegmentIdx = -1) and (lastSegmentIdx <> -1) then
|
|
begin
|
|
// snap first segment to the closest known one
|
|
firstSegmentIdx := FindNextSegmentIdx(address);
|
|
// remove the overlapping segments, excluding the last one. keep in mind that the
|
|
// indexes change after deleting a segment, so the to be deleted segment is always at
|
|
// index firstSegmentIdx
|
|
for segmentIdx := firstSegmentIdx to (lastSegmentIdx - 1) do
|
|
begin
|
|
FSegmentList.Delete(firstSegmentIdx);
|
|
end;
|
|
// note that last segment index changed because we deleted segments so refresh it
|
|
lastSegmentIdx := FindSegmentIdx(address + Longword(length) - 1);
|
|
// try to snap it
|
|
if lastSegmentIdx = -1 then
|
|
begin
|
|
segmentIdx := FindNextSegmentIdx(address + Longword(length) - 1);
|
|
if segmentIdx <> - 1 then
|
|
begin
|
|
if (address + Longword(length)) = FSegmentList[segmentIdx].BaseAddress then
|
|
lastSegmentIdx := segmentIdx;
|
|
end;
|
|
end;
|
|
// now add the data to the first last, which will automatically expand it
|
|
if lastSegmentIdx <> -1 then
|
|
begin
|
|
Result := FSegmentList[lastSegmentIdx].Add(data, length, address);
|
|
end
|
|
else
|
|
begin
|
|
Result := False;
|
|
end;
|
|
// make sure segments are properly sorted
|
|
SortSegments;
|
|
// all done
|
|
Exit;
|
|
end;
|
|
end; //*** end of AddData ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: RemoveData
|
|
// PARAMETER: length Number of bytes to remove.
|
|
// address Address where to start removing from.
|
|
// RETURN VALUE: True is successful, False otherwise.
|
|
// DESCRIPTION: Removes firmware data from the data segments.
|
|
//
|
|
//***************************************************************************************
|
|
function TFirmwareData.RemoveData(length: Integer; address: Longword): Boolean;
|
|
var
|
|
firstSegmentIdx: Integer;
|
|
lastSegmentIdx: Integer;
|
|
segmentDelCnt: Integer;
|
|
segmentIdx: Integer;
|
|
byteIdx: Integer;
|
|
remainderData: array of Byte;
|
|
remainderLen: Integer;
|
|
remainderAddr: Longword;
|
|
begin
|
|
Result := True;
|
|
|
|
// find the starting and ending segment index
|
|
firstSegmentIdx := FindSegmentIdx(address);
|
|
lastSegmentIdx := FindSegmentIdx(address + Longword(length) - 1);
|
|
|
|
// in case the start and end is not in a segment, try to align it to the closest one
|
|
if firstSegmentIdx = -1 then
|
|
firstSegmentIdx := FindNextSegmentIdx(address);
|
|
if lastSegmentIdx = -1 then
|
|
lastSegmentIdx := FindPrevSegmentIdx(address + Longword(length) - 1);
|
|
|
|
// after the align operation both indexes must be valid, otherwise there are no
|
|
// segments to remove, which means we are done already
|
|
if (firstSegmentIdx = -1) or (lastSegmentIdx = -1) then
|
|
Exit;
|
|
|
|
// check if a segment split is needed, which is a special case and should be done first
|
|
if (firstSegmentIdx = lastSegmentIdx) and
|
|
(address > FSegmentList[firstSegmentIdx].BaseAddress) and
|
|
((address + Longword(length) - 1) < FSegmentList[lastSegmentIdx].LastAddress) then
|
|
begin
|
|
// copy remainder data after the split to a temporary buffer
|
|
remainderAddr := address + Longword(length);
|
|
remainderLen := (FSegmentList[firstSegmentIdx].LastAddress + 1) - remainderAddr;
|
|
SetLength(remainderData, remainderLen);
|
|
for byteIdx := 0 to (remainderLen - 1) do
|
|
remainderData[byteIdx] := FSegmentList[firstSegmentIdx].Data[(FSegmentList[firstSegmentIdx].Size - remainderLen) + byteIdx];
|
|
// create a new segment where the remainder data will be copied to
|
|
segmentIdx := FSegmentList.Add(TDataSegment.Create);
|
|
if segmentIdx >= 0 then
|
|
Result := FSegmentList[segmentIdx].Add(remainderData, remainderLen, remainderAddr)
|
|
else
|
|
begin
|
|
// this should not happen and indicates a severe error
|
|
Result := False;
|
|
end;
|
|
// the part after the split can be safely removed no. by removing the length of the
|
|
// segment, it is guaranteerd that the remainder after the split is also removed
|
|
if Result then
|
|
begin
|
|
Result := FSegmentList[firstSegmentIdx].Remove(FSegmentList[firstSegmentIdx].Size, address);
|
|
end;
|
|
// a segment was added so perform sorting
|
|
SortSegments;
|
|
// all done
|
|
Exit;
|
|
end;
|
|
|
|
// begin and end belongs to existing segments? note that this should always be the
|
|
// case because of the segment alignment that is performed at the start.
|
|
if (firstSegmentIdx <> -1) and (lastSegmentIdx <> -1) then
|
|
begin
|
|
// remove bytes from the end of the first segment. note that the remove will only
|
|
// operates on the specified segment so no need to worry that it removes too many
|
|
Result := FSegmentList[firstSegmentIdx].Remove(length, address);
|
|
// remove bytes from the end of the last segment. note that the remove will only
|
|
// operates on the specified segment so no need to worry that it removes too many
|
|
if Result then
|
|
Result := FSegmentList[lastSegmentIdx].Remove(length, address);
|
|
// remove overlapping segments if any, but not the first and the last one. keep in
|
|
// mind that the indexes change after deleting a segment so the to be deleted segment
|
|
// is always firstSegmentIdx + 1
|
|
if Result then
|
|
begin
|
|
segmentDelCnt := 0;
|
|
for segmentIdx := (firstSegmentIdx + 1) to (lastSegmentIdx - 1) do
|
|
begin
|
|
FSegmentList.Delete(firstSegmentIdx + 1);
|
|
segmentDelCnt := segmentDelCnt + 1;
|
|
end;
|
|
// refresh last segment index
|
|
lastSegmentIdx := lastSegmentIdx - segmentDelCnt;
|
|
// check if last segment is now empty and delete it if so
|
|
if FSegmentList[lastSegmentIdx].Size = 0 then
|
|
FSegmentList.Delete(lastSegmentIdx);
|
|
// check if first segment is now empty and delete it if so
|
|
if FSegmentList[firstSegmentIdx].Size = 0 then
|
|
FSegmentList.Delete(firstSegmentIdx);
|
|
end;
|
|
// no need to sort again so all done
|
|
Exit;
|
|
end;
|
|
end; //*** end of RemoveData ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: ClearData
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Clear all segments with firmware data.
|
|
//
|
|
//***************************************************************************************
|
|
procedure TFirmwareData.ClearData;
|
|
begin
|
|
FSegmentList.Clear;
|
|
end; //*** end of ClearData ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: LoadFromFile
|
|
// PARAMETER: firmwareFile Filename with full path of the firmware file to load.
|
|
// append True to append the firmware data to what is currently loaded,
|
|
// False to clear the current firmware data first.
|
|
// RETURN VALUE: True if successful, False otherwise.
|
|
// DESCRIPTION: Loads firmware data from a firmware file.
|
|
//
|
|
//***************************************************************************************
|
|
function TFirmwareData.LoadFromFile(firmwareFile: String; append: Boolean): Boolean;
|
|
var
|
|
firmwareFileType: TFirmwareFileType;
|
|
firmwareFileHandler: TFirmwareFileHandler;
|
|
begin
|
|
|
|
// init locals and the result
|
|
Result := False;
|
|
firmwareFileHandler := nil;
|
|
|
|
// determine firmware file type
|
|
firmwareFileType := GetFirmwareFileType(firmwareFile);
|
|
|
|
// check if the file type is an S-record and if so, load it
|
|
if firmwareFileType = FFT_SRECORD then
|
|
begin
|
|
// create instance of the firmware file handler
|
|
firmwareFileHandler := TSRecordFileHandler.Create;
|
|
end;
|
|
|
|
// check if the firmware file handler object was instantiated, which flags that a
|
|
// firmware file can be loaded through it
|
|
if Assigned(firmwareFileHandler) then
|
|
begin
|
|
// clear the current firmware data if we should not append the new data from the file
|
|
if not append then
|
|
begin
|
|
ClearData;
|
|
end;
|
|
|
|
// set onload handler which does the actual data processing
|
|
{$IFDEF FPC}
|
|
firmwareFileHandler.OnDataRead := @FirmwareFileDataRead;
|
|
{$ELSE}
|
|
firmwareFileHandler.OnDataRead := FirmwareFileDataRead;
|
|
{$ENDIF}
|
|
// load data from the file
|
|
Result := firmwareFileHandler.Load(firmwareFile);
|
|
|
|
// release instance of the firmware file handler
|
|
firmwareFileHandler.Free
|
|
end;
|
|
end; //*** end of LoadFromFile ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: SaveToFile
|
|
// PARAMETER: firmwareFile Filename with full path of the firmware file to save.
|
|
// firwareFileType Firmware file type to use when saving.
|
|
// RETURN VALUE: True if successful, False otherwise.
|
|
// DESCRIPTION: Saves firmware data to a firmware file of the specified format.
|
|
//
|
|
//***************************************************************************************
|
|
function TFirmwareData.SaveToFile(firmwareFile: String; firmwareFileType: TFirmwareFileType): Boolean;
|
|
var
|
|
firmwareFileHandler: TFirmwareFileHandler;
|
|
begin
|
|
// init result
|
|
Result := False;
|
|
|
|
// check if the file type is an S-record and if so, save it
|
|
if firmwareFileType = FFT_SRECORD then
|
|
begin
|
|
// create instance of the firmware file handler
|
|
firmwareFileHandler := TSRecordFileHandler.Create;
|
|
// perform firmware file save operation
|
|
Result := firmwareFileHandler.Save(firmwareFile, FSegmentList);
|
|
// release the firmware file handler
|
|
firmwareFileHandler.Free;
|
|
end
|
|
// check if the file type is a binary file and if so, save it
|
|
else if firmwareFileType = FFT_BINARY then
|
|
begin
|
|
// create instance of the firmware file handler
|
|
firmwareFileHandler := TBinaryFileHandler.Create;
|
|
// perform firmware file save operation
|
|
Result := firmwareFileHandler.Save(firmwareFile, FSegmentList);
|
|
// release the firmware file handler
|
|
firmwareFileHandler.Free;
|
|
end;
|
|
end; //*** end of SaveToFile ***
|
|
|
|
|
|
//***************************************************************************************
|
|
// NAME: Dump
|
|
// PARAMETER: none
|
|
// RETURN VALUE: none
|
|
// DESCRIPTION: Dumps the segment contents to the standard output for debugging
|
|
// purposes.
|
|
//
|
|
//***************************************************************************************
|
|
procedure TFirmwareData.Dump;
|
|
{$IFDEF DEBUG}
|
|
var
|
|
segmentIdx: Integer;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF DEBUG}
|
|
for segmentIdx := 0 to (SegmentCount - 1) do
|
|
begin
|
|
Writeln('Segment index = ' + IntToStr(segmentIdx));
|
|
Segment[segmentIdx].Dump;
|
|
end;
|
|
{$ENDIF}
|
|
end; //*** end of DumpFirmwareData ***
|
|
|
|
|
|
end.
|
|
//******************************** end of FirmwareData.pas ******************************
|
|
|