Files
mazegame/tools/MSXtk/src/MSXhex.cpp
T

670 lines
19 KiB
C++

// ____________________________
// ██▀▀█▀▀██▀▀▀▀▀▀▀█▀▀█ │ ▄▄
// ██ ▀ █▄ ▀██▄ ▀ ▄█ ▄▀▀ █ │ ██▄ ▄███ ▀█▄▀
// █ █ █ ▀▀ ▄█ █ █ ▀▄█ █▄ │ ██ █ ▀█▄▄ ▄▀█▄
// ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀────────┘
// by Guillaume "Aoineko" Blanchard (aoineko@free.fr)
// available on GitHub (https://github.com/aoineko-fr/MSXgl/tree/main/tools/MSXtk)
// under CC-BY-SA free license (https://creativecommons.org/licenses/by-sa/2.0/)
//─────────────────────────────────────────────────────────────────────────────
// std
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <ctime>
#include <algorithm>
#include <vector>
#include <iostream>
#include <fstream>
#include <cstdarg>
#include <map>
// MSXtk
#include "MSXtk.h"
//=============================================================================
// DEFINES
//=============================================================================
#define MSXHEX_VERSION "1.0.0"
#define INVALID_ADDR 0xFFFF
// Record structure
struct Record
{
u8 Count;
u16 Address;
u8 Type;
std::vector<u8> Data;
u8 Checksum;
Record() : Count(0), Address(0), Type(0), Checksum(0) {}
};
// Record structure
struct Segment
{
u16 Base;
u16 Size;
u16 Lower;
u16 Higher;
Segment() : Base(INVALID_ADDR), Size(0), Lower(0xFFFF), Higher(0) {}
};
// RawData structure
struct RawData
{
u32 Offset;
std::vector<u8> Data;
RawData() : Offset(0) {}
};
// IHX Record Types
enum RecordType
{
Type_Data = 0,
Type_EndOfFile = 1,
Type_ExtendedSegmentAddress = 2,
Type_StartSegmentAddress = 3,
Type_ExtendedLinearAddress = 4,
Type_StartLinearAddress = 5,
};
// IHX Record Types
enum LogType
{
Log_None = 0,
Log_Verbose = 0b00000001,
Log_Records = 0b00000010,
Log_Segments = 0b00000100,
};
//=============================================================================
// VARIABLES
//=============================================================================
const char* VERSION = MSXHEX_VERSION;
// Hexadecimal character mapping
const std::map<c8, u8> g_HexaMap = {
{ '0', 0 },
{ '1', 1 },
{ '2', 2 },
{ '3', 3 },
{ '4', 4 },
{ '5', 5 },
{ '6', 6 },
{ '7', 7 },
{ '8', 8 },
{ '9', 9 },
{ 'A', 10 },
{ 'B', 11 },
{ 'C', 12 },
{ 'D', 13 },
{ 'E', 14 },
{ 'F', 15 },
};
// Named values
const std::map<const c8*, u32> g_NamedValue = {
{ "1K", 1024 * 1 },
{ "2K", 1024 * 2 },
{ "4K", 1024 * 4 },
{ "8K", 1024 * 8 },
{ "16K", 1024 * 16 },
{ "24K", 1024 * 24 },
{ "32K", 1024 * 32 },
{ "48K", 1024 * 48 },
{ "64K", 1024 * 64 },
{ "128K", 1024 * 128 },
{ "256K", 1024 * 256 },
{ "512K", 1024 * 512 },
{ "1M", 1024 * 1024 * 1 },
{ "2M", 1024 * 1024 * 2 },
{ "4M", 1024 * 1024 * 4 },
{ "8M", 1024 * 1024 * 8 },
{ "16M", 1024 * 1024 * 16 },
{ "32M", 1024 * 1024 * 32 },
{ "64M", 1024 * 1024 * 64 },
};
//=============================================================================
// VARIABLES
//=============================================================================
// Configuration
std::string g_InputFile;
std::string g_OutputFile;
std::string g_OutputExt;
u8 g_Log = Log_Verbose;
bool g_Check = false;
u32 g_StartAddress = 0;
u32 g_DataSize = 0;
u32 g_BankSize = 0;
std::vector<u8> g_BinData;
std::vector<bool> g_BinCheck;
u8 g_Padding = 0xFF;
std::vector<Segment> g_SegmentInfo;
std::vector<RawData> g_RawData;
std::vector<u32> g_SegException;
//=============================================================================
// FUNCTIONS
//=============================================================================
//-----------------------------------------------------------------------------
// Get 8-bit unsigned integer
const c8* GetU8(const c8* ptr, u8& byte)
{
byte = (1 << 4) * g_HexaMap.at(*ptr++);
byte += (1 << 0) * g_HexaMap.at(*ptr++);
return ptr;
}
//-----------------------------------------------------------------------------
// Get 16-bit unsigned integer
const c8* GetU16(const c8* ptr, u16& word)
{
word = (1 << 12) * g_HexaMap.at(*ptr++);
word += (1 << 8) * g_HexaMap.at(*ptr++);
word += (1 << 4) * g_HexaMap.at(*ptr++);
word += (1 << 0) * g_HexaMap.at(*ptr++);
return ptr;
}
//-----------------------------------------------------------------------------
// Get named value
u32 GetValue(std::string name)
{
// Named value
std::map<const c8*, u32>::const_iterator it = g_NamedValue.find(name.c_str());
if (it != g_NamedValue.end())
return it->second;
// Hexadecimal
if ((name[0] == '0') && (name[1] == 'x'))
return strtol(name.c_str(), NULL, 16);
// Decimal
return atol(name.c_str());
}
//-----------------------------------------------------------------------------
//
bool IsSeparator(u8 a)
{
return (a == ' ') || (a == '\t') || (a == '\n') || (a == '\r');
}
//-----------------------------------------------------------------------------
//
void ExtractParameters(std::vector<u8>& data, std::vector<std::string>& params)
{
std::string str;
for (u32 i = 0; i < data.size(); i++)
{
if (IsSeparator(data[i]))
{
if (!str.empty())
{
params.push_back(str);
str.clear();
}
}
else
str += data[i];
}
if (!str.empty())
params.push_back(str);
}
//-----------------------------------------------------------------------------
//
bool WriteBytesAtOffset(u32 offsetStart, const std::vector<u8>& data)
{
// Get file offset
u32 offsetEnd = offsetStart + (u32)data.size() - 1;
// Check max size
if ((g_DataSize != 0) && (offsetEnd > g_DataSize - 1))
{
printf("Error: Try to write value outside defined data length! (Max=%08Xh Destination=%08Xh)\n", g_DataSize - 1, offsetEnd);
return false;
}
// Resize data if needed
if (offsetEnd + 1 > (u32)g_BinData.size())
{
g_BinData.resize(offsetEnd + 1, g_Padding);
g_BinCheck.resize(offsetEnd + 1, false);
}
// Write data
u32 offset = offsetStart;
for (u32 i = 0; i < data.size(); i++)
{
if (g_BinCheck[offset])
{
printf("Error: Data overwrite at offset %08Xh!\n", offset);
return false;
}
g_BinData[offset] = data[i];
g_BinCheck[offset] = true;
offset++;
}
return true;
}
//-----------------------------------------------------------------------------
//
bool WriteBytesAtAddress(u32 addr, const std::vector<u8>& data)
{
// Check starting address
if (addr < g_StartAddress)
{
printf("Error: Try to write value below start address! (Start=%08Xh Addr=%08Xh)\n", g_StartAddress, addr);
return false;
}
// Check banked data segment
u16 segNum = (addr >> 16);
u16 segAddr = (addr & 0xFFFF);
if (((u16)g_SegmentInfo.size() > segNum) && (g_SegmentInfo[segNum].Base != INVALID_ADDR)) // Use provided segment base address...
{
addr = g_StartAddress + (segNum * g_BankSize) + (u16)addr - g_SegmentInfo[segNum].Base;
}
else if ((g_BankSize != 0) && (segNum > 0)) // ...or try to guess it
{
u16 segOffset = (u16)(addr & (g_BankSize - 1));
addr = g_StartAddress + (segNum * g_BankSize) + segOffset;
}
// Update segment info
if ((u16)g_SegmentInfo.size() < segNum + 1)
g_SegmentInfo.resize(segNum + 1);
g_SegmentInfo[segNum].Size += (u16)data.size();
if (segAddr < g_SegmentInfo[segNum].Lower)
g_SegmentInfo[segNum].Lower = segAddr;
if (segAddr + data.size() - 1 > g_SegmentInfo[segNum].Higher)
g_SegmentInfo[segNum].Higher = segAddr + (u16)data.size() - 1;
if (g_Log & Log_Segments)
{
if ((g_SegmentInfo[segNum].Size > g_BankSize) && (std::find(g_SegException.begin(), g_SegException.end(), segNum) == g_SegException.end()))
printf("Warning: Segment %i size (%i) exceed defined bank size (%i)\n", segNum, g_SegmentInfo[segNum].Size, g_BankSize);
}
return WriteBytesAtOffset(addr - g_StartAddress, data);
}
//-----------------------------------------------------------------------------
// Save binary file
int SaveBinary(std::string outFile)
{
if (g_Log & Log_Verbose)
{
u32 size = 0;
printf("Saving %s...\n", outFile.c_str());
for (u16 i = 0; i < g_SegmentInfo.size(); i++)
{
if (g_SegmentInfo[i].Size > 0)
{
printf(" Seg[%04i]: Lower=%04Xh Higher=%04Xh Size=%i (Holes=%i)\n", i, g_SegmentInfo[i].Lower, g_SegmentInfo[i].Higher, g_SegmentInfo[i].Higher - g_SegmentInfo[i].Lower + 1, (g_SegmentInfo[i].Higher - g_SegmentInfo[i].Lower + 1) - g_SegmentInfo[i].Size);
size += g_SegmentInfo[i].Higher - g_SegmentInfo[i].Lower + 1;
}
}
if(g_SegmentInfo.size() > 1)
printf(" Total size: %i\n", size);
}
// Pad to desired size
if ((g_DataSize != 0) && (g_BinData.size() < g_DataSize))
g_BinData.resize(g_DataSize, g_Padding);
// Open binary file
FILE* file = fopen(outFile.c_str(), "wb");
if (file == NULL)
{
printf("Error: Fail to open output file %s\n", outFile.c_str());
return 1;
}
// Write data
if (fwrite(g_BinData.data(), sizeof(u8), g_BinData.size(), file) != g_BinData.size())
{
printf("Error: Fail to write %i bytes to file %s\n", (int)g_BinData.size(), outFile.c_str());
return 1;
}
fclose(file);
return 0;
}
//-----------------------------------------------------------------------------
//
bool AddRawData(u32 offset, std::string inFile)
{
//if (g_Log)
printf("Log: Add file '%s' at offset %08Xh\n", inFile.c_str(), offset);
RawData raw;
raw.Offset = offset;
// Read binary file
FILE* file = fopen(inFile.c_str(), "rb");
if (file == NULL)
{
printf("Error: Fail to open input file %s\n", inFile.c_str());
return 1;
}
fseek(file, 0, SEEK_END);
u32 fileSize = ftell(file);
raw.Data.resize(fileSize);
fseek(file, 0, SEEK_SET);
if (fread(&raw.Data[0], sizeof(u8), fileSize, file) != fileSize)
{
printf("Error: Fail to read file %s\n", inFile.c_str());
return false;
}
fclose(file);
g_RawData.push_back(raw);
return true;
}
//-----------------------------------------------------------------------------
// Load and parse Intel HEX file
bool ParseHex(std::string inFile)
{
std::string strData;
u8* binData;
u32 baseAddr = 0x00000000, lowAddr = 0xFFFFFFFF;
// Display header
printf("MSXhex %s - Convert an Intel HEX file to binary\n", VERSION);
if (g_Log & Log_Verbose)
printf(" Start=%08Xh Size=%08Xh Bank=%08Xh Pad=%02Xh\n", g_StartAddress, g_DataSize, g_BankSize, g_Padding);
// Read binary file
FILE* file = fopen(inFile.c_str(), "rb");
if (file == NULL)
{
printf("Error: Fail to open input file %s\n", inFile.c_str());
return false;
}
fseek(file, 0, SEEK_END);
u32 fileSize = ftell(file);
binData = (u8*)malloc(fileSize);
fseek(file, 0, SEEK_SET);
if (fread(binData, sizeof(u8), fileSize, file) != fileSize)
{
free(binData);
printf("Error: Fail to read file %s\n", inFile.c_str());
return false;
}
fclose(file);
// Parse data
const c8* ptr = (const c8*)binData;
u32 index = 0;
while (1)
{
// Skip to next record
while (*ptr != ':')
ptr++;
ptr++;
// Read record
Record rec;
ptr = GetU8(ptr, rec.Count);
ptr = GetU16(ptr, rec.Address);
ptr = GetU8(ptr, rec.Type);
for (u8 i = 0; i < rec.Count; i++)
{
u8 byte;
ptr = GetU8(ptr, byte);
rec.Data.push_back(byte);
}
ptr = GetU8(ptr, rec.Checksum);
// Check checksum validation
if (g_Check)
{
u8 sum = rec.Count + (rec.Address >> 8) + (rec.Address & 0xFF) + rec.Type + rec.Checksum;
for (u8 i = 0; i < rec.Data.size(); i++)
sum += rec.Data[i];
if (sum != 0)
{
printf("Error: Record[%i] checksum error!\n", index);
return false;
}
}
// Compute record global address
u32 addr = baseAddr + rec.Address;
if (addr < lowAddr)
lowAddr = addr;
// Parse record
switch (rec.Type)
{
case Type_Data:
if (g_Log & Log_Records)
printf("Log: Record[%i] Addr=%08Xh Size=%i\n", index, addr, rec.Count);
if (!WriteBytesAtAddress(addr, rec.Data))
return false;
break;
case Type_EndOfFile:
if (g_Log & Log_Records)
printf("Log: End of file\n");
free(binData);
return true;
case Type_ExtendedSegmentAddress:
baseAddr = (rec.Data[0] << 12) + (rec.Data[1] << 4);
if (g_Log & Log_Records)
printf("Log: Set base Addr=%08Xh\n", baseAddr);
break;
case Type_StartSegmentAddress:
break;
case Type_ExtendedLinearAddress:
baseAddr = (rec.Data[0] << 24) + (rec.Data[1] << 16);
if (g_Log & Log_Records)
printf("Log: Set base Addr=%08Xh\n", baseAddr);
break;
case Type_StartLinearAddress:
break;
};
index++;
}
return false;
}
//-----------------------------------------------------------------------------
// Display help information
void PrintHelp()
{
printf("MSXhex %s - Convert Intel HEX file to binary\n", VERSION);
printf("--------------------------------------------------------------------------------\n");
printf("Usage: msxhex <inputfile> [options]\n\n");
printf("Options:\n");
printf(" -o <filename> Output filename (default: use input filename with '.bin')\n");
printf(" -e <ext> Output filename extension (can't be use with -o)\n");
printf(" -s <addr> Starting address (default: 0)\n");
printf(" -l <length> Total data length (default: 0, means autodetect)\n");
printf(" -b <length> Bank size (default: 0, means don't use)\n");
printf(" -p <value> Pading value (default: 0xFF)\n");
printf(" -r <offset> <file> Raw data 'file' to add at a given 'offset'\n");
printf(" -f <file> Additionnal parameters (text) file\n");
printf(" --check Validate each record using checksum\n");
printf(" --log Log each record\n");
printf(" --segcheck Check if segment size does not exceed the bank size\n");
printf(" --segexcept <num> Don't report oversize for this segment (can be use more than once)\n");
printf(" --segaddr <seg> <addr> Base address of the given segment (can be use more than once)\n");
printf(" If no base address is supplied, autodetection is used.\n");
printf(" -help Display this help\n");
printf("\n");
printf(" All integers can be decimal or hexadecimal starting with '0x'.\n");
printf(" One of the following named values can also be used:\n ");
for (std::map<const c8*, u32>::const_iterator it = g_NamedValue.cbegin(); it != g_NamedValue.cend(); ++it)
{
printf("%s", it->first);
if(std::next(it) != g_NamedValue.end())
printf(", ");
}
printf("\n");
}
//-----------------------------------------------------------------------------
//const char* ARGV[] = { "", "../testcases/s_swsprt.ihx", "-e", "rom", "-s", "0x4000", "-l", "131072", "-b", "8192", "--segaddr", "4", "0x0000"};
//const char* ARGV[] = { "", "../testcases/s_swsprt.ihx", "-f", "../testcases/msxhex_paramst.txt"};
//#define DEBUG_ARGS
//-----------------------------------------------------------------------------
// MAIN LOOP
int main(int argc, const char* argv[])
{
#ifdef DEBUG_ARGS
argc = sizeof(ARGV) / sizeof(ARGV[0]); argv = ARGV;
#endif
// Search for -help option
for (i32 i = 1; i < argc; ++i)
{
if (MSX::StrEqual(argv[i], "-help"))
{
PrintHelp();
return 0;
}
}
// Error check
if (argc < 2)
{
printf("Error: No enough parameters!\n");
PrintHelp();
return 0;
}
g_InputFile = argv[1];
std::vector<std::string> params;
for (i32 i = 2; i < argc; ++i)
params.push_back(argv[i]);
// Parse command line parameters
for (u32 i = 0; i < params.size(); i++)
{
// Output filename
if (MSX::StdStrEqual(params[i], "-o"))
{
g_OutputFile = params[++i];
}
// Output filename extension
else if (MSX::StdStrEqual(params[i], "-e"))
{
g_OutputExt = params[++i];
}
// Starting address
else if (MSX::StdStrEqual(params[i], "-s"))
{
g_StartAddress = GetValue(params[++i]);
}
// Total data length (size)
else if (MSX::StdStrEqual(params[i], "-l"))
{
g_DataSize = GetValue(params[++i]);
}
// Bank size
else if (MSX::StdStrEqual(params[i], "-b"))
{
g_BankSize = GetValue(params[++i]);
}
// Padding value
else if (MSX::StdStrEqual(params[i], "-p"))
{
g_Padding = (u8)GetValue(params[++i]);
}
// Raw data file
else if (MSX::StdStrEqual(params[i], "-r"))
{
u32 offset = GetValue(params[++i]);
std::string file = params[++i];
AddRawData(offset, file);
}
// Additionnal parameters file
else if (MSX::StdStrEqual(params[i], "-f"))
{
MSX::FileData fd(params[++i]);
if (!MSX::File::Load(fd))
{
printf("Error: Invalid additionnal parameters file!\n");
return 1;
}
std::vector<std::string> ap; // additionnal parameters
ExtractParameters(fd.Data, ap);
for (std::vector<std::string>::iterator itap = ap.begin(); itap != ap.cend(); ++itap)
params.push_back(*itap);
}
// Activate record logging
else if (MSX::StdStrEqual(params[i], "--log") || MSX::StdStrEqual(params[i], "-log"))
{
g_Log |= Log_Records;
}
// Activate record logging
else if (MSX::StdStrEqual(params[i], "--segcheck"))
{
g_Log |= Log_Segments;
}
// Activate record logging
else if (MSX::StdStrEqual(params[i], "--segexcept"))
{
u32 val = GetValue(params[++i]);
g_SegException.push_back(val);
}
// Activate record validation check
else if (MSX::StdStrEqual(params[i], "--check") || MSX::StdStrEqual(params[i], "-check"))
{
g_Check = true;
}
// Activate record validation check
else if (MSX::StdStrEqual(params[i], "--segaddr"))
{
u32 seg = GetValue(params[++i]);
u32 addr = GetValue(params[++i]);
if ((u16)g_SegmentInfo.size() < seg + 1)
g_SegmentInfo.resize(seg + 1);
g_SegmentInfo[seg].Base = addr;
}
}
// Generate output filename
if (g_OutputFile.empty())
{
g_OutputFile = MSX::RemoveExt(g_InputFile);
if(g_OutputExt.empty())
g_OutputFile += ".bin";
else
g_OutputFile += "." + g_OutputExt;
}
// Parse IHX file
if (!ParseHex(g_InputFile))
return 1;
// Insert raw data
for (u32 i = 0; i < g_RawData.size(); i++)
{
if (!WriteBytesAtOffset(g_RawData[i].Offset, g_RawData[i].Data))
return 1;
}
// Save final binary file
return SaveBinary(g_OutputFile);
}