// no constructor; use static members only
        public static void Convert(String input_file_name_1, String input_file_name_2, String output_file_name)
        {
            System.IO.Stream out_file;
            EndianBinaryWriter out_writer;
            ObjectFile obj_file_1, obj_file_2;
            Boolean second_input_file = (input_file_name_2 == "") ? false : true;

            // attempt to open output file for writing
            out_file = System.IO.File.Open(output_file_name, System.IO.FileMode.Create);
            out_writer = new EndianBinaryWriter(out_file, Endian.LittleEndian);

            // note: entrypoint accessible as obj_file_x.EntryPoint
            // note: section count accessible as obj_file_x.LoadableSectionCount
            // note: section contents accessible as obj_file_x.LoadableSections

            if (second_input_file)
            {
                // attempt to open input files as COFF or ELF
                openObjFile(input_file_name_1, out obj_file_1);
                openObjFile(input_file_name_2, out obj_file_2);

                // write header contents to output file
                writeRprcHeader(out_writer, obj_file_1.LoadableSectionCount + obj_file_2.LoadableSectionCount);

                // write entrypoint of both applications as "resource" sections to output file
                writeEntrypoint(out_writer, obj_file_1.EntryPoint);
                writeEntrypoint(out_writer, obj_file_2.EntryPoint);

                // write section contents to output file
                foreach (ObjectSection section in obj_file_1.LoadableSections)
                    writeSection(out_writer, section, obj_file_1.secRead(section));
                foreach (ObjectSection section in obj_file_2.LoadableSections)
                {
                    writeSection(out_writer, section, obj_file_2.secRead(section));

                    // warn if section collides with another section from first application
                    if (sectionOverlap(section, obj_file_1.LoadableSections))
                        Console.WriteLine("Warning: section in file " + input_file_name_2 +
                            " starting at 0x" + section.loadAddr.ToString("x8") +
                            " overlaps with one or more sections in file " + input_file_name_1 + ".");
                }

                // close input files
                obj_file_1.Close();
                obj_file_2.Close();
            }
            else
            {
                // attempt to open input file as COFF or ELF
                openObjFile(input_file_name_1, out obj_file_1);

                // write header contents to output file
                writeRprcHeader(out_writer, obj_file_1.LoadableSectionCount);

                // write entrypoint of first application as "resource" section to output file
                writeEntrypoint(out_writer, obj_file_1.EntryPoint);

                // write section contents to output file
                foreach (ObjectSection section in obj_file_1.LoadableSections)
                    writeSection(out_writer, section, obj_file_1.secRead(section));

                // close input file
                obj_file_1.Close();
            }

            // close output file
            out_writer.Close();
        }
        private static void writeRprcHeader(EndianBinaryWriter writer, uint section_count)
        {
            // RPRC header contents:
            //  char magic[4] = { 'R', 'P', 'R', 'C' };
            //  u32 version;
            //  u32 header_len;
            //  char header[...] = { header_len bytes of unformatted, textual header };

            char[] magic = {'R', 'P', 'R', 'C'};
            uint version = 1; // TODO: confirm with SDO

            // use text header to record section count
            uint length = 4;
            uint header = section_count;
            // TODO: add more text header contents?

            // write header to file
            writer.Write(magic);
            writer.Write(version);
            writer.Write(length);
            writer.Write(header);
        }
        private static void writeSection(EndianBinaryWriter writer, ObjectSection section, byte[] contents)
        {
            // RPRC section contents:
            //  u32 type;
            //  u64 da;
            //  u32 len;
            //  u8 content[...] = { len bytes of binary data };

            uint type = 1;  // 1: TEXT, 2: DATA (TODO use both?)
            ulong addr = section.loadAddr;
            uint size = (uint)section.size;

            // write section to file
            writer.Write(type);
            writer.Write(addr);
            writer.Write(size);
            writer.Write(contents);
        }
        private static void writeEntrypoint(EndianBinaryWriter writer, ulong entrypoint)
        {
            // RPRC section contents:
            //  u32 type;
            //  u64 da;
            //  u32 len;
            //  u8 content[...] = { len bytes of binary data };

            // RPRC resource contents (inside section contents):
            //  u32 type;
            //  u64 da;
            //  u32 len;
            //  u32 reserved;
            //  u8 name[48];

            uint section_type = 0;  // 0: RESOURCE
            ulong section_addr = entrypoint;
            uint section_size = 4 + 8 + 4 + 4 + 48;

            uint resource_type = 5; // 5: RSC_BOOTADDR
            ulong resource_addr = entrypoint;
            uint resource_size = 48;
            uint resource_rsvd = 0;
            char[] resource_name = newResourceName("Entrypoint");

            // write section + resource to file
            writer.Write(section_type);
            writer.Write(section_addr);
            writer.Write(section_size);
            writer.Write(resource_type);
            writer.Write(resource_addr);
            writer.Write(resource_size);
            writer.Write(resource_rsvd);
            writer.Write(resource_name);
        }