| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147 | //	Copyright (C) 2014 Michael McMaster <michael@codesrc.com>////	This file is part of SCSI2SD.////	SCSI2SD 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.////	SCSI2SD 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 should have received a copy of the GNU General Public License//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.// For compilers that support precompilation, includes "wx/wx.h".#include <wx/wxprec.h>#ifndef WX_PRECOMP#include <wx/wx.h>#endif#include <wx/app.h>#include <wx/filedlg.h>#include <wx/filefn.h>#include <wx/filename.h>#include <wx/log.h>#include <wx/notebook.h>#include <wx/progdlg.h>#include <wx/utils.h>#include <wx/wfstream.h>#include <wx/windowptr.h>#include <wx/thread.h>#include <wx/txtstrm.h>#include <zipper.hh>#include "ConfigUtil.hh"#include "BoardPanel.hh"#include "TargetPanel.hh"#include "SCSI2SD_Bootloader.hh"#include "SCSI2SD_HID.hh"#include "Firmware.hh"#include <algorithm>#include <iomanip>#include <vector>#include <set>#include <sstream>#if __cplusplus >= 201103L#include <cstdint>#include <memory>using std::shared_ptr;#else#include <stdint.h>#include <tr1/memory>using std::tr1::shared_ptr;#endif#define MIN_FIRMWARE_VERSION 0x0400using namespace SCSI2SD;class ProgressWrapper{public:	void setProgressDialog(		const wxWindowPtr<wxGenericProgressDialog>& dlg,		size_t maxRows)	{		myProgressDialog = dlg;		myMaxRows = maxRows;		myNumRows = 0;	}	void clearProgressDialog()	{		myProgressDialog->Show(false);		myProgressDialog.reset();	}	void update(unsigned char arrayId, unsigned short rowNum)	{		if (!myProgressDialog) return;		myNumRows++;		std::stringstream ss;		ss << "Writing flash array " <<			static_cast<int>(arrayId) << " row " <<			static_cast<int>(rowNum);		wxLogMessage("%s", ss.str());		myProgressDialog->Update(myNumRows, ss.str());	}private:	wxWindowPtr<wxGenericProgressDialog> myProgressDialog;	size_t myMaxRows;	size_t myNumRows;};static ProgressWrapper TheProgressWrapper;extern "C"void ProgressUpdate(unsigned char arrayId, unsigned short rowNum){	TheProgressWrapper.update(arrayId, rowNum);}namespace{static uint8_t sdCrc7(uint8_t* chr, uint8_t cnt, uint8_t crc){	uint8_t a;	for(a = 0; a < cnt; a++)	{		uint8_t data = chr[a];		uint8_t i;		for(i = 0; i < 8; i++)		{			crc <<= 1;			if ((data & 0x80) ^ (crc & 0x80))			{				crc ^= 0x09;			}			data <<= 1;		}	}	return crc & 0x7F;}class TimerLock{public:	TimerLock(wxTimer* timer) :		myTimer(timer),		myInterval(myTimer->GetInterval())	{		myTimer->Stop();	};	virtual ~TimerLock()	{		if (myTimer && myInterval > 0)		{			myTimer->Start(myInterval);		}	}private:	wxTimer* myTimer;	int myInterval;};class AppFrame : public wxFrame{public:	AppFrame() :		wxFrame(NULL, wxID_ANY, "scsi2sd-util", wxPoint(50, 50), wxSize(600, 650)),		myInitialConfig(false),		myTickCounter(0),		myLastPollTime(0)	{		wxMenu *menuFile = new wxMenu();		menuFile->Append(			ID_SaveFile,			_("&Save to file..."),			_("Save settings to local file."));		menuFile->Append(			ID_OpenFile,			_("&Open file..."),			_("Load settings from local file."));		menuFile->AppendSeparator();		menuFile->Append(			ID_ConfigDefaults,			_("Load &Defaults"),			_("Load default configuration options."));		menuFile->Append(			ID_Firmware,			_("&Upgrade Firmware..."),			_("Upgrade or inspect device firmware version."));		menuFile->AppendSeparator();		menuFile->Append(wxID_EXIT);		wxMenu *menuWindow= new wxMenu();		menuWindow->Append(			ID_LogWindow,			_("Show &Log"),			_("Show debug log window"));		wxMenu *menuDebug = new wxMenu();		mySCSILogChk = menuDebug->AppendCheckItem(			ID_SCSILog,			_("Log SCSI data"),			_("Log SCSI commands"));		mySelfTestChk = menuDebug->AppendCheckItem(			ID_SelfTest,			_("SCSI Standalone Self-Test"),			_("SCSI Standalone Self-Test"));		wxMenu *menuHelp = new wxMenu();		menuHelp->Append(wxID_ABOUT);		wxMenuBar *menuBar = new wxMenuBar();		menuBar->Append( menuFile, _("&File") );		menuBar->Append( menuDebug, _("&Debug") );		menuBar->Append( menuWindow, _("&Window") );		menuBar->Append( menuHelp, _("&Help") );		SetMenuBar( menuBar );		CreateStatusBar();		{			wxPanel* cfgPanel = new wxPanel(this);			wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 1, 15, 15);			cfgPanel->SetSizer(fgs);			// Empty space below menu bar.			fgs->Add(5, 5, wxALL);			wxNotebook* tabs = new wxNotebook(cfgPanel, ID_Notebook);			myBoardPanel = new BoardPanel(tabs, ConfigUtil::DefaultBoardConfig());			tabs->AddPage(myBoardPanel, _("General Settings"));			for (int i = 0; i < MAX_SCSI_TARGETS; ++i)			{				TargetPanel* target =					new TargetPanel(tabs, ConfigUtil::Default(i));				myTargets.push_back(target);				std::stringstream ss;				ss << "Device " << (i + 1);				tabs->AddPage(target, ss.str());				target->Fit();			}			tabs->Fit();			fgs->Add(tabs);			wxPanel* btnPanel = new wxPanel(cfgPanel);			wxFlexGridSizer *btnFgs = new wxFlexGridSizer(1, 2, 5, 5);			btnPanel->SetSizer(btnFgs);			myLoadButton =				new wxButton(btnPanel, ID_BtnLoad, _("Load from device"));			btnFgs->Add(myLoadButton);			mySaveButton =				new wxButton(btnPanel, ID_BtnSave, _("Save to device"));			btnFgs->Add(mySaveButton);			fgs->Add(btnPanel);			btnPanel->Fit();			cfgPanel->Fit();		}		//Fit(); // Needed to reduce window size on Windows		FitInside(); // Needed on Linux to prevent status bar overlap		myLogWindow = new wxLogWindow(this, _("scsi2sd-util debug log"), true);		myLogWindow->PassMessages(false); // Prevent messagebox popups		myTimer = new wxTimer(this, ID_Timer);		myTimer->Start(16); //ms, suitable for scsi debug logging	}private:	wxLogWindow* myLogWindow;	BoardPanel* myBoardPanel;	std::vector<TargetPanel*> myTargets;	wxButton* myLoadButton;	wxButton* mySaveButton;	wxMenuItem* mySCSILogChk;	wxMenuItem* mySelfTestChk;	wxTimer* myTimer;	shared_ptr<HID> myHID;	shared_ptr<Bootloader> myBootloader;	bool myInitialConfig;	uint8_t myTickCounter;	time_t myLastPollTime;	void mmLogStatus(const std::string& msg)	{		// We set PassMessages to false on our log window to prevent popups, but		// this also prevents wxLogStatus from updating the status bar.		SetStatusText(msg);		wxLogMessage(this, "%s", msg.c_str());	}	void onConfigChanged(wxCommandEvent& event)	{		evaluate();	}	void evaluate()	{		bool valid = true;		// Check for duplicate SCSI IDs		std::set<uint8_t> enabledID;		// Check for overlapping SD sectors.		std::vector<std::pair<uint32_t, uint64_t> > sdSectors;		bool isTargetEnabled = false; // Need at least one enabled		uint32_t autoStartSector = 0;		for (size_t i = 0; i < myTargets.size(); ++i)		{			myTargets[i]->setAutoStartSector(autoStartSector);			valid = myTargets[i]->evaluate() && valid;			if (myTargets[i]->isEnabled())			{				isTargetEnabled = true;				uint8_t scsiID = myTargets[i]->getSCSIId();				if (enabledID.find(scsiID) != enabledID.end())				{					myTargets[i]->setDuplicateID(true);					valid = false;				}				else				{					enabledID.insert(scsiID);					myTargets[i]->setDuplicateID(false);				}				auto sdSectorRange = myTargets[i]->getSDSectorRange();				for (auto it(sdSectors.begin()); it != sdSectors.end(); ++it)				{					if (sdSectorRange.first < it->second &&						sdSectorRange.second > it->first)					{						valid = false;						myTargets[i]->setSDSectorOverlap(true);					}					else					{						myTargets[i]->setSDSectorOverlap(false);					}				}				sdSectors.push_back(sdSectorRange);				autoStartSector = sdSectorRange.second;			}			else			{				myTargets[i]->setDuplicateID(false);				myTargets[i]->setSDSectorOverlap(false);			}		}		valid = valid && isTargetEnabled; // Need at least one.		mySaveButton->Enable(			valid &&			myHID &&			(myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION));		myLoadButton->Enable(			myHID &&			(myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION));	}	enum	{		ID_ConfigDefaults = wxID_HIGHEST + 1,		ID_Firmware,		ID_Timer,		ID_Notebook,		ID_BtnLoad,		ID_BtnSave,		ID_LogWindow,		ID_SCSILog,		ID_SelfTest,		ID_SaveFile,		ID_OpenFile	};	void OnID_ConfigDefaults(wxCommandEvent& event)	{		myBoardPanel->setConfig(ConfigUtil::DefaultBoardConfig());		for (size_t i = 0; i < myTargets.size(); ++i)		{			myTargets[i]->setConfig(ConfigUtil::Default(i));		}	}	void OnID_SaveFile(wxCommandEvent& event)	{		TimerLock lock(myTimer);		wxFileDialog dlg(			this,			"Save config settings",			"",			"",			"XML files (*.xml)|*.xml",			wxFD_SAVE | wxFD_OVERWRITE_PROMPT);		if (dlg.ShowModal() == wxID_CANCEL) return;		wxFileOutputStream file(dlg.GetPath());		if (!file.IsOk())		{			wxLogError("Cannot save settings to file '%s'.", dlg.GetPath());			return;		}		wxTextOutputStream s(file);		s << "<SCSI2SD>\n";		s << ConfigUtil::toXML(myBoardPanel->getConfig());		for (size_t i = 0; i < myTargets.size(); ++i)		{			s << ConfigUtil::toXML(myTargets[i]->getConfig());		}		s << "</SCSI2SD>\n";	}	void OnID_OpenFile(wxCommandEvent& event)	{		TimerLock lock(myTimer);		wxFileDialog dlg(			this,			"Load config settings",			"",			"",			"XML files (*.xml)|*.xml",			wxFD_OPEN | wxFD_FILE_MUST_EXIST);		if (dlg.ShowModal() == wxID_CANCEL) return;		try		{			std::pair<BoardConfig, std::vector<TargetConfig>> configs(				ConfigUtil::fromXML(std::string(dlg.GetPath())));			myBoardPanel->setConfig(configs.first);			size_t i;			for (i = 0; i < configs.second.size() && i < myTargets.size(); ++i)			{				myTargets[i]->setConfig(configs.second[i]);			}			for (; i < myTargets.size(); ++i)			{				myTargets[i]->setConfig(ConfigUtil::Default(i));			}		}		catch (std::exception& e)		{			wxLogError(				"Cannot load settings from file '%s'.\n%s",				dlg.GetPath(),				e.what());			wxMessageBox(				e.what(),				"Load error",				wxOK | wxICON_ERROR);		}	}	void OnID_Firmware(wxCommandEvent& event)	{		TimerLock lock(myTimer);		doFirmwareUpdate();	}	void OnID_LogWindow(wxCommandEvent& event)	{		myLogWindow->Show();	}	void doFirmwareUpdate()	{		wxFileDialog dlg(			this,			"Load firmware file",			"",			"",			"SCSI2SD Firmware files (*.scsi2sd;*.cyacd)|*.cyacd;*.scsi2sd",			wxFD_OPEN | wxFD_FILE_MUST_EXIST);		if (dlg.ShowModal() == wxID_CANCEL) return;		std::string filename(dlg.GetPath());		wxWindowPtr<wxGenericProgressDialog> progress(			new wxGenericProgressDialog(				"Searching for bootloader",				"Searching for bootloader",				100,				this,				wxPD_AUTO_HIDE | wxPD_CAN_ABORT)				);		mmLogStatus("Searching for bootloader");		while (true)		{			try			{				if (!myHID) myHID.reset(HID::Open());				if (myHID)				{					mmLogStatus("Resetting SCSI2SD into bootloader");					myHID->enterBootloader();					myHID.reset();				}				if (!myBootloader)				{					myBootloader.reset(Bootloader::Open());					if (myBootloader)					{						mmLogStatus("Bootloader found");						break;					}				}				else if (myBootloader)				{					// Verify the USB HID connection is valid					if (!myBootloader->ping())					{						mmLogStatus("Bootloader ping failed");						myBootloader.reset();					}					else					{						mmLogStatus("Bootloader found");						break;					}				}			}			catch (std::exception& e)			{				mmLogStatus(e.what());				myHID.reset();				myBootloader.reset();			}			wxMilliSleep(100);			if (!progress->Pulse())			{				return; // user cancelled.			}		}		int totalFlashRows = 0;		std::string tmpFile;		try		{			zipper::ReaderPtr reader(new zipper::FileReader(filename));			zipper::Decompressor decomp(reader);			std::vector<zipper::CompressedFilePtr> files(decomp.getEntries());			for (auto it(files.begin()); it != files.end(); it++)			{				if (myBootloader->isCorrectFirmware((*it)->getPath()))				{					std::stringstream msg;					msg << "Found firmware entry " << (*it)->getPath() <<						" within archive " << filename;					mmLogStatus(msg.str());					tmpFile =						wxFileName::CreateTempFileName(							_("SCSI2SD_Firmware"), static_cast<wxFile*>(NULL)							);					zipper::FileWriter out(tmpFile);					(*it)->decompress(out);					msg.clear();					msg << "Firmware extracted to " << tmpFile;					mmLogStatus(msg.str());					break;				}			}			if (tmpFile.empty())			{				// TODO allow "force" option				wxMessageBox(					"Wrong filename",					"Wrong filename",					wxOK | wxICON_ERROR);				return;			}			Firmware firmware(tmpFile);			totalFlashRows = firmware.totalFlashRows();		}		catch (std::exception& e)		{			mmLogStatus(e.what());			std::stringstream msg;			msg << "Could not open firmware file: " << e.what();			wxMessageBox(				msg.str(),				"Bad file",				wxOK | wxICON_ERROR);			wxRemoveFile(tmpFile);			return;		}		{			wxWindowPtr<wxGenericProgressDialog> progress(				new wxGenericProgressDialog(					"Loading firmware",					"Loading firmware",					totalFlashRows,					this,					wxPD_AUTO_HIDE | wxPD_REMAINING_TIME)					);			TheProgressWrapper.setProgressDialog(progress, totalFlashRows);		}		std::stringstream msg;		msg << "Upgrading firmware from file: " << tmpFile;		mmLogStatus(msg.str());		try		{			myBootloader->load(tmpFile, &ProgressUpdate);			TheProgressWrapper.clearProgressDialog();			wxMessageBox(				"Firmware update successful",				"Firmware OK",				wxOK);			mmLogStatus("Firmware update successful");			myHID.reset();			myBootloader.reset();		}		catch (std::exception& e)		{			TheProgressWrapper.clearProgressDialog();			mmLogStatus(e.what());			myHID.reset();			myBootloader.reset();			wxMessageBox(				"Firmware Update Failed",				e.what(),				wxOK | wxICON_ERROR);			wxRemoveFile(tmpFile);		}	}	void dumpSCSICommand(std::vector<uint8_t> buf)        {		std::stringstream msg;		msg << std::hex;		for (size_t i = 0; i < 32 && i < buf.size(); ++i)		{			msg << std::setfill('0') << std::setw(2) <<			static_cast<int>(buf[i]) << ' ';		}		wxLogMessage(this, msg.str().c_str());        }	void logSCSI()	{		if (!mySCSILogChk->IsChecked() ||			!myHID)		{			return;		}		try		{			std::vector<uint8_t> info(HID::HID_PACKET_SIZE);			if (myHID->readSCSIDebugInfo(info))			{				dumpSCSICommand(info);			}		}		catch (std::exception& e)		{			wxLogWarning(this, e.what());			myHID.reset();		}	}	void OnID_Timer(wxTimerEvent& event)	{		logSCSI();		time_t now = time(NULL);		if (now == myLastPollTime) return;		myLastPollTime = now;		// Check if we are connected to the HID device.		// AND/or bootloader device.		try		{			if (myBootloader)			{				// Verify the USB HID connection is valid				if (!myBootloader->ping())				{					myBootloader.reset();				}			}			if (!myBootloader)			{				myBootloader.reset(Bootloader::Open());				if (myBootloader)				{					mmLogStatus("SCSI2SD Bootloader Ready");				}			}			int supressLog = 0;			if (myHID && myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION)			{				// No method to check connection is still valid.				// So assume it isn't.				myHID.reset();				supressLog = 1;			}			else if (myHID && !myHID->ping())			{				// Verify the USB HID connection is valid				myHID.reset();			}			if (!myHID)			{				myHID.reset(HID::Open());				if (myHID)				{					if (myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION)					{						if (!supressLog)						{							// Oh dear, old firmware							std::stringstream msg;							msg << "Firmware update required. Version " <<								myHID->getFirmwareVersionStr();							mmLogStatus(msg.str());						}					}					else					{						std::stringstream msg;						msg << "SCSI2SD Ready, firmware version " <<							myHID->getFirmwareVersionStr();						mmLogStatus(msg.str());						std::vector<uint8_t> csd(myHID->getSD_CSD());						std::vector<uint8_t> cid(myHID->getSD_CID());						std::stringstream sdinfo;						sdinfo << "SD Capacity (512-byte sectors): " <<							myHID->getSDCapacity() << std::endl;						sdinfo << "SD CSD Register: ";						if (sdCrc7(&csd[0], 15, 0) != (csd[15] >> 1))						{							sdinfo << "BADCRC ";						}						for (size_t i = 0; i < csd.size(); ++i)						{							sdinfo <<								std::hex << std::setfill('0') << std::setw(2) <<								static_cast<int>(csd[i]);						}						sdinfo << std::endl;						sdinfo << "SD CID Register: ";						if (sdCrc7(&cid[0], 15, 0) != (cid[15] >> 1))						{							sdinfo << "BADCRC ";						}						for (size_t i = 0; i < cid.size(); ++i)						{							sdinfo <<								std::hex << std::setfill('0') << std::setw(2) <<								static_cast<int>(cid[i]);						}						wxLogMessage(this, "%s", sdinfo.str());						if (mySelfTestChk->IsChecked())						{							std::stringstream scsiInfo;							scsiInfo << "SCSI Self-Test: " <<								(myHID->scsiSelfTest() ? "Passed" : "FAIL");							wxLogMessage(this, "%s", scsiInfo.str());						}						if (!myInitialConfig)						{/* This doesn't work properly, and causes crashes.							wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad);							GetEventHandler()->AddPendingEvent(loadEvent);*/						}					}				}				else				{					char ticks[] = {'/', '-', '\\', '|'};					std::stringstream ss;					ss << "Searching for SCSI2SD device " << ticks[myTickCounter % sizeof(ticks)];					myTickCounter++;					SetStatusText(ss.str());				}			}		}		catch (std::runtime_error& e)		{			std::cerr << e.what() << std::endl;			mmLogStatus(e.what());		}		evaluate();	}	void doLoad(wxCommandEvent& event)	{		TimerLock lock(myTimer);		if (!myHID) return;		mmLogStatus("Loading configuration");		wxWindowPtr<wxGenericProgressDialog> progress(			new wxGenericProgressDialog(				"Load config settings",				"Loading config settings",				100,				this,				wxPD_CAN_ABORT | wxPD_REMAINING_TIME)				);		int currentProgress = 0;		int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS + 1;		// Read board config first.		std::vector<uint8_t> boardCfgFlashData;		int flashRow = SCSI_CONFIG_BOARD_ROW;		{			std::stringstream ss;			ss << "Reading flash array " << SCSI_CONFIG_ARRAY <<				" row " << flashRow;			mmLogStatus(ss.str());			currentProgress += 1;			if (!progress->Update(					(100 * currentProgress) / totalProgress,					ss.str()					)				)			{				goto abort;			}			try			{				myHID->readFlashRow(					SCSI_CONFIG_ARRAY, flashRow, boardCfgFlashData);				myBoardPanel->setConfig(					ConfigUtil::boardConfigFromBytes(&boardCfgFlashData[0]));			}			catch (std::runtime_error& e)			{				mmLogStatus(e.what());				goto err;			}		}		flashRow = SCSI_CONFIG_0_ROW;		for (size_t i = 0;			i < myTargets.size();			++i, flashRow += SCSI_CONFIG_ROWS)		{			std::vector<uint8_t> raw(sizeof(TargetConfig));			for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j)			{				std::stringstream ss;				ss << "Reading flash array " << SCSI_CONFIG_ARRAY <<					" row " << (flashRow + j);				mmLogStatus(ss.str());				currentProgress += 1;				if (currentProgress == totalProgress)				{					ss.str("Load Complete.");					mmLogStatus("Load Complete.");				}				if (!progress->Update(						(100 * currentProgress) / totalProgress,						ss.str()						)					)				{					goto abort;				}				std::vector<uint8_t> flashData;				try				{					myHID->readFlashRow(						SCSI_CONFIG_ARRAY, flashRow + j, flashData);				}				catch (std::runtime_error& e)				{					mmLogStatus(e.what());					goto err;				}				std::copy(					flashData.begin(),					flashData.end(),					&raw[j * SCSI_CONFIG_ROW_SIZE]);			}			myTargets[i]->setConfig(ConfigUtil::fromBytes(&raw[0]));		}		// Support old boards without board config set		if (memcmp(&boardCfgFlashData[0], "BCFG", 4)) {			BoardConfig defCfg = ConfigUtil::DefaultBoardConfig();			defCfg.flags = myTargets[0]->getConfig().flagsDEPRECATED;			myBoardPanel->setConfig(defCfg);		}		myInitialConfig = true;		goto out;	err:		mmLogStatus("Load failed");		progress->Update(100, "Load failed");		goto out;	abort:		mmLogStatus("Load Aborted");	out:		return;	}	void doSave(wxCommandEvent& event)	{		TimerLock lock(myTimer);		if (!myHID) return;		mmLogStatus("Saving configuration");		wxWindowPtr<wxGenericProgressDialog> progress(			new wxGenericProgressDialog(				"Save config settings",				"Saving config settings",				100,				this,				wxPD_CAN_ABORT | wxPD_REMAINING_TIME)				);		int currentProgress = 0;		int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS + 1;		// Write board config first.		int flashRow = SCSI_CONFIG_BOARD_ROW;		{			std::stringstream ss;			ss << "Programming flash array " << SCSI_CONFIG_ARRAY <<				" row " << flashRow;			mmLogStatus(ss.str());			currentProgress += 1;			if (!progress->Update(					(100 * currentProgress) / totalProgress,					ss.str()					)				)			{				goto abort;			}			std::vector<uint8_t> flashData =				ConfigUtil::boardConfigToBytes(myBoardPanel->getConfig());			try			{				myHID->writeFlashRow(					SCSI_CONFIG_ARRAY, flashRow, flashData);			}			catch (std::runtime_error& e)			{				mmLogStatus(e.what());				goto err;			}		}		flashRow = SCSI_CONFIG_0_ROW;		for (size_t i = 0;			i < myTargets.size();			++i, flashRow += SCSI_CONFIG_ROWS)		{			TargetConfig config(myTargets[i]->getConfig());			std::vector<uint8_t> raw(ConfigUtil::toBytes(config));			for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j)			{				std::stringstream ss;				ss << "Programming flash array " << SCSI_CONFIG_ARRAY <<					" row " << (flashRow + j);				mmLogStatus(ss.str());				currentProgress += 1;				if (currentProgress == totalProgress)				{					ss.str("Save Complete.");					mmLogStatus("Save Complete.");				}				if (!progress->Update(						(100 * currentProgress) / totalProgress,						ss.str()						)					)				{					goto abort;				}				std::vector<uint8_t> flashData(SCSI_CONFIG_ROW_SIZE, 0);				std::copy(					&raw[j * SCSI_CONFIG_ROW_SIZE],					&raw[(1+j) * SCSI_CONFIG_ROW_SIZE],					flashData.begin());				try				{					myHID->writeFlashRow(						SCSI_CONFIG_ARRAY, flashRow + j, flashData);				}				catch (std::runtime_error& e)				{					mmLogStatus(e.what());					goto err;				}			}		}		// Reboot so new settings take effect.		myHID->enterBootloader();		myHID.reset();		goto out;	err:		mmLogStatus("Save failed");		progress->Update(100, "Save failed");		goto out;	abort:		mmLogStatus("Save Aborted");	out:		return;	}	// Note: Don't confuse this with the wxApp::OnExit virtual method	void OnExitEvt(wxCommandEvent& event);	void OnCloseEvt(wxCloseEvent& event);	void OnAbout(wxCommandEvent& event)	{		wxMessageBox(			"SCSI2SD (scsi2sd-util)\n"			"Copyright (C) 2014 Michael McMaster <michael@codesrc.com>\n"			"\n""This program is free software: you can redistribute it and/or modify\n""it under the terms of the GNU General Public License as published by\n""the Free Software Foundation, either version 3 of the License, or\n""(at your option) any later version.\n""\n""This program is distributed in the hope that it will be useful,\n""but WITHOUT ANY WARRANTY; without even the implied warranty of\n""MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n""GNU General Public License for more details.\n""\n""You should have received a copy of the GNU General Public License\n""along with this program.  If not, see <http://www.gnu.org/licenses/>.\n",			"About scsi2sd-util", wxOK | wxICON_INFORMATION );	}	wxDECLARE_EVENT_TABLE();};wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)	EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)	EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)	EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow)	EVT_MENU(AppFrame::ID_SaveFile, AppFrame::OnID_SaveFile)	EVT_MENU(AppFrame::ID_OpenFile, AppFrame::OnID_OpenFile)	EVT_MENU(wxID_EXIT, AppFrame::OnExitEvt)	EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)	EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)	EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged)	EVT_BUTTON(ID_BtnSave, AppFrame::doSave)	EVT_BUTTON(ID_BtnLoad, AppFrame::doLoad)	EVT_CLOSE(AppFrame::OnCloseEvt)wxEND_EVENT_TABLE()class App : public wxApp{public:	virtual bool OnInit()	{		AppFrame* frame = new AppFrame();		frame->Show(true);		SetTopWindow(frame);		return true;	}};} // namespace// Main MethodwxIMPLEMENT_APP(App);voidAppFrame::OnExitEvt(wxCommandEvent& event){	wxGetApp().ExitMainLoop();}voidAppFrame::OnCloseEvt(wxCloseEvent& event){	wxGetApp().ExitMainLoop();}
 |