/* ###
 * IP: GHIDRA
 * NOTE: Excluded from Build.  Used for development only in support of console mode - Links to GNU BFD library which is GPL 3
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "bfd_arch.hh"

namespace ghidra {

// Constructing this object registers capability
BfdArchitectureCapability BfdArchitectureCapability::bfdArchitectureCapability;

ElementId ELEM_BFD_SAVEFILE = ElementId("bfd_savefile",238);

BfdArchitectureCapability::BfdArchitectureCapability(void)

{
  name = "bfd";
}

BfdArchitectureCapability::~BfdArchitectureCapability(void)

{
  SleighArchitecture::shutdown();
}

Architecture *BfdArchitectureCapability::buildArchitecture(const string &filename,const string &target,ostream *estream)

{
  return new BfdArchitecture(filename,target,estream);
}

bool BfdArchitectureCapability::isFileMatch(const string &filename) const

{
  ifstream s(filename.c_str());
  if (!s)
    return false;
  int4 val1,val2,val3;
  s >> ws;
  val1 = s.get();
  val2 = s.get();
  val3 = s.get();
  s.close();
  if ((val1=='<')&&(val2=='b')&&(val3=='i'))
    return false;		// Probably XML, not BFD
  return true;
}

bool BfdArchitectureCapability::isXmlMatch(Document *doc) const

{
  return (doc->getRoot()->getName() == "bfd_savefile");
}

void BfdArchitecture::buildLoader(DocumentStorage &store)

{
  unique_ptr<LoadImageBfd> ldr;

  collectSpecFiles(*errorstream);
  if (getTarget().find("binary")==0)
    ldr.reset(new LoadImageBfd(getFilename(),"binary"));
  else if (getTarget().find("default")==0)
    ldr.reset(new LoadImageBfd(getFilename(),"default"));
  else
    ldr.reset(new LoadImageBfd(getFilename(),getTarget()));
  ldr->open();
  if (adjustvma!=0)
    ldr->adjustVma(adjustvma);
  loader = ldr.release();
}

void BfdArchitecture::resolveArchitecture(void)

{
  archid = getTarget();
  if (archid.find(':')==string::npos) {
    archid = loader->getArchType();
    // kludge to distinguish windows binaries from linux/gcc
    if (archid.find("efi-app-ia32") != string::npos)
      archid = "x86:LE:32:default:windows";
    else if (archid.find("pe-i386") != string::npos)
      archid = "x86:LE:32:default:windows";
    else if (archid.find("pei-i386") != string::npos)
      archid = "x86:LE:32:default:windows";
    else if (archid.find("pei-x86-64") != string::npos)
      archid = "x86:LE:64:default:windows";
    else if (archid.find("sparc") != string::npos)
      archid = "sparc:BE:32:default:default";
    else if (archid.find("elf64") != string::npos)
      archid = "x86:LE:64:default:gcc";
    else if (archid.find("elf") != string::npos)
      archid = "x86:LE:32:default:gcc";
    else if (archid.find("mach-o") != string::npos)
      archid = "PowerPC:BE:32:default:macosx";
    else
      throw LowlevelError("Cannot convert bfd target to sleigh target: "+archid);
  }
  SleighArchitecture::resolveArchitecture();
}

void BfdArchitecture::postSpecFile(void)

{ // Attach default space to loader
  Architecture::postSpecFile();
  ((LoadImageBfd *)loader)->attachToSpace(getDefaultCodeSpace());
}

/// This just wraps the base class constructor
/// \param fname is the path to the executable file
/// \param targ is the (optional) language id to use for the file
/// \param estream is the stream to use for the error console
BfdArchitecture::BfdArchitecture(const string &fname,const string &targ,ostream *estream)
  : SleighArchitecture(fname,targ,estream)

{				// Select architecture from string
  adjustvma = 0;
}

void BfdArchitecture::encode(Encoder &encoder) const

{				// prepend extra stuff to specify binary file and spec
  encoder.openElement(ELEM_BFD_SAVEFILE);
  encodeHeader(encoder);
  encoder.writeUnsignedInteger(ATTRIB_ADJUSTVMA, adjustvma);
  types->encodeCoreTypes(encoder);
  SleighArchitecture::encode(encoder); // Save the rest of the state
  encoder.closeElement(ELEM_BFD_SAVEFILE);
}

void BfdArchitecture::restoreXml(DocumentStorage &store)

{
  const Element *el = store.getTag("bfd_savefile");
  if (el == (const Element *)0)
    throw LowlevelError("Could not find bfd_savefile tag");

  restoreXmlHeader(el);
  {
    istringstream s( el->getAttributeValue("adjustvma"));
    s.unsetf(ios::dec | ios::hex | ios::oct);
    s >> adjustvma;
  }
  const List &list(el->getChildren());
  List::const_iterator iter;

  iter = list.begin();

  if (iter != list.end()) {
    if ((*iter)->getName() == "coretypes") {
      store.registerTag(*iter);
      ++iter;
    }
  }
  init(store); // Load the image and configure

  if (iter != list.end()) {
    store.registerTag(*iter);
    SleighArchitecture::restoreXml(store);
  }
}

} // End namespace ghidra
