/*
 * Out2Rprc.cs
*/

/*
 * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ 
*/

/* 
 *  Redistribution and use in source and binary forms, with or without 
 *  modification, are permitted provided that the following conditions 
 *  are met:
 *
 *    Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the   
 *    distribution.
 *
 *    Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
*/
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TI.UtilLib.IO;
using TI.UtilLib.ObjectFile;

namespace out2rprc
{
    class Out2Rprc
    {
        // 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 openObjFile(String file_name, out ObjectFile obj_file)
        {
            if (ElfFile.IsElfFile(file_name))
                obj_file = new ElfFile(file_name);
            else if (CoffFile.IsCoffFile(file_name))
                obj_file = new CoffFile(file_name);
            else
                throw new Exception("File " + file_name + " is not a valid object file.");
        }

        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);
        }

        private static char[] newResourceName(String text)
        {
            char[] name = new char[48];

            // limit name to 47 characters
            if (text.Length > 47)
                text = text.Substring(0, 47);

            // copy string to name array
            for (int i = 0; i < text.Length; i++)
                name[i] = text[i];

            // fill in remaining space with null characters
            for (int j = 0; j < 48 - text.Length; j++)
                name[text.Length + j] = (char)0;

            return name;
        }

        private static bool sectionOverlap(ObjectSection section, ObjectSection[] externSections)
        {
            // note: s1, s2 are the beginning and end locations of this section in memory
            ulong s1 = section.loadAddr,
                s2 = section.loadAddr + section.size - 1,
                x1, x2;

            foreach (ObjectSection exSection in externSections)
            {
                // note: x1, x2 are the beginning and end locations of the external section in memory
                x1 = exSection.loadAddr;
                x2 = exSection.loadAddr + exSection.size - 1;
                
                // sections collide if any of the following is true:
                // 1) s1 is inside the external section
                // 2) s2 is inside the external section
                // 3) s1 is before the external section and s2 is after it
                if ((s1 >= x1 && s1 <= x2) ||
                    (s2 >= x1 && s2 <= x2) ||
                    (s1 < x1 && s2 > x2))
                    return true;
            }

            return false;
        }
    }
}
