Sun, 28 Nov 2021 23:53:23 +0200
Simplify ZandronumVersion structure
/* * ZCinema: Zandronum demo launcher * Copyright (C) 2013-2015 Teemu Piippo * * 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 <QFile> #include <QDataStream> #include <QMessageBox> #include <QProcess> #include <errno.h> #include <assert.h> #include <commonlib/misc.h> #include <commonlib/config.h> #include <commonlib/version.h> #include "demo.h" #include "prompts.h" #include "ui_demoprompt.h" // // ------------------------------------------------------------------------------------------------- // QString uncolorize (const QString& in) { // TODO: Handle long-form colors like \c[Red] QString out; int skip = 0; for (int i = 0; i < in.length(); ++i) { if (skip-- > 0) continue; QChar ch = in[i]; if (ch == QChar ('\034')) { skip = 1; continue; } out += ch; } return out; } // // ------------------------------------------------------------------------------------------------- // static QString tr (const char* msg) { return QObject::tr (msg); } // // ------------------------------------------------------------------------------------------------- // static void error (QString msg) { QMessageBox::critical (NULL, "Error", msg); } // // ------------------------------------------------------------------------------------------------- // static ZandronumVersion findVersion (QString versionName) { QList<QVariant> versions = Config::get ("versions").toList(); for (int i = 0; i < versions.size(); ++i) { if (not versions[i].canConvert<ZandronumVersion>()) continue; ZandronumVersion version = versions[i].value<ZandronumVersion>(); if (version.name == versionName or version.name + "M" == versionName or version.name == versionName + "M") { return version; } } return ZandronumVersion(); } // // ------------------------------------------------------------------------------------------------- // static QString findWad (QString name) { QStringList wadpaths = Config::get ("wadpaths").toStringList(); for (int i = 0; i < wadpaths.size(); ++i) { QString fullpath = QString ("%1/%2").arg (wadpaths[i]).arg (name); QFile f (fullpath); if (f.exists()) return fullpath; } // WAD names are case-sensitive under non-Windows and they can appear in uppercase // so we need to test that too. if (name != name.toUpper()) return findWad (name.toUpper()); return ""; } // // ------------------------------------------------------------------------------------------------- // QString readString (QDataStream& stream) { QString out; quint8 ch; for (stream >> ch; ch != 0; stream >> ch) out += QChar (ch); return out; } // // ------------------------------------------------------------------------------------------------- // struct UserInfo { QString netname; QString skin; QString className; quint32 color; quint32 aimdist; quint32 railcolor; quint8 gender; quint8 handicap; quint8 unlagged; quint8 respawnOnFire; quint8 ticsPerUpdate; quint8 connectionType; quint8 colorset; }; // // ------------------------------------------------------------------------------------------------- // struct DemoHeaders { quint8 length; quint8 version; quint8 userInfo; quint8 bodyStart; quint8 wads; }; // // ------------------------------------------------------------------------------------------------- // int launchDemo (QString path) { QFile f (path); if (not f.open (QIODevice::ReadOnly)) { error (tr ("Couldn't open '%1' for reading: %2").arg (path).arg (strerror (errno))); return 1; } QDataStream stream (&f); stream.setByteOrder (QDataStream::LittleEndian); DemoHeaders headers; quint32 length; quint16 zanversionID, numWads; quint32 longSink; QString zanversion; QString dateversion = "00000000"; QStringList wads; UserInfo userinfo; // Assume a release build if the build ID not supplied. The demo only got the "ZCLD" signature // in the 1.1 release build, 1.1.1 had no testing binaries and the build ID is included in 1.2 // onward. BuildType buildID = ReleaseBuild; bool ready = false; // Check signature { quint32 demosignature; stream >> demosignature; if (demosignature != makeByteID ('Z', 'C', 'L', 'D')) { error (tr ("'%1' is not a valid Zandronum demo file!").arg (path)); return 1; } } stream >> headers.length; stream >> length; // The remaining headers are variable and relative to the length header. headers.version = headers.length + 1; headers.userInfo = headers.length + 3, headers.bodyStart = headers.length + 4; headers.wads = headers.length + 10; // Read the demo header and get data for (;;) { quint8 header; stream >> header; if (header == headers.bodyStart) { ready = true; break; } else if (header == headers.version) { stream >> zanversionID; zanversion = readString (stream); if (not zanversion.startsWith ("1.1-") and not zanversion.startsWith ("1.1.1-")) { quint8 a; stream >> a; buildID = (BuildType) a; } if (zanversion.contains("-r")) { dateversion = zanversion.split("-r").last(); } // The demo wads header accidentally changed in 1.3. :( if (zanversion.left(1).toInt() >= 2 or zanversion.startsWith ("1.3")) headers.wads = headers.length + 8; stream >> longSink; // rng seed - we don't need it } else if (header == headers.userInfo) { userinfo.netname = readString (stream); stream >> userinfo.gender; // Zandronum commit 507a744e69c4 added support for colorsets and versions since that // write a byte for the colorset in the userinfo. if (dateversion >= "210307-0319") { stream >> userinfo.colorset; } stream >> userinfo.color; stream >> userinfo.aimdist; userinfo.skin = readString (stream); stream >> userinfo.railcolor; stream >> userinfo.handicap; stream >> userinfo.unlagged; stream >> userinfo.respawnOnFire; stream >> userinfo.ticsPerUpdate; stream >> userinfo.connectionType; userinfo.className = readString (stream); } else if (header == headers.wads) { QString sink; stream >> numWads; for (quint8 i = 0; i < numWads; ++i) { QString wad = readString (stream); wads << wad; } // The demo has two checksum strings. We're not interested in them, though. (sink = readString (stream)) = readString (stream); } else { error (tr ("Unknown header %1!\n").arg (int (header))); return 1; } } if (not ready) { error (tr ("Incomplete demo header in '%s'!").arg (path)); return 1; } ZandronumVersion version = findVersion (zanversion); if (version.name.isNull()) { QDialog* prompt = new UnknownVersionPrompt (path, zanversion, (buildID == ReleaseBuild)); if (not prompt->exec()) return 1; version = findVersion (zanversion); assert (not version.name.isNull()); } QString iwadpath; QStringList pwadpaths; bool doneAssimilation = false; // Find the WADs for (int i = 0; i < wads.size(); ++i) { const QString& wad = wads[i]; QString path = findWad (wad); if (path.isEmpty() and not doneAssimilation and Config::get ("autoassimilate").toBool()) { QStringList wadpaths = Config::get ("wadpaths").toStringList(); // If there are no wad paths, try assimilate from other sources. assimilateWadPaths (wadpaths); Config::set ("wadpaths", wadpaths); // Try find the wad again path = findWad (wad); } if (path.isEmpty()) { error (tr ("Couldn't find %1!").arg (wad)); return 1; } if (&wad == &wads.first()) iwadpath = path; else pwadpaths << path; } if (not Config::get ("noprompt").toBool()) { QString pwadtext; for (int i = 0; i < wads.size(); ++i) { if (i == 0) continue; // skip the IWAD if (not pwadtext.isEmpty()) pwadtext += "<br />"; pwadtext += wads[i]; } QDialog* dlg = new QDialog; Ui_DemoPrompt ui; ui.setupUi (dlg); ui.demoNameLabel->setText (basename (path)); ui.demoRecorder->setText (uncolorize (userinfo.netname)); ui.versionLabel->setText (zanversion); ui.iwadLabel->setText (wads[0]); ui.pwadsLabel->setText (pwadtext); dlg->setWindowTitle (versionSignature()); if (not dlg->exec()) return 0; } QStringList cmdlineList; cmdlineList << "-playdemo" << path << "-iwad" << iwadpath; if (pwadpaths.size() > 0) cmdlineList << "-file" << pwadpaths; // print ("Executing: %1 %2\n", binarypath, cmdlineList.join (" ")); QProcess* proc = new QProcess; proc->start (version.binaryPath, cmdlineList); proc->waitForFinished (-1); return 0; }