c608e325edbf8b481c4dc2f3334b8728771ab286
[SCSI2SD-V6.git] / software / scsi2sd-util / 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/filedlg.h>
26 #include <wx/filefn.h>
27 #include <wx/filename.h>
28 #include <wx/log.h>
29 #include <wx/notebook.h>
30 #include <wx/progdlg.h>
31 #include <wx/utils.h>
32 #include <wx/windowptr.h>
33 #include <wx/thread.h>
34
35 #include <zipper.hh>
36
37 #include "ConfigUtil.hh"
38 #include "TargetPanel.hh"
39 #include "SCSI2SD_Bootloader.hh"
40 #include "SCSI2SD_HID.hh"
41 #include "Firmware.hh"
42
43 #include <algorithm>
44 #include <vector>
45 #include <set>
46 #include <sstream>
47
48 #if __cplusplus >= 201103L
49 #include <cstdint>
50 #include <memory>
51 using std::shared_ptr;
52 #else
53 #include <stdint.h>
54 #include <tr1/memory>
55 using std::tr1::shared_ptr;
56 #endif
57
58 #define MIN_FIRMWARE_VERSION 0x0400
59
60 using namespace SCSI2SD;
61
62 class ProgressWrapper
63 {
64 public:
65         void setProgressDialog(
66                 const wxWindowPtr<wxGenericProgressDialog>& dlg,
67                 size_t maxRows)
68         {
69                 myProgressDialog = dlg;
70                 myMaxRows = maxRows;
71                 myNumRows = 0;
72         }
73
74         void clearProgressDialog()
75         {
76                 myProgressDialog->Show(false);
77                 myProgressDialog.reset();
78         }
79
80         void update(unsigned char arrayId, unsigned short rowNum)
81         {
82                 if (!myProgressDialog) return;
83
84                 myNumRows++;
85
86                 std::stringstream ss;
87                 ss << "Writing flash array " <<
88                         static_cast<int>(arrayId) << " row " <<
89                         static_cast<int>(rowNum);
90                 wxLogMessage("%s", ss.str());
91                 myProgressDialog->Update(myNumRows, ss.str());
92         }
93
94 private:
95         wxWindowPtr<wxGenericProgressDialog> myProgressDialog;
96         size_t myMaxRows;
97         size_t myNumRows;
98 };
99 static ProgressWrapper TheProgressWrapper;
100
101 extern "C"
102 void ProgressUpdate(unsigned char arrayId, unsigned short rowNum)
103 {
104         TheProgressWrapper.update(arrayId, rowNum);
105 }
106
107 namespace
108 {
109
110 class TimerLock
111 {
112 public:
113         TimerLock(wxTimer* timer) :
114                 myTimer(timer),
115                 myInterval(myTimer->GetInterval())
116         {
117                 myTimer->Stop();
118         };
119
120         virtual ~TimerLock()
121         {
122                 if (myTimer && myInterval > 0)
123                 {
124                         myTimer->Start(myInterval);
125                 }
126         }
127 private:
128         wxTimer* myTimer;
129         int myInterval;
130 };
131
132 class AppFrame : public wxFrame
133 {
134 public:
135         AppFrame() :
136                 wxFrame(NULL, wxID_ANY, "scsi2sd-util", wxPoint(50, 50), wxSize(600, 650)),
137                 myInitialConfig(false),
138                 myTickCounter(0)
139         {
140                 wxMenu *menuFile = new wxMenu();
141                 menuFile->Append(
142                         ID_ConfigDefaults,
143                         "Load &Defaults",
144                         "Load default configuration options.");
145                 menuFile->Append(
146                         ID_Firmware,
147                         "&Upgrade Firmware...",
148                         "Upgrade or inspect device firmware version.");
149                 menuFile->AppendSeparator();
150                 menuFile->Append(wxID_EXIT);
151
152                 wxMenu *menuWindow= new wxMenu();
153                 menuWindow->Append(
154                         ID_LogWindow,
155                         "Show &Log",
156                         "Show debug log window");
157
158                 wxMenu *menuHelp = new wxMenu();
159                 menuHelp->Append(wxID_ABOUT);
160
161                 wxMenuBar *menuBar = new wxMenuBar();
162                 menuBar->Append( menuFile, "&File" );
163                 menuBar->Append( menuWindow, "&Window" );
164                 menuBar->Append( menuHelp, "&Help" );
165                 SetMenuBar( menuBar );
166
167                 CreateStatusBar();
168                 wxLogStatus(this, "Searching for SCSI2SD");
169
170                 {
171                         wxPanel* cfgPanel = new wxPanel(this);
172                         wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 1, 15, 15);
173                         cfgPanel->SetSizer(fgs);
174
175                         // Empty space below menu bar.
176                         fgs->Add(5, 5, wxALL);
177
178                         wxNotebook* tabs = new wxNotebook(cfgPanel, ID_Notebook);
179
180                         for (int i = 0; i < MAX_SCSI_TARGETS; ++i)
181                         {
182                                 TargetPanel* target =
183                                         new TargetPanel(tabs, ConfigUtil::Default(i));
184                                 myTargets.push_back(target);
185                                 std::stringstream ss;
186                                 ss << "Device " << (i + 1);
187                                 tabs->AddPage(target, ss.str());
188                                 target->Fit();
189                         }
190                         tabs->Fit();
191                         fgs->Add(tabs);
192
193
194                         wxPanel* btnPanel = new wxPanel(cfgPanel);
195                         wxFlexGridSizer *btnFgs = new wxFlexGridSizer(1, 2, 5, 5);
196                         btnPanel->SetSizer(btnFgs);
197                         myLoadButton =
198                                 new wxButton(btnPanel, ID_BtnLoad, wxT("Load from device"));
199                         btnFgs->Add(myLoadButton);
200                         mySaveButton =
201                                 new wxButton(btnPanel, ID_BtnSave, wxT("Save to device"));
202                         btnFgs->Add(mySaveButton);
203                         fgs->Add(btnPanel);
204
205                         btnPanel->Fit();
206                         cfgPanel->Fit();
207                 }
208                 //Fit(); // Needed to reduce window size on Windows
209                 FitInside(); // Needed on Linux to prevent status bar overlap
210
211                 myTimer = new wxTimer(this, ID_Timer);
212                 myTimer->Start(1000); //ms
213
214                 myLogWindow = new wxLogWindow(this, wxT("scsi2sd-util debug log"), true);
215         }
216 private:
217         wxLogWindow* myLogWindow;
218         std::vector<TargetPanel*> myTargets;
219         wxButton* myLoadButton;
220         wxButton* mySaveButton;
221         wxTimer* myTimer;
222         shared_ptr<HID> myHID;
223         shared_ptr<Bootloader> myBootloader;
224         bool myInitialConfig;
225
226         uint8_t myTickCounter;
227
228         void onConfigChanged(wxCommandEvent& event)
229         {
230                 evaluate();
231         }
232
233         void evaluate()
234         {
235                 bool valid = true;
236
237                 // Check for duplicate SCSI IDs
238                 std::set<uint8_t> enabledID;
239
240                 // Check for overlapping SD sectors.
241                 std::vector<std::pair<uint32_t, uint64_t> > sdSectors;
242
243                 bool isTargetEnabled = false; // Need at least one enabled
244                 uint32_t autoStartSector = 0;
245                 for (size_t i = 0; i < myTargets.size(); ++i)
246                 {
247                         myTargets[i]->setAutoStartSector(autoStartSector);
248                         valid = myTargets[i]->evaluate() && valid;
249
250                         if (myTargets[i]->isEnabled())
251                         {
252                                 isTargetEnabled = true;
253                                 uint8_t scsiID = myTargets[i]->getSCSIId();
254                                 if (enabledID.find(scsiID) != enabledID.end())
255                                 {
256                                         myTargets[i]->setDuplicateID(true);
257                                         valid = false;
258                                 }
259                                 else
260                                 {
261                                         enabledID.insert(scsiID);
262                                         myTargets[i]->setDuplicateID(false);
263                                 }
264
265                                 auto sdSectorRange = myTargets[i]->getSDSectorRange();
266                                 for (auto it(sdSectors.begin()); it != sdSectors.end(); ++it)
267                                 {
268                                         if (sdSectorRange.first < it->second &&
269                                                 sdSectorRange.second > it->first)
270                                         {
271                                                 valid = false;
272                                                 myTargets[i]->setSDSectorOverlap(true);
273                                         }
274                                         else
275                                         {
276                                                 myTargets[i]->setSDSectorOverlap(false);
277                                         }
278                                 }
279                                 sdSectors.push_back(sdSectorRange);
280                                 autoStartSector = sdSectorRange.second + 1;
281                         }
282                         else
283                         {
284                                 myTargets[i]->setDuplicateID(false);
285                                 myTargets[i]->setSDSectorOverlap(false);
286                         }
287                 }
288
289                 valid = valid && isTargetEnabled; // Need at least one.
290
291                 mySaveButton->Enable(
292                         valid &&
293                         myHID &&
294                         (myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION));
295
296                 myLoadButton->Enable(
297                         myHID &&
298                         (myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION));
299         }
300
301
302         enum
303         {
304                 ID_ConfigDefaults = wxID_HIGHEST + 1,
305                 ID_Firmware,
306                 ID_Timer,
307                 ID_Notebook,
308                 ID_BtnLoad,
309                 ID_BtnSave,
310                 ID_LogWindow
311         };
312
313         void OnID_ConfigDefaults(wxCommandEvent& event)
314         {
315                 for (size_t i = 0; i < myTargets.size(); ++i)
316                 {
317                         myTargets[i]->setConfig(ConfigUtil::Default(i));
318                 }
319         }
320
321         void OnID_Firmware(wxCommandEvent& event)
322         {
323                 TimerLock lock(myTimer);
324                 doFirmwareUpdate();
325         }
326
327         void OnID_LogWindow(wxCommandEvent& event)
328         {
329                 myLogWindow->Show();
330         }
331
332         void doFirmwareUpdate()
333         {
334                 wxFileDialog dlg(
335                         this,
336                         "Load firmware file",
337                         "",
338                         "",
339                         "SCSI2SD Firmware files (*.scsi2sd;*.cyacd)|*.cyacd;*.scsi2sd",
340                         wxFD_OPEN | wxFD_FILE_MUST_EXIST);
341                 if (dlg.ShowModal() == wxID_CANCEL) return;
342
343                 std::string filename(dlg.GetPath());
344
345                 wxWindowPtr<wxGenericProgressDialog> progress(
346                         new wxGenericProgressDialog(
347                                 "Searching for bootloader",
348                                 "Searching for bootloader",
349                                 100,
350                                 this,
351                                 wxPD_AUTO_HIDE | wxPD_CAN_ABORT)
352                                 );
353                 wxLogStatus(this, "Searching for bootloader");
354                 while (true)
355                 {
356                         try
357                         {
358                                 if (!myHID) myHID.reset(HID::Open());
359                                 if (myHID)
360                                 {
361                                         wxLogStatus(this, "Resetting SCSI2SD into bootloader");
362
363                                         myHID->enterBootloader();
364                                         myHID.reset();
365                                 }
366
367
368                                 if (!myBootloader)
369                                 {
370                                         myBootloader.reset(Bootloader::Open());
371                                         if (myBootloader)
372                                         {
373                                                 wxLogStatus(this, "Bootloader found");
374                                                 break;
375                                         }
376                                 }
377
378                                 else if (myBootloader)
379                                 {
380                                         // Verify the USB HID connection is valid
381                                         if (!myBootloader->ping())
382                                         {
383                                                 wxLogStatus(this, "Bootloader ping failed");
384                                                 myBootloader.reset();
385                                         }
386                                         else
387                                         {
388                                                 wxLogStatus(this, "Bootloader found");
389                                                 break;
390                                         }
391                                 }
392                         }
393                         catch (std::exception& e)
394                         {
395                                 wxLogStatus(this, "%s", e.what());
396                                 myHID.reset();
397                                 myBootloader.reset();
398                         }
399                         wxMilliSleep(100);
400                         if (!progress->Pulse())
401                         {
402                                 return; // user cancelled.
403                         }
404                 }
405
406                 int totalFlashRows = 0;
407                 std::string tmpFile;
408                 try
409                 {
410                         zipper::ReaderPtr reader(new zipper::FileReader(filename));
411                         zipper::Decompressor decomp(reader);
412                         std::vector<zipper::CompressedFilePtr> files(decomp.getEntries());
413                         for (auto it(files.begin()); it != files.end(); it++)
414                         {
415                                 if (myBootloader->isCorrectFirmware((*it)->getPath()))
416                                 {
417                                         wxLogStatus(this,
418                                                 "Found firmware entry %s within archive %s",
419                                                 (*it)->getPath(),
420                                                 filename);
421                                         tmpFile =
422                                                 wxFileName::CreateTempFileName(
423                                                         wxT("SCSI2SD_Firmware"), static_cast<wxFile*>(NULL)
424                                                         );
425                                         zipper::FileWriter out(tmpFile);
426                                         (*it)->decompress(out);
427                                         wxLogStatus(this,
428                                                 "Firmware extracted to %s",
429                                                 tmpFile);
430                                         break;
431                                 }
432                         }
433
434                         if (tmpFile.empty())
435                         {
436                                 // TODO allow "force" option
437                                 wxMessageBox(
438                                         "Wrong filename",
439                                         "Wrong filename",
440                                         wxOK | wxICON_ERROR);
441                                 return;
442                         }
443
444                         Firmware firmware(tmpFile);
445                         totalFlashRows = firmware.totalFlashRows();
446                 }
447                 catch (std::exception& e)
448                 {
449                         wxLogStatus(this, "%s", e.what());
450                         std::stringstream msg;
451                         msg << "Could not open firmware file: " << e.what();
452                         wxMessageBox(
453                                 msg.str(),
454                                 "Bad file",
455                                 wxOK | wxICON_ERROR);
456                         wxRemoveFile(tmpFile);
457                         return;
458                 }
459
460                 {
461                         wxWindowPtr<wxGenericProgressDialog> progress(
462                                 new wxGenericProgressDialog(
463                                         "Loading firmware",
464                                         "Loading firmware",
465                                         totalFlashRows,
466                                         this,
467                                         wxPD_AUTO_HIDE | wxPD_REMAINING_TIME)
468                                         );
469                         TheProgressWrapper.setProgressDialog(progress, totalFlashRows);
470                 }
471
472                 wxLogStatus(this, "Upgrading firmware from file: %s", tmpFile);
473
474                 try
475                 {
476                         myBootloader->load(tmpFile, &ProgressUpdate);
477                         TheProgressWrapper.clearProgressDialog();
478
479                         wxMessageBox(
480                                 "Firmware update successful",
481                                 "Firmware OK",
482                                 wxOK);
483                         wxLogStatus(this, "Firmware update successful");
484
485
486                         myHID.reset();
487                         myBootloader.reset();
488                 }
489                 catch (std::exception& e)
490                 {
491                         TheProgressWrapper.clearProgressDialog();
492                         wxLogStatus(this, "%s", e.what());
493                         myHID.reset();
494                         myBootloader.reset();
495
496                         wxMessageBox(
497                                 "Firmware Update Failed",
498                                 e.what(),
499                                 wxOK | wxICON_ERROR);
500
501                         wxRemoveFile(tmpFile);
502                 }
503         }
504
505         void OnID_Timer(wxTimerEvent& event)
506         {
507                 // Check if we are connected to the HID device.
508                 // AND/or bootloader device.
509                 try
510                 {
511                         if (myBootloader)
512                         {
513                                 // Verify the USB HID connection is valid
514                                 if (!myBootloader->ping())
515                                 {
516                                         myBootloader.reset();
517                                 }
518                         }
519
520                         if (!myBootloader)
521                         {
522                                 myBootloader.reset(Bootloader::Open());
523
524                                 if (myBootloader)
525                                 {
526                                         wxLogStatus(this, "%s", "SCSI2SD Bootloader Ready");
527                                 }
528                         }
529
530                         int supressLog = 0;
531                         if (myHID && myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION)
532                         {
533                                 // No method to check connection is still valid.
534                                 // So assume it isn't.
535                                 myHID.reset();
536                                 supressLog = 1;
537                         }
538                         else if (myHID && !myHID->ping())
539                         {
540                                 // Verify the USB HID connection is valid
541                                 myHID.reset();
542                         }
543
544                         if (!myHID)
545                         {
546                                 myHID.reset(HID::Open());
547                                 if (myHID)
548                                 {
549                                         if (myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION)
550                                         {
551                                                 if (!supressLog)
552                                                 {
553                                                         // Oh dear, old firmware
554                                                         wxLogStatus(
555                                                                 this,
556                                                                 "Firmware update required. Version %s",
557                                                                 myHID->getFirmwareVersionStr());
558                                                 }
559                                         }
560                                         else
561                                         {
562                                                 wxLogStatus(
563                                                         this,
564                                                         "SCSI2SD Ready, firmware version %s",
565                                                         myHID->getFirmwareVersionStr());
566
567                                                 if (!myInitialConfig)
568                                                 {
569                                                         wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad);
570                                                         GetEventHandler()->AddPendingEvent(loadEvent);
571                                                 }
572                                         }
573                                 }
574                                 else
575                                 {
576                                         char ticks[] = {'/', '-', '\\', '|'};
577                                         std::stringstream ss;
578                                         ss << "Searching for SCSI2SD device " << ticks[myTickCounter % sizeof(ticks)];
579                                         myTickCounter++;
580                                         SetStatusText(ss.str());
581                                 }
582                         }
583                 }
584                 catch (std::runtime_error& e)
585                 {
586                         wxLogStatus(this, "%s", e.what());
587                 }
588
589                 evaluate();
590         }
591
592         void doLoad(wxCommandEvent& event)
593         {
594                 TimerLock lock(myTimer);
595                 if (!myHID) return;
596
597                 wxLogStatus(this, "Loading configuration");
598
599                 wxWindowPtr<wxGenericProgressDialog> progress(
600                         new wxGenericProgressDialog(
601                                 "Load config settings",
602                                 "Loading config settings",
603                                 100,
604                                 this,
605                                 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
606                                 );
607
608                 int flashRow = SCSI_CONFIG_0_ROW;
609                 int currentProgress = 0;
610                 int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS;
611                 for (size_t i = 0;
612                         i < myTargets.size();
613                         ++i, flashRow += SCSI_CONFIG_ROWS)
614                 {
615                         std::vector<uint8_t> raw(sizeof(TargetConfig));
616
617                         for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j)
618                         {
619                                 std::stringstream ss;
620                                 ss << "Reading flash array " << SCSI_CONFIG_ARRAY <<
621                                         " row " << (flashRow + j);
622                                 wxLogStatus(this, "%s", ss.str());
623                                 currentProgress += 1;
624                                 if (!progress->Update(
625                                                 (100 * currentProgress) / totalProgress,
626                                                 ss.str()
627                                                 )
628                                         )
629                                 {
630                                         goto abort;
631                                 }
632
633                                 std::vector<uint8_t> flashData;
634
635                                 try
636                                 {
637                                         myHID->readFlashRow(
638                                                 SCSI_CONFIG_ARRAY, flashRow + j, flashData);
639
640                                 }
641                                 catch (std::runtime_error& e)
642                                 {
643                                         wxLogStatus(this, "%s", e.what());
644                                         goto err;
645                                 }
646
647                                 std::copy(
648                                         flashData.begin(),
649                                         flashData.end(),
650                                         &raw[j * SCSI_CONFIG_ROW_SIZE]);
651                         }
652                         myTargets[i]->setConfig(ConfigUtil::fromBytes(&raw[0]));
653                 }
654
655                 myInitialConfig = true;
656                 wxLogStatus(this, "%s", "Load Complete");
657                 while (progress->Update(100, "Load Complete"))
658                 {
659                         // Wait for the user to click "Close"
660                         wxMilliSleep(50);
661                 }
662                 goto out;
663
664         err:
665                 wxLogStatus(this, "%s", "Load failed");
666                 while (progress->Update(100, "Load failed"))
667                 {
668                         // Wait for the user to click "Close"
669                         wxMilliSleep(50);
670                 }
671                 goto out;
672
673         abort:
674                 wxLogStatus(this, "Load Aborted");
675
676         out:
677                 return;
678         }
679
680         void doSave(wxCommandEvent& event)
681         {
682                 TimerLock lock(myTimer);
683                 if (!myHID) return;
684
685                 wxLogStatus(this, "Saving configuration");
686
687                 wxWindowPtr<wxGenericProgressDialog> progress(
688                         new wxGenericProgressDialog(
689                                 "Save config settings",
690                                 "Saving config settings",
691                                 100,
692                                 this,
693                                 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
694                                 );
695
696                 int flashRow = SCSI_CONFIG_0_ROW;
697                 int currentProgress = 0;
698                 int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS;
699                 for (size_t i = 0;
700                         i < myTargets.size();
701                         ++i, flashRow += SCSI_CONFIG_ROWS)
702                 {
703                         TargetConfig config(myTargets[i]->getConfig());
704                         std::vector<uint8_t> raw(ConfigUtil::toBytes(config));
705
706                         for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j)
707                         {
708                                 std::stringstream ss;
709                                 ss << "Programming flash array " << SCSI_CONFIG_ARRAY <<
710                                         " row " << (flashRow + j);
711                                 wxLogStatus(this, "%s", ss.str());
712                                 currentProgress += 1;
713                                 if (!progress->Update(
714                                                 (100 * currentProgress) / totalProgress,
715                                                 ss.str()
716                                                 )
717                                         )
718                                 {
719                                         goto abort;
720                                 }
721
722                                 std::vector<uint8_t> flashData(SCSI_CONFIG_ROW_SIZE, 0);
723                                 std::copy(
724                                         &raw[j * SCSI_CONFIG_ROW_SIZE],
725                                         &raw[(1+j) * SCSI_CONFIG_ROW_SIZE],
726                                         flashData.begin());
727                                 try
728                                 {
729                                         myHID->writeFlashRow(
730                                                 SCSI_CONFIG_ARRAY, flashRow + j, flashData);
731                                 }
732                                 catch (std::runtime_error& e)
733                                 {
734                                         wxLogStatus(this, "%s", e.what());
735                                         goto err;
736                                 }
737                         }
738                 }
739
740                 // Reboot so new settings take effect.
741                 myHID->enterBootloader();
742                 myHID.reset();
743
744                 wxLogStatus(this, "Save Complete");
745                 while (progress->Update(100, "Save Complete"))
746                 {
747                         // Wait for the user to click "Close"
748                         wxMilliSleep(50);
749                 }
750                 goto out;
751
752         err:
753                 wxLogStatus(this, "Save failed");
754                 while (progress->Update(100, "Save failed"))
755                 {
756                         // Wait for the user to click "Close"
757                         wxMilliSleep(50);
758                 }
759                 goto out;
760
761         abort:
762                 wxLogStatus(this, "Save Aborted");
763
764         out:
765                 (void) true; // empty statement.
766         }
767
768         void OnExit(wxCommandEvent& event)
769         {
770                 Close(true);
771         }
772         void OnAbout(wxCommandEvent& event)
773         {
774                 wxMessageBox(
775                         "SCSI2SD (scsi2sd-util)\n"
776                         "Copyright (C) 2014 Michael McMaster <michael@codesrc.com>\n"
777                         "\n"
778 "This program is free software: you can redistribute it and/or modify\n"
779 "it under the terms of the GNU General Public License as published by\n"
780 "the Free Software Foundation, either version 3 of the License, or\n"
781 "(at your option) any later version.\n"
782 "\n"
783 "This program is distributed in the hope that it will be useful,\n"
784 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
785 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
786 "GNU General Public License for more details.\n"
787 "\n"
788 "You should have received a copy of the GNU General Public License\n"
789 "along with this program.  If not, see <http://www.gnu.org/licenses/>.\n",
790
791                         "About scsi2sd-util", wxOK | wxICON_INFORMATION );
792         }
793
794         wxDECLARE_EVENT_TABLE();
795 };
796
797 wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
798         EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)
799         EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)
800         EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow)
801         EVT_MENU(wxID_EXIT, AppFrame::OnExit)
802         EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)
803
804         EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)
805
806         EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged)
807
808         EVT_BUTTON(ID_BtnSave, AppFrame::doSave)
809         EVT_BUTTON(ID_BtnLoad, AppFrame::doLoad)
810
811
812 wxEND_EVENT_TABLE()
813
814
815
816 class App : public wxApp
817 {
818 public:
819         virtual bool OnInit()
820         {
821                 AppFrame* frame = new AppFrame();
822                 frame->Show(true);
823                 SetTopWindow(frame);
824                 return true;
825         }
826 };
827 } // namespace
828
829 // Main Method
830 wxIMPLEMENT_APP(App);
831
832