public void ReadSong(Song2014 songXml, Sng2014File sngFile)
        {
            Int16[] tuning = {
                songXml.Tuning.String0,
                songXml.Tuning.String1,
                songXml.Tuning.String2,
                songXml.Tuning.String3,
                songXml.Tuning.String4,
                songXml.Tuning.String5,
            };
            parseEbeats(songXml, sngFile);
            parsePhrases(songXml, sngFile);
            parseChords(songXml, sngFile, tuning, songXml.Arrangement == "Bass");
            // vocals use different parse function
            sngFile.Vocals = new VocalSection { Vocals = new Vocal[0] };
            parsePhraseIterations(songXml, sngFile);
            parsePhraseExtraInfo(songXml, sngFile);
            parseNLD(songXml, sngFile);
            parseActions(songXml, sngFile);
            parseEvents(songXml, sngFile);
            parseTones(songXml, sngFile);
            parseDNAs(songXml, sngFile);
            parseSections(songXml, sngFile);
            parseArrangements(songXml, sngFile);
            parseMetadata(songXml, sngFile, tuning);

            // this needs to be initialized after arrangements
            parseChordNotes(songXml, sngFile);
        }
        /// <param name="song"></param>
        /// <param name="difficultyLevel">null = highest level available</param>
        public static Score GetScoreForMaxDifficultyLevel(Song2014 song, int? difficultyLevel)
        {
            List<PhraseIterationWithEndTime> iterationsWithEndTime = PhraseIterationWithEndTime.listFromBaseArray(song.PhraseIterations);

            IEnumerable<SongNoteChordWrapper> allSounds = Enumerable.Empty<SongNoteChordWrapper>();

            for (int phraseId = 0; phraseId < song.Phrases.Length;phraseId++)
            {
                var phrase = song.Phrases[phraseId];
                var diffLevel = phrase.MaxDifficulty;
                if (difficultyLevel.HasValue && difficultyLevel < diffLevel)
                    diffLevel = difficultyLevel.Value;

                var selectedLevel = song.Levels.FirstOrDefault(x => x.Difficulty == diffLevel);

                var phraseIterations = iterationsWithEndTime.Where(x => x.PhraseId == phraseId).ToArray();
                for (int i = 0; i < phraseIterations.Length; i++)
                {
                    var iterationWithEndTime = phraseIterations[i];
                    var notes = selectedLevel.Notes.Where(x => iterationWithEndTime.contains(x.Time));
                    var chords = selectedLevel.Chords.Where(x => iterationWithEndTime.contains(x.Time));

                    allSounds = allSounds.Union(notes.Select(x => new SongNoteChordWrapper(x)))
                                .Union(chords.Select(x => new SongNoteChordWrapper(x)));
                }
            }
            var score = CreateSong(song, allSounds);
            return score;
        }
        public Song Song2014ToSong(Song2014 rs2014Song)
        {
            var rs1Song = new Song();
            AddSongMetadata(rs2014Song, rs1Song);
            AddElements(rs2014Song, rs1Song);
            AddDifferences(rs2014Song, rs1Song);

            return rs1Song;
        }
        public AttributesHeader2014(string arrangementFileName, Arrangement arrangement, DLCPackageData info, Platform platform)
        {
            IsVocal = arrangement.ArrangementType == Sng.ArrangementType.Vocal;
            SongContent = (IsVocal) ? null : Song2014.LoadFromFile(arrangement.SongXml.File);
            var dlcName = info.Name.ToLower();

            var albumUrn = String.Format(URN_TEMPLATE, TagValue.Image.GetDescription(), TagValue.DDS.GetDescription(), String.Format("album_{0}", dlcName));
            var jsonUrn = String.Format(URN_TEMPLATE, TagValue.Database.GetDescription(), TagValue.JsonDB.GetDescription(), String.Format("{0}_{1}", dlcName, arrangementFileName));

            //FILL ATTRIBUTES
            this.AlbumArt = albumUrn;
            ArrangementName = arrangement.Name.ToString();
            DLC = true;
            DLCKey = info.Name;
            LeaderboardChallengeRating = 0;
            ManifestUrn = jsonUrn;
            MasterID_RDV = arrangement.MasterId;
            PersistentID = arrangement.Id.ToString().Replace("-", "").ToUpper();
            Shipping = true;
            SKU = "RS2";
            SongKey = info.Name;

            if (!IsVocal)
            {
                AlbumName = AlbumNameSort = info.SongInfo.Album;
                ArtistName = info.SongInfo.Artist;
                CentOffset = arrangement.TuningPitch != 0 ? TuningFrequency.Frequency2Cents(arrangement.TuningPitch) : 0.0;
                ArtistNameSort = info.SongInfo.ArtistSort;
                CapoFret = (arrangement.Sng2014.Metadata.CapoFretId == 0xFF) ? CapoFret = 0 : Convert.ToDecimal(arrangement.Sng2014.Metadata.CapoFretId);
                DNA_Chords = arrangement.Sng2014.DNACount[(int) DNAId.Chord];
                DNA_Riffs = arrangement.Sng2014.DNACount[(int) DNAId.Riff];
                DNA_Solo = arrangement.Sng2014.DNACount[(int) DNAId.Solo];
                NotesEasy = arrangement.Sng2014.NoteCount[0];
                NotesMedium = arrangement.Sng2014.NoteCount[1];
                NotesHard = arrangement.Sng2014.NoteCount[2];
                EasyMastery = NotesEasy / NotesHard;
                MediumMastery = NotesMedium / NotesHard;
                Representative = Convert.ToInt32(!arrangement.BonusArr);
                RouteMask = (int)arrangement.RouteMask;

                // TODO this is not quite it but much closer
                SongDiffEasy = SongContent.SongLength / NotesEasy;
                SongDiffMed = SongContent.SongLength / NotesMedium;
                SongDiffHard = SongContent.SongLength / NotesHard;
                SongDifficulty = SongDiffHard;

                SongLength = (double?)Math.Round(SongContent.SongLength, 3, MidpointRounding.AwayFromZero);
                SongName = info.SongInfo.SongDisplayName;
                SongNameSort = info.SongInfo.SongDisplayNameSort;
                SongYear = info.SongInfo.SongYear;

                var tunDef = TuningDefinitionRepository.Instance().Select(arrangement.Tuning, platform.version);
                Tuning = tunDef.Tuning;
            }
        }
        public void Convert(string zigSrcPath, string destPath)
        {
            var deser = new XmlSerializer(typeof(ZpeSong));
            ZpeSong zigSong;
            using (FileStream stream = new FileStream(zigSrcPath, FileMode.Open))
            {
                zigSong = (ZpeSong)deser.Deserialize(stream);
            }

            if (zigSong.PueVersion != 46)
                throw new Exception("Incompatable version of Ziggy Pro Editor XML file");

            bool foundTrack = false;
            StringBuilder sb = new StringBuilder();
            // cross matching arrangment arrays
            string[] rsArray = new string[] { "Lead", "Rhythm", "Bass" };
            string[] zigArray = new string[] { "PART REAL_GUITAR_22", "PART REAL_GUITAR", "PART REAL_BASS_22" };
            int arrIndex = -1;

            foreach (var arrangement in zigArray)
            {
                arrIndex++;
                var guitarTrack = zigSong.Tracks.SingleOrDefault(tr => arrangement.Equals(tr.Name));

                if (guitarTrack == null)
                {
                    continue;
                    //throw new Exception("Couldn't find a guitar track");
                }

                foundTrack = true;
                var rsSong = new Song2014();
                AddSongMetadata(rsSong, zigSong, rsArray[arrIndex]);
                AddEbeats(rsSong, zigSong, arrangement);
                AddNotes(rsSong, zigSong, arrangement);
                AddToneProps(rsSong, rsArray[arrIndex]);

                var destDir = Path.GetDirectoryName(destPath);
                var destName = Path.GetFileNameWithoutExtension(destPath);
                var xmlDestPath = String.Format("{0}_{1}.xml", Path.Combine(destDir, destName), rsArray[arrIndex]);

                using (FileStream stream = new FileStream(xmlDestPath, FileMode.Create))
                {
                    rsSong.Serialize(stream, true);
                }
            }

            if (!foundTrack)
                throw new NullReferenceException("Did not find any Rocksmith 2014 compatible Ziggy Pro tracks in " + Path.GetFileName(zigSrcPath) + Environment.NewLine);
        }
        private static void AddSongMetadata(Song2014 rsSong, ZpeSong zigSong, string arrangment)
        {
            // standard meta header data
            rsSong.Version = "7";
            rsSong.Arrangement = arrangment;
            rsSong.Part = 1;
            rsSong.Offset = 0;
            rsSong.CentOffset = "0";
            rsSong.StartBeat = 0;
            rsSong.Capo = 0;
            rsSong.AlbumName = "Unknown Album";
            rsSong.AlbumYear = DateTime.Now.ToString("yyyy");
            rsSong.CrowdSpeed = "1";
            rsSong.LastConversionDateTime = DateTime.Now.ToString();
            rsSong.SongLength = zigSong.Length;

            Regex regex = new Regex(" - ");
            string[] artistTitle = regex.Split(zigSong.Name);
            rsSong.ArtistName = artistTitle[0] == null ? zigSong.Name : artistTitle[0];
            rsSong.Title = artistTitle[1] == null ? zigSong.Name : artistTitle[1];

            ZpeTempo tempo = zigSong.Tracks[0].Tempos[0];
            float BPM = (float)Math.Round((float)60000000 / tempo.RawTempo, 3);
            rsSong.AverageTempo = BPM;

            ZpeTuning tuning = null;
            for (int i = 0; i < zigSong.Tunings.Tuning.Count; i++)
            {
                if (arrangment == "Lead" || arrangment == "Rhythm")
                    if (zigSong.Tunings.Tuning[i].IsGuitarTuning)
                    {
                        tuning = zigSong.Tunings.Tuning[i];
                        break;
                    }

                if (arrangment == "Bass")
                    if (zigSong.Tunings.Tuning[i].IsBassTuning)
                    {
                        tuning = zigSong.Tunings.Tuning[i];
                        break;
                    }
            }

            if (tuning == null)
                throw new Exception("ZPE XML does not contain tuning");

            rsSong.Tuning = new TuningStrings { String0 = tuning.E, String1 = tuning.A, String2 = tuning.D, String3 = tuning.G, String4 = tuning.B, String5 = tuning.HighE };
        }
        /// <param name="song"></param>
        /// <param name="difficultyLevel">null = highest level available</param>
        public static Score GetScoreForExactDifficultyLevel(Song2014 song, int? difficultyLevel)
        {
            SongLevel2014 selectedLevel;

            if(difficultyLevel.HasValue)
                selectedLevel = song.Levels.FirstOrDefault(x => x.Difficulty == difficultyLevel);
            else
                selectedLevel = song.Levels.OrderBy(x => x.Difficulty).LastOrDefault();

            if (selectedLevel == null)
                return null;

            var allSounds = selectedLevel.Notes.Select(x => new SongNoteChordWrapper(x))
                            .Union(selectedLevel.Chords.Select(x => new SongNoteChordWrapper(x)));

            var score = CreateSong(song, allSounds);
            return score;
        }
        /// <summary>
        /// Convert RS2014 (Song2014) to XML file
        /// </summary>
        /// <param name="rs2014Song"></param>
        /// <param name="outputPath"></param>
        /// <param name="overWrite"></param>
        /// <returns>RS2014 XML file path</returns>
        public string Song2014ToXml(Song2014 rs2014Song, string outputPath, bool overWrite)
        {
            if (File.Exists(outputPath) && overWrite)
                File.Delete(outputPath);
            else
            {
                var outputDir = Path.GetDirectoryName(outputPath);
                var outputFile = String.Format("{0}_{1}", rs2014Song.Title, rs2014Song.Arrangement);
                outputFile = String.Format("{0}{1}", outputFile.GetValidName(false, true).ToLower(), "_rs2014.xml");
                outputPath = Path.Combine(outputDir, outputFile);
            }

            using (var stream = File.OpenWrite(outputPath))
            {
                rs2014Song.Serialize(stream, false);
            }

            return outputPath;
        }
        private void AddSongMetadata(Song2014 rs2014Song, Song rs1Song)
        {
            // for consistency apply old naming method ;(
            rs1Song.Title = String.Format("{0} - {1}", rs2014Song.Title, rs2014Song.Arrangement);
            if (rs2014Song.Part > 1)
                rs1Song.Title = String.Format("{0} {1}", rs1Song.Title, rs2014Song.Part);

            rs1Song.Arrangement = rs2014Song.Arrangement;
            rs1Song.Part = rs2014Song.Part;
            rs1Song.Offset = rs2014Song.Offset;
            rs1Song.SongLength = rs2014Song.SongLength;
            rs1Song.StartBeat = rs2014Song.StartBeat;
            rs1Song.AverageTempo = rs2014Song.AverageTempo;
            rs1Song.Tuning = rs2014Song.Tuning;
            rs1Song.Artist = rs2014Song.ArtistName;
            rs1Song.AlbumName = rs2014Song.AlbumName;
            rs1Song.AlbumYear = rs2014Song.AlbumYear;

            // use correct LastConversionDateTime format
            rs1Song.LastConversionDateTime = DateTime.Now.ToString("MM-dd-yy HH:mm");
        }
Example #10
0
 static void ExportArrangement(Score score, Song2014 arrangement, string identifier, int difficulty, 
     string originalFile, ToolkitInfo toolkitInfo)
 {
     var track = Converter.ConvertArrangement(arrangement, identifier, difficulty);
     score.Tracks.Add(track);
     score.Title = arrangement.Title;
     score.Artist = arrangement.ArtistName;
     score.ArtistSort = arrangement.ArtistNameSort;
     score.Album = arrangement.AlbumName;
     score.Year = arrangement.AlbumYear;
     score.Comments = new List<string>();
     score.Comments.Add("Generated by RocksmithToTab v" + VersionInfo.VERSION);
     score.Comments.Add("=> http://www.rocksmithtotab.de");
     score.Comments.Add("Created from archive: " + Path.GetFileName(originalFile));
     if (toolkitInfo != null && toolkitInfo.PackageAuthor != string.Empty)
     {
         score.Comments.Add("CDLC author:  " + toolkitInfo.PackageAuthor);
         score.Tabber = toolkitInfo.PackageAuthor;
     }
     if (toolkitInfo != null && toolkitInfo.PackageVersion != string.Empty)
         score.Comments.Add("CDLC version: " + toolkitInfo.PackageVersion);
 }
        public string SongFile2Song2014File(string songFilePath, bool overWrite)
        {
            Song2014 song2014 = new Song2014();
            using (var obj = new Rs1Converter())
                song2014 = obj.SongToSong2014(Song.LoadFromFile(songFilePath));

            if (!overWrite)
            {
                var srcDir = Path.GetDirectoryName(songFilePath);
                var srcName = Path.GetFileNameWithoutExtension(songFilePath);
                var backupSrcPath = String.Format("{0}_{1}.xml", Path.Combine(srcDir, srcName), "RS1");

                // backup original RS1 file
                File.Copy(songFilePath, backupSrcPath);
            }

            // write converted RS1 file
            using (FileStream stream = new FileStream(songFilePath, FileMode.Create))
                song2014.Serialize(stream, true);

            return songFilePath;
        }
        private void parseMetadata(Song2014 xml, Sng2014File sng, Int16[] tuning)
        {
            // Easy, Medium, Hard
            NoteCount = new int[3];
            NoteCount[0] = getNoteCount(sng, 0);
            NoteCount[1] = getNoteCount(sng, 1);
            NoteCount[2] = getNoteCount(sng, 2);

            sng.Metadata = new Metadata();
            sng.Metadata.MaxScore = 100000;

            sng.Metadata.MaxDifficulty = getMaxDifficulty(xml);
            sng.Metadata.MaxNotesAndChords = NoteCount[2];
            sng.Metadata.MaxNotesAndChords_Real = sng.Metadata.MaxNotesAndChords;//num unique notes+not ignored
            sng.Metadata.PointsPerNote = sng.Metadata.MaxScore / sng.Metadata.MaxNotesAndChords;

            sng.Metadata.FirstBeatLength = xml.Ebeats[1].Time - xml.Ebeats[0].Time;
            sng.Metadata.StartTime = xml.Offset * -1;
            sng.Metadata.CapoFretId = (xml.Capo == 0) ? unchecked((Byte)(-1)) : xml.Capo;
            readString(xml.LastConversionDateTime, sng.Metadata.LastConversionDateTime);
            sng.Metadata.Part = xml.Part;
            sng.Metadata.SongLength = xml.SongLength;
            sng.Metadata.StringCount = 6;
            sng.Metadata.Tuning = tuning ?? new Int16[sng.Metadata.StringCount];
            // calculated when parsing arrangements
            sng.Metadata.Unk11_FirstNoteTime = first_note_time;
            sng.Metadata.Unk12_FirstNoteTime = first_note_time;
        }
        private void parseEvents(Song2014 xml, Sng2014File sng)
        {
            sng.Events = new EventSection();
            sng.Events.Count = xml.Events.Length;
            sng.Events.Events = new Event[sng.Events.Count];

            for (int i = 0; i < sng.Events.Count; i++)
            {
                var evnt = xml.Events[i];
                var e = new Event();
                e.Time = evnt.Time;
                readString(evnt.Code, e.EventName);
                sng.Events.Events[i] = e;
            }
        }
 private void parseEbeats(Song2014 xml, Sng2014File sng)
 {
     sng.BPMs = new BpmSection();
     sng.BPMs.Count = xml.Ebeats.Length;
     sng.BPMs.BPMs = new Bpm[sng.BPMs.Count];
     Int16 measure = 0;
     Int16 beat = 0;
     for (int i = 0; i < sng.BPMs.Count; i++)
     {
         var ebeat = xml.Ebeats[i];
         var bpm = new Bpm();
         bpm.Time = ebeat.Time;
         if (ebeat.Measure >= 0)
         {
             measure = ebeat.Measure;
             beat = 0;
         }
         else
         {
             beat++;
         }
         bpm.Measure = measure;
         bpm.Beat = beat;
         bpm.PhraseIteration = getPhraseIterationId(xml, bpm.Time, true);
         if (beat == 0)
         {
             bpm.Mask |= 1;
             if (measure % 2 == 0)
                 bpm.Mask |= 2;
         }
         sng.BPMs.BPMs[i] = bpm;
     }
 }
        private void parseDNAs(Song2014 xml, Sng2014File sng)
        {
            sng.DNAs = new DnaSection();
            List<Dna> dnas = new List<Dna>();

            DNACount = new int[4];

            // based on events: dna_none=0, dna_solo=1, dna_riff=2, dna_chord=3
            foreach (var e in xml.Events)
            {
                Int32 id = -1;
                switch (e.Code)
                {
                    case "dna_none":
                        id = 0;
                        break;
                    case "dna_solo":
                        id = 1;
                        break;
                    case "dna_riff":
                        id = 2;
                        break;
                    case "dna_chord":
                        id = 3;
                        break;
                }

                if (id != -1)
                {
                    var dna = new Dna();
                    dna.Time = e.Time;
                    dna.DnaId = id;
                    DNACount[id] += 1;
                    dnas.Add(dna);
                }
            }

            sng.DNAs.Dnas = dnas.ToArray();
            sng.DNAs.Count = sng.DNAs.Dnas.Length;
        }
        private Song2014 ConvertPhraseIterations(Song rsSong, Song2014 rsSong2014)
        {
            var phraseIterations = new List<SongPhraseIteration2014>();
            foreach (var songPhraseIteration in rsSong.PhraseIterations)
            {
                // HeroLevels set to null -> prevent some hangs
                phraseIterations.Add(new SongPhraseIteration2014 { PhraseId = songPhraseIteration.PhraseId, HeroLevels = null, Time = songPhraseIteration.Time });
            }
            rsSong2014.PhraseIterations = phraseIterations.ToArray();

            return rsSong2014;
        }
        private Song2014 ConvertChordTemplates(Song rsSong, Song2014 rsSong2014)
        {
            // add chordTemplates elements
            var chordTemplate = new List<SongChordTemplate2014>();
            foreach (var songChordTemplate in rsSong.ChordTemplates)
            {
                // tested ... not the source of game hangs
                //if (String.IsNullOrEmpty(songChordTemplate.ChordName))
                //    continue;

                chordTemplate.Add(new SongChordTemplate2014 { ChordName = songChordTemplate.ChordName, DisplayName = songChordTemplate.ChordName, Finger0 = (sbyte)songChordTemplate.Finger0, Finger1 = (sbyte)songChordTemplate.Finger1, Finger2 = (sbyte)songChordTemplate.Finger2, Finger3 = (sbyte)songChordTemplate.Finger3, Finger4 = (sbyte)songChordTemplate.Finger4, Finger5 = (sbyte)songChordTemplate.Finger5, Fret0 = (sbyte)songChordTemplate.Fret0, Fret1 = (sbyte)songChordTemplate.Fret1, Fret2 = (sbyte)songChordTemplate.Fret2, Fret3 = (sbyte)songChordTemplate.Fret3, Fret4 = (sbyte)songChordTemplate.Fret4, Fret5 = (sbyte)songChordTemplate.Fret5 });
            }

            // tested ... not the source of game hangs
            // get rid of duplicate chords if any
            // chordTemplate = chordTemplate.Distinct().ToList();

            // tested ... could be source of game hangs
            if (rsSong.ChordTemplates == null)
            {
                Console.WriteLine("Applied fix to RS1->RS2 ChordTemplates conversion");
                rsSong2014.ChordTemplates = new SongChordTemplate2014[0];
            }
            else
                rsSong2014.ChordTemplates = chordTemplate.ToArray();

            return rsSong2014;
        }
        // Load RS1 CDLC into PackageCreator
        public static DLCPackageData RS1LoadFromFolder(string unpackedDir, Platform targetPlatform, bool convert)
        {
            var data = new DLCPackageData();
            data.Arrangements = new List<Arrangement>();
            data.TonesRS2014 = new List<Tone2014>();
            data.Tones = new List<Tone>();

            data.GameVersion = (convert ? GameVersion.RS2014 : GameVersion.RS2012);
            data.SignatureType = PackageMagic.CON;
            // set default volumes
            data.Volume = -6.5F; // default maybe too quite
            data.PreviewVolume = data.Volume;

            //Load song manifest
            var songsManifestJson = Directory.GetFiles(unpackedDir, "songs.manifest.json", SearchOption.AllDirectories);
            if (songsManifestJson.Length < 1)
                throw new DataException("No songs.manifest.json file found.");
            if (songsManifestJson.Length > 1)
                throw new DataException("More than one songs.manifest.json file found.");

            var attr = new List<Attributes>();
            var songsManifest = Manifest.Manifest.LoadFromFile(songsManifestJson[0]).Entries.ToArray();

            for (int smIndex = 0; smIndex < songsManifest.Count(); smIndex++)
            {
                var smData = songsManifest[smIndex].Value.ToArray()[0].Value;
                attr.Add(smData);
            }

            if (attr.FirstOrDefault() == null)
                throw new DataException("songs.manifest.json file did not parse correctly.");

            // Fill SongInfo
            data.SongInfo = new SongInfo();
            data.SongInfo.SongDisplayName = attr.FirstOrDefault().SongName;
            data.SongInfo.SongDisplayNameSort = attr.FirstOrDefault().SongNameSort;
            data.SongInfo.Album = attr.FirstOrDefault().AlbumName;
            data.SongInfo.SongYear = (attr.FirstOrDefault().SongYear == 0 ? 2012 : attr.FirstOrDefault().SongYear);
            data.SongInfo.Artist = attr.FirstOrDefault().ArtistName;
            data.SongInfo.ArtistSort = attr.FirstOrDefault().ArtistNameSort;
            data.Name = attr.FirstOrDefault().SongKey;

            //Load tone manifest, even poorly formed tone_bass.manifest.json
            var toneManifestJson = Directory.GetFiles(unpackedDir, "*tone*.manifest.json", SearchOption.AllDirectories);
            if (toneManifestJson.Length < 1)
                throw new DataException("No tone.manifest.json file found.");

            // toolkit produces multiple tone.manifest.json files when packing RS1 CDLC files
            // rather than change toolkit behavior just merge manifest files for now
            if (toneManifestJson.Length > 1)
            {
                var mergeSettings = new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Union };
                JObject toneObject1 = new JObject();

                foreach (var tone in toneManifestJson)
                {
                    JObject toneObject2 = JObject.Parse(File.ReadAllText(tone));
                    //(toneObject1.SelectToken("Entries") as JArray).Merge(toneObject2.SelectToken("Entries"));
                    toneObject1.Merge(toneObject2, mergeSettings);
                }

                toneManifestJson = new string[1];
                toneManifestJson[0] = Path.Combine(unpackedDir, "merged.tone.manifest.json");
                string json = JsonConvert.SerializeObject(toneObject1, Formatting.Indented);
                File.WriteAllText(toneManifestJson[0], json);
            }

            var tones2014 = new List<Tone2014>();
            var tones = new List<Tone>();
            var toneManifest = Manifest.Tone.Manifest.LoadFromFile(toneManifestJson[0]);

            for (int tmIndex = 0; tmIndex < toneManifest.Entries.Count(); tmIndex++)
            {
                var tmData = toneManifest.Entries[tmIndex];
                tones.Add(tmData);
            }

            data.Tones = tones;

            // Load AggregateGraph.nt 
            var songDir = Path.Combine(unpackedDir, data.Name);
            if (targetPlatform.platform == GamePlatform.XBox360)
                songDir = Path.Combine(unpackedDir, "Root", data.Name);

            var aggFile = Directory.GetFiles(songDir, "*.nt", SearchOption.TopDirectoryOnly)[0];
            var aggGraphData = AggregateGraph.AggregateGraph.ReadFromFile(aggFile);

            // Load Exports\Songs\*.xblock
            var xblockDir = Path.Combine(songDir, "Exports\\Songs");
            var xblockFile = Directory.GetFiles(xblockDir, "*.xblock", SearchOption.TopDirectoryOnly)[0];
            // xblockFile = "D:\\Temp\\Mapping\\songs.xblock";
            var songsXblock = XblockX.LoadFromFile(xblockFile);

            // create project map for cross referencing arrangements with tones
            var projectMap = AggregateGraph.AggregateGraph.ProjectMap(aggGraphData, songsXblock, toneManifest);

            // Load xml arrangements
            var xmlFiles = Directory.GetFiles(unpackedDir, "*.xml", SearchOption.AllDirectories);
            if (xmlFiles.Length <= 0)
                throw new DataException("Can not find any XML arrangement files");

            foreach (var xmlFile in xmlFiles)
            {
                if (xmlFile.ToLower().Contains("metadata")) // skip DeadFox file
                    continue;

                // some poorly formed RS1 CDLC use just "vocal"
                if (xmlFile.ToLower().Contains("vocal"))
                {
                    // Add Vocal Arrangement
                    data.Arrangements.Add(new Arrangement
                    {
                        Name = ArrangementName.Vocals,
                        ArrangementType = ArrangementType.Vocal,
                        ScrollSpeed = 20,
                        SongXml = new SongXML { File = xmlFile },
                        SongFile = new SongFile { File = "" },
                        CustomFont = false
                    });
                }
                else
                {
                    var attr2014 = new Attributes2014();
                    var rsSong = new Song();
                    var rsSong2014 = new Song2014();

                    // optimized tone matching effort using project mapping algo
                    var result = projectMap.First(m => String.Equals(Path.GetFileName(m.SongXmlPath), Path.GetFileName(xmlFile), StringComparison.CurrentCultureIgnoreCase));
                    if (result.Tones.Count != 1)
                        throw new DataException("Invalid RS1 CDLC Tones Data");

                    var arrangement = attr.First(s => s.SongXml.ToLower().Contains(result.LLID));
                    var tone = tones.First(t => t.Key == result.Tones[0]);

                    using (var obj1 = new Rs1Converter())
                    {
                        rsSong = obj1.XmlToSong(xmlFile);
                        data.SongInfo.AverageTempo = (int)obj1.AverageBPM(rsSong);
                    }

                    if (arrangement.Tuning == "E Standard")
                        rsSong.Tuning = new TuningStrings { String0 = 0, String1 = 0, String2 = 0, String3 = 0, String4 = 0, String5 = 0 };
                    else if (arrangement.Tuning == "DropD")
                        rsSong.Tuning = new TuningStrings { String0 = -2, String1 = 0, String2 = 0, String3 = 0, String4 = 0, String5 = 0 };
                    else if (arrangement.Tuning == "OpenG")
                        rsSong.Tuning = new TuningStrings { String0 = -2, String1 = -2, String2 = 0, String3 = 0, String4 = 0, String5 = -2 };
                    else if (arrangement.Tuning == "EFlat")
                        rsSong.Tuning = new TuningStrings { String0 = -1, String1 = -1, String2 = -1, String3 = -1, String4 = -1, String5 = -1 };
                    else // default to standard tuning
                    {
                        arrangement.Tuning = "E Standard";
                        rsSong.Tuning = new TuningStrings { String0 = 0, String1 = 0, String2 = 0, String3 = 0, String4 = 0, String5 = 0 };
                    }

                    // save/write the changes to xml file
                    using (var obj1 = new Rs1Converter())
                        obj1.SongToXml(rsSong, xmlFile, true);

                    if (convert)
                        using (var obj1 = new Rs1Converter())
                            tones2014.Add(obj1.ToneToTone2014(tone, rsSong));

                    // load attr2014 with RS1 mapped values for use by Arrangement()
                    attr2014.Tone_Base = tone.Name;
                    attr2014.ArrangementName = arrangement.ArrangementName;
                    attr2014.CentOffset = 0;
                    attr2014.DynamicVisualDensity = new List<float>() { 2 };
                    attr2014.SongPartition = arrangement.SongPartition;
                    attr2014.PersistentID = IdGenerator.Guid().ToString();
                    attr2014.MasterID_RDV = RandomGenerator.NextInt();
                    attr2014.ArrangementProperties = new SongArrangementProperties2014();

                    // processing order is important - CAREFUL
                    // RouteMask  None = 0, Lead = 1, Rhythm = 2, Any = 3, Bass = 4
                    // XML file names are usually meaningless to arrangement determination

                    if (arrangement.ArrangementName.ToLower().Contains("lead") ||
                        rsSong.Arrangement.ToLower().Contains("lead"))
                    {
                        attr2014.ArrangementName = "Lead";
                        attr2014.ArrangementType = (int)ArrangementType.Guitar;
                        attr2014.ArrangementProperties.RouteMask = (int)RouteMask.Lead;
                        attr2014.ArrangementProperties.PathLead = 1;
                        attr2014.ArrangementProperties.PathRhythm = 0;
                        attr2014.ArrangementProperties.PathBass = 0;
                    }
                    else if (arrangement.ArrangementName.ToLower().Contains("rhythm") ||
                        rsSong.Arrangement.ToLower().Contains("rhythm"))
                    // || rsSong.Arrangement.ToLower().Contains("guitar"))
                    {
                        attr2014.ArrangementName = "Rhythm";
                        attr2014.ArrangementType = (int)ArrangementType.Guitar;
                        attr2014.ArrangementProperties.RouteMask = (int)RouteMask.Rhythm;
                        attr2014.ArrangementProperties.PathLead = 0;
                        attr2014.ArrangementProperties.PathRhythm = 1;
                        attr2014.ArrangementProperties.PathBass = 0;
                    }
                    else if (arrangement.ArrangementName.ToLower().Contains("combo") ||
                        rsSong.Arrangement.ToLower().Contains("combo"))
                    {
                        attr2014.ArrangementName = "Combo";
                        attr2014.ArrangementType = (int)ArrangementType.Guitar;
                        attr2014.ArrangementProperties.RouteMask = arrangement.EffectChainName.ToLower().Contains("lead") ? (int)RouteMask.Lead : (int)RouteMask.Rhythm;
                        attr2014.ArrangementProperties.PathLead = arrangement.EffectChainName.ToLower().Contains("lead") ? 1 : 0;
                        attr2014.ArrangementProperties.PathRhythm = arrangement.EffectChainName.ToLower().Contains("lead") ? 0 : 1;
                        attr2014.ArrangementProperties.PathBass = 0;
                    }
                    else if (arrangement.ArrangementName.ToLower().Contains("bass") ||
                        rsSong.Arrangement.ToLower().Contains("bass"))
                    {
                        attr2014.ArrangementName = "Bass";
                        attr2014.ArrangementType = (int)ArrangementType.Bass;
                        attr2014.ArrangementProperties.RouteMask = (int)RouteMask.Bass;
                        attr2014.ArrangementProperties.PathLead = 0;
                        attr2014.ArrangementProperties.PathRhythm = 0;
                        attr2014.ArrangementProperties.PathBass = 1;
                    }
                    else
                    {
                        // default to Lead arrangment
                        attr2014.ArrangementName = "Lead";
                        attr2014.ArrangementType = (int)ArrangementType.Guitar;
                        attr2014.ArrangementProperties.RouteMask = (int)RouteMask.Lead;
                        attr2014.ArrangementProperties.PathLead = 1;
                        attr2014.ArrangementProperties.PathRhythm = 0;
                        attr2014.ArrangementProperties.PathBass = 0;

                        Console.WriteLine("RS1->RS2 CDLC Conversion defaulted to 'Lead' arrangment");
                    }

                    if (convert) // RS1 -> RS2 magic
                    {
                        using (var obj1 = new Rs1Converter())
                            rsSong2014 = obj1.SongToSong2014(rsSong);

                        // update ArrangementProperties
                        rsSong2014.ArrangementProperties.RouteMask = attr2014.ArrangementProperties.RouteMask;
                        rsSong2014.ArrangementProperties.PathLead = attr2014.ArrangementProperties.PathLead;
                        rsSong2014.ArrangementProperties.PathRhythm = attr2014.ArrangementProperties.PathRhythm;
                        rsSong2014.ArrangementProperties.PathBass = attr2014.ArrangementProperties.PathBass;
                        rsSong2014.ArrangementProperties.StandardTuning = (arrangement.Tuning == "E Standard" ? 1 : 0);

                        // <note time="58.366" linkNext="0" accent="0" bend="0" fret="7" hammerOn="0" harmonic="0" hopo="0" ignore="0" leftHand="-1" mute="0" palmMute="0" pluck="-1" pullOff="0" slap="-1" slideTo="-1" string="3" sustain="0.108" tremolo="0" harmonicPinch="0" pickDirection="0" rightHand="-1" slideUnpitchTo="-1" tap="0" vibrato="0" />
                        if (rsSong2014.Levels.Any(sl => sl.Notes.Any(sln => sln.Bend != 0)))
                            rsSong2014.ArrangementProperties.Bends = 1;
                        if (rsSong2014.Levels.Any(sl => sl.Notes.Any(sln => sln.Hopo != 0)))
                            rsSong2014.ArrangementProperties.Hopo = 1;
                        if (rsSong2014.Levels.Any(sl => sl.Notes.Any(sln => sln.SlideTo != -1)))
                            rsSong2014.ArrangementProperties.Slides = 1;
                        if (rsSong2014.Levels.Any(sl => sl.Notes.Any(sln => sln.Sustain > 0)))
                            rsSong2014.ArrangementProperties.Sustain = 1;

                        // fixing times that are off
                        var lastEbeatsTime = rsSong2014.Ebeats[rsSong2014.Ebeats.Length - 1].Time;
                        var lastPhraseIterationsTime = rsSong2014.PhraseIterations[rsSong2014.PhraseIterations.Length - 1].Time;

                        // tested ... not source of in game hangs
                        // confirm last PhraseIterations time is less than last Ebeats time
                        if (lastPhraseIterationsTime > lastEbeatsTime)
                        {
                            rsSong2014.PhraseIterations[rsSong2014.PhraseIterations.Length - 1].Time = lastEbeatsTime;
                            rsSong2014.Sections[rsSong2014.Sections.Length - 1].StartTime = lastEbeatsTime;
                        }

                        // tested ... not source of in game hangs
                        // confirm SongLength at least equals last Ebeats time
                        if (rsSong2014.SongLength < lastEbeatsTime)
                            rsSong2014.SongLength = lastEbeatsTime;

                        using (var obj2 = new Rs2014Converter())
                            obj2.Song2014ToXml(rsSong2014, xmlFile, true);
                    }

                    // Adding Song Arrangement
                    try
                    {
                        data.Arrangements.Add(new Arrangement(attr2014, xmlFile));
                    }
                    catch (Exception ex)
                    {
                        // mainly for the benifit of convert2012 CLI users
                        Console.WriteLine(@"This CDLC could not be auto converted." + Environment.NewLine + "You can still try manually adding the arrangements and assests." + Environment.NewLine + ex.Message);
                    }
                }
            }
            if (convert)
            {
                // get rid of duplicate tone names
                tones2014 = tones2014.Where(p => p.Name != null)
                    .GroupBy(p => p.Name).Select(g => g.First()).ToList();
                data.TonesRS2014 = tones2014;
            }

            //Get Album Artwork DDS Files
            var artFiles = Directory.GetFiles(unpackedDir, "*.dds", SearchOption.AllDirectories);
            if (artFiles.Length < 1)
                throw new DataException("No Album Artwork file found.");
            if (artFiles.Length > 1)
                throw new DataException("More than one Album Artwork file found.");

            var targetArtFiles = new List<DDSConvertedFile>();
            data.AlbumArtPath = artFiles[0];
            targetArtFiles.Add(new DDSConvertedFile() { sizeX = 256, sizeY = 256, sourceFile = artFiles[0], destinationFile = artFiles[0].CopyToTempFile(".dds") });
            data.ArtFiles = targetArtFiles;

            //Audio files
            var targetAudioFiles = new List<string>();
            var audioFiles = Directory.GetFiles(unpackedDir, "*.ogg", SearchOption.AllDirectories);
            if (audioFiles.Length < 1)
                throw new DataException("No Audio file found.");
            if (audioFiles.Length > 2)
                throw new DataException("Too many Audio files found.");

            int i;
            for (i = 0; i < audioFiles.Length; i++)
            {
                if (convert && audioFiles[i].Contains("_fixed.ogg")) // use it
                    break;
                if (!convert && !audioFiles[i].Contains("_fixed.ogg"))
                    break;
            }
            // FIXME: platform specific decode is broken
            var sourcePlatform = unpackedDir.GetPlatform();
            if (targetPlatform.IsConsole != (sourcePlatform = audioFiles[i].GetAudioPlatform()).IsConsole)
            {
                var newFile = Path.Combine(Path.GetDirectoryName(audioFiles[i]), String.Format("{0}_cap.ogg", Path.GetFileNameWithoutExtension(audioFiles[i])));
                OggFile.ConvertAudioPlatform(audioFiles[i], newFile);
                audioFiles[i] = newFile;
            }

            targetAudioFiles.Add(audioFiles[i]);

            if (!targetAudioFiles.Any())
                throw new DataException("Audio file not found.");

            var a = new FileInfo(audioFiles[i]);
            data.OggPath = a.FullName;

            //AppID
            if (!sourcePlatform.IsConsole)
            {
                if (!convert)
                {
                    var appidFile = Directory.GetFiles(unpackedDir, "*APP_ID*", SearchOption.AllDirectories);
                    if (appidFile.Length > 0)
                        data.AppId = File.ReadAllText(appidFile[0]);
                }
                else
                    data.AppId = "248750";
            }

            try
            {//TODO: validate that rs1 songs have no this file
                //Package version
                var versionFile = Directory.GetFiles(unpackedDir, "toolkit.version", SearchOption.AllDirectories);
                if (versionFile.Length > 0)
                    data.PackageVersion = GeneralExtensions.ReadPackageVersion(versionFile[0]);
                else data.PackageVersion = "1";
            }
            catch {}

            if (convert)
                data.Tones = null;

            return data;
        }
        private void convertSngXmlButton_Click(object sender, EventArgs e) {
            if (String.IsNullOrEmpty(ConverterSngXmlFile)) {
                MessageBox.Show(String.Format("File not found: {0}: ", ConverterSngXmlFile), MESSAGEBOX_CAPTION, MessageBoxButtons.OK, MessageBoxIcon.Error);
                sngXmlTB.Focus();
                return;
            }

            if (sng2xmlRadio.Checked) {
                if (String.IsNullOrEmpty(ConverterManifestFile))
                    MessageBox.Show("No manifest file was entered. The song xml file will be generated without song informations like song title, album, artist, tone names, etc.", MESSAGEBOX_CAPTION, MessageBoxButtons.OK, MessageBoxIcon.Error);

                Attributes2014 att = null;
                if (ConverterArrangementType != ArrangementType.Vocal && !String.IsNullOrEmpty(ConverterManifestFile))
                    att = Manifest2014<Attributes2014>.LoadFromFile(ConverterManifestFile).Entries.ToArray()[0].Value.ToArray()[0].Value;

                var sng = Sng2014File.LoadFromFile(ConverterSngXmlFile, ConverterPlatform);

                var outputFile = Path.Combine(Path.GetDirectoryName(ConverterSngXmlFile), String.Format("{0}.xml", Path.GetFileNameWithoutExtension(ConverterSngXmlFile)));
                using (FileStream outputStream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite))
                {
                    dynamic xml = null;

                    if (ConverterArrangementType == ArrangementType.Vocal)
                        xml = new Vocals(sng);
                    else
                        xml = new Song2014(sng, att ?? null);

                    xml.Serialize(outputStream);

                    MessageBox.Show(String.Format("XML file was generated! {0}It was saved on same location of sng file specified.", Environment.NewLine), MESSAGEBOX_CAPTION, MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            } else if (xml2sngRadio.Checked) {
                var outputFile = Path.Combine(Path.GetDirectoryName(ConverterSngXmlFile), String.Format("{0}.sng", Path.GetFileNameWithoutExtension(ConverterSngXmlFile)));

                using (FileStream outputStream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite)) {
                    Sng2014File sng = Sng2014File.ConvertXML(ConverterSngXmlFile, ConverterArrangementType);
                    sng.WriteSng(outputStream, ConverterPlatform);
                }

                MessageBox.Show(String.Format("SNG file was generated! {0}It was saved on same location of xml file specified.", Environment.NewLine), MESSAGEBOX_CAPTION, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
        private void parseNLD(Song2014 xml, Sng2014File sng)
        {
            sng.NLD = new NLinkedDifficultySection();
            sng.NLD.Count = xml.NewLinkedDiff.Length;
            sng.NLD.NLinkedDifficulties = new NLinkedDifficulty[sng.NLD.Count];

            for (int i = 0; i < sng.NLD.Count; i++)
            {
                var nld = xml.NewLinkedDiff[i];
                var n = new NLinkedDifficulty();
                // TODO: Ratio attribute unused?
                n.LevelBreak = nld.LevelBreak;
                n.PhraseCount = nld.PhraseCount;
                n.NLD_Phrase = new Int32[n.PhraseCount];
                for (int j = 0; j < n.PhraseCount; j++)
                {
                    n.NLD_Phrase[j] = nld.Nld_phrase[j].Id;
                }
                sng.NLD.NLinkedDifficulties[i] = n;
            }
        }
        private void parseNote(Song2014 xml, SongNote2014 note, Notes n, Notes prev)
        {
            n.NoteMask = parseNoteMask(note, true);
            // numbering (NoteFlags) will be set later
            n.Time = note.Time;
            n.StringIndex = note.String;
            // actual fret number
            n.FretId = (Byte)note.Fret;
            // anchor fret will be set later
            n.AnchorFretId = unchecked((Byte)(-1));
            // will be overwritten
            n.AnchorWidth = unchecked((Byte)(-1));
            n.ChordId = -1;
            n.ChordNotesId = -1;
            n.PhraseIterationId = getPhraseIterationId(xml, n.Time, false);
            n.PhraseId = xml.PhraseIterations[n.PhraseIterationId].PhraseId;
            // these will be overwritten
            n.FingerPrintId[0] = -1;
            n.FingerPrintId[1] = -1;
            // these will be overwritten
            n.NextIterNote = -1;
            n.PrevIterNote = -1;
            n.ParentPrevNote = -1;
            n.SlideTo = unchecked((Byte)note.SlideTo);
            n.SlideUnpitchTo = unchecked((Byte)note.SlideUnpitchTo);
            n.LeftHand = unchecked((Byte)note.LeftHand);
            // 'bvibrato' and 'rchords8' are using 0 value but without TAP mask
            if (note.Tap != 0)
                n.Tap = unchecked((Byte)note.Tap);
            else
                n.Tap = unchecked((Byte)(-1));

            n.PickDirection = (Byte)note.PickDirection;
            n.Slap = (Byte)note.Slap;
            n.Pluck = (Byte)note.Pluck;
            n.Vibrato = note.Vibrato;
            n.Sustain = note.Sustain;
            n.MaxBend = note.Bend;
            n.BendData = new BendDataSection();
            n.BendData.BendData = parseBendData(note, true);
            n.BendData.Count = n.BendData.BendData.Length;
        }
        private void parseActions(Song2014 xml, Sng2014File sng)
        {
            // there is no XML example, EOF does not support it either
            sng.Actions = new ActionSection();
            sng.Actions.Count = 0;
            sng.Actions.Actions = new Action[sng.Actions.Count];

            // no RS2 SNG is using this
            // for (int i = 0; i < sng.Actions.Count; i++) {
            //     //var action = xml.?[i];
            //     var a = new Action();
            //     //a.Time = action.Time;
            //     //read_string(action.ActionName, a.ActionName);
            //     sng.Actions.Actions[i] = a;
            // }
        }
        /// <summary>
        /// Unpack the specified File, returns unpacked dir.
        /// </summary>
        /// <param name="sourceFileName">Source file path.</param>
        /// <param name="savePath">Save path.</param>
        /// <param name="decodeAudio">If set to <c>true</c> decode audio.</param>
        /// <param name="extractSongXml">If set to <c>true</c> extract song xml from sng.</param>
        /// <param name="overwriteSongXml">If set to <c>true</c> overwrite existing song xml with produced.</param>
        /// <param name="predefinedPlatform">Predefined source platform.</param>
        public static string Unpack(string sourceFileName, string savePath, bool decodeAudio = false, bool extractSongXml = false, bool overwriteSongXml = true, Platform predefinedPlatform = null)
        {
            Platform platform = sourceFileName.GetPlatform();
            if (predefinedPlatform != null && predefinedPlatform.platform != GamePlatform.None && predefinedPlatform.version != GameVersion.None)
                platform = predefinedPlatform;
            var fnameWithoutExt = Path.GetFileNameWithoutExtension(sourceFileName);
            if (platform.platform == GamePlatform.PS3)
                fnameWithoutExt = fnameWithoutExt.Substring(0, fnameWithoutExt.LastIndexOf("."));
            var unpackedDir = Path.Combine(savePath, String.Format("{0}_{1}", fnameWithoutExt, platform.platform));
            if (Directory.Exists(unpackedDir))
                DirectoryExtension.SafeDelete(unpackedDir);

            var useCryptography = platform.version == GameVersion.RS2012; // Cryptography way is used only for PC in Rocksmith 1
            switch (platform.platform)
            {
                case GamePlatform.Pc:
                case GamePlatform.Mac:
                    if (platform.version == GameVersion.RS2014)
                        using (var inputStream = File.OpenRead(sourceFileName))
                            ExtractPSARC(sourceFileName, savePath, inputStream, platform);
                    else
                    {
                        using (var inputFileStream = File.OpenRead(sourceFileName))
                        using (var inputStream = new MemoryStream())
                        {
                            if (useCryptography)
                                RijndaelEncryptor.DecryptFile(inputFileStream, inputStream, RijndaelEncryptor.DLCKey);
                            else
                                inputFileStream.CopyTo(inputStream);

                            ExtractPSARC(sourceFileName, savePath, inputStream, platform);
                        }
                    }
                    break;
                case GamePlatform.XBox360:
                    UnpackXBox360Package(sourceFileName, savePath, platform);
                    break;
                case GamePlatform.PS3:
                    UnpackPS3Package(sourceFileName, savePath, platform);
                    break;
                case GamePlatform.None:
                    throw new InvalidOperationException("Platform not found :(");
            }

            // DECODE AUDIO
            if (decodeAudio)
            {
                var audioFiles = Directory.EnumerateFiles(unpackedDir, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".ogg") || s.EndsWith(".wem"));
                foreach (var file in audioFiles)
                {
                    var outputAudioFileName = Path.Combine(Path.GetDirectoryName(file), String.Format("{0}_fixed{1}", Path.GetFileNameWithoutExtension(file), ".ogg"));
                    OggFile.Revorb(file, outputAudioFileName, Path.GetDirectoryName(Application.ExecutablePath), Path.GetExtension(file).GetWwiseVersion());
                }
            }

            // EXTRACT XML FROM SNG
            if (extractSongXml && platform.version == GameVersion.RS2014)
            {
                var sngFiles = Directory.EnumerateFiles(unpackedDir, "*.sng", SearchOption.AllDirectories);

                foreach (var sngFile in sngFiles)
                {
                    var xmlOutput = Path.Combine(Path.GetDirectoryName(sngFile), String.Format("{0}.xml", Path.GetFileNameWithoutExtension(sngFile)));
                    xmlOutput = xmlOutput.Replace(String.Format("bin{0}{1}", Path.DirectorySeparatorChar, platform.GetPathName()[1].ToLower()), "arr");

                    if (File.Exists(xmlOutput) && !overwriteSongXml)
                        continue;

                    var arrType = ArrangementType.Guitar;
                    if (Path.GetFileName(xmlOutput).ToLower().Contains("vocal"))
                        arrType = ArrangementType.Vocal;

                    Attributes2014 att = null;
                    if (arrType != ArrangementType.Vocal)
                    {
                        var jsonFiles = Directory.EnumerateFiles(unpackedDir, String.Format("{0}.json", Path.GetFileNameWithoutExtension(sngFile)), SearchOption.AllDirectories).FirstOrDefault();
                        if (jsonFiles.Any() && !String.IsNullOrEmpty(jsonFiles))
                            att = Manifest2014<Attributes2014>.LoadFromFile(jsonFiles).Entries.ToArray()[0].Value.ToArray()[0].Value;
                    }

                    var sngContent = Sng2014File.LoadFromFile(sngFile, platform);
                    using (var outputStream = new FileStream(xmlOutput, FileMode.Create, FileAccess.ReadWrite))
                    {
                        dynamic xmlContent = null;

                        if (arrType == ArrangementType.Vocal)
                            xmlContent = new Vocals(sngContent);
                        else
                            xmlContent = new Song2014(sngContent, att);

                        xmlContent.Serialize(outputStream);
                    }
                }
            }

            return unpackedDir;
        }
        private void parseArrangements(Song2014 xml, Sng2014File sng)
        {
            sng.Arrangements = new ArrangementSection();
            sng.Arrangements.Count = getMaxDifficulty(xml) + 1;
            sng.Arrangements.Arrangements = new Arrangement[sng.Arrangements.Count];

            // not strictly necessary but more helpful than hash value
            var note_id = new Dictionary<UInt32, UInt32>();

            for (int i = 0; i < sng.Arrangements.Count; i++)
            {
                var level = xml.Levels[i];
                var a = new Arrangement();
                a.Difficulty = level.Difficulty;

                var anchors = new AnchorSection();
                anchors.Count = level.Anchors.Length;
                anchors.Anchors = new Anchor[anchors.Count];

                for (int j = 0; j < anchors.Count; j++)
                {
                    var anchor = new Anchor();
                    anchor.StartBeatTime = level.Anchors[j].Time;
                    if (j + 1 < anchors.Count)
                        anchor.EndBeatTime = level.Anchors[j + 1].Time;
                    else
                        // last phrase iteration = noguitar/end
                        anchor.EndBeatTime = xml.PhraseIterations[xml.PhraseIterations.Length - 1].Time;
                    // TODO: not 100% clear
                    // times will be updated later
                    // these "garbage" values are everywhere!
                    //anchor.Unk3_FirstNoteTime = (float) 3.4028234663852886e+38;
                    //anchor.Unk4_LastNoteTime = (float) 1.1754943508222875e-38;
                    anchor.FretId = (byte)level.Anchors[j].Fret;
                    anchor.Width = (Int32)level.Anchors[j].Width;
                    anchor.PhraseIterationId = getPhraseIterationId(xml, anchor.StartBeatTime, false);
                    anchors.Anchors[j] = anchor;
                }

                a.Anchors = anchors;
                // each slideTo will get anchor extension
                a.AnchorExtensions = new AnchorExtensionSection();

                foreach (var note in level.Notes)
                    if (note.SlideTo != -1)
                        ++a.AnchorExtensions.Count;

                a.AnchorExtensions.AnchorExtensions = new AnchorExtension[a.AnchorExtensions.Count];
                // Fingerprints1 is for handshapes without "arp" displayName
                a.Fingerprints1 = new FingerprintSection();
                // Fingerprints2 is for handshapes with "arp" displayName
                a.Fingerprints2 = new FingerprintSection();

                var fp1 = new List<Fingerprint>();
                var fp2 = new List<Fingerprint>();
                foreach (var h in level.HandShapes)
                {
                    if (h.ChordId < 0) continue;
                    var fp = new Fingerprint
                    {
                        ChordId = h.ChordId, StartTime = h.StartTime, EndTime = h.EndTime
                    // TODO: not always StartTime
                    //fp.Unk3_FirstNoteTime = fp.StartTime;
                    //fp.Unk4_LastNoteTime = fp.StartTime;
                    };

                    if (xml.ChordTemplates[fp.ChordId].DisplayName.EndsWith("arp"))
                        fp2.Add(fp);
                    else
                        fp1.Add(fp);
                }

                a.Fingerprints1.Count = fp1.Count;
                a.Fingerprints1.Fingerprints = fp1.ToArray();
                a.Fingerprints2.Count = fp2.Count;
                a.Fingerprints2.Fingerprints = fp2.ToArray();

                // calculated as we go through notes, seems to work
                // NotesInIteration1 is count without ignore="1" notes
                a.PhraseIterationCount1 = xml.PhraseIterations.Length;
                a.NotesInIteration1 = new Int32[a.PhraseIterationCount1];
                // NotesInIteration2 seems to be the full count
                a.PhraseIterationCount2 = a.PhraseIterationCount1;
                a.NotesInIteration2 = new Int32[a.PhraseIterationCount2];

                // notes and chords sorted by time
                List<Notes> notes = new List<Notes>();
                int acent = 0;
                foreach (var note in level.Notes)
                {
                    var n = new Notes();
                    Notes prev = null;
                    if (notes.Count > 0)
                        prev = notes.Last();
                    parseNote(xml, note, n, prev);
                    notes.Add(n);

                    for (int j = 0; j < xml.PhraseIterations.Length; j++)
                    {
                        var piter = xml.PhraseIterations[j];
                        if (piter.Time > note.Time)
                        {
                            if (note.Ignore == 0)
                                ++a.NotesInIteration1[j - 1];
                            ++a.NotesInIteration2[j - 1];
                            break;
                        }
                    }
                    if (note.SlideTo != -1)
                    {
                        var ae = new AnchorExtension();
                        ae.FretId = (Byte)note.SlideTo;
                        ae.BeatTime = note.Time + note.Sustain;
                        a.AnchorExtensions.AnchorExtensions[acent++] = ae;
                    }
                }

                foreach (var chord in level.Chords)
                {
                    var cn = new Notes();
                    Int32 id = -1;
                    if (chord.ChordNotes != null && chord.ChordNotes.Length > 0)
                        id = addChordNotes(sng, chord);
                    parseChord(xml, sng, chord, cn, id);
                    notes.Add(cn);

                    for (int j = 0; j < xml.PhraseIterations.Length; j++)
                    {
                        var piter = xml.PhraseIterations[j];
                        if (chord.Time >= piter.Time && piter.Time >= chord.Time)
                        {
                            if (chord.Ignore == 0)
                                ++a.NotesInIteration1[j];
                            ++a.NotesInIteration2[j]; // j-1 not safe with j=0
                            break;
                        }
                    }
                }

                // exception handler for some poorly formed RS1 CDLC
                try
                {
                    // need to be sorted before anchor note times are updated
                    notes.Sort((x, y) => x.Time.CompareTo(y.Time));

                    // check for RS1 CDLC note time errors
                    // if (notes.Count > 0) // alt method to deal with the exception
                    if ((int)first_note_time == 0 || first_note_time > notes[0].Time)
                        first_note_time = notes[0].Time;
                }
                catch (Exception)
                {
                    // show error in convert2012CLI command window and continue
                    Console.WriteLine(@" -- CDLC contains note time errors and may not play properly"); // + ex.Message);
                }

                foreach (var n in notes)
                {
                    for (Int16 id = 0; id < fp1.Count; id++) //FingerPrints 1st level (common handshapes?)
                        if (n.Time >= fp1[id].StartTime && n.Time < fp1[id].EndTime)
                        {
                            n.FingerPrintId[0] = id;
                            // add STRUM to chords if highDensity = 0
                            if (n.ChordId != -1 && (n.NoteMask & CON.NOTE_MASK_HIGHDENSITY) != CON.NOTE_MASK_HIGHDENSITY)
                                n.NoteMask |= CON.NOTE_MASK_STRUM;
                            if (fp1[id].Unk3_FirstNoteTime == 0)
                                fp1[id].Unk3_FirstNoteTime = n.Time;

                            float sustain = 0;
                            if (n.Time + n.Sustain < fp1[id].EndTime)
                                sustain = n.Sustain;
                            fp1[id].Unk4_LastNoteTime = n.Time + sustain;
                            break;
                        }
                    for (Int16 id = 0; id < fp2.Count; id++) //FingerPrints 2nd level (used for -arp(eggio) handshapes)
                        if (n.Time >= fp2[id].StartTime && n.Time < fp2[id].EndTime)
                        {
                            n.FingerPrintId[1] = id;
                            // add STRUM to chords
                            if (fp2[id].StartTime == n.Time && n.ChordId != -1)
                                n.NoteMask |= CON.NOTE_MASK_STRUM;
                            n.NoteMask |= CON.NOTE_MASK_ARPEGGIO;
                            if (fp2[id].Unk3_FirstNoteTime == 0)
                                fp2[id].Unk3_FirstNoteTime = n.Time;

                            float sustain = 0;
                            if (n.Time + n.Sustain < fp2[id].EndTime)
                                sustain = n.Sustain;
                            fp2[id].Unk4_LastNoteTime = n.Time + sustain;
                            break;
                        }
                    for (int j = 0; j < a.Anchors.Count; j++)
                        if (n.Time >= a.Anchors.Anchors[j].StartBeatTime && n.Time < a.Anchors.Anchors[j].EndBeatTime)
                        {
                            n.AnchorWidth = (Byte)a.Anchors.Anchors[j].Width;
                            // anchor fret
                            n.AnchorFretId = (Byte)a.Anchors.Anchors[j].FretId;
                            if (a.Anchors.Anchors[j].Unk3_FirstNoteTime == 0)
                                a.Anchors.Anchors[j].Unk3_FirstNoteTime = n.Time;

                            float sustain = 0;
                            if (n.Time + n.Sustain < a.Anchors.Anchors[j].EndBeatTime - 0.1)
                                sustain = n.Sustain;
                            a.Anchors.Anchors[j].Unk4_LastNoteTime = n.Time + sustain;
                            break;
                        }
                }

                // initialize times for empty anchors, based on 'lrocknroll'
                foreach (var anchor in a.Anchors.Anchors)
                    if (anchor.Unk3_FirstNoteTime == 0)
                    {
                        anchor.Unk3_FirstNoteTime = anchor.StartBeatTime;
                        anchor.Unk4_LastNoteTime = anchor.StartBeatTime + (float)0.1;
                    }

                a.Notes = new NotesSection();
                a.Notes.Count = notes.Count;
                a.Notes.Notes = notes.ToArray();

                foreach (var piter in sng.PhraseIterations.PhraseIterations)
                {
                    int count = 0;
                    int j = 0;
                    for (; j < a.Notes.Count; j++)
                    {
                        // skip notes outside of a phraseiteration
                        if (a.Notes.Notes[j].Time < piter.StartTime)
                            continue;
                        if (a.Notes.Notes[j].Time >= piter.NextPhraseTime)
                        {
                            break;
                        }
                        // set to next arrangement note
                        a.Notes.Notes[j].NextIterNote = (Int16)(j + 1);
                        // set all but first note to previous note
                        if (count > 0)
                            a.Notes.Notes[j].PrevIterNote = (Int16)(j - 1);
                        ++count;
                    }
                    // fix last phrase note
                    if (count > 0)
                        a.Notes.Notes[j - 1].NextIterNote = -1;
                }

                for (int j = 1; j < a.Notes.Notes.Length; j++)
                {
                    var n = a.Notes.Notes[j];
                    var p = a.Notes.Notes[j - 1];
                    int prvnote = 1; //set current + prev note + initialize prvnote variable
                    //do not do this searching for a parent, if the previous note timestamp != current time stamp
                    if (n.Time != p.Time) prvnote = 1;
                    else
                    {
                        for (int x = 1; x < (a.Notes.Notes.Length); x++) //search up till the beginning of iteration
                        {
                            if (j - x < 1) //don't search past the first note in iteration
                            {
                                prvnote = x;
                                x = a.Notes.Notes.Length + 2;
                                break; // stop searching for a match we reached the beginning
                            }
                            var prv = a.Notes.Notes[j - x]; // get the info for the note we are checking against
                            if (prv.Time != n.Time)
                            {
                                //now check the timestamp if its the same timestamp then keep looking
                                if (prv.ChordId != -1)
                                {
                                    //check if its a chord
                                    prvnote = x;
                                    x = a.Notes.Notes.Length + 2;
                                    break; //stop here, its a chord so don't need to check the strings
                                }
                                if (prv.StringIndex == n.StringIndex)
                                {
                                    //check to see if we are looking at the same string
                                    prvnote = x;
                                    x = a.Notes.Notes.Length + 2;
                                    break; //stop here we found the same string, at a different timestamp, thats not a chord
                                }
                            }
                        }
                    }

                    var prev = a.Notes.Notes[j - prvnote]; //this will be either the first note of piter, or the last note on the same string at previous timestamp
                    if ((prev.NoteMask & CON.NOTE_MASK_PARENT) != 0)
                    {
                        n.ParentPrevNote = (short)(prev.NextIterNote - 1);
                        n.NoteMask |= CON.NOTE_MASK_CHILD; //set the ParentPrevNote# = the matched Note#//add CHILD flag
                    }
                }

                a.PhraseCount = xml.Phrases.Length;
                a.AverageNotesPerIteration = new float[a.PhraseCount];
                var iter_count = new float[a.PhraseCount];
                for (int j = 0; j < xml.PhraseIterations.Length; j++)
                {
                    var piter = xml.PhraseIterations[j];
                    // using NotesInIteration2 to calculate
                    a.AverageNotesPerIteration[piter.PhraseId] += a.NotesInIteration2[j];
                    ++iter_count[piter.PhraseId];
                }

                for (int j = 0; j < iter_count.Length; j++)
                {
                    if (iter_count[j] > 0)
                        a.AverageNotesPerIteration[j] /= iter_count[j];
                }

                // this is some kind of optimization in RS2 where they
                // hash all note data but their position in phrase iteration
                // to mark otherwise unchanged notes
                foreach (var n in a.Notes.Notes)
                {
                    MemoryStream data = sng.CopyStruct(n);
                    var r = new EndianBinaryReader(EndianBitConverter.Little, data);
                    var ncopy = new Notes();
                    ncopy.read(r);
                    ncopy.NextIterNote = 0;
                    ncopy.PrevIterNote = 0;
                    ncopy.ParentPrevNote = 0;
                    UInt32 crc = sng.HashStruct(ncopy);
                    if (!note_id.ContainsKey(crc))
                        note_id[crc] = (UInt32)note_id.Count;
                    n.Hash = note_id[crc];
                }

                numberNotes(sng, a.Notes.Notes);
                sng.Arrangements.Arrangements[i] = a;
            }
        }
        /// <summary>
        /// Convert RS1 Song Object to RS2 Song2014 Object
        /// RS1 to RS2014 Mapping Method
        /// </summary>
        /// <param name="rsSong"></param>
        /// <param name="srcPath"></param>
        /// <returns>Song2014</returns>
        public Song2014 SongToSong2014(Song rsSong)
        {
            // Song to Song2014 Mapping
            Song2014 rsSong2014 = new Song2014();

            // song info parsed and loaded later from
            // RS1 song.manifest.json file by RS1LoadFromFolder
            rsSong2014.Version = "7";
            rsSong2014.Title = rsSong.Title;
            rsSong2014.Arrangement = rsSong.Arrangement;
            rsSong2014.Part = rsSong.Part;
            rsSong2014.Offset = rsSong.Offset;
            rsSong2014.CentOffset = "0";
            rsSong2014.SongLength = rsSong.SongLength;
            rsSong2014.LastConversionDateTime = DateTime.Now.ToString("MM-dd-yy HH:mm");
            rsSong2014.StartBeat = rsSong.Ebeats[0].Time;
            // if RS1 CDLC Song XML originates from EOF it may
            // already contain AverageTempo otherwise it gets calculated
            rsSong2014.AverageTempo = rsSong.AverageTempo == 0 ? AverageBPM(rsSong) : rsSong.AverageTempo;

            // tuning parsed from RS1 song.manifest.json file by RS1LoadFromFolder
            rsSong2014.Tuning = rsSong.Tuning == null ? new TuningStrings { String0 = 0, String1 = 0, String2 = 0, String3 = 0, String4 = 0, String5 = 0 } : rsSong.Tuning;
            rsSong2014.Capo = 0;
            rsSong2014.ArtistName = rsSong.ArtistName;
            rsSong2014.AlbumName = rsSong.AlbumName;
            rsSong2014.AlbumYear = rsSong.AlbumYear;
            rsSong2014.CrowdSpeed = "1";

            // initialize arrangement properties
            rsSong2014.ArrangementProperties = new SongArrangementProperties2014
            {
                Represent = 1,
                StandardTuning = 1,
                NonStandardChords = 0,
                BarreChords = 0,
                PowerChords = 0,
                DropDPower = 0,
                OpenChords = 0,
                FingerPicking = 0,
                PickDirection = 0,
                DoubleStops = 0,
                PalmMutes = 0,
                Harmonics = 0,
                PinchHarmonics = 0,
                Hopo = 0,
                Tremolo = 0,
                Slides = 0,
                UnpitchedSlides = 0,
                Bends = 0,
                Tapping = 0,
                Vibrato = 0,
                FretHandMutes = 0,
                SlapPop = 0,
                TwoFingerPicking = 0,
                FifthsAndOctaves = 0,
                Syncopation = 0,
                BassPick = 0,
                Sustain = 0,
                BonusArr = 0,
                RouteMask = 0,
                PathLead = 0,
                PathRhythm = 0,
                PathBass = 0
            };

            // initial SWAG based on RS1 arrangement element
            rsSong2014.ArrangementProperties.RouteMask = rsSong.Arrangement.ToLower().Contains("lead") ? 1
                : (rsSong.Arrangement.ToLower().Contains("rhythm") ? 2
                : (rsSong.Arrangement.ToLower().Contains("combo") ? 2 // may not always be true
                : (rsSong.Arrangement.ToLower().Contains("bass") ? 4 : 1))); //  but ok for now
            rsSong2014.ArrangementProperties.PathLead = rsSong2014.ArrangementProperties.RouteMask == 1 ? 1 : 0;
            rsSong2014.ArrangementProperties.PathRhythm = rsSong2014.ArrangementProperties.RouteMask == 1 ? 1 : 0;
            rsSong2014.ArrangementProperties.PathBass = rsSong2014.ArrangementProperties.RouteMask == 1 ? 1 : 0;

            // set tone defaults used to produce RS2014 CDLC
            rsSong2014.ToneBase = "Default";
            rsSong2014.ToneA = "";
            rsSong2014.ToneB = "";
            rsSong2014.ToneC = "";
            rsSong2014.ToneD = "";

            // these elements have direct mappings
            rsSong2014.Phrases = rsSong.Phrases;
            rsSong2014.FretHandMuteTemplates = rsSong.FretHandMuteTemplates;
            rsSong2014.Ebeats = rsSong.Ebeats;
            rsSong2014.Sections = rsSong.Sections;
            rsSong2014.Events = rsSong.Events;
            // these prevent in game hanging
            rsSong2014.LinkedDiffs = new SongLinkedDiff[0];
            rsSong2014.PhraseProperties = new SongPhraseProperty[0];

            // these elements have no direct mapping, processing order is important
            rsSong2014 = ConvertChordTemplates(rsSong, rsSong2014);
            rsSong2014 = ConvertLevels(rsSong, rsSong2014);
            rsSong2014 = ConvertPhraseIterations(rsSong, rsSong2014);
            // these prevent in game hanging
            rsSong2014.Tones = new SongTone2014[0];
            rsSong2014.NewLinkedDiff = new SongNewLinkedDiff[0];
            // tested ... not the source of in game hangs
            // rsSong2014.TranscriptionTrack = TranscriptionTrack2014.GetDefault();

            // tested ... confirmed this is a source of in game hangs
            // check alignment of Sections time with Ebeats first beat of measure time
            // TODO: use LINQ
            float fbomTime = 0;
            float nfbomTime = 0;
            foreach (var section in rsSong2014.Sections)
            {
                foreach (var ebeat in rsSong2014.Ebeats)
                {
                    // save Ebeats first beat of measure time
                    if (ebeat.Measure != -1)
                        fbomTime = ebeat.Time;
                    else
                        nfbomTime = ebeat.Time;

                    if (section.Name.ToLower().Contains("noguitar") && Math.Abs(ebeat.Time - section.StartTime) < 0.001)
                    {
                        // CRITICAL - fix Section noguitar time (matches EOF output)
                        if (ebeat.Measure != -1)
                        {
                            section.StartTime = nfbomTime;
                            Console.WriteLine("Applied fix to RS1->RS2 Section StartTime for: " + section.Name);
                        }

                        break;
                    }

                    // found a valid Section time
                    if (ebeat.Measure != -1 && Math.Abs(ebeat.Time - section.StartTime) < 0.001)
                        break;

                    // fix invalid Section time
                    if (ebeat.Measure == -1 && ebeat.Time > section.StartTime)
                    {
                        section.StartTime = fbomTime;
                        Console.WriteLine("Applied fix to RS1->RS2 Section StartTime for: " + section.Name);
                        break;
                    }
                }
            }

            return rsSong2014;
        }
        private void parseChord(Song2014 xml, Sng2014File sng, SongChord2014 chord, Notes n, Int32 chordNotesId)
        {
            n.NoteMask |= CON.NOTE_MASK_CHORD;
            if (chordNotesId != -1)
            {
                // there should always be a STRUM too => handshape at chord time
                // probably even for chordNotes which are not exported to SNG
                n.NoteMask |= CON.NOTE_MASK_CHORDNOTES;
            }

            if (chord.LinkNext != 0)
                n.NoteMask |= CON.NOTE_MASK_PARENT;

            if (chord.Accent != 0)
                n.NoteMask |= CON.NOTE_MASK_ACCENT;
            if (chord.FretHandMute != 0)
                n.NoteMask |= CON.NOTE_MASK_FRETHANDMUTE;
            if (chord.HighDensity != 0)
                n.NoteMask |= CON.NOTE_MASK_HIGHDENSITY;
            if (chord.Ignore != 0)
                n.NoteMask |= CON.NOTE_MASK_IGNORE;
            if (chord.PalmMute != 0)
                n.NoteMask |= CON.NOTE_MASK_PALMMUTE;
            // TODO: does not seem to have a mask or any effect
            // if (chord.Hopo != 0)
            //     n.NoteMask |= ;

            // numbering will be set later
            //n.NoteFlags = CON.NOTE_FLAGS_NUMBERED;

            n.Time = chord.Time;
            n.StringIndex = unchecked((Byte)(-1));
            // always -1
            n.FretId = unchecked((Byte)(-1));
            // anchor fret will be set later
            n.AnchorFretId = unchecked((Byte)(-1));
            // will be overwritten
            n.AnchorWidth = unchecked((Byte)(-1));
            n.ChordId = chord.ChordId;
            n.ChordNotesId = chordNotesId;
            n.PhraseIterationId = getPhraseIterationId(xml, n.Time, false);
            n.PhraseId = xml.PhraseIterations[n.PhraseIterationId].PhraseId;
            // these will be overwritten
            n.FingerPrintId[0] = -1;
            n.FingerPrintId[1] = -1;
            // these will be overwritten
            n.NextIterNote = -1;
            n.PrevIterNote = -1;
            // seems to be unused for chords
            n.ParentPrevNote = -1;
            n.SlideTo = unchecked((Byte)(-1));
            n.SlideUnpitchTo = unchecked((Byte)(-1));
            n.LeftHand = unchecked((Byte)(-1));
            n.Tap = unchecked((Byte)(-1));
            n.PickDirection = unchecked((Byte)(-1));
            n.Slap = unchecked((Byte)(-1));
            n.Pluck = unchecked((Byte)(-1));
            if (chord.ChordNotes != null)
            {
                foreach (var cn in chord.ChordNotes)
                    if (cn.Sustain > n.Sustain)
                        n.Sustain = cn.Sustain;
            }

            if (n.Sustain > 0)
                n.NoteMask |= CON.NOTE_MASK_SUSTAIN;

            int cnt = 0;
            for (int str = 0; str < 6; str++)
                if (sng.Chords.Chords[chord.ChordId].Frets[str] != 255)
                    ++cnt;
            if (cnt == 2)
                n.NoteMask |= CON.NOTE_MASK_DOUBLESTOP;

            // there are only zeros for all chords in lessons
            //n.Vibrato = 0;
            //n.MaxBend = 0;
            n.BendData = new BendDataSection();
            n.BendData.Count = 0;
            n.BendData.BendData = new BendData32[n.BendData.Count];
        }
        private Song2014 ConvertLevels(Song rsSong, Song2014 rsSong2014)
        {
            // add levels elements
            var levels = new List<SongLevel2014>();

            foreach (var songLevel in rsSong.Levels)
            {
                var anchors = new List<SongAnchor2014>();
                var notes = new List<SongNote2014>();
                var chords = new List<SongChord2014>();
                var handShapes = new List<SongHandShape>();

                for (int anchorIndex = 0; anchorIndex < songLevel.Anchors.Length; anchorIndex++)
                {
                    var anchor = songLevel.Anchors[anchorIndex];
                    anchors.Add(new SongAnchor2014 { Fret = anchor.Fret, Time = anchor.Time, Width = 4 });
                }

                for (int noteIndex = 0; noteIndex < songLevel.Notes.Length; noteIndex++)
                {
                    var songNote = songLevel.Notes[noteIndex];
                    notes.Add(GetNoteInfo(songNote));
                }

                for (int chordIndex = 0; chordIndex < songLevel.Chords.Length; chordIndex++)
                {
                    // RS1 does not contain chordNotes so need to make them from chordtemplate
                    List<SongNote2014> chordNotes = new List<SongNote2014>();
                    var zChord = songLevel.Chords[chordIndex];
                    var zChordId = zChord.ChordId;
                    var zChordTemplate = rsSong.ChordTemplates[zChordId];

                    // this is ok no code crash
                    //if (String.IsNullOrEmpty(zChordTemplate.ChordName))
                    //    continue;

                    if (zChordTemplate.Finger0 != -1) // finger > -1 is a string played
                        chordNotes.Add(DecodeChordTemplate(zChord, 0, zChordTemplate.Fret0));

                    if (zChordTemplate.Finger1 != -1)
                        chordNotes.Add(DecodeChordTemplate(zChord, 1, zChordTemplate.Fret1));

                    if (zChordTemplate.Finger2 != -1)
                        chordNotes.Add(DecodeChordTemplate(zChord, 2, zChordTemplate.Fret2));

                    if (zChordTemplate.Finger3 != -1)
                        chordNotes.Add(DecodeChordTemplate(zChord, 3, zChordTemplate.Fret3));

                    if (zChordTemplate.Finger4 != -1)
                        chordNotes.Add(DecodeChordTemplate(zChord, 4, zChordTemplate.Fret4));

                    if (zChordTemplate.Finger5 != -1)
                        chordNotes.Add(DecodeChordTemplate(zChord, 5, zChordTemplate.Fret5));

                    if (chordNotes.Any())
                    {
                        chords.Add(new SongChord2014 { ChordId = zChord.ChordId, ChordNotes = chordNotes.ToArray(), HighDensity = zChord.HighDensity, Ignore = zChord.Ignore, Strum = zChord.Strum, Time = zChord.Time });
                        // add chordNotes to songNotes for compatibility
                        notes.AddRange(chordNotes);
                    }
                }

                // tested ... not the source of game hangs
                // get rid of duplicate notes if any
                // notes = notes.Distinct().ToList();

                for (int shapeIndex = 0; shapeIndex < songLevel.HandShapes.Length; shapeIndex++)
                {
                    var handshape = songLevel.HandShapes[shapeIndex];
                    handShapes.Add(new SongHandShape { ChordId = handshape.ChordId, EndTime = handshape.EndTime, StartTime = handshape.StartTime });
                }

                levels.Add(new SongLevel2014 { Anchors = anchors.ToArray(), Chords = chords.ToArray(), Difficulty = songLevel.Difficulty, HandShapes = handShapes.ToArray(), Notes = notes.ToArray() });
            }

            rsSong2014.Levels = levels.ToArray();

            return rsSong2014;
        }
 private void parseChordNotes(Song2014 xml, Sng2014File sng)
 {
     sng.ChordNotes = new ChordNotesSection();
     sng.ChordNotes.ChordNotes = cns.ToArray();
     sng.ChordNotes.Count = sng.ChordNotes.ChordNotes.Length;
 }
        /// <summary>
        /// Unpack the specified File, returns unpacked dir.
        /// </summary>
        /// <param name="sourceFileName">Source file path.</param>
        /// <param name="savePath">Save path.</param>
        /// <param name="decodeAudio">If set to <c>true</c> decode audio.</param>
        /// <param name="overwriteSongXml">If set to <c>true</c> overwrite existing song (EOF) xml with SNG data</param>
        /// <param name="predefinedPlatform">Predefined source platform.</param>
        public static string Unpack(string sourceFileName, string savePath, bool decodeAudio = false, bool overwriteSongXml = false, Platform predefinedPlatform = null)
        {
            Platform platform = sourceFileName.GetPlatform();
            if (predefinedPlatform != null && predefinedPlatform.platform != GamePlatform.None && predefinedPlatform.version != GameVersion.None)
                platform = predefinedPlatform;
            var fnameWithoutExt = Path.GetFileNameWithoutExtension(sourceFileName);
            if (platform.platform == GamePlatform.PS3)
                fnameWithoutExt = fnameWithoutExt.Substring(0, fnameWithoutExt.LastIndexOf("."));
            var unpackedDir = Path.Combine(savePath, String.Format("{0}_{1}", fnameWithoutExt, platform.platform));
            if (Directory.Exists(unpackedDir))
                DirectoryExtension.SafeDelete(unpackedDir);

            var useCryptography = platform.version == GameVersion.RS2012; // Cryptography way is used only for PC in Rocksmith 1
            switch (platform.platform)
            {
                case GamePlatform.Pc:
                case GamePlatform.Mac:
                    if (platform.version == GameVersion.RS2014)
                        using (var inputStream = File.OpenRead(sourceFileName))
                            ExtractPSARC(sourceFileName, savePath, inputStream, platform);
                    else
                    {
                        using (var inputFileStream = File.OpenRead(sourceFileName))
                        using (var inputStream = new MemoryStream())
                        {
                            if (useCryptography)
                                RijndaelEncryptor.DecryptFile(inputFileStream, inputStream, RijndaelEncryptor.DLCKey);
                            else
                                inputFileStream.CopyTo(inputStream);

                            ExtractPSARC(sourceFileName, savePath, inputStream, platform);
                        }
                    }
                    break;
                case GamePlatform.XBox360:
                    UnpackXBox360Package(sourceFileName, savePath, platform);
                    break;
                case GamePlatform.PS3:
                    UnpackPS3Package(sourceFileName, savePath, platform);
                    break;
                case GamePlatform.None:
                    throw new InvalidOperationException("Platform not found :(");
            }

            // DECODE AUDIO
            if (decodeAudio)
            {
                GlobalExtension.ShowProgress("Decoding Audio ...", 50);
                var audioFiles = Directory.EnumerateFiles(unpackedDir, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".ogg") || s.EndsWith(".wem"));
                foreach (var file in audioFiles)
                {
                    var outputAudioFileName = Path.Combine(Path.GetDirectoryName(file), String.Format("{0}_fixed{1}", Path.GetFileNameWithoutExtension(file), ".ogg"));
                    OggFile.Revorb(file, outputAudioFileName, Path.GetDirectoryName(Application.ExecutablePath), Path.GetExtension(file).GetWwiseVersion());
                }

                //GlobalExtension.HideProgress();
            }

            // for debugging
            //overwriteSongXml = false;

            // Extract XML from SNG and check it against the EOF XML (correct bass tuning from older toolkit/EOF xml files)
            if (platform.version == GameVersion.RS2014)
            {
                var sngFiles = Directory.EnumerateFiles(unpackedDir, "*.sng", SearchOption.AllDirectories).ToList();
                var step = Math.Round(1.0 / (sngFiles.Count + 2) * 100, 3);
                double progress = 0;
                GlobalExtension.ShowProgress("Extracting XML from SNG ...");

                foreach (var sngFile in sngFiles)
                {
                    var xmlEofFile = Path.Combine(Path.GetDirectoryName(sngFile), String.Format("{0}.xml", Path.GetFileNameWithoutExtension(sngFile)));
                    xmlEofFile = xmlEofFile.Replace(String.Format("bin{0}{1}", Path.DirectorySeparatorChar, platform.GetPathName()[1].ToLower()), "arr");
                    var xmlSngFile = xmlEofFile.Replace(".xml", ".sng.xml");

                    var arrType = ArrangementType.Guitar;

                    if (Path.GetFileName(xmlSngFile).ToLower().Contains("vocal"))
                        arrType = ArrangementType.Vocal;

                    Attributes2014 att = null;
                    if (arrType != ArrangementType.Vocal)
                    {
                        var jsonFiles = Directory.EnumerateFiles(unpackedDir, String.Format("{0}.json", Path.GetFileNameWithoutExtension(sngFile)), SearchOption.AllDirectories).FirstOrDefault();
                        if (!String.IsNullOrEmpty(jsonFiles) && jsonFiles.Any())
                            att = Manifest2014<Attributes2014>.LoadFromFile(jsonFiles).Entries.ToArray()[0].Value.ToArray()[0].Value;
                    }

                    var sngContent = Sng2014File.LoadFromFile(sngFile, platform);
                    using (var outputStream = new FileStream(xmlSngFile, FileMode.Create, FileAccess.ReadWrite))
                    {
                        dynamic xmlContent = null;

                        if (arrType == ArrangementType.Vocal)
                            xmlContent = new Vocals(sngContent);
                        else
                            xmlContent = new Song2014(sngContent, att);

                        xmlContent.Serialize(outputStream);
                    }

                    // correct old toolkit/EOF xml (tuning) issues ... sync with SNG data
                    if (File.Exists(xmlEofFile) &&
                        !overwriteSongXml && arrType != ArrangementType.Vocal)
                    {
                        var eofSong = Song2014.LoadFromFile(xmlEofFile);
                        var sngSong = Song2014.LoadFromFile(xmlSngFile);
                        if (eofSong.Tuning != sngSong.Tuning)
                        {
                            eofSong.Tuning = sngSong.Tuning;
                            var xmlComments = Song2014.ReadXmlComments(xmlEofFile);

                            using (var stream = File.Open(xmlEofFile, FileMode.Create))
                                eofSong.Serialize(stream, true);

                            Song2014.WriteXmlComments(xmlEofFile, xmlComments, customComment: "Synced with SNG file");
                        }

                        File.Delete(xmlSngFile);
                    }
                    else
                    {
                        if (arrType != ArrangementType.Vocal)
                            Song2014.WriteXmlComments(xmlSngFile, customComment: "Generated from SNG file");

                        File.Copy(xmlSngFile, xmlEofFile, true);
                        File.Delete(xmlSngFile);
                    }

                    progress += step;
                    GlobalExtension.UpdateProgress.Value = (int)progress;
                }

                //GlobalExtension.HideProgress();
            }
            return unpackedDir;
        }
        private void parseChords(Song2014 xml, Sng2014File sng, Int16[] tuning, bool bass)
        {
            sng.Chords = new ChordSection();
            sng.Chords.Count = xml.ChordTemplates.Length;
            sng.Chords.Chords = new Chord[sng.Chords.Count];

            for (int i = 0; i < sng.Chords.Count; i++)
            {
                var chord = xml.ChordTemplates[i];
                var c = new Chord();
                // TODO: skip if DisplayName == null
                if (chord.DisplayName.EndsWith("arp"))
                    c.Mask |= CON.CHORD_MASK_ARPEGGIO;
                else if (chord.DisplayName.EndsWith("nop"))
                    c.Mask |= CON.CHORD_MASK_NOP;

                c.Frets[0] = (Byte)chord.Fret0;
                c.Frets[1] = (Byte)chord.Fret1;
                c.Frets[2] = (Byte)chord.Fret2;
                c.Frets[3] = (Byte)chord.Fret3;
                c.Frets[4] = (Byte)chord.Fret4;
                c.Frets[5] = (Byte)chord.Fret5;
                c.Fingers[0] = (Byte)chord.Finger0;
                c.Fingers[1] = (Byte)chord.Finger1;
                c.Fingers[2] = (Byte)chord.Finger2;
                c.Fingers[3] = (Byte)chord.Finger3;
                c.Fingers[4] = (Byte)chord.Finger4;
                c.Fingers[5] = (Byte)chord.Finger5;
                for (Byte s = 0; s < 6; s++)
                    c.Notes[s] = GetMidiNote(tuning, s, c.Frets[s], bass, xml.Capo, template: true);
                readString(chord.ChordName, c.Name);
                sng.Chords.Chords[i] = c;
            }
        }