1 #include <stdlib.h>
2 #include <string.h>
3 
4 #include "util.h"
5 #include "log.h"
6 #include <1541img/event.h>
7 #include <1541img/filedata.h>
8 #include <1541img/hostfilereader.h>
9 #include <1541img/hostfilewriter.h>
10 #include <1541img/petscii.h>
11 
12 #include <1541img/cbmdosfile.h>
13 
14 static const char *exts[] =
15 {
16     "DEL",
17     "SEQ",
18     "PRG",
19     "USR",
20     "REL"
21 };
22 
CbmdosFileType_name(CbmdosFileType type)23 SOEXPORT const char *CbmdosFileType_name(CbmdosFileType type)
24 {
25     if (type < CFT_DEL || type > CFT_REL) return 0;
26     return exts[type];
27 }
28 
29 struct CbmdosFile
30 {
31     CbmdosFileType type;
32     int invalidType;
33     int locked;
34     int closed;
35     int autoMapToLc;
36     char *name;
37     FileData *data;
38     Event *changedEvent;
39     uint8_t nameLength;
40     uint8_t recordLength;
41     uint16_t forcedBlocks;
42 };
43 
fileDataHandler(void * receiver,int id,const void * sender,const void * args)44 static void fileDataHandler(void *receiver, int id, const void *sender,
45         const void *args)
46 {
47     (void)id;
48     (void)args;
49     (void)sender;
50 
51     CbmdosFileEventArgs ea = { CFE_DATACHANGED };
52     CbmdosFile *self = receiver;
53     Event_raise(self->changedEvent, &ea);
54 }
55 
CbmdosFile_create(void)56 SOEXPORT CbmdosFile *CbmdosFile_create(void)
57 {
58     CbmdosFile *self = xmalloc(sizeof *self);
59     memset(self, 0, sizeof *self);
60     self->data = FileData_create();
61     self->changedEvent = Event_create(0, self);
62     self->type = CFT_PRG;
63     self->invalidType = -1;
64     self->closed = 1;
65     self->forcedBlocks = 0xffff;
66     self->recordLength = 254;
67     Event_register(FileData_changedEvent(self->data), self, fileDataHandler);
68     return self;
69 }
70 
CbmdosFile_clone(const CbmdosFile * other)71 SOEXPORT CbmdosFile *CbmdosFile_clone(const CbmdosFile *other)
72 {
73     CbmdosFile *self = xmalloc(sizeof *self);
74     self->type = other->type;
75     self->invalidType = other->invalidType;
76     self->locked = other->locked;
77     self->closed = other->closed;
78     self->autoMapToLc = 0;
79     self->name = 0;
80     self->data = FileData_clone(other->data);
81     self->changedEvent = Event_create(0, self);
82     self->nameLength = 0;
83     self->recordLength = other->recordLength;
84     self->forcedBlocks = other->forcedBlocks;
85     uint8_t len;
86     const char *name = CbmdosFile_name(other, &len);
87     CbmdosFile_setName(self, name, len);
88     self->autoMapToLc = other->autoMapToLc;
89     Event_register(FileData_changedEvent(self->data), self, fileDataHandler);
90     return self;
91 }
92 
CbmdosFile_type(const CbmdosFile * self)93 SOEXPORT CbmdosFileType CbmdosFile_type(const CbmdosFile *self)
94 {
95     return self->type;
96 }
97 
CbmdosFile_invalidType(const CbmdosFile * self)98 SOEXPORT int CbmdosFile_invalidType(const CbmdosFile *self)
99 {
100     return self->invalidType;
101 }
102 
CbmdosFile_setType(CbmdosFile * self,CbmdosFileType type)103 SOEXPORT int CbmdosFile_setType(CbmdosFile *self, CbmdosFileType type)
104 {
105     int rc = 0;
106     int invalidType = -1;
107     type &= 0xf;
108     if (type < CFT_DEL || type > CFT_REL)
109     {
110         logmsg(L_WARNING, "CbmdosFile_setType: invalid type.");
111 	invalidType = type;
112         type = CFT_DEL;
113 	rc = -1;
114     }
115     if (type == self->type && invalidType == self->invalidType) return rc;
116     if ((self->invalidType < 0 && self->type == CFT_DEL) ||
117 	    (invalidType < 0 && type == CFT_DEL))
118     {
119 	CbmdosFile_setData(self, FileData_create());
120     }
121     self->type = type;
122     self->invalidType = invalidType;
123     CbmdosFileEventArgs args = { CFE_TYPECHANGED };
124     Event_raise(self->changedEvent, &args);
125     return rc;
126 }
127 
CbmdosFile_name(const CbmdosFile * self,uint8_t * length)128 SOEXPORT const char *CbmdosFile_name(const CbmdosFile *self, uint8_t *length)
129 {
130     if (length)
131     {
132         *length = self->nameLength;
133     }
134     return self->name;
135 }
136 
CbmdosFile_setName(CbmdosFile * self,const char * name,uint8_t length)137 SOEXPORT void CbmdosFile_setName(
138 	CbmdosFile *self, const char *name, uint8_t length)
139 {
140     if (length > 16)
141     {
142         logmsg(L_WARNING, "CbmdosFile_setName: truncating long name.");
143         length = 16;
144     }
145     free(self->name);
146     if (name)
147     {
148         self->name = xmalloc(length+1);
149         memcpy(self->name, name, length);
150         self->name[length] = 0;
151     }
152     else
153     {
154         self->name = 0;
155         if (length)
156         {
157             logmsg(L_WARNING, "CbmdosFile_setName: length > 0 given for empty "
158                     "name.");
159             length = 0;
160         }
161     }
162     self->nameLength = length;
163     if (self->autoMapToLc)
164     {
165 	petscii_mapUpperGfxToLower(self->name, self->nameLength);
166     }
167     CbmdosFileEventArgs args = { CFE_NAMECHANGED };
168     Event_raise(self->changedEvent, &args);
169 }
170 
CbmdosFile_mapUpperGfxToLower(CbmdosFile * self)171 SOEXPORT void CbmdosFile_mapUpperGfxToLower(CbmdosFile *self)
172 {
173     petscii_mapUpperGfxToLower(self->name, self->nameLength);
174     CbmdosFileEventArgs args = { CFE_NAMECHANGED };
175     Event_raise(self->changedEvent, &args);
176 }
177 
CbmdosFile_rdata(const CbmdosFile * self)178 SOEXPORT const FileData *CbmdosFile_rdata(const CbmdosFile *self)
179 {
180     return self->data;
181 }
182 
CbmdosFile_data(CbmdosFile * self)183 SOEXPORT FileData *CbmdosFile_data(CbmdosFile *self)
184 {
185     return self->data;
186 }
187 
CbmdosFile_setData(CbmdosFile * self,FileData * data)188 SOEXPORT void CbmdosFile_setData(CbmdosFile *self, FileData *data)
189 {
190     Event_unregister(FileData_changedEvent(self->data), self, fileDataHandler);
191     FileData_destroy(self->data);
192     self->data = data;
193     Event_register(FileData_changedEvent(self->data), self, fileDataHandler);
194     CbmdosFileEventArgs ea = { CFE_DATACHANGED };
195     Event_raise(self->changedEvent, &ea);
196 }
197 
CbmdosFile_exportRaw(const CbmdosFile * self,FILE * file)198 SOEXPORT int CbmdosFile_exportRaw(const CbmdosFile *self, FILE *file)
199 {
200     return writeHostFile(self->data, file);
201 }
202 
CbmdosFile_exportPC64(const CbmdosFile * self,FILE * file)203 SOEXPORT int CbmdosFile_exportPC64(const CbmdosFile *self, FILE *file)
204 {
205     uint8_t header[26] = "C64File";
206     memcpy(header+8, self->name, self->nameLength);
207     if (self->type == CFT_REL) header[25] = self->recordLength;
208     if (fwrite(header, sizeof header, 1, file) != 1) return -1;
209     return writeHostFile(self->data, file);
210 }
211 
CbmdosFile_import(CbmdosFile * self,FILE * file)212 SOEXPORT int CbmdosFile_import(CbmdosFile *self, FILE *file)
213 {
214     FileData *data = readHostFile(file);
215     if (!data) return -1;
216     const uint8_t *content = FileData_rcontent(data);
217     if (FileData_size(data) > 26 && !content[24]
218 	    && !memcmp(content, "C64File", sizeof "C64File"))
219     {
220 	FileData *raw = FileData_create();
221 	if (!raw)
222 	{
223 	    FileData_destroy(data);
224 	    return -1;
225 	}
226 	if (FileData_append(raw, content+26, FileData_size(data)-26) < 0)
227 	{
228 	    FileData_destroy(raw);
229 	    FileData_destroy(data);
230 	    return -1;
231 	}
232 	CbmdosFile_setData(self, raw);
233 	CbmdosFile_setName(self, (const char *)content+8,
234 		strlen((const char *)content+8));
235 	CbmdosFile_setRecordLength(self, content[25] ? content[25] : 254);
236 	FileData_destroy(data);
237     }
238     else
239     {
240 	CbmdosFile_setData(self, data);
241     }
242     return 0;
243 }
244 
CbmdosFile_recordLength(const CbmdosFile * self)245 SOEXPORT uint8_t CbmdosFile_recordLength(const CbmdosFile *self)
246 {
247     return self->recordLength;
248 }
249 
CbmdosFile_setRecordLength(CbmdosFile * self,uint8_t recordLength)250 SOEXPORT int CbmdosFile_setRecordLength(CbmdosFile *self, uint8_t recordLength)
251 {
252     if (recordLength > 254)
253     {
254         logmsg(L_WARNING, "CbmdosFile_setRecordLength: invalid length, "
255                 "max is 254.");
256         return -1;
257     }
258     if (recordLength < 1)
259     {
260         logmsg(L_WARNING, "CbmdosFile_setRecordLength: invalid length, "
261                 "min is 1.");
262         return -1;
263     }
264     self->recordLength = recordLength;
265     CbmdosFileEventArgs ea = { CFE_RECORDLENGTHCHANGED };
266     Event_raise(self->changedEvent, &ea);
267     return 0;
268 }
269 
CbmdosFile_realBlocks(const CbmdosFile * self)270 SOEXPORT uint16_t CbmdosFile_realBlocks(const CbmdosFile *self)
271 {
272     if ((self->invalidType < 0 && self->type == CFT_DEL) || !self->data)
273     {
274 	return 0;
275     }
276     size_t size = FileData_size(self->data);
277     uint16_t blocks = size / 254;
278     if (size % 254) ++blocks;
279     return blocks;
280 }
281 
CbmdosFile_blocks(const CbmdosFile * self)282 SOEXPORT uint16_t CbmdosFile_blocks(const CbmdosFile *self)
283 {
284     if (self->forcedBlocks != 0xffff) return self->forcedBlocks;
285     return CbmdosFile_realBlocks(self);
286 }
287 
CbmdosFile_forcedBlocks(const CbmdosFile * self)288 SOEXPORT uint16_t CbmdosFile_forcedBlocks(const CbmdosFile *self)
289 {
290     return self->forcedBlocks;
291 }
292 
CbmdosFile_setForcedBlocks(CbmdosFile * self,uint16_t forcedBlocks)293 SOEXPORT void CbmdosFile_setForcedBlocks(
294 	CbmdosFile *self, uint16_t forcedBlocks)
295 {
296     self->forcedBlocks = forcedBlocks;
297     CbmdosFileEventArgs args = { CFE_FORCEDBLOCKSCHANGED };
298     Event_raise(self->changedEvent, &args);
299 }
300 
CbmdosFile_locked(const CbmdosFile * self)301 SOEXPORT int CbmdosFile_locked(const CbmdosFile *self)
302 {
303     return self->locked;
304 }
305 
CbmdosFile_setLocked(CbmdosFile * self,int locked)306 SOEXPORT void CbmdosFile_setLocked(CbmdosFile *self, int locked)
307 {
308     self->locked = !!locked;
309     CbmdosFileEventArgs args = { CFE_LOCKEDCHANGED };
310     Event_raise(self->changedEvent, &args);
311 }
312 
CbmdosFile_closed(const CbmdosFile * self)313 SOEXPORT int CbmdosFile_closed(const CbmdosFile *self)
314 {
315     return self->closed;
316 }
317 
CbmdosFile_setClosed(CbmdosFile * self,int closed)318 SOEXPORT void CbmdosFile_setClosed(CbmdosFile *self, int closed)
319 {
320     self->closed = !!closed;
321     CbmdosFileEventArgs args = { CFE_CLOSEDCHANGED };
322     Event_raise(self->changedEvent, &args);
323 }
324 
CbmdosFile_autoMapToLc(const CbmdosFile * self)325 SOEXPORT int CbmdosFile_autoMapToLc(const CbmdosFile *self)
326 {
327     return self->autoMapToLc;
328 }
329 
CbmdosFile_setAutoMapToLc(CbmdosFile * self,int autoMapToLc)330 SOEXPORT void CbmdosFile_setAutoMapToLc(CbmdosFile *self, int autoMapToLc)
331 {
332     self->autoMapToLc = !!autoMapToLc;
333 }
334 
CbmdosFile_getDirLine(const CbmdosFile * self,uint8_t * line)335 SOEXPORT void CbmdosFile_getDirLine(const CbmdosFile *self, uint8_t *line)
336 {
337     int blocklen = sprintf((char *)line, "%u", CbmdosFile_blocks(self));
338     memset(line + blocklen, 0xa0, 28 - blocklen);
339     memcpy(line + 6, self->name, self->nameLength);
340     if (self->invalidType < 0)
341     {
342 	memcpy(line + 24, CbmdosFileType_name(self->type), 3);
343     }
344     else
345     {
346 	memcpy(line + 24, "?  ", 3);
347     }
348     line[5] = 0x22;
349     uint8_t endidx = 6;
350     while (line[endidx] != 0xa0) ++endidx;
351     line[endidx] = 0x22;
352     if (!self->closed) line[23] = 0x2a;
353     if (self->locked) line[27] = 0x3c;
354 }
355 
CbmdosFile_changedEvent(CbmdosFile * self)356 SOEXPORT Event *CbmdosFile_changedEvent(CbmdosFile *self)
357 {
358     return self->changedEvent;
359 }
360 
CbmdosFile_destroy(CbmdosFile * self)361 SOEXPORT void CbmdosFile_destroy(CbmdosFile *self)
362 {
363     if (!self) return;
364     free(self->name);
365     Event_destroy(self->changedEvent);
366     FileData_destroy(self->data);
367 }
368 
369