//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
#include "headers.h"

#include "parse.h"

#include "bin.h"
#include "compcat.h"

#include "sre.h"
#include "rom.h"

#include "pbtimebomb.h"

bool last_pass = true;

bool process_args(char *argv[], int argc, string &bib_file, wstring &output_file, bool &launch);
bool pass_state(Config &config, ModuleList &module_list, FileList &file_list, MemoryList &memory_list, MemoryList &reserve_list, ModuleList::iterator &kernel);
bool get_kernel(ModuleList::iterator &kernel, const ModuleList &module_list);
bool init_kernel(ModuleList::iterator &kernel, ModuleList &module_list, const MemoryList &memory_list, const Config &config);
bool compute_load_locataions(ModuleList &module_list, const MemoryList &reserve_list, Config &config);

bool compact_image(AddressList &hole_list, 
                   CopyList &copy_list, 
                   ModuleList &module_list, 
                   MemoryList &memory_list,
                   FileList &file_list, 
                   const MemoryList &reserve_list, 
                   const Config &config,
                   const MemoryList::iterator &xip_mem);

#define FATAL(x) do{ if(!(x)){ fprintf(stderr, "Fatal error hit, exiting...\n"); exit(1); } } while(0)

/*****************************************************************************/
int main(int argc, char *argv[]){
  if(getenv("d_break"))
    DebugBreak();

#ifdef PBTIMEBOMB
  cout << "\nWindows CE ROM Image Builder v4.0." << PBTIMEBOMB << " Copyright (c) Microsoft Corporation\n";
  cout << "Built: " __DATE__ " " __TIME__ << "\n\n";
  
  // check PB PID Timebomb
  if(!IsRomImageEnabled()){
    fprintf(stderr, "Error: failed PB timebomb check\n");
    exit(1);
  }
#else
  cout << "\nWindows CE ROM Image Builder v4.0.000 Copyright (c) Microsoft Corporation\n";
  cout << "Built: " __DATE__ " " __TIME__ << "\n\n";
#endif
  
  Config     config;
  // prereserve 100 memory spots because if someone adds a memory section after the modules are set STL
  // may realoc and invalidate all the memory_iterators in the files and modules.
  MemoryList memory_list;
  MemoryList reserve_list;
  ModuleList module_list;
  FileList   file_list;

  MemoryList::iterator mem_itr;
  ModuleList::iterator mod_itr;
  FileList::iterator file_itr;

  string bib_file;
  bool launch = false;

  // do command line arguments
  FATAL(process_args(argv, argc, bib_file, config.output_file, launch));

  // parse the bib file
  FATAL(ParseBIBFile(bib_file, memory_list, reserve_list, module_list, file_list, config));

  if(module_list.empty())
  {
    cerr << "Error: No modules defined in bib file, aborting...\n";
    FATAL(false);
  }

  // this only needs to be done once
  for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) FATAL(mod_itr->sync_names(config.profile, config.copy_files));
  for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) FATAL(mod_itr->add_sig_files(file_list));

  // find the kernel and make sure it's first.
  ModuleList::iterator kernel;

  for(kernel = module_list.begin(); kernel != module_list.end(); kernel++)
    if(kernel->name() == "nk.exe"){
      Module temp = *kernel;

      module_list.erase(kernel);

      module_list.insert(module_list.begin(), temp);
      
      break;
    }

  kernel = module_list.begin();

  // sort by exe
  stable_partition(kernel + 1, module_list.end(), mem_fun_ref(Module::is_exe));
  stable_partition(kernel + 1, module_list.end(), mem_fun_ref(Module::is_like_kernel));

  reserve_list.sort();

  Config     config_backup(config);
  ModuleList module_list_backup(module_list);

  if(config.xipchain){
    Memory memory;
    wStringList wtoken_list;
    char buffer[10];

    int count = 1; // for the chain file itself
    for(mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++)
      if(mem_itr->type() == RAMIMAGE_TYPE || 
         mem_itr->type() == NANDIMAGE_TYPE || 
         mem_itr->type() == ROM8_TYPE  || 
         mem_itr->type() == ROMx8_TYPE || 
         mem_itr->type() == ROM16_TYPE)
        count++;
    
    wtoken_list.push_back(tounicode(CHAIN_INFORMATION));
    sprintf(buffer, "%08x", config.xipchain);
    wtoken_list.push_back(tounicode(buffer));
    sprintf(buffer, "%08x", count * sizeof(XIPCHAIN_SUMMARY));
    wtoken_list.push_back(tounicode(buffer));
    wtoken_list.push_back(tounicode(EXTENSION_TYPE));
    wtoken_list.push_back(tounicode(kernel->memory_iterator()->name()));

    memory.set(wtoken_list);

    memory_list.push_back(memory);
  }

  do{
    FATAL(pass_state(config, module_list, file_list, memory_list, reserve_list, kernel));

    ModuleList::iterator modbak_itr = module_list_backup.begin();

    // if we autosized and are now on the last pass restore the modules and config
    if(!config.autosize && config_backup.autosize){
      module_list = module_list_backup;
      
      DWORD tempxip = config.xipchain;
      config = config_backup;
      config.xipchain = tempxip;
      config.autosize = false;
    }
  
    FATAL(init_kernel(kernel, module_list, memory_list, config));

    // process the modules
    for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) FATAL(mod_itr->load());
    for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) FATAL(mod_itr->verify_cpu());

    // reset memory ranges    
    for(mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++){
      mem_itr->dll_data_top = mem_itr->dll_data_bottom_orig;
      mem_itr->dll_data_bottom = mem_itr->dll_data_bottom_orig;

      mem_itr->kernel_top = mem_itr->kernel_bottom_orig;
      mem_itr->kernel_bottom = mem_itr->kernel_bottom_orig;

      mem_itr->kernel_dll_top = mem_itr->kernel_dll_bottom_orig;
      mem_itr->kernel_dll_bottom = mem_itr->kernel_dll_bottom_orig;

      mem_itr->m_fixed_up = false;
    }
      
    FATAL(compute_load_locataions(module_list, reserve_list, config));

    // get symbols
    for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) 
      if(config.profile || mod_itr->is_kernel() || mod_itr->is_like_kernel()) 
        FATAL(mod_itr->get_symbols(config.profile_all, memory_list));

    // relocate image      
    for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) FATAL(mod_itr->relocate_image());

    // do fixups
    for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) 
      if(!mod_itr->fixup()){
        cerr << "Error: fatal fixup error, aborting....\n";
        exit(1);
      }

    // remove discardables    
    for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) FATAL(mod_itr->remove_discardable_sections());

    // imports
    for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) mod_itr->resolve_imports(module_list, config.error_late_bind);
    // Check import results afterwards so that they can check all missing functions at one time
    FATAL(Module::import_success());
    
    for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) FATAL(mod_itr->compress(config.compress));
    for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) FATAL(mod_itr->sig_adjust());
  
    // process the files
    for(file_itr = file_list.begin(); file_itr != file_list.end(); file_itr++) FATAL(file_itr->load());
    for(file_itr = file_list.begin(); file_itr != file_list.end(); file_itr++) FATAL(file_itr->compress(config.compress));

    CopyList copy_list;
    AddressList hole_list;

    // per region processing
    for(mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++){
      if(mem_itr->type() == RAMIMAGE_TYPE || mem_itr->type() == NANDIMAGE_TYPE || mem_itr->type() == ROM8_TYPE  || mem_itr->type() == ROMx8_TYPE || mem_itr->type() == ROM16_TYPE){
        LAST_PASS_PRINT cout << "Processing " << mem_itr->name() << endl;
        FATAL(compact_image(hole_list, copy_list, module_list, memory_list, file_list, reserve_list, config, mem_itr));

        for(MemoryList::iterator mem_itr2 = memory_list.begin(); mem_itr2 != memory_list.end(); mem_itr2++){
          // fixup variables
          if(mem_itr2->type() == FIXUPVAR_TYPE){
            if(mem_itr2->name().find(":") == string::npos){
              cerr << "Warning: FixupVar module not defined for " << mem_itr2->name() << ", assuming kernel.";
              kernel->fixupvar(mem_itr2->fixupvar_section, mem_itr2->address(), mem_itr2->length());
            }
            else{
              for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++){
                if(lowercase(mem_itr2->name()).find(lowercase(mod_itr->name()) + ":") != string::npos){
                  if(mod_itr->fixupvar(mem_itr2->fixupvar_section, mem_itr2->address(), mem_itr2->length()))
                  {
                    mem_itr2->m_fixed_up = true;
                  }
                  break;
                }
              }
            }
          }

          // set extension address
          if(mem_itr2->type() == EXTENSION_TYPE && mem_itr2->extension_location == mem_itr->name()){
            if(mem_itr->is_kernel() && kernel->is_kernel() && kernel->rom_extensions())
              kernel->fixupvar(0, 
                               kernel->rom_extensions() - kernel->memory_iterator()->address() + sizeof(EXTENSION) - sizeof(DWORD) - kernel->page_size(), 
                               mem_itr2->extension_offset);
            else if(mem_itr->is_kernel() && kernel->is_kernel())
              kernel->set_rom_extensions(mem_itr2->extension_offset);
            else
              Module::s_romhdr.pExtensions = (void*)mem_itr2->extension_offset;
          }
        }
        
        FATAL(write_bin(hole_list, copy_list, module_list, file_list, memory_list, reserve_list, kernel, config, mem_itr));
  
        // final pass stuff
        if(!config.autosize){
          static char *debug_nocomp = (debug_nocomp = getenv("ri_debug_info")) ? strstr(debug_nocomp, "nocomp") : NULL;
          
          // compact bin
          if(!debug_nocomp)
            FATAL(compact_bin(config.output_file));
        }
      }
    }

    for(MemoryList::iterator mem_itr2 = memory_list.begin(); mem_itr2 != memory_list.end(); mem_itr2++){
      // fixup variables
      if(mem_itr2->type() == FIXUPVAR_TYPE && !mem_itr2->m_fixed_up){
        cerr << "Warning: Module for FixupVar " << mem_itr2->name()  << " not found.  Variable not fixed up.\n";
      }
    }
  }while(config.autosize--);

  // process the chain file
  if(config.xipchain){
    if(config.output_file.rfind(L"\\") != wstring::npos)
      config.output_file = config.output_file.substr(0, config.output_file.rfind(L"\\"));
  
    config.output_file += L"\\chain.bin";
    
    FATAL(write_chain(config, memory_list));

    FATAL(cat_bins(config.output_file));
    FATAL(compact_bin(config.output_file));
  }

  if(config.bSRE ||
     kernel->memory_iterator()->type() == ROM8_TYPE  ||
     kernel->memory_iterator()->type() == ROM16_TYPE ||
     kernel->memory_iterator()->type() == ROMx8_TYPE)
    FATAL(write_sre(config.output_file, kernel->memory_iterator()->type(), config.rom_info.offset, kernel->entry_rva() + kernel->memory_iterator()->address(), launch));

  // write the rom file
  if(config.rom_info.start ||
     config.rom_info.size ||
     config.rom_info.width){
    if(// config.rom_info.start &&     // not sure if start is allowed to be zero sometimes or not.  Could work.
       config.rom_info.size &&
       config.rom_info.width)
      FATAL(write_rom(config.output_file, config.rom_info));
    else
      fprintf(stderr, "Warning: ROMSIZE = %08x and ROMWIDTH = %d, both must be set to generate a ROM\n", config.rom_info.size, config.rom_info.width);
  }

  // post build warnings about private bits in the image
  for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++)
    if(mod_itr->warn_private_bits())
      cerr << "Warning: Module " << mod_itr->name() << " contains updated private code\n";

  cout << "Done!\n";
  
  return 0;
}

/*****************************************************************************/
bool process_args(char *argv[], int argc, string &bib_file, wstring &output_file, bool &launch){
  bib_file = getenv("_winceroot")  ? getenv("_winceroot") : "\\wince";
  bib_file += "\\release\\ram.bib";

  for(int i = 0; i < argc; i++){
    if(strnicmp(argv[i], "-o", 3) == 0)
      output_file = tounicode(argv[i] + 3);
    else if(strnicmp(argv[i], "-g", 3) == 0)
      launch = true;
    else if(strnicmp(argv[i], "/?", 3) == 0 || argc == 1){
      printf("Usage:  romimage [options] filename.bib\n\n"
             "Valid options are:\n"
             "  -o output file - defaults to kernelname.bin\n"
             "  -g             - auto launch (only used when generating an sre file)\n"
             "  /?             - this usage statement\n\n"
             "Romimage also checks various environment variables\n"
             "  To suppress various output:\n"
             "    ri_suppress_info = all,move,module,file,memory,hole,e32,o32,symbol\n\n"
             "  To comma delimit some of the output lists for easier parsing:\n"
             "    ri_comma_delimit = 1\n\n");
      return false;
    }
    else if(argv[i][0] != '-')
      bib_file = argv[i];
  }

  char *suppress = (suppress = getenv("ri_suppress_info")) ? strstr(suppress, "all") : NULL;
  if(suppress)
    _putenv("ri_suppress_info=all,move,module,file,memory,hole,e32,o32,symbol");

  return true;
}

/*****************************************************************************/
/*
  this is what happens when people want weird features
 */
bool comp_mem_itr(const MemoryList::iterator &mem1, const MemoryList::iterator &mem2){ 
  return mem1->origional_address.address() < mem2->origional_address.address();
}

/*****************************************************************************/
/*
  theory here is that we go through all the memory sections on the first pass 
  and make the ram start and size cover them all (no gaps allowed here).  

  on the second pass ...
 */
bool pass_state(Config &config, ModuleList &module_list, FileList &file_list, MemoryList &memory_list, MemoryList &reserve_list, ModuleList::iterator &kernel){
  static int pass = 0;

  static char *debug_show = (debug_show = getenv("ri_debug_info")) ? strstr(debug_show, "allpasses") : NULL;
  if(!debug_show)
    last_pass = !config.autosize; // is this the last pass.  Last pass happens when autosize is 0
  
  if(!config.autosize && !pass)
    return true;

  printf("\nPass %d...\n\n", ++pass);

  MemoryList::iterator mem_itr, mem_itr2, ram_section;
  DWORD start = 0xffffffff;
  DWORD size = 0;

  DWORD dll_data_start = 0;
  DWORD dll_data_end   = 0;

// autosize stuff

//*********************************************//  
// multixip part
  if(config.xipchain){
    if(config.autosize){
      if(config.rom_autosize){
        // rom autosize
        for(mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++){
          if(mem_itr->type() == RAM_TYPE)
            ram_section = mem_itr;
          
          if(mem_itr->type() == RAMIMAGE_TYPE || mem_itr->type() == NANDIMAGE_TYPE || (mem_itr->type() == RAM_TYPE && config.ram_autosize)){
            start = min(start, mem_itr->address());
            size += mem_itr->length();
          }
        }

        // automoving the fixupvar for the chain location to the end of everything
        if(config.chainvar.size()){
          DWORD newchain = start + size;
        
          for(mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++){
            if(mem_itr->type() == FIXUPVAR_TYPE && mem_itr->name() == config.chainvar){
              mem_itr->Address::set(mem_itr->address(), newchain);
              break;
            }
          }
  
          // moving the reserved region
          for(mem_itr = reserve_list.begin(); mem_itr != reserve_list.end(); mem_itr++){
            if(mem_itr->address() == config.xipchain){
              mem_itr->Address::set(newchain, mem_itr->length());
              break;
            }
          }
  
          // move pointer to the end
          config.xipchain = newchain;
        }

        for(mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++){
          if(mem_itr->type() == RAMIMAGE_TYPE || mem_itr->type() == NANDIMAGE_TYPE || (mem_itr->type() == RAM_TYPE && config.ram_autosize)){
            mem_itr->Address::set(start, size);
          }
        }
      }
    }
    else{
      bool setchain = true;       // to prevent this from happening every ramimage section through
      
      MemoryList::iterator last;
      vector<MemoryList::iterator>::iterator last_itr;
      vector<MemoryList::iterator>::iterator mem_itr_itr;

      vector<MemoryList::iterator> temp_list;

      /* we need to do the rom positioning by address but we can't sort the list directly because
         we'll break the iterator links in the modules list.  So we'll create a list of iterators 
         to the memory list.  Sort that the way we want it.  Then we can process on that for this 
         step and we don't need to restore the origional list at all.         
         */
      for(mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++)
        temp_list.push_back(mem_itr);

      sort(temp_list.begin(), temp_list.end(), comp_mem_itr);

      // rom positioning
      for(last_itr = temp_list.end(), mem_itr_itr = temp_list.begin(); mem_itr_itr != temp_list.end(); mem_itr_itr++){
        if((*mem_itr_itr)->type() == RAM_TYPE)
          ram_section = *mem_itr_itr;
        
        if((*mem_itr_itr)->type() == RAMIMAGE_TYPE){
          if(config.rom_autosize){
            if(last_itr != temp_list.end()){
              if(setchain && config.chainvar.size()){
                DWORD newchain = (*last_itr)->address_end();
                
                // automoving the fixupvar for the chain location to the end of everything
                for(mem_itr2 = memory_list.begin(); mem_itr2 != memory_list.end(); mem_itr2++){
                  if(mem_itr2->type() == FIXUPVAR_TYPE && mem_itr2->name() == config.chainvar){
                    mem_itr2->Address::set(mem_itr2->address(), newchain);
                    fprintf(stderr, "Warning: Changed fixupvar %s to point to new chain location %08x\n", mem_itr2->name().c_str(), newchain);
                    break;
                  }
                }

                // moving the reserved region
                for(mem_itr2 = reserve_list.begin(); mem_itr2 != reserve_list.end(); mem_itr2++){
                  if(mem_itr2->address() == config.xipchain){
                    mem_itr2->Address::set(newchain, mem_itr2->length());
                    fprintf(stderr, "Warning: Moved Chain file start to %08x\n", newchain);
                    break;
                  }
                }

                if(mem_itr2 == reserve_list.end()){
                  fprintf(stderr, "Error: Chain address specified in XIPSCHAIN but no matching reserve section found.\n");
                  return false;
                }

                start = align_64k((*last_itr)->address_end() + mem_itr2->length());
                
                // move pointer to the end
                config.xipchain = newchain;
                setchain = false;
              }
              else              
                start = align_64k((*last_itr)->address_end());
            }
            else
              start = (*mem_itr_itr)->address();
            
            size = (*mem_itr_itr)->m_romhdr.physlast - (*mem_itr_itr)->m_romhdr.physfirst + (*mem_itr_itr)->rom_gap;

            (*mem_itr_itr)->Address::set(start, size);

            if(debug_show){
              printf("Region %s (%08x - %08x)\n", (*mem_itr_itr)->name().c_str(), (*mem_itr_itr)->address(), (*mem_itr_itr)->address_end());
            }
          }

          last_itr = mem_itr_itr;
        }
      }

      // sort by bib order (origional order) done automaticaly because we never changed it.  hahaha

      // ram positioning
      if(config.ram_autosize){
        start = align_64k(start + size);

        if(start > ram_section->address())
          ram_section->Address::set(start, ram_section->address_end() - start);
        else
          ram_section->Address::set(start, ram_section->address_end());

        if(debug_show){
          printf("Ram (%08x - %08x)\n", ram_section->address(), ram_section->address_end());
        }
      }

      // nandimage allocations
      for(last = memory_list.end(), mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++){
        if(mem_itr->type() == NANDIMAGE_TYPE){
          if(config.rom_autosize){
            if(last != memory_list.end())
              start = align_64k(last->address_end());
            else
              start = align_64k(start + size);
            
            size = mem_itr->m_romhdr.physlast - mem_itr->m_romhdr.physfirst + mem_itr->rom_gap;
  
            mem_itr->Address::set(start, size);

            if(debug_show){
              printf("Region %s (%08x - %08x)\n", mem_itr->name().c_str(), mem_itr->address(), mem_itr->address_end());
            }
          }

          last = mem_itr;
        }
      }

      // dll allocations
      for(last = memory_list.end(), mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++){
        if(mem_itr->type() == NANDIMAGE_TYPE || mem_itr->type() == RAMIMAGE_TYPE){
          if(config.dll_addr_autosize){
            if(last != memory_list.end()){
              mem_itr->dll_data_top = align_64k(last->dll_data_top + (mem_itr->dll_data_top - mem_itr->dll_data_bottom));
              mem_itr->dll_data_bottom_orig = mem_itr->dll_data_bottom = last->dll_data_top;

              mem_itr->kernel_top = align_64k(last->kernel_top + (mem_itr->kernel_top - mem_itr->kernel_bottom));
              mem_itr->kernel_bottom_orig = mem_itr->kernel_bottom = last->kernel_top;

              mem_itr->kernel_dll_top = align_64k(last->kernel_dll_top + (mem_itr->kernel_dll_top - mem_itr->kernel_dll_bottom));
              mem_itr->kernel_dll_bottom_orig = mem_itr->kernel_dll_bottom = last->kernel_dll_top;
            }
            else{
              mem_itr->dll_data_top = align_64k(mem_itr->dll_data_top + mem_itr->dll_gap);
              mem_itr->kernel_top = align_64k(mem_itr->kernel_top + mem_itr->dll_gap);
              mem_itr->kernel_dll_top = align_64k(mem_itr->kernel_dll_top + mem_itr->dll_gap);
            }

            if(debug_show && last != memory_list.end()){
              printf("Region %s, data %08x\n", mem_itr->name().c_str(), mem_itr->dll_data_bottom);
            }              
            last = mem_itr;
          }
        }
      }
    }
  }
//*********************************************//  
// non multixip part
  else{
    if(config.autosize){
      for(mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++){
        if(mem_itr->type() == RAM_TYPE)
          ram_section = mem_itr;
  
        if(mem_itr->type() == RAMIMAGE_TYPE || mem_itr->type() == RAM_TYPE){
          start = min(start, mem_itr->address());
          size += mem_itr->length();
        }
      }

      kernel->memory_iterator()->Address::set(start, size);
      ram_section->Address::set(start, size);
    }
    else{
      for(mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++){
        if(mem_itr->type() == RAM_TYPE){
          if(kernel->is_arm())
            start = align_64k(Module::s_romhdr.physlast);
          else
            start = kernel->align_page(Module::s_romhdr.physlast);
          
          size = mem_itr->address_end() - start;

          mem_itr->Address::set(start, size);
          printf("RAM AutoSize: RAM Start=%08x RAM Size=%08x\n", start, size);
          
          if(mem_itr->address() > mem_itr->address_end()){
            cerr << "Error: Image is too large for current RAM and RAMIMAGE settings" << endl;
            cerr << "The default end address of RAM was exceeded. To correct this error, set the environment variable IMGRAM64=1." << endl;
            return false;
          }
          
          break;
        }      
      }
    }
  }

  return true;
}

/*****************************************************************************/
bool init_kernel(ModuleList::iterator &kernel, ModuleList &module_list, const MemoryList &memory_list, const Config &config){
  for(MemoryList::const_iterator mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++)
    if(mem_itr->type() == RAM_TYPE){
      if(!kernel->init_kernel(config.kernel_fixups, *mem_itr))
        return false;

      bool ret = true;
      for(ModuleList::iterator mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++)
        if(mod_itr->is_like_kernel() && !mod_itr->memory_iterator()->is_kernel()){
          cerr << "Error: Module " << mod_itr->name() << " is kernel module but not in kernel region\n";
          ret = false;
        }

      return ret;
    }

  cerr << "Error: No ram section found\n";

  return false;
}

/*****************************************************************************/
bool compute_load_locataions(ModuleList &module_list, const MemoryList &reserve_list, Config &config){
  int offset = 0;
  for(ModuleList::iterator itr = module_list.begin(); itr != module_list.end(); itr++){
    itr->set_load_address(offset);

    offset += itr->get_load_size();

    itr->set_base(reserve_list);

    if(itr->is_like_kernel()){
      static char *suppress = (suppress = getenv("ri_suppress_info")) ? strstr(suppress, "move") : NULL;
      if(!suppress){
        LAST_PASS_PRINT 
          printf("Module %-13s at offset %08x", 
               itr->name().c_str(), 
               itr->memory_iterator()->kernel_top + File::page_size());
        
        LAST_PASS_PRINT printf("\n");
      }
    }
    else if(itr->is_kernel_dll()){
      static char *suppress = (suppress = getenv("ri_suppress_info")) ? strstr(suppress, "move") : NULL;
      if(!suppress){
        LAST_PASS_PRINT 
          printf("Module %-13s at offset %08x", 
               itr->name().c_str(), 
               itr->memory_iterator()->kernel_dll_top + File::page_size());
        
        LAST_PASS_PRINT printf("\n");
      }
    }
    else if(itr->is_dll()){
      static char *suppress = (suppress = getenv("ri_suppress_info")) ? strstr(suppress, "move") : NULL;
      if(!suppress){
        LAST_PASS_PRINT 
          printf("Module %-13s at offset %08x", 
               itr->name().c_str(), 
               itr->memory_iterator()->dll_data_top + File::page_size());
        
        LAST_PASS_PRINT printf("\n");
      }
    }
  }

  return true;
}

/*****************************************************************************/
bool compact_image(AddressList &hole_list, 
                   CopyList &copy_list, 
                   ModuleList &module_list, 
                   MemoryList &memory_list,
                   FileList &file_list, 
                   const MemoryList &reserve_list, 
                   const Config &config,
                   const MemoryList::iterator &xip_mem){
  DWORD next_avail = xip_mem->address();

  MemoryList::iterator mem_itr;
  ModuleList::iterator mod_itr;
  FileList::iterator file_itr;

  // clean up from previous passes
  hole_list.clear();
  copy_list.clear();

  // put the readony sections in place and find holes
  Module::print_header();
  for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) 
    if(mod_itr->memory_iterator() == xip_mem) 
      FATAL(mod_itr->move_readonly_sections(reserve_list, next_avail, hole_list));

  static char *debug_show = (debug_show = getenv("ri_debug_info")) ? strstr(debug_show, "holes") : NULL;

  // add holes around bin signature in non kernel images
  if(!xip_mem->is_kernel()){
    DWORD temp = xip_mem->address();
    hole_list.push_back(Address(temp, ROM_SIGNATURE_OFFSET));
    hole_list.rbegin()->set_hole();

    // signature size for the sig, then another dword for the toc_offset
    temp += ROM_SIGNATURE_OFFSET + (SIGNATURE_SIZE + 1) * sizeof(DWORD);
    hole_list.push_back(Address(temp, xip_mem->address() + File::page_size() - temp));
    hole_list.rbegin()->set_hole();
  }  

  Address::size_sort(hole_list);

  if(debug_show)
    Address::dump(hole_list, false);

  // add great big hole at the end for everything else, actual size is arbitrary, just has to be BIG!
  // (AND HAS TO WATCH OUT FOR ANY RESERVED SECTIONS IN IT'S WAY)
  // check for reserve sections in the way, may have to be changed for XIP's also?
  if(config.xipchain){
    if(xip_mem->address_end() < next_avail){
      fprintf(stderr, "Error: No room left in memory section.  Image is too large for defined regions.\n");
      fprintf(stderr, "  Region %s from %08x to %08x, has grown to %08x\n",
              xip_mem->name().c_str(),
              xip_mem->address(),
              xip_mem->address_end(),
              next_avail);
            
      return false;
    }

    next_avail = File::align_page(next_avail);
    next_avail = max(next_avail, xip_mem->address() + File::page_size());
    next_avail = min(next_avail, xip_mem->address_end());
    
    if(reserve_list.empty() || reserve_list.begin()->address() >= xip_mem->address_end()){
      // reserve section is so far away that it's of no concern
      // we can just add our default size
      hole_list.push_back(Address(next_avail, xip_mem->address_end() - next_avail));
    }
    else{
      for(MemoryList::const_iterator hitr = reserve_list.begin(); 
          hitr != reserve_list.end() && hitr->address()  < xip_mem->address_end(); 
          hitr++){
        if(hitr->address() >= next_avail){
          hole_list.push_back(Address(next_avail, hitr->address() - next_avail));
          next_avail = Memory::find_next_gap(reserve_list, hitr->address(), 0);
        }
      }
  
      hole_list.push_back(Address(next_avail, xip_mem->address_end() - next_avail));
    }
  }
  else{
    next_avail = File::align_page(next_avail);
    
    if(reserve_list.empty() || reserve_list.begin()->address() >= next_avail + 0x08000000){
      // reserve section is so far away that it's of no concern
      // we can just add our default size
      hole_list.push_back(Address(next_avail, 0x08000000));
    }
    else{
      for(MemoryList::const_iterator hitr = reserve_list.begin(); hitr != reserve_list.end(); hitr++){
        if(hitr->address() >= next_avail){
          hole_list.push_back(Address(next_avail, hitr->address() - next_avail));
          next_avail = Memory::find_next_gap(reserve_list, hitr->address(), 0);
        }
      }
  
      hole_list.push_back(Address(next_avail, 0x08000000));
    }
  }

  for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) if(mod_itr->memory_iterator() == xip_mem) FATAL(mod_itr->move_readwrite_sections(hole_list, copy_list));
  for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) if(mod_itr->memory_iterator() == xip_mem) FATAL(mod_itr->move_eo32_structures(hole_list));

  for(mem_itr = memory_list.begin(); mem_itr != memory_list.end(); mem_itr++){
    if(mem_itr->type() == EXTENSION_TYPE){
      if(mem_itr->extension_location.empty() && xip_mem->is_kernel())
        mem_itr->set_extension_location(xip_mem->name());
      
      if(mem_itr->extension_location == xip_mem->name())
        FATAL(mem_itr->reserve_extension(hole_list));
    }
  }

  if(file_list.size()){
    File::print_header();
    for(file_itr = file_list.begin(); file_itr != file_list.end(); file_itr++) if(file_itr->memory_iterator() == xip_mem) FATAL(file_itr->move_locataion(hole_list));
  }

  Module::print_header();
  for(mod_itr = module_list.begin(); mod_itr != module_list.end(); mod_itr++) if(mod_itr->memory_iterator() == xip_mem) FATAL(mod_itr->move_name(hole_list));
  for(file_itr = file_list.begin(); file_itr != file_list.end(); file_itr++) if(file_itr->memory_iterator()   == xip_mem) FATAL(file_itr->move_name(hole_list));

  Address::dump(hole_list);

  return true;
}

