1 /*
2  * RpMap Tiled Plugin
3  * Copyright 2020, Christof Petig <christof.petig@arcor.de>
4  *
5  * This file is part of Tiled.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "rpmapplugin.h"
22 
23 #include "gidmapper.h"
24 #include "map.h"
25 #include "mapobject.h"
26 #include "savefile.h"
27 #include "tile.h"
28 #include "tiled.h"
29 #include "tilelayer.h"
30 #include "tileset.h"
31 #include "objectgroup.h"
32 
33 #include <QCoreApplication>
34 #include <QDir>
35 #include <QFileInfo>
36 #include <QStringList>
37 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
38 #include <QStringView>
39 #endif
40 #include <QTextStream>
41 #include <QXmlStreamWriter>
42 #include <QUuid>
43 #include <QCryptographicHash>
44 #include <kzip.h>
45 
46 #include <memory>
47 
48 using namespace Tiled;
49 
50 namespace RpMap {
51 
RpMapPlugin()52 RpMapPlugin::RpMapPlugin()
53 {
54 }
55 
56 #if 0 // not implemented for now
57 std::unique_ptr<Tiled::Map> RpMapPlugin::read(const QString &fileName)
58 {
59     // the problem is to either reference an already loaded tileset (how) or to import the included image resources
60     KZip archive(fileName);
61     if (archive.open(QIODevice::ReadOnly)) {
62         const KArchiveDirectory *dir = archive.directory();
63 
64         const KArchiveEntry *e = dir->entry("content.xml");
65         if (!e) {
66             //qDebug() << "File not found!";
67             return nullptr;
68         }
69         const KArchiveFile *f = static_cast<const KArchiveFile *>(e);
70         QByteArray arr(f->data());
71         //qDebug() << arr; // the file contents
72 
73         // To avoid reading everything into memory in one go, we can use createDevice() instead
74 #if 0
75         QIODevice *dev = f->createDevice();
76         while (!dev->atEnd()) {
77             qDebug() << dev->readLine();
78         }
79         delete dev;
80 #endif
81     }
82     return nullptr;
83 }
84 
85 bool RpMapPlugin::supportsFile(const QString &fileName) const
86 {
87     return fileName.endsWith(QLatin1String(".rpmap"), Qt::CaseInsensitive);
88 }
89 #endif
90 
nameFilter() const91 QString RpMapPlugin::nameFilter() const
92 {
93     return tr("RpTool MapTool files (*.rpmap)");
94 }
95 
shortName() const96 QString RpMapPlugin::shortName() const
97 {
98     return QStringLiteral("rpmap");
99 }
100 
errorString() const101 QString RpMapPlugin::errorString() const
102 {
103     return mError;
104 }
105 
writeEntry(QXmlStreamWriter & writer,QString const & key,QString const & value)106 static void writeEntry(QXmlStreamWriter &writer, QString const &key, QString const& value)
107 {
108     writer.writeStartElement(QStringLiteral("entry"));
109     writer.writeTextElement(QStringLiteral("string"), key);
110     writer.writeTextElement(QStringLiteral("string"), value);
111     writer.writeEndElement();
112 }
113 
writeGUID(QXmlStreamWriter & writer,QString const & key,QUuid const & id)114 static void writeGUID(QXmlStreamWriter &writer, QString const &key, QUuid const& id)
115 {
116     writer.writeStartElement(key);
117     writer.writeTextElement(QStringLiteral("baGUID"), id.toRfc4122().toBase64());
118     writer.writeEndElement();
119 }
120 
writeTile(QXmlStreamWriter & writer,int x,int y,QString const & name,int facing,QString const & md5,bool flipx,bool flipy)121 static void writeTile(QXmlStreamWriter &writer, int x, int y, QString const& name, int facing, QString const& md5, bool flipx, bool flipy)
122 {
123     writer.writeStartElement(QStringLiteral("entry"));
124     writeGUID(writer, QStringLiteral("net.rptools.maptool.model.GUID"), QUuid::createUuid());
125     writer.writeStartElement(QStringLiteral("net.rptools.maptool.model.Token"));
126     writer.writeStartElement(QStringLiteral("id")); writer.writeAttribute(QStringLiteral("reference"), QStringLiteral("../../net.rptools.maptool.model.GUID")); writer.writeEndElement();
127     //writeGUID(writer, QStringLiteral("exposedAreaGUID"), QUuid::createUuid());
128     writer.writeStartElement(QStringLiteral("imageAssetMap"));
129     writer.writeStartElement(QStringLiteral("entry"));
130     writer.writeEmptyElement(QStringLiteral("null"));
131     writer.writeStartElement(QStringLiteral("net.rptools.lib.MD5Key"));
132     writer.writeTextElement(QStringLiteral("id"), md5);
133     writer.writeEndElement(); // MD5Key
134     writer.writeEndElement(); // entry
135     writer.writeEndElement(); // imageAssetMap
136     writer.writeTextElement(QStringLiteral("x"), QString::number(x));
137     writer.writeTextElement(QStringLiteral("y"), QString::number(y));
138     writer.writeTextElement(QStringLiteral("z"), QString::number(1));
139     writer.writeTextElement(QStringLiteral("anchorX"), QString::number(0));
140     writer.writeTextElement(QStringLiteral("anchorY"), QString::number(0));
141     writer.writeTextElement(QStringLiteral("snapToScale"), QStringLiteral("false"));
142     writer.writeTextElement(QStringLiteral("width"), QString::number(300));
143     writer.writeTextElement(QStringLiteral("height"), QString::number(300));
144     writer.writeTextElement(QStringLiteral("isoWidth"), QString::number(0));
145     writer.writeTextElement(QStringLiteral("isoHeight"), QString::number(0));
146     writer.writeTextElement(QStringLiteral("scaleX"), QString::number(1.0));
147     writer.writeTextElement(QStringLiteral("scaleY"), QString::number(1.0));
148 
149     writer.writeStartElement(QStringLiteral("sizeMap"));
150     writer.writeStartElement(QStringLiteral("entry"));
151     writer.writeTextElement(QStringLiteral("java-class"), QStringLiteral("net.rptools.maptool.model.SquareGrid"));
152     writeGUID(writer, QStringLiteral("net.rptools.maptool.model.GUID"), QUuid::createUuid());
153     writer.writeEndElement(); // entry
154     writer.writeEndElement(); // sizeMap
155 
156     writer.writeTextElement(QStringLiteral("snapToGrid"), QStringLiteral("true"));
157     writer.writeTextElement(QStringLiteral("isVisible"), QStringLiteral("true"));
158     writer.writeTextElement(QStringLiteral("visibleOnlyToOwner"), QStringLiteral("false"));
159     writer.writeTextElement(QStringLiteral("vblColorSensitivity"), QString::number(-1));
160     writer.writeTextElement(QStringLiteral("alwaysVisibleTolerance"), QString::number(2));
161     writer.writeTextElement(QStringLiteral("isAlwaysVisible"), QStringLiteral("false"));
162     writer.writeTextElement(QStringLiteral("name"), name);
163     writer.writeTextElement(QStringLiteral("ownerType"), QString::number(0));
164     writer.writeTextElement(QStringLiteral("tokenShape"), QStringLiteral("TOP_DOWN"));
165     writer.writeTextElement(QStringLiteral("tokenType"), QStringLiteral("NPC"));
166     writer.writeTextElement(QStringLiteral("layer"), QStringLiteral("BACKGROUND"));
167     writer.writeTextElement(QStringLiteral("propertyType"), QStringLiteral("Basic"));
168     writer.writeTextElement(QStringLiteral("facing"), QString::number(facing));
169     writer.writeTextElement(QStringLiteral("tokenOpacity"), QString::number(1.0));
170     writer.writeTextElement(QStringLiteral("terrainModifier"), QString::number(0.0));
171     writer.writeTextElement(QStringLiteral("terrainModifierOperation"), QStringLiteral("NONE"));
172     writer.writeStartElement(QStringLiteral("terrainModifiersIgnored"));
173     writer.writeTextElement(QStringLiteral("net.rptools.maptool.model.Token_-TerrainModifierOperation"), QStringLiteral("NONE"));
174     writer.writeEndElement(); // terrainModifiersIgnored
175     writer.writeTextElement(QStringLiteral("isFlippedX"), flipx ? QStringLiteral("true") : QStringLiteral("false"));
176     writer.writeTextElement(QStringLiteral("isFlippedY"), flipy ? QStringLiteral("true") : QStringLiteral("false"));
177     writer.writeTextElement(QStringLiteral("sightType"), QStringLiteral("Normal"));
178     writer.writeTextElement(QStringLiteral("hasSight"), QStringLiteral("false"));
179     writer.writeEmptyElement(QStringLiteral("state"));
180     writer.writeStartElement(QStringLiteral("propertyMapCI"));
181     writer.writeEmptyElement(QStringLiteral("store"));
182     writer.writeEndElement(); // propertyMapCI
183     writer.writeEmptyElement(QStringLiteral("macroPropertiesMap"));
184     writer.writeEmptyElement(QStringLiteral("speechMap"));
185 
186     writer.writeEndElement(); // net.rptools.maptool.model.Token
187     writer.writeEndElement(); // entry
188 }
189 
writeCellShape(QXmlStreamWriter & writer,Tiled::Map const * map)190 static void writeCellShape(QXmlStreamWriter &writer, Tiled::Map const* map)
191 {
192     writer.writeStartElement(QStringLiteral("cellShape"));
193     writer.writeStartElement(QStringLiteral("curves"));
194     writer.writeStartElement(QStringLiteral("sun.awt.geom.Order0"));
195     writer.writeTextElement(QStringLiteral("direction"), QStringLiteral("1"));
196     writer.writeTextElement(QStringLiteral("x"), QStringLiteral("0.0"));
197     writer.writeTextElement(QStringLiteral("y"), QStringLiteral("0.0"));
198     writer.writeEndElement(); // Order0
199     writer.writeStartElement(QStringLiteral("sun.awt.geom.Order1"));
200     writer.writeTextElement(QStringLiteral("direction"), QStringLiteral("1"));
201     writer.writeTextElement(QStringLiteral("x0"), QStringLiteral("0.0"));
202     writer.writeTextElement(QStringLiteral("y0"), QStringLiteral("0.0"));
203     writer.writeTextElement(QStringLiteral("x1"), QStringLiteral("0.0"));
204     writer.writeTextElement(QStringLiteral("y1"), QString::number(map->tileHeight()));
205     writer.writeTextElement(QStringLiteral("xmin"), QStringLiteral("0.0"));
206     writer.writeTextElement(QStringLiteral("xmax"), QStringLiteral("0.0"));
207     writer.writeEndElement(); // Order1
208     writer.writeStartElement(QStringLiteral("sun.awt.geom.Order1"));
209     writer.writeTextElement(QStringLiteral("direction"), QStringLiteral("-1"));
210     writer.writeTextElement(QStringLiteral("x0"), QString::number(map->tileWidth()));
211     writer.writeTextElement(QStringLiteral("y0"), QStringLiteral("0.0"));
212     writer.writeTextElement(QStringLiteral("x1"), QString::number(map->tileWidth()));
213     writer.writeTextElement(QStringLiteral("y1"), QString::number(map->tileHeight()));
214     writer.writeTextElement(QStringLiteral("xmin"), QString::number(map->tileWidth()));
215     writer.writeTextElement(QStringLiteral("xmax"), QString::number(map->tileWidth()));
216     writer.writeEndElement(); // Order1
217     writer.writeEndElement(); // curves
218     writer.writeEndElement(); // cellShape
219 }
220 
writeGrid(QXmlStreamWriter & writer,Tiled::Map const * map)221 static void writeGrid(QXmlStreamWriter &writer, Tiled::Map const* map)
222 {
223     writer.writeStartElement(QStringLiteral("grid"));
224     writer.writeAttribute(QStringLiteral("class"), QStringLiteral("net.rptools.maptool.model.SquareGrid"));
225     writer.writeTextElement(QStringLiteral("offsetX"), QString::number(0));
226     writer.writeTextElement(QStringLiteral("offsetY"), QString::number(0));
227     writer.writeTextElement(QStringLiteral("size"), QString::number(std::max(map->tileWidth(),map->tileHeight())));
228     writer.writeStartElement(QStringLiteral("zone")); writer.writeAttribute(QStringLiteral("reference"), QStringLiteral("../..")); writer.writeEndElement();
229     writeCellShape(writer, map);
230     writer.writeEndElement(); // grid
231 }
232 
writeClass(QXmlStreamWriter & writer,QString const & name,QString const & type)233 static void writeClass(QXmlStreamWriter &writer, QString const &name, QString const& type)
234 {
235     writer.writeStartElement(name);
236     writer.writeAttribute(QStringLiteral("class"), type);
237     writer.writeEndElement();
238 }
239 
writeTokenMap(QXmlStreamWriter & writer,Tiled::Map const * map)240 void RpMapPlugin::writeTokenMap(QXmlStreamWriter &writer, Tiled::Map const* map)
241 {
242     const int tileWidth = map->tileWidth();
243     const int tileHeight = map->tileHeight();
244 //    const QColor backgroundColor = map->backgroundColor();
245     writer.writeStartElement(QStringLiteral("tokenMap"));
246 
247     for (Layer *layer : map->layers()) {
248         if (TileLayer *tileLayer = layer->asTileLayer()) {
249             for (int y = 0; y < tileLayer->height(); ++y) {
250                 for (int x = 0; x < tileLayer->width(); ++x) {
251                     Cell t = tileLayer->cellAt(x, y);
252                     if (t.isEmpty())
253                         continue;
254 
255                     static constexpr uint16_t rotation[8] = { 270, 270, 270, 90, 0, 0, 180, 180 };
256                     // in addition to rotation
257                     static constexpr bool flip_horiz[8] = { false, false, true, false,  true, false, false, true };
258                     static constexpr bool flip_vert[8] = { false, true, false, false,  false, false, false, false };
259 
260                     uint8_t rot_index = (t.flippedVertically() ? 1 : 0) | (t.flippedHorizontally() ? 2 : 0) | (t.flippedAntiDiagonally() ? 4 : 0);
261                     //int tileid= t.tileId();
262                     Tile const* tile = t.tile();
263                     QUrl tileurl = tile->imageSource();
264                     if (tileurl.isLocalFile()) {
265                         QString tilepath = tileurl.toLocalFile();
266                         auto it = filename2md5.find(tilepath);
267                         if (it == filename2md5.end()) {
268                             QFile file(tilepath);
269                             if (file.open(QIODevice::ReadOnly)) {
270                                 QByteArray image = file.readAll();
271                                 QByteArray md5bin = QCryptographicHash::hash(image, QCryptographicHash::Md5);
272                                 QString md5string = md5bin.toHex();
273                                 it = filename2md5.insert(tilepath, md5string);
274                                 // remember the first element (tile) referencing this file
275                                 first_used_md5.push_back(number_of_tiles);
276                             }
277                             else
278                                 continue;
279                         }
280                         assert(it != filename2md5.end());
281                         QString md5 = it.value();
282                         writeTile(writer, x*tileWidth, y*tileHeight, QStringLiteral("token"), rotation[rot_index], md5, flip_horiz[rot_index], flip_vert[rot_index]);
283                         ++number_of_tiles;
284                     }
285                 }
286             }
287             break; // only output first layer (for now)
288         }
289     }
290     //writeTile(writer, 400, 300, QStringLiteral("token"), 180);
291     //writeTile(writer, 400, 600, QStringLiteral("token2"), 0);
292 
293     writer.writeEndElement(); // tokenMap
294 }
295 
writeTokenOrderedList(QXmlStreamWriter & writer)296 void RpMapPlugin::writeTokenOrderedList(QXmlStreamWriter &writer)
297 {
298     writer.writeStartElement(QStringLiteral("tokenOrderedList"));
299     writer.writeAttribute(QStringLiteral("class"), QStringLiteral("linked-list"));
300 
301     writer.writeStartElement(QStringLiteral("net.rptools.maptool.model.Token"));
302     writer.writeAttribute(QStringLiteral("reference"), QStringLiteral("../../tokenMap/entry/net.rptools.maptool.model.Token"));
303     writer.writeEndElement(); // Token
304     for (uint32_t i = 1; i < number_of_tiles; ++i) {
305         writer.writeStartElement(QStringLiteral("net.rptools.maptool.model.Token"));
306         writer.writeAttribute(QStringLiteral("reference"), QStringLiteral("../../tokenMap/entry[")
307                               +QString::number(i+1)
308                               +QStringLiteral("]/net.rptools.maptool.model.Token"));
309         writer.writeEndElement(); // Token
310     }
311 
312     writer.writeEndElement(); // tokenOrderedList
313 }
314 
writeZone2(QXmlStreamWriter & writer)315 static void writeZone2(QXmlStreamWriter &writer)
316 {
317     writer.writeStartElement(QStringLiteral("initiativeList"));
318     writer.writeEmptyElement(QStringLiteral("tokens"));
319     writer.writeTextElement(QStringLiteral("current"), QString::number(-1));
320     writer.writeTextElement(QStringLiteral("round"), QString::number(-1));
321     writer.writeStartElement(QStringLiteral("zoneId")); writer.writeAttribute(QStringLiteral("reference"), QStringLiteral("../../id")); writer.writeEndElement();
322     writer.writeTextElement(QStringLiteral("fullUpdate"), QStringLiteral("false"));
323     writer.writeTextElement(QStringLiteral("hideNPC"), QStringLiteral("false"));
324     writer.writeEndElement(); // initiativeList
325 
326     writer.writeStartElement(QStringLiteral("exposedArea"));
327     writer.writeEmptyElement(QStringLiteral("curves"));
328     writer.writeEndElement(); // exposedArea
329     writer.writeTextElement(QStringLiteral("hasFog"), QStringLiteral("false"));
330 
331     writer.writeStartElement(QStringLiteral("fogPaint"));
332     writer.writeAttribute(QStringLiteral("class"), QStringLiteral("net.rptools.maptool.model.drawing.DrawableColorPaint"));
333     writer.writeTextElement(QStringLiteral("color"), QString::number(-16777216));
334     writer.writeEndElement(); // fogPaint
335 
336     writer.writeStartElement(QStringLiteral("topology"));
337     writer.writeStartElement(QStringLiteral("curves"));
338     writer.writeAttribute(QStringLiteral("reference"), QStringLiteral("../../exposedArea/curves"));
339     writer.writeEndElement(); // curves
340     writer.writeEndElement(); // topology
341     writer.writeStartElement(QStringLiteral("topologyTerrain"));
342     writer.writeStartElement(QStringLiteral("curves"));
343     writer.writeAttribute(QStringLiteral("reference"), QStringLiteral("../../exposedArea/curves"));
344     writer.writeEndElement(); // curves
345     writer.writeEndElement(); // topologyTerrain
346 
347     writer.writeStartElement(QStringLiteral("backgroundPaint"));
348     writer.writeAttribute(QStringLiteral("class"), QStringLiteral("net.rptools.maptool.model.drawing.DrawableColorPaint"));
349     writer.writeTextElement(QStringLiteral("color"), QString::number(-16777216));
350     writer.writeEndElement(); // backgroundPaint
351 
352     writer.writeStartElement(QStringLiteral("boardPosition"));
353     writer.writeTextElement(QStringLiteral("x"), QString::number(0));
354     writer.writeTextElement(QStringLiteral("y"), QString::number(0));
355     writer.writeEndElement(); // boardPosition
356 
357     writer.writeTextElement(QStringLiteral("drawBoard"), QStringLiteral("true"));
358     writer.writeTextElement(QStringLiteral("boardChanged"), QStringLiteral("false"));
359     writer.writeTextElement(QStringLiteral("name"), QStringLiteral("Tiled export"));
360     writer.writeTextElement(QStringLiteral("isVisible"), QStringLiteral("true"));
361     writer.writeTextElement(QStringLiteral("visionType"), QStringLiteral("OFF"));
362     writer.writeTextElement(QStringLiteral("tokenSelection"), QStringLiteral("ALL"));
363     writer.writeTextElement(QStringLiteral("height"), QString::number(0));
364     writer.writeTextElement(QStringLiteral("width"), QString::number(0));
365 }
366 
writeMap(QXmlStreamWriter & writer,Tiled::Map const * map)367 void RpMapPlugin::writeMap(QXmlStreamWriter &writer, Tiled::Map const* map)
368 {
369     writer.writeStartElement(QStringLiteral("zone"));
370     writer.writeTextElement(QStringLiteral("creationTime"), QString::number(QDateTime::currentMSecsSinceEpoch()));
371     writeGUID(writer, QStringLiteral("id"), QUuid::createUuid());
372     writeGrid(writer, map);
373     writer.writeTextElement(QStringLiteral("gridColor"), QString::number(-16777216));
374     writer.writeTextElement(QStringLiteral("imageScaleX"), QString::number(1.0));
375     writer.writeTextElement(QStringLiteral("imageScaleY"), QString::number(1.0));
376     writer.writeTextElement(QStringLiteral("tokenVisionDistance"), QString::number(1000));
377     writer.writeTextElement(QStringLiteral("unitsPerCell"), QString::number(5.0));
378     writer.writeTextElement(QStringLiteral("aStarRounding"), QStringLiteral("NONE"));
379     writer.writeTextElement(QStringLiteral("topologyMode"), QStringLiteral("COMBINED"));
380     writeClass(writer, QStringLiteral("drawables"), QStringLiteral("linked-list"));
381     writeClass(writer, QStringLiteral("gmDrawables"), QStringLiteral("linked-list"));
382     writeClass(writer, QStringLiteral("objectDrawables"), QStringLiteral("linked-list"));
383     writeClass(writer, QStringLiteral("backgroundDrawables"), QStringLiteral("linked-list"));
384     writeClass(writer, QStringLiteral("labels"), QStringLiteral("linked-hash-map"));
385     writeTokenMap(writer, map);
386     writer.writeStartElement(QStringLiteral("exposedAreaMeta"));
387     writer.writeEndElement(); // exposedAreaMeta
388     writeTokenOrderedList(writer);
389     writeZone2(writer); // some arbitrary data
390     writer.writeEndElement(); // zone
391 
392     writer.writeStartElement(QStringLiteral("assetMap"));
393     for (auto i: first_used_md5) {
394         writer.writeStartElement(QStringLiteral("entry"));
395         writer.writeStartElement(QStringLiteral("net.rptools.lib.MD5Key"));
396         QString item;
397         if (i > 0)
398             item = QStringLiteral("[") + QString::number(i+1) + QStringLiteral("]");
399         writer.writeAttribute(QStringLiteral("reference"), QStringLiteral("../../../zone/tokenMap/entry")
400                               +item
401                               +QStringLiteral("/net.rptools.maptool.model.Token/imageAssetMap/entry/net.rptools.lib.MD5Key"));
402         writer.writeEndElement(); // net.rptools.lib.MD5Key
403         writer.writeEmptyElement(QStringLiteral("null"));
404         writer.writeEndElement(); // entry
405     }
406     writer.writeEndElement(); // assetMap
407 }
408 
write(const Tiled::Map * map,const QString & fileName,Options options)409 bool RpMapPlugin::write(const Tiled::Map *map, const QString &fileName, Options options)
410 {
411     filename2md5.clear();
412     first_used_md5.clear();
413     number_of_tiles = 0;
414 
415     Q_UNUSED(options)
416     KZip archive(fileName);
417     if (archive.open(QIODevice::WriteOnly))
418     {
419         {
420             QByteArray properties;
421             QXmlStreamWriter writer(&properties);
422             writer.setAutoFormatting(true);
423             writer.setAutoFormattingIndent(1);
424             writer.writeStartDocument();
425             writer.writeStartElement(QStringLiteral("map"));
426             writeEntry(writer, QStringLiteral("campaignVersion"), QStringLiteral("1.4.1"));
427             writeEntry(writer, QStringLiteral("version"), QStringLiteral("1.7.0"));
428             writer.writeEndElement();
429             writer.writeEndDocument();
430             archive.writeFile(QStringLiteral("properties.xml"), properties);
431         }
432         {
433             QByteArray content;
434             QXmlStreamWriter writer(&content);
435             writer.setAutoFormatting(true);
436             writer.setAutoFormattingIndent(1);
437             writer.writeStartDocument();
438             writer.writeStartElement(QStringLiteral("net.rptools.maptool.util.PersistenceUtil_-PersistedMap"));
439             writeMap(writer, map);
440             writer.writeEndElement(); // PersistedMap
441             writer.writeEndDocument();
442             archive.writeFile(QStringLiteral("content.xml"), content);
443         }
444         archive.close();
445         return true;
446     }
447     return false;
448 }
449 
450 } // namespace RpMap
451