// omfEmit — wrap a flat binary in a minimal Apple IIgs OMF v2.1 // container so GS/OS can load and execute it. // // Single-segment output (CODE, kind=0), no INTERSEG opcodes (multi- // segment output is a follow-on). Header layout per OMF 2.1 spec: // 44-byte fixed header + 10-byte LOAD_NAME + 32-byte SEG_NAME, then // the body (DS opcode for the payload, END opcode terminator). // // CLI mirrors the Python tool exactly: // omfEmit --input flat.bin --map flat.map --base 0x8000 // --entry main --output prog.omf [--name SEG] #include #include #include #include #include #include #include #include namespace { [[noreturn]] static void die(const std::string &msg) { std::fprintf(stderr, "omfEmit: %s\n", msg.c_str()); std::exit(1); } static std::vector readFile(const std::string &path) { std::ifstream f(path, std::ios::binary); if (!f) die("cannot open '" + path + "' for reading"); return std::vector((std::istreambuf_iterator(f)), std::istreambuf_iterator()); } static std::map readMap(const std::string &path) { std::map syms; std::ifstream f(path); if (!f) die("cannot open '" + path + "' for reading"); std::string line; while (std::getline(f, line)) { auto eq = line.find(" = "); if (eq == std::string::npos) continue; std::string name = line.substr(0, eq); std::string addr = line.substr(eq + 3); // Trim trailing whitespace. while (!name.empty() && std::isspace((unsigned char)name.back())) name.pop_back(); while (!addr.empty() && std::isspace((unsigned char)addr.back())) addr.pop_back(); try { syms[name] = std::stoul(addr, nullptr, 16); } catch (...) { /* skip non-hex entries */ } } return syms; } // Emit little-endian. static void put32(std::vector &v, uint32_t x) { v.push_back(x & 0xFF); v.push_back((x >> 8) & 0xFF); v.push_back((x >> 16) & 0xFF); v.push_back((x >> 24) & 0xFF); } static void put16(std::vector &v, uint16_t x) { v.push_back(x & 0xFF); v.push_back((x >> 8) & 0xFF); } static std::vector emitOMF(const std::vector &image, uint32_t entryOffset, const std::string &name) { // Body: DS (literal data) + END. std::vector body; if (!image.empty()) { body.push_back(0xF1); // DS opcode put32(body, static_cast(image.size())); body.insert(body.end(), image.begin(), image.end()); } body.push_back(0x00); // END opcode // LOAD_NAME: 10 bytes, space-padded. std::string loadName = name.substr(0, 10); while (loadName.size() < 10) loadName += ' '; // SEG_NAME: 1-byte length prefix + 31 bytes (truncated, padded with NUL). std::string segNameTxt = name.substr(0, 31); std::vector segName; segName.push_back(static_cast(segNameTxt.size())); for (char c : segNameTxt) segName.push_back((uint8_t)c); while (segName.size() < 32) segName.push_back(0); constexpr uint16_t DISPNAME = 44; const uint16_t DISPDATA = DISPNAME + 10 + 32; const uint32_t LENGTH = static_cast(image.size()); const uint32_t BYTECNT = DISPDATA + static_cast(body.size()); const uint32_t RESSPC = 0; const uint32_t BANKSIZE = 0x10000; const uint16_t KIND = 0x0000; // CODE const uint32_t ORG = 0; const uint32_t ALIGN = 0; const uint8_t NUMSEX = 0; const uint16_t SEGNUM = 1; const uint32_t ENTRY = entryOffset; std::vector hdr; put32(hdr, BYTECNT); put32(hdr, RESSPC); put32(hdr, LENGTH); hdr.push_back(0x00); // undefined hdr.push_back(10); // LABLEN hdr.push_back(4); // NUMLEN hdr.push_back(0x21); // VERSION 2.1 put32(hdr, BANKSIZE); put16(hdr, KIND); hdr.push_back(0x00); hdr.push_back(0x00); // undefined (2 bytes) put32(hdr, ORG); put32(hdr, ALIGN); hdr.push_back(NUMSEX); hdr.push_back(0x00); // undefined put16(hdr, SEGNUM); put32(hdr, ENTRY); put16(hdr, DISPNAME); put16(hdr, DISPDATA); if (hdr.size() != 44) die("internal: header size != 44"); std::vector out; out.insert(out.end(), hdr.begin(), hdr.end()); out.insert(out.end(), loadName.begin(), loadName.end()); out.insert(out.end(), segName.begin(), segName.end()); out.insert(out.end(), body.begin(), body.end()); return out; } static uint32_t parseInt(const std::string &s) { return static_cast(std::stoul(s, nullptr, 0)); } static void usage(const char *argv0) { std::fprintf(stderr, "usage: %s --input FLAT --map FILE --base ADDR --entry SYM\n" " --output OMF [--name NAME]\n", argv0); std::exit(2); } } // namespace int main(int argc, char **argv) { std::string input, mapFile, output, entry = "main", name; uint32_t base = 0; bool baseSet = false; int i = 1; while (i < argc) { std::string a = argv[i]; if (a == "--input") { if (++i >= argc) usage(argv[0]); input = argv[i++]; } else if (a == "--map") { if (++i >= argc) usage(argv[0]); mapFile = argv[i++]; } else if (a == "--base") { if (++i >= argc) usage(argv[0]); base = parseInt(argv[i++]); baseSet = true; } else if (a == "--entry") { if (++i >= argc) usage(argv[0]); entry = argv[i++]; } else if (a == "--name") { if (++i >= argc) usage(argv[0]); name = argv[i++]; } else if (a == "--output" || a == "-o") { if (++i >= argc) usage(argv[0]); output = argv[i++]; } else if (a == "-h" || a == "--help") usage(argv[0]); else die("unknown option '" + a + "'"); } if (input.empty() || mapFile.empty() || !baseSet || output.empty()) usage(argv[0]); auto image = readFile(input); auto syms = readMap(mapFile); auto it = syms.find(entry); if (it == syms.end()) die("entry symbol '" + entry + "' not in map"); uint32_t entryAddr = it->second; if (entryAddr < base || entryAddr >= base + image.size()) die("entry symbol outside linked image"); uint32_t entryOff = entryAddr - base; if (name.empty()) { // Default name: output basename without extension. size_t slash = output.find_last_of('/'); std::string base_n = (slash == std::string::npos) ? output : output.substr(slash + 1); size_t dot = base_n.find_last_of('.'); name = (dot == std::string::npos) ? base_n : base_n.substr(0, dot); } auto blob = emitOMF(image, entryOff, name); std::ofstream f(output, std::ios::binary); if (!f) die("cannot open '" + output + "' for writing"); f.write(reinterpret_cast(blob.data()), blob.size()); std::fprintf(stderr, "OMF: 1 segment, %zu bytes payload, entry='%s' at +0x%x -> %s " "(%zu bytes total)\n", image.size(), entry.c_str(), entryOff, output.c_str(), blob.size()); return 0; }