26acaa79a8770c7f81143043e12e1b80fadcc540
[SCSI2SD-V6.git] / src / scsi2sd-util6 / ConfigUtil.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 #include "ConfigUtil.hh"
19
20 #include <limits>
21 #include <sstream>
22 #include <stdexcept>
23
24 #include <string.h>
25
26 #include <wx/wxprec.h>
27 #ifndef WX_PRECOMP
28 #include <wx/wx.h>
29 #endif
30 #include <wx/base64.h>
31 #include <wx/buffer.h>
32 #include <wx/xml/xml.h>
33
34
35 using namespace SCSI2SD;
36
37 namespace
38 {
39         // Endian conversion routines.
40         // The Cortex-M3 inside the Cypress PSoC 5LP is a
41         // little-endian device.
42
43         bool isHostLE()
44         {
45                 union
46                 {
47                         int i;
48                         char c[sizeof(int)];
49                 } x;
50                 x.i = 1;
51                 return (x.c[0] == 1);
52         }
53
54         uint16_t toLE16(uint16_t in)
55         {
56                 if (isHostLE())
57                 {
58                         return in;
59                 }
60                 else
61                 {
62                         return (in >> 8) | (in << 8);
63                 }
64         }
65         uint16_t fromLE16(uint16_t in)
66         {
67                 return toLE16(in);
68         }
69
70         uint32_t toLE32(uint32_t in)
71         {
72                 if (isHostLE())
73                 {
74                         return in;
75                 }
76                 else
77                 {
78                         return (in >> 24) |
79                                 ((in >> 8) & 0xff00) |
80                                 ((in << 8) & 0xff0000) |
81                                 (in << 24);
82                 }
83         }
84         uint32_t fromLE32(uint32_t in)
85         {
86                 return toLE32(in);
87         }
88
89 }
90
91 S2S_BoardCfg
92 ConfigUtil::DefaultBoardConfig()
93 {
94         S2S_BoardCfg config;
95         memset(&config, 0, sizeof(config));
96
97         memcpy(config.magic, "BCFG", 4);
98
99
100         // Default to maximum fail-safe options.
101         config.flags6 = S2S_CFG_ENABLE_TERMINATOR;
102         config.selectionDelay = 255; // auto
103
104         return config;
105 }
106
107 S2S_TargetCfg
108 ConfigUtil::Default(size_t targetIdx)
109 {
110         S2S_TargetCfg config;
111         memset(&config, 0, sizeof(config));
112
113         config.scsiId = targetIdx;
114         if (targetIdx == 0)
115         {
116                 config.scsiId = config.scsiId | S2S_CFG_TARGET_ENABLED;
117         }
118         config.deviceType = S2S_CFG_FIXED;
119
120         // Default to maximum fail-safe options.
121         config.flagsDEPRECATED = 0;
122         config.deviceTypeModifier = 0;
123         config.sdSectorStart = 0;
124
125         // Default to 2GB. Many systems have trouble with > 2GB disks, and
126         // a few start to complain at 1GB.
127         config.scsiSectors = 4194303; // 2GB - 1 sector
128         config.bytesPerSector = 512;
129         config.sectorsPerTrack = 63;
130         config.headsPerCylinder = 255;
131         memcpy(config.vendor, " codesrc", 8);
132         memcpy(config.prodId, "         SCSI2SD", 16);
133         memcpy(config.revision, " 6.0", 4);
134         memcpy(config.serial, "1234567812345678", 16);
135
136         // Reserved fields, already set to 0
137         // config.reserved
138
139         // not supported yet.
140         // config.vpd
141
142         return config;
143 }
144
145
146 S2S_TargetCfg
147 ConfigUtil::fromBytes(const uint8_t* data)
148 {
149         S2S_TargetCfg result;
150         memcpy(&result, data, sizeof(S2S_TargetCfg));
151         result.sdSectorStart = toLE32(result.sdSectorStart);
152         result.scsiSectors = toLE32(result.scsiSectors);
153         result.bytesPerSector = toLE16(result.bytesPerSector);
154         result.sectorsPerTrack = toLE16(result.sectorsPerTrack);
155         result.headsPerCylinder = toLE16(result.headsPerCylinder);
156         return result;
157 }
158
159
160 std::vector<uint8_t>
161 ConfigUtil::toBytes(const S2S_TargetCfg& _config)
162 {
163         S2S_TargetCfg config(_config);
164         config.sdSectorStart = fromLE32(config.sdSectorStart);
165         config.scsiSectors = fromLE32(config.scsiSectors);
166         config.bytesPerSector = fromLE16(config.bytesPerSector);
167         config.sectorsPerTrack = fromLE16(config.sectorsPerTrack);
168         config.headsPerCylinder = fromLE16(config.headsPerCylinder);
169
170         const uint8_t* begin = reinterpret_cast<const uint8_t*>(&config);
171
172         return std::vector<uint8_t>(begin, begin + sizeof(config));
173 }
174
175 S2S_BoardCfg
176 ConfigUtil::boardConfigFromBytes(const uint8_t* data)
177 {
178         S2S_BoardCfg result;
179         memcpy(&result, data, sizeof(S2S_BoardCfg));
180
181         if (memcmp("BCFG", result.magic, 4))
182         {
183                 return DefaultBoardConfig();
184         }
185
186         return result;
187 }
188
189
190 std::vector<uint8_t>
191 ConfigUtil::boardConfigToBytes(const S2S_BoardCfg& _config)
192 {
193         S2S_BoardCfg config(_config);
194
195         memcpy(config.magic, "BCFG", 4);
196         const uint8_t* begin = reinterpret_cast<const uint8_t*>(&config);
197         return std::vector<uint8_t>(begin, begin + sizeof(config));
198 }
199
200 std::string
201 ConfigUtil::toXML(const S2S_TargetCfg& config)
202 {
203         std::stringstream s;
204
205         s <<
206                 "<SCSITarget id=\"" <<
207                         static_cast<int>(config.scsiId & S2S_CFG_TARGET_ID_BITS) << "\">\n" <<
208
209                 "       <enabled>" <<
210                         (config.scsiId & S2S_CFG_TARGET_ENABLED ? "true" : "false") <<
211                         "</enabled>\n" <<
212
213                 "\n" <<
214                 "       <!-- ********************************************************\n" <<
215                 "       Space separated list. Available options:\n" <<
216                 "       apple\t\tReturns Apple-specific mode pages\n" <<
217                 "       omti\t\tOMTI host non-standard link control\n" <<
218                 "       xebec\t\tXEBEC ignore step options in control byte\n" <<
219                 "       ********************************************************* -->\n" <<
220                 "       <quirks>";
221         if (config.quirks == S2S_CFG_QUIRKS_APPLE)
222         {
223                 s << "apple";
224         }
225         else if (config.quirks == S2S_CFG_QUIRKS_OMTI)
226         {
227                 s << "omti";
228         }
229         else if (config.quirks == S2S_CFG_QUIRKS_XEBEC)
230         {
231                 s << "xebec";
232         }
233         else if (config.quirks == S2S_CFG_QUIRKS_VMS)
234         {
235                 s << "vms";
236         }
237
238         s <<
239                         "</quirks>\n" <<
240
241                 "\n\n" <<
242                 "       <!-- ********************************************************\n" <<
243                 "       0x0    Fixed hard drive.\n" <<
244                 "       0x1    Removable drive.\n" <<
245                 "       0x2    Optical drive  (ie. CD drive).\n" <<
246                 "       0x3    1.44MB Floppy Drive.\n" <<
247                 "       ********************************************************* -->\n" <<
248                 "       <deviceType>0x" <<
249                                 std::hex << static_cast<int>(config.deviceType) <<
250                         "</deviceType>\n" <<
251
252                 "\n\n" <<
253                 "       <!-- ********************************************************\n" <<
254                 "       Device type modifier is usually 0x00. Only change this if your\n" <<
255                 "       OS requires some special value.\n" <<
256                 "\n" <<
257                 "       0x4C    Data General Micropolis disk\n" <<
258                 "       ********************************************************* -->\n" <<
259                 "       <deviceTypeModifier>0x" <<
260                                 std::hex << static_cast<int>(config.deviceTypeModifier) <<
261                         "</deviceTypeModifier>\n" <<
262
263                 "\n\n" <<
264                 "       <!-- ********************************************************\n" <<
265                 "       SD card offset, as a sector number (always 512 bytes).\n" <<
266                 "       ********************************************************* -->\n" <<
267                 "       <sdSectorStart>" << std::dec << config.sdSectorStart << "</sdSectorStart>\n" <<
268                 "\n\n" <<
269                 "       <!-- ********************************************************\n" <<
270                 "       Drive geometry settings.\n" <<
271                 "       ********************************************************* -->\n" <<
272                 "\n"
273                 "       <scsiSectors>" << std::dec << config.scsiSectors << "</scsiSectors>\n" <<
274                 "       <bytesPerSector>" << std::dec << config.bytesPerSector << "</bytesPerSector>\n" <<
275                 "       <sectorsPerTrack>" << std::dec << config.sectorsPerTrack<< "</sectorsPerTrack>\n" <<
276                 "       <headsPerCylinder>" << std::dec << config.headsPerCylinder << "</headsPerCylinder>\n" <<
277                 "\n\n" <<
278                 "       <!-- ********************************************************\n" <<
279                 "       Drive identification information. The SCSI2SD doesn't\n" <<
280                 "       care what these are set to. Use these strings to trick a OS\n" <<
281                 "       thinking a specific hard drive model is attached.\n" <<
282                 "       ********************************************************* -->\n" <<
283                 "\n"
284                 "       <!-- 8 character vendor string -->\n" <<
285                 "       <!-- For Apple HD SC Setup/Drive Setup, use ' SEAGATE' -->\n" <<
286                 "       <vendor>" << std::string(config.vendor, 8) << "</vendor>\n" <<
287                 "\n" <<
288                 "       <!-- 16 character produce identifier -->\n" <<
289                 "       <!-- For Apple HD SC Setup/Drive Setup, use '          ST225N' -->\n" <<
290                 "       <prodId>" << std::string(config.prodId, 16) << "</prodId>\n" <<
291                 "\n" <<
292                 "       <!-- 4 character product revision number -->\n" <<
293                 "       <!-- For Apple HD SC Setup/Drive Setup, use '1.0 ' -->\n" <<
294                 "       <revision>" << std::string(config.revision, 4) << "</revision>\n" <<
295                 "\n" <<
296                 "       <!-- 16 character serial number -->\n" <<
297                 "       <serial>" << std::string(config.serial, 16) << "</serial>\n" <<
298                 "\n" <<
299                 "</SCSITarget>\n";
300
301         return s.str();
302 }
303
304 std::string
305 ConfigUtil::toXML(const S2S_BoardCfg& config)
306 {
307         std::stringstream s;
308
309         s << "<S2S_BoardCfg>\n" <<
310
311                 "       <!-- ********************************************************\n" <<
312                 "       Enable the onboard active terminator.\n"
313                 "       Both ends of the SCSI chain should be terminated. Disable\n" <<
314                 "       only if the SCSI2SD is in the middle of a chain with other\n" <<
315                 "       devices.\n" <<
316                 "       ********************************************************* -->\n" <<
317                 "       <enableTerminator>" <<
318                         (config.flags6 & S2S_CFG_ENABLE_TERMINATOR ? "true" : "false") <<
319                         "</enableTerminator>\n" <<
320
321                 "       <unitAttention>" <<
322                         (config.flags & S2S_CFG_ENABLE_UNIT_ATTENTION ? "true" : "false") <<
323                         "</unitAttention>\n" <<
324
325                 "       <parity>" <<
326                         (config.flags & S2S_CFG_ENABLE_PARITY ? "true" : "false") <<
327                         "</parity>\n" <<
328
329                 "       <!-- ********************************************************\n" <<
330                 "       Only set to true when using with a fast SCSI2 host\n " <<
331                 "       controller. This can cause problems with older/slower\n" <<
332                 "       hardware.\n" <<
333                 "       ********************************************************* -->\n" <<
334                 "       <enableScsi2>" <<
335                         (config.flags & S2S_CFG_ENABLE_SCSI2 ? "true" : "false") <<
336                         "</enableScsi2>\n" <<
337
338                 "       <!-- ********************************************************\n" <<
339                 "       Respond to very short duration selection attempts. This supports\n" <<
340                 "       non-standard hardware, but is generally safe to enable.\n" <<
341                 "       Required for Philips P2000C.\n" <<
342                 "       ********************************************************* -->\n" <<
343                 "       <selLatch>" <<
344                         (config.flags & S2S_CFG_ENABLE_SEL_LATCH? "true" : "false") <<
345                         "</selLatch>\n" <<
346
347
348                 "       <!-- ********************************************************\n" <<
349                 "       Convert luns to IDs. The unit must already be configured to respond\n" <<
350                 "       on the ID. Allows dual drives to be accessed from a \n" <<
351                 "       XEBEC S1410 SASI bridge.\n" <<
352                 "       eg. Configured for dual drives as IDs 0 and 1, but the XEBEC will\n" <<
353                 "       access the second disk as ID0, lun 1.\n" <<
354                 "       See ttp://bitsavers.trailing-edge.com/pdf/xebec/104524C_S1410Man_Aug83.pdf\n" <<
355                 "       ********************************************************* -->\n" <<
356                 "       <mapLunsToIds>" <<
357                         (config.flags & S2S_CFG_MAP_LUNS_TO_IDS ? "true" : "false") <<
358                         "</mapLunsToIds>\n" <<
359
360
361                 "       <!-- ********************************************************\n" <<
362                 "       Delay (in milliseconds) before responding to a SCSI selection.\n" <<
363                 "       255 (auto) sets it to 0 for SCSI2 hosts and 1ms otherwise.\n" <<
364                 "       Some samplers need this set to 1 manually.\n" <<
365                 "       ********************************************************* -->\n" <<
366                 "       <selectionDelay>" << static_cast<int>(config.selectionDelay) << "</selectionDelay>\n" <<
367
368                 "       <!-- ********************************************************\n" <<
369                 "       Startup delay (in seconds) before responding to the SCSI bus \n" <<
370                 "       after power on. Default = 0.\n" <<
371                 "       ********************************************************* -->\n" <<
372                 "       <startupDelay>" << static_cast<int>(config.startupDelay) << "</startupDelay>\n" <<
373
374                 "       <!-- ********************************************************\n" <<
375                 "       Speed limit the SCSI interface. This is the -max- speed the \n" <<
376                 "       device will run at. The actual spee depends on the capability\n" <<
377                 "       of the host controller.\n" <<
378                 "       0       No limit\n" <<
379                 "       1       Async 1.5MB/s\n" <<
380                 "       2       Async 3.3MB/s\n" <<
381                 "       3       Async 5MB/s\n" <<
382                 "       4       Sync 5MB/s\n" <<
383                 "       5       Sync 10MB/s\n" <<
384                 "       ********************************************************* -->\n" <<
385                 "       <scsiSpeed>" << static_cast<int>(config.scsiSpeed) << "</scsiSpeed>\n" <<
386
387                 "       <!-- ********************************************************\n" <<
388                 "       Enable SD card blind writes, which starts writing to the SD\n"
389                 "       card before all the SCSI data has been received. Can cause problems\n" <<
390                 "       with some SCSI hosts\n" <<
391                 "       ********************************************************* -->\n" <<
392                 "       <blindWrites>" <<
393                         (config.flags6 & S2S_CFG_ENABLE_BLIND_WRITES ? "true" : "false") <<
394                         "</blindWrites>\n" <<
395
396                 "</S2S_BoardCfg>\n";
397
398         return s.str();
399 }
400
401
402 static uint64_t parseInt(wxXmlNode* node, uint64_t limit)
403 {
404         std::string str(node->GetNodeContent().mb_str());
405         if (str.empty())
406         {
407                 throw std::runtime_error("Empty " + node->GetName());
408         }
409
410         std::stringstream s;
411         if (str.find("0x") == 0)
412         {
413                 s << std::hex << str.substr(2);
414         }
415         else
416         {
417                 s << str;
418         }
419
420         uint64_t result;
421         s >> result;
422         if (!s)
423         {
424                 throw std::runtime_error("Invalid value for " + node->GetName());
425         }
426
427         if (result > limit)
428         {
429                 std::stringstream msg;
430                 msg << "Invalid value for " << node->GetName() <<
431                         " (max=" << limit << ")";
432                 throw std::runtime_error(msg.str());
433         }
434         return result;
435 }
436
437 static S2S_TargetCfg
438 parseTarget(wxXmlNode* node)
439 {
440         int id;
441         {
442                 std::stringstream s;
443                 s << node->GetAttribute("id", "7");
444                 s >> id;
445                 if (!s) throw std::runtime_error("Could not parse SCSITarget id attr");
446         }
447         S2S_TargetCfg result = ConfigUtil::Default(id & 0x7);
448
449         wxXmlNode *child = node->GetChildren();
450         while (child)
451         {
452                 if (child->GetName() == "enabled")
453                 {
454                         std::string s(child->GetNodeContent().mb_str());
455                         if (s == "true")
456                         {
457                                 result.scsiId |= S2S_CFG_TARGET_ENABLED;
458                         }
459                         else
460                         {
461                                 result.scsiId = result.scsiId & ~S2S_CFG_TARGET_ENABLED;
462                         }
463                 }
464                 else if (child->GetName() == "quirks")
465                 {
466                         std::stringstream s(std::string(child->GetNodeContent().mb_str()));
467                         std::string quirk;
468                         while (s >> quirk)
469                         {
470                                 if (quirk == "apple")
471                                 {
472                                         result.quirks |= S2S_CFG_QUIRKS_APPLE;
473                                 }
474                                 else if (quirk == "omti")
475                                 {
476                                         result.quirks |= S2S_CFG_QUIRKS_OMTI;
477                                 }
478                                 else if (quirk == "xebec")
479                                 {
480                                         result.quirks |= S2S_CFG_QUIRKS_XEBEC;
481                                 }
482                                 else if (quirk == "vms")
483                                 {
484                                         result.quirks |= S2S_CFG_QUIRKS_VMS;
485                                 }
486                         }
487                 }
488                 else if (child->GetName() == "deviceType")
489                 {
490                         result.deviceType = parseInt(child, 0xFF);
491                 }
492                 else if (child->GetName() == "deviceTypeModifier")
493                 {
494                         result.deviceTypeModifier = parseInt(child, 0xFF);
495                 }
496                 else if (child->GetName() == "sdSectorStart")
497                 {
498                         result.sdSectorStart = parseInt(child, 0xFFFFFFFF);
499                 }
500                 else if (child->GetName() == "scsiSectors")
501                 {
502                         result.scsiSectors = parseInt(child, 0xFFFFFFFF);
503                 }
504                 else if (child->GetName() == "bytesPerSector")
505                 {
506                         result.bytesPerSector = parseInt(child, 8192);
507                 }
508                 else if (child->GetName() == "sectorsPerTrack")
509                 {
510                         result.sectorsPerTrack = parseInt(child, 255);
511                 }
512                 else if (child->GetName() == "headsPerCylinder")
513                 {
514                         result.headsPerCylinder = parseInt(child, 255);
515                 }
516                 else if (child->GetName() == "vendor")
517                 {
518                         std::string s(child->GetNodeContent().mb_str());
519                         s = s.substr(0, sizeof(result.vendor));
520                         memset(result.vendor, ' ', sizeof(result.vendor));
521                         memcpy(result.vendor, s.c_str(), s.size());
522                 }
523                 else if (child->GetName() == "prodId")
524                 {
525                         std::string s(child->GetNodeContent().mb_str());
526                         s = s.substr(0, sizeof(result.prodId));
527                         memset(result.prodId, ' ', sizeof(result.prodId));
528                         memcpy(result.prodId, s.c_str(), s.size());
529                 }
530                 else if (child->GetName() == "revision")
531                 {
532                         std::string s(child->GetNodeContent().mb_str());
533                         s = s.substr(0, sizeof(result.revision));
534                         memset(result.revision, ' ', sizeof(result.revision));
535                         memcpy(result.revision, s.c_str(), s.size());
536                 }
537                 else if (child->GetName() == "serial")
538                 {
539                         std::string s(child->GetNodeContent().mb_str());
540                         s = s.substr(0, sizeof(result.serial));
541                         memset(result.serial, ' ', sizeof(result.serial));
542                         memcpy(result.serial, s.c_str(), s.size());
543                 }
544
545
546                 child = child->GetNext();
547         }
548         return result;
549 }
550
551 static S2S_BoardCfg
552 parseBoardConfig(wxXmlNode* node)
553 {
554         S2S_BoardCfg result = ConfigUtil::DefaultBoardConfig();
555
556         wxXmlNode *child = node->GetChildren();
557         while (child)
558         {
559                 if (child->GetName() == "selectionDelay")
560                 {
561                         result.selectionDelay = parseInt(child, 255);
562                 }
563                 else if (child->GetName() == "startupDelay")
564                 {
565                         result.startupDelay = parseInt(child, 255);
566                 }
567                 else if (child->GetName() == "unitAttention")
568                 {
569                         std::string s(child->GetNodeContent().mb_str());
570                         if (s == "true")
571                         {
572                                 result.flags |= S2S_CFG_ENABLE_UNIT_ATTENTION;
573                         }
574                         else
575                         {
576                                 result.flags = result.flags & ~S2S_CFG_ENABLE_UNIT_ATTENTION;
577                         }
578                 }
579                 else if (child->GetName() == "parity")
580                 {
581                         std::string s(child->GetNodeContent().mb_str());
582                         if (s == "true")
583                         {
584                                 result.flags |= S2S_CFG_ENABLE_PARITY;
585                         }
586                         else
587                         {
588                                 result.flags = result.flags & ~S2S_CFG_ENABLE_PARITY;
589                         }
590                 }
591                 else if (child->GetName() == "enableScsi2")
592                 {
593                         std::string s(child->GetNodeContent().mb_str());
594                         if (s == "true")
595                         {
596                                 result.flags |= S2S_CFG_ENABLE_SCSI2;
597                         }
598                         else
599                         {
600                                 result.flags = result.flags & ~S2S_CFG_ENABLE_SCSI2;
601                         }
602                 }
603                 else if (child->GetName() == "enableTerminator")
604                 {
605                         std::string s(child->GetNodeContent().mb_str());
606                         if (s == "true")
607                         {
608                                 result.flags6 |= S2S_CFG_ENABLE_TERMINATOR;
609                         }
610                         else
611                         {
612                                 result.flags6 = result.flags & ~S2S_CFG_ENABLE_TERMINATOR;
613                         }
614                 }
615                 else if (child->GetName() == "blindWrites")
616                 {
617                         std::string s(child->GetNodeContent().mb_str());
618                         if (s == "true")
619                         {
620                                 result.flags6 |= S2S_CFG_ENABLE_BLIND_WRITES;
621                         }
622                         else
623                         {
624                                 result.flags6 = result.flags & ~S2S_CFG_ENABLE_BLIND_WRITES;
625                         }
626                 }
627                 else if (child->GetName() == "selLatch")
628                 {
629                         std::string s(child->GetNodeContent().mb_str());
630                         if (s == "true")
631                         {
632                                 result.flags |= S2S_CFG_ENABLE_SEL_LATCH;
633                         }
634                         else
635                         {
636                                 result.flags = result.flags & ~S2S_CFG_ENABLE_SEL_LATCH;
637                         }
638                 }
639                 else if (child->GetName() == "mapLunsToIds")
640                 {
641                         std::string s(child->GetNodeContent().mb_str());
642                         if (s == "true")
643                         {
644                                 result.flags |= S2S_CFG_MAP_LUNS_TO_IDS;
645                         }
646                         else
647                         {
648                                 result.flags = result.flags & ~S2S_CFG_MAP_LUNS_TO_IDS;
649                         }
650                 }
651                 else if (child->GetName() == "scsiSpeed")
652                 {
653                         result.scsiSpeed = parseInt(child, S2S_CFG_SPEED_SYNC_10);
654                 }
655                 child = child->GetNext();
656         }
657         return result;
658 }
659
660
661 std::pair<S2S_BoardCfg, std::vector<S2S_TargetCfg>>
662 ConfigUtil::fromXML(const std::string& filename)
663 {
664         wxXmlDocument doc;
665         if (!doc.Load(filename))
666         {
667                 throw std::runtime_error("Could not load XML file");
668         }
669
670         // start processing the XML file
671         if (doc.GetRoot()->GetName() != "SCSI2SD")
672         {
673                 throw std::runtime_error("Invalid root node, expected <SCSI2SD>");
674         }
675
676         S2S_BoardCfg boardConfig = DefaultBoardConfig();
677         int boardConfigFound = 0;
678
679         std::vector<S2S_TargetCfg> targets;
680         wxXmlNode *child = doc.GetRoot()->GetChildren();
681         while (child)
682         {
683                 if (child->GetName() == "SCSITarget")
684                 {
685                         targets.push_back(parseTarget(child));
686                 }
687                 else if (child->GetName() == "S2S_BoardCfg")
688                 {
689                         boardConfig = parseBoardConfig(child);
690                         boardConfigFound = 1;
691                 }
692                 child = child->GetNext();
693         }
694
695         if (!boardConfigFound && targets.size() > 0)
696         {
697                 boardConfig.flags = targets[0].flagsDEPRECATED;
698         }
699         return make_pair(boardConfig, targets);
700 }
701