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