lc4-disassembler / lc4_loader.c
lc4_loader.c
Raw
#include "lc4_loader.h"
#include "lc4_memory.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

FILE *open_file(char *file_name) {
  FILE *file = fopen(file_name, "rb");
  if (!file) {
    fprintf(stderr, "Error: could not open file %s\n", file_name);
    return NULL;
  }

  return file;
}

int parse_file(FILE *my_obj_file, row_of_memory **memory) {
  unsigned short int directive;
  unsigned short int address;
  unsigned short int n;
  unsigned short int contents;
  char *label;
  row_of_memory *new_memory_row;

  // read through the file until no more data can be read
  while (fread(&directive, sizeof(unsigned short int), 1, my_obj_file) == 1) {
    // convert the endianess of the header
    convert_endian(&directive, 8);

    if (fread(&address, sizeof(unsigned short int), 1, my_obj_file) != 1 ||
        fread(&n, sizeof(unsigned short int), 1, my_obj_file) != 1) {
      close_file(my_obj_file, memory);
      return -1;
    }

    convert_endian(&address, 8);
    convert_endian(&n, 8);

    // handle cade/dada headers
    if (directive == 0xcade || directive == 0xdada) {
      // read and convert memory contents
      unsigned short int i = 0;
      for (i = 0; i < n; i++) {
        if (fread(&contents, sizeof(unsigned short int), 1, my_obj_file) != 1) {
          close_file(my_obj_file, memory);
          return -1;
        }
        convert_endian(&contents, 8);
        add_to_list(memory, address++, contents);
      }
    }
    // handle c3b7 header
    else if (directive == 0xc3b7) {
      label = NULL;
      // allocate memory for the label (n bytes + null terminator)
      label = malloc((sizeof(char) * n) + 1);

      if (!label) {
        close_file(my_obj_file, memory);
        return -1;
      }

      // read the label char by char
      unsigned short int i = 0;
      for (i = 0; i < n; i++) {
        if (fread(&label[i], sizeof(char), 1, my_obj_file) != 1) {
          free(label);
          close_file(my_obj_file, memory);
          return -1;
        }
      }

      // null-terminate the label string
      label[n] = '\0';

      // search for the address in the memory list
      new_memory_row = search_address(*memory, address);

      // if the address is not found, add it to the list
      // and assign the label to the address
      if (!new_memory_row) {
        int result = add_to_list(memory, address, 0);
        if (result != 0) {
          free(label);
          close_file(my_obj_file, memory);
          return -1;
        }

        new_memory_row = search_address(*memory, address);
        if (!new_memory_row) {
          free(label);
          close_file(my_obj_file, memory);
          return -1;
        }
      }
      // handle case where memory row might already have a label
      if (new_memory_row->label) {
        free(new_memory_row->label);
      }
      new_memory_row->label = label;
    } else {
      close_file(my_obj_file, memory);
      return -1;
    }
  }

  if (close_file(my_obj_file, memory) != 0) {
    return -1;
  }

  return 0;
}

void convert_endian(unsigned short int *value, const short int endian) {
  *value = ((*value << endian) | (*value >> endian)) & 0xFFFF;
}

int output_file(char *file_name, row_of_memory *memory) {
  if (!memory) {
    return -1;
  }

  char output_file_name[110];
  strncpy(output_file_name, file_name, sizeof(output_file_name) - 1);
  replace_file_ext(output_file_name, ".obj", ".asm");

  FILE *file = fopen(output_file_name, "w");
  if (!file) {
    fprintf(stderr, "Error: could not open/create file %s\n", file_name);
    return -1;
  }

  row_of_memory *curr = memory;
  unsigned short int addr = -1;
  unsigned short int first_directive = 0;
  while (curr) {
    char fout[256] = "";
    unsigned short int width = 0;
    if (curr->assembly) {
      width = strlen(curr->assembly) + 4;
    }

    // USER .CODE
    if (curr->address >= 0x0000 && curr->address <= 0x1FFF) {
      if (addr > 0x1fff || first_directive == 0) {
        sprintf(fout, "\n\n%9s\n%9s x%.04X", ".CODE", ".ADDR", curr->address);
        first_directive = 1;
      } else if (curr->address - addr != 1) {
        // address may not be continuous, set the new
        // starting address if there's a leap from the last ending address
        sprintf(fout, "\n\n%9s x%.04X\n", ".ADDR", curr->address);
      }

      char code_line[256];
      if (curr->label && strlen(curr->label) > 0) {
        // Label exists, include both newlines
        sprintf(code_line, "\n%s\n%*s", curr->label ? curr->label : "", width,
                curr->assembly == 0 ? "NOP"
                : curr->assembly    ? curr->assembly
                                    : "");
      } else {
        sprintf(code_line, "\n%*s", width,
                curr->assembly == 0 ? "NOP"
                : curr->assembly    ? curr->assembly
                                    : "");
      }
      strcat(fout, code_line);
    }
    // USER .DATA
    else if (curr->address >= 0x2000 && curr->address <= 0x7FFF) {
      // so we don't pad .DATA everytime
      if (addr < 0x2000 || first_directive == 0) {
        sprintf(fout, "\n\n%9s\n%9s x%.04X", ".DATA", ".ADDR", curr->address);
        first_directive = 1;
      } else if (curr->address - addr != 1) {
        sprintf(fout, "\n\n%9s x%.04X\n", ".ADDR", curr->address);
      }

      // Append the data directive to fout
      char directive[256];
      if (curr->label && strlen(curr->label) > 0) {
        // Label exists, include both newlines
        sprintf(directive, "\n%s\n%s x%.04X", curr->label ? curr->label : "",
                ".FILL", curr->contents);
      } else {
        sprintf(directive, "\n%s x%.04X", ".FILL", curr->contents);
      }
      strcat(fout, directive);
    }
    // .OS CODE
    else if (curr->address >= 0x8000 && curr->address <= 0x9FFF) {
      if (addr < 0x8000 || addr > 0x9FFF || first_directive == 0) {
        sprintf(fout, "\n\n%7s\n%9s\n%9s x%.04X", ".OS", ".CODE", ".ADDR",
                curr->address);
        first_directive = 1;
      } else if (curr->address - addr != 1) {
        sprintf(fout, "\n\n%9s x%.04X\n", ".ADDR", curr->address);
      }

      char os_line[256];
      if (curr->label && strlen(curr->label) > 0) {
        sprintf(os_line, "\n\n%s\n%s\n%*s", ".CODE",
                curr->label ? curr->label : "", width,
                curr->assembly == 0 ? "NOP"
                : curr->assembly    ? curr->assembly
                                    : "");
      } else {
        sprintf(os_line, "\n%*s", width,
                curr->assembly == 0 ? "NOP"
                : curr->assembly    ? curr->assembly
                                    : "");
      }
      strcat(fout, os_line);
    }
    // .OS DATA + Device Memory
    else if (curr->address >= 0xA000 && curr->address <= 0xFFFF) {
      if (addr < 0xA000 || first_directive == 0) {
        sprintf(fout, "\n\n%9s\n%9s x%.04X", ".DATA", ".ADDR", curr->address);
        first_directive = 1;
      } else if (curr->address - addr != 1) {
        sprintf(fout, "\n\n%9s x%.04X\n", ".ADDR", curr->address);
      }

      char os_data_line[256];
      // when reserving addresses using .BLKW, we decided to use NOP
      // as .BLKW doesn't do anything at those addresses the same way as NOP
      // doing so also catches both empty lines
      if (curr->label && strlen(curr->label) > 0) {
        sprintf(os_data_line, "\n%s\n%*s", curr->label ? curr->label : "",
                width,
                curr->assembly == 0 ? "NOP"
                : curr->assembly    ? curr->assembly
                                    : "");
      } else {
        sprintf(os_data_line, "\n%*s", width,
                curr->assembly == 0 ? "NOP"
                : curr->assembly    ? curr->assembly
                                    : "");
      }
      strcat(fout, os_data_line);
    } else {
      printf("Error: outputting invalid address 0x%04X\n", curr->address);
      return -1;
    }

    addr = curr->address;

    // Write only if we have content
    if (strlen(fout) > 0) {
      fwrite(fout, sizeof(char), strlen(fout), file);
    }

    // go to the next node
    curr = curr->next;
  }

  close_file(file, &memory);

  return 0;
}

void replace_file_ext(char *file_name, char *curr_ext, char *new_ext) {
  // replace .obj with .asm
  char *dot = strrchr(file_name, '.');

  // check if the last '.' matches the current extension
  if (dot && strcmp(dot, curr_ext) == 0) {
    *dot = '\0'; // null-terminate where the current extension starts
  }

  // concatenate the new extension
  strcat(file_name, new_ext);
}

int close_file(FILE *file, row_of_memory **memory) {
  if (file && fclose(file) != 0) {
    printf("error: unable to close the input .obj file\n");
    delete_list(memory);
    return -1;
  }
  return 0;
}