2
0

bin2c.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # Copyright (c) 2025-2026 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. #!/usr/bin/env python3
  18. import sys, os, argparse
  19. def to_identifier(name):
  20. """Convert filename to valid C identifier"""
  21. return name.upper().replace('.', '_').replace('-', '_')
  22. def escape_c_string(text):
  23. """Escape special characters for C string literals"""
  24. text = text.replace('\\', '\\\\') # Double backslashes
  25. text = text.replace('"', '\\"') # Escape quotes
  26. text = text.replace('\n', '\\n') # Convert actual newlines to \n
  27. text = text.replace('\r', '\\r') # Convert carriage returns
  28. text = text.replace('\t', '\\t') # Convert tabs
  29. return text
  30. def write_header_from_file(file_path, out_path, mode='binary', custom_name=None):
  31. """Convert a file into a C header"""
  32. if custom_name:
  33. array_name = to_identifier(custom_name)
  34. guard = array_name + '_H'
  35. else:
  36. name = os.path.basename(file_path)
  37. guard = to_identifier(name) + '_H'
  38. array_name = to_identifier(name)
  39. if mode == 'text':
  40. with open(file_path, 'r', encoding='utf-8') as f:
  41. text = f.read()
  42. data = text.encode('utf-8')
  43. else:
  44. with open(file_path, 'rb') as f:
  45. data = f.read()
  46. write_data_to_header(data, out_path, guard, array_name, mode)
  47. def write_header_from_string(input_string, array_name, out_path, mode='binary'):
  48. """Convert a string into a C header"""
  49. guard = to_identifier(array_name) + '_H'
  50. array_name = to_identifier(array_name)
  51. if mode == 'text':
  52. try:
  53. interpreted_string = input_string.encode().decode('unicode_escape')
  54. data = interpreted_string.encode('utf-8')
  55. except UnicodeDecodeError:
  56. data = input_string.encode('utf-8')
  57. else:
  58. data = input_string.encode('utf-8')
  59. write_data_to_header(data, out_path, guard, array_name, mode)
  60. def write_header_from_stdin(array_name, out_path, mode='binary'):
  61. """Convert stdin input into a C header"""
  62. guard = to_identifier(array_name) + '_H'
  63. array_name = to_identifier(array_name)
  64. input_string = sys.stdin.read()
  65. if mode == 'text':
  66. try:
  67. interpreted_string = input_string.encode().decode('unicode_escape')
  68. data = interpreted_string.encode('utf-8')
  69. except UnicodeDecodeError:
  70. data = input_string.encode('utf-8')
  71. else:
  72. data = input_string.encode('utf-8')
  73. write_data_to_header(data, out_path, guard, array_name, mode)
  74. def write_data_to_header(data, out_path, guard, array_name, mode='binary'):
  75. """Write data to C header in text or binary format"""
  76. with open(out_path, 'w') as f:
  77. f.write(f"#ifndef {guard}\n")
  78. f.write(f"#define {guard}\n\n")
  79. f.write("#ifdef __cplusplus\n")
  80. f.write("extern \"C\" {\n")
  81. f.write("#endif\n\n")
  82. if mode == 'text':
  83. write_text_array(f, data, array_name)
  84. else:
  85. write_binary_array(f, data, array_name)
  86. f.write("#ifdef __cplusplus\n")
  87. f.write("}\n")
  88. f.write("#endif\n\n")
  89. f.write(f"#endif // {guard}\n")
  90. def write_text_array(f, data, array_name):
  91. """Write data as single-line C string literal"""
  92. try:
  93. text = data.decode('utf-8')
  94. except UnicodeDecodeError:
  95. write_binary_array(f, data, array_name)
  96. return
  97. text_size = len(data)
  98. escaped_text = escape_c_string(text)
  99. f.write(f'static const char {array_name}[] = "{escaped_text}";\n\n')
  100. f.write(f"#define {array_name}_SIZE {text_size}\n\n")
  101. def write_binary_array(f, data, array_name):
  102. """Write data as binary byte array"""
  103. if not data.endswith(b'\0'):
  104. data = data + b'\0'
  105. data_size = len(data) - 1
  106. f.write(f"static const unsigned char {array_name}[] =\n")
  107. f.write("{\n")
  108. for i in range(0, len(data), 16):
  109. chunk = data[i:i+16]
  110. f.write(" ")
  111. hex_values = [f"0x{byte:02X}" for byte in chunk]
  112. f.write(", ".join(hex_values))
  113. if i + 16 < len(data):
  114. f.write(",")
  115. f.write("\n")
  116. f.write("};\n\n")
  117. f.write(f"#define {array_name}_SIZE {data_size}\n\n")
  118. def main():
  119. parser = argparse.ArgumentParser(
  120. description='Convert a file, string, or stdin to a C array header'
  121. )
  122. parser.add_argument('output', help='Output header file (.h)')
  123. input_group = parser.add_mutually_exclusive_group(required=True)
  124. input_group.add_argument('-f', '--file', help='Input file to convert')
  125. input_group.add_argument('-s', '--string', help='String to convert')
  126. input_group.add_argument('--stdin', action='store_true', help='Read from stdin')
  127. parser.add_argument('-n', '--name', help='Array name (overrides filename-based name when using --file)')
  128. parser.add_argument('-m', '--mode', choices=['text', 'binary'],
  129. default='binary',
  130. help='Output mode: text for string literals, binary for byte arrays (default: binary)')
  131. args = parser.parse_args()
  132. if (args.string or args.stdin) and not args.name:
  133. parser.error("--name is required when using --string or --stdin")
  134. if args.file:
  135. write_header_from_file(args.file, args.output, args.mode, args.name)
  136. elif args.string:
  137. write_header_from_string(args.string, args.name, args.output, args.mode)
  138. elif args.stdin:
  139. write_header_from_stdin(args.name, args.output, args.mode)
  140. if __name__ == "__main__":
  141. main()