/* ihex Copyright (C) 2025 Thomas Albers Raviola <thomas@thomaslabs.org> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ #include <ihex.h> #include <util.h> #include <array.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <limits.h> #define MAX_IHEX_BYTE_COUNT 255 enum record_type { REC_DATA = 0x00, REC_EOF = 0x01, REC_EXT_SEG_ADDR = 0x02, REC_START_SEG_ADDR = 0x03, REC_EXT_LIN_ADDR = 0x04, REC_START_LIN_ADDR = 0x05, REC_UNKNOWN = 0xFF }; struct record { unsigned int length; unsigned int offset; unsigned int type; unsigned int checksum; }; static const char *current_file; static int skip_to_record(FILE *file) { int c; while ((c = fgetc(file)) != EOF) { if (c == ':') return 0; } return EOF; } #define HEX_UINT8 2 #define HEX_UINT16 4 static unsigned int read_hex(int digits, FILE *file) { int c; int i; unsigned int ret = 0; for (i = digits - 1; i >= 0; --i) { c = fgetc(file); if (c == EOF) die(current_file, "Unexpected end of line\n"); if (!isxdigit(c)) { die(current_file, "Invalid character at position %ld\n", ftell(file)); } if (c >= 'a') c = c - 'a' + 10; else if (c >= 'A') c = c - 'A' + 10; else c = c - '0'; ret |= c << (4 * i); } return ret; } static unsigned int read_data_record(const struct record *record, struct array *data, FILE *file) { char *p; unsigned int i; unsigned int b; unsigned int checksum = 0; for (i = 0; i < record->length; ++i) { p = array_append(data, NULL); b = read_hex(HEX_UINT8, file); *p = b; checksum += b; } return checksum; } #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) > (b) ? (b) : (a)) static void rebase_blocks(size_t num_blocks, struct block *blocks, long base_address) { size_t i; long offset = LONG_MAX; if (base_address < 0) return; for (i = 0; i < num_blocks; ++i) offset = MIN(offset, blocks[i].address); offset = base_address - offset; for (i = 0; i < num_blocks; ++i) blocks[i].address += offset; } int read_ihex_file(const char *filename, struct array *blocks, struct array *data, long base_address) { size_t num_blocks; size_t block_offset = blocks->length; struct block *new_blocks; FILE *file = fopen(filename, "r"); current_file = filename; while (!skip_to_record(file)) { unsigned int checksum = 0; struct record record; struct block *block; record.length = read_hex(HEX_UINT8, file); record.offset = read_hex(HEX_UINT16, file); record.type = read_hex(HEX_UINT8, file); checksum += record.length; checksum += ((record.offset >> 8) + record.offset) & 0xFF; checksum += record.type; if (record.type == REC_EOF) break; switch (record.type) { case REC_DATA: block = array_append(blocks, NULL); block->address = record.offset; block->length = record.length; block->offset = data->length; block->filename = filename; checksum += read_data_record(&record, data, file); break; default: die(current_file, "Invalid record type\n"); } record.checksum = read_hex(HEX_UINT8, file); if ((checksum + record.checksum) & 0xFF) die(current_file, "Checksum mismatch\n", filename); } fclose(file); num_blocks = blocks->length - block_offset; new_blocks = &((struct block *)blocks->data)[block_offset]; rebase_blocks(num_blocks, new_blocks, base_address); return 0; } static int blocks_collide(const struct block *a, const struct block *b) { return (((a->address >= b->address) && (a->address < b->address + b->length)) || ((b->address >= a->address) && (b->address < a->address + a->length))); } static int block_compar(const void *a, const void *b) { unsigned long x = ((const struct block *)a)->address; unsigned long y = ((const struct block *)b)->address; if (x < y) return -1; if (x > y) return 1; return 0; } int write_hex(struct array *_blocks, struct array *_data, FILE *file) { unsigned int i; unsigned long pos = 0; struct block *b; struct block *blocks; size_t num_blocks = _blocks->length; unsigned char *data; array_sort(_blocks, block_compar); blocks = (struct block *)_blocks->data; data = (unsigned char *)_data->data; for (i = 0; i < num_blocks; ++i) { b = &blocks[i]; if (i < num_blocks - 1 && blocks_collide(&b[0], &b[1])) { fprintf(stderr, "Block collision between files %s and %s\n", b[0].filename, b[1].filename); return -1; } for (; pos < b->address; ++pos) fputc(0xFF, file); fwrite(&data[b->offset], 1, b->length, file); pos += b->length; } return 0; }