1 //============================================================================
2 //
3 //   SSSS    tt          lll  lll
4 //  SS  SS   tt           ll   ll
5 //  SS     tttttt  eeee   ll   ll   aaaa
6 //   SSSS    tt   ee  ee  ll   ll      aa
7 //      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
8 //  SS  SS   tt   ee      ll   ll  aa  aa
9 //   SSSS     ttt  eeeee llll llll  aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17 
18 #include "bspf.hxx"
19 #include "Launcher.hxx"
20 #include "Bankswitch.hxx"
21 #include "BrowserDialog.hxx"
22 #include "DialogContainer.hxx"
23 #include "EditTextWidget.hxx"
24 #include "ProgressDialog.hxx"
25 #include "FSNode.hxx"
26 #include "Font.hxx"
27 #include "MessageBox.hxx"
28 #include "OSystem.hxx"
29 #include "FrameBuffer.hxx"
30 #include "MD5.hxx"
31 #include "Props.hxx"
32 #include "PropsSet.hxx"
33 #include "Settings.hxx"
34 #include "RomAuditDialog.hxx"
35 
36 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RomAuditDialog(OSystem & osystem,DialogContainer & parent,const GUI::Font & font,int max_w,int max_h)37 RomAuditDialog::RomAuditDialog(OSystem& osystem, DialogContainer& parent,
38                                const GUI::Font& font, int max_w, int max_h)
39   : Dialog(osystem, parent, font, "Audit ROMs"),
40     myMaxWidth{max_w},
41     myMaxHeight{max_h}
42 {
43   const int lineHeight   = Dialog::lineHeight(),
44             fontWidth    = Dialog::fontWidth(),
45             buttonHeight = Dialog::buttonHeight(),
46             buttonWidth  = Dialog::buttonWidth("Audit path" + ELLIPSIS),
47             VBORDER      = Dialog::vBorder(),
48             HBORDER      = Dialog::hBorder(),
49             VGAP         = Dialog::vGap();
50   const int lwidth = font.getStringWidth("ROMs without properties (skipped) ");
51   int xpos, ypos = _th + VBORDER;
52   WidgetArray wid;
53 
54   // Set real dimensions
55   _w = 64 * fontWidth + HBORDER * 2;
56   _h = _th + VBORDER * 2 + buttonHeight * 2 + lineHeight * 3 + VGAP * 10;
57 
58   // Audit path
59   ButtonWidget* romButton =
60     new ButtonWidget(this, font, HBORDER, ypos, buttonWidth, buttonHeight,
61                      "Audit path" + ELLIPSIS, kChooseAuditDirCmd);
62   wid.push_back(romButton);
63   xpos = HBORDER + buttonWidth + fontWidth;
64   myRomPath = new EditTextWidget(this, font, xpos, ypos + (buttonHeight - lineHeight) / 2 - 1,
65                                  _w - xpos - HBORDER, lineHeight);
66   wid.push_back(myRomPath);
67 
68   // Show results of ROM audit
69   ypos += buttonHeight + VGAP * 4;
70   new StaticTextWidget(this, font, HBORDER, ypos, "ROMs with properties (renamed) ");
71   myResults1 = new EditTextWidget(this, font, HBORDER + lwidth, ypos - 2,
72                                   fontWidth * 6, lineHeight);
73   myResults1->setEditable(false, true);
74   ypos += buttonHeight;
75   new StaticTextWidget(this, font, HBORDER, ypos, "ROMs without properties (skipped) ");
76   myResults2 = new EditTextWidget(this, font, HBORDER + lwidth, ypos - 2,
77                                   fontWidth * 6, lineHeight);
78   myResults2->setEditable(false, true);
79 
80   ypos += buttonHeight + VGAP * 2;
81   new StaticTextWidget(this, font, HBORDER, ypos, "(*) WARNING: Operation cannot be undone!");
82 
83   // Add OK and Cancel buttons
84   addOKCancelBGroup(wid, font, "Audit", "Close");
85   addBGroupToFocusList(wid);
86 
87   setHelpAnchor("ROMAudit");
88 }
89 
90 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
~RomAuditDialog()91 RomAuditDialog::~RomAuditDialog()
92 {
93 }
94 
95 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
loadConfig()96 void RomAuditDialog::loadConfig()
97 {
98   const string& currentdir =
99     instance().launcher().currentDir().getShortPath();
100   const string& path = currentdir == "" ?
101     instance().settings().getString("romdir") : currentdir;
102 
103   myRomPath->setText(path);
104   myResults1->setText("");
105   myResults2->setText("");
106 }
107 
108 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
auditRoms()109 void RomAuditDialog::auditRoms()
110 {
111   const string& auditPath = myRomPath->getText();
112   myResults1->setText("");
113   myResults2->setText("");
114 
115   FilesystemNode node(auditPath);
116   FSList files;
117   files.reserve(2048);
118   node.getChildren(files, FilesystemNode::ListMode::FilesOnly);
119 
120   // Create a progress dialog box to show the progress of processing
121   // the ROMs, since this is usually a time-consuming operation
122   ostringstream buf;
123   ProgressDialog progress(this, instance().frameBuffer().font());
124 
125   buf << "Auditing ROM files" << ELLIPSIS;
126   progress.setMessage(buf.str());
127   progress.setRange(0, int(files.size()) - 1, 5);
128   progress.open();
129 
130   Properties props;
131   uInt32 renamed = 0, notfound = 0;
132   for(uInt32 idx = 0; idx < files.size() && !progress.isCancelled(); ++idx)
133   {
134     string extension;
135     if(files[idx].isFile() &&
136        Bankswitch::isValidRomName(files[idx], extension))
137     {
138       bool renameSucceeded = false;
139 
140       // Calculate the MD5 so we can get the rest of the info
141       // from the PropertiesSet (stella.pro)
142       const string& md5 = MD5::hash(files[idx]);
143       if(instance().propSet().getMD5(md5, props))
144       {
145         const string& name = props.get(PropType::Cart_Name);
146 
147         // Only rename the file if we found a valid properties entry
148         if(name != "" && name != files[idx].getName())
149         {
150           string newfile = node.getPath();
151           newfile.append(name).append(".").append(extension);
152           if(files[idx].getPath() != newfile && files[idx].rename(newfile))
153             renameSucceeded = true;
154         }
155       }
156       if(renameSucceeded)
157         ++renamed;
158       else
159         ++notfound;
160     }
161 
162     // Update the progress bar, indicating one more ROM has been processed
163     progress.incProgress();
164   }
165   progress.close();
166 
167   myResults1->setText(std::to_string(renamed));
168   myResults2->setText(std::to_string(notfound));
169 }
170 
171 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleCommand(CommandSender * sender,int cmd,int data,int id)172 void RomAuditDialog::handleCommand(CommandSender* sender, int cmd,
173                                    int data, int id)
174 {
175   switch (cmd)
176   {
177     case GuiObject::kOKCmd:
178       if(!myConfirmMsg)
179       {
180         StringList msg;
181         msg.push_back("This operation cannot be undone.  Your ROMs");
182         msg.push_back("will be modified, and as such there is a chance");
183         msg.push_back("that files may be lost.  You are recommended");
184         msg.push_back("to back up your files before proceeding.");
185         msg.push_back("");
186         msg.push_back("If you're sure you want to proceed with the");
187         msg.push_back("audit, click 'OK', otherwise click 'Cancel'.");
188         myConfirmMsg = make_unique<GUI::MessageBox>
189           (this, _font, msg, myMaxWidth, myMaxHeight, kConfirmAuditCmd,
190           "OK", "Cancel", "ROM Audit", false);
191       }
192       myConfirmMsg->show();
193       break;
194 
195     case kConfirmAuditCmd:
196       auditRoms();
197       instance().launcher().reload();
198       break;
199 
200     case kChooseAuditDirCmd:
201       BrowserDialog::show(this, _font, "Select ROM Directory to Audit",
202                           myRomPath->getText(),
203                           BrowserDialog::Mode::Directories,
204                           [this](bool OK, const FilesystemNode& node) {
205                             if(OK) {
206                               myRomPath->setText(node.getShortPath());
207                               myResults1->setText("");
208                               myResults2->setText("");
209                             }
210                           });
211       break;
212 
213     default:
214       Dialog::handleCommand(sender, cmd, data, 0);
215       break;
216   }
217 }
218