added buffering to minimise GtkTextBuffer insert calls
[xonotic/netradiant.git] / plugins / archivepak / 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 "archive.h"
23
24 #include "idatastream.h"
25 #include "cmdlib.h"
26 #include "bytestreamutils.h"
27 #include <algorithm>
28 #include "stream/filestream.h"
29
30 #include "iarchive.h"
31
32 #include "archivelib.h"
33
34
35 #include "plugin.h"
36
37 #include <map>
38 #include "string/string.h"
39 #include "fs_filesystem.h"
40
41 inline void buffer_findreplace(char* buffer, char find, char replace)
42 {
43   while(*buffer != '\0')
44   {
45     if(*buffer == find)
46       *buffer = replace;
47     ++buffer;
48   }
49 }
50
51 #include "pak.h"
52
53 class PakArchive : public Archive
54 {
55   class PakRecord
56   {
57   public:
58     PakRecord(unsigned int position, unsigned int stream_size)
59       : m_position(position), m_stream_size(stream_size)
60     {
61     }
62     unsigned int m_position;
63     unsigned int m_stream_size;
64   };
65   typedef GenericFileSystem<PakRecord> PakFileSystem;
66   PakFileSystem m_filesystem;
67   FileInputStream m_pakfile;
68   CopiedString m_name;
69
70 public:
71   
72   PakArchive(const char* name)
73     : m_pakfile(name), m_name(name)
74   {
75     if(!m_pakfile.failed())
76     {
77       pakheader_t header;
78
79       m_pakfile.read(reinterpret_cast<FileInputStream::byte_type*>(header.magic), 4);
80       header.diroffset = istream_read_uint32_le(m_pakfile);
81       header.dirsize = istream_read_uint32_le(m_pakfile);
82
83       if(strncmp (header.magic, "PACK", 4) == 0)
84       {
85         m_pakfile.seek(header.diroffset);
86
87         for(unsigned int i = 0; i < header.dirsize; i += sizeof(pakentry_t))
88         {
89           pakentry_t entry;
90
91           m_pakfile.read(reinterpret_cast<FileInputStream::byte_type*>(entry.filename), 0x38);
92           entry.offset = istream_read_uint32_le(m_pakfile);
93           entry.size = istream_read_uint32_le(m_pakfile);
94
95           buffer_findreplace(entry.filename, '\\', '/');
96
97           PakFileSystem::entry_type& file = m_filesystem[entry.filename];
98           if(!file.is_directory())
99           {
100             globalOutputStream() << "Warning: pak archive " << makeQuoted(m_name.c_str()) << " contains duplicated file: " << makeQuoted(entry.filename) << "\n";
101           }
102           else
103           {
104             file = new PakRecord(entry.offset, entry.size);
105           }
106         }
107       }
108     }
109   }
110
111   ~PakArchive()
112   {
113     for(PakFileSystem::iterator i = m_filesystem.begin(); i != m_filesystem.end(); ++i)
114       delete i->second.file();
115   }
116   
117   void release()
118   {
119     delete this;
120   }
121   ArchiveFile* openFile(const char* name)
122   {
123     PakFileSystem::iterator i = m_filesystem.find(name);
124     if(i != m_filesystem.end() && !i->second.is_directory())
125     {
126       PakRecord* file = i->second.file();
127       return StoredArchiveFile::create(name, m_name.c_str(), file->m_position, file->m_stream_size, file->m_stream_size);
128     }
129     return 0;
130   }
131   virtual ArchiveTextFile* openTextFile(const char* name)
132   {
133     PakFileSystem::iterator i = m_filesystem.find(name);
134     if(i != m_filesystem.end() && !i->second.is_directory())
135     {
136       PakRecord* file = i->second.file();
137       return StoredArchiveTextFile::create(name, m_name.c_str(), file->m_position, file->m_stream_size);
138     }
139     return 0;
140   }
141   bool containsFile(const char* name)
142   {
143     PakFileSystem::iterator i = m_filesystem.find(name);
144     return i != m_filesystem.end() && !i->second.is_directory();
145   }
146   void forEachFile(VisitorFunc visitor, const char* root)
147   {
148     m_filesystem.traverse(visitor, root);
149   }
150 };
151
152
153 Archive* OpenArchive(const char* name)
154 {
155   return new PakArchive(name);
156 }
157
158 #if 0
159
160 class TestArchive
161 {
162 public:
163   TestArchive()
164   {
165     Archive* archive = OpenArchive("c:/quake3/baseq3/pak0.pak");
166     ArchiveFile* file = archive->openFile("gfx/palette.lmp");
167     if(file != 0)
168     {
169       char buffer[1024];
170       file->getInputStream().read((InputStream::byte_type*)buffer, 1024);
171       file->release();
172     }
173     archive->release();
174   }
175 };
176
177 TestArchive g_test;
178
179 #endif
180
181 #if 0
182
183 class TestArchive
184 {
185   class TestVisitor : public Archive::IVisitor
186   {
187   public:
188     void visit(const char* name)
189     {
190       int bleh = 0;
191     }
192   };
193 public:
194   TestArchive()
195   {
196     {
197     Archive* archive = OpenArchive("");
198     archive->release();
199     }
200     {
201     Archive* archive = OpenArchive("NONEXISTANTFILE");
202     archive->release();
203     }
204     {
205     Archive* archive = OpenArchive("c:/quake/id1/pak0.pak");
206     ArchiveFile* file = archive->openFile("gfx/palette.lmp");
207     if(file != 0)
208     {
209       char buffer[1024];
210       file->getInputStream().read((InputStream::byte_type*)buffer, 1024);
211       file->release();
212     }
213     TestVisitor visitor;
214     archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFilesAndDirectories, 0), "");
215     archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFiles, 0), "progs/");
216     archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFiles, 0), "maps/");
217     archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFiles, 1), "sound/ambience/");
218     archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFilesAndDirectories, 1), "sound/");
219     archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eDirectories, 1), "sound/");
220     archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFilesAndDirectories, 2), "sound/");
221     archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFilesAndDirectories, 2), "");
222     archive->release();
223     }
224   }
225 };
226
227 TestArchive g_test;
228
229 #endif