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