1 /*
2     Read DeLorme drawing files (.an1)
3 
4     Copyright (C) 2005-2014 Ron Parker and Robert Lipe.
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 
20  */
21 
22 #include <cstdio>               // for sprintf, SEEK_CUR
23 #include <cstdint>              // for int32_t
24 #include <cstdlib>              // for atoi, atof
25 #include <cstring>              // for strlen, strchr, memcpy, strstr
26 
27 #include <QtCore/QString>       // for QString
28 #include <QtCore/QVector>       // for QVector
29 
30 #include "defs.h"
31 #include "formspec.h"           // for FsChainAdd, FsChainFind, FormatSpecificData, kFsAn1L, kFsAn1V, kFsAn1W
32 #include "gbfile.h"             // for gbfgetint32, gbfputint32, gbfputint16, gbfgetint16, gbfile, gbfputs, gbfgetc, gbfputc, gbfclose, gbfopen_le, gbfgetdbl, gbfputdbl, gbfread, gbfseek
33 #include "src/core/datetime.h"  // for DateTime
34 
35 
36 #define MYNAME "an1"
37 
38 static gbfile* infile;
39 static gbfile* outfile;
40 
41 static char* output_type = nullptr;
42 static char* road_changes = nullptr;
43 static char* nogc = nullptr;
44 static char* nourl = nullptr;
45 static char* opt_symbol = nullptr;
46 static char* opt_color = nullptr;
47 static char* opt_zoom  = nullptr;
48 static char* opt_wpt_type = nullptr;
49 static char* opt_radius = nullptr;
50 
51 static short output_type_num = 0;
52 static short opt_zoom_num = 0;
53 static long opt_color_num = 0;
54 static short wpt_type_num = 0;
55 static short last_read_type = 0;
56 static double radius = 0.0;
57 
58 static long serial=10000;
59 static long rtserial=1;
60 
61 struct roadchange {
62   long type;
63   char* name;
64 };
65 
66 static roadchange* roadchanges = nullptr;
67 
68 static
69 QVector<arglist_t> an1_args = {
70   {
71     "type", &output_type, "Type of .an1 file",
72     "", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
73   },
74   {
75     "road", &road_changes, "Road type changes",
76     "", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
77   },
78   {
79     "nogc", &nogc, "Do not add geocache data to description",
80     nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
81   },
82   {
83     "nourl", &nourl, "Do not add URLs to description",
84     nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
85   },
86   {
87     "deficon", &opt_symbol, "Symbol to use for point data",
88     "Red Flag", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
89   },
90   {
91     "color", &opt_color, "Color for lines or mapnotes",
92     "red", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
93   },
94   {
95     "zoom", &opt_zoom, "Zoom level to reduce points",
96     nullptr, ARGTYPE_INT, ARG_NOMINMAX, nullptr
97   },
98   {
99     "wpt_type", &opt_wpt_type,
100     "Waypoint type",
101     "", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
102   },
103   {
104     "radius", &opt_radius, "Radius for circles",
105     nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr
106   },
107 };
108 
109 struct guid_t {
110   unsigned long l;
111   unsigned short s[3];
112   unsigned char c[6];
113 };
114 
115 #include "an1sym.h"
116 
117 #define ReadShort(f) gbfgetint16(f)
118 #define WriteShort(f,s) gbfputint16((s),f)
119 #define ReadLong(f) gbfgetint32(f)
120 #define WriteLong(f,l) gbfputint32((l),f)
121 #define ReadDouble(f) gbfgetdbl(f)
122 #define WriteDouble(f,d) gbfputdbl((d),f)
123 
124 static char*
ReadString(gbfile * f,short len)125 ReadString(gbfile* f, short len)
126 {
127   auto* result = (char*)xcalloc(1, len + 1);
128   if (len) {
129     gbfread(result, 1, len, f);
130   }
131   return result;
132 }
133 
134 #define ReadChar(f) (unsigned char) gbfgetc(f)
135 #define WriteChar(f,c) gbfputc((unsigned char)(c),f)
136 #define WriteString(f,s) gbfputs((s),f)
137 
138 static void
ReadGuid(gbfile * f,guid_t * guid)139 ReadGuid(gbfile* f, guid_t* guid)
140 {
141   guid->l = ReadLong(f);
142   for (unsigned short& i : guid->s) {
143     i = ReadShort(f);
144   }
145   for (unsigned char& i : guid->c) {
146     i = ReadChar(f);
147   }
148 }
149 
150 static void
WriteGuid(gbfile * f,guid_t * guid)151 WriteGuid(gbfile* f, guid_t* guid)
152 {
153   WriteLong(f, guid->l);
154   for (int i = 0; i < 3; i++) {
155     WriteShort(f, guid->s[i]);
156   }
157   for (int i = 0; i < 6; i++) {
158     WriteChar(f, guid->c[i]);
159   }
160 }
161 
162 static void
Skip(gbfile * f,unsigned long distance)163 Skip(gbfile* f,
164      unsigned long distance)
165 {
166   gbfseek(f, distance, SEEK_CUR);
167 }
168 
169 static double
DecodeOrd(long ord)170 DecodeOrd(long ord)
171 {
172   return (double)((int32_t)(0x80000000 - ord)) / 0x800000;
173 }
174 
175 static long
EncodeOrd(double ord)176 EncodeOrd(double ord)
177 {
178   return (int32_t)(0x80000000 - (int32_t)(ord * 0x800000));
179 }
180 
181 struct an1_symbol_record {
182   short hotspotxhi{0};
183   long hotspoty{0};
184   long unk1{0};
185   guid_t guid{};
186   char* name{nullptr};
187 };
188 
189 struct an1_waypoint_record : FormatSpecificData {
an1_waypoint_recordan1_waypoint_record190   an1_waypoint_record() : FormatSpecificData(kFsAn1W) {}
191 private:
192   an1_waypoint_record(const an1_waypoint_record&) = default;
193 public:
194   an1_waypoint_record& operator=(const an1_waypoint_record&) = delete;
195   an1_waypoint_record(an1_waypoint_record&&) = delete;
196   an1_waypoint_record& operator=(an1_waypoint_record&&) = delete;
~an1_waypoint_recordan1_waypoint_record197   ~an1_waypoint_record() override
198   {
199     xfree(name);
200     xfree(fontname);
201     xfree(url);
202     xfree(comment);
203     xfree(image_name);
204   }
205 
clonean1_waypoint_record206   an1_waypoint_record* clone() const override
207   {
208     auto* copy = new an1_waypoint_record(*this);
209     copy->name = xstrdup(name);
210     copy->fontname = xstrdup(fontname);
211     copy->url = xstrdup(url);
212     copy->comment = xstrdup(comment);
213     copy->image_name = xstrdup(image_name);
214     return copy;
215   }
216 
217   short magic{0};
218   long unk1{0};
219   long lon{0};
220   long lat{0};
221   short type{0};
222   long height{0};
223   long width{0};
224   short unk2{0};
225   short unk3{0};
226   short serial{0};
227   short unk4{0};
228   unsigned char create_zoom{0};
229   unsigned char visible_zoom{0};
230   short unk5{0};
231   double radius{0.0}; /* in km */
232   char* name{nullptr};
233   char* fontname{nullptr};
234   guid_t guid{};
235   long fontcolor{0};
236   long fontstyle{0};
237   long fontsize{0};
238   long outlineweight{0};
239   long outlinecolor{0};
240   long outlineflags{0};
241   long fillcolor{0};
242   long unk6{0};
243   long fillflags{0};
244 
245   /* Added in SA2006/Topo 6.0 */
246   short unk6_1{0};
247   char* url{nullptr};
248   char* comment{nullptr};
249   long creation_time{0};
250   long modification_time{0};
251   char* image_name{nullptr};
252 };
253 
254 struct an1_vertex_record : FormatSpecificData {
an1_vertex_recordan1_vertex_record255   an1_vertex_record() : FormatSpecificData(kFsAn1V) {}
clonean1_vertex_record256   an1_vertex_record* clone() const override
257   {
258     return new an1_vertex_record(*this);
259   }
260 
261   short magic{0};
262   long unk0{0};
263   long lon{0};
264   long lat{0};
265   short unk1{0};
266 };
267 
268 struct an1_line_record : FormatSpecificData {
an1_line_recordan1_line_record269   an1_line_record() : FormatSpecificData(kFsAn1L) {}
270 private:
271   an1_line_record(const an1_line_record&) = default;
272 public:
273   an1_line_record& operator=(const an1_line_record&) = delete;
274   an1_line_record(an1_line_record&&) = delete;
275   an1_line_record& operator=(an1_line_record&&) = delete;
~an1_line_recordan1_line_record276   ~an1_line_record() override
277   {
278     xfree(name);
279   }
280 
clonean1_line_record281   an1_line_record* clone() const override
282   {
283     auto* copy = new an1_line_record(*this);
284     copy->name = xstrdup(name);
285     return copy;
286   }
287 
288   long roadtype{0};
289   short serial{0};
290   long unk2{0};
291   short unk3{0};
292   short type{0};
293   long unk4{0};
294   char* name{nullptr};
295   long lineweight{0};
296   long linestyle{0};
297   long linecolor{0};
298   long opacity{0};
299   long polyfillcolor{0};
300   long unk6{0};
301   long unk7{0};
302   short unk8{0};
303   long pointcount{0};
304 };
305 
Destroy_AN1_Symbol(an1_symbol_record * symbol)306 static void Destroy_AN1_Symbol(an1_symbol_record* symbol)
307 {
308   xfree(symbol->name);
309 }
310 
Read_AN1_Waypoint(gbfile * f,an1_waypoint_record * wpt)311 static void Read_AN1_Waypoint(gbfile* f, an1_waypoint_record* wpt)
312 {
313   wpt->magic = ReadShort(f);
314   wpt->unk1 = ReadLong(f);
315   wpt->lon = ReadLong(f);
316   wpt->lat = ReadLong(f);
317   wpt->type = ReadShort(f);
318   wpt->height = ReadLong(f);
319   wpt->width = ReadLong(f);
320   wpt->unk2 = ReadShort(f);
321   wpt->unk3 = ReadShort(f);
322   wpt->serial = ReadShort(f);
323   wpt->unk4 = ReadShort(f);
324   wpt->create_zoom = ReadChar(f);
325   wpt->visible_zoom = ReadChar(f);
326   wpt->unk5 = ReadShort(f);
327   wpt->radius = ReadDouble(f);
328   unsigned short len = ReadShort(f);
329   wpt->name = ReadString(f, len);
330 
331   if (len != strlen(wpt->name)) {
332     /* This happens in 06/6.0 files that put extra data in the
333      * name record for backward compatibility's sake */
334     char* ofs = wpt->name + strlen(wpt->name) + 1;
335     wpt->unk6_1 = le_read16(ofs);
336     ofs += 2;
337 
338     len = le_read16(ofs);
339     ofs += 2;
340 
341     if (len) {
342       /*
343        * Trust URL encoded in new format over one in
344        * old format if both are present.  Whack the
345        * name starting at '{URL='.
346        */
347       char* oldurlstr = strstr(wpt->name, "{URL=");
348       if (oldurlstr) {
349         *oldurlstr = 0;
350       }
351       wpt->url = (char*) xcalloc(len+1, 1);
352       memcpy(wpt->url, ofs, len);
353       ofs += len;
354     }
355 
356     len = le_read16(ofs);
357     ofs += 2;
358 
359     if (len) {
360       wpt->comment = (char*) xcalloc(len+1, 1);
361       memcpy(wpt->comment, ofs, len);
362       ofs += len;
363     }
364 
365     /* these are quadwords, presumably for year-2038 compat. */
366     wpt->creation_time = le_read32(ofs);
367     ofs += 8;
368 
369     wpt->modification_time = le_read32(ofs);
370     ofs += 8;
371   }
372 
373   if (wpt->type == 0x12) {
374     /* 'image' type */
375     ReadShort(f);    /* length of font + filename */
376     len = ReadShort(f);
377     wpt->fontname = ReadString(f, len);
378     len = ReadShort(f);
379     wpt->image_name = ReadString(f, len);
380   } else {
381     len = ReadShort(f);
382     wpt->fontname = ReadString(f, len);
383     wpt->image_name = nullptr;
384   }
385   ReadGuid(f, &wpt->guid);
386   wpt->fontcolor = ReadLong(f);
387   wpt->fontstyle = ReadLong(f);
388   wpt->fontsize = ReadLong(f);
389   wpt->outlineweight = ReadLong(f);
390   wpt->outlinecolor = ReadLong(f);
391   wpt->outlineflags = ReadLong(f);
392   wpt->fillcolor = ReadLong(f);
393   wpt->unk6 = ReadLong(f);
394   wpt->fillflags = ReadLong(f);
395 }
396 
Write_AN1_Waypoint(gbfile * f,an1_waypoint_record * wpt)397 static void Write_AN1_Waypoint(gbfile* f, an1_waypoint_record* wpt)
398 {
399   WriteShort(f, wpt->magic);
400   WriteLong(f, wpt->unk1);
401   WriteLong(f, wpt->lon);
402   WriteLong(f, wpt->lat);
403   WriteShort(f, wpt->type);
404   WriteLong(f, wpt->height);
405   WriteLong(f, wpt->width);
406   WriteShort(f, wpt->unk2);
407   WriteShort(f, wpt->unk3);
408   WriteShort(f, wpt->serial);
409   WriteShort(f, wpt->unk4);
410   WriteChar(f, wpt->create_zoom);
411   WriteChar(f, wpt->visible_zoom);
412   WriteShort(f, wpt->unk5);
413   WriteDouble(f, wpt->radius);
414 
415   short len = strlen(wpt->name) + 1 + 2 + 2 +
416               (wpt->url ? strlen(wpt->url) : 0) + 2 +
417               (wpt->comment ? strlen(wpt->comment) : 0) + 8 + 8;
418   WriteShort(f, len);
419   WriteString(f, wpt->name);
420   WriteChar(f, 0);    /* name string terminator */
421 
422   WriteShort(f, wpt->unk6_1);
423 
424   if (wpt->url) {
425     WriteShort(f, strlen(wpt->url));
426     WriteString(f, wpt->url);
427   } else {
428     WriteShort(f, 0);
429   }
430 
431   if (wpt->comment) {
432     WriteShort(f, strlen(wpt->comment));
433     WriteString(f, wpt->comment);
434   } else {
435     WriteShort(f, 0);
436   }
437 
438   WriteLong(f, wpt->creation_time);
439   WriteLong(f, 0);
440 
441   WriteLong(f, wpt->modification_time);
442   WriteLong(f, 0);
443 
444   if (wpt->type == 0x12) {   /* image */
445     len = 2 + (wpt->fontname ? strlen(wpt->fontname) : 0) +
446           2 + (wpt->image_name ? strlen(wpt->image_name) : 0);
447     WriteShort(f, len);
448     if (wpt->fontname) {
449       len = strlen(wpt->fontname);
450       WriteShort(f, len);
451       WriteString(f, wpt->fontname);
452     } else {
453       WriteShort(f, 0);
454     }
455     if (wpt->image_name) {
456       len = strlen(wpt->image_name);
457       WriteShort(f, len);
458       WriteString(f, wpt->image_name);
459     } else {
460       WriteShort(f, 0);
461     }
462   } else {
463     len = strlen(wpt->fontname);
464     WriteShort(f, len);
465     WriteString(f, wpt->fontname);
466   }
467   WriteGuid(f, &wpt->guid);
468   WriteLong(f, wpt->fontcolor);
469   WriteLong(f, wpt->fontstyle);
470   WriteLong(f, wpt->fontsize);
471   WriteLong(f, wpt->outlineweight);
472   WriteLong(f, wpt->outlinecolor);
473   WriteLong(f, wpt->outlineflags);
474   WriteLong(f, wpt->fillcolor);
475   WriteLong(f, wpt->unk6);
476   WriteLong(f, wpt->fillflags);
477 }
478 
Read_AN1_Vertex(gbfile * f,an1_vertex_record * vertex)479 static void Read_AN1_Vertex(gbfile* f, an1_vertex_record* vertex)
480 {
481 
482   vertex->magic = ReadShort(f);
483   vertex->unk0 = ReadLong(f);
484   vertex->lon = ReadLong(f);
485   vertex->lat = ReadLong(f);
486   vertex->unk1 = ReadShort(f);
487 }
488 
Write_AN1_Vertex(gbfile * f,an1_vertex_record * vertex)489 static void Write_AN1_Vertex(gbfile* f, an1_vertex_record* vertex)
490 {
491   WriteShort(f, vertex->magic);
492   WriteLong(f, vertex->unk0);
493   WriteLong(f, vertex->lon);
494   WriteLong(f, vertex->lat);
495   WriteShort(f, vertex->unk1);
496 }
497 
Read_AN1_Line(gbfile * f,an1_line_record * line)498 static void Read_AN1_Line(gbfile* f, an1_line_record* line)
499 {
500   line->roadtype = ReadLong(f);
501   line->serial = ReadShort(f);
502   line->unk2 = ReadLong(f);
503   line->unk3 = ReadShort(f);
504   line->type = ReadShort(f);
505   line->unk4 = ReadLong(f);
506   short len = ReadShort(f);
507   line->name = ReadString(f, len);
508   line->lineweight = ReadShort(f);
509   line->linestyle = ReadLong(f);
510   line->linecolor = ReadLong(f);
511   line->opacity = ReadLong(f);
512   line->polyfillcolor = ReadLong(f);
513   line->unk6 = ReadLong(f);
514   line->unk7 = ReadLong(f);
515   line->unk8 = ReadShort(f);
516   line->pointcount = ReadLong(f);
517 }
518 
Write_AN1_Line(gbfile * f,an1_line_record * line)519 static void Write_AN1_Line(gbfile* f, an1_line_record* line)
520 {
521   WriteLong(f, line->roadtype);
522   WriteShort(f, line->serial);
523   WriteLong(f, line->unk2);
524   WriteShort(f, line->unk3);
525   WriteShort(f, line->type);
526   WriteLong(f, line->unk4);
527   short len = strlen(line->name);
528   WriteShort(f, len);
529   WriteString(f, line->name);
530   WriteShort(f, (short) line->lineweight);
531   WriteLong(f, line->linestyle);
532   WriteLong(f, line->linecolor);
533   WriteLong(f, line->opacity);
534   WriteLong(f, line->polyfillcolor);
535   WriteLong(f, line->unk6);
536   WriteLong(f, line->unk7);
537   WriteShort(f, line->unk8);
538   WriteLong(f, line->pointcount);
539 }
540 
Skip_AN1_IL(gbfile * f)541 static void Skip_AN1_IL(gbfile* f)
542 {
543   Skip(f, 26);
544 }
545 
Skip_AN1_BM(gbfile * f)546 static void Skip_AN1_BM(gbfile* f)
547 {
548   Skip(f, 8);    /* BITMAPFILEHEADER fields 1-3 */
549   unsigned long bitoffset = ReadLong(f);
550 
551   unsigned long bmisize = ReadLong(f);
552   Skip(f, 16);    /* BITMAPINFOHEADER fields 2-6 */
553   unsigned long bmsize = ReadLong(f);
554   Skip(f, 16);    /* BITMAPINFOHEADER fields 8-11 */
555 
556   unsigned long palettesize = bitoffset - bmisize - 14;
557   Skip(f, bmsize + palettesize);
558 }
559 
Read_AN1_Symbol(gbfile * f,an1_symbol_record * symbol)560 static void Read_AN1_Symbol(gbfile* f, an1_symbol_record* symbol)
561 {
562   /* This is just the high word of a long; we ate the low
563    * word in the caller.  Fortunately, we don't care. */
564   symbol->hotspotxhi = ReadShort(f);
565   symbol->hotspoty = ReadLong(f);
566   symbol->unk1 = ReadLong(f);
567   ReadGuid(f, &symbol->guid);
568   short len = ReadChar(f);
569   symbol->name = ReadString(f, len);
570 }
571 
Read_AN1_Header(gbfile * f)572 static void Read_AN1_Header(gbfile* f)
573 {
574   unsigned short magic = ReadShort(f);
575   (void) magic; // hush warning.
576   unsigned short type = ReadShort(f);
577 
578   last_read_type = type;
579 }
580 
Write_AN1_Header(gbfile * f)581 static void Write_AN1_Header(gbfile* f)
582 {
583   WriteShort(f, 11557);
584   WriteShort(f, output_type_num);
585 }
586 
Read_AN1_Bitmaps(gbfile * f)587 static void Read_AN1_Bitmaps(gbfile* f)
588 {
589   an1_symbol_record symbol;
590 
591   long count = ReadLong(f);
592 
593   while (count) {
594     unsigned short magic = ReadShort(f);
595     switch (magic) {
596     case 0x4d42:
597       Skip_AN1_BM(f);
598       break;
599     case 0x4c49:
600       Skip_AN1_IL(f);
601       break;
602     default:
603       Read_AN1_Symbol(f, &symbol);
604       Destroy_AN1_Symbol(&symbol);
605       count--;
606       break;
607     }
608   }
609 
610   /* Read the symbol table */
611 }
612 
Write_AN1_Bitmaps(gbfile * f)613 static void Write_AN1_Bitmaps(gbfile* f)
614 {
615   /* On write, we don't output any bitmaps, so writing them
616    * is just a matter of writing a count of zero */
617   WriteLong(f, 0);
618 }
619 
Read_AN1_Waypoints(gbfile * f)620 static void Read_AN1_Waypoints(gbfile* f)
621 {
622   const char* icon = nullptr;
623   ReadShort(f);
624   unsigned long count = ReadLong(f);
625   for (unsigned long i = 0; i < count; i++) {
626     auto* rec = new an1_waypoint_record;
627     Read_AN1_Waypoint(f, rec);
628     auto* wpt_tmp = new Waypoint;
629 
630     if (rec->creation_time) {
631       wpt_tmp->SetCreationTime(rec->creation_time);
632     }
633     wpt_tmp->longitude = -DecodeOrd(rec->lon);
634     wpt_tmp->latitude = DecodeOrd(rec->lat);
635     wpt_tmp->notes = rec->comment;
636     wpt_tmp->description = rec->name;
637 
638     if (rec->url) {
639       wpt_tmp->AddUrlLink(rec->url);
640     } else {
641       int u = wpt_tmp->description.indexOf("{URL=");
642       if (u != -1) {
643         QString us = wpt_tmp->description.mid(u);
644         us.remove(0,5); // throw away anything up to and including "{URL="
645         us.chop(1); // throw away final character, assumed to be "}"
646         if (!us.isEmpty()) {
647           wpt_tmp->AddUrlLink(us);
648         }
649       }
650     }
651 
652     if (rec->image_name) {
653       wpt_tmp->icon_descr = rec->image_name;
654     } else if (FindIconByGuid(&rec->guid, &icon)) {
655       wpt_tmp->icon_descr = icon;
656     }
657 
658     wpt_tmp->fs.FsChainAdd(rec);
659     rec = nullptr;
660     waypt_add(wpt_tmp);
661   }
662 }
663 
664 static void
Write_One_AN1_Waypoint(const Waypoint * wpt)665 Write_One_AN1_Waypoint(const Waypoint* wpt)
666 {
667   an1_waypoint_record* rec;
668 
669   const auto* source_rec = reinterpret_cast<an1_waypoint_record*>(wpt->fs.FsChainFind(kFsAn1W));
670 
671   if (source_rec != nullptr) {
672     rec = source_rec->clone();
673     if (opt_zoom) {
674       rec->visible_zoom = opt_zoom_num;
675     }
676   } else {
677     rec = new an1_waypoint_record;
678     rec->magic = 1;
679     rec->type = wpt_type_num;
680     rec->unk2 = 3;
681     rec->unk3 = 18561;
682     rec->radius = radius;
683     rec->fillcolor = opt_color_num;
684     rec->fillflags = 3;
685     if (wpt_type_num == 5) {
686       rec->fillflags = 0x8200;
687     }
688     rec->height = -50;
689     rec->width = 20;
690     rec->fontname = xstrdup("Arial");
691     FindIconByName(opt_symbol, &rec->guid);
692     rec->fontsize = 10;
693     rec->visible_zoom = opt_zoom?opt_zoom_num:10;
694     rec->unk6_1 = 1;
695   }
696   xfree(rec->name);
697   rec->name = xstrdup(wpt->description);
698 
699   if (!nogc && wpt->gc_data->id) {
700     // FIXME: this whole mess should be qstring concatenation
701     auto* extra = (char*) xmalloc(25 + wpt->gc_data->placer.length() + wpt->shortname.length());
702     sprintf(extra, "\r\nBy %s\r\n%s (%1.1f/%1.1f)",
703             CSTR(wpt->gc_data->placer),
704             CSTRc(wpt->shortname), wpt->gc_data->diff/10.0,
705             wpt->gc_data->terr/10.0);
706     rec->name = xstrappend(rec->name, extra);
707     xfree(extra);
708   }
709 
710   if (!nourl && wpt->HasUrlLink()) {
711     UrlLink l = wpt->GetUrlLink();
712     int len = 7 + l.url_.length();
713     auto* extra = (char*)xmalloc(len);
714     sprintf(extra, "{URL=%s}", CSTR(l.url_));
715     rec->name = xstrappend(rec->name, extra);
716     xfree(extra);
717     if (rec->url) {
718       xfree(rec->url);
719     }
720     rec->url = xstrdup(l.url_);
721   }
722   if (!wpt->notes.isEmpty()) {
723     if (rec->comment) {
724       xfree(rec->comment);
725     }
726     rec->comment = xstrdup(wpt->notes);
727   }
728 
729 
730   rec->creation_time = rec->modification_time = wpt->GetCreationTime().toTime_t();
731   rec->lat = EncodeOrd(wpt->latitude);
732   rec->lon = EncodeOrd(-wpt->longitude);
733   rec->serial = serial++;
734 
735   if (rec->type == 0x12) {    /* image */
736     if (wpt->icon_descr.contains(":\\")) {
737       rec->image_name = xstrdup(wpt->icon_descr);
738       rec->height = -244;
739       rec->width = -1;
740     }
741   }
742   if (!rec->image_name && !wpt->icon_descr.isNull()) {
743     FindIconByName(CSTR(wpt->icon_descr), &rec->guid);
744   }
745 
746   Write_AN1_Waypoint(outfile, rec);
747   delete rec;
748 }
749 
Write_AN1_Waypoints(gbfile * f)750 static void Write_AN1_Waypoints(gbfile* f)
751 {
752   WriteShort(f, 2);
753   WriteLong(f, waypt_count());
754   waypt_disp_all(Write_One_AN1_Waypoint);
755 }
756 
Read_AN1_Lines(gbfile * f)757 static void Read_AN1_Lines(gbfile* f)
758 {
759   ReadShort(f);
760   unsigned long count = ReadLong(f);
761   for (unsigned long i = 0; i < count; i++) {
762     auto* rec = new an1_line_record;
763     Read_AN1_Line(f, rec);
764     /* create route rec */
765     auto* rte_head = new route_head;
766     rte_head->line_color.bbggrr = rec->linecolor;
767     if (rec->opacity == 0x8200) {
768       rte_head->line_color.opacity = 128;
769     }
770     // lineweight isn't set for dashed/dotted lines
771     // Since we don't have a way to represent this internally yet,
772     // use leave line_width at the default.
773     if (rec->lineweight) {
774       rte_head->line_width = rec->lineweight;
775     }
776     rte_head->rte_name = rec->name;
777     rte_head->fs.FsChainAdd(rec);
778     route_add_head(rte_head);
779     for (unsigned long j = 0; j < (unsigned) rec->pointcount; j++) {
780       auto* vert = new an1_vertex_record;
781       Read_AN1_Vertex(f, vert);
782 
783       /* create route point */
784       auto* wpt_tmp = new Waypoint;
785       wpt_tmp->latitude = DecodeOrd(vert->lat);
786       wpt_tmp->longitude = -DecodeOrd(vert->lon);
787       wpt_tmp->shortname = QString::asprintf("\\%5.5lx", rtserial++);
788       wpt_tmp->fs.FsChainAdd(vert);
789       route_add_wpt(rte_head, wpt_tmp);
790     }
791   }
792 }
793 
794 static void
Make_Road_Changes(an1_line_record * rec)795 Make_Road_Changes(an1_line_record* rec)
796 {
797   int i = 0;
798 
799   if (!rec) {
800     return;
801   }
802 
803   if (!roadchanges) {
804     return;
805   }
806 
807   while (roadchanges[i].name) {
808     if (!case_ignore_strcmp(roadchanges[i].name, rec->name)) {
809       rec->roadtype = roadchanges[i].type;
810       break;
811     }
812     i++;
813   }
814 }
815 
816 static void
Write_One_AN1_Line(const route_head * rte)817 Write_One_AN1_Line(const route_head* rte)
818 {
819   an1_line_record* rec;
820 
821   const auto* source_rec = reinterpret_cast<an1_line_record*>(rte->fs.FsChainFind(kFsAn1L));
822 
823   if (source_rec != nullptr) {
824     rec = source_rec->clone();
825     switch (output_type_num) {
826     case 1:
827       if (rec->type != 14) {
828         rec->roadtype = 0x11100541;
829         rec->unk2 = 655360;
830         rec->type = 14;
831         rec->unk8 = 2;
832       } // end if
833       Make_Road_Changes(rec);
834       break;
835     case 2:
836       if (rec->type != 15) {
837         rec->type = 15;
838       } // end if
839       break;
840     case 4:
841       if (rec->type != 16) {
842         rec->type = 16;
843       } // end if
844       break;
845     }
846   } else {
847     rec = new an1_line_record;
848     rec->name = nullptr;
849     switch (output_type_num) {
850     /*  drawing road trail waypoint track  */
851     case 1: /* road */
852       rec->roadtype = 0x11100541;
853       rec->unk2 = 655360;
854       rec->type = 14;
855       rec->unk8 = 2;
856       rec->name = xstrdup(rte->rte_name);
857       break;
858 
859     case 2: /* trail */
860       rec->roadtype = 0x11071c50;
861       rec->unk2 = 917504;
862       rec->type = 15;
863       rec->unk8 = 2;
864       break;
865 
866     case 4: /* track */
867       rec->roadtype = 0x48800015;
868       rec->unk2 = 917504;
869       rec->type = 16;
870       rec->unk4 = 2;
871       rec->unk8 = 2;
872       break;
873 
874     case 0: /* drawing */
875     case 3: /* waypoint - shouldn't have lines */
876     default:
877       rec->roadtype = 0x48800015;
878       rec->unk2 = 1048576;
879       rec->type = 2;
880       rec->unk4 = 2;
881       rec->lineweight = 6;
882       rec->linecolor = opt_color_num; /* red */
883       rec->opacity = 3;
884       rec->unk8 = 2;
885       break;
886     }
887     if (!rec->name) {
888       rec->name = xstrdup("");
889     }
890 
891   }
892   rec->serial = serial++;
893   rec->pointcount = rte->rte_waypt_ct;
894   Write_AN1_Line(outfile, rec);
895   delete rec;
896 }
897 
898 static void
Write_One_AN1_Vertex(const Waypoint * wpt)899 Write_One_AN1_Vertex(const Waypoint* wpt)
900 {
901   an1_vertex_record* rec;
902 
903   const auto* source_rec = reinterpret_cast<an1_vertex_record*>(wpt->fs.FsChainFind(kFsAn1V));
904 
905   if (source_rec != nullptr) {
906     rec = source_rec->clone();
907   } else {
908     rec = new an1_vertex_record;
909     rec->magic = 1;
910   }
911   rec->lat = EncodeOrd(wpt->latitude);
912   rec->lon = EncodeOrd(-wpt->longitude);
913 
914   Write_AN1_Vertex(outfile, rec);
915   delete rec;
916 }
917 
Write_AN1_Lines(gbfile * f)918 static void Write_AN1_Lines(gbfile* f)
919 {
920   WriteShort(f, 2);
921   WriteLong(f, route_count()+track_count());
922 
923   route_disp_all(Write_One_AN1_Line, nullptr, Write_One_AN1_Vertex);
924   track_disp_all(Write_One_AN1_Line, nullptr, Write_One_AN1_Vertex);
925 }
926 
927 static void
Init_Wpt_Type()928 Init_Wpt_Type()
929 {
930   if (!opt_wpt_type || !opt_wpt_type[0]) {
931     wpt_type_num = 1; /* marker */
932     return;
933   }
934   if ((opt_wpt_type[0] & 0xf0) == 0x30) {
935     wpt_type_num = atoi(opt_wpt_type);
936   } else {
937     wpt_type_num = 1; /* marker */
938     if (!case_ignore_strcmp(opt_wpt_type, "marker")) {
939       wpt_type_num = 1;
940     } else if (!case_ignore_strcmp(opt_wpt_type, "symbol")) {
941       wpt_type_num = 1; /* symbol and marker are synonyms */
942     } else if (!case_ignore_strcmp(opt_wpt_type, "text")) {
943       wpt_type_num = 4;
944     } else if (!case_ignore_strcmp(opt_wpt_type, "mapnote")) {
945       wpt_type_num = 6;
946     } else if (!case_ignore_strcmp(opt_wpt_type, "circle")) {
947       wpt_type_num = 5;
948     } else if (!case_ignore_strcmp(opt_wpt_type, "image")) {
949       wpt_type_num = 18;
950     } else {
951       fatal(MYNAME ": wpt_type must be "
952             "symbol, text, mapnote, circle, or image\n");
953     }
954   }
955 }
956 
957 static void
Init_Output_Type()958 Init_Output_Type()
959 {
960   if (!output_type || !output_type[0]) {
961     output_type_num = last_read_type;
962     return;
963   }
964   if ((output_type[0] & 0xf0) == 0x30) {
965     output_type_num = atoi(output_type);
966   } else {
967     output_type_num = 0;
968     if (!case_ignore_strcmp(output_type, "drawing")) {
969       output_type_num = 0;
970     } else if (!case_ignore_strcmp(output_type, "road")) {
971       output_type_num = 1;
972     } else if (!case_ignore_strcmp(output_type, "trail")) {
973       output_type_num = 2;
974     } else if (!case_ignore_strcmp(output_type, "waypoint")) {
975       output_type_num = 3;
976     } else if (!case_ignore_strcmp(output_type, "track")) {
977       output_type_num = 4;
978     } else {
979       fatal(MYNAME ": type must be "
980             "drawing, road, trail, waypoint, or track\n");
981     }
982   }
983   last_read_type = output_type_num;
984 }
985 
986 static long
Parse_Change_Type(char * type)987 Parse_Change_Type(char* type)
988 {
989   long retval = 0x11100541;
990 
991   if (!case_ignore_strcmp(type, "limited")) {
992     retval = 0x11070430;
993   } else if (!case_ignore_strcmp(type, "toll")) {
994     retval = 0x11070470;
995   } else if (!case_ignore_strcmp(type, "us")) {
996     retval = 0x11070870;
997   } else if (!case_ignore_strcmp(type, "state")) {
998     retval = 0x11070c10;
999   } else if (!case_ignore_strcmp(type, "primary")) {
1000     /* primary state/provincial routes */
1001     retval = 0x11070840;
1002   } else if (!case_ignore_strcmp(type, "major")) {
1003     retval = 0x11070c30;
1004   } else if (!case_ignore_strcmp(type, "local")) {
1005     retval = 0x11071010;
1006   } else if (!case_ignore_strcmp(type, "ramp")) {
1007     retval = 0x11070cb0;
1008   } else if (!case_ignore_strcmp(type, "ferry")) {
1009     retval = 0x11070ca0;
1010   } else if (!case_ignore_strcmp(type, "editable")) {
1011     retval = 0x11100541;
1012   } else {
1013     fatal(MYNAME ": unknown road type for road changes\n");
1014   }
1015   return retval;
1016 }
1017 
1018 static void
Free_Road_Changes()1019 Free_Road_Changes()
1020 {
1021   int i = 0;
1022   if (roadchanges) {
1023     while (roadchanges[i].name) {
1024       xfree(roadchanges[i].name);
1025       i++;
1026     }
1027     xfree(roadchanges);
1028   }
1029   roadchanges = nullptr;
1030 }
1031 
1032 static void
Init_Road_Changes()1033 Init_Road_Changes()
1034 {
1035   int count = 0;
1036   Free_Road_Changes();
1037 
1038   if (!road_changes || !road_changes[0]) {
1039     return;
1040   }
1041   char* bar = strchr(road_changes, '!');
1042   while (bar) {
1043     count++;
1044     bar = strchr(bar+1, '!');
1045   }
1046   if (!(count&1)) {
1047     fatal(MYNAME ": invalid format for road changes\n");
1048   }
1049   count = 1 + count / 2;
1050   roadchanges = (roadchange*)xmalloc((count+1) * sizeof(roadchange));
1051 
1052   roadchanges[count].type = 0;
1053   roadchanges[count].name = nullptr;
1054 
1055   char* copy = xstrdup(road_changes);
1056   bar = copy;
1057 
1058   while (count) {
1059     count--;
1060     char* name = bar;
1061     bar = strchr(name, '!');
1062     *bar = '\0';
1063     bar++;
1064     char* strType = bar;
1065     bar = strchr(strType, '!');
1066     if (bar) {
1067       *bar = '\0';
1068       bar++;
1069     }
1070     roadchanges[count].name = xstrdup(name);
1071     roadchanges[count].type = Parse_Change_Type(strType);
1072   }
1073 
1074   xfree(copy);
1075 }
1076 
1077 static void
rd_init(const QString & fname)1078 rd_init(const QString& fname)
1079 {
1080   infile = gbfopen_le(fname, "rb", MYNAME);
1081 }
1082 
1083 static void
rd_deinit()1084 rd_deinit()
1085 {
1086   gbfclose(infile);
1087 }
1088 
1089 static void
my_read()1090 my_read()
1091 {
1092   Read_AN1_Header(infile);
1093   Read_AN1_Bitmaps(infile);
1094   Read_AN1_Waypoints(infile);
1095   Read_AN1_Lines(infile);
1096 }
1097 
1098 static void
wr_init(const QString & fname)1099 wr_init(const QString& fname)
1100 {
1101   outfile = gbfopen_le(fname, "wb", MYNAME);
1102   Init_Output_Type();
1103   Init_Road_Changes();
1104   opt_color_num = color_to_bbggrr(opt_color);
1105   Init_Wpt_Type();
1106   if (opt_zoom) {
1107     opt_zoom_num = atoi(opt_zoom);
1108   }
1109   radius = .1609344; /* 1/10 mi in kilometers */
1110   if (opt_radius) {
1111     radius = atof(opt_radius);
1112     if (!strchr(opt_radius,'k') && !strchr(opt_radius,'K')) {
1113       radius *= kKilometersPerMile;
1114     }
1115   }
1116 }
1117 
1118 static void
wr_deinit()1119 wr_deinit()
1120 {
1121   Free_Road_Changes();
1122   gbfclose(outfile);
1123 }
1124 
1125 static void
my_write()1126 my_write()
1127 {
1128   Write_AN1_Header(outfile);
1129   Write_AN1_Bitmaps(outfile);
1130   Write_AN1_Waypoints(outfile);
1131   Write_AN1_Lines(outfile);
1132 }
1133 
1134 ff_vecs_t an1_vecs = {
1135   ff_type_file,
1136   {
1137     (ff_cap)(ff_cap_read | ff_cap_write)	/* waypoints */,
1138     ff_cap_write 			/* tracks */,
1139     (ff_cap)(ff_cap_read | ff_cap_write) 	/* routes */,
1140   },
1141   rd_init,
1142   wr_init,
1143   rd_deinit,
1144   wr_deinit,
1145   my_read,
1146   my_write,
1147   nullptr,
1148   &an1_args,
1149   CET_CHARSET_ASCII, 0	/* CET-REVIEW */
1150   , NULL_POS_OPS,
1151   nullptr
1152 };
1153