1 | /*****************************************************************************/ |
2 | /* */ |
3 | /* xex.c */ |
4 | /* */ |
5 | /* Module to handle the Atari XEX binary format */ |
6 | /* */ |
7 | /* */ |
8 | /* */ |
9 | /* (C) 2018 Daniel Serpell */ |
10 | /* */ |
11 | /* */ |
12 | /* This software is provided 'as-is', without any expressed or implied */ |
13 | /* warranty. In no event will the authors be held liable for any damages */ |
14 | /* arising from the use of this software. */ |
15 | /* */ |
16 | /* Permission is granted to anyone to use this software for any purpose, */ |
17 | /* including commercial applications, and to alter it and redistribute it */ |
18 | /* freely, subject to the following restrictions: */ |
19 | /* */ |
20 | /* 1. The origin of this software must not be misrepresented; you must not */ |
21 | /* claim that you wrote the original software. If you use this software */ |
22 | /* in a product, an acknowledgment in the product documentation would be */ |
23 | /* appreciated but is not required. */ |
24 | /* 2. Altered source versions must be plainly marked as such, and must not */ |
25 | /* be misrepresented as being the original software. */ |
26 | /* 3. This notice may not be removed or altered from any source */ |
27 | /* distribution. */ |
28 | /* */ |
29 | /*****************************************************************************/ |
30 | |
31 | |
32 | |
33 | #include <stdio.h> |
34 | #include <string.h> |
35 | #include <errno.h> |
36 | |
37 | /* common */ |
38 | #include "alignment.h" |
39 | #include "print.h" |
40 | #include "xmalloc.h" |
41 | |
42 | /* ld65 */ |
43 | #include "xex.h" |
44 | #include "config.h" |
45 | #include "exports.h" |
46 | #include "expr.h" |
47 | #include "error.h" |
48 | #include "global.h" |
49 | #include "fileio.h" |
50 | #include "lineinfo.h" |
51 | #include "memarea.h" |
52 | #include "segments.h" |
53 | #include "spool.h" |
54 | |
55 | |
56 | |
57 | /*****************************************************************************/ |
58 | /* Data */ |
59 | /*****************************************************************************/ |
60 | |
61 | /* Linked list of memory area initialization addresses */ |
62 | typedef struct XexInitAd { |
63 | MemoryArea *InitMem; |
64 | Import *InitAd; |
65 | struct XexInitAd *next; |
66 | } XexInitAd; |
67 | |
68 | |
69 | struct XexDesc { |
70 | unsigned Undef; /* Count of undefined externals */ |
71 | FILE* F; /* Output file */ |
72 | const char* Filename; /* Name of output file */ |
73 | Import* RunAd; /* Run Address */ |
74 | XexInitAd* InitAds; /* List of Init Addresses */ |
75 | unsigned long HeadPos; /* Position in the file of current header */ |
76 | unsigned long HeadEnd; /* End address of current header */ |
77 | unsigned long HeadSize; /* Last header size, can be removed if zero */ |
78 | }; |
79 | |
80 | |
81 | /*****************************************************************************/ |
82 | /* Code */ |
83 | /*****************************************************************************/ |
84 | |
85 | |
86 | |
87 | XexDesc* NewXexDesc (void) |
88 | /* Create a new XEX format descriptor */ |
89 | { |
90 | /* Allocate memory for a new XexDesc struct */ |
91 | XexDesc* D = xmalloc (sizeof (XexDesc)); |
92 | |
93 | /* Initialize the fields */ |
94 | D->Undef = 0; |
95 | D->F = 0; |
96 | D->Filename = 0; |
97 | D->RunAd = 0; |
98 | D->InitAds = 0; |
99 | D->HeadPos = 0; |
100 | D->HeadEnd = 0; |
101 | D->HeadSize = 0; |
102 | |
103 | /* Return the created struct */ |
104 | return D; |
105 | } |
106 | |
107 | |
108 | |
109 | void FreeXexDesc (XexDesc* D) |
110 | /* Free a XEX format descriptor */ |
111 | { |
112 | xfree (D); |
113 | } |
114 | |
115 | |
116 | |
117 | void XexSetRunAd (XexDesc* D, Import *RunAd) |
118 | /* Set the RUNAD export */ |
119 | { |
120 | D->RunAd = RunAd; |
121 | } |
122 | |
123 | XexInitAd* XexSearchInitMem(XexDesc* D, MemoryArea *InitMem) |
124 | { |
125 | XexInitAd* I; |
126 | for (I=D->InitAds; I != 0; I=I->next) |
127 | { |
128 | if (I->InitMem == InitMem) |
129 | return I; |
130 | } |
131 | return NULL; |
132 | } |
133 | |
134 | |
135 | int XexAddInitAd (XexDesc* D, MemoryArea *InitMem, Import *InitAd) |
136 | /* Sets and INITAD for the given memory area */ |
137 | { |
138 | XexInitAd* I; |
139 | |
140 | /* Search for repeated entry */ |
141 | if (XexSearchInitMem (D, InitMem)) |
142 | return 1; |
143 | |
144 | I = xmalloc (sizeof (XexInitAd)); |
145 | I->InitAd = InitAd; |
146 | I->InitMem = InitMem; |
147 | I->next = D->InitAds; |
148 | D->InitAds = I; |
149 | return 0; |
150 | } |
151 | |
152 | static unsigned XexWriteExpr (ExprNode* E, int Signed, unsigned Size, |
153 | unsigned long Offs attribute ((unused)), |
154 | void* Data) |
155 | /* Called from SegWrite for an expression. Evaluate the expression, check the |
156 | ** range and write the expression value to the file. |
157 | */ |
158 | { |
159 | /* There's a predefined function to handle constant expressions */ |
160 | return SegWriteConstExpr (((XexDesc*)Data)->F, E, Signed, Size); |
161 | } |
162 | |
163 | |
164 | |
165 | static void PrintNumVal (const char* Name, unsigned long V) |
166 | /* Print a numerical value for debugging */ |
167 | { |
168 | Print (stdout, 2, " %s = 0x%lx\n" , Name, V); |
169 | } |
170 | |
171 | |
172 | |
173 | static void XexStartSegment (XexDesc *D, unsigned long Addr, unsigned long Size) |
174 | { |
175 | /* Skip segment without size */ |
176 | if (!Size) |
177 | return; |
178 | |
179 | /* Store current position */ |
180 | unsigned long Pos = ftell (D->F); |
181 | unsigned long End = Addr + Size - 1; |
182 | |
183 | /* See if last header can be expanded into this one */ |
184 | if (D->HeadPos && ((D->HeadEnd + 1) == Addr)) { |
185 | /* Expand current header */ |
186 | D->HeadEnd = End; |
187 | D->HeadSize += Size; |
188 | fseek (D->F, D->HeadPos + 2, SEEK_SET); |
189 | Write16 (D->F, End); |
190 | /* Seek to old position */ |
191 | fseek (D->F, Pos, SEEK_SET); |
192 | } |
193 | else |
194 | { |
195 | if (D->HeadSize == 0) { |
196 | /* Last header had no data, replace */ |
197 | Pos = D->HeadPos; |
198 | fseek (D->F, Pos, SEEK_SET); |
199 | } |
200 | |
201 | /* If we are at start of file, write XEX heder */ |
202 | if (Pos == 0) |
203 | Write16 (D->F, 0xFFFF); |
204 | |
205 | /* Writes a new segment header */ |
206 | D->HeadPos = ftell (D->F); |
207 | D->HeadEnd = End; |
208 | D->HeadSize = Size; |
209 | Write16 (D->F, Addr); |
210 | Write16 (D->F, End); |
211 | } |
212 | } |
213 | |
214 | |
215 | |
216 | static void XexFakeSegment (XexDesc *D, unsigned long Addr) |
217 | { |
218 | /* See if last header can be expanded into this one, we are done */ |
219 | if (D->HeadPos && ((D->HeadEnd + 1) == Addr)) |
220 | return; |
221 | |
222 | /* If we are at start of file, write XEX heder */ |
223 | if (ftell (D->F) == 0) |
224 | Write16 (D->F, 0xFFFF); |
225 | |
226 | /* Writes a new (invalid) segment header */ |
227 | D->HeadPos = ftell (D->F); |
228 | D->HeadEnd = Addr - 1; |
229 | D->HeadSize = 0; |
230 | Write16 (D->F, Addr); |
231 | Write16 (D->F, D->HeadEnd); |
232 | } |
233 | |
234 | |
235 | |
236 | static unsigned long XexWriteMem (XexDesc* D, MemoryArea* M) |
237 | /* Write the segments of one memory area to a file */ |
238 | { |
239 | unsigned I; |
240 | |
241 | /* Store initial position to get total file size */ |
242 | unsigned long StartPos = ftell (D->F); |
243 | |
244 | /* Always write a segment header for each memory area */ |
245 | D->HeadPos = 0; |
246 | |
247 | /* Get the start address and size of this memory area */ |
248 | unsigned long Addr = M->Start; |
249 | |
250 | /* Walk over all segments in this memory area */ |
251 | for (I = 0; I < CollCount (&M->SegList); ++I) { |
252 | |
253 | int DoWrite; |
254 | |
255 | /* Get the segment */ |
256 | SegDesc* S = CollAtUnchecked (&M->SegList, I); |
257 | |
258 | /* Keep the user happy */ |
259 | Print (stdout, 1, " ATARI EXE Writing `%s'\n" , GetString (S->Name)); |
260 | |
261 | /* Writes do only occur in the load area and not for BSS segments */ |
262 | DoWrite = (S->Flags & SF_BSS) == 0 && /* No BSS segment */ |
263 | S->Load == M && /* LOAD segment */ |
264 | S->Seg->Dumped == 0; /* Not already written */ |
265 | |
266 | /* If this is the run memory area, we must apply run alignment. If |
267 | ** this is not the run memory area but the load memory area (which |
268 | ** means that both are different), we must apply load alignment. |
269 | ** Beware: DoWrite may be true even if this is the run memory area, |
270 | ** because it may be also the load memory area. |
271 | */ |
272 | if (S->Run == M) { |
273 | |
274 | /* Handle ALIGN and OFFSET/START */ |
275 | if (S->Flags & SF_ALIGN) { |
276 | /* Align the address */ |
277 | unsigned long NewAddr = AlignAddr (Addr, S->RunAlignment); |
278 | if (DoWrite || (M->Flags & MF_FILL) != 0) { |
279 | XexStartSegment (D, Addr, NewAddr - Addr); |
280 | WriteMult (D->F, M->FillVal, NewAddr - Addr); |
281 | PrintNumVal ("SF_ALIGN" , NewAddr - Addr); |
282 | } |
283 | Addr = NewAddr; |
284 | } else if (S->Flags & (SF_OFFSET | SF_START)) { |
285 | unsigned long NewAddr = S->Addr; |
286 | if (S->Flags & SF_OFFSET) { |
287 | /* It's an offset, not a fixed address, make an address */ |
288 | NewAddr += M->Start; |
289 | } |
290 | if (DoWrite || (M->Flags & MF_FILL) != 0) { |
291 | /* "overwrite" segments are not supported */ |
292 | if (S->Flags & SF_OVERWRITE) { |
293 | Error ("ATARI file format does not support overwrite for segment '%s'." , |
294 | GetString (S->Name)); |
295 | } else { |
296 | XexStartSegment (D, Addr, NewAddr - Addr); |
297 | WriteMult (D->F, M->FillVal, NewAddr-Addr); |
298 | PrintNumVal ("SF_OFFSET" , NewAddr - Addr); |
299 | } |
300 | } |
301 | Addr = NewAddr; |
302 | } |
303 | |
304 | } else if (S->Load == M) { |
305 | |
306 | /* Handle ALIGN_LOAD */ |
307 | if (S->Flags & SF_ALIGN_LOAD) { |
308 | /* Align the address */ |
309 | unsigned long NewAddr = AlignAddr (Addr, S->LoadAlignment); |
310 | if (DoWrite || (M->Flags & MF_FILL) != 0) { |
311 | XexStartSegment (D, Addr, NewAddr - Addr); |
312 | WriteMult (D->F, M->FillVal, NewAddr - Addr); |
313 | PrintNumVal ("SF_ALIGN_LOAD" , NewAddr - Addr); |
314 | } |
315 | Addr = NewAddr; |
316 | } |
317 | |
318 | } |
319 | |
320 | /* Now write the segment to disk if it is not a BSS type segment and |
321 | ** if the memory area is the load area. |
322 | */ |
323 | if (DoWrite) { |
324 | /* Start a segment with only one byte, will fix later */ |
325 | XexFakeSegment (D, Addr); |
326 | unsigned long P = ftell (D->F); |
327 | SegWrite (D->Filename, D->F, S->Seg, XexWriteExpr, D); |
328 | unsigned long Size = ftell (D->F) - P; |
329 | /* Fix segment size */ |
330 | XexStartSegment (D, Addr, Size); |
331 | PrintNumVal ("Wrote" , Size); |
332 | } else if (M->Flags & MF_FILL) { |
333 | XexStartSegment (D, Addr, S->Seg->Size); |
334 | WriteMult (D->F, S->Seg->FillVal, S->Seg->Size); |
335 | PrintNumVal ("Filled" , (unsigned long) S->Seg->Size); |
336 | } |
337 | |
338 | /* If this was the load memory area, mark the segment as dumped */ |
339 | if (S->Load == M) { |
340 | S->Seg->Dumped = 1; |
341 | } |
342 | |
343 | /* Calculate the new address */ |
344 | Addr += S->Seg->Size; |
345 | } |
346 | |
347 | /* If a fill was requested, fill the remaining space */ |
348 | if ((M->Flags & MF_FILL) != 0 && M->FillLevel < M->Size) { |
349 | unsigned long ToFill = M->Size - M->FillLevel; |
350 | Print (stdout, 2, " Filling 0x%lx bytes with 0x%02x\n" , |
351 | ToFill, M->FillVal); |
352 | XexStartSegment (D, Addr, ToFill); |
353 | WriteMult (D->F, M->FillVal, ToFill); |
354 | M->FillLevel = M->Size; |
355 | } |
356 | |
357 | /* If the last segment is empty, remove */ |
358 | if (D->HeadSize == 0 && D->HeadPos) { |
359 | fseek (D->F, D->HeadPos, SEEK_SET); |
360 | } |
361 | |
362 | return ftell (D->F) - StartPos; |
363 | } |
364 | |
365 | |
366 | |
367 | static int XexUnresolved (unsigned Name attribute ((unused)), void* D) |
368 | /* Called if an unresolved symbol is encountered */ |
369 | { |
370 | /* Unresolved symbols are an error in XEX format. Bump the counter |
371 | ** and return zero telling the caller that the symbol is indeed |
372 | ** unresolved. |
373 | */ |
374 | ((XexDesc*) D)->Undef++; |
375 | return 0; |
376 | } |
377 | |
378 | |
379 | |
380 | void XexWriteTarget (XexDesc* D, struct File* F) |
381 | /* Write a XEX output file */ |
382 | { |
383 | unsigned I; |
384 | |
385 | /* Place the filename in the control structure */ |
386 | D->Filename = GetString (F->Name); |
387 | |
388 | /* Check for unresolved symbols. The function XexUnresolved is called |
389 | ** if we get an unresolved symbol. |
390 | */ |
391 | D->Undef = 0; /* Reset the counter */ |
392 | CheckUnresolvedImports (XexUnresolved, D); |
393 | if (D->Undef > 0) { |
394 | /* We had unresolved symbols, cannot create output file */ |
395 | Error ("%u unresolved external(s) found - cannot create output file" , D->Undef); |
396 | } |
397 | |
398 | /* Open the file */ |
399 | D->F = fopen (D->Filename, "wb" ); |
400 | if (D->F == 0) { |
401 | Error ("Cannot open `%s': %s" , D->Filename, strerror (errno)); |
402 | } |
403 | |
404 | /* Keep the user happy */ |
405 | Print (stdout, 1, "Opened `%s'...\n" , D->Filename); |
406 | |
407 | /* Dump all memory areas */ |
408 | for (I = 0; I < CollCount (&F->MemoryAreas); ++I) { |
409 | /* Get this entry */ |
410 | MemoryArea* M = CollAtUnchecked (&F->MemoryAreas, I); |
411 | /* See if we have an init address for this area */ |
412 | XexInitAd* I = XexSearchInitMem (D, M); |
413 | Print (stdout, 1, " ATARI EXE Dumping `%s'\n" , GetString (M->Name)); |
414 | if (XexWriteMem (D, M) && I) { |
415 | Write16 (D->F, 0x2E2); |
416 | Write16 (D->F, 0x2E3); |
417 | Write16 (D->F, GetExportVal (I->InitAd->Exp)); |
418 | } |
419 | } |
420 | |
421 | /* Write RUNAD at file end */ |
422 | if (D->RunAd) { |
423 | Write16 (D->F, 0x2E0); |
424 | Write16 (D->F, 0x2E1); |
425 | Write16 (D->F, GetExportVal (D->RunAd->Exp)); |
426 | } |
427 | |
428 | /* Close the file */ |
429 | if (fclose (D->F) != 0) { |
430 | Error ("Cannot write to `%s': %s" , D->Filename, strerror (errno)); |
431 | } |
432 | |
433 | /* Reset the file and filename */ |
434 | D->F = 0; |
435 | D->Filename = 0; |
436 | } |
437 | |