1 // Written by Thorsten Brehm, started November 2012.
2 //
3 // Copyright (C) 2012 Thorsten Brehm, brehmt at gmail dt com
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 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 // Library General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 //
19 // $Id$
20
21 /**
22 * GZ Container File Format
23 *
24 * A (gzipped) binary file.
25 *
26 * Byte-order is little endian (we're ignoring big endian machines - for now...,
27 * - still usable, but they are writing an incompatible format).
28 *
29 * The binary file may contain an arbitrary number of containers.
30 *
31 * Each container has three elements:
32 * - container type
33 * - container payload size
34 * - container payload data (of given "size" in bytes)
35 *
36 * A container may consist of strings, property trees, raw binary data, and whatever else
37 * is implemented.
38 *
39 * Use this for storing/loading user-data only - not for distribution files (scenery, aircraft etc).
40 */
41
42 #ifdef HAVE_CONFIG_H
43 # include <simgear_config.h>
44 #endif
45
46 #include "gzcontainerfile.hxx"
47
48 #include <simgear/props/props_io.hxx>
49 #include <simgear/misc/stdint.hxx>
50 #include <simgear/misc/sg_path.hxx>
51
52 #include <string.h>
53
54 using namespace std;
55 using namespace simgear;
56
57 #if 1
58 #define MY_SG_DEBUG SG_DEBUG
59 #else
60 #define MY_SG_DEBUG SG_ALERT
61 #endif
62
63 /** The header is always the first container in each file, so it's ID doesn't
64 * really matter (as long as it isn't changed) - it cannot conflict with user-specific types. */
65 const ContainerType HeaderType = 0;
66
67 /** Magic word to detect big/little-endian file formats */
68 static const uint32_t EndianMagic = 0x11223344;
69
70 /**************************************************************************
71 * gzContainerWriter
72 **************************************************************************/
73
gzContainerWriter(const SGPath & name,const std::string & fileMagic)74 gzContainerWriter::gzContainerWriter(const SGPath& name,
75 const std::string& fileMagic) :
76 sg_gzofstream(name, ios_out | ios_binary),
77 filename(name.utf8Str())
78 {
79 /* write byte-order marker **************************************/
80 write((char*)&EndianMagic, sizeof(EndianMagic));
81
82 /* write file header ********************************************/
83 writeContainer(HeaderType, fileMagic.c_str());
84 }
85
86 /** Write the header of a single container. */
87 bool
writeContainerHeader(ContainerType Type,size_t Size)88 gzContainerWriter::writeContainerHeader(ContainerType Type, size_t Size)
89 {
90 uint32_t ui32Data;
91 uint64_t ui64Data;
92
93 SG_LOG(SG_IO, MY_SG_DEBUG, "Writing container " << Type << ", size " << Size);
94
95 // write container type
96 ui32Data = Type;
97 write((char*)&ui32Data, sizeof(ui32Data));
98 if (fail())
99 return false;
100
101 // write container payload size
102 ui64Data = Size;
103 write((char*)&ui64Data, sizeof(ui64Data));
104 return !fail();
105 }
106
107 /** Write a complete container. */
108 bool
writeContainer(ContainerType Type,const char * pData,size_t Size)109 gzContainerWriter::writeContainer(ContainerType Type, const char* pData, size_t Size)
110 {
111 // write container type and size
112 if (!writeContainerHeader(Type, Size))
113 return false;
114
115 // write container payload
116 write(pData, Size);
117 return !fail();
118 }
119
120
121 /** Save a single string in a separate container */
122 bool
writeContainer(ContainerType Type,const char * stringBuffer)123 gzContainerWriter::writeContainer(ContainerType Type, const char* stringBuffer)
124 {
125 return writeContainer(Type, stringBuffer, strlen(stringBuffer)+1);
126 }
127
128 /** Save a property tree in a separate container */
129 bool
writeContainer(ContainerType Type,SGPropertyNode * root)130 gzContainerWriter::writeContainer(ContainerType Type, SGPropertyNode* root)
131 {
132 stringstream oss;
133 writeProperties(oss, root, true);
134 return writeContainer(Type, oss.str().c_str());
135 }
136
137 /**************************************************************************
138 * gzContainerReader
139 **************************************************************************/
140
gzContainerReader(const SGPath & name,const std::string & fileMagic)141 gzContainerReader::gzContainerReader(const SGPath& name,
142 const std::string& fileMagic) :
143 sg_gzifstream(SGPath(name), ios_in | ios_binary),
144 filename(name.utf8Str())
145 {
146 bool ok = (good() && !eof());
147
148 /* check byte-order marker **************************************/
149 if (ok)
150 {
151 uint32_t EndianCheck;
152 read((char*)&EndianCheck, sizeof(EndianCheck));
153 if (eof() || !good())
154 {
155 SG_LOG(SG_IO, SG_ALERT, "Error reading file " << name);
156 ok = false;
157 }
158 else
159 if (EndianCheck != EndianMagic)
160 {
161 SG_LOG(SG_IO, SG_ALERT, "Byte-order mismatch. This file was created for another architecture: " << filename);
162 ok = false;
163 }
164 }
165
166 /* read file header *********************************************/
167 if (ok)
168 {
169 char* FileHeader = NULL;
170 size_t Size = 0;
171 ContainerType Type = -1;
172 if (!readContainer(&Type, &FileHeader, &Size))
173 {
174 SG_LOG(SG_IO, SG_ALERT, "File format not recognized. Missing file header: " << filename);
175 ok = false;
176 }
177 else
178 if ((HeaderType != Type)||(strcmp(FileHeader, fileMagic.c_str())))
179 {
180 SG_LOG(SG_IO, MY_SG_DEBUG, "Invalid header. Container type " << Type << ", Header " <<
181 ((FileHeader) ? FileHeader : "(none)"));
182 SG_LOG(SG_IO, SG_ALERT, "File not recognized. This is not a valid '" << fileMagic << "' file: " << filename);
183 ok = false;
184 }
185
186 if (FileHeader)
187 {
188 free(FileHeader);
189 FileHeader = NULL;
190 }
191 }
192
193 if (!ok)
194 {
195 setstate(badbit);
196 }
197 }
198
199 /** Read the header of a single container. */
200 bool
readContainerHeader(ContainerType * pType,size_t * pSize)201 gzContainerReader::readContainerHeader(ContainerType* pType, size_t* pSize)
202 {
203 uint32_t ui32Data;
204 uint64_t ui64Data;
205
206 *pSize = 0;
207 *pType = -1;
208
209 read((char*) &ui32Data, sizeof(ui32Data));
210 if (eof())
211 {
212 SG_LOG(SG_IO, SG_ALERT, "File corrupt? Unexpected end of file when reading container type in " << filename);
213 return false;
214 }
215 *pType = (ContainerType) ui32Data;
216
217 read((char*) &ui64Data, sizeof(ui64Data));
218 if (eof())
219 {
220 SG_LOG(SG_IO, SG_ALERT, "File corrupt? Unexpected end of file when reading container size in " << filename);
221 return false;
222 }
223 *pSize = ui64Data;
224
225 return !fail();
226 }
227
228 /** Read a single container and return its contents as a binary blob */
229 bool
readContainer(ContainerType * pType,char ** ppData,size_t * pSize)230 gzContainerReader::readContainer(ContainerType* pType, char** ppData, size_t* pSize)
231 {
232 size_t Size = 0;
233
234 *ppData = 0;
235
236 if (!readContainerHeader(pType, &Size))
237 {
238 SG_LOG(SG_IO, SG_ALERT, "Cannot load data. Invalid container header in " << filename);
239 return false;
240 }
241
242 char* pData = (char*) malloc(Size);
243 if (!pData)
244 {
245 SG_LOG(SG_IO, SG_ALERT, "Cannot load data. No more memory when reading " << filename);
246 return false;
247 }
248
249 read(pData, Size);
250 if (fail())
251 {
252 SG_LOG(SG_IO, SG_ALERT, "File corrupt? Unexpected end of file when reading " << filename);
253 free(pData);
254 pData = 0;
255 return false;
256 }
257
258 *ppData = pData;
259 *pSize = Size;
260 return true;
261 }
262