]> git.sur5r.net Git - cc65/blobdiff - src/ld65/xex.c
Rewrites ATARI EXE segment writing to optimally write segment sizes.
[cc65] / src / ld65 / xex.c
index 71065ea5755f26c742b63b2ac4f560f83cc7b521..18190c063641e4f1c036afa69b8d942b69a625d8 100644 (file)
@@ -64,6 +64,10 @@ struct XexDesc {
     unsigned    Undef;          /* Count of undefined externals */
     FILE*       F;              /* Output file */
     const char* Filename;       /* Name of output file */
+    Import*     RunAd;          /* Run Address */
+    unsigned long HeadPos;      /* Position in the file of current header */
+    unsigned long HeadEnd;      /* End address of current header */
+    unsigned long HeadSize;     /* Last header size, can be removed if zero */
 };
 
 
@@ -84,6 +88,10 @@ XexDesc* NewXexDesc (void)
     D->Undef    = 0;
     D->F        = 0;
     D->Filename = 0;
+    D->RunAd    = 0;
+    D->HeadPos  = 0;
+    D->HeadEnd  = 0;
+    D->HeadSize = 0;
 
     /* Return the created struct */
     return D;
@@ -99,6 +107,14 @@ void FreeXexDesc (XexDesc* D)
 
 
 
+void XexSetRunAd (XexDesc* D, Import *RunAd)
+/* Set the RUNAD export */
+{
+    D->RunAd = RunAd;
+}
+
+
+
 static unsigned XexWriteExpr (ExprNode* E, int Signed, unsigned Size,
                               unsigned long Offs attribute ((unused)),
                               void* Data)
@@ -120,65 +136,81 @@ static void PrintNumVal (const char* Name, unsigned long V)
 
 
 
-static void XexWriteMem (XexDesc* D, MemoryArea* M)
-/* Write the segments of one memory area to a file */
+static void XexStartSegment (XexDesc *D, unsigned long Addr, unsigned long Size)
 {
-    unsigned I;
-
-    /* Get the start address and size of this memory area */
-    unsigned long Addr = M->Start;
+    /* Skip segment without size */
+    if (!Size)
+        return;
+
+    /* Store current position */
+    unsigned long Pos = ftell (D->F);
+    unsigned long End = Addr + Size - 1;
+
+    /* See if last header can be expanded into this one */
+    if (D->HeadPos && ((D->HeadEnd + 1) == Addr)) {
+        /* Expand current header */
+        D->HeadEnd = End;
+        D->HeadSize += Size;
+        fseek (D->F, D->HeadPos + 2, SEEK_SET);
+        Write16 (D->F, End);
+        /* Seek to old position */
+        fseek (D->F, Pos, SEEK_SET);
+    }
+    else
+    {
+        if (D->HeadSize == 0) {
+            /* Last header had no data, replace */
+            Pos = D->HeadPos;
+            fseek (D->F, Pos, SEEK_SET);
+        }
 
-    /* Walk over segments twice: first to get real area size, then to write
-     * all segments. */
-    for (I = 0; I < CollCount (&M->SegList); ++I) {
+        /* If we are at start of file, write XEX heder */
+        if (Pos == 0)
+            Write16 (D->F, 0xFFFF);
 
-        /* Get the segment */
-        SegDesc* S = CollAtUnchecked (&M->SegList, I);
+        /* Writes a new segment header */
+        D->HeadPos = ftell (D->F);
+        D->HeadEnd = End;
+        D->HeadSize = Size;
+        Write16 (D->F, Addr);
+        Write16 (D->F, End);
+    }
+}
 
-        /* Keep the user happy */
-        Print (stdout, 1, "    Allocating `%s'\n", GetString (S->Name));
 
-        /* If this is the run memory area, we must apply run alignment. If
-        ** this is not the run memory area but the load memory area (which
-        ** means that both are different), we must apply load alignment.
-        ** Beware: DoWrite may be true even if this is the run memory area,
-        ** because it may be also the load memory area.
-        */
-        if (S->Run == M) {
 
-            /* Handle ALIGN and OFFSET/START */
-            if (S->Flags & SF_ALIGN) {
-                /* Align the address */
-                Addr = AlignAddr (Addr, S->RunAlignment);
-            } else if (S->Flags & (SF_OFFSET | SF_START)) {
-                Addr = S->Addr;
-                if (S->Flags & SF_OFFSET) {
-                    /* It's an offset, not a fixed address, make an address */
-                    Addr += M->Start;
-                }
-            }
+static void XexFakeSegment (XexDesc *D, unsigned long Addr)
+{
+    /* See if last header can be expanded into this one, we are done */
+    if (D->HeadPos && ((D->HeadEnd + 1) == Addr))
+        return;
+
+    /* If we are at start of file, write XEX heder */
+    if (ftell (D->F) == 0)
+        Write16 (D->F, 0xFFFF);
+
+    /* Writes a new (invalid) segment header */
+    D->HeadPos = ftell (D->F);
+    D->HeadEnd = Addr - 1;
+    D->HeadSize = 0;
+    Write16 (D->F, Addr);
+    Write16 (D->F, D->HeadEnd);
+}
 
-        } else if (S->Load == M) {
 
-            /* Handle ALIGN_LOAD */
-            if (S->Flags & SF_ALIGN_LOAD) {
-                /* Align the address */
-                Addr = AlignAddr (Addr, S->LoadAlignment);
-            }
 
-        }
+static void XexWriteMem (XexDesc* D, MemoryArea* M)
+/* Write the segments of one memory area to a file */
+{
+    unsigned I;
 
-        /* Calculate the new address */
-        Addr += S->Seg->Size;
-    }
+    /* Always write a segment header for each memory area */
+    D->HeadPos = 0;
 
-    /* Write header */
-    Write16(D->F, 0xFFFF);
-    Write16(D->F, M->Start);
-    Write16(D->F, Addr-1);
+    /* Get the start address and size of this memory area */
+    unsigned long Addr = M->Start;
 
-    /* Redo */
-    Addr = M->Start;
+    /* Walk over all segments in this memory area */
     for (I = 0; I < CollCount (&M->SegList); ++I) {
 
         int DoWrite;
@@ -187,7 +219,7 @@ static void XexWriteMem (XexDesc* D, MemoryArea* M)
         SegDesc* S = CollAtUnchecked (&M->SegList, I);
 
         /* Keep the user happy */
-        Print (stdout, 1, "    Allocating `%s'\n", GetString (S->Name));
+        Print (stdout, 1, "    ATARI EXE Writing `%s'\n", GetString (S->Name));
 
         /* Writes do only occur in the load area and not for BSS segments */
         DoWrite = (S->Flags & SF_BSS) == 0      &&      /* No BSS segment */
@@ -207,6 +239,7 @@ static void XexWriteMem (XexDesc* D, MemoryArea* M)
                 /* Align the address */
                 unsigned long NewAddr = AlignAddr (Addr, S->RunAlignment);
                 if (DoWrite || (M->Flags & MF_FILL) != 0) {
+                    XexStartSegment (D, Addr, NewAddr - Addr);
                     WriteMult (D->F, M->FillVal, NewAddr - Addr);
                     PrintNumVal ("SF_ALIGN", NewAddr - Addr);
                 }
@@ -218,10 +251,12 @@ static void XexWriteMem (XexDesc* D, MemoryArea* M)
                     NewAddr += M->Start;
                 }
                 if (DoWrite || (M->Flags & MF_FILL) != 0) {
-                    /* Seek in "overwrite" segments */
+                    /* "overwrite" segments are not supported */
                     if (S->Flags & SF_OVERWRITE) {
-                        fseek (D->F, NewAddr - M->Start, SEEK_SET);
+                        Error ("ATARI file format does not support overwrite for segment '%s'.",
+                               GetString (S->Name));
                     } else {
+                        XexStartSegment (D, Addr, NewAddr - Addr);
                         WriteMult (D->F, M->FillVal, NewAddr-Addr);
                         PrintNumVal ("SF_OFFSET", NewAddr - Addr);
                     }
@@ -236,6 +271,7 @@ static void XexWriteMem (XexDesc* D, MemoryArea* M)
                 /* Align the address */
                 unsigned long NewAddr = AlignAddr (Addr, S->LoadAlignment);
                 if (DoWrite || (M->Flags & MF_FILL) != 0) {
+                    XexStartSegment (D, Addr, NewAddr - Addr);
                     WriteMult (D->F, M->FillVal, NewAddr - Addr);
                     PrintNumVal ("SF_ALIGN_LOAD", NewAddr - Addr);
                 }
@@ -248,10 +284,16 @@ static void XexWriteMem (XexDesc* D, MemoryArea* M)
         ** if the memory area is the load area.
         */
         if (DoWrite) {
+            /* Start a segment with only one byte, will fix later */
+            XexFakeSegment (D, Addr);
             unsigned long P = ftell (D->F);
             SegWrite (D->Filename, D->F, S->Seg, XexWriteExpr, D);
-            PrintNumVal ("Wrote", (unsigned long) (ftell (D->F) - P));
+            unsigned long Size = ftell (D->F) - P;
+            /* Fix segment size */
+            XexStartSegment (D, Addr, Size);
+            PrintNumVal ("Wrote", Size);
         } else if (M->Flags & MF_FILL) {
+            XexStartSegment (D, Addr, S->Seg->Size);
             WriteMult (D->F, S->Seg->FillVal, S->Seg->Size);
             PrintNumVal ("Filled", (unsigned long) S->Seg->Size);
         }
@@ -270,9 +312,15 @@ static void XexWriteMem (XexDesc* D, MemoryArea* M)
         unsigned long ToFill = M->Size - M->FillLevel;
         Print (stdout, 2, "    Filling 0x%lx bytes with 0x%02x\n",
                ToFill, M->FillVal);
+        XexStartSegment (D, Addr, ToFill);
         WriteMult (D->F, M->FillVal, ToFill);
         M->FillLevel = M->Size;
     }
+
+    /* If the last segment is empty, remove */
+    if (D->HeadSize == 0 && D->HeadPos) {
+        fseek (D->F, D->HeadPos, SEEK_SET);
+    }
 }
 
 
@@ -321,10 +369,17 @@ void XexWriteTarget (XexDesc* D, struct File* F)
     for (I = 0; I < CollCount (&F->MemoryAreas); ++I) {
         /* Get this entry */
         MemoryArea* M = CollAtUnchecked (&F->MemoryAreas, I);
-        Print (stdout, 1, "  XEX Dumping `%s'\n", GetString (M->Name));
+        Print (stdout, 1, "  ATARI EXE Dumping `%s'\n", GetString (M->Name));
         XexWriteMem (D, M);
     }
 
+    /* Write RUNAD at file end */
+    if (D->RunAd) {
+        Write16 (D->F, 0x2E0);
+        Write16 (D->F, 0x2E1);
+        Write16 (D->F, GetExportVal (D->RunAd->Exp));
+    }
+
     /* Close the file */
     if (fclose (D->F) != 0) {
         Error ("Cannot write to `%s': %s", D->Filename, strerror (errno));