/*
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;
}