context: /*
      Copyright 2007, Joe Davidson <joedavidson@gmail.com>
  
      This file is part of FFTPatcher.
  
      FFTPatcher is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      FFTPatcher is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with FFTPatcher.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Collections.Generic;
  using System.IO;
  using PatcherLib.Datatypes;
  using PatcherLib.Helpers;
  using PatcherLib.Utilities;
  
  namespace PatcherLib.Iso
  {
      public static class PspIso
      {
          public class PspIsoInfo
          {
              private delegate void MyFunc( string path, Sectors sector, bool throwWhenNotFound );
  
              private IDictionary<Sectors, long> fileToSectorMap;
              private IDictionary<Sectors, long> fileToSizeMap;
  
              private PspIsoInfo() { }
              public long this[PspIso.Sectors file] { get { return fileToSectorMap[file]; } }
  
              [System.Diagnostics.DebuggerDisplay( "Start: {StartSector} - End: {EndSector} - {Size}" )]
              private struct FileSpaceInfo
              {
                  public long StartSector { get; set; }
                  public long EndSector { get; set; }
                  public long Size { get; set; }
                  public long NumSectors { get { return EndSector - StartSector + 1; } }
              }
  
              public bool ContainsKey( Sectors sector )
              {
                  return fileToSectorMap.ContainsKey( sector );
              }
  
              public int GetSectorWithFreeSpace( int freeSpaceNeeded )
              {
                  int sectorsNeeded = (freeSpaceNeeded - 1) / 2048 + 1;
                      
                  List<FileSpaceInfo> allFiles = new List<FileSpaceInfo>();
  
                  foreach (var key in fileToSectorMap.Keys)
                  {
                      allFiles.Add( new FileSpaceInfo
                      {
                          StartSector = fileToSectorMap[key],
                          Size = fileToSizeMap[key],
                          EndSector = fileToSectorMap[key] + (fileToSizeMap[key] - 1) / 2048
                      } );
                  }
  
                  allFiles.Sort( ( a, b ) => a.StartSector.CompareTo( b.StartSector ) );
  
                  for (int i = 0; i < allFiles.Count - 1; i++)
                  {
                      if (allFiles[i + 1].StartSector - allFiles[i].EndSector - 1 >= sectorsNeeded)
                      {
                          return (int)allFiles[i].EndSector + 1;
                      }
                  }
  
                  return -1;
              }
  
              public void AddFile( PspIso.Sectors file, int sector, int size )
              {
                  fileToSectorMap[file] = sector;
                  fileToSizeMap[file] = size;
              }
  
              public void RemoveFile( PspIso.Sectors file )
              {
                  if (fileToSectorMap.ContainsKey( file ))
                  {
                      fileToSectorMap.Remove( file );
                  }
                  if (fileToSizeMap.ContainsKey( file ))
                  {
                      fileToSizeMap.Remove( file );
                  }
              }
  
              public long GetFileSize( PspIso.Sectors file )
              {
                  return fileToSizeMap[file];
              }
  
  
              static IDictionary<int, PspIsoInfo> hashCodeToInfoMap = new Dictionary<int, PspIsoInfo>();
  
              public static PspIsoInfo GetPspIsoInfo( Stream iso )
              {
                  int hashCode = iso.GetHashCode();
                  if (!hashCodeToInfoMap.ContainsKey( hashCode ))
                  {
                      hashCodeToInfoMap[hashCode] = GetPspIsoInfo( ImageMaster.IsoReader.GetRecord( iso ) );
                  }
                  return hashCodeToInfoMap[hashCode];
              }
  
              private static PspIsoInfo GetPspIsoInfo( ImageMaster.ImageRecord record )
              {
                  ImageMaster.ImageRecord myRecord = null;
                  var myDict = new Dictionary<Sectors, long>();
                  var myOtherDict = new Dictionary<Sectors, long>();
                  MyFunc func =
                      delegate( string path, Sectors sector, bool throwOnError )
                      {
                          myRecord = record.GetItemPath( path );
                          if (myRecord != null)
                          {
                              myDict[sector] = myRecord.Location;
                              myOtherDict[sector] = myRecord.Size;
                          }
                          else if (throwOnError)
                          {
                              throw new FileNotFoundException( "couldn't find file in ISO", path );
                          }
                      };
                  myDict[Sectors.Root] = 22;
                  myOtherDict[Sectors.Root] = 0x800;
                  func( "PSP_GAME", Sectors.PSP_GAME, true );
                  func( "PSP_GAME/ICON0.PNG", Sectors.PSP_GAME_ICON0_PNG, true );
                  func( "PSP_GAME/PARAM.SFO", Sectors.PSP_GAME_PARAM_SFO, true );
                  func( "PSP_GAME/PIC0.PNG", Sectors.PSP_GAME_PIC0_PNG, true );
                  func( "PSP_GAME/PIC1.PNG", Sectors.PSP_GAME_PIC1_PNG, true );
                  func( "PSP_GAME/SYSDIR", Sectors.PSP_GAME_SYSDIR, true );
                  func( "PSP_GAME/SYSDIR/BOOT.BIN", Sectors.PSP_GAME_SYSDIR_BOOT_BIN, true );
                  func( "PSP_GAME/SYSDIR/EBOOT.BIN", Sectors.PSP_GAME_SYSDIR_EBOOT_BIN, true );
                  func( "PSP_GAME/SYSDIR/UPDATE", Sectors.PSP_GAME_SYSDIR_UPDATE, true );
                  func( "PSP_GAME/SYSDIR/UPDATE/DATA.BIN", Sectors.PSP_GAME_SYSDIR_UPDATE_DATA_BIN, true );
                  func( "PSP_GAME/SYSDIR/UPDATE/EBOOT.BIN", Sectors.PSP_GAME_SYSDIR_UPDATE_EBOOT_BIN, true );
                  func( "PSP_GAME/SYSDIR/UPDATE/PARAM.SFO", Sectors.PSP_GAME_SYSDIR_UPDATE_PARAM_SFO, true );
                  func( "PSP_GAME/USRDIR", Sectors.PSP_GAME_USRDIR, true );
                  func( "PSP_GAME/USRDIR/fftpack.bin", Sectors.PSP_GAME_USRDIR_fftpack_bin, true );
                  func( "PSP_GAME/USRDIR/movie", Sectors.PSP_GAME_USRDIR_movie, true );
                  func( "PSP_GAME/USRDIR/movie/001_HolyStone.pmf", Sectors.PSP_GAME_USRDIR_movie_001_HolyStone_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/002_Opening.pmf", Sectors.PSP_GAME_USRDIR_movie_002_Opening_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/003_Abduction.pmf", Sectors.PSP_GAME_USRDIR_movie_003_Abduction_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/004_Kusabue.pmf", Sectors.PSP_GAME_USRDIR_movie_004_Kusabue_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/005_Get_away.pmf", Sectors.PSP_GAME_USRDIR_movie_005_Get_away_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/006_Reassume_Dilita.pmf", Sectors.PSP_GAME_USRDIR_movie_006_Reassume_Dilita_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/007_Dilita_Advice.pmf", Sectors.PSP_GAME_USRDIR_movie_007_Dilita_Advice_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/008_Ovelia_and_Dilita.pmf", Sectors.PSP_GAME_USRDIR_movie_008_Ovelia_and_Dilita_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/009_Dilita_Musing.pmf", Sectors.PSP_GAME_USRDIR_movie_009_Dilita_Musing_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/010_Ending.pmf", Sectors.PSP_GAME_USRDIR_movie_010_Ending_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/011_Russo.pmf", Sectors.PSP_GAME_USRDIR_movie_011_Russo_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/012_Valuhurea.pmf", Sectors.PSP_GAME_USRDIR_movie_012_Valuhurea_pmf, true );
                  func( "PSP_GAME/USRDIR/movie/013_StaffRoll.pmf", Sectors.PSP_GAME_USRDIR_movie_013_StaffRoll_pmf, true );
                  func( "UMD_DATA.BIN", Sectors.UMD_DATA_BIN, true );
                  func( "PSP_GAME/USRDIR/CHARMAP", Sectors.PSP_GAME_USRDIR_CHARMAP, false );
                  PspIsoInfo result = new PspIsoInfo();
                  result.fileToSectorMap = myDict;
                  result.fileToSizeMap = myOtherDict;
                  return result;
              }
  
  
              public static PspIsoInfo GetPspIsoInfo( string iso )
              {
                  return GetPspIsoInfo( ImageMaster.IsoReader.GetRecord( iso ) );
              }
          }
  
  
          private static readonly long[] bootBinLocations = { 0x10000, 0x0FED8000 };
          private static byte[] buffer = new byte[1024];
          private const int bufferSize = 1024;
          private static byte[] euSizes = new byte[] { 0xA4, 0x84, 0x3A, 0x00, 0x00, 0x3A, 0x84, 0xA4 };
          //public const long FFTPackLocation = 0x02C20000;
          private static byte[] jpSizes = new byte[] { 0xE4, 0xD9, 0x37, 0x00, 0x00, 0x37, 0xD9, 0xE4 };
  
          private static readonly Dictionary<PspIso.Sectors, int> FileToRamOffsets = new Dictionary<PspIso.Sectors, int>()
          {
              { Sectors.PSP_GAME_SYSDIR_BOOT_BIN, 0x8803FAC }
          };
  
          public static uint GetRamOffsetUnsigned(int sector)
          {
              return GetRamOffsetUnsigned((PspIso.Sectors)sector);
          }
  
          public static uint GetRamOffsetUnsigned(PspIso.Sectors sector)
          {
              int fileToRamOffset = GetRamOffset(sector);
              return (fileToRamOffset == -1) ? 0U : (uint)fileToRamOffset;
          }
  
          public static int GetRamOffset(int sector)
          {
              return GetRamOffset((PspIso.Sectors)sector);
          }
  
          public static int GetRamOffset(PspIso.Sectors sector)
          {
              int fileToRamOffset = 0;
              if (!FileToRamOffsets.TryGetValue(sector, out fileToRamOffset))
                  fileToRamOffset = -1;
  
              return fileToRamOffset;
          }
  
          public static string GetSectorName(int sector)
          {
              return GetSectorName((PspIso.Sectors)sector);
          }
  
          public static string GetSectorName(PspIso.Sectors sector)
          {
              string name = Enum.GetName(typeof(PspIso.Sectors), sector);
              return name ?? ((int)sector).ToString();
          }
  
          public static string GetFileName(FFTPack.Files file)
          {
              string name = Enum.GetName(typeof(FFTPack.Files), file);
              return name ?? "FFTPACK.BIN (" + ((int)file).ToString() + ")";
          }
  
          public static string GetModifiedSectorName(int sector)
          {
              return GetModifiedSectorName((PspIso.Sectors)sector);
          }
  
          public static string GetModifiedSectorName(PspIso.Sectors sector)
          {
              if (sector == Sectors.PSP_GAME_SYSDIR_BOOT_BIN)
              {
                  return "BOOT.BIN";
              }
  
              return ISOHelper.GetModifiedPathName(GetSectorName(sector));
          }
  
          public static string GetModifiedFileName(FFTPack.Files file)
          {
              return ISOHelper.GetModifiedPathName(GetFileName(file));
          }
  
          /// <summary>
          /// Decrypts the ISO.
          /// </summary>
          /// <param name="filename">The filename of the ISO to decrypt.</param>
          public static void DecryptISO( string filename )
          {
              FileStream stream = null;
              try
              {
                  stream = new FileStream( filename, FileMode.Open );
                  PspIsoInfo info = PspIsoInfo.GetPspIsoInfo( stream );
                  DecryptISO( stream, info );
              }
              catch (NotSupportedException)
              {
                  throw;
              }
              finally
              {
                  if (stream != null)
                  {
                      stream.Flush();
                      stream.Close();
                      stream = null;
                  }
              }
          }
  
          /// <summary>
          /// Decrypts the ISO.
          /// </summary>
          /// <param name="stream">The stream of the ISO to decrypt.</param>
          public static void DecryptISO( Stream stream, PspIsoInfo info )
          {
              if (IsJP( stream, info ))
              {
                  CopyBytes( stream, info[Sectors.PSP_GAME_SYSDIR_BOOT_BIN] * 2048, 0x37D9E4, info[Sectors.PSP_GAME_SYSDIR_EBOOT_BIN] * 2048, 0x37DB40 );
              }
  
gt:             else if (IsUS( stream, info ) || IsEU( stream, info ))