ddd10919b70a1b6758c7633c44f1eb1c3bed9e4c
[SCSI2SD-V6.git] / src / scsi2sd-util6 / scsi2sd-util.cc
1 //      Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
2 //
3 //      This file is part of SCSI2SD.
4 //
5 //      SCSI2SD is free software: you can redistribute it and/or modify
6 //      it under the terms of the GNU General Public License as published by
7 //      the Free Software Foundation, either version 3 of the License, or
8 //      (at your option) any later version.
9 //
10 //      SCSI2SD is distributed in the hope that it will be useful,
11 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //      GNU General Public License for more details.
14 //
15 //      You should have received a copy of the GNU General Public License
16 //      along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
17
18
19 // For compilers that support precompilation, includes "wx/wx.h".
20 #include <wx/wxprec.h>
21 #ifndef WX_PRECOMP
22 #include <wx/wx.h>
23 #endif
24
25 #include <wx/app.h>
26 #include <wx/filedlg.h>
27 #include <wx/filefn.h>
28 #include <wx/filename.h>
29 #include <wx/log.h>
30 #include <wx/notebook.h>
31 #include <wx/progdlg.h>
32 #include <wx/utils.h>
33 #include <wx/wfstream.h>
34 #include <wx/windowptr.h>
35 #include <wx/stdpaths.h>
36 #include <wx/stream.h>
37 #include <wx/thread.h>
38 #include <wx/txtstrm.h>
39
40 #include <zipper.hh>
41
42 #include "ConfigUtil.hh"
43 #include "BoardPanel.hh"
44 #include "TargetPanel.hh"
45 #include "SCSI2SD_HID.hh"
46 #include "Dfu.hh"
47
48 #include "terminalwx.h"
49
50 #include <algorithm>
51 #include <iomanip>
52 #include <vector>
53 #include <set>
54 #include <sstream>
55
56 #if __cplusplus >= 201103L
57 #include <cstdint>
58 #include <memory>
59 using std::shared_ptr;
60 #else
61 #include <stdint.h>
62 #include <tr1/memory>
63 using std::tr1::shared_ptr;
64 #endif
65
66 using namespace SCSI2SD;
67
68 class ProgressWrapper
69 {
70 public:
71         void setProgressDialog(
72                 const wxWindowPtr<wxGenericProgressDialog>& dlg,
73                 size_t maxRows)
74         {
75                 myProgressDialog = dlg;
76                 myMaxRows = maxRows;
77                 myNumRows = 0;
78         }
79
80         void clearProgressDialog()
81         {
82                 myProgressDialog->Show(false);
83                 myProgressDialog.reset();
84         }
85
86         void update(unsigned char arrayId, unsigned short rowNum)
87         {
88                 if (!myProgressDialog) return;
89
90                 myNumRows++;
91
92                 std::stringstream ss;
93                 ss << "Writing flash array " <<
94                         static_cast<int>(arrayId) << " row " <<
95                         static_cast<int>(rowNum);
96                 wxLogMessage("%s", ss.str());
97                 myProgressDialog->Update(myNumRows, ss.str());
98         }
99
100 private:
101         wxWindowPtr<wxGenericProgressDialog> myProgressDialog;
102         size_t myMaxRows;
103         size_t myNumRows;
104 };
105 static ProgressWrapper TheProgressWrapper;
106
107 extern "C"
108 void ProgressUpdate(unsigned char arrayId, unsigned short rowNum)
109 {
110         TheProgressWrapper.update(arrayId, rowNum);
111 }
112
113 namespace
114 {
115
116
117 class TimerLock
118 {
119 public:
120         TimerLock(wxTimer* timer) :
121                 myTimer(timer),
122                 myInterval(myTimer->GetInterval())
123         {
124                 myTimer->Stop();
125         };
126
127         virtual ~TimerLock()
128         {
129                 if (myTimer && myInterval > 0)
130                 {
131                         myTimer->Start(myInterval);
132                 }
133         }
134 private:
135         wxTimer* myTimer;
136         int myInterval;
137 };
138
139 class AppFrame : public wxFrame
140 {
141 public:
142         AppFrame() :
143                 wxFrame(NULL, wxID_ANY, "scsi2sd-util6", wxPoint(50, 50), wxSize(600, 700)),
144                 myInitialConfig(false),
145                 myTickCounter(0),
146                 myLastPollTime(0),
147                 myConsoleProcess(NULL)
148         {
149                 wxMenu *menuFile = new wxMenu();
150                 menuFile->Append(
151                         ID_SaveFile,
152                         _("&Save to file..."),
153                         _("Save settings to local file."));
154                 menuFile->Append(
155                         ID_OpenFile,
156                         _("&Open file..."),
157                         _("Load settings from local file."));
158                 menuFile->AppendSeparator();
159                 menuFile->Append(
160                         ID_ConfigDefaults,
161                         _("Load &Defaults"),
162                         _("Load default configuration options."));
163
164                 menuFile->AppendSeparator();
165                 myLoadButton = menuFile->Append(
166                         ID_BtnLoad,
167                         _("Load from device"),
168                         _("Load configuration from hardware device"));
169                 mySaveButton = menuFile->Append(
170                         ID_BtnSave,
171                         _("Save to device"),
172                         _("Save configuration to hardware device"));
173
174                 menuFile->AppendSeparator();
175                 menuFile->Append(
176                         ID_Firmware,
177                         _("&Upgrade Firmware..."),
178                         _("Upgrade or inspect device firmware version."));
179                 menuFile->Append(wxID_EXIT);
180
181                 wxMenu *menuWindow= new wxMenu();
182                 menuWindow->Append(
183                         ID_LogWindow,
184                         _("Show &Log"),
185                         _("Show debug log window"));
186
187                 wxMenu *menuDebug = new wxMenu();
188                 mySCSILogChk = menuDebug->AppendCheckItem(
189                         ID_SCSILog,
190                         _("Log SCSI data"),
191                         _("Log SCSI commands"));
192
193                 mySelfTestChk = menuDebug->AppendCheckItem(
194                         ID_SelfTest,
195                         _("SCSI Standalone Self-Test"),
196                         _("SCSI Standalone Self-Test"));
197
198                 wxMenu *menuHelp = new wxMenu();
199                 menuHelp->Append(wxID_ABOUT);
200
201                 wxMenuBar *menuBar = new wxMenuBar();
202                 menuBar->Append( menuFile, _("&File") );
203                 menuBar->Append( menuDebug, _("&Debug") );
204                 menuBar->Append( menuWindow, _("&Window") );
205                 menuBar->Append( menuHelp, _("&Help") );
206                 SetMenuBar( menuBar );
207
208                 CreateStatusBar();
209
210                 {
211                         wxPanel* cfgPanel = new wxPanel(this);
212                         wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 1, 15, 15);
213                         cfgPanel->SetSizer(fgs);
214
215                         // Empty space below menu bar.
216                         fgs->Add(5, 5, wxALL);
217
218                         wxNotebook* tabs = new wxNotebook(cfgPanel, ID_Notebook);
219                         myBoardPanel = new BoardPanel(tabs, ConfigUtil::DefaultBoardConfig());
220                         tabs->AddPage(myBoardPanel, _("General Settings"));
221                         for (int i = 0; i < S2S_MAX_TARGETS; ++i)
222                         {
223                                 TargetPanel* target =
224                                         new TargetPanel(tabs, ConfigUtil::Default(i));
225                                 myTargets.push_back(target);
226                                 std::stringstream ss;
227                                 ss << "Device " << (i + 1);
228                                 tabs->AddPage(target, ss.str());
229                                 target->Fit();
230                         }
231                         tabs->Fit();
232                         fgs->Add(tabs);
233
234                         cfgPanel->Fit();
235                 }
236
237 #ifdef __WINDOWS__
238                 Fit(); // Needed to reduce window size on Windows
239 #else
240                 FitInside(); // Needed on Linux to prevent status bar overlap
241 #endif
242
243                 myLogWindow = new wxLogWindow(this, _("scsi2sd-util6 debug log"), true);
244                 myLogWindow->PassMessages(false); // Prevent messagebox popups
245
246                 myTimer = new wxTimer(this, ID_Timer);
247                 myTimer->Start(64); //ms, suitable for scsi debug logging
248         }
249
250 private:
251         Dfu myDfu;
252         wxLogWindow* myLogWindow;
253         BoardPanel* myBoardPanel;
254         std::vector<TargetPanel*> myTargets;
255         wxMenuItem* mySCSILogChk;
256         wxMenuItem* mySelfTestChk;
257         wxMenuItem* myLoadButton;
258         wxMenuItem* mySaveButton;
259         wxTimer* myTimer;
260         shared_ptr<HID> myHID;
261         bool myInitialConfig;
262
263         uint8_t myTickCounter;
264
265         time_t myLastPollTime;
266
267         wxWindowPtr<TerminalWx> myConsoleTerm;
268         shared_ptr<wxProcess> myConsoleProcess;
269         wxInputStream* myConsoleStdout;
270         wxInputStream* myConsoleStderr;
271         
272
273         void mmLogStatus(const std::string& msg)
274         {
275                 // We set PassMessages to false on our log window to prevent popups, but
276                 // this also prevents wxLogStatus from updating the status bar.
277                 SetStatusText(msg);
278                 wxLogMessage(this, "%s", msg.c_str());
279         }
280
281         void onConfigChanged(wxCommandEvent& event)
282         {
283                 evaluate();
284         }
285
286         void evaluate()
287         {
288                 bool valid = true;
289
290                 // Check for duplicate SCSI IDs
291                 std::set<uint8_t> enabledID;
292
293                 // Check for overlapping SD sectors.
294                 std::vector<std::pair<uint32_t, uint64_t> > sdSectors;
295
296                 bool isTargetEnabled = false; // Need at least one enabled
297                 uint32_t autoStartSector = 0;
298                 for (size_t i = 0; i < myTargets.size(); ++i)
299                 {
300                         myTargets[i]->setAutoStartSector(autoStartSector);
301                         valid = myTargets[i]->evaluate() && valid;
302
303                         if (myTargets[i]->isEnabled())
304                         {
305                                 isTargetEnabled = true;
306                                 uint8_t scsiID = myTargets[i]->getSCSIId();
307                                 if (enabledID.find(scsiID) != enabledID.end())
308                                 {
309                                         myTargets[i]->setDuplicateID(true);
310                                         valid = false;
311                                 }
312                                 else
313                                 {
314                                         enabledID.insert(scsiID);
315                                         myTargets[i]->setDuplicateID(false);
316                                 }
317
318                                 auto sdSectorRange = myTargets[i]->getSDSectorRange();
319                                 for (auto it(sdSectors.begin()); it != sdSectors.end(); ++it)
320                                 {
321                                         if (sdSectorRange.first < it->second &&
322                                                 sdSectorRange.second > it->first)
323                                         {
324                                                 valid = false;
325                                                 myTargets[i]->setSDSectorOverlap(true);
326                                         }
327                                         else
328                                         {
329                                                 myTargets[i]->setSDSectorOverlap(false);
330                                         }
331                                 }
332                                 sdSectors.push_back(sdSectorRange);
333                                 autoStartSector = sdSectorRange.second;
334                         }
335                         else
336                         {
337                                 myTargets[i]->setDuplicateID(false);
338                                 myTargets[i]->setSDSectorOverlap(false);
339                         }
340                 }
341
342                 valid = valid && isTargetEnabled; // Need at least one.
343
344                 mySaveButton->Enable(valid && myHID);
345
346                 myLoadButton->Enable(static_cast<bool>(myHID));
347         }
348
349
350         enum
351         {
352                 ID_ConfigDefaults = wxID_HIGHEST + 1,
353                 ID_Firmware,
354                 ID_Timer,
355                 ID_Notebook,
356                 ID_BtnLoad,
357                 ID_BtnSave,
358                 ID_LogWindow,
359                 ID_SCSILog,
360                 ID_SelfTest,
361                 ID_SaveFile,
362                 ID_OpenFile,
363                 ID_ConsoleTerm
364         };
365
366         void OnID_ConfigDefaults(wxCommandEvent& event)
367         {
368                 myBoardPanel->setConfig(ConfigUtil::DefaultBoardConfig());
369                 for (size_t i = 0; i < myTargets.size(); ++i)
370                 {
371                         myTargets[i]->setConfig(ConfigUtil::Default(i));
372                 }
373         }
374
375         void OnID_SaveFile(wxCommandEvent& event)
376         {
377                 TimerLock lock(myTimer);
378
379
380
381                 wxFileDialog dlg(
382                         this,
383                         "Save config settings",
384                         "",
385                         "",
386                         "XML files (*.xml)|*.xml",
387                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
388                 if (dlg.ShowModal() == wxID_CANCEL) return;
389
390                 wxFileOutputStream file(dlg.GetPath());
391                 if (!file.IsOk())
392                 {
393                         wxLogError("Cannot save settings to file '%s'.", dlg.GetPath());
394                         return;
395                 }
396
397                 wxTextOutputStream s(file);
398
399                 s << "<SCSI2SD>\n";
400
401                 s << ConfigUtil::toXML(myBoardPanel->getConfig());
402                 for (size_t i = 0; i < myTargets.size(); ++i)
403                 {
404                         s << ConfigUtil::toXML(myTargets[i]->getConfig());
405                 }
406
407                 s << "</SCSI2SD>\n";
408         }
409
410         void OnID_OpenFile(wxCommandEvent& event)
411         {
412                 TimerLock lock(myTimer);
413
414                 wxFileDialog dlg(
415                         this,
416                         "Load config settings",
417                         "",
418                         "",
419                         "XML files (*.xml)|*.xml",
420                         wxFD_OPEN | wxFD_FILE_MUST_EXIST);
421                 if (dlg.ShowModal() == wxID_CANCEL) return;
422
423                 try
424                 {
425                         std::pair<S2S_BoardCfg, std::vector<S2S_TargetCfg>> configs(
426                                 ConfigUtil::fromXML(std::string(dlg.GetPath())));
427
428                         myBoardPanel->setConfig(configs.first);
429
430                         size_t i;
431                         for (i = 0; i < configs.second.size() && i < myTargets.size(); ++i)
432                         {
433                                 myTargets[i]->setConfig(configs.second[i]);
434                         }
435
436                         for (; i < myTargets.size(); ++i)
437                         {
438                                 myTargets[i]->setConfig(ConfigUtil::Default(i));
439                         }
440                 }
441                 catch (std::exception& e)
442                 {
443                         wxLogError(
444                                 "Cannot load settings from file '%s'.\n%s",
445                                 dlg.GetPath(),
446                                 e.what());
447
448                         wxMessageBox(
449                                 e.what(),
450                                 "Load error",
451                                 wxOK | wxICON_ERROR);
452                 }
453         }
454
455         void OnID_Firmware(wxCommandEvent& event)
456         {
457                 TimerLock lock(myTimer);
458                 doFirmwareUpdate();
459         }
460
461         void OnID_LogWindow(wxCommandEvent& event)
462         {
463                 myLogWindow->Show();
464         }
465
466         void doFirmwareUpdate()
467         {
468                 wxFileDialog dlg(
469                         this,
470                         "Load firmware file",
471                         "",
472                         "",
473                         "SCSI2SD Firmware files (*.dfu)|*.dfu",
474                         wxFD_OPEN | wxFD_FILE_MUST_EXIST);
475                 if (dlg.ShowModal() == wxID_CANCEL) return;
476
477                 std::string filename(dlg.GetPath());
478                 wxLogMessage("Attempting firmware update from file %s", filename);
479
480                 wxWindowPtr<wxGenericProgressDialog> progress(
481                         new wxGenericProgressDialog(
482                                 "Searching for bootloader",
483                                 "Searching for bootloader",
484                                 100,
485                                 this,
486                                 wxPD_AUTO_HIDE | wxPD_CAN_ABORT)
487                                 );
488                 mmLogStatus("Searching for bootloader");
489                 bool versionChecked = false;
490                 while (true)
491                 {
492                         try
493                         {
494                                 if (!myHID) myHID.reset(HID::Open());
495                                 if (myHID)
496                                 {
497                                         if (!myHID->isCorrectFirmware(filename))
498                                         {
499                                                 wxMessageBox(
500                                                         "Firmware does not match device hardware",
501                                                         "Wrong filename",
502                                                         wxOK | wxICON_ERROR);
503                                                 return;
504                                         }
505                                         versionChecked = true;
506
507                                         mmLogStatus("Resetting SCSI2SD into bootloader");
508
509                                         myHID->enterBootloader();
510                                         myHID.reset();
511                                 }
512
513
514                                 if (myDfu.hasDevice() && !versionChecked)
515                                 {
516                                         mmLogStatus("STM DFU Bootloader found, checking compatibility");
517                                         progress->Show(0);
518                                         if (!checkVersionMarker(filename))
519                                         {
520                                                 wxMessageBox(
521                                                         "Firmware does not match device hardware",
522                                                         "Wrong filename",
523                                                         wxOK | wxICON_ERROR);
524                                                 return;
525                                         }
526                                         versionChecked = true;
527                                 }
528
529                                 if (myDfu.hasDevice())
530                                 {
531                                         mmLogStatus("STM DFU Bootloader found");
532                                         progress->Show(10);
533                                         doDFUUpdate(filename);
534                                         return;
535                                 }
536                         }
537                         catch (std::exception& e)
538                         {
539                                 mmLogStatus(e.what());
540                                 myHID.reset();
541                         }
542                         wxMilliSleep(100);
543                         if (!progress->Pulse())
544                         {
545                                 return; // user cancelled.
546                         }
547                 }
548         }
549
550         bool checkVersionMarker(const std::string& firmware)
551         {
552                 std::stringstream ss;
553 #ifdef __WINDOWS__
554                 ss << "dfu-util ";
555 #else
556                 if (wxExecute("which dfu-util", wxEXEC_SYNC) == 0)
557                 {
558                         ss << "dfu-util ";
559                 } else {
560                         wxFileName exePath(wxStandardPaths::Get().GetExecutablePath());
561                         ss << '"' << exePath.GetPathWithSep() << "dfu-util\" ";
562                 }
563 #endif
564
565                 std::string tmpFile =
566                         wxFileName::CreateTempFileName(
567                                 _("SCSI2SD_MARKER"), static_cast<wxFile*>(NULL));
568                 wxRemoveFile(tmpFile); // dfu-util won't overwrite.
569
570                 ss << "--alt 2 -s 0x1FFF7800:4 -U \"" << tmpFile << "\"";
571
572                 wxLogMessage("Running: %s", ss.str());
573
574                 std::string cmd = ss.str();
575                 long result = wxExecute(
576                         cmd.c_str(),
577                         wxEXEC_SYNC
578                         );
579                 if (result != 0)
580                 {
581                         wxLogMessage("OTP Version check failed.");
582                         return false;
583                 }
584
585                 // Ok, we now have a file with 8 bytes in it.
586                 wxFile file(tmpFile);
587                 if (file.Length() != 4)
588                 {
589                         wxLogMessage("OTP Version check file isn't 4 bytes.");
590                         return false;
591                 }
592
593                 uint8_t data[4];
594                 if (file.Read(data, sizeof(data)) != sizeof(data))
595                 {
596                         wxMessageBox(
597                                 "Couldn't read file",
598                                 "Couldn't read file",
599                                 wxOK | wxICON_ERROR);
600                         return false;
601                 }
602                 wxRemoveFile(tmpFile);
603
604                 uint32_t value =
605                         (((uint32_t)(data[0]))) |
606                         (((uint32_t)(data[1])) << 8) |
607                         (((uint32_t)(data[2])) << 16) |
608                         (((uint32_t)(data[3])) << 24);
609                 if (value == 0xFFFFFFFF)
610                 {
611                         // Not set, ignore.
612                         wxLogMessage("OTP Hardware version not set. Ignoring.");
613                         return true;
614                 }
615                 else if (value == 0x06002020)
616                 {
617                         wxLogMessage("Found V6 2020 hardware marker");
618                         return firmware.rfind("firmware.V6.2020.dfu") != std::string::npos;
619                 }
620                 else if (value == 0x06002019)
621                 {
622                         wxLogMessage("Found V6 revF hardware marker");
623                         return firmware.rfind("firmware.V6.revF.dfu") != std::string::npos ||
624                                 firmware.rfind("firmware.dfu") != std::string::npos;
625                 }
626                 else
627                 {
628                         wxLogMessage("Found unknown hardware marker: %u", value);
629                         return false; // Some unknown version.
630                 }
631         }
632
633
634         void doDFUUpdate(const std::string& filename)
635         {
636                 if (filename.find(".dfu") == std::string::npos)
637                 {
638                         wxMessageBox(
639                                 "Wrong filename",
640                                 "SCSI2SD V6 requires a .dfu file",
641                                 wxOK | wxICON_ERROR);
642                         return;
643                 }
644
645                 std::stringstream ss;
646 #ifdef __WINDOWS__
647                 ss << "dfu-util --download \""
648                         << filename.c_str() << "\" --alt 0 --reset";
649 #else
650                 if (wxExecute("which dfu-util", wxEXEC_SYNC) == 0)
651                 {
652                         ss << "dfu-util ";
653                 } else {
654                         wxFileName exePath(wxStandardPaths::Get().GetExecutablePath());
655                         ss << '"' << exePath.GetPathWithSep() << "dfu-util\" ";
656                 }
657                 ss << "--download \"" << filename.c_str() << "\" --alt 0 --reset";
658 #endif
659
660                 wxLogMessage("Running: %s", ss.str());
661
662                 myConsoleProcess.reset(new wxProcess(this));
663                 myConsoleProcess->Redirect();
664                 std::string cmd = ss.str();
665                 long result = wxExecute(
666                         cmd.c_str(),
667                         wxEXEC_ASYNC,
668                         myConsoleProcess.get()
669                         );
670                 if (!result)
671                 {
672                         wxMessageBox(
673                                 "Update failed",
674                                 "Firmware update failed (dfu-util not found ?) Command = " + cmd,
675                                 wxOK | wxICON_ERROR);
676                         mmLogStatus("Firmware update failed");
677                         myConsoleProcess.reset();
678                 } else {
679                         myConsoleStdout = myConsoleProcess->GetInputStream();
680                         myConsoleStderr = myConsoleProcess->GetErrorStream();
681                         wxFrame* frame(new wxFrame(this, wxID_ANY, "dfu-util"));
682                         myConsoleTerm.reset(new TerminalWx(frame, wxID_ANY, wxDefaultPosition));
683                         frame->Fit();
684                         frame->Show();
685                 }
686         }
687
688         void redirectDfuOutput()
689         {
690                 if (myConsoleProcess)
691                 {
692                         std::stringstream ss;
693                         while (myConsoleStderr && !myConsoleStderr->Eof() && myConsoleStderr->CanRead())
694                         {
695                                 int c = myConsoleStderr->GetC();
696                                 if (c == '\n')
697                                 {
698                                         ss << "\r\n";
699                                 }
700                                 else if (c >= 0)
701                                 {
702                                         ss << (char) c;
703                                 }
704                         }
705                         while (myConsoleStdout && !myConsoleStdout->Eof() && myConsoleStdout->CanRead())
706                         {
707                                 int c = myConsoleStdout->GetC();
708                                 if (c == '\n')
709                                 {
710                                         ss << "\r\n";
711                                 }
712                                 else if (c >= 0)
713                                 {
714                                         ss << (char) c;
715                                 }
716                         }
717                         myConsoleTerm->DisplayCharsUnsafe(ss.str());
718                 }
719         }
720
721         void doFinishDfu(wxProcessEvent& event)
722         {
723                 redirectDfuOutput();
724
725                 if (event.GetExitCode() == 0)
726                 {
727                         wxMessageBox(
728                                 "Update complete",
729                                 "Firmware update complete. Please reconnect USB cable.",
730                                 wxOK | wxICON_ERROR);
731                         mmLogStatus("Firmware update succeeded");
732                 } else {
733                         wxMessageBox(
734                                 "Update failed",
735                                 "Firmware update failed.",
736                                 wxOK | wxICON_ERROR);
737                         mmLogStatus("Firmware update failed");
738                 }
739
740                 myConsoleStdout = myConsoleStderr = NULL;
741                 myConsoleProcess.reset();
742                 myConsoleTerm->GetParent()->Close();
743                 myConsoleTerm->Close();
744                 myConsoleTerm.reset();
745
746         }
747
748         void dumpSCSICommand(std::vector<uint8_t> buf)
749         {
750                 std::stringstream msg;
751                 msg << std::hex;
752                 for (size_t i = 0; i < 32 && i < buf.size(); ++i)
753                 {
754                         msg << std::setfill('0') << std::setw(2) <<
755                         static_cast<int>(buf[i]) << ' ';
756                 }
757                 wxLogMessage(this, msg.str().c_str());
758         }
759
760         void logSCSI()
761         {
762                 if (!mySCSILogChk->IsChecked() ||
763                         !myHID)
764                 {
765                         return;
766                 }
767                 try
768                 {
769                         std::vector<uint8_t> info;
770                         if (myHID->readSCSIDebugInfo(info))
771                         {
772                                 dumpSCSICommand(info);
773                         }
774                 }
775                 catch (std::exception& e)
776                 {
777                         wxLogWarning(this, e.what());
778                         myHID.reset();
779                 }
780         }
781
782         void OnID_Timer(wxTimerEvent& event)
783         {
784                 redirectDfuOutput();
785
786                 logSCSI();
787                 time_t now = time(NULL);
788                 if (now == myLastPollTime) return;
789                 myLastPollTime = now;
790
791                 // Check if we are connected to the HID device.
792                 try
793                 {
794                         if (myHID && !myHID->ping())
795                         {
796                                 // Verify the USB HID connection is valid
797                                 myHID.reset();
798                         }
799
800                         if (!myHID)
801                         {
802                                 myHID.reset(HID::Open());
803                                 if (myHID)
804                                 {
805                                         std::stringstream msg;
806                                         msg << "SCSI2SD Ready, firmware version " <<
807                                                 myHID->getFirmwareVersionStr();
808                                         mmLogStatus(msg.str());
809
810                                         std::stringstream devInfo;
811                                         devInfo << "Hardware version: " <<
812                                                 myHID->getHardwareVersion() << std::endl <<
813                                                 "Serial Number: " <<
814                                                 myHID->getSerialNumber();
815                                         wxLogMessage(this, "%s", devInfo.str());
816
817                                         std::vector<uint8_t> csd(myHID->getSD_CSD());
818                                         std::vector<uint8_t> cid(myHID->getSD_CID());
819                                         std::stringstream sdinfo;
820                                         sdinfo << "SD Capacity (512-byte sectors): " <<
821                                                 myHID->getSDCapacity() << std::endl;
822
823                                         sdinfo << "SD CSD Register: ";
824                                         for (size_t i = 0; i < csd.size(); ++i)
825                                         {
826                                                 sdinfo <<
827                                                         std::hex << std::setfill('0') << std::setw(2) <<
828                                                         static_cast<int>(csd[i]);
829                                         }
830                                         sdinfo << std::endl;
831                                         sdinfo << "SD CID Register: ";
832                                         for (size_t i = 0; i < cid.size(); ++i)
833                                         {
834                                                 sdinfo <<
835                                                         std::hex << std::setfill('0') << std::setw(2) <<
836                                                         static_cast<int>(cid[i]);
837                                         }
838
839                                         wxLogMessage(this, "%s", sdinfo.str());
840
841                                         if (mySelfTestChk->IsChecked())
842                                         {
843                                                 std::stringstream scsiInfo;
844                                                 int errcode;
845                                                 scsiInfo << "SCSI Self-Test: ";
846                                                 if (myHID->scsiSelfTest(errcode))
847                                                 {
848                                                         scsiInfo << "Passed";
849                                                 }
850                                                 else
851                                                 {
852                                                         scsiInfo << "FAIL (" << errcode << ")";
853                                                 }
854                                                 wxLogMessage(this, "%s", scsiInfo.str());
855                                         }
856
857                                         if (!myInitialConfig)
858                                         {
859 /* This doesn't work properly, and causes crashes.
860                                                 wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad);
861                                                 GetEventHandler()->AddPendingEvent(loadEvent);
862 */
863                                         }
864
865                                 }
866                                 else
867                                 {
868                                         char ticks[] = {'/', '-', '\\', '|'};
869                                         std::stringstream ss;
870                                         ss << "Searching for SCSI2SD device " << ticks[myTickCounter % sizeof(ticks)];
871                                         myTickCounter++;
872                                         SetStatusText(ss.str());
873                                 }
874                         }
875                 }
876                 catch (std::runtime_error& e)
877                 {
878                         std::cerr << e.what() << std::endl;
879                         mmLogStatus(e.what());
880                 }
881
882                 evaluate();
883         }
884
885         void doLoad(wxCommandEvent& event)
886         {
887                 TimerLock lock(myTimer);
888                 if (!myHID) return;
889
890                 mmLogStatus("Loading configuration");
891
892                 wxWindowPtr<wxGenericProgressDialog> progress(
893                         new wxGenericProgressDialog(
894                                 "Load config settings",
895                                 "Loading config settings",
896                                 100,
897                                 this,
898                                 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
899                                 );
900
901                 int currentProgress = 0;
902                 int totalProgress = 2;
903
904                 std::vector<uint8_t> cfgData(S2S_CFG_SIZE);
905                 uint32_t sector = myHID->getSDCapacity() - 2;
906                 for (size_t i = 0; i < 2; ++i)
907                 {
908                         std::stringstream ss;
909                         ss << "Reading sector " << sector;
910                         mmLogStatus(ss.str());
911                         currentProgress += 1;
912                         if (currentProgress == totalProgress)
913                         {
914                                 ss.str("Load Complete.");
915                                 mmLogStatus("Load Complete.");
916                         }
917
918                         if (!progress->Update(
919                                         (100 * currentProgress) / totalProgress,
920                                         ss.str()
921                                         )
922                                 )
923                         {
924                                 goto abort;
925                         }
926
927                         std::vector<uint8_t> sdData;
928
929                         try
930                         {
931                                 myHID->readSector(sector++, sdData);
932                         }
933                         catch (std::runtime_error& e)
934                         {
935                                 mmLogStatus(e.what());
936                                 goto err;
937                         }
938
939                         std::copy(
940                                 sdData.begin(),
941                                 sdData.end(),
942                                 &cfgData[i * 512]);
943                 }
944
945                 myBoardPanel->setConfig(ConfigUtil::boardConfigFromBytes(&cfgData[0]));
946                 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
947                 {
948                         myTargets[i]->setConfig(
949                                 ConfigUtil::fromBytes(
950                                         &cfgData[sizeof(S2S_BoardCfg) + i * sizeof(S2S_TargetCfg)]
951                                         )
952                                 );
953                 }
954
955                 myInitialConfig = true;
956                 goto out;
957
958         err:
959                 mmLogStatus("Load failed");
960                 progress->Update(100, "Load failed");
961                 goto out;
962
963         abort:
964                 mmLogStatus("Load Aborted");
965
966         out:
967                 return;
968
969         }
970
971         void doSave(wxCommandEvent& event)
972         {
973                 TimerLock lock(myTimer);
974                 if (!myHID) return;
975
976                 mmLogStatus("Saving configuration");
977
978                 wxWindowPtr<wxGenericProgressDialog> progress(
979                         new wxGenericProgressDialog(
980                                 "Save config settings",
981                                 "Saving config settings",
982                                 100,
983                                 this,
984                                 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
985                                 );
986
987
988                 int currentProgress = 0;
989                 int totalProgress = 2;
990
991                 std::vector<uint8_t> cfgData(
992                         ConfigUtil::boardConfigToBytes(myBoardPanel->getConfig())
993                         );
994                 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
995                 {
996                         std::vector<uint8_t> raw(
997                                 ConfigUtil::toBytes(myTargets[i]->getConfig())
998                                 );
999                         cfgData.insert(cfgData.end(), raw.begin(), raw.end());
1000                 }
1001
1002                 uint32_t sector = myHID->getSDCapacity() - 2;
1003
1004                 for (size_t i = 0; i < 2; ++i)
1005                 {
1006                         std::stringstream ss;
1007                         ss << "Writing SD sector " << sector;
1008                         mmLogStatus(ss.str());
1009                         currentProgress += 1;
1010
1011                         if (currentProgress == totalProgress)
1012                         {
1013                                 ss.str("Save Complete.");
1014                                 mmLogStatus("Save Complete.");
1015                         }
1016                         if (!progress->Update(
1017                                         (100 * currentProgress) / totalProgress,
1018                                         ss.str()
1019                                         )
1020                                 )
1021                         {
1022                                 goto abort;
1023                         }
1024
1025                         try
1026                         {
1027                                 std::vector<uint8_t> buf;
1028                                 buf.insert(buf.end(), &cfgData[i * 512], &cfgData[(i+1) * 512]);
1029                                 myHID->writeSector(sector++, buf);
1030                         }
1031                         catch (std::runtime_error& e)
1032                         {
1033                                 mmLogStatus(e.what());
1034                                 goto err;
1035                         }
1036                 }
1037
1038                 myHID.reset();
1039
1040                 goto out;
1041
1042         err:
1043                 mmLogStatus("Save failed");
1044                 progress->Update(100, "Save failed");
1045                 goto out;
1046
1047         abort:
1048                 mmLogStatus("Save Aborted");
1049
1050         out:
1051                 return;
1052
1053         }
1054
1055         // Note: Don't confuse this with the wxApp::OnExit virtual method
1056         void OnExitEvt(wxCommandEvent& event);
1057
1058         void OnCloseEvt(wxCloseEvent& event);
1059
1060         void OnAbout(wxCommandEvent& event)
1061         {
1062                 wxMessageBox(
1063                         "SCSI2SD (scsi2sd-util6)\n"
1064                         "Copyright (C) 2014-2017 Michael McMaster <michael@codesrc.com>\n"
1065                         "\n"
1066 "This program is free software: you can redistribute it and/or modify\n"
1067 "it under the terms of the GNU General Public License as published by\n"
1068 "the Free Software Foundation, either version 3 of the License, or\n"
1069 "(at your option) any later version.\n"
1070 "\n"
1071 "This program is distributed in the hope that it will be useful,\n"
1072 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
1073 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
1074 "GNU General Public License for more details.\n"
1075 "\n"
1076 "You should have received a copy of the GNU General Public License\n"
1077 "along with this program.  If not, see <http://www.gnu.org/licenses/>.\n",
1078
1079                         "About scsi2sd-util6", wxOK | wxICON_INFORMATION );
1080         }
1081
1082         wxDECLARE_EVENT_TABLE();
1083 };
1084
1085 wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
1086         EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)
1087         EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)
1088         EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow)
1089         EVT_MENU(AppFrame::ID_SaveFile, AppFrame::OnID_SaveFile)
1090         EVT_MENU(AppFrame::ID_OpenFile, AppFrame::OnID_OpenFile)
1091         EVT_MENU(wxID_EXIT, AppFrame::OnExitEvt)
1092         EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)
1093
1094         EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)
1095
1096         EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged)
1097
1098         EVT_MENU(ID_BtnSave, AppFrame::doSave)
1099         EVT_MENU(ID_BtnLoad, AppFrame::doLoad)
1100
1101         EVT_CLOSE(AppFrame::OnCloseEvt)
1102
1103         EVT_END_PROCESS(wxID_ANY, AppFrame::doFinishDfu)
1104
1105 wxEND_EVENT_TABLE()
1106
1107
1108
1109 class App : public wxApp
1110 {
1111 public:
1112         virtual bool OnInit()
1113         {
1114                 AppFrame* frame = new AppFrame();
1115                 frame->Show(true);
1116                 SetTopWindow(frame);
1117                 return true;
1118         }
1119 };
1120 } // namespace
1121
1122 // Main Method
1123 wxIMPLEMENT_APP(App);
1124
1125 void
1126 AppFrame::OnExitEvt(wxCommandEvent& event)
1127 {
1128         wxGetApp().ExitMainLoop();
1129 }
1130
1131 void
1132 AppFrame::OnCloseEvt(wxCloseEvent& event)
1133 {
1134         wxGetApp().ExitMainLoop();
1135 }
1136