1 /*
2 
3 Copyright (C) 2015-2018 Night Dive Studios, LLC.
4 
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9 
10 This program 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
13 GNU 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, see <http://www.gnu.org/licenses/>.
17 
18 */
19 //    RESBUILD.C    Resource-file building routines
20 //    Rex E. Bradford (REX)
21 /*
22  * $Header: n:/project/lib/src/res/rcs/resbuild.c 1.10 1994/06/16 11:06:30 rex
23  * Exp $
24  * $Log: resbuild.c $
25  * Revision 1.10  1994/06/16  11:06:30  rex
26  * Got rid of RDF_NODROP flag
27  *
28  * Revision 1.9  1994/02/17  11:25:32  rex
29  * Moved some stuff out to resmake.c and resfile.c
30  *
31  */
32 
33 #include <assert.h>
34 #include <string.h>
35 #if defined(_MSC_VER)
36 #include <windows.h>  // SetFilePointer / SetEndOfFile
37 #else
38 #include <unistd.h>   // ftruncate
39 #endif
40 
41 #include "lg.h"
42 #include "lzw.h"
43 #include "res.h"
44 #include "res_.h"
45 
46 #include <stdlib.h>
47 
48 // make sure comment ends with one, so can type a file
49 #define CTRL_Z 26
50 
51 bool ResEraseIfInFile(Id id);
52 
53 //  Internal prototypes
54 static void ResCopyBytes(FILE *fd, int32_t writePos, int32_t readPos, int32_t size);
55 
56 //  -------------------------------------------------------
57 //
58 //  ResSetComment() sets comment in res header.
59 //  -------------------------------------------------------
60 //  For Mac version:  Does nothing.  May go back later and add comment via
61 // the
62 // desktop database, maybe.
63 
ResSetComment(int32_t filenum,char * comment)64 void ResSetComment(int32_t filenum, char *comment) {
65 
66     ResFileHeader *phead;
67     if (resFile[filenum].pedit == NULL) {
68         WARN("%s: file %d not open for writing", __FUNCTION__, filenum);
69         return;
70     }
71     TRACE("%s: setting comment for filenum %d to: %s", __FUNCTION__, filenum, comment);
72 
73 
74     phead = &resFile[filenum].pedit->hdr;
75     memset(phead->comment, 0, sizeof(phead->comment));
76     strncpy(phead->comment, comment, sizeof(phead->comment) - 2);
77     phead->comment[strlen(phead->comment)] = CTRL_Z;
78 }
79 
80 //  -------------------------------------------------------
81 //
82 //  ResWrite() writes a resource to an open resource file.
83 //  This routine assumes that the file position is already set to
84 //  the current data position.
85 //      Returns the total number of bytes written out, or -1 if there
86 //      was a writing error.
87 //
88 //    id = id to write
89 //  -------------------------------------------------------
90 #define EXTRA 250
91 
ResWrite(Id id)92 int32_t ResWrite(Id id) {
93     static uint8_t pad[] = {0, 0, 0, 0, 0, 0, 0, 0};
94     ResDesc *prd;
95     ResDesc2 *prd2;
96     ResFile *prf;
97     ResDirEntry *pDirEntry;
98     uint8_t *p;
99     size_t size, sizeTable;
100     void *pcompbuff;
101     int32_t compsize, padBytes;
102 
103     TRACE("%s: writing", __FUNCTION__);
104     if (!ResCheckId(id))
105         return -1;
106 
107     prd = RESDESC(id);
108     prf = &resFile[prd->filenum];
109 
110     if (prf->pedit == NULL) {
111         ERROR("%s: file %i not open for writing!", __FUNCTION__, prd->filenum);
112         return -1;
113     }
114     //});
115 
116     // Check if item already in directory, if so erase it
117     ResEraseIfInFile(id);
118 
119     // If directory full, grow it
120     if (prf->pedit->pdir->numEntries == prf->pedit->numAllocDir) {
121         TRACE("%s: growing directory of filenum %d", __FUNCTION__, prd->filenum);
122 
123         prf->pedit->numAllocDir += DEFAULT_RES_GROWDIRENTRIES;
124         prf->pedit->pdir =
125             realloc(prf->pedit->pdir, sizeof(ResDirHeader) + (sizeof(ResDirEntry) * prf->pedit->numAllocDir));
126     }
127 
128     // Set resource's file offset
129     prd->offset = RES_OFFSET_REAL2DESC(prf->pedit->currDataOffset);
130 
131     // See if it needs encoding.
132     assert(prd->format != NULL);
133     size = prd->msize;
134     if (prd->format->encoder != NULL) {
135 	p = prd->format->encoder(prd->ptr, &size, prd->format->data);
136     } else {
137 	p = prd->ptr;
138     }
139     prd->fsize = size;
140 
141     // Fill in directory entry
142     pDirEntry = ((ResDirEntry *)(prf->pedit->pdir + 1)) + prf->pedit->pdir->numEntries;
143 
144     pDirEntry->id = id;
145     prd2 = RESDESC2(id);
146     pDirEntry->flags = prd2->flags;
147     pDirEntry->type = prd2->type;
148     pDirEntry->size = size;
149 
150     TRACE("%s: writing $%x\n", __FUNCTION__, id);
151 
152     // If compound, write out reftable without compression
153     fseek(prf->fd, prf->pedit->currDataOffset, SEEK_SET);
154     sizeTable = 0;
155 
156     // Body (post compound res header) if compound, else main pointer.
157     uint8_t *body = p;
158     // FIXME will need rework for compound refs if we reinstate, e.g. if we want
159     // to integrate a resource/level editor.
160     if (prd2->flags & RDF_COMPOUND) {
161         sizeTable = REFTABLESIZE(((RefTable *)p)->numRefs);
162         fwrite(p, sizeTable, 1, prf->fd);
163         body = p + sizeTable;
164         size -= sizeTable;
165     }
166 
167     // If compression, try it (may not work out)
168     if (pDirEntry->flags & RDF_LZW) {
169         pcompbuff = malloc(size + EXTRA);
170         compsize = LzwCompressBuff2Buff(p, size, pcompbuff, size);
171         if (compsize < 0) {
172             pDirEntry->flags &= ~RDF_LZW;
173         } else {
174             pDirEntry->csize = sizeTable + compsize;
175             fwrite(pcompbuff, compsize, 1, prf->fd);
176         }
177         free(pcompbuff);
178     }
179 
180     // If no compress (or failed to compress well), just write out
181     if (!(pDirEntry->flags & RDF_LZW)) {
182         pDirEntry->csize = size;
183         fwrite(body, size, 1, prf->fd);
184     }
185 
186     // Pad to align on data boundary
187     padBytes = RES_OFFSET_PADBYTES(pDirEntry->csize);
188     if (padBytes)
189         fwrite(pad, padBytes, 1, prf->fd);
190 
191     // FIXME Error handling
192     //  if (ftell(prf->fd) & 3)
193     //    Warning(("ResWrite: misaligned writing!\n"));
194 
195     // If we encoded it, free the encode buffer.
196     if (prd->format->encoder) {
197 	free(p);
198     }
199     // Advance dir num entries, current data offset
200     prf->pedit->pdir->numEntries++;
201     prf->pedit->currDataOffset = RES_OFFSET_ALIGN(prf->pedit->currDataOffset + pDirEntry->csize);
202 
203     return 0;
204 }
205 
206 //  -------------------------------------------------------------
207 //
208 //  ResKill() not only deletes a resource from memory, it removes it
209 //  from the file too.
210 //  -------------------------------------------------------------
211 //  For Mac version:  Use Resource Manager to remove resource from file.  Have
212 //  to do
213 //  our own thing (instead of calling ResDelete()) because RmveResource turns
214 //  the resource handle into a normal handle.
215 
ResKill(Id id)216 void ResKill(Id id) {
217     ResDesc *prd = RESDESC(id);
218 
219     if (prd->ptr) {
220         if (prd->lock == 0)
221             ResRemoveFromLRU(prd);
222     }
223     memset(prd, 0, sizeof(ResDesc));
224 
225     // Check for valid id
226     if (!ResCheckId(id))
227         return;
228     TRACE("%s: killing $%x\n", __FUNCTION__, id);
229 
230     // Delete it
231     ResDelete(id);
232 
233     // Make sure file is writeable
234     prd = RESDESC(id);
235     if (resFile[prd->filenum].pedit == NULL) {
236         WARN("%s: file %d not open for writing", __FUNCTION__, prd->filenum);
237         return;
238     }
239 
240     // If so, erase it
241     ResEraseIfInFile(id);
242 }
243 
244 //  -------------------------------------------------------------
245 //
246 //  ResPack() removes holes from a resource file.
247 //
248 //    filenum = resource filenum (must already be open for
249 //                create/edit)
250 //
251 //  Returns: # bytes reclaimed
252 
ResPack(int32_t filenum)253 int32_t ResPack(int32_t filenum) {
254     ResFile *prf;
255     ResDirEntry *pDirEntry;
256     int32_t numReclaimed, sizeReclaimed;
257     int32_t dataRead, dataWrite;
258     int32_t i;
259     ResDirEntry *peWrite;
260 
261     // Check for errors
262     prf = &resFile[filenum];
263     if (prf->pedit == NULL) {
264         ERROR("%s: filenum %d not open for editing", __FUNCTION__, filenum);
265         return (0);
266     }
267 
268     // Set up
269     sizeReclaimed = numReclaimed = 0;
270     dataRead = dataWrite = prf->pedit->pdir->dataOffset;
271 
272     // Scan thru directory, copying over all empty entries
273     pDirEntry = (ResDirEntry *)(prf->pedit->pdir + 1);
274     for (i = 0; i < prf->pedit->pdir->numEntries; i++) {
275         if (pDirEntry->id == 0) {
276             numReclaimed++;
277             sizeReclaimed += pDirEntry->csize;
278         } else {
279             if (gResDesc[pDirEntry->id].offset > RES_OFFSET_PENDING)
280                 gResDesc[pDirEntry->id].offset = RES_OFFSET_REAL2DESC(dataWrite);
281             if (dataRead != dataWrite)
282                 ResCopyBytes(prf->fd, dataWrite, dataRead, pDirEntry->csize);
283             dataWrite = RES_OFFSET_ALIGN(dataWrite + pDirEntry->csize);
284         }
285         dataRead = RES_OFFSET_ALIGN(dataRead + pDirEntry->csize);
286         pDirEntry++;
287     }
288 
289     // Now pack directory itself
290     pDirEntry = (ResDirEntry *)(prf->pedit->pdir + 1);
291     peWrite = pDirEntry;
292     for (i = 0; i < prf->pedit->pdir->numEntries; i++) {
293         if (pDirEntry->id) {
294             if (pDirEntry != peWrite)
295                 *peWrite = *pDirEntry;
296             peWrite++;
297         }
298         pDirEntry++;
299     }
300     prf->pedit->pdir->numEntries -= numReclaimed;
301 
302     // Set new current data offset
303     prf->pedit->currDataOffset = dataWrite;
304     fseek(prf->fd, dataWrite, SEEK_SET);
305     prf->pedit->flags &= ~RFF_NEEDSPACK;
306 
307     // Truncate file to just header & data (will be extended later when
308     // write directory on closing)
309 
310     // FIXME Non-portable
311 #ifndef _MSC_VER
312     ftruncate(fileno(prf->fd), dataWrite);
313 #else // So much for POSIX.
314     SetFilePointer(fileno(prf->fd), dataWrite, NULL, FILE_BEGIN);
315     SetEndOfFile(fileno(prf->fd));
316 #endif
317 
318     // Return # bytes reclaimed
319     TRACE("%s: reclaimed %d bytes", __FUNCTION__, sizeReclaimed);
320 
321     return (sizeReclaimed);
322 }
323 
324 #define SIZE_RESCOPY 32768
325 
ResCopyBytes(FILE * fd,int32_t writePos,int32_t readPos,int32_t size)326 static void ResCopyBytes(FILE *fd, int32_t writePos, int32_t readPos, int32_t size) {
327     int32_t sizeCopy;
328     uint8_t *buff;
329 
330     buff = malloc(SIZE_RESCOPY);
331 
332     while (size > 0) {
333         sizeCopy = lg_min(SIZE_RESCOPY, size);
334         fseek(fd, readPos, SEEK_SET);
335         fread(buff, sizeCopy, 1, fd);
336         fseek(fd, writePos, SEEK_SET);
337         fwrite(buff, sizeCopy, 1, fd);
338         readPos += sizeCopy;
339         writePos += sizeCopy;
340         size -= sizeCopy;
341     }
342 
343     free(buff);
344 }
345 
346 //  --------------------------------------------------------
347 //    INTERNAL ROUTINES
348 //  --------------------------------------------------------
349 //
350 //  ResEraseIfInFile() erases a resource if it's in a file's directory.
351 //
352 //    id = id of item
353 //
354 //  Returns: TRUE if found & erased, FALSE otherwise
355 
ResEraseIfInFile(Id id)356 bool ResEraseIfInFile(Id id) {
357     ResDesc *prd;
358     ResFile *prf;
359     ResDirEntry *pDirEntry;
360     int32_t i;
361 
362     prd = RESDESC(id);
363     prf = &resFile[prd->filenum];
364     pDirEntry = (ResDirEntry *)(prf->pedit->pdir + 1);
365 
366     for (i = 0; i < prf->pedit->pdir->numEntries; i++) {
367         if (id == pDirEntry->id) {
368             TRACE("%s: $%x being erased\n", __FUNCTION__, id);
369             pDirEntry->id = 0;
370             prf->pedit->flags |= RFF_NEEDSPACK;
371             if (prf->pedit->flags & RFF_AUTOPACK)
372                 ResPack(prd->filenum);
373             return true;
374         }
375         pDirEntry++;
376     }
377 
378     return false;
379 }
380