]> git.xonotic.org Git - xonotic/netradiant.git/blob - plugins/archivezip/archive.cpp
reformat code! now the code is only ugly on the *inside*
[xonotic/netradiant.git] / plugins / archivezip / archive.cpp
1 /*
2    Copyright (C) 2001-2006, William Joseph.
3    All Rights Reserved.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #include "idatastream.h"
23 #include "cmdlib.h"
24 #include "bytestreamutils.h"
25
26 #include "modulesystem.h"
27 #include "iarchive.h"
28
29 #include <algorithm>
30 #include "stream/filestream.h"
31 #include "container/array.h"
32 #include "archivelib.h"
33 #include "zlibstream.h"
34
35 class DeflatedArchiveFile : public ArchiveFile {
36     CopiedString m_name;
37     FileInputStream m_istream;
38     SubFileInputStream m_substream;
39     DeflatedInputStream m_zipstream;
40     FileInputStream::size_type m_size;
41 public:
42     typedef FileInputStream::size_type size_type;
43     typedef FileInputStream::position_type position_type;
44
45     DeflatedArchiveFile(const char *name, const char *archiveName, position_type position, size_type stream_size,
46                         size_type file_size)
47             : m_name(name), m_istream(archiveName), m_substream(m_istream, position, stream_size),
48               m_zipstream(m_substream), m_size(file_size)
49     {
50     }
51
52     void release()
53     {
54         delete this;
55     }
56
57     size_type size() const
58     {
59         return m_size;
60     }
61
62     const char *getName() const
63     {
64         return m_name.c_str();
65     }
66
67     InputStream &getInputStream()
68     {
69         return m_zipstream;
70     }
71 };
72
73 class DeflatedArchiveTextFile : public ArchiveTextFile {
74     CopiedString m_name;
75     FileInputStream m_istream;
76     SubFileInputStream m_substream;
77     DeflatedInputStream m_zipstream;
78     BinaryToTextInputStream<DeflatedInputStream> m_textStream;
79 public:
80     typedef FileInputStream::size_type size_type;
81     typedef FileInputStream::position_type position_type;
82
83     DeflatedArchiveTextFile(const char *name, const char *archiveName, position_type position, size_type stream_size)
84             : m_name(name), m_istream(archiveName), m_substream(m_istream, position, stream_size),
85               m_zipstream(m_substream), m_textStream(m_zipstream)
86     {
87     }
88
89     void release()
90     {
91         delete this;
92     }
93
94     TextInputStream &getInputStream()
95     {
96         return m_textStream;
97     }
98 };
99
100 #include "pkzip.h"
101
102 #include <map>
103 #include "string/string.h"
104 #include "fs_filesystem.h"
105
106
107 class ZipArchive : public Archive {
108     class ZipRecord {
109     public:
110         enum ECompressionMode {
111             eStored,
112             eDeflated,
113         };
114
115         ZipRecord(unsigned int position, unsigned int compressed_size, unsigned int uncompressed_size,
116                   ECompressionMode mode)
117                 : m_position(position), m_stream_size(compressed_size), m_file_size(uncompressed_size), m_mode(mode)
118         {
119         }
120
121         unsigned int m_position;
122         unsigned int m_stream_size;
123         unsigned int m_file_size;
124         ECompressionMode m_mode;
125     };
126
127     typedef GenericFileSystem<ZipRecord> ZipFileSystem;
128     ZipFileSystem m_filesystem;
129     CopiedString m_name;
130     FileInputStream m_istream;
131
132     bool read_record()
133     {
134         zip_magic magic;
135         istream_read_zip_magic(m_istream, magic);
136         if (!(magic == zip_root_dirent_magic)) {
137             return false;
138         }
139         zip_version version_encoder;
140         istream_read_zip_version(m_istream, version_encoder);
141         zip_version version_extract;
142         istream_read_zip_version(m_istream, version_extract);
143         //unsigned short flags =
144         istream_read_int16_le(m_istream);
145         unsigned short compression_mode = istream_read_int16_le(m_istream);
146         if (compression_mode != Z_DEFLATED && compression_mode != 0) {
147             return false;
148         }
149         zip_dostime dostime;
150         istream_read_zip_dostime(m_istream, dostime);
151         //unsigned int crc32 =
152         istream_read_int32_le(m_istream);
153         unsigned int compressed_size = istream_read_uint32_le(m_istream);
154         unsigned int uncompressed_size = istream_read_uint32_le(m_istream);
155         unsigned int namelength = istream_read_uint16_le(m_istream);
156         unsigned short extras = istream_read_uint16_le(m_istream);
157         unsigned short comment = istream_read_uint16_le(m_istream);
158         //unsigned short diskstart =
159         istream_read_int16_le(m_istream);
160         //unsigned short filetype =
161         istream_read_int16_le(m_istream);
162         //unsigned int filemode =
163         istream_read_int32_le(m_istream);
164         unsigned int position = istream_read_int32_le(m_istream);
165
166         Array<char> filename(namelength + 1);
167         m_istream.read(reinterpret_cast<FileInputStream::byte_type *>( filename.data()), namelength);
168         filename[namelength] = '\0';
169
170         m_istream.seek(extras + comment, FileInputStream::cur);
171
172         if (path_is_directory(filename.data())) {
173             m_filesystem[filename.data()] = 0;
174         } else {
175             ZipFileSystem::entry_type &file = m_filesystem[filename.data()];
176             if (!file.is_directory()) {
177                 globalOutputStream() << "Warning: zip archive " << makeQuoted(m_name.c_str())
178                                      << " contains duplicated file: " << makeQuoted(filename.data()) << "\n";
179             } else {
180                 file = new ZipRecord(position, compressed_size, uncompressed_size,
181                                      (compression_mode == Z_DEFLATED) ? ZipRecord::eDeflated : ZipRecord::eStored);
182             }
183         }
184
185         return true;
186     }
187
188     bool read_pkzip()
189     {
190         SeekableStream::position_type pos = pkzip_find_disk_trailer(m_istream);
191         if (pos != 0) {
192             zip_disk_trailer disk_trailer;
193             m_istream.seek(pos);
194             istream_read_zip_disk_trailer(m_istream, disk_trailer);
195             if (!(disk_trailer.z_magic == zip_disk_trailer_magic)) {
196                 return false;
197             }
198
199             m_istream.seek(disk_trailer.z_rootseek);
200             for (unsigned int i = 0; i < disk_trailer.z_entries; ++i) {
201                 if (!read_record()) {
202                     return false;
203                 }
204             }
205             return true;
206         }
207         return false;
208     }
209
210 public:
211     ZipArchive(const char *name)
212             : m_name(name), m_istream(name)
213     {
214         if (!m_istream.failed()) {
215             if (!read_pkzip()) {
216                 globalErrorStream() << "ERROR: invalid zip-file " << makeQuoted(name) << '\n';
217             }
218         }
219     }
220
221     ~ZipArchive()
222     {
223         for (ZipFileSystem::iterator i = m_filesystem.begin(); i != m_filesystem.end(); ++i) {
224             delete i->second.file();
225         }
226     }
227
228     bool failed()
229     {
230         return m_istream.failed();
231     }
232
233     void release()
234     {
235         delete this;
236     }
237
238     ArchiveFile *openFile(const char *name)
239     {
240         ZipFileSystem::iterator i = m_filesystem.find(name);
241         if (i != m_filesystem.end() && !i->second.is_directory()) {
242             ZipRecord *file = i->second.file();
243
244             m_istream.seek(file->m_position);
245             zip_file_header file_header;
246             istream_read_zip_file_header(m_istream, file_header);
247             if (file_header.z_magic != zip_file_header_magic) {
248                 globalErrorStream() << "error reading zip file " << makeQuoted(m_name.c_str());
249                 return 0;
250             }
251
252             switch (file->m_mode) {
253                 case ZipRecord::eStored:
254                     return StoredArchiveFile::create(name, m_name.c_str(), m_istream.tell(), file->m_stream_size,
255                                                      file->m_file_size);
256                 case ZipRecord::eDeflated:
257                     return new DeflatedArchiveFile(name, m_name.c_str(), m_istream.tell(), file->m_stream_size,
258                                                    file->m_file_size);
259             }
260         }
261         return 0;
262     }
263
264     ArchiveTextFile *openTextFile(const char *name)
265     {
266         ZipFileSystem::iterator i = m_filesystem.find(name);
267         if (i != m_filesystem.end() && !i->second.is_directory()) {
268             ZipRecord *file = i->second.file();
269
270             m_istream.seek(file->m_position);
271             zip_file_header file_header;
272             istream_read_zip_file_header(m_istream, file_header);
273             if (file_header.z_magic != zip_file_header_magic) {
274                 globalErrorStream() << "error reading zip file " << makeQuoted(m_name.c_str());
275                 return 0;
276             }
277
278             switch (file->m_mode) {
279                 case ZipRecord::eStored:
280                     return StoredArchiveTextFile::create(name, m_name.c_str(), m_istream.tell(), file->m_stream_size);
281                 case ZipRecord::eDeflated:
282                     return new DeflatedArchiveTextFile(name, m_name.c_str(), m_istream.tell(), file->m_stream_size);
283             }
284         }
285         return 0;
286     }
287
288     bool containsFile(const char *name)
289     {
290         ZipFileSystem::iterator i = m_filesystem.find(name);
291         return i != m_filesystem.end() && !i->second.is_directory();
292     }
293
294     void forEachFile(VisitorFunc visitor, const char *root)
295     {
296         m_filesystem.traverse(visitor, root);
297     }
298 };
299
300 Archive *OpenArchive(const char *name)
301 {
302     return new ZipArchive(name);
303 }
304
305 #if 0
306
307 class TestZip
308 {
309 class TestVisitor : public Archive::IVisitor
310 {
311 public:
312 void visit( const char* name ){
313     int bleh = 0;
314 }
315 };
316 public:
317 TestZip(){
318     testzip( "c:/quake3/baseq3/mapmedia.pk3", "textures/radiant/notex.tga" );
319 }
320
321 void testzip( const char* name, const char* filename ){
322     Archive* archive = OpenArchive( name );
323     ArchiveFile* file = archive->openFile( filename );
324     if ( file != 0 ) {
325         unsigned char buffer[4096];
326         std::size_t count = file->getInputStream().read( (InputStream::byte_type*)buffer, 4096 );
327         file->release();
328     }
329     TestVisitor visitor;
330     archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 0 ), "" );
331     archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "" );
332     archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFiles, 1 ), "" );
333     archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eDirectories, 1 ), "" );
334     archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "textures" );
335     archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "textures/" );
336     archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 2 ), "" );
337     archive->release();
338 }
339 };
340
341 TestZip g_TestZip;
342
343 #endif