1 /** @file importdeh.cpp  DeHackEd patch reader plugin for Doomsday Engine.
2  *
3  * @authors Copyright © 2013-2014 Daniel Swanson <danij@dengine.net>
4  * @authors Copyright © 2012-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "importdeh.h"
22 
23 #include <QDir>
24 #include <QFile>
25 #include <doomsday/filesys/lumpindex.h>
26 #include <doomsday/resource/bundles.h>
27 #include <doomsday/DoomsdayApp>
28 #include <de/App>
29 #include <de/CommandLine>
30 #include <de/Block>
31 #include <de/Log>
32 #include <de/String>
33 
34 #include "dehreader.h"
35 
36 using namespace de;
37 
38 // Global handle on the engine's definition databases.
39 ded_t *ded;
40 
41 // This is the original data before it gets replaced by any patches.
42 ded_sprid_t  origSpriteNames[NUMSPRITES];
43 String origActionNames[NUMSTATES];
44 
backupData()45 static void backupData()
46 {
47     for (int i = 0; i < NUMSPRITES && i < ded->sprites.size(); i++)
48     {
49         qstrncpy(origSpriteNames[i].id, ded->sprites[i].id, DED_SPRITEID_LEN + 1);
50     }
51 
52     for (int i = 0; i < NUMSTATES && i < ded->states.size(); i++)
53     {
54         origActionNames[i] = ded->states[i].gets("action");
55     }
56 }
57 
readLump(LumpIndex const & lumpIndex,lumpnum_t lumpNum)58 static void readLump(LumpIndex const &lumpIndex, lumpnum_t lumpNum)
59 {
60     if (0 > lumpNum || lumpNum >= lumpIndex.size())
61     {
62         LOG_AS("DehRead::readLump");
63         LOG_WARNING("Invalid lump index #%i, ignoring.") << lumpNum;
64         return;
65     }
66 
67     File1 &lump = lumpIndex[lumpNum];
68     size_t len  = lump.size();
69     Block deh   = Block::fromRawData(reinterpret_cast<char const *>(lump.cache()), len);
70     /// @attention Results in a deep-copy of the lump data into the Block
71     ///            thus the cached lump can be released after this call.
72     ///
73     /// @todo Do not use a local buffer - read using QTextStream.
74     lump.unlock();
75 
76     /// @todo Custom status for contained files is not inherited from the container?
77     bool lumpIsCustom = (lump.isContained()? lump.container().hasCustom() : lump.hasCustom());
78 
79     LOG_RES_MSG("Applying DeHackEd patch lump #%i \"%s:%s\"%s")
80             << lumpNum
81             << NativePath(lump.container().composePath()).pretty()
82             << lump.name()
83             << (lumpIsCustom? " (custom)" : "");
84 
85     readDehPatch(deh, lumpIsCustom, NoInclude | IgnoreEOF);
86 }
87 
88 #if 0
89 static void readFile(String const &sourcePath, bool sourceIsCustom = true)
90 {
91     LOG_AS("DehRead::readFile");
92 
93     QFile file(sourcePath);
94     if (!file.open(QFile::ReadOnly | QFile::Text))
95     {
96         LOG_WARNING("Failed opening \"%s\" for read, aborting...") << QDir::toNativeSeparators(sourcePath);
97         return;
98     }
99 
100     LOG_RES_MSG("Applying DeHackEd patch file \"%s\"%s")
101             << NativePath(sourcePath).pretty()
102             << (sourceIsCustom? " (custom)" : "");
103 
104     readDehPatch(file.readAll(), sourceIsCustom, IgnoreEOF);
105 }
106 #endif
107 
readFile2(String const & path,bool sourceIsCustom=true)108 static void readFile2(String const &path, bool sourceIsCustom = true)
109 {
110     LOG_AS("DehRead::readFile2");
111 
112     if (File const *file = App::rootFolder().tryLocate<File const>(path))
113     {
114         LOG_RES_MSG("Applying %s%s")
115                 << file->description()
116                 << (sourceIsCustom? " (custom)" : "");
117 
118         Block deh;
119         *file >> deh;
120         readDehPatch(deh, sourceIsCustom, IgnoreEOF);
121     }
122     else
123     {
124         LOG_RES_WARNING("\"%s\" not found") << path;
125     }
126 }
127 
readPatchLumps(LumpIndex const & lumpIndex)128 static void readPatchLumps(LumpIndex const &lumpIndex)
129 {
130     bool const readAll = DENG2_APP->commandLine().check("-alldehs");
131     for (int i = lumpIndex.size() - 1; i >= 0; i--)
132     {
133         if (lumpIndex[i].name().fileNameExtension().toLower() == ".deh")
134         {
135             readLump(lumpIndex, i);
136             if (!readAll) return;
137         }
138     }
139 }
140 
readPatchFiles()141 static void readPatchFiles()
142 {
143     // Patches may be loaded as data bundles.
144     for (DataBundle const *bundle : DataBundle::loadedBundles())
145     {
146         if (bundle->format() == DataBundle::Dehacked)
147         {
148             String const bundleRoot = bundle->rootPath();
149             for (Value const *path : bundle->packageMetadata().geta("dataFiles").elements())
150             {
151                 readFile2(bundleRoot / path->asText());
152             }
153         }
154     }
155 }
156 
157 /**
158  * This will be called after the engine has loaded all definitions but before
159  * the data they contain has been initialized.
160  */
DefsHook(int,int,void * data)161 int DefsHook(int /*hook_type*/, int /*parm*/, void *data)
162 {
163     // Grab the DED definition handle supplied by the engine.
164     ded = reinterpret_cast<ded_t *>(data);
165 
166     backupData();
167 
168     // Check for DEHACKED lumps.
169     readPatchLumps(*reinterpret_cast<de::LumpIndex const *>(F_LumpIndex()));
170 
171     // Process all patch files specified with -deh options on the command line.
172     readPatchFiles();
173 
174     return true;
175 }
176 
177 /**
178  * This function is called automatically when the plugin is loaded.
179  * We let the engine know what we'd like to do.
180  */
DP_Initialize()181 DENG_ENTRYPOINT void DP_Initialize()
182 {
183     Plug_AddHook(HOOK_DEFS, DefsHook);
184 }
185 
186 /**
187  * Declares the type of the plugin so the engine knows how to treat it. Called
188  * automatically when the plugin is loaded.
189  */
deng_LibraryType()190 DENG_ENTRYPOINT char const *deng_LibraryType()
191 {
192     return "deng-plugin/generic";
193 }
194 
195 #if defined (DENG_STATIC_LINK)
196 
staticlib_importdeh_symbol(char const * name)197 DENG_EXTERN_C void *staticlib_importdeh_symbol(char const *name)
198 {
199     DENG_SYMBOL_PTR(name, deng_LibraryType)
200     DENG_SYMBOL_PTR(name, DP_Initialize);
201     qWarning() << name << "not found in importdeh";
202     return nullptr;
203 }
204 
205 #else
206 
207 DENG_DECLARE_API(Base);
208 DENG_DECLARE_API(Con);
209 DENG_DECLARE_API(Def);
210 DENG_DECLARE_API(F);
211 
212 DENG_API_EXCHANGE(
213     DENG_GET_API(DE_API_BASE, Base);
214     DENG_GET_API(DE_API_CONSOLE, Con);
215     DENG_GET_API(DE_API_DEFINITIONS, Def);
216     DENG_GET_API(DE_API_FILE_SYSTEM, F);
217 )
218 
219 #endif
220