modreader.cpp
1 //------------------------------------------------------------------------------
2 // modreader.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library 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 GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2019 Pol Marcet Sardà <polmarcetsarda@gmail.com>
22 //------------------------------------------------------------------------------
23 
24 #include "modreader.h"
25 
26 #include "datastreamoperatorwrapper.h"
27 #include "wadseeker/zip/unarchive.h"
28 
29 #include <QBuffer>
30 #include <QDataStream>
31 #include <QFile>
32 #include <QFileInfo>
33 #include <QTemporaryDir>
34 
35 QSharedPointer<ModReader> ModReader::create(const QString &path)
36 {
37  QFileInfo f(path);
38  if (f.suffix().compare("wad", Qt::CaseInsensitive) == 0)
39  {
40  return QSharedPointer<ModReader>(new WadReader(path));
41  }
42  else if (f.suffix().compare("zip", Qt::CaseInsensitive) == 0 ||
43  f.suffix().compare("7z", Qt::CaseInsensitive) == 0)
44  {
45  return QSharedPointer<ModReader>(new CompressedReader(path));
46  }
47  else if (f.suffix().compare("pk3", Qt::CaseInsensitive) == 0 ||
48  f.suffix().compare("pk7", Qt::CaseInsensitive) == 0)
49  {
50  return QSharedPointer<ModReader>(new PkReader(path));
51  }
52  return QSharedPointer<ModReader> ();
53 }
54 
55 DClass<WadReader>
56 {
57 public:
58  QString filepath;
59  bool isIwad;
60  QList<DirectoryEntry> directory;
61 };
62 
63 DPointered(WadReader)
64 
65 WadReader::WadReader(const QString &path)
66 {
67  d->filepath = path;
68 }
69 
70 WadReader::~WadReader()
71 {
72 }
73 
75 {
76  QFile f(d->filepath);
77  if (f.open(QIODevice::ReadOnly))
78  {
79  QDataStream streamUnwrapped(&f);
80  streamUnwrapped.setByteOrder(QDataStream::LittleEndian);
81  DataStreamOperatorWrapper stream(&streamUnwrapped);
82 
83  QString isIwadText = stream.readRaw(4);
84  d->isIwad = (isIwadText == "IWAD");
85  stream.skipRawData(4);
86 
87  int directoryPosition = stream.readQInt32();
88  stream.skipRawData(directoryPosition - 12);
89  while (stream.hasRemaining())
90  {
91  DirectoryEntry dirEntry;
92  dirEntry.position = stream.readQInt32();
93  dirEntry.size = stream.readQInt32();
94  dirEntry.name = stream.readRaw(8);
95  d->directory << dirEntry;
96  }
97  }
98  else
99  return false;
100  return true;
101 }
102 
104 {
105  return d->isIwad;
106 }
107 
108 QList<DirectoryEntry> WadReader::getDirectory()
109 {
110  return d->directory;
111 }
112 
114 {
115  QStringList names;
116  for (DirectoryEntry dirEntry : getDirectory())
117  names << dirEntry.name;
118 
119  QStringList maps = getClassicMaps(names);
120  maps << getUdmfMaps(names);
121  return maps;
122 }
123 
124 QStringList WadReader::getClassicMaps(const QStringList &names)
125 {
126  QStringList maps;
127  QStringList initialMandatoryLumps = {"THINGS", "LINEDEFS", "SIDEDEFS", "VERTEXES"};
128  QStringList extraMandatoryLumps = {"SECTORS", "REJECT"};
129 
130  for (int mainIter = 0; mainIter < names.size() - (initialMandatoryLumps.size() + extraMandatoryLumps.size()); ++mainIter)
131  {
132  bool allInitsFound = true;
133  for (int subInitIter = 0; subInitIter < initialMandatoryLumps.size(); ++subInitIter)
134  {
135  allInitsFound &= (names[mainIter + subInitIter + 1] == initialMandatoryLumps[subInitIter]);
136  if (!allInitsFound)
137  break;
138  }
139  if (allInitsFound)
140  {
141  int extrasChecked = 0;
142 
143  for (int subExtraIter = 0; mainIter + subExtraIter + 1 < names.size(); ++subExtraIter)
144  {
145  if (extrasChecked == extraMandatoryLumps.size())
146  {
147  maps << names[mainIter];
148  break;
149  }
150  if (names[mainIter + subExtraIter + 1] == extraMandatoryLumps[extrasChecked])
151  ++extrasChecked;
152  }
153  }
154  }
155  return maps;
156 }
157 
158 QStringList WadReader::getUdmfMaps(const QStringList &names)
159 {
160  QStringList maps;
161  QString firstLump = "TEXTMAP";
162  QString lastLump = "ENDMAP";
163  unsigned char lumpAmount = 2;
164 
165  for (int mainIter = 0; mainIter < names.size() - lumpAmount; ++mainIter)
166  {
167  if (names[mainIter + 1] == firstLump)
168  {
169  for (int checkIter = lumpAmount; mainIter + checkIter < names.size(); ++checkIter)
170  {
171  if (names[mainIter + checkIter] == lastLump)
172  {
173  maps << names[mainIter];
174  mainIter += checkIter;
175  break;
176  }
177  }
178  }
179  }
180  return maps;
181 }
182 
183 
184 DClass<CompressedReader>
185 {
186 public:
187  QScopedPointer<UnArchive> archive;
188  QString filepath;
189  QStringList directory;
190 };
191 
192 DPointeredNoCopy(CompressedReader)
193 
194 CompressedReader::CompressedReader(const QString &path)
195 {
196  d->filepath = path;
197  d->archive.reset(nullptr);
198 }
199 
200 CompressedReader::~CompressedReader()
201 {
202 }
203 
205 {
206  d->archive.reset(UnArchive::openArchive(d->filepath));
207  if (!d->archive.isNull())
208  {
209  d->directory = d->archive->files();
210  }
211  else
212  return false;
213  return true;
214 }
215 
217 {
218  return getAllMapsRootDir();
219 }
220 
221 QStringList CompressedReader::getAllMapsRootDir()
222 {
223  QStringList rootPaths;
224  QStringList mapList;
225  QTemporaryDir tempDir;
226 
227  if (tempDir.isValid())
228  {
229  for (QString dirEntry : d->directory)
230  {
231  if (!dirEntry.contains("/"))
232  {
233  QString extractedFilePath = tempDir.path() + QDir::separator() + dirEntry;
234  QSharedPointer<ModReader> modReader = ModReader::create(extractedFilePath);
235  if (!modReader.isNull() && !d->archive.isNull() &&
236  d->archive->extract(d->archive->findFileEntry(dirEntry), extractedFilePath))
237  {
238  modReader->load();
239  mapList << modReader->getAllMaps();
240  }
241  }
242  }
243  }
244  return mapList;
245 }
246 
247 PkReader::PkReader(const QString &path) : CompressedReader(path)
248 {
249 }
250 
251 QStringList PkReader::getAllMaps()
252 {
253  QStringList mapList;
254  QStringList rootPaths;
255  for (const QString &dirEntry : d->directory)
256  {
257  QFileInfo fileInfo(dirEntry);
258  if (dirEntry.startsWith("maps/", Qt::CaseInsensitive) &&
259  fileInfo.suffix().compare("wad", Qt::CaseInsensitive) == 0)
260  {
261  mapList << fileInfo.baseName();
262  }
263  }
264  mapList << getAllMapsRootDir();
265  return mapList;
266 }