| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067 | //	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 "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);			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, wxT("Load from device"));			btnFgs->Add(myLoadButton);			mySaveButton =				new wxButton(btnPanel, ID_BtnSave, wxT("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, wxT("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;	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 + 1;			}			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)	{		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";		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::vector<TargetConfig> configs(				ConfigUtil::fromXML(dlg.GetPath()));			size_t i;			for (i = 0; i < configs.size() && i < myTargets.size(); ++i)			{				myTargets[i]->setConfig(configs[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(							wxT("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 logSCSI()	{		if (!mySCSILogChk->IsChecked() ||			!myHID)		{			return;		}		try		{			std::vector<uint8_t> info(HID::HID_PACKET_SIZE);			if (myHID->readSCSIDebugInfo(info))			{				std::stringstream msg;				msg << std::hex;				for (size_t i = 0; i < 32 && i < info.size(); ++i)				{					msg << std::setfill('0') << std::setw(2) <<						static_cast<int>(info[i]) << ' ';				}				wxLogMessage(this, msg.str().c_str());			}		}		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 flashRow = SCSI_CONFIG_0_ROW;		int currentProgress = 0;		int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS;		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 (!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]));		}		myInitialConfig = true;		mmLogStatus("Load Complete");		while (progress->Update(100, "Load Complete"))		{			// Wait for the user to click "Close"			wxMilliSleep(50);		}		goto out;	err:		mmLogStatus("Load failed");		while (progress->Update(100, "Load failed"))		{			// Wait for the user to click "Close"			wxMilliSleep(50);		}		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 flashRow = SCSI_CONFIG_0_ROW;		int currentProgress = 0;		int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS;		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 (!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();		mmLogStatus("Save Complete");		while (progress->Update(100, "Save Complete"))		{			// Wait for the user to click "Close"			wxMilliSleep(50);		}		goto out;	err:		mmLogStatus("Save failed");		while (progress->Update(100, "Save failed"))		{			// Wait for the user to click "Close"			wxMilliSleep(50);		}		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();}
 |