1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 
5 #include "util.h"
6 #include "log.h"
7 #include "filename.h"
8 
9 #include <1541img/filedata.h>
10 #include <1541img/hostfilereader.h>
11 #include <1541img/d64.h>
12 #include <1541img/d64reader.h>
13 #include <1541img/cbmdosvfs.h>
14 #include <1541img/cbmdosfile.h>
15 #include <1541img/cbmdosvfsreader.h>
16 
17 #include <1541img/zcfileset.h>
18 
19 struct ZcFileSet
20 {
21     ZcType type;
22     int count;
23     char *name;
24     FileData *files[];
25 };
26 
ZcFileSet_create(ZcType type,const char * name)27 SOEXPORT ZcFileSet *ZcFileSet_create(ZcType type, const char *name)
28 {
29     int count;
30     switch (type)
31     {
32         case ZT_4PACK:
33             count = 4;
34             break;
35         case ZT_5PACK:
36             count = 5;
37             break;
38         case ZT_6PACK:
39             count = 6;
40             break;
41         default:
42             logmsg(L_ERROR, "ZcFileSet: invalid type.");
43             return 0;
44     }
45 
46     ZcFileSet *self = xmalloc(sizeof *self + count * sizeof *self->files);
47     self->type = type;
48     self->count = count;
49     self->name = copystr(name);
50     for (int i = 0; i < count; ++i) self->files[i] = FileData_create();
51     return self;
52 }
53 
fromFileData(const char * name,FileData ** files,int logerrors)54 static ZcFileSet *fromFileData(
55         const char *name, FileData **files, int logerrors)
56 {
57     ZcFileSet *self = 0;
58 
59     if (files[0] && files[1] && files[2] && files[3])
60     {
61         if (files[4])
62         {
63             logmsg(L_INFO, "ZcFileSet: 5-file disk-packed zipcode "
64                     "read successfully.");
65             self = xmalloc(sizeof *self + 5 * sizeof *self->files);
66             self->type = ZT_5PACK;
67             self->count = 5;
68             memcpy(self->files, files, 5 * sizeof *self->files);
69         }
70         else
71         {
72             logmsg(L_INFO, "ZcFileSet: 4-file disk-packed zipcode "
73                     "read successfully.");
74             self = xmalloc(sizeof *self + 4 * sizeof *self->files);
75             self->type = ZT_4PACK;
76             self->count = 4;
77             memcpy(self->files, files, 4 * sizeof *self->files);
78         }
79         self->name = copystr(name);
80     }
81     else
82     {
83         if (logerrors)
84         {
85             logmsg(L_ERROR, "ZcFileSet: reading failed (missing or "
86                     "unreadable files).");
87         }
88         for (int i = 0; i < 5; ++i) FileData_destroy(files[i]);
89     }
90 
91     return self;
92 }
93 
fromD64(const char * filename)94 static ZcFileSet *fromD64(const char *filename)
95 {
96     FILE *d64file = fopen_internal(filename, "rb");
97     if (!d64file)
98     {
99         logfmt(L_ERROR,
100 		"ZcFileSet: error opening `%s' for reading.", filename);
101         return 0;
102     }
103     FileData *data = readHostFile(d64file);
104     if (!data)
105     {
106 	fclose(d64file);
107 	logfmt(L_ERROR, "ZcFileSet: error reading from `%s'.", filename);
108 	return 0;
109     }
110     fclose(d64file);
111     ZcFileSet *self = ZcFileSet_fromFileData(data);
112     FileData_destroy(data);
113     return self;
114 }
115 
fromVfsInternal(const CbmdosVfs * vfs,int logerrors)116 static ZcFileSet *fromVfsInternal(const CbmdosVfs *vfs, int logerrors)
117 {
118     logfmt(L_INFO, "ZcFileSet: checking disk `%s,%s'", CbmdosVfs_name(vfs, 0),
119             CbmdosVfs_id(vfs, 0));
120 
121     const char *compare = 0;
122     FileData *files[5] = { 0 };
123     for (unsigned pos = 0; pos < CbmdosVfs_fileCount(vfs); ++pos)
124     {
125         const CbmdosFile *dosfile = CbmdosVfs_rfile(vfs, pos);
126         if (CbmdosFile_type(dosfile) != CFT_PRG) continue;
127         uint8_t namelen;
128         const char *name = CbmdosFile_name(dosfile, &namelen);
129         if (namelen < 3) continue;
130         if (name[1] != '!' || name[2] == '!') continue;
131         if (name[0] < '1' || name[0] > '5') continue;
132         if (compare)
133         {
134             if (strcmp(name+2, compare)) continue;
135         }
136         else compare = name+2;
137         int partidx = name[0] - '1';
138         if (files[partidx])
139         {
140             logfmt(logerrors ? L_WARNING : L_INFO,
141                     "ZcFileSet: skipping duplicate `%s.PRG'", name);
142             continue;
143         }
144         files[partidx] = FileData_clone(CbmdosFile_rdata(dosfile));
145         logfmt(L_INFO, "ZcFileSet: found `%s.PRG' (%lu bytes)", name,
146                 (unsigned long)FileData_size(files[partidx]));
147     }
148 
149     return fromFileData(compare, files, logerrors);
150 }
151 
ZcFileSet_fromVfs(const CbmdosVfs * vfs)152 SOEXPORT ZcFileSet *ZcFileSet_fromVfs(const CbmdosVfs *vfs)
153 {
154     return fromVfsInternal(vfs, 1);
155 }
156 
ZcFileSet_tryFromVfs(const CbmdosVfs * vfs)157 SOEXPORT ZcFileSet *ZcFileSet_tryFromVfs(const CbmdosVfs *vfs)
158 {
159     return fromVfsInternal(vfs, 0);
160 }
161 
fromFileDataInternal(const FileData * file,int logerrors)162 static ZcFileSet *fromFileDataInternal(const FileData *file, int logerrors)
163 {
164     D64 *d64 = readD64FromFileData(file);
165     if (!d64) return 0;
166     CbmdosVfs *vfs = CbmdosVfs_create();
167     int rc = readCbmdosVfs(vfs, d64, 0);
168     D64_destroy(d64);
169     if (rc < 0)
170     {
171         CbmdosVfs_destroy(vfs);
172         return 0;
173     }
174     ZcFileSet *self = fromVfsInternal(vfs, logerrors);
175     CbmdosVfs_destroy(vfs);
176     return self;
177 }
178 
ZcFileSet_fromFileData(const FileData * file)179 SOEXPORT ZcFileSet *ZcFileSet_fromFileData(const FileData *file)
180 {
181     return fromFileDataInternal(file, 1);
182 }
183 
ZcFileSet_tryFromFileData(const FileData * file)184 SOEXPORT ZcFileSet *ZcFileSet_tryFromFileData(const FileData *file)
185 {
186     return fromFileDataInternal(file, 0);
187 }
188 
ZcFileSet_fromFile(const char * filename)189 SOEXPORT ZcFileSet *ZcFileSet_fromFile(const char *filename)
190 {
191     ZcFileSet *self = 0;
192     Filename *fn = Filename_create();
193     Filename_setFull(fn, filename);
194     char *ext = upperstr(Filename_ext(fn));
195     if ((!ext || strcmp(ext, "D64"))
196 	    && Filename_base(fn)[1] == '!')
197     {
198 	char *base = copystr(Filename_base(fn));
199 	if (base[0] >= '1' && base[0] <= '5'
200 		&& base[1] == '!' && base[2] != '!')
201 	{
202             logmsg(L_INFO, "ZcFileSet: 4/5-file disk-packed zipcode "
203                     "detected, looking for all member files...");
204 	    FileData *files[5] = { 0 };
205 	    for (base[0] = '1'; base[0] <= '5'; ++base[0])
206 	    {
207 		Filename *pn = Filename_clone(fn);
208 		Filename_setBase(pn, base);
209                 const char *ffn = Filename_full(pn);
210                 logfmt(L_INFO, "ZcFileSet: trying to read `%s'.", ffn);
211 		FILE *p = fopen_internal(ffn, "rb");
212 		if (p)
213 		{
214 		    files[base[0]-'1'] = readHostFile(p);
215 		    fclose(p);
216 		}
217 	    }
218 	    self = fromFileData(base+2, files, 1);
219 	}
220         else
221         {
222             logmsg(L_WARNING, "ZcFileSet: no known ZipCode structure found.");
223         }
224 	free(base);
225     }
226     else if (!strcmp(ext, "D64"))
227     {
228         self = fromD64(filename);
229     }
230     else
231     {
232         logmsg(L_WARNING, "ZcFileSet: no known ZipCode structure found.");
233     }
234     free(ext);
235     return self;
236 }
237 
ZcFileSet_type(const ZcFileSet * self)238 SOEXPORT ZcType ZcFileSet_type(const ZcFileSet *self)
239 {
240     return self->type;
241 }
242 
ZcFileSet_count(const ZcFileSet * self)243 SOEXPORT int ZcFileSet_count(const ZcFileSet *self)
244 {
245     return self->count;
246 }
247 
ZcFileSet_name(const ZcFileSet * self)248 SOEXPORT const char *ZcFileSet_name(const ZcFileSet *self)
249 {
250     return self->name;
251 }
252 
checkIndex(const ZcFileSet * self,int index)253 static int checkIndex(const ZcFileSet *self, int index)
254 {
255     if (index < 0 || index >= self->count)
256     {
257         logfmt(L_ERROR, "ZcFileSet: non-existing member file %d requested.",
258                 index);
259         return -1;
260     }
261     return 0;
262 }
263 
ZcFileSet_fileData(ZcFileSet * self,int index)264 SOEXPORT FileData *ZcFileSet_fileData(ZcFileSet *self, int index)
265 {
266     if (checkIndex(self, index) < 0) return 0;
267     return self->files[index];
268 }
269 
ZcFileSet_rfileData(const ZcFileSet * self,int index)270 SOEXPORT const FileData *ZcFileSet_rfileData(const ZcFileSet *self, int index)
271 {
272     if (checkIndex(self, index) < 0) return 0;
273     return self->files[index];
274 }
275 
ZcFileSet_save(const ZcFileSet * self,const char * filename)276 SOEXPORT int ZcFileSet_save(const ZcFileSet *self, const char *filename)
277 {
278     Filename *fn = Filename_create();
279     Filename_setFull(fn, filename);
280     const char *basename = self->name;
281     const char *fnbase = Filename_base(fn);
282     if (fnbase)
283     {
284         if (fnbase[0] && fnbase[1] == '!') basename = fnbase+2;
285         else basename = fnbase;
286     }
287 
288     char *nextname = xmalloc(strlen(basename) + 3);
289     nextname[1] = '!';
290     strcpy(nextname+2, basename);
291 
292     const char *ext = Filename_ext(fn);
293     if (!ext) Filename_setExt(fn, "prg");
294 
295     for (nextname[0] = '1';
296             nextname[0] < (self->type == ZT_5PACK ? '6' : '5');
297             ++nextname[0])
298     {
299         Filename_setBase(fn, nextname);
300         FILE *f = fopen_internal(Filename_full(fn), "wb");
301         if (!f)
302         {
303             free(nextname);
304             logfmt(L_ERROR, "ZcFileSet_save: error opening `%s' for writing.",
305                     Filename_full(fn));
306             Filename_destroy(fn);
307             return -1;
308         }
309         const FileData *fd = self->files[nextname[0]-'1'];
310         if (!fwrite(FileData_rcontent(fd), FileData_size(fd), 1, f))
311         {
312             fclose(f);
313             free(nextname);
314             logfmt(L_ERROR, "ZcFileSet_save: error writing to `%s'.",
315                     Filename_full(fn));
316             Filename_destroy(fn);
317             return -1;
318         }
319         logfmt(L_INFO, "ZcFileSet_save: saved `%s'.", Filename_full(fn));
320         fclose(f);
321     }
322     free(nextname);
323     Filename_destroy(fn);
324     return 0;
325 }
326 
ZcFileSet_saveVfs(const ZcFileSet * self,CbmdosVfs * vfs)327 SOEXPORT int ZcFileSet_saveVfs(const ZcFileSet *self, CbmdosVfs *vfs)
328 {
329     char name[16];
330     uint8_t baselen = strlen(self->name) > 14 ? 14 : strlen(self->name);
331     memcpy(name+2, self->name, baselen);
332     name[1] = '!';
333     for (name[0] = '1'; name[0] < (self->type == ZT_5PACK ? '6' : '5');
334 	    ++name [0])
335     {
336 	CbmdosFile *file = CbmdosFile_create();
337 	CbmdosFile_setName(file, name, baselen+2);
338 	FileData *cfd = CbmdosFile_data(file);
339 	const FileData *fd = self->files[name[0]-'1'];
340 	if (FileData_append(cfd, FileData_rcontent(fd), FileData_size(fd)) < 0)
341 	{
342 	    return -1;
343 	}
344 	if (CbmdosVfs_append(vfs, file))
345 	{
346 	    return -1;
347 	}
348     }
349     return 0;
350 }
351 
ZcFileSet_destroy(ZcFileSet * self)352 SOEXPORT void ZcFileSet_destroy(ZcFileSet *self)
353 {
354     if (!self) return;
355     free(self->name);
356     for (int i = 0; i < self->count; ++i) FileData_destroy(self->files[i]);
357     free(self);
358 }
359 
360