unit MainUnit;

interface

uses
	WinAPI.Windows, WinAPI.Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
	StdCtrls, ExtCtrls;

type
	TMainForm = class(TForm)
		LogMemo: TMemo;
		Panel2: TPanel;
		CardCombo: TComboBox;
		ControlPanel: TPanel;
		RegisterGroup: TGroupBox;
		Label1: TLabel;
		Label2: TLabel;
		OffsetEdit: TEdit;
		DataEdit: TEdit;
		ReadButton: TButton;
		WriteButton: TButton;
		SaveLoadGroup: TGroupBox;
		LoadFileButton: TButton;
		SaveFileButton: TButton;
		LoadCardButton: TButton;
		SaveCardButton: TButton;
		EnableEraseButton: TButton;
		OpenEm: TOpenDialog;
		SaveEm: TSaveDialog;
		UnsavedLabel: TLabel;
		procedure FormCreate(Sender: TObject);
		procedure CardComboChange(Sender: TObject);
		procedure OffsetEditChange(Sender: TObject);
		procedure ReadButtonClick(Sender: TObject);
		procedure WriteButtonClick(Sender: TObject);
		procedure LoadCardButtonClick(Sender: TObject);
		procedure SaveCardButtonClick(Sender: TObject);
		procedure LoadFileButtonClick(Sender: TObject);
		procedure SaveFileButtonClick(Sender: TObject);
		procedure EnableEraseButtonClick(Sender: TObject);
	protected
		procedure LogLine(CardNum: Integer; Line: String);
		procedure SetUnsaved(CardNum: Integer; NewUnsaved: Boolean);
	public
	end;

var
	MainForm: TMainForm;

implementation

{$R *.DFM}

uses
	AIOAIO;

const
	Flash_Control = $FF000000;
	Flash_Write = $80000000;
	Flash_Upper = $00008000;

type
	TFlashBuf = array[$00..$7FFF] of TUInt8;

var
	bEraseEnable: array of Boolean;
	TicksPerSec: Int64;

procedure DoDelay(Hz: Int64 = 250000); //4µs
var
	dT, EndTicks: Int64;

begin
	QueryPerformanceCounter(EndTicks);
	Inc(EndTicks, TicksPerSec div Hz);
	repeat
		QueryPerformanceCounter(dT);
		Dec(dT, EndTicks);
	until dT > 0;
end;

function PIO_InFlash(CardNum: TBoardIndex; FlashIndex: TUInt32): TUInt8;
begin
	FlashIndex := FlashIndex and not Flash_Control;

	PIO_Out32(CardNum, $70, FlashIndex);
	DoDelay;
	Result := PIO_In32(CardNum, $74);
end;

function PIO_OutFlash(CardNum: TBoardIndex; FlashIndex: TUInt32; Data: TUInt8): TWinErrCode;
begin
	FlashIndex := FlashIndex and not Flash_Control;
	FlashIndex := FlashIndex or Flash_Write;

	PIO_Out32(CardNum, $74, Data);
	//DoDelay;
	PIO_Out32(CardNum, $70, FlashIndex);
	DoDelay(125000); //8µs
	Result := ERROR_SUCCESS;
end;

function PIO_EnableFlashErase1(CardNum: TBoardIndex): TWinErrCode;
begin
	bEraseEnable[CardNum] := True;
	Result := ERROR_SUCCESS;
end;

function PIO_EraseFlash(CardNum: TBoardIndex): TWinErrCode;
var
	UpperSegment: TFlashBuf;
	iDWord: Integer;
	DeviceID: TUInt32;

begin
	Result := BRD_GetDeviceID(CardNum, @DeviceID);
	DeviceID := DeviceID and $FFFF;
	if Result <> ERROR_SUCCESS then Exit;

	if not bEraseEnable[CardNum] then begin
		Result := ERROR_ACCESS_DENIED;
		Exit;
	end;
	Result := ERROR_SUCCESS;

	bEraseEnable[CardNum] := False;

	for iDWord := High(UpperSegment) downto 0 do
		UpperSegment[iDWord] := PIO_InFlash(CardNum, iDWord or Flash_Upper)
	;

	PIO_Out32(CardNum, $78, $494F0000 or DeviceID);
	PIO_Out32(CardNum, $78, $0ACCE510);

	Sleep(600);

	for iDWord := High(UpperSegment) downto 0 do
		PIO_OutFlash(CardNum, iDWord or Flash_Upper, UpperSegment[iDWord])
	;
end;

function PIO_SlurpFlashFromCard(CardNum: TBoardIndex; out IntoBuf: TFlashBuf): TWinErrCode;
var
	iDWord: Integer;

begin
	for iDWord := High(IntoBuf) downto 0 do begin
		IntoBuf[iDWord] := PIO_InFlash(CardNum, iDWord);
	end;
	Result := ERROR_SUCCESS;
end;

function PIO_SlurpFlashToCard(CardNum: TBoardIndex; const FromBuf: TFlashBuf): TWinErrCode;
var
	iDWord: Integer;

begin
	Result := PIO_EraseFlash(CardNum);
	if Result <> ERROR_SUCCESS then Exit;

	for iDWord := High(FromBuf) downto 0 do
		PIO_OutFlash(CardNum, iDWord, FromBuf[iDWord])
	;
end;



type
	TCardData = record
		DeviceID: Word;
		Log: String;
		bEraseEnabled: Boolean;
		bCachedFlash: Boolean;
		bUnsaved: Boolean;
		FlashCache: TFlashBuf;
	end;

var
	CardData: array of TCardData;
	LastFN: String;

function CleanerFN(FN: String): String;
var
	Here: String;
begin
	Here := IncludeTrailingPathDelimiter(GetCurrentDir);
	if UpperCase(Copy(FN, 1, Length(Here))) = UpperCase(Here) then
		Result := Copy(FN, 1+Length(Here), MAXINT)
	else
		Result := FN
	;
end;

procedure TMainForm.FormCreate(Sender: TObject);
var
	HighCard, CardNum: TBoardIndex;
	I, J: Integer;
	DeviceID: LongWord;
	CardNameSize: LongWord;
	CardName: AnsiString;

begin
	CardCombo.Items.Clear;
	HighCard := BRD_GetHighestBoardIndex;
	if HighCard < 0 then begin
		for I := 0 to ControlPanel.ControlCount - 1 do
			for J := 0 to TWinControl(ControlPanel.Controls[I]).ControlCount - 1 do
				TWinControl(ControlPanel.Controls[I]).Controls[J].Enabled := False
			;
		LogMemo.Text := 'No cards found. The cards may not be installed, or they may be installed using a driver other than AIOWDM.sys.';
	end
	else begin
		SetLength(CardData, HighCard+1);
		SetLength(bEraseEnable, HighCard+1);
		for CardNum := 0 to HighCard do begin
			CardNameSize := 256;
			SetString(CardName, nil, CardNameSize);
			if (BRD_GetName(CardNum, CardNameSize, TPAnsiChar(CardName), @CardNameSize) <> ERROR_SUCCESS)
			or (BRD_GetDeviceID(CardNum, @DeviceID) <> ERROR_SUCCESS)
			then begin
				CardCombo.Items.Add('Error Getting Data [????]');
			end
			else begin
				SetLength(CardName, CardNameSize);
				CardCombo.Items.Add(String(CardName) + '[' + IntToHex(DeviceID, 4) + ']');

				CardData[CardNum].DeviceID := DeviceID;
				CardData[CardNum].bEraseEnabled := False;
				CardData[CardNum].bCachedFlash := False;
				CardData[CardNum].bUnsaved := False;
			end;
		end;
		CardCombo.ItemIndex := 0;
		CardComboChange(CardCombo);
	end;
end;

procedure TMainForm.LogLine(CardNum: Integer; Line: String);
begin
	with CardData[CardNum] do Log := Log + (Line + #13#10);

	LogMemo.Text := CardData[CardNum].Log;
	LogMemo.SelStart := Length(CardData[CardNum].Log);
	LogMemo.SelLength := 1;
end;

procedure TMainForm.CardComboChange(Sender: TObject);
var
	CardNum: Integer;

begin
	CardNum := CardCombo.ItemIndex;

	SaveCardButton.Enabled := CardData[CardNum].bEraseEnabled;
	if CardData[CardNum].bCachedFlash then begin
		LogMemo.Text := CardData[CardNum].Log;
		LogMemo.SelStart := Length(CardData[CardNum].Log);
		LogMemo.SelLength := 1;
	end
	else begin
		LoadCardButtonClick(LoadCardButton);
	end;
end;

procedure TMainForm.OffsetEditChange(Sender: TObject);
var
	Offset: Word;
	Code: Integer;
	bOK: Boolean;

begin
	Val('$' + OffsetEdit.Text, Offset, Code);

	bOK := (Code = 0) and (Offset <= High(TFlashBuf));
	ReadButton.Enabled := bOK;
	WriteButton.Enabled := bOK;
end;

procedure TMainForm.SetUnsaved(CardNum: Integer; NewUnsaved: Boolean);
begin
	CardData[CardNum].bUnsaved := NewUnsaved;

	if CardData[CardNum].bUnsaved then
		UnsavedLabel.Caption := 'Changes not saved to card.'
	else
		UnsavedLabel.Caption := ''
	;
end;

procedure TMainForm.ReadButtonClick(Sender: TObject);
var
	CardNum: Integer;
	Offset: LongWord;
	Data: TUInt8;

begin
	CardNum := CardCombo.ItemIndex;

	Offset := StrToInt('$' + OffsetEdit.Text);
	Data := CardData[CardNum].FlashCache[Offset];
	DataEdit.Text := IntToHex(Data, 2);
end;

procedure TMainForm.WriteButtonClick(Sender: TObject);
var
	CardNum: Integer;
	Offset: LongWord;
	Data: TUInt8;

begin
	CardNum := CardCombo.ItemIndex;

	Offset := StrToInt('$' + OffsetEdit.Text);
	Data := StrToInt('$' + DataEdit.Text);
	DataEdit.Text := IntToHex(Data, 2);
	CardData[CardNum].FlashCache[Offset] := Data;

	SetUnsaved(CardNum, True);
end;

procedure TMainForm.LoadFileButtonClick(Sender: TObject);
var
	CardNum: Integer;
	Fil: TFileStream;
	LTar: Integer;
	InterBuf: TFlashBuf;

begin
	CardNum := CardCombo.ItemIndex;

	OpenEm.InitialDir := ExtractFilePath(LastFN);
	if OpenEm.InitialDir = '' then OpenEm.InitialDir := GetCurrentDir;
	OpenEm.FileName := ExtractFileName(LastFN);

	if not OpenEm.Execute(Handle) then Exit;

	LTar := Length(InterBuf) * SizeOf(InterBuf[0]);
	Fil := TFileStream.Create(OpenEm.FileName, fmOpenRead or fmShareDenyNone);
	try
		Fil.ReadBuffer(InterBuf[0], LTar);
	finally
		Fil.Free;
	end;
	CardData[CardNum].FlashCache := InterBuf;

	SetUnsaved(CardNum, True);
	LogLine(CardNum, 'Loaded flash from ' + CleanerFN(OpenEm.FileName) + '.');
end;

procedure TMainForm.SaveFileButtonClick(Sender: TObject);
var
	CardNum: Integer;
	Fil: TFileStream;

begin
	CardNum := CardCombo.ItemIndex;

	SaveEm.InitialDir := ExtractFilePath(LastFN);
	if SaveEm.InitialDir = '' then SaveEm.InitialDir := GetCurrentDir;
	SaveEm.FileName := ExtractFileName(LastFN);

	if not SaveEm.Execute(Handle) then Exit;

	Fil := TFileStream.Create(SaveEm.FileName, fmCreate or fmShareDenyWrite);
	try
		with CardData[CardNum] do Fil.WriteBuffer(FlashCache[0], Length(FlashCache) * SizeOf(FlashCache[0]));
	finally
		Fil.Free;
	end;

	LogLine(CardNum, 'Saved flash to ' + CleanerFN(SaveEm.FileName) + '.');
end;

procedure TMainForm.LoadCardButtonClick(Sender: TObject);
var
	CardNum: Integer;

begin
	CardNum := CardCombo.ItemIndex;

	LogLine(CardNum, 'Loading flash from card…');

	PIO_SlurpFlashFromCard(CardNum, CardData[CardNum].FlashCache);

	CardData[CardNum].bCachedFlash := True;
	SetUnsaved(CardNum, False);
	LogLine(CardNum, 'Loaded flash from card.');
end;

procedure TMainForm.SaveCardButtonClick(Sender: TObject);
var
	CardNum: Integer;

begin
	CardNum := CardCombo.ItemIndex;

	LogLine(CardNum, 'Saving flash to card…');

	PIO_SlurpFlashToCard(CardNum, CardData[CardNum].FlashCache);

	CardData[CardNum].bEraseEnabled := False;
	SaveCardButton.Enabled := CardData[CardNum].bEraseEnabled;

	SetUnsaved(CardNum, False);
	LogLine(CardNum, 'Saved flash to card.');
end;

procedure TMainForm.EnableEraseButtonClick(Sender: TObject);
var
	CardNum: Integer;

begin
	CardNum := CardCombo.ItemIndex;

	PIO_EnableFlashErase1(CardNum);
	CardData[CardNum].bEraseEnabled := True;
	SaveCardButton.Enabled := CardData[CardNum].bEraseEnabled;

	LogLine(CardNum, 'Enabled card flash erase. You can now save to card flash.');
end;

begin
	QueryPerformanceFrequency(TicksPerSec);
end.
