bin2c.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. # Copyright (c) 2024-2025 Le Juez Victor
  2. #
  3. # This software is provided "as-is", without any express or implied warranty. In no event
  4. # will the authors be held liable for any damages arising from the use of this software.
  5. #
  6. # Permission is granted to anyone to use this software for any purpose, including commercial
  7. # applications, and to alter it and redistribute it freely, subject to the following restrictions:
  8. #
  9. # 1. The origin of this software must not be misrepresented; you must not claim that you
  10. # wrote the original software. If you use this software in a product, an acknowledgment
  11. # in the product documentation would be appreciated but is not required.
  12. #
  13. # 2. Altered source versions must be plainly marked as such, and must not be misrepresented
  14. # as being the original software.
  15. #
  16. # 3. This notice may not be removed or altered from any source distribution.
  17. import sys, os, argparse
  18. def to_identifier(name):
  19. """Convert filename to valid C identifier"""
  20. return name.upper().replace('.', '_').replace('-', '_')
  21. def write_header_from_file(file_path, out_path, char_type='unsigned char'):
  22. """Convert a file into a C header with null termination"""
  23. name = os.path.basename(file_path)
  24. guard = to_identifier(name) + '_H'
  25. array_name = to_identifier(name)
  26. with open(file_path, 'rb') as f:
  27. data = f.read()
  28. write_data_to_header(data, out_path, guard, array_name, char_type)
  29. def write_header_from_string(input_string, array_name, out_path, char_type='unsigned char'):
  30. """Convert a string into a C header with null termination"""
  31. guard = to_identifier(array_name) + '_H'
  32. array_name = to_identifier(array_name)
  33. # Interpret escape sequences like \n, \t, etc., then encode to bytes
  34. data = input_string.encode('utf-8').decode('unicode_escape').encode('utf-8')
  35. write_data_to_header(data, out_path, guard, array_name, char_type)
  36. def write_data_to_header(data, out_path, guard, array_name, char_type='unsigned char'):
  37. """Write binary data to C header with null termination"""
  38. # Always ensure there is a null terminator, even if it's duplicated
  39. # Because some formats will include one by default and others won't
  40. # A '\0' must therefore always be added for simplicity
  41. data = data + b'\0'
  42. # Size without null terminator for convenience
  43. data_size = len(data) - 1
  44. with open(out_path, 'w') as f:
  45. f.write(f"#ifndef {guard}\n")
  46. f.write(f"#define {guard}\n\n")
  47. # Add C++ compatibility guard
  48. f.write("#ifdef __cplusplus\n")
  49. f.write("extern \"C\" {\n")
  50. f.write("#endif\n\n")
  51. f.write(f"static const {char_type} {array_name}[] = {{\n")
  52. # Write bytes in groups of 16 per line for readability
  53. for i, byte in enumerate(data):
  54. if i % 16 == 0:
  55. f.write(" ")
  56. f.write(f"0x{byte:02x}")
  57. if i < len(data) - 1:
  58. f.write(", ")
  59. if (i + 1) % 16 == 0 or i == len(data) - 1:
  60. f.write("\n")
  61. f.write("};\n\n")
  62. f.write(f"#define {array_name}_SIZE {data_size}\n\n")
  63. # Close C++ compatibility guard
  64. f.write("#ifdef __cplusplus\n")
  65. f.write("}\n")
  66. f.write("#endif\n\n")
  67. f.write(f"#endif // {guard}\n")
  68. def main():
  69. parser = argparse.ArgumentParser(
  70. description='Convert a file or string to a null-terminated C array header'
  71. )
  72. parser.add_argument('output', help='Output header file (.h)')
  73. input_group = parser.add_mutually_exclusive_group(required=True)
  74. input_group.add_argument('-f', '--file', help='Input file to convert')
  75. input_group.add_argument('-s', '--string', help='String to convert')
  76. parser.add_argument('-n', '--name', help='Array name (required with --string)')
  77. parser.add_argument('-t', '--type', choices=['char', 'unsigned char'],
  78. default='unsigned char',
  79. help='Character type for the array (default: unsigned char)')
  80. args = parser.parse_args()
  81. if args.string and not args.name:
  82. parser.error("--name is required when using --string")
  83. if args.file:
  84. write_header_from_file(args.file, args.output, args.type)
  85. elif args.string:
  86. write_header_from_string(args.string, args.name, args.output, args.type)
  87. if __name__ == "__main__":
  88. main()