mirror of
https://github.com/libretro/scummvm.git
synced 2025-03-04 09:18:38 +00:00

Now it at least shows the progress bar but the current approach is hackish: we do a forced full redraw and huge files are not updating mid-way
584 lines
18 KiB
C++
584 lines
18 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "gui/integrity-dialog.h"
|
|
|
|
#include "common/array.h"
|
|
#include "common/config-manager.h"
|
|
#include "common/debug.h"
|
|
#include "common/file.h"
|
|
#include "common/macresman.h"
|
|
#include "common/md5.h"
|
|
#include "common/tokenizer.h"
|
|
#include "common/translation.h"
|
|
|
|
#include "gui/gui-manager.h"
|
|
#include "gui/launcher.h"
|
|
#include "gui/message.h"
|
|
#include "gui/widget.h"
|
|
|
|
namespace GUI {
|
|
|
|
enum {
|
|
kResponseCmd = 'IDRC',
|
|
kCopyEmailCmd = 'IDCE',
|
|
kCleanupCmd = 'IDCl',
|
|
kDownloadProgressCmd = 'DLPg',
|
|
};
|
|
|
|
struct ResultFormat {
|
|
bool error;
|
|
Common::U32StringArray messageText;
|
|
Common::String emailLink;
|
|
Common::String errorText;
|
|
|
|
ResultFormat() {
|
|
error = 0;
|
|
messageText = Common::U32StringArray();
|
|
emailLink = "";
|
|
errorText = "";
|
|
}
|
|
|
|
} static *g_result;
|
|
|
|
struct ChecksumDialogState {
|
|
IntegrityDialog *dialog;
|
|
ProcessState state;
|
|
|
|
int totalSize;
|
|
int calculatedSize;
|
|
uint32 lastUpdate;
|
|
|
|
Common::String endpoint;
|
|
Common::Path gamePath;
|
|
Common::String gameid;
|
|
Common::String engineid;
|
|
Common::String extra;
|
|
Common::String platform;
|
|
Common::String language;
|
|
|
|
ChecksumDialogState() {
|
|
state = kChecksumStateNone;
|
|
totalSize = calculatedSize = 0;
|
|
lastUpdate = 0;
|
|
dialog = nullptr;
|
|
}
|
|
} static *g_checksum_state;
|
|
|
|
uint32 getCalculationProgress() {
|
|
if (!g_checksum_state || g_checksum_state->totalSize == 0)
|
|
return 0;
|
|
|
|
uint32 progress = (uint32)(100 * ((double)g_checksum_state->calculatedSize / (double)g_checksum_state->totalSize));
|
|
|
|
return progress;
|
|
}
|
|
|
|
IntegrityDialog::IntegrityDialog(Common::String endpoint, Common::String domain) : Dialog("GameOptions_IntegrityDialog"), CommandSender(this), _close(false) {
|
|
|
|
_backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
|
|
|
|
Common::U32String warningMessage = _(
|
|
"Verifying file integrity may take a long time to complete. Please wait...\n");
|
|
_warningText = new StaticTextWidget(this, "GameOptions_IntegrityDialog.WarningText", warningMessage);
|
|
_resultsText = new ListWidget(this, "GameOptions_IntegrityDialog.ResultsText");
|
|
_resultsText->setNumberingMode(kListNumberingOff);
|
|
_resultsText->setList({Common::U32String()});
|
|
_resultsText->setVisible(false);
|
|
|
|
_statusText = new StaticTextWidget(this, "GameOptions_IntegrityDialog.StatusText", Common::U32String::format(_("Calculating file checksums...")));
|
|
_errorText = new StaticTextWidget(this, "GameOptions_IntegrityDialog.ErrorText", Common::U32String(""));
|
|
|
|
uint32 progress = getCalculationProgress();
|
|
_progressBar = new SliderWidget(this, "GameOptions_IntegrityDialog.ProgressBar");
|
|
_progressBar->setMinValue(0);
|
|
_progressBar->setMaxValue(100);
|
|
_progressBar->setValue(progress);
|
|
_progressBar->setEnabled(false);
|
|
_percentLabel = new StaticTextWidget(this, "GameOptions_IntegrityDialog.PercentText", Common::String::format("%u %%", progress));
|
|
_calcSizeLabel = new StaticTextWidget(this, "GameOptions_IntegrityDialog.DownloadSize", Common::U32String());
|
|
_cancelButton = new ButtonWidget(this, "GameOptions_IntegrityDialog.MainButton", _("Cancel"), Common::U32String(), kCleanupCmd);
|
|
|
|
_copyEmailButton = new ButtonWidget(this, "GameOptions_IntegrityDialog.CopyButton", _("Launch Email Client"), Common::U32String(), kCopyEmailCmd);
|
|
_copyEmailButton->setEnabled(false);
|
|
|
|
if (!g_checksum_state) {
|
|
g_checksum_state = new ChecksumDialogState();
|
|
g_checksum_state->dialog = this;
|
|
|
|
setState(kChecksumStateCalculating);
|
|
refreshWidgets();
|
|
|
|
g_checksum_state->endpoint = endpoint;
|
|
g_checksum_state->gamePath = Common::Path(ConfMan.get("path", domain));
|
|
g_checksum_state->gameid = ConfMan.get("gameid", domain);
|
|
g_checksum_state->engineid = ConfMan.get("engineid", domain);
|
|
g_checksum_state->extra = ConfMan.get("extra", domain);
|
|
g_checksum_state->platform = ConfMan.get("platform", domain);
|
|
g_checksum_state->language = ConfMan.get("language", domain);
|
|
calculateTotalSize(g_checksum_state->gamePath);
|
|
} else {
|
|
g_checksum_state->dialog = this;
|
|
|
|
setState(g_checksum_state->state);
|
|
refreshWidgets();
|
|
}
|
|
}
|
|
|
|
IntegrityDialog::~IntegrityDialog() {
|
|
}
|
|
|
|
void IntegrityDialog::open() {
|
|
Dialog::open();
|
|
reflowLayout();
|
|
g_gui.scheduleTopDialogRedraw();
|
|
}
|
|
|
|
void IntegrityDialog::close() {
|
|
if (g_checksum_state)
|
|
g_checksum_state->dialog = nullptr;
|
|
|
|
Dialog::close();
|
|
}
|
|
|
|
void IntegrityDialog::setState(ProcessState state) {
|
|
g_checksum_state->state = state;
|
|
|
|
switch (state) {
|
|
case kChecksumStateNone:
|
|
case kChecksumStateCalculating:
|
|
_statusText->setLabel(Common::U32String::format(_("Calculating file checksums...")));
|
|
_cancelButton->setLabel(_("Cancel"));
|
|
_cancelButton->setCmd(kCleanupCmd);
|
|
break;
|
|
|
|
case kChecksumComplete:
|
|
_statusText->setLabel(Common::U32String::format(_("Calculation complete")));
|
|
_cancelButton->setVisible(true);
|
|
_cancelButton->setLabel(_("OK"));
|
|
_cancelButton->setCmd(kCleanupCmd);
|
|
|
|
// Hide all elements
|
|
_warningText->setVisible(false);
|
|
_statusText->setVisible(false);
|
|
_errorText->setVisible(false);
|
|
_percentLabel->setVisible(false);
|
|
_calcSizeLabel->setVisible(false);
|
|
_progressBar->setVisible(false);
|
|
break;
|
|
|
|
case kResponseReceived:
|
|
if (g_result->messageText.size() != 0) {
|
|
_resultsText->setList(g_result->messageText);
|
|
} else
|
|
_resultsText->setList(Common::U32StringArray({g_result->errorText}));
|
|
|
|
if (g_result->error != 0) {
|
|
_copyEmailButton->setEnabled(true);
|
|
_copyEmailButton->setCmd(kCopyEmailCmd);
|
|
}
|
|
|
|
_resultsText->setVisible(true);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void IntegrityDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
|
|
switch (cmd) {
|
|
case kResponseReceived:
|
|
setState(kResponseReceived);
|
|
break;
|
|
case kCleanupCmd: {
|
|
delete g_checksum_state;
|
|
g_checksum_state = nullptr;
|
|
|
|
delete g_result;
|
|
g_result = nullptr;
|
|
|
|
close();
|
|
break;
|
|
}
|
|
case kDownloadProgressCmd:
|
|
if (!_close) {
|
|
refreshWidgets();
|
|
g_gui.redrawFull();
|
|
}
|
|
break;
|
|
case kCopyEmailCmd: {
|
|
g_system->openUrl(g_result->emailLink);
|
|
break;
|
|
}
|
|
default:
|
|
Dialog::handleCommand(sender, cmd, data);
|
|
}
|
|
}
|
|
|
|
void IntegrityDialog::handleTickle() {
|
|
if (_close) {
|
|
close();
|
|
_close = false;
|
|
return;
|
|
}
|
|
|
|
if (g_checksum_state->state == kChecksumStateCalculating)
|
|
sendJSON();
|
|
|
|
int32 progress = getCalculationProgress();
|
|
if (_progressBar->getValue() != progress) {
|
|
refreshWidgets();
|
|
g_gui.scheduleTopDialogRedraw();
|
|
}
|
|
|
|
Dialog::handleTickle();
|
|
}
|
|
|
|
void IntegrityDialog::reflowLayout() {
|
|
Dialog::reflowLayout();
|
|
refreshWidgets();
|
|
}
|
|
|
|
Common::U32String IntegrityDialog::getSizeLabelText() {
|
|
const char *calculatedUnits, *totalUnits;
|
|
Common::String calculated = Common::getHumanReadableBytes(g_checksum_state->calculatedSize, calculatedUnits);
|
|
Common::String total = Common::getHumanReadableBytes(g_checksum_state->totalSize, totalUnits);
|
|
return Common::U32String::format(_("Calculated %s %S / %s %S"), calculated.c_str(), _(calculatedUnits).c_str(), total.c_str(), _(totalUnits).c_str());
|
|
}
|
|
|
|
void IntegrityDialog::refreshWidgets() {
|
|
uint32 progress = getCalculationProgress();
|
|
_percentLabel->setLabel(Common::String::format("%u %%", progress));
|
|
_calcSizeLabel->setLabel(getSizeLabelText());
|
|
_progressBar->setValue(progress);
|
|
}
|
|
|
|
void IntegrityDialog::setError(Common::U32String &msg) {
|
|
_errorText->setLabel(msg);
|
|
|
|
_cancelButton->setLabel(_("Close"));
|
|
_cancelButton->setCmd(kCleanupCmd);
|
|
}
|
|
|
|
void IntegrityDialog::calculateTotalSize(Common::Path gamePath) {
|
|
const Common::FSNode dir(gamePath);
|
|
|
|
if (!dir.exists() || !dir.isDirectory())
|
|
return;
|
|
|
|
Common::FSList fileList;
|
|
if (!dir.getChildren(fileList, Common::FSNode::kListAll))
|
|
return;
|
|
|
|
if (fileList.empty())
|
|
return;
|
|
|
|
// Process the files and subdirectories in the current directory recursively
|
|
for (Common::FSList::const_iterator it = fileList.begin(); it != fileList.end(); it++) {
|
|
const Common::FSNode &entry = *it;
|
|
|
|
if (entry.isDirectory())
|
|
calculateTotalSize(entry.getPath());
|
|
else {
|
|
Common::File file;
|
|
if (!file.open(entry))
|
|
continue;
|
|
|
|
g_checksum_state->totalSize += file.size();
|
|
}
|
|
}
|
|
}
|
|
|
|
Common::Array<Common::StringArray> IntegrityDialog::generateChecksums(Common::Path gamePath, Common::Array<Common::StringArray> &fileChecksums) {
|
|
const Common::FSNode dir(gamePath);
|
|
|
|
if (!dir.exists() || !dir.isDirectory())
|
|
return {};
|
|
|
|
Common::FSList fileList;
|
|
if (!dir.getChildren(fileList, Common::FSNode::kListAll))
|
|
return {};
|
|
|
|
if (fileList.empty())
|
|
return {};
|
|
|
|
// Process the files and subdirectories in the current directory recursively
|
|
for (Common::FSList::const_iterator it = fileList.begin(); it != fileList.end(); it++) {
|
|
const Common::FSNode &entry = *it;
|
|
|
|
if (entry.isDirectory())
|
|
generateChecksums(entry.getPath(), fileChecksums);
|
|
else {
|
|
const Common::Path filename(entry.getPath());
|
|
auto macFile = Common::MacResManager();
|
|
if (macFile.open(filename)) {
|
|
auto fileStream = macFile.openFileOrDataFork(filename);
|
|
Common::SeekableReadStream *dataForkStream;
|
|
if (macFile.isMacBinary(*fileStream))
|
|
dataForkStream = macFile.openDataForkFromMacBinary(fileStream);
|
|
else
|
|
dataForkStream = fileStream;
|
|
|
|
Common::Array<Common::String> fileChecksum = {filename.toString()};
|
|
|
|
// Data fork
|
|
// Various checksizes
|
|
for (auto size : {0, 5000, 1024 * 1024}) {
|
|
fileChecksum.push_back(Common::computeStreamMD5AsString(*dataForkStream, size).c_str());
|
|
dataForkStream->seek(0);
|
|
}
|
|
// Tail checksums with checksize 5000
|
|
dataForkStream->seek(-5000, SEEK_END);
|
|
fileChecksum.push_back(Common::computeStreamMD5AsString(*dataForkStream).c_str());
|
|
|
|
// Resource fork
|
|
if (macFile.hasResFork()) {
|
|
// Various checksizes
|
|
for (auto size : {0, 5000, 1024 * 1024}) {
|
|
fileChecksum.push_back(macFile.computeResForkMD5AsString(size).c_str());
|
|
}
|
|
// Tail checksums with checksize 5000
|
|
fileChecksum.push_back(macFile.computeResForkMD5AsString(5000, true).c_str());
|
|
fileChecksums.push_back(fileChecksum);
|
|
}
|
|
|
|
g_checksum_state->calculatedSize += fileStream->size();
|
|
}
|
|
|
|
Common::File file;
|
|
if (!file.open(entry))
|
|
continue;
|
|
|
|
Common::Array<Common::String> fileChecksum = {filename.toString()};
|
|
// Various checksizes
|
|
for (auto size : {0, 5000, 1024 * 1024}) {
|
|
fileChecksum.push_back(Common::computeStreamMD5AsString(file, size).c_str());
|
|
file.seek(0);
|
|
}
|
|
// Tail checksums with checksize 5000
|
|
file.seek(-5000, SEEK_END);
|
|
fileChecksum.push_back(Common::computeStreamMD5AsString(file).c_str());
|
|
|
|
g_checksum_state->calculatedSize += file.size();
|
|
|
|
if (g_system->getMillis() > g_checksum_state->lastUpdate + 500) {
|
|
g_checksum_state->lastUpdate = g_system->getMillis();
|
|
sendCommand(kDownloadProgressCmd, 0);
|
|
}
|
|
|
|
file.close();
|
|
fileChecksums.push_back(fileChecksum);
|
|
}
|
|
}
|
|
|
|
setState(kChecksumComplete);
|
|
return fileChecksums;
|
|
}
|
|
|
|
Common::JSONValue *IntegrityDialog::generateJSONRequest(Common::Path gamePath, Common::String gameid, Common::String engineid, Common::String extra, Common::String platform, Common::String language) {
|
|
Common::Array<Common::StringArray> fileChecksums = {};
|
|
fileChecksums = generateChecksums(gamePath, fileChecksums);
|
|
Common::JSONObject requestObject;
|
|
|
|
requestObject.setVal("gameid", new Common::JSONValue(gameid));
|
|
requestObject.setVal("engineid", new Common::JSONValue(engineid));
|
|
requestObject.setVal("extra", new Common::JSONValue(extra));
|
|
requestObject.setVal("platform", new Common::JSONValue(platform));
|
|
requestObject.setVal("language", new Common::JSONValue(language));
|
|
|
|
Common::JSONArray filesObject;
|
|
|
|
for (Common::StringArray fileChecksum : fileChecksums) {
|
|
Common::JSONObject file;
|
|
Common::Path relativePath = Common::Path(fileChecksum[0]).relativeTo(gamePath);
|
|
file.setVal("name", new Common::JSONValue(relativePath.toConfig()));
|
|
|
|
auto tempNode = Common::FSNode(Common::Path(fileChecksum[0]));
|
|
Common::File tempFile;
|
|
if (!tempFile.open(tempNode))
|
|
continue;
|
|
uint64 fileSize = tempFile.size();
|
|
tempFile.close();
|
|
|
|
file.setVal("size", new Common::JSONValue((long long)fileSize));
|
|
|
|
Common::JSONArray checksums;
|
|
Common::StringArray checkcodes;
|
|
if (fileChecksum.size() == 8)
|
|
checkcodes = {"md5-d", "md5-d-5000", "md5-d-1M", "md5-dt-5000", "md5-r", "md5-r-5000", "md5-r-1M", "md5-rt-5000"};
|
|
else
|
|
checkcodes = {"md5", "md5-5000", "md5-1M", "md5-t-5000"};
|
|
|
|
int index = -1;
|
|
for (Common::String val : fileChecksum) {
|
|
index++;
|
|
|
|
Common::JSONObject checksum;
|
|
if (index < 1) {
|
|
continue;
|
|
}
|
|
|
|
checksum.setVal("type", new Common::JSONValue(checkcodes[index - 1]));
|
|
checksum.setVal("checksum", new Common::JSONValue(val));
|
|
|
|
checksums.push_back(new Common::JSONValue(checksum));
|
|
}
|
|
file.setVal("checksums", new Common::JSONValue(checksums));
|
|
|
|
filesObject.push_back(new Common::JSONValue(file));
|
|
}
|
|
|
|
requestObject.setVal("files", new Common::JSONValue(filesObject));
|
|
|
|
Common::JSONValue *request = new Common::JSONValue(requestObject);
|
|
return request;
|
|
}
|
|
|
|
void IntegrityDialog::checksumResponseCallback(const Common::JSONValue *r) {
|
|
debug(3, "JSON Response: %s", r->stringify().c_str());
|
|
IntegrityDialog::parseJSON(r);
|
|
|
|
if (g_checksum_state->dialog)
|
|
g_checksum_state->dialog->sendCommand(kResponseReceived, 0);
|
|
}
|
|
|
|
void IntegrityDialog::errorCallback(const Networking::ErrorResponse &error) {
|
|
warning("ERROR %ld: %s", error.httpResponseCode, error.response.c_str());
|
|
g_result->errorText = Common::String::format("ERROR %ld: %s", error.httpResponseCode, error.response.c_str());
|
|
|
|
if (g_checksum_state->dialog)
|
|
g_checksum_state->dialog->sendCommand(kResponseCmd, 0);
|
|
}
|
|
|
|
void IntegrityDialog::sendJSON() {
|
|
g_result = new ResultFormat();
|
|
|
|
auto conn = new Networking::PostRequest(g_checksum_state->endpoint,
|
|
new Common::Callback<IntegrityDialog, const Common::JSONValue *>(this, &IntegrityDialog::checksumResponseCallback),
|
|
new Common::Callback<IntegrityDialog, const Networking::ErrorResponse &>(this, &IntegrityDialog::errorCallback));
|
|
|
|
Common::JSONValue *json = generateJSONRequest(
|
|
g_checksum_state->gamePath, g_checksum_state->gameid, g_checksum_state->engineid, g_checksum_state->extra, g_checksum_state->platform, g_checksum_state->language);
|
|
conn->setJSONData(json);
|
|
conn->setContentType("application/json");
|
|
conn->start();
|
|
delete json;
|
|
}
|
|
|
|
void IntegrityDialog::parseJSON(const Common::JSONValue *response) {
|
|
Common::JSONObject responseObject = response->asObject();
|
|
int responseError = responseObject.getVal("error")->asIntegerNumber();
|
|
|
|
Common::U32StringArray messageText;
|
|
if (responseError == -1) { // Unknown variant
|
|
g_result->error = true;
|
|
|
|
Common::String fileset = responseObject.getVal("fileset")->asString();
|
|
|
|
Common::String emailSubj = "Unknown game variant fileset ";
|
|
Common::String emailBody = "Fileset %s is a new or unknown fileset, the game details are:\n"
|
|
" gameid: %s\n"
|
|
" platform: %s\n"
|
|
" language: %s\n"
|
|
" \n"
|
|
"Below please describe the details of your release:\n";
|
|
|
|
Common::String emailText =
|
|
Common::String::format(emailBody.c_str(),
|
|
fileset.c_str(), g_checksum_state->gameid.c_str(), g_checksum_state->platform.c_str(),
|
|
g_checksum_state->language.c_str());
|
|
|
|
Common::String emailLink = Common::String::format("mailto:integrity@scummvm.org?subject=%s&body=%s",
|
|
Common::percentEncodeString(Common::String("Subject: ") + emailSubj + fileset).c_str(),
|
|
Common::percentEncodeString(emailText).c_str());
|
|
|
|
Common::U32String message = _(
|
|
"### Results\n"
|
|
"Your set of game files seems to be unknown to us.\n"
|
|
"\n"
|
|
"If you are sure that this is a valid unknown variant, "
|
|
"please send the following e-mail to integrity@scummvm.org");
|
|
|
|
Common::U32StringTokenizer mtok(message, "\n");
|
|
for (auto &line : mtok.split())
|
|
messageText.push_back(line);
|
|
|
|
messageText.push_back(Common::U32String(""));
|
|
messageText.push_back(Common::U32String("To: integrity@scummvm.org"));
|
|
messageText.push_back(Common::U32String::format("Subject: %s%s", emailSubj.c_str(), fileset.c_str()));
|
|
messageText.push_back(Common::U32String(""));
|
|
|
|
Common::StringTokenizer tok(emailText, "\n");
|
|
for (auto &line : tok.split())
|
|
messageText.push_back(line);
|
|
|
|
g_result->messageText = messageText;
|
|
g_result->emailLink = emailLink;
|
|
return;
|
|
|
|
} else if (responseError == 2) { // Fileset is empty
|
|
messageText.push_back(_("The game doesn't seem to have any files. Are you sure the path is correct?"));
|
|
|
|
g_result->messageText = messageText;
|
|
return;
|
|
} else if (responseError == 3) { // Game does not have any metadata
|
|
messageText.push_back(_("The game doesn't seem to have any metadata associated with it, so it is unable to be matched. Please fill in the correct metadata for the game."));
|
|
|
|
g_result->messageText = messageText;
|
|
return;
|
|
}
|
|
|
|
Common::Array<int> results = Common::Array<int>(5, 0);
|
|
for (Common::JSONValue *fileJSON : responseObject.getVal("files")->asArray()) {
|
|
Common::String name = fileJSON->asObject().getVal("name")->asString();
|
|
Common::String status = fileJSON->asObject().getVal("status")->asString();
|
|
|
|
if (status == "ok") {
|
|
results[OK]++;
|
|
continue;
|
|
} else if (status == "missing")
|
|
results[MISSING]++;
|
|
else if (status == "checksum_mismatch")
|
|
results[CHECKSUM_MISMATCH]++;
|
|
else if (status == "size_mismatch")
|
|
results[SIZE_MISMATCH]++;
|
|
else if (status == "unknown_file")
|
|
results[UNKNOWN]++;
|
|
|
|
messageText.push_back(Common::String::format("%s %s\n", name.c_str(), status.c_str()));
|
|
}
|
|
|
|
if (messageText.size() == 0)
|
|
messageText.push_back(_("Files all OK"));
|
|
else {
|
|
g_result->error = true;
|
|
|
|
Common::String resultSummary = "\n\nTotal: ";
|
|
resultSummary += Common::U32String::format("%d OK, %d missing, %d mismatch, %d unknown files",
|
|
results[OK], results[MISSING], results[SIZE_MISMATCH] + results[CHECKSUM_MISMATCH], results[UNKNOWN]);
|
|
|
|
messageText.push_back(resultSummary);
|
|
}
|
|
|
|
g_result->messageText = messageText;
|
|
}
|
|
|
|
} // End of namespace GUI
|