summaryrefslogtreecommitdiffstats
path: root/gbdk/gbdk-support/makecom
diff options
context:
space:
mode:
Diffstat (limited to 'gbdk/gbdk-support/makecom')
-rw-r--r--gbdk/gbdk-support/makecom/Makefile32
-rw-r--r--gbdk/gbdk-support/makecom/bin_to_com.c164
-rw-r--r--gbdk/gbdk-support/makecom/bin_to_com.h31
-rw-r--r--gbdk/gbdk-support/makecom/common.h25
-rw-r--r--gbdk/gbdk-support/makecom/files.c62
-rw-r--r--gbdk/gbdk-support/makecom/files.h7
-rw-r--r--gbdk/gbdk-support/makecom/list.c70
-rw-r--r--gbdk/gbdk-support/makecom/list.h20
-rw-r--r--gbdk/gbdk-support/makecom/makecom.c147
-rw-r--r--gbdk/gbdk-support/makecom/noi_file.c164
-rw-r--r--gbdk/gbdk-support/makecom/noi_file.h40
-rw-r--r--gbdk/gbdk-support/makecom/pascal/Makefile31
-rw-r--r--gbdk/gbdk-support/makecom/pascal/makecom.dpr184
-rw-r--r--gbdk/gbdk-support/makecom/path_ops.c145
-rw-r--r--gbdk/gbdk-support/makecom/path_ops.h15
15 files changed, 1137 insertions, 0 deletions
diff --git a/gbdk/gbdk-support/makecom/Makefile b/gbdk/gbdk-support/makecom/Makefile
new file mode 100644
index 00000000..7e46e311
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/Makefile
@@ -0,0 +1,32 @@
+# makecom makefile
+
+ifndef TARGETDIR
+TARGETDIR = /opt/gbdk
+endif
+
+ifeq ($(OS),Windows_NT)
+ BUILD_OS := Windows_NT
+else
+ BUILD_OS := $(shell uname -s)
+endif
+
+# Target older macOS version than whatever build OS is for better compatibility
+ifeq ($(BUILD_OS),Darwin)
+ export MACOSX_DEPLOYMENT_TARGET=10.10
+endif
+
+CC = $(TOOLSPREFIX)gcc
+CFLAGS = -ggdb -O -Wno-incompatible-pointer-types -DGBDKLIBDIR=\"$(TARGETDIR)\"
+OBJ = makecom.o noi_file.o files.o bin_to_com.o list.o path_ops.o
+BIN = makecom
+
+all: $(BIN)
+
+$(BIN): $(OBJ)
+
+clean:
+ rm -f *.o $(BIN) *~
+ rm -f tmp.*
+ rm -f *.exe
+
+
diff --git a/gbdk/gbdk-support/makecom/bin_to_com.c b/gbdk/gbdk-support/makecom/bin_to_com.c
new file mode 100644
index 00000000..dd3b7722
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/bin_to_com.c
@@ -0,0 +1,164 @@
+// This is free and unencumbered software released into the public domain.
+// For more information, please refer to <https://unlicense.org>
+// bbbbbr 2020
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "common.h"
+#include "list.h"
+#include "files.h"
+#include "noi_file.h"
+#include "bin_to_com.h"
+
+
+uint8_t * banks[BANKS_MAX_COUNT] = {NULL};
+uint16_t banks_len[BANKS_MAX_COUNT] = {0};
+uint16_t banks_count = 0;
+
+
+void banks_cleanup(void) {
+ for (int c = 0; c <= BANKS_MAX_ID; c++) {
+ if (banks[c] != NULL) {
+
+ free(banks[c]);
+ banks[c] = NULL;
+ }
+ }
+}
+
+
+// Allocate memory for banks as banks are requested
+// If preceding banks are not yet allocated then do those too
+uint8_t * bank_get_ptr(uint16_t bank_num) {
+
+ if (bank_num > BANKS_MAX_ID) {
+ printf("makecom: ERROR: Requested bank %d is larger than max bank num %d!\n", bank_num, BANKS_MAX_ID);
+ exit(EXIT_FAILURE);
+ }
+
+ if (banks[bank_num] == NULL) {
+
+ // Make sure all banks leading up to requested are also allocated
+ for (int c = 0; c <= bank_num; c++) {
+ if (banks[c] == NULL) {
+
+ banks[c] = malloc(BANK_SIZE);
+
+ if (!banks[c]) {
+ printf("makecom: ERROR: Failed to allocate memory for bank %d!\n", c);
+ exit(EXIT_FAILURE);
+ }
+
+ // zero out buffer (though unused space will not get written out)
+ memset(banks[c], 0x00u, BANK_SIZE);
+ }
+ }
+ }
+
+ return banks[bank_num];
+}
+
+
+
+// Copy data from source bin file at offset address into requested bank buffer
+void copy_to_bank(uint16_t bank_num, uint32_t rom_src_addr, uint32_t bank_out_addr, uint32_t length) {
+
+ // TODO: make sure bin src addr doesn't exceed source file buffer length
+ if ((rom_src_addr + length) > rom_buf_in_len) {
+ printf("makecom: ERROR: Can't copy %d bytes from %x for bank %d, would overflow past end of ROM source buffer of size %d!\n", length, rom_src_addr, bank_num, (uint32_t)rom_buf_in_len);
+ exit(EXIT_FAILURE);
+ }
+
+ if ((bank_out_addr + length) > BANK_SIZE) {
+ printf("makecom: ERROR: Can't copy %d bytes to %04x for bank %d, would overflow past end of bank buffer of size %d!\n",length, bank_out_addr, bank_num, BANK_SIZE);
+ exit(EXIT_FAILURE);
+ }
+
+ if (bank_num > banks_count)
+ banks_count = bank_num;
+
+ // Update length of bank data buffers (to allow for truncating output later)
+ if ((bank_out_addr + length) > banks_len[bank_num])
+ banks_len[bank_num] = (bank_out_addr + length);
+
+ // printf("* copying... bank:%d from:%x to:%x len:%d\n",
+ // bank_num, rom_src_addr, bank_out_addr, length);
+
+ memcpy(bank_get_ptr(bank_num) + bank_out_addr, p_rom_buf_in + rom_src_addr, length);
+}
+
+
+void banks_write_out(void) {
+
+ // If any of below fails it will trigger exit() and cleanup() will be called
+
+ // Write first bank out, handled differently than rest
+ if ((banks[0] != NULL) && (banks_len[0] > 0)) {
+ // printf("Bank %d: Writing %d bytes to %s\n",0, banks_len[0], filename_out_com);
+ file_write_from_buffer(filename_out_com, banks[0], banks_len[0]);
+ } else {
+ printf("makecom: Error: Bank 0 was empty, no data written\n");
+ exit;
+ }
+
+ // Write out remaining banks if applicable
+ char bank_fname[MAX_STR_LEN] = "";
+ for (int c = 1; c <= BANKS_MAX_ID; c++) {
+ if ((banks[c] != NULL) && (banks_len[0] > 0)) {
+ // Format to 8.3 filename with bank num as zero padded extension
+ sprintf(bank_fname, "%s.%03d", filename_banks_base, c);
+ // printf("Bank %d: Writing %d bytes to %s\n",c, banks_len[c], bank_fname);
+ file_write_from_buffer(bank_fname, banks[c], banks_len[c]);
+ } else
+ break; // Exit loop on first unpopulated bank
+ }
+
+}
+
+
+
+int bin2com(void) {
+
+ symbol_item * symbols = (symbol_item *)symbol_list.p_array;
+
+ for(int c = 0; c < symbol_list.count; c++) {
+
+ // Only process completed symbols (start and length both set, lenth not zero)
+ if ((symbols[c].addr_start != SYM_VAL_UNSET) &&
+ (symbols[c].length != SYM_VAL_UNSET) &&
+ (symbols[c].length > 0)) {
+
+ // If symbol is for a bank > 0
+ if ((strncmp(symbols[c].name, "_CODE_", (sizeof("_CODE_") - 1)) == 0) && (symbols[c].bank_num > 0)) {
+ // Copy data from bank addr in source ROM to start of separate bank buffer
+ copy_to_bank(symbols[c].bank_num, symbols[c].src_rom_addr, BANK_START_ADDR, symbols[c].length);
+ } else {
+ // For remaining symbols (assume bank 0), relocate them by -100 when possible (addr -> addr - 100)
+ copy_to_bank(BANK_0, symbols[c].addr_start, BANK_0_RELOC_ADDR(symbols[c].addr_start), symbols[c].length);
+ }
+ }
+ }
+
+ // Patch in updated bank / overlay count and bank filename
+ if ((banks_count > 0) && (overlay_count_addr != SYM_VAL_UNSET) && (overlay_name_addr != SYM_VAL_UNSET)) {
+
+ if (overlay_count_addr > BANK_0_ADDR_OFFSET)
+ *(banks[0] + (overlay_count_addr - BANK_0_ADDR_OFFSET)) = banks_count;
+
+ if (overlay_name_addr > BANK_0_ADDR_OFFSET)
+ memcpy(banks[0] + (overlay_name_addr - BANK_0_ADDR_OFFSET), filename_overlay, COM_OVERLAY_NAME_LEN-1);
+ }
+
+ // No write the data out
+ banks_write_out();
+
+ return EXIT_SUCCESS;
+}
+
+
+
diff --git a/gbdk/gbdk-support/makecom/bin_to_com.h b/gbdk/gbdk-support/makecom/bin_to_com.h
new file mode 100644
index 00000000..79d38c2b
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/bin_to_com.h
@@ -0,0 +1,31 @@
+// This is free and unencumbered software released into the public domain.
+// For more information, please refer to <https://unlicense.org>
+// bbbbbr 2020
+
+#ifndef _BIN_TO_COM_H
+#define _BIN_TO_COM_H
+
+#define MAX(a, b) ((a > b) ? a : b)
+#define BANKS_MAX_COUNT 256
+#define BANKS_MAX_ID (BANKS_MAX_COUNT - 1)
+#define BANK_SIZE (0x4000u)
+#define BANK_START_ADDR (0x0000u)
+#define BANK_0 0
+#define BANK_0_ADDR_OFFSET 0x100
+
+#define BANK_GET_NUM(addr) ((addr & 0xFFFF0000U) >> 16)
+#define WITHOUT_BANK(addr) (addr & 0x0000FFFFU)
+// Converts a symbol bank address such as 0x34000 to a bin/rom address such as 0x12000 (i.e: ~0x4000 * 3)
+// Make sure base bank addess doesn't go negative
+#define BANK_GET_ROM_ADDR(addr) ( (MAX(WITHOUT_BANK(addr), BANK_SIZE) - BANK_SIZE) + (BANK_SIZE * BANK_GET_NUM(addr)) )
+
+// Apply bank 0 address offset and make sure it doesn't go negative
+#define BANK_0_RELOC_ADDR(addr) (MAX((addr), BANK_0_ADDR_OFFSET) - BANK_0_ADDR_OFFSET)
+
+void copy_data(uint16_t bank_num, uint32_t rom_src_addr, uint32_t bank_out_addr, uint32_t length);
+uint8_t * bank_get_ptr(uint16_t bank_num);
+void copy_to_bank(uint16_t bank_num, uint32_t rom_src_addr, uint32_t bank_out_addr, uint32_t length);
+void banks_cleanup(void);
+int bin2com(void);
+
+#endif // _BIN_TO_COM_H \ No newline at end of file
diff --git a/gbdk/gbdk-support/makecom/common.h b/gbdk/gbdk-support/makecom/common.h
new file mode 100644
index 00000000..b38600aa
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/common.h
@@ -0,0 +1,25 @@
+#ifndef _COMMON_H
+#define _COMMON_H
+
+#include <stdint.h>
+
+#define ARRAY_LEN(A) (sizeof(A) / sizeof(A[0]))
+
+#define MAX_STR_LEN 4096
+#define MAX_FILE_STR (MAX_STR_LEN)
+#define SYM_MAX_STR_LEN 1024
+
+#define BANK_FNAME_LEN (8+1+3 +1) // 8.3 filename style + terminator, ex: MYCOMFIL.001 (from "mycomfile.com")
+#define COM_OVERLAY_NAME_LEN (8 + 1) // 8 chars for overlay string + terminator "MYCOMFIL"
+
+
+extern char filename_in_bin[];
+extern char filename_in_noi[];
+extern char filename_out_com[];
+extern char filename_banks_base[];
+extern char filename_overlay[];
+
+extern uint8_t * p_rom_buf_in;
+extern size_t rom_buf_in_len;
+
+#endif // _COMMON_H \ No newline at end of file
diff --git a/gbdk/gbdk-support/makecom/files.c b/gbdk/gbdk-support/makecom/files.c
new file mode 100644
index 00000000..c6f78e9d
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/files.c
@@ -0,0 +1,62 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+
+
+// Read from a file into a buffer (will allocate needed memory)
+// Returns NULL if reading file didn't succeed
+uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size) {
+
+ long fsize;
+ FILE * file_in = fopen(filename, "rb");
+ uint8_t * filedata = NULL;
+
+ if (file_in) {
+ // Get file size
+ fseek(file_in, 0, SEEK_END);
+ fsize = ftell(file_in);
+ if (fsize != -1L) {
+ fseek(file_in, 0, SEEK_SET);
+
+ filedata = malloc(fsize);
+ if (filedata) {
+ if (fsize != fread(filedata, 1, fsize, file_in)) {
+ printf("makecom: Warning: File read size didn't match expected for %s\n", filename);
+ filedata = NULL;
+ }
+ // Read was successful, set return size
+ *ret_size = fsize;
+ } else printf("makecom: ERROR: Failed to allocate memory to read file %s\n", filename);
+
+ } else printf("makecom: ERROR: Failed to read size of file %s\n", filename);
+
+ fclose(file_in);
+ } else printf("makecom: ERROR: Failed to open input file %s\n", filename);
+
+ return filedata;
+}
+
+
+
+// Writes a buffer to a file
+bool file_write_from_buffer(char * filename, uint8_t * p_buf, uint32_t data_len) {
+
+ bool status = false;
+ size_t wrote_bytes;
+ FILE * file_out = fopen(filename, "wb");
+ if (file_out) {
+ if (data_len == fwrite(p_buf, 1, data_len, file_out))
+ status = true;
+ else
+ printf("makecom: Warning: File write size didn't match expected for %s\n", filename);
+
+ fclose(file_out);
+ } else {
+ printf("makecom: ERROR: Failed to open output file %s!\n",filename);
+ exit(EXIT_FAILURE);
+ }
+
+ return status;
+}
diff --git a/gbdk/gbdk-support/makecom/files.h b/gbdk/gbdk-support/makecom/files.h
new file mode 100644
index 00000000..c027b173
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/files.h
@@ -0,0 +1,7 @@
+#ifndef _FILES_H
+#define _FILES_H
+
+uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size);
+bool file_write_from_buffer(char * filename, uint8_t * p_buf, uint32_t data_len);
+
+#endif // _FILES_H
diff --git a/gbdk/gbdk-support/makecom/list.c b/gbdk/gbdk-support/makecom/list.c
new file mode 100644
index 00000000..868bee3a
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/list.c
@@ -0,0 +1,70 @@
+// This is free and unencumbered software released into the public domain.
+// For more information, please refer to <https://unlicense.org>
+// bbbbbr 2020
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "common.h"
+#include "list.h"
+
+#define LIST_GROW_SIZE 100 // 50 // grow array by N entries at a time
+
+// Initialize the list and it's array
+// typesize *must* match the type that will be used with the array
+void list_init(list_type * p_list, size_t array_typesize) {
+ p_list->typesize = array_typesize;
+ p_list->count = 0;
+ p_list->size = LIST_GROW_SIZE;
+ p_list->p_array = (void *)malloc(p_list->size * p_list->typesize);
+
+ if (!p_list->p_array) {
+ printf("makecom: ERROR: Failed to allocate memory for list!\n");
+ exit(EXIT_FAILURE);
+ }
+}
+
+
+// Free the array memory allocated for the list
+void list_cleanup(list_type * p_list) {
+ if (p_list->p_array) {
+ free (p_list->p_array);
+ p_list->p_array = NULL;
+ }
+}
+
+
+// Add a new item to the lists array, resize if needed
+// p_newitem *must* be the same type the list was initialized with
+void list_additem(list_type * p_list, void * p_newitem) {
+
+ void * tmp_list;
+
+ p_list->count++;
+
+ // Grow array if needed
+ if (p_list->count == p_list->size) {
+ // Save a copy in case reallocation fails
+ tmp_list = p_list->p_array;
+
+ p_list->size += p_list->typesize * LIST_GROW_SIZE;
+ p_list->p_array = (void *)realloc(p_list->p_array, p_list->size * p_list->typesize);
+ // If realloc failed, free original buffer before quitting
+ if (!p_list->p_array) {
+ printf("makecom: ERROR: Failed to reallocate memory for list!\n");
+ if (tmp_list) {
+ free(tmp_list);
+ tmp_list = NULL;
+ }
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ // Copy new entry
+ memcpy((uint_least8_t *)p_list->p_array + ((p_list->count - 1) * p_list->typesize),
+ p_newitem,
+ p_list->typesize);
+}
+
diff --git a/gbdk/gbdk-support/makecom/list.h b/gbdk/gbdk-support/makecom/list.h
new file mode 100644
index 00000000..ef9d3d52
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/list.h
@@ -0,0 +1,20 @@
+// This is free and unencumbered software released into the public domain.
+// For more information, please refer to <https://unlicense.org>
+// bbbbbr 2020
+
+#ifndef _LIST_H
+#define _LIST_H
+
+
+typedef struct list_type {
+ void * p_array;
+ uint32_t size;
+ uint32_t count;
+ size_t typesize;
+} list_type;
+
+void list_init(list_type *, size_t);
+void list_cleanup(list_type *);
+void list_additem(list_type *, void *);
+
+#endif // _LIST_H \ No newline at end of file
diff --git a/gbdk/gbdk-support/makecom/makecom.c b/gbdk/gbdk-support/makecom/makecom.c
new file mode 100644
index 00000000..a6abd51d
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/makecom.c
@@ -0,0 +1,147 @@
+// This is free and unencumbered software released into the public domain.
+// For more information, please refer to <https://unlicense.org>
+// bbbbbr 2022
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include "path_ops.h"
+#include "common.h"
+#include "files.h"
+#include "noi_file.h"
+#include "bin_to_com.h"
+
+#define EXT_NOI "noi"
+
+char filename_in_bin[MAX_STR_LEN] = "";
+char filename_in_noi[MAX_STR_LEN] = "";
+char filename_out_com[MAX_STR_LEN] = "";
+char filename_banks_base[MAX_STR_LEN] = "";
+char filename_overlay[COM_OVERLAY_NAME_LEN] = "";
+
+uint8_t * p_rom_buf_in = NULL;
+size_t rom_buf_in_len = 0;
+
+static void str_to_upper(char * str);
+static void filenames_out_prepare(void);
+static void display_help(void);
+static int handle_args(int argc, char * argv[]);
+void cleanup(void);
+
+
+static void str_to_upper(char * str) {
+ while (*str) {
+ *str = toupper(*str);
+ str++;
+ }
+}
+
+
+
+// Generate names for the exported bank/overlay files
+// based on the output COM filename and path
+static void filenames_out_prepare(void) {
+
+ char str_banks_name[MAX_STR_LEN];
+ char str_path_only[MAX_STR_LEN];
+
+ snprintf(str_banks_name, sizeof(str_banks_name), "%s", filename_out_com);
+
+ if (!get_path_without_filename(filename_out_com, str_path_only, sizeof(str_path_only))) {
+ printf("makecom: Error: source path %s exceeds max length\n", filename_out_com);
+ exit;
+ }
+
+ // Convert filename to uppercase and remove extension
+ str_to_upper(str_banks_name);
+ filename_remove_extension(str_banks_name);
+
+ // Then truncate it to 8 characters with no padding for bank filenames
+ int ret = snprintf(filename_banks_base, sizeof(filename_banks_base), "%s%.8s", str_path_only, get_filename_from_path(str_banks_name));
+ if (ret < 0) printf("makecom: Warning, banks output path truncated\n");
+ // Then with trailing space char padding for the overlay filename patch
+ snprintf(filename_overlay, sizeof(filename_overlay), "%-8.8s", get_filename_from_path(str_banks_name));
+}
+
+
+static void display_help(void) {
+
+ fprintf(stdout,
+ "makecom image.rom image.noi output.com\n"
+ "Use: convert a binary .rom file to .msxdos com format.\n"
+ );
+}
+
+
+int handle_args(int argc, char * argv[]) {
+
+ if (argc == 3) {
+ // Copy input and output filenames from arguments
+ snprintf(filename_in_bin, sizeof(filename_in_bin), "%s", argv[1]);
+ snprintf(filename_out_com, sizeof(filename_out_com), "%s", argv[2]);
+ // Generate .noi file name from input .bin file name
+ snprintf(filename_in_noi, sizeof(filename_in_noi), "%s", argv[1]);
+ filename_replace_extension(filename_in_noi, EXT_NOI, MAX_STR_LEN);
+ } else if (argc == 4) {
+ // Copy input and output filenames from arguments
+ snprintf(filename_in_bin, sizeof(filename_in_bin), "%s", argv[1]);
+ snprintf(filename_in_noi, sizeof(filename_in_noi), "%s", argv[2]);
+ snprintf(filename_out_com, sizeof(filename_out_com), "%s", argv[3]);
+ } else {
+ display_help();
+ return false;
+ }
+
+ filenames_out_prepare();
+
+
+ // printf("bin: %s\n",filename_in_bin);
+ // printf("noi: %s\n",filename_in_noi);
+ // printf("com: %s\n",filename_out_com);
+
+ // printf("banks base: _%s_\n",filename_banks_base);
+ // printf("overlay : _%s_\n",filename_overlay);
+
+
+ return true;
+}
+
+
+// Registered as atexit() handler
+// Free resources during normal shutdown or from a call to exit()
+void cleanup(void) {
+
+ banks_cleanup();
+ noi_cleanup();
+
+ if (p_rom_buf_in != NULL) {
+ free(p_rom_buf_in);
+ p_rom_buf_in = NULL;
+ }
+}
+
+
+int main( int argc, char *argv[] ) {
+
+ // Exit with failure by default
+ int ret = EXIT_FAILURE;
+
+ // Register cleanup with exit handler
+ atexit(cleanup);
+
+ if (handle_args(argc, argv)) {
+
+ p_rom_buf_in = file_read_into_buffer(filename_in_bin, &rom_buf_in_len);
+
+ if (p_rom_buf_in)
+ if (noi_file_load_symbols(filename_in_noi))
+ ret = bin2com();
+ }
+ cleanup();
+
+ return ret; // Exit with failure by default
+}
diff --git a/gbdk/gbdk-support/makecom/noi_file.c b/gbdk/gbdk-support/makecom/noi_file.c
new file mode 100644
index 00000000..f2fc935d
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/noi_file.c
@@ -0,0 +1,164 @@
+// This is free and unencumbered software released into the public domain.
+// For more information, please refer to <https://unlicense.org>
+// bbbbbr 2020
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "common.h"
+#include "noi_file.h"
+#include "bin_to_com.h"
+
+// Example data to parse from a .noi file:
+/*
+DEF s__CODE 0x200
+DEF l__BASE 0x2A3
+DEF __shadow_OAM_OFF 0x455
+DEF .mode 0x456
+*/
+
+const char * known_section_names[] =
+ {"_CODE","_HOME","_BASE","_CODE_0","_INITIALIZER","_LIT","_GSINIT","_GSFINAL"};
+
+
+list_type symbol_list;
+uint32_t overlay_count_addr = SYM_VAL_UNSET;
+uint32_t overlay_name_addr = SYM_VAL_UNSET;
+
+
+// Initialize the symbol list
+void noi_init(void) {
+
+ list_init(&symbol_list, sizeof(symbol_item));
+}
+
+
+// Free the symbol list
+void noi_cleanup(void) {
+
+ list_cleanup(&symbol_list);
+}
+
+
+// Find a matching symbol, if none matches a new one is added and returned
+static int symbollist_get_id_by_name(char * symbol_name) {
+
+ symbol_item * symbols = (symbol_item *)symbol_list.p_array;
+
+ // Check for matching symbol name
+ for(int c = 0;c < symbol_list.count; c++) {
+ // Return matching symbol index if present
+ if (strcmp(symbol_name, symbols[c].name) == 0) {
+ return c;
+ }
+ }
+
+ // no match was found, add symbol
+ symbol_item new_symbol = {.name = "", .addr_start = SYM_VAL_UNSET, .length = SYM_VAL_UNSET, .bank_num = 0x00, .src_rom_addr = SYM_VAL_UNSET};
+ snprintf(new_symbol.name, sizeof(new_symbol.name), "%s", symbol_name);
+ list_additem(&symbol_list, &new_symbol);
+
+ return (symbol_list.count - 1);
+}
+
+
+// Add the start address or length for a s_ or l_ symbol record
+static void noi_symbollist_add(char rec_type, char * name, char * value) {
+
+ // Only add symbols if they're in the known section name list
+ for (int c = 0; c < ARRAY_LEN(known_section_names); c++) {
+
+ if ((strcmp(name, known_section_names[c]) == 0) ||
+ (strncmp(name, "_CODE_", (sizeof("_CODE_") - 1)) == 0)) {
+
+ symbol_item * symbols = (symbol_item *)symbol_list.p_array;
+
+ // Check to see if there is an existing record to update
+ // If one isn't found a new record will have been created automatically
+ int symbol_id = symbollist_get_id_by_name(name);
+ if (symbol_id != ERR_NO_SYMBOLS_LEFT) {
+
+ // Handle whether it's a start-of-address or a length record for the given symbol
+ if (rec_type == NOI_REC_START) {
+ symbols[symbol_id].addr_start = strtol(value, NULL, 16);
+ symbols[symbol_id].bank_num = BANK_GET_NUM(symbols[symbol_id].addr_start);
+ symbols[symbol_id].src_rom_addr = BANK_GET_ROM_ADDR(symbols[symbol_id].addr_start);
+ }
+ else if (rec_type == NOI_REC_LENGTH) {
+ symbols[symbol_id].length = strtol(value, NULL, 16);
+ }
+ }
+
+ return;
+ }
+ }
+}
+
+
+// Load symbols from a .noi file and create paired records for start & length entries
+// Plus, look for and store for overlay symbol information
+int noi_file_load_symbols(char * filename_in) {
+
+ char cols;
+ char * p_str;
+ char * p_words[MAX_SPLIT_WORDS];
+ char strline_in[MAX_STR_LEN] = "";
+ FILE * noi_file = fopen(filename_in, "r");
+ symbol_item symbol;
+ int symbol_id;
+
+ noi_init();
+
+ if (noi_file) {
+
+ // Read one line at a time into \0 terminated string
+ while ( fgets(strline_in, sizeof(strline_in), noi_file) != NULL) {
+
+ // Require minimum length to match
+ if (strlen(strline_in) >= NOI_REC_START_LEN) {
+
+ // Split string into words separated by spaces
+ cols = 0;
+ p_str = strtok(strline_in," ");
+ while (p_str != NULL)
+ {
+ p_words[cols++] = p_str;
+ // Only split on underscore for the second match
+ p_str = strtok(NULL, " ");
+ if (cols >= MAX_SPLIT_WORDS) break;
+ }
+
+ // [0] = "DEF"
+ // [1] = symbol name
+ // [2] = symbol value
+ if (cols >= NOI_REC_COUNT_MATCH) {
+
+ // If it matches either _s_egment or _l_ength records (first two chars)
+ // then add a record for it with the first two chars truncated
+ if ((strncmp(p_words[1], "l_", NOI_REC_S_L_CHK) == 0) ||
+ (strncmp(p_words[1], "s_", NOI_REC_S_L_CHK) == 0)) {
+ noi_symbollist_add(p_words[1][0], p_words[1] + NOI_REC_S_L_CHK, p_words[2]);
+ }
+ // Otherwise check for records of interest and store them
+ else if (strcmp(p_words[1], "___overlay_count") == 0) {
+ overlay_count_addr = strtol(p_words[2], NULL, 16);
+ }
+ else if (strcmp(p_words[1], "___overlay_name") == 0) {
+ overlay_name_addr = strtol(p_words[2], NULL, 16);
+ }
+ }
+ } // end: valid min chars to process line
+
+ } // end: while still lines to process
+
+ fclose(noi_file);
+
+ } // end: if valid file
+ else return (false);
+
+ return true;
+}
diff --git a/gbdk/gbdk-support/makecom/noi_file.h b/gbdk/gbdk-support/makecom/noi_file.h
new file mode 100644
index 00000000..b6d873b7
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/noi_file.h
@@ -0,0 +1,40 @@
+// This is free and unencumbered software released into the public domain.
+// For more information, please refer to <https://unlicense.org>
+// bbbbbr 2020
+
+#ifndef _NOI_FILE_H
+#define _NOI_FILE_H
+
+#include "list.h"
+
+#define MAX_STR_LEN 4096
+#define MAX_SPLIT_WORDS 4
+
+#define NOI_REC_COUNT_MATCH 3 // "DEF ...symbol_name... ...symbol_value...
+
+#define ERR_NO_SYMBOLS_LEFT -1
+#define SYM_VAL_UNSET 0xFFFFFFFF
+
+#define NOI_REC_START_LEN 4 // length of "DEF "
+#define NOI_REC_S_L_CHK 2 // length of "s_" or "l_"
+#define NOI_REC_START 's'
+#define NOI_REC_LENGTH 'l'
+
+typedef struct symbol_item {
+ char name[SYM_MAX_STR_LEN];
+ uint32_t addr_start;
+ uint32_t length;
+ uint16_t bank_num;
+ uint32_t src_rom_addr;
+} symbol_item;
+
+extern list_type symbol_list;
+extern uint32_t overlay_count_addr;
+extern uint32_t overlay_name_addr;
+
+
+int noi_file_load_symbols(char * filename_in);
+void noi_init(void);
+void noi_cleanup(void);
+
+#endif // _NOI_FILE_H \ No newline at end of file
diff --git a/gbdk/gbdk-support/makecom/pascal/Makefile b/gbdk/gbdk-support/makecom/pascal/Makefile
new file mode 100644
index 00000000..5a1d29ed
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/pascal/Makefile
@@ -0,0 +1,31 @@
+# bankcheck (auto bank tool) makefile
+
+ifndef TARGETDIR
+TARGETDIR = /opt/gbdk
+endif
+
+ifeq ($(OS),Windows_NT)
+ BUILD_OS := Windows_NT
+else
+ BUILD_OS := $(shell uname -s)
+endif
+
+# Target older macOS version than whatever build OS is for better compatibility
+ifeq ($(BUILD_OS),Darwin)
+ export MACOSX_DEPLOYMENT_TARGET=10.10
+endif
+
+CC =
+CFLAGS =
+OBJ =
+BIN = makecom
+
+all: $(BIN)
+
+$(BIN): makecom.dpr
+ fpc -B $<
+
+clean:
+ rm -f *.o $(BIN) *~ *.dof *.cfg *.dsk
+ rm -f *.exe
+
diff --git a/gbdk/gbdk-support/makecom/pascal/makecom.dpr b/gbdk/gbdk-support/makecom/pascal/makecom.dpr
new file mode 100644
index 00000000..c2164095
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/pascal/makecom.dpr
@@ -0,0 +1,184 @@
+{$APPTYPE CONSOLE}
+{$IFDEF FPC}
+ {$MODE DELPHI}
+{$ENDIF}
+uses classes, sysutils, math;
+
+procedure ERROR(const msg: ansistring; const params: array of const);
+begin
+ writeln(format(msg, params));
+ writeln('USAGE: makecom <image.bin> [<image.noi>] <output.com>');
+ halt(1);
+end;
+
+procedure DecodeCommaText(const Value: ansistring; result: tStringList; adelimiter: ansichar);
+var P, P1 : PAnsiChar;
+ S : ansistring;
+begin
+ if assigned(result) then begin
+ result.BeginUpdate;
+ try
+ result.Clear;
+ P := PChar(Value);
+ while P^ in [#1..#31] do inc(P);
+ while P^ <> #0 do
+ begin
+ if P^ = '"' then
+ S := AnsiExtractQuotedStr(P, '"')
+ else
+ begin
+ P1 := P;
+ while (P^ >= ' ') and (P^ <> adelimiter) do inc(P);
+ SetString(S, P1, P - P1);
+ end;
+ result.Add(S);
+ while P^ in [#1..#31] do inc(P);
+ if P^ = adelimiter then
+ repeat
+ inc(P);
+ until not (P^ in [#1..#31]);
+ end;
+ finally
+ result.EndUpdate;
+ end;
+ end;
+end;
+
+function load_symbols(const filename: ansistring; symbols: tStringList): boolean;
+var s : textfile;
+ str : ansistring;
+ row : tStringList;
+begin
+ result:= assigned(symbols);
+ if result then begin
+ assignfile(s, filename); reset(s);
+ try
+ row := tStringList.create;
+ try
+ while not eof(s) do begin
+ readln(s, str);
+ DecodeCommaText(str, row, ' ');
+ if (row[0] = 'DEF') then begin
+ symbols.Values[row[1]] := row[2];
+ end;
+ end;
+ result:= true;
+ finally freeandnil(row); end;
+ finally closefile(s); end;
+ end;
+end;
+
+function CopyData(banks: tList; bank: longint; image: tMemoryStream; source_ofs, dest_ofs: longint; len: longint): boolean;
+var i : longint;
+ data : tMemoryStream;
+begin
+ result:= (bank < 255);
+ if result then begin
+ if (banks.count <= bank) then
+ for i:= banks.count to bank do banks.Add(tMemoryStream.Create());
+ data:= banks[bank];
+ data.Seek(dest_ofs, soFromBeginning);
+ data.Write(pAnsiChar(image.Memory)[source_ofs], len);
+ end;
+end;
+procedure WriteData(banks: tList; const destname: ansistring);
+var i : longint;
+ ovr : ansistring;
+begin
+ if (banks.count > 0) then begin
+ with tMemoryStream(banks[0]) do try
+ writeln(format('writing program: %s', [destname]));
+ SaveToFile(destname);
+ finally free; end;
+ for i:= 1 to banks.count - 1 do
+ with tMemoryStream(banks[i]) do try
+ ovr:= ChangeFileExt(destname, format('.%.3d', [i]));
+ writeln(format('writing overlay: %s', [ovr]));
+ SaveToFile(ovr);
+ finally free; end;
+ end;
+end;
+
+function Hex2Int(value: ansistring): longint;
+begin
+ if (copy(value, 1, 2)) = '0x' then begin value[1]:= ' '; value[2]:= '$'; end;
+ result:= StrToIntDef(value, 0);
+end;
+
+const section_names = '_CODE,_HOME,_BASE,_CODE_0,_INITIALIZER,_LIT,_GSINIT,_GSFINAL';
+var name_bin : ansistring;
+ name_noi : ansistring;
+ name_out : ansistring;
+ symbols : tStringList;
+ known : tStringList;
+ banks : tList;
+ source : tMemoryStream;
+ i, bank : longint;
+ name, v, l : ansistring;
+ addr, len : longint;
+begin
+ if (paramcount() = 2) then begin
+ name_bin:= paramstr(1); name_noi:= changefileext(name_bin, '.noi'); name_out:= paramstr(2);
+ end else begin
+ if (paramcount() < 3) then ERROR('ERROR: Not sufficient parameters', []);
+ name_bin:= paramstr(1); name_noi:= paramstr(2); name_out:= paramstr(3);
+ end;
+
+ if not fileexists(name_noi) then ERROR('ERROR: symbol file: "%s" not found', [name_noi]);
+ if not fileexists(name_bin) then ERROR('ERROR: binary image: "%s" not found', [name_bin]);
+
+ known:= tStringList.create;
+ symbols := tStringList.create;
+ try
+ DecodeCommaText(section_names, known, ',');
+ if load_symbols(name_noi, symbols) then begin
+ source:= tMemoryStream.Create;
+ try
+ source.LoadFromFile(name_bin);
+ banks:= tList.Create;
+ with banks do try
+ for i:= 0 to symbols.count - 1 do begin
+ name:= symbols.Names[i];
+ v:= symbols.Values[name];
+ if (copy(name, 1, 2) = 's_') then begin
+ name:= copy(name, 3, length(name));
+ l:= symbols.Values[format('l_%s',[name])];
+ if length(l) > 0 then begin
+ addr:= Hex2Int(v);
+ len:= Hex2Int(l);
+
+ if (len > 0) then begin
+ if (known.IndexOf(name) >= 0) then begin
+ CopyData(banks, 0, source, addr, math.max(0, addr - $100), len);
+ end;
+ if (copy(Name, 1, 6) = '_CODE_') then begin
+ bank:= addr shr 16;
+ CopyData(banks, bank, source, math.max(0, (addr and $ffff) - $4000) + $4000 * bank, 0, len);
+ end;
+ end;
+ end;
+ end;
+ end;
+
+ if (banks.Count > 0) then begin
+ addr:= Hex2Int(symbols.Values['___overlay_count']);
+ if (addr > $100) then pAnsiChar(tMemoryStream(banks[0]).Memory)[addr - $100]:= chr(banks.Count - 1);
+ addr:= Hex2Int(symbols.Values['___overlay_name']);
+ if (addr > $100) then begin
+ name:= format('%-8.8s', [uppercase(changefileext(extractfilename(name_out), ''))]);
+ system.move(name[1], pAnsiChar(tMemoryStream(banks[0]).Memory)[addr - $100], 8);
+ end;
+ end;
+
+ writeln('writing...');
+ WriteData(banks, name_out);
+ writeln('done!');
+ finally banks.free; end;
+ finally freeandnil(source); end;
+ end;
+ finally
+ freeandnil(symbols);
+ freeandnil(known);
+ end;
+
+end. \ No newline at end of file
diff --git a/gbdk/gbdk-support/makecom/path_ops.c b/gbdk/gbdk-support/makecom/path_ops.c
new file mode 100644
index 00000000..974a4870
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/path_ops.c
@@ -0,0 +1,145 @@
+// This is free and unencumbered software released into the public domain.
+// For more information, please refer to <https://unlicense.org>
+// bbbbbr 2020
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include "common.h"
+#include "path_ops.h"
+
+const char kExtensionSeparator = '.';
+const char kPathSeparator =
+
+#ifdef _WIN32
+ #ifndef _WIN32
+ #define __WIN32__
+ #endif
+#endif
+
+#ifdef __WIN32__
+ '\\';
+#else
+ '/';
+#endif
+
+const char kPathSeparator_unix = '/';
+
+
+void filename_replace_extension(char * filename, char * new_ext, size_t maxlen) {
+
+ // Use a temp work string in case out and in filename are the same pointer
+ char temp[MAX_FILE_STR];
+ char ext_sep[2] = {'\0'}; // default to empty string
+
+ // Add leading . to path if needed
+ if (new_ext[0] != kExtensionSeparator) {
+ ext_sep[0] = kExtensionSeparator;
+ ext_sep[1] = '\0';
+ }
+
+ // Strip extension from filename, append new extension
+ filename_remove_extension(filename);
+ snprintf(temp, maxlen, "%s%s%s", filename, ext_sep, new_ext);
+ snprintf(filename, maxlen, "%s", temp);
+}
+
+
+void filename_replace_path(char * filename, char * new_path, size_t maxlen) {
+
+ // Use a temp work string in case out and in filename are the same pointer
+ char temp[MAX_FILE_STR];
+ char path_sep[2] = {'\0'}; // default to empty string
+
+ // Add trailing slash to path if needed (Windows needs both for when running under linix like env)
+#ifdef __WIN32__
+ if (((new_path[(strlen(new_path)-1)] != kPathSeparator)) &&
+ ((new_path[(strlen(new_path)-1)] != kPathSeparator_unix)))
+#else
+ if ((new_path[(strlen(new_path)-1)] != kPathSeparator))
+#endif
+ {
+ path_sep[0] = kPathSeparator;
+ path_sep[1] = '\0';
+ }
+
+ // Strip path from path+filename, pre-pend new path
+ snprintf(temp, maxlen, "%s%s%s", new_path, path_sep, get_filename_from_path(filename));
+ snprintf(filename, maxlen, "%s", temp);
+}
+
+
+const char * get_filename_from_path(const char * path)
+{
+ size_t i;
+
+ // Returns string starting at last occurrance of path separator char
+ for(i = strlen(path) - 1; i; i--) {
+
+ // Add trailing slash to path if needed (Windows needs both for when running under linix like env)
+ #ifdef __WIN32__
+ if ((path[i] == kPathSeparator) || (path[i] == kPathSeparator_unix))
+ #else
+ if (path[i] == kPathSeparator)
+ #endif
+ {
+ return &path[i+1];
+ }
+ }
+ return path;
+}
+
+
+void filename_remove_extension(char * path)
+{
+ char * last_ext;
+ char * last_slash;
+
+ // Find the last path separator if present
+ // Starting from here ensures that no path ".." characters
+ // get mistaken as extension delimiters.
+ last_slash = strrchr (path, kExtensionSeparator);
+ if (!last_slash)
+ last_slash = path;
+
+ // Then check to see if there is an extension (starting with the last occurance of '.')
+ // (tries to remove *all* trailing extensions backward until the last slash)
+ last_ext = strrchr (last_slash, kExtensionSeparator);
+ while (last_ext) {
+ if (last_ext != NULL) {
+ // If an extension is found then overwrite it with a string terminator
+ *last_ext = '\0';
+ }
+ last_ext = strrchr (last_slash, kExtensionSeparator);
+ }
+}
+
+
+bool get_path_without_filename(const char * path, char * path_only, uint32_t str_max)
+{
+ size_t i;
+
+ if (strlen(path) + 1 > str_max)
+ return false;
+
+ for(i = strlen(path) - 1; i; i--) {
+
+ // Add trailing slash to path if needed (Windows needs both for when running under linix like env)
+ #ifdef __WIN32__
+ if ((path[i] == kPathSeparator) || (path[i] == kPathSeparator_unix))
+ #else
+ if (path[i] == kPathSeparator)
+ #endif
+ {
+ memcpy(path_only, path, i+1 );
+ path_only[i+1] = '\0';
+ return true;
+ }
+ }
+
+ // memcpy(path_only, path, strlen(path));
+ // No separater found, so no path
+ path_only[0] = '\0';
+ return true;
+}
+
diff --git a/gbdk/gbdk-support/makecom/path_ops.h b/gbdk/gbdk-support/makecom/path_ops.h
new file mode 100644
index 00000000..2ae8e3a0
--- /dev/null
+++ b/gbdk/gbdk-support/makecom/path_ops.h
@@ -0,0 +1,15 @@
+// This is free and unencumbered software released into the public domain.
+// For more information, please refer to <https://unlicense.org>
+// bbbbbr 2020
+
+#ifndef _PATH_OPS_H
+#define _PATH_OPS_H
+
+void filename_replace_extension(char * filename, char * new_ext, size_t maxlen);
+void filename_replace_path(char * filename, char * new_path, size_t maxlen);
+const char * get_filename_from_path(const char * path);
+void filename_remove_extension(char * path);
+bool get_path_without_filename(const char * path, char * path_only, uint32_t str_max);
+
+
+#endif // _PATH_OPS_H \ No newline at end of file