Skip to content

How to bring the RS2014 built-in songs to Learn & Play

 

Hello everyone,

I'd like to share how to bring the RS2014 built-in songs to the L&P ("Learn & Play") version. But first things first - this post is probably borderline within the rules, but imo it's still within them: besides the L&P, I own the PS4 version and I bought a bunch of Ubisoft's RS cables (even though I used other means via RS_ASIO), so I paid my fair share to the developers and I feel that practicing the original songs on the PC is fair with them. The target audience of my post are people in a similar situation. In case you view it differently, feel free to delete the post, but please don't ban me, I'm acting here in good faith (remember to keep the faith).

Now, if you own neither the original RS2014 nor the console version, then stop here and buy the console version to be fair with the developers.

At this point I assume you have the "songs.psarc" file from the original RS2014 version's installation directory (I am not going to break the rules by sharing it).

MAJOR DISCLAIMER FIRST

The method is not perfect:

  • initially I wanted to rebuild the extracted files without any modifications whatsoever - unfortunately both DLC Builder and Rocksmith Toolkit do modify the files; after trying a couple of approaches I don't believe that such a method exists (that is without modifying the tools, but that would be going too far for me)
  • initially I wanted the arrangement IDs to stay the same - so I could port my progress from my friend's PC; unfortunately it seems that Ubisoft has taken some proactive steps to delist any files with their original arrangement IDs (they wouldn't even appear on the song list)

To summarize: the resulting songs are playable, but not exactly the same as the original ones.

An important note: the use Rocksmith Toolkit is not accidental; my first approach was with DLC Builder, but I found it pretty aggressive (not allowing to build some songs without removal of some notes), so I went with Rocksmith Toolkit instead (tbh I didn't check if it doesn't remove any notes silently, but if it does, then at the very least without any "ERROR: FIX THE ARRANGEMENT" complaints; I simply assumed it doesn't - you know, being an older tool and all).

THE MANUAL METHOD
This method has two major drawbacks:

  • that's a lot of work if you're interested in multiple pieces; true, I've never been interested in bringing all of them, but you know: an engineer encountering an engineering problem... 🥲
  • it's still pretty technical...

if any of these is an issue for you, you can find an automated approach in the next section.

  1. download the Rocksmith Toolkit from GitHub and install it; I used the latest available version (v2.9.2.1)
  2. RS Toolkit > Packer/Unpacker > Unpack the songs.psarc
  3. for each song you're interested in:
    1. if it's alphabetically after ultrasoul, you'd have to convert its sng files to xml (using song2014 in console; if you're interested in arguments, see the attached code); the unpacking fails on jvocals arragement of ultrasoul and consequently all subsequent are not generated either
    2. create a folder for the song; IMPORTANT: its name must be different from any original song's songKey, otherwise the game will hang on play! for example: if you're interested in "poursomesugar" song, then don't name the folder "poursomesugar", but whatever else not conflicting with any other song, for example: "poursomesugardlc"
    3. copy the bnk files from audio/windows
    4. copy respective wem files from audio/windows (now that's not trivial on its own - you'd have to open the bnk file in some hex editor, navigate to 44th byte, select the subsequent 8 bytes, type them in reversed order into programmer's calculator and decode decimal number which is the wem filename; see the attached 44b.png)
    5. copy respective album art from gfxassets (copy all three sizes); the rs2\* training songs don't have any; personally I used the rocksmithintro's album art
    6. copy respective arrangements from songs/arr
    7. copy respective json manifests from manifests/songs
    8. for each manifest regenerate its arrangement ID, otherwise the song will not be discovered! use a random guid and to override the entry's name and its PersistentID to some random guid (see the attached arrid.png)
    9. if it's ultrasoul - I haven't succeeded in displaying its jvocals, but nonetheless I copied assets/ui/lyrics/ultrasoul/lyrics_ultrasoul.dds and the attached glyphs file (generated using DLC Builder) for completeness' sake; that's good enough for me, I never cared about this song anyway
    10. build directory (using packagecreator in console; if you're interested in arguments, see the attached code)

THE AUTOMATED APPROACH
Before jumping into the automation, some more disclaimers:

  1. it's still pretty technical, so some minimal technical knowledge is required: how to install Visual Studio, create a console project, copy my code, set the variables' values and run it - it's not a rocket science, but if it seems too hard, involve a programmer friend
  2. I haven't tested all arrangements of all songs - what I can only guarantee is that they all appear in L&P (I did check if they all appear in RS lol)
  3. I don't know if ultrasoul has its japanese vocals displaying correctly; as mentioned above I failed to display any jvocals in any way; I must have done something wrong, but I never cared about this song anyway, so whatever 🙂
  4. I tested the code with Pc verson of the songs.psarc - when running against Xbox or Playstation version, you might need to introduce some additional changes to the code
  5. THIS IS NOT A PROGRAMMING COURSE - there's no good object-oriented design, SOLID principles, exception handling, unit-testability or whatever to learn here 🥲); in case you're interested, then that's a topic for another time; still, I did clean the code quite a bit so it should be easier to understand...

To the method itself:

  1. download and install Visual Studio (there's a free Community Edition)
  2. download the Rocksmith Toolkit from GitHub and install it; I used the latest available version (v2.9.2.1)
  3. in Visual Studio create a console application and Program.cs with the attached code
  4. set the variables at the beginning of the Main method according to your setup
  5. build the app
  6. copy the attached glyphs file to the build directory
  7. run the app
  8. wait quite a bit; like seriously, go grab a lunch or do the laundry

Some notes:

  • I made generating the arragement IDs deterministic - so the songs receive the same values every time the program is run (technically across different machines, but I didn't test it)
  • I appended the original songKeys with "dlc" - I couldn't use the original ones as the songs would cause the game to freeze on playing them (this took me quite a while to realize)
  • this method seems to import the volumes of the song and preview instead of using the default values - what is a nice bonus (isn't it?)

TWO FINAL NOTES
01. copy the resulting psarc files to Rocksmith's dlc directory (most likely C:\Program Files (x86)\Steam\steamapps\common\Rocksmith2014\dlc)
10. enjoy!


That's all folks!



THE CODE

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;

namespace RSRenamer
{
    internal static class Program
    {
        static void Main()
        {
            // set the variables
            var toolkitDirectory = @"C:\Users\Patryk\Desktop\RocksmithToolkit";
            var songsFilePath = @"C:\Users\Patryk\Desktop\RS2014\songs.psarc";
            var songsFilePlatform = "Pc"; // "Xbox" or "PS3"
            // end of variables

            // roughly check if the variables are set correctly
            var packer = Path.Combine(toolkitDirectory, "packer.exe");
            var sng2014 = Path.Combine(toolkitDirectory, "sng2014.exe");
            var packageCreator = Path.Combine(toolkitDirectory, "packagecreator.exe");
            var ultrasoulGlyphsFile = "lyrics_ultrasoul.glyphs.xml";
            if (!Directory.Exists(toolkitDirectory) || !File.Exists(packer) || !File.Exists(sng2014) || !File.Exists(packageCreator))
            {
                Console.WriteLine("Incorrect rocksmith toolkit directory or missing crucial files.");
                return;
            }
            if (!File.Exists(songsFilePath))
            {
                Console.WriteLine("Songs file doesn't exist.");
                return;
            }
            if (!File.Exists(ultrasoulGlyphsFile))
            {
                Console.WriteLine("Glyphs file was not copied to the output directory.");
                return;
            }

            Console.WriteLine("Unpacking the songs file, it may take a couple of minutes...");
            Run(packer, $"-u -i \"{songsFilePath}\" -o \".\" -u -v RS2014 -f {songsFilePlatform}");
            Console.WriteLine();

            // generated by the packer tool
            var extractedDirectory = $"{Path.GetFileName(songsFilePath).Replace('.', '_')}_RS2014_{songsFilePlatform}";

            // sadly, unpacking fails on ultrasoul's jvocals arrangement (I assume it's not discovered as vocals)
            // and consequently for all subsequent arrangements their respective xml files are missing and therefore:
            // removing the faulty file...
            var faultyFile = Path.Combine(extractedDirectory, "songs", "arr", "ultrasoul_jvocals.sng.xml");
            if (File.Exists(faultyFile))
                File.Delete(faultyFile);

            // ...and regenerating all xml files manually
            Console.WriteLine("Regenerating all xml files, it may also take some time...");
            foreach (var arrangementSng in Directory.GetFiles(Path.Combine(extractedDirectory, "songs", "bin", "generic")))
            {
                var arrangementName = Path.GetFileNameWithoutExtension(arrangementSng);
                var arrangementType = "Guitar";
                if (arrangementName.EndsWith("_bass", StringComparison.InvariantCultureIgnoreCase))
                    arrangementType = "Bass";
                else if (arrangementName.EndsWith("_vocals", StringComparison.InvariantCultureIgnoreCase)
                    || arrangementName.EndsWith("_jvocals", StringComparison.InvariantCultureIgnoreCase))
                    arrangementType = "Vocal";

                Console.Write($"{arrangementName}: ");
                var manifest = Path.Combine(extractedDirectory, "manifests", "songs", $"{arrangementName}.json");
                Run(sng2014, $"-x -i \"{arrangementSng}\" -m \"{manifest}\" -a {arrangementType}");

                // move the generated xml file
                var arrangement = Path.Combine(Path.GetDirectoryName(arrangementSng), $"{arrangementName}.xml");
                File.Move(arrangement, Path.Combine(extractedDirectory, "songs", "arr", Path.GetFileName(arrangement)), true);
            }
            Console.WriteLine();

            // random for generating new arrangement IDs
            // using fixed seed so that everyone running the code ends with the same IDs
            var random = new Random(549799200);
            var outputDirectory = "temp";
            var songKeys = Directory.GetFiles(Path.Combine(extractedDirectory, "gamexblocks", "nsongs"), "*") // get all xblock files
                .Select(Path.GetFileNameWithoutExtension) // take the song key
                .Where(s => !s.EndsWith("_fcp_disk", StringComparison.InvariantCultureIgnoreCase)) // skip those with missing files (RS1 previews?)
                .OrderBy(s => s); // sort alphabetically
            foreach (var songKey in songKeys)
            {
                Console.WriteLine($"Preparing files for {songKey}...");

                // must be different from songKey, otherwise the game will freeze on play!
                var songDirectory = $"{songKey}dlc";
                var workingDirectory = Path.Combine(outputDirectory, songDirectory);

                // copy instead of move, because some files are used more than once
                var filesToCopy = new List<string>();

                // work on an empty workspace; delete if anything is here
                if (Directory.Exists(workingDirectory))
                    Directory.Delete(workingDirectory, true);
                Directory.CreateDirectory(workingDirectory);

                // audio files
                var bnkFiles = new string[]
                {
                    Path.Combine(extractedDirectory, "audio", "windows", $"song_{songKey}.bnk"),
                    Path.Combine(extractedDirectory, "audio", "windows", $"song_{songKey}_preview.bnk")
                };
                filesToCopy.AddRange(bnkFiles);

                var wemFiles = bnkFiles.Select(f =>
                {
                    var buffer = new byte[8];
                    using (var stream = File.Open(f, FileMode.Open, FileAccess.Read))
                    {
                        // well, it's more complex than this in general, but it works for songs.psarc...
                        stream.Seek(44, SeekOrigin.Begin);
                        stream.Read(buffer, 0, 8);
                        var span = buffer.AsSpan(0, 8);
                        var wemNumber = BitConverter.ToInt64(span);

                        return Path.Combine(extractedDirectory, "audio", "windows", $"{wemNumber}.wem");
                    }
                });
                filesToCopy.AddRange(wemFiles);

                // album art
                var albumArtSongKey = songKey;

                // the Rocksmith training songs don't have their album art, so reusing the rocksmith intro's
                if (songKey == "rs2arpeggios" || songKey == "rs2chordnamestress" || songKey == "rs2levelbreak" || songKey == "rs2tails")
                    albumArtSongKey = "rocksmithintro";

                filesToCopy.AddRange(Directory.GetFiles(Path.Combine(extractedDirectory, "gfxassets", "album_art"), $"album_{albumArtSongKey}*"));

                // arrangements
                filesToCopy.AddRange(Directory.GetFiles(Path.Combine(extractedDirectory, "songs", "arr"), $"{songKey}*"));

                // manifests
                var manifestsDirectory = Directory.GetDirectories(Path.Combine(extractedDirectory, "manifests")).Single();
                var manifests = Directory.GetFiles(manifestsDirectory, $"{songKey}*");
                filesToCopy.AddRange(manifests);

                // lyrics assets
                if (songKey == "ultrasoul")
                {
                    filesToCopy.AddRange(Directory.GetFiles(Path.Combine(extractedDirectory, "assets", "ui", "lyrics", songKey)));
                    filesToCopy.Add(ultrasoulGlyphsFile);
                }

                // other files (hsan, model files and graph) are not needed

                // copy all files around
                foreach (var file in filesToCopy)
                {
                    var destFileName = Path.Combine(workingDirectory, Path.GetFileName(file));
                    File.Copy(file, destFileName);
                }

                // modify arrangement IDs (otherwise the game will not even discover the songs!)
                var movedManifests = manifests.Select(m => Path.Combine(workingDirectory, Path.GetFileName(m)));
                foreach (var manifest in movedManifests)
                {
                    var newArrangementID = random.NextGuid().ToString("N");

                    // this probably could be done nicer... I'm not JSON manipulation expert
                    var content = JObject.Parse(File.ReadAllText(manifest));
                    var entry = (JProperty)content["Entries"].Single();

                    // rename the entry itself...
                    entry.Replace(new JProperty(newArrangementID, entry.Value));

                    // ...and its PersistentID attribute
                    content["Entries"][newArrangementID]["Attributes"]["PersistentID"] = new JValue(newArrangementID);

                    File.WriteAllText(manifest, content.ToString());
                }
            }
            Console.WriteLine();

            // build the packages
            Console.WriteLine("Preparing packages, this will again take some time...");
            Run(packageCreator, outputDirectory);

            // move the psarc files up
            foreach (var file in Directory.GetFiles(outputDirectory))
            {
                File.Move(file, Path.GetFileName(file), true);
            }
            Console.WriteLine("Done!");
            Console.WriteLine();

            // cleanup
            Directory.Delete(outputDirectory, true);
            Directory.Delete(extractedDirectory, true);

            // credits
            Console.WriteLine("Enjoy!");
            Console.WriteLine("- Patryk marchewek87");
            Console.WriteLine();
        }

        private static void Run(string program, string arguments)
        {
            var programProcess = Process.Start(program, arguments);
            programProcess.WaitForExit();

            // none of the programs ever failed for me, but just in case...
            if (programProcess.ExitCode != 0)
            {
                throw new Exception($"Something went wrong when running: {Path.GetFileName(program)} {arguments}");
            }
        }

        private static Guid NextGuid(this Random @this)
        {
            Span<byte> bytes = stackalloc byte[16];
            @this.NextBytes(bytes);
            return new Guid(bytes);
        }

        static void OriginalRun()
        {
            var path = @"C:\Users\Patryk\Desktop\RS2014\test\songs_psarc_RS2014_Pc";
            var outpath = @"C:\Users\Patryk\Desktop\RS2014\test\final";
            foreach (var xblock in Directory.GetFiles(Path.Combine(path, "gamexblocks", "nsongs")))
            {
                try
                {
                    var song = Path.GetFileNameWithoutExtension(xblock);

                    // ignore songs with different/missing structure (rs1 previews?)
                    if (song.EndsWith("_fcp_disk", StringComparison.InvariantCultureIgnoreCase))
                    {
                        Console.WriteLine($"Skipping {song}");
                        continue;
                    }

                    Console.WriteLine($"Generating {song}...");

                    // create an empty workspace (delete if anything was there)
                    var outPath = Path.Combine(outpath, song);
                    if (Directory.Exists(outPath))
                    {
                        Directory.Delete(outPath, true);
                    }
                    Directory.CreateDirectory(outPath);

                    // xblock file
                    var dest = Path.Combine(outPath, "gamexblocks", "nsongs");
                    Directory.CreateDirectory(dest);
                    File.Copy(xblock, Path.Combine(dest, Path.GetFileName(xblock)));

                    // audio files
                    dest = Path.Combine(outPath, "audio", "windows");
                    Directory.CreateDirectory(dest);
                    var songFiles = new[]
                    {
                        Path.Combine(path, "audio", "windows", $"song_{song}.bnk"),
                        Path.Combine(path, "audio", "windows", $"song_{song}_preview.bnk")
                    };

                    foreach (var songFile in songFiles)
                    {
                        File.Copy(songFile, Path.Combine(dest, Path.GetFileName(songFile)));

                        var buffer = new byte[8];
                        using (var fo = File.Open(songFile, FileMode.Open, FileAccess.Read))
                        {
                            // well, it's more complex than that in general but it works in songs.psarc file
                            fo.Seek(44, SeekOrigin.Begin);
                            fo.Read(buffer, 0, 8);
                            var span = buffer.AsSpan(0, 8);
                            var no = BitConverter.ToInt64(span);

                            var wemFile = Path.Combine(path, "audio", "windows", $"{no}.wem");
                            File.Copy(wemFile, Path.Combine(dest, Path.GetFileName(wemFile)));
                        }
                    }

                    // album art
                    dest = Path.Combine(outPath, "gfxassets", "album_art");
                    Directory.CreateDirectory(dest);
                    foreach (var file in Directory.GetFiles(Path.Combine(path, "gfxassets", "album_art"), $"album_{song}*"))
                    {
                        File.Copy(file, Path.Combine(dest, Path.GetFileName(file)));
                    }

                    // manifests and songs
                    var manifestsDir = Path.GetFileName(Directory.GetDirectories(Path.Combine(path, "manifests")).Single());
                    var additionalDirs = new[]
                    {
                        Path.Combine("manifests", manifestsDir),
                        Path.Combine("songs", "arr"),
                        Path.Combine("songs", "bin", "generic")
                    };

                    foreach (var additionalDir in additionalDirs)
                    {
                        dest = Path.Combine(outPath, additionalDir);
                        Directory.CreateDirectory(dest);

                        foreach (var file in Directory.GetFiles(Path.Combine(path, additionalDir), $"{song}*"))
                        {
                            File.Copy(file, Path.Combine(dest, Path.GetFileName(file)));
                        }
                    }

                    // lyrics assets; basically ultrasoul only
                    var lyrics = Path.Combine(path, "assets", "ui", "lyrics", song);
                    if (Directory.Exists(lyrics))
                    {
                        dest = Path.Combine(outPath, "assets", "ui", "lyrics", song);
                        Directory.CreateDirectory(dest);

                        foreach (var file in Directory.GetFiles(lyrics))
                            File.Copy(file, Path.Combine(dest, Path.GetFileName(file)));
                    }

                    // hsan - extract the current song only
                    var psarcHsanFileName = Directory.GetFiles(Path.Combine(path, "manifests"), "*.hsan", SearchOption.AllDirectories).Single();
                    var psarcHsan = JObject.Parse(File.ReadAllText(psarcHsanFileName));
                    var entriesToBeRemoved = psarcHsan["Entries"].Where(c => !((string)c.Children()["Attributes"]["SongKey"].Single()).Equals(song, StringComparison.InvariantCultureIgnoreCase)).ToList();
                    foreach (var entryToBeRemoved in entriesToBeRemoved)
                        entryToBeRemoved.Remove();

                    var hsanout = Path.Combine(outPath, "manifests", manifestsDir, Path.GetFileName(psarcHsanFileName));
                    File.WriteAllText(hsanout, psarcHsan.ToString());

                    // model files and graph not needed
                }
                catch (Exception e)
                {
                    var color = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine($"Failed with error: {e}");
                    Console.ForegroundColor = color;
                }
            }
        }
    }
}

THE GLYPHS FILE

lyrics_ultrasoul.glyphs.xml

<?xml version="1.0" encoding="utf-8"?>
<GlyphDefinitions TextureWidth="512" TextureHeight="1024">
  <GlyphDefinition Symbol="…" InnerYMin="0.03125" InnerYMax="0.037109375" InnerXMin="0.015625" InnerXMax="0.0625" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0" OuterXMax="0.078125" />
  <GlyphDefinition Symbol="a" InnerYMin="0.022460938" InnerYMax="0.037109375" InnerXMin="0.09375" InnerXMax="0.12109375" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.078125" OuterXMax="0.13671875" />
  <GlyphDefinition Symbol="c" InnerYMin="0.022460938" InnerYMax="0.037109375" InnerXMin="0.15234375" InnerXMax="0.171875" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.13671875" OuterXMax="0.1875" />
  <GlyphDefinition Symbol="D" InnerYMin="0.015625" InnerYMax="0.036132812" InnerXMin="0.203125" InnerXMax="0.234375" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.1875" OuterXMax="0.25" />
  <GlyphDefinition Symbol="e" InnerYMin="0.022460938" InnerYMax="0.037109375" InnerXMin="0.265625" InnerXMax="0.29101562" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.25" OuterXMax="0.30664062" />
  <GlyphDefinition Symbol="I" InnerYMin="0.015625" InnerYMax="0.036132812" InnerXMin="0.32226562" InnerXMax="0.33007812" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.30664062" OuterXMax="0.34570312" />
  <GlyphDefinition Symbol="l" InnerYMin="0.0126953125" InnerYMax="0.036132812" InnerXMin="0.36132812" InnerXMax="0.36914062" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.34570312" OuterXMax="0.38476562" />
  <GlyphDefinition Symbol="n" InnerYMin="0.022460938" InnerYMax="0.036132812" InnerXMin="0.40039062" InnerXMax="0.42382812" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.38476562" OuterXMax="0.43945312" />
  <GlyphDefinition Symbol="o" InnerYMin="0.022460938" InnerYMax="0.037109375" InnerXMin="0.45507812" InnerXMax="0.48242188" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.43945312" OuterXMax="0.49804688" />
  <GlyphDefinition Symbol="t" InnerYMin="0.018554688" InnerYMax="0.036132812" InnerXMin="0.5136719" InnerXMax="0.5292969" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.49804688" OuterXMax="0.5449219" />
  <GlyphDefinition Symbol="i" InnerYMin="0.0146484375" InnerYMax="0.036132812" InnerXMin="0.5605469" InnerXMax="0.5703125" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.5449219" OuterXMax="0.5859375" />
  <GlyphDefinition Symbol="あ" InnerYMin="0.0146484375" InnerYMax="0.0390625" InnerXMin="0.6015625" InnerXMax="0.6484375" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.5859375" OuterXMax="0.6640625" />
  <GlyphDefinition Symbol="い" InnerYMin="0.016601562" InnerYMax="0.037109375" InnerXMin="0.6796875" InnerXMax="0.7246094" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.6640625" OuterXMax="0.7402344" />
  <GlyphDefinition Symbol="う" InnerYMin="0.013671875" InnerYMax="0.0390625" InnerXMin="0.7558594" InnerXMax="0.7910156" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.7402344" OuterXMax="0.8066406" />
  <GlyphDefinition Symbol="え" InnerYMin="0.013671875" InnerYMax="0.0390625" InnerXMin="0.8222656" InnerXMax="0.8691406" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.8066406" OuterXMax="0.8847656" />
  <GlyphDefinition Symbol="お" InnerYMin="0.0146484375" InnerYMax="0.038085938" InnerXMin="0.9003906" InnerXMax="0.94921875" OuterYMin="0" OuterYMax="0.05078125" OuterXMin="0.8847656" OuterXMax="0.96484375" />
  <GlyphDefinition Symbol="か" InnerYMin="0.06542969" InnerYMax="0.08984375" InnerXMin="0.015625" InnerXMax="0.06640625" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0" OuterXMax="0.08203125" />
  <GlyphDefinition Symbol="が" InnerYMin="0.06347656" InnerYMax="0.08886719" InnerXMin="0.09765625" InnerXMax="0.15039062" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.08203125" OuterXMax="0.16601562" />
  <GlyphDefinition Symbol="き" InnerYMin="0.06542969" InnerYMax="0.08984375" InnerXMin="0.18164062" InnerXMax="0.22460938" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.16601562" OuterXMax="0.24023438" />
  <GlyphDefinition Symbol="く" InnerYMin="0.06542969" InnerYMax="0.08984375" InnerXMin="0.25585938" InnerXMax="0.29101562" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.24023438" OuterXMax="0.30664062" />
  <GlyphDefinition Symbol="ぐ" InnerYMin="0.06347656" InnerYMax="0.08984375" InnerXMin="0.32226562" InnerXMax="0.3671875" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.30664062" OuterXMax="0.3828125" />
  <GlyphDefinition Symbol="け" InnerYMin="0.06542969" InnerYMax="0.08984375" InnerXMin="0.3984375" InnerXMax="0.44335938" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.3828125" OuterXMax="0.45898438" />
  <GlyphDefinition Symbol="げ" InnerYMin="0.06347656" InnerYMax="0.08984375" InnerXMin="0.47460938" InnerXMax="0.5234375" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.45898438" OuterXMax="0.5390625" />
  <GlyphDefinition Symbol="こ" InnerYMin="0.068359375" InnerYMax="0.08886719" InnerXMin="0.5546875" InnerXMax="0.59375" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.5390625" OuterXMax="0.609375" />
  <GlyphDefinition Symbol="さ" InnerYMin="0.064453125" InnerYMax="0.08886719" InnerXMin="0.625" InnerXMax="0.6640625" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.609375" OuterXMax="0.6796875" />
  <GlyphDefinition Symbol="し" InnerYMin="0.06640625" InnerYMax="0.08886719" InnerXMin="0.6953125" InnerXMax="0.734375" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.6796875" OuterXMax="0.75" />
  <GlyphDefinition Symbol="じ" InnerYMin="0.06542969" InnerYMax="0.08886719" InnerXMin="0.765625" InnerXMax="0.8046875" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.75" OuterXMax="0.8203125" />
  <GlyphDefinition Symbol="す" InnerYMin="0.06542969" InnerYMax="0.08984375" InnerXMin="0.8359375" InnerXMax="0.8828125" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.8203125" OuterXMax="0.8984375" />
  <GlyphDefinition Symbol="そ" InnerYMin="0.064453125" InnerYMax="0.08886719" InnerXMin="0.9140625" InnerXMax="0.9609375" OuterYMin="0.05078125" OuterYMax="0.1015625" OuterXMin="0.8984375" OuterXMax="0.9765625" />
  <GlyphDefinition Symbol="た" InnerYMin="0.11621094" InnerYMax="0.13964844" InnerXMin="0.015625" InnerXMax="0.064453125" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0" OuterXMax="0.080078125" />
  <GlyphDefinition Symbol="だ" InnerYMin="0.115234375" InnerYMax="0.13964844" InnerXMin="0.095703125" InnerXMax="0.14648438" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.080078125" OuterXMax="0.16210938" />
  <GlyphDefinition Symbol="っ" InnerYMin="0.12402344" InnerYMax="0.13964844" InnerXMin="0.17773438" InnerXMax="0.21484375" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.16210938" OuterXMax="0.23046875" />
  <GlyphDefinition Symbol="つ" InnerYMin="0.119140625" InnerYMax="0.13867188" InnerXMin="0.24609375" InnerXMax="0.29296875" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.23046875" OuterXMax="0.30859375" />
  <GlyphDefinition Symbol="づ" InnerYMin="0.11425781" InnerYMax="0.13867188" InnerXMin="0.32421875" InnerXMax="0.375" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.30859375" OuterXMax="0.390625" />
  <GlyphDefinition Symbol="て" InnerYMin="0.1171875" InnerYMax="0.13964844" InnerXMin="0.40625" InnerXMax="0.45117188" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.390625" OuterXMax="0.46679688" />
  <GlyphDefinition Symbol="で" InnerYMin="0.1171875" InnerYMax="0.13964844" InnerXMin="0.48242188" InnerXMax="0.53125" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.46679688" OuterXMax="0.546875" />
  <GlyphDefinition Symbol="と" InnerYMin="0.11621094" InnerYMax="0.13964844" InnerXMin="0.5625" InnerXMax="0.6015625" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.546875" OuterXMax="0.6171875" />
  <GlyphDefinition Symbol="ど" InnerYMin="0.11425781" InnerYMax="0.13964844" InnerXMin="0.6328125" InnerXMax="0.67578125" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.6171875" OuterXMax="0.69140625" />
  <GlyphDefinition Symbol="な" InnerYMin="0.115234375" InnerYMax="0.13964844" InnerXMin="0.70703125" InnerXMax="0.7578125" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.69140625" OuterXMax="0.7734375" />
  <GlyphDefinition Symbol="に" InnerYMin="0.1171875" InnerYMax="0.13964844" InnerXMin="0.7890625" InnerXMax="0.8339844" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.7734375" OuterXMax="0.8496094" />
  <GlyphDefinition Symbol="の" InnerYMin="0.1171875" InnerYMax="0.13867188" InnerXMin="0.8652344" InnerXMax="0.9140625" OuterYMin="0.1015625" OuterYMax="0.15234375" OuterXMin="0.8496094" OuterXMax="0.9296875" />
  <GlyphDefinition Symbol="は" InnerYMin="0.16796875" InnerYMax="0.19140625" InnerXMin="0.015625" InnerXMax="0.064453125" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0" OuterXMax="0.080078125" />
  <GlyphDefinition Symbol="ば" InnerYMin="0.16503906" InnerYMax="0.19140625" InnerXMin="0.095703125" InnerXMax="0.14453125" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.080078125" OuterXMax="0.16015625" />
  <GlyphDefinition Symbol="び" InnerYMin="0.16503906" InnerYMax="0.18945312" InnerXMin="0.17578125" InnerXMax="0.22460938" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.16015625" OuterXMax="0.24023438" />
  <GlyphDefinition Symbol="ま" InnerYMin="0.16699219" InnerYMax="0.19140625" InnerXMin="0.25585938" InnerXMax="0.29882812" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.24023438" OuterXMax="0.31445312" />
  <GlyphDefinition Symbol="み" InnerYMin="0.16601562" InnerYMax="0.19042969" InnerXMin="0.33007812" InnerXMax="0.38085938" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.31445312" OuterXMax="0.39648438" />
  <GlyphDefinition Symbol="め" InnerYMin="0.16699219" InnerYMax="0.19042969" InnerXMin="0.41210938" InnerXMax="0.4609375" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.39648438" OuterXMax="0.4765625" />
  <GlyphDefinition Symbol="も" InnerYMin="0.16699219" InnerYMax="0.19042969" InnerXMin="0.4921875" InnerXMax="0.5332031" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.4765625" OuterXMax="0.5488281" />
  <GlyphDefinition Symbol="ゃ" InnerYMin="0.171875" InnerYMax="0.19140625" InnerXMin="0.5644531" InnerXMax="0.6074219" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.5488281" OuterXMax="0.6230469" />
  <GlyphDefinition Symbol="や" InnerYMin="0.16601562" InnerYMax="0.19042969" InnerXMin="0.6386719" InnerXMax="0.6894531" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.6230469" OuterXMax="0.7050781" />
  <GlyphDefinition Symbol="ょ" InnerYMin="0.171875" InnerYMax="0.19140625" InnerXMin="0.7207031" InnerXMax="0.7578125" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.7050781" OuterXMax="0.7734375" />
  <GlyphDefinition Symbol="ら" InnerYMin="0.16601562" InnerYMax="0.19140625" InnerXMin="0.7890625" InnerXMax="0.828125" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.7734375" OuterXMax="0.84375" />
  <GlyphDefinition Symbol="り" InnerYMin="0.16796875" InnerYMax="0.19140625" InnerXMin="0.859375" InnerXMax="0.8925781" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.84375" OuterXMax="0.9082031" />
  <GlyphDefinition Symbol="る" InnerYMin="0.16699219" InnerYMax="0.19042969" InnerXMin="0.9238281" InnerXMax="0.96875" OuterYMin="0.15234375" OuterYMax="0.203125" OuterXMin="0.9082031" OuterXMax="0.984375" />
  <GlyphDefinition Symbol="れ" InnerYMin="0.21777344" InnerYMax="0.24121094" InnerXMin="0.015625" InnerXMax="0.068359375" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0" OuterXMax="0.083984375" />
  <GlyphDefinition Symbol="わ" InnerYMin="0.21777344" InnerYMax="0.2421875" InnerXMin="0.099609375" InnerXMax="0.1484375" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.083984375" OuterXMax="0.1640625" />
  <GlyphDefinition Symbol="を" InnerYMin="0.21679688" InnerYMax="0.2421875" InnerXMin="0.1796875" InnerXMax="0.2265625" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.1640625" OuterXMax="0.2421875" />
  <GlyphDefinition Symbol="ん" InnerYMin="0.21777344" InnerYMax="0.24121094" InnerXMin="0.2578125" InnerXMax="0.30859375" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.2421875" OuterXMax="0.32421875" />
  <GlyphDefinition Symbol="ア" InnerYMin="0.21777344" InnerYMax="0.24121094" InnerXMin="0.33984375" InnerXMax="0.38476562" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.32421875" OuterXMax="0.40039062" />
  <GlyphDefinition Symbol="イ" InnerYMin="0.21679688" InnerYMax="0.24121094" InnerXMin="0.41601562" InnerXMax="0.4609375" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.40039062" OuterXMax="0.4765625" />
  <GlyphDefinition Symbol="ウ" InnerYMin="0.21777344" InnerYMax="0.24121094" InnerXMin="0.4921875" InnerXMax="0.5332031" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.4765625" OuterXMax="0.5488281" />
  <GlyphDefinition Symbol="ソ" InnerYMin="0.21972656" InnerYMax="0.24121094" InnerXMin="0.5644531" InnerXMax="0.6074219" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.5488281" OuterXMax="0.6230469" />
  <GlyphDefinition Symbol="ト" InnerYMin="0.21777344" InnerYMax="0.24121094" InnerXMin="0.6386719" InnerXMax="0.671875" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.6230469" OuterXMax="0.6875" />
  <GlyphDefinition Symbol="ド" InnerYMin="0.21777344" InnerYMax="0.24121094" InnerXMin="0.703125" InnerXMax="0.7363281" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.6875" OuterXMax="0.7519531" />
  <GlyphDefinition Symbol="パ" InnerYMin="0.21777344" InnerYMax="0.24023438" InnerXMin="0.7675781" InnerXMax="0.8203125" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.7519531" OuterXMax="0.8359375" />
  <GlyphDefinition Symbol="ペ" InnerYMin="0.21875" InnerYMax="0.23925781" InnerXMin="0.8515625" InnerXMax="0.90234375" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.8359375" OuterXMax="0.91796875" />
  <GlyphDefinition Symbol="ホ" InnerYMin="0.21777344" InnerYMax="0.24121094" InnerXMin="0.93359375" InnerXMax="0.984375" OuterYMin="0.203125" OuterYMax="0.25390625" OuterXMin="0.91796875" OuterXMax="1" />
  <GlyphDefinition Symbol="マ" InnerYMin="0.2705078" InnerYMax="0.29101562" InnerXMin="0.015625" InnerXMax="0.0625" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0" OuterXMax="0.078125" />
  <GlyphDefinition Symbol="メ" InnerYMin="0.2685547" InnerYMax="0.2919922" InnerXMin="0.09375" InnerXMax="0.13671875" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.078125" OuterXMax="0.15234375" />
  <GlyphDefinition Symbol="ラ" InnerYMin="0.26953125" InnerYMax="0.29296875" InnerXMin="0.16796875" InnerXMax="0.20898438" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.15234375" OuterXMax="0.22460938" />
  <GlyphDefinition Symbol="ル" InnerYMin="0.26953125" InnerYMax="0.29296875" InnerXMin="0.24023438" InnerXMax="0.29296875" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.22460938" OuterXMax="0.30859375" />
  <GlyphDefinition Symbol="ン" InnerYMin="0.26953125" InnerYMax="0.2919922" InnerXMin="0.32421875" InnerXMax="0.36914062" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.30859375" OuterXMax="0.38476562" />
  <GlyphDefinition Symbol="ー" InnerYMin="0.27734375" InnerYMax="0.28125" InnerXMin="0.40039062" InnerXMax="0.44335938" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.38476562" OuterXMax="0.45898438" />
  <GlyphDefinition Symbol="一" InnerYMin="0.27539062" InnerYMax="0.28027344" InnerXMin="0.47460938" InnerXMax="0.5292969" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.45898438" OuterXMax="0.5449219" />
  <GlyphDefinition Symbol="中" InnerYMin="0.26367188" InnerYMax="0.29296875" InnerXMin="0.5605469" InnerXMax="0.6113281" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.5449219" OuterXMax="0.6269531" />
  <GlyphDefinition Symbol="事" InnerYMin="0.26367188" InnerYMax="0.29296875" InnerXMin="0.6425781" InnerXMax="0.69921875" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.6269531" OuterXMax="0.71484375" />
  <GlyphDefinition Symbol="人" InnerYMin="0.26464844" InnerYMax="0.2939453" InnerXMin="0.73046875" InnerXMax="0.7910156" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.71484375" OuterXMax="0.8066406" />
  <GlyphDefinition Symbol="今" InnerYMin="0.26367188" InnerYMax="0.2939453" InnerXMin="0.8222656" InnerXMax="0.8847656" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.8066406" OuterXMax="0.9003906" />
  <GlyphDefinition Symbol="傷" InnerYMin="0.26367188" InnerYMax="0.2939453" InnerXMin="0.9160156" InnerXMax="0.9765625" OuterYMin="0.25390625" OuterYMax="0.3046875" OuterXMin="0.9003906" OuterXMax="0.9921875" />
  <GlyphDefinition Symbol="分" InnerYMin="0.3154297" InnerYMax="0.34472656" InnerXMin="0.015625" InnerXMax="0.078125" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0" OuterXMax="0.09375" />
  <GlyphDefinition Symbol="取" InnerYMin="0.3154297" InnerYMax="0.34472656" InnerXMin="0.109375" InnerXMax="0.16992188" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0.09375" OuterXMax="0.18554688" />
  <GlyphDefinition Symbol="夢" InnerYMin="0.31445312" InnerYMax="0.34472656" InnerXMin="0.20117188" InnerXMax="0.2578125" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0.18554688" OuterXMax="0.2734375" />
  <GlyphDefinition Symbol="大" InnerYMin="0.3154297" InnerYMax="0.34472656" InnerXMin="0.2890625" InnerXMax="0.34960938" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0.2734375" OuterXMax="0.36523438" />
  <GlyphDefinition Symbol="失" InnerYMin="0.31445312" InnerYMax="0.34375" InnerXMin="0.38085938" InnerXMax="0.44140625" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0.36523438" OuterXMax="0.45703125" />
  <GlyphDefinition Symbol="希" InnerYMin="0.31445312" InnerYMax="0.34375" InnerXMin="0.47265625" InnerXMax="0.53125" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0.45703125" OuterXMax="0.546875" />
  <GlyphDefinition Symbol="底" InnerYMin="0.31445312" InnerYMax="0.34375" InnerXMin="0.5625" InnerXMax="0.6230469" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0.546875" OuterXMax="0.6386719" />
  <GlyphDefinition Symbol="悲" InnerYMin="0.31445312" InnerYMax="0.34375" InnerXMin="0.6542969" InnerXMax="0.71484375" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0.6386719" OuterXMax="0.73046875" />
  <GlyphDefinition Symbol="意" InnerYMin="0.31445312" InnerYMax="0.34375" InnerXMin="0.74609375" InnerXMax="0.8066406" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0.73046875" OuterXMax="0.8222656" />
  <GlyphDefinition Symbol="戦" InnerYMin="0.31445312" InnerYMax="0.34472656" InnerXMin="0.8378906" InnerXMax="0.9003906" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0.8222656" OuterXMax="0.9160156" />
  <GlyphDefinition Symbol="手" InnerYMin="0.31445312" InnerYMax="0.34375" InnerXMin="0.9316406" InnerXMax="0.98828125" OuterYMin="0.3046875" OuterYMax="0.35546875" OuterXMin="0.9160156" OuterXMax="1.0039062" />
  <GlyphDefinition Symbol="抜" InnerYMin="0.36621094" InnerYMax="0.3955078" InnerXMin="0.015625" InnerXMax="0.076171875" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0" OuterXMax="0.091796875" />
  <GlyphDefinition Symbol="揺" InnerYMin="0.36523438" InnerYMax="0.39453125" InnerXMin="0.107421875" InnerXMax="0.16601562" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0.091796875" OuterXMax="0.18164062" />
  <GlyphDefinition Symbol="暴" InnerYMin="0.36621094" InnerYMax="0.3955078" InnerXMin="0.19726562" InnerXMax="0.25976562" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0.18164062" OuterXMax="0.27539062" />
  <GlyphDefinition Symbol="望" InnerYMin="0.36621094" InnerYMax="0.39453125" InnerXMin="0.29101562" InnerXMax="0.34765625" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0.27539062" OuterXMax="0.36328125" />
  <GlyphDefinition Symbol="末" InnerYMin="0.36523438" InnerYMax="0.39453125" InnerXMin="0.37890625" InnerXMax="0.43945312" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0.36328125" OuterXMax="0.45507812" />
  <GlyphDefinition Symbol="楽" InnerYMin="0.36523438" InnerYMax="0.3955078" InnerXMin="0.47070312" InnerXMax="0.53125" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0.45507812" OuterXMax="0.546875" />
  <GlyphDefinition Symbol="欲" InnerYMin="0.36523438" InnerYMax="0.3955078" InnerXMin="0.5625" InnerXMax="0.625" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0.546875" OuterXMax="0.640625" />
  <GlyphDefinition Symbol="歓" InnerYMin="0.36523438" InnerYMax="0.39648438" InnerXMin="0.65625" InnerXMax="0.7167969" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0.640625" OuterXMax="0.7324219" />
  <GlyphDefinition Symbol="気" InnerYMin="0.36523438" InnerYMax="0.3955078" InnerXMin="0.7480469" InnerXMax="0.80859375" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0.7324219" OuterXMax="0.82421875" />
  <GlyphDefinition Symbol="決" InnerYMin="0.36523438" InnerYMax="0.39453125" InnerXMin="0.83984375" InnerXMax="0.9003906" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0.82421875" OuterXMax="0.9160156" />
  <GlyphDefinition Symbol="泣" InnerYMin="0.36621094" InnerYMax="0.39453125" InnerXMin="0.9316406" InnerXMax="0.9902344" OuterYMin="0.35546875" OuterYMax="0.40625" OuterXMin="0.9160156" OuterXMax="1.0058594" />
  <GlyphDefinition Symbol="無" InnerYMin="0.41601562" InnerYMax="0.44628906" InnerXMin="0.015625" InnerXMax="0.076171875" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0" OuterXMax="0.091796875" />
  <GlyphDefinition Symbol="独" InnerYMin="0.41601562" InnerYMax="0.4453125" InnerXMin="0.107421875" InnerXMax="0.16992188" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0.091796875" OuterXMax="0.18554688" />
  <GlyphDefinition Symbol="界" InnerYMin="0.4169922" InnerYMax="0.44628906" InnerXMin="0.20117188" InnerXMax="0.26367188" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0.18554688" OuterXMax="0.27929688" />
  <GlyphDefinition Symbol="番" InnerYMin="0.41601562" InnerYMax="0.4453125" InnerXMin="0.29492188" InnerXMax="0.35546875" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0.27929688" OuterXMax="0.37109375" />
  <GlyphDefinition Symbol="真" InnerYMin="0.41601562" InnerYMax="0.4453125" InnerXMin="0.38671875" InnerXMax="0.4453125" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0.37109375" OuterXMax="0.4609375" />
  <GlyphDefinition Symbol="瞬" InnerYMin="0.41601562" InnerYMax="0.44628906" InnerXMin="0.4765625" InnerXMax="0.5332031" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0.4609375" OuterXMax="0.5488281" />
  <GlyphDefinition Symbol="知" InnerYMin="0.41601562" InnerYMax="0.44628906" InnerXMin="0.5644531" InnerXMax="0.6230469" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0.5488281" OuterXMax="0.6386719" />
  <GlyphDefinition Symbol="祝" InnerYMin="0.4169922" InnerYMax="0.44628906" InnerXMin="0.6542969" InnerXMax="0.7167969" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0.6386719" OuterXMax="0.7324219" />
  <GlyphDefinition Symbol="福" InnerYMin="0.4169922" InnerYMax="0.44628906" InnerXMin="0.7480469" InnerXMax="0.8066406" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0.7324219" OuterXMax="0.8222656" />
  <GlyphDefinition Symbol="終" InnerYMin="0.41601562" InnerYMax="0.44628906" InnerXMin="0.8378906" InnerXMax="0.9003906" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0.8222656" OuterXMax="0.9160156" />
  <GlyphDefinition Symbol="結" InnerYMin="0.41601562" InnerYMax="0.4453125" InnerXMin="0.9316406" InnerXMax="0.9921875" OuterYMin="0.40625" OuterYMax="0.45703125" OuterXMin="0.9160156" OuterXMax="1.0078125" />
  <GlyphDefinition Symbol="羽" InnerYMin="0.46777344" InnerYMax="0.4970703" InnerXMin="0.015625" InnerXMax="0.072265625" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0" OuterXMax="0.087890625" />
  <GlyphDefinition Symbol="胸" InnerYMin="0.46679688" InnerYMax="0.49609375" InnerXMin="0.103515625" InnerXMax="0.1640625" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0.087890625" OuterXMax="0.1796875" />
  <GlyphDefinition Symbol="見" InnerYMin="0.46777344" InnerYMax="0.49609375" InnerXMin="0.1953125" InnerXMax="0.25585938" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0.1796875" OuterXMax="0.27148438" />
  <GlyphDefinition Symbol="誰" InnerYMin="0.46679688" InnerYMax="0.49609375" InnerXMin="0.28710938" InnerXMax="0.34570312" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0.27148438" OuterXMax="0.36132812" />
  <GlyphDefinition Symbol="輝" InnerYMin="0.46679688" InnerYMax="0.49609375" InnerXMin="0.37695312" InnerXMax="0.4375" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0.36132812" OuterXMax="0.453125" />
  <GlyphDefinition Symbol="迎" InnerYMin="0.46679688" InnerYMax="0.49609375" InnerXMin="0.46875" InnerXMax="0.5292969" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0.453125" OuterXMax="0.5449219" />
  <GlyphDefinition Symbol="遊" InnerYMin="0.46679688" InnerYMax="0.49609375" InnerXMin="0.5605469" InnerXMax="0.62109375" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0.5449219" OuterXMax="0.63671875" />
  <GlyphDefinition Symbol="鍛" InnerYMin="0.46679688" InnerYMax="0.4970703" InnerXMin="0.65234375" InnerXMax="0.71484375" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0.63671875" OuterXMax="0.73046875" />
  <GlyphDefinition Symbol="開" InnerYMin="0.46777344" InnerYMax="0.49609375" InnerXMin="0.74609375" InnerXMax="0.7988281" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0.73046875" OuterXMax="0.8144531" />
  <GlyphDefinition Symbol="間" InnerYMin="0.46777344" InnerYMax="0.49609375" InnerXMin="0.8300781" InnerXMax="0.8828125" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0.8144531" OuterXMax="0.8984375" />
  <GlyphDefinition Symbol="限" InnerYMin="0.46777344" InnerYMax="0.4970703" InnerXMin="0.9140625" InnerXMax="0.97265625" OuterYMin="0.45703125" OuterYMax="0.5078125" OuterXMin="0.8984375" OuterXMax="0.98828125" />
  <GlyphDefinition Symbol="?" InnerYMin="0.5234375" InnerYMax="0.54589844" InnerXMin="0.015625" InnerXMax="0.046875" OuterYMin="0.5078125" OuterYMax="0.55859375" OuterXMin="0" OuterXMax="0.0625" />
</GlyphDefinitions>

 

44b.png

arrid.png

Featured Replies

  • Author
8 hours ago, sc0rp9 said:

Jesus mate you went town on that when you could have just used the search button and saved your self a week or so…

you can find it here…

https://ignition4.customsforge.com/tools/cdlcenabler

And your welcome 😆

Did you actually read the first paragraph or u a troll? You're sharing how to enable CDLCs, while I never mentioned them in the first place. My post is about RS2014 built-in songs that are available neither here (there were officially released so obviously this forum wouldn't allow sharing them) nor anywhere else (you wouldn't find them in any torrents) - apparently no one bothered. That is until I did. So actually it's you who are welcome...

Well I’m so happy your pleased, I cannot help skip boring text so write properly if you want someone to read your whole post. Now bore off.

  • 3 weeks later...
  • Author
On 11/23/2025 at 1:32 AM, sc0rp9 said:

Well I’m so happy your pleased, I cannot help skip boring text so write properly if you want someone to read your whole post. Now bore off.

I stated my intent very clearly in the very first sentence:

I'd like to share how to bring the RS2014 built-in songs to the L&P ("Learn & Play") version.

so apparently you didn't even read it before jumping into details. And you know, once you realized that you jumped into the wrong conclusions, it wouldn't hurt to just say "sorry". Instead you're complaining that the details are lengthy - yes, they are, because the goal was not easy to reach in the first place.

Have a nice day.

  • 1 month later...

I want to take a moment to thank you for the solution. It was exactly what I needed for my Rocksmith 2014 L&P installation, since I’m using the older version with songs.psarc. I can confirm that your solution works flawlessly.

As for the person criticizing in the comments—ignore them. It’s clear they didn’t even bother reading past the first paragraph. A solution this complex requires a thorough explanation, and you delivered it perfectly. Thanks again!

imagen.png

DUDE this is awesome, waiting for it to unpack songs.psarc file.
took some reading on the basics of visual studio cuz I'm not used to messing around with programming IDEs as much as I used to be
Tip if VS doesnt let you build because of errors, download the newtonsoft dependency with PM> Install-Package Newtonsoft.Json

  • 4 months later...

You did a good job here :) I used VS code with C# extension and had to add Newsoft.Json. Never did a terminal app before, and it's busy running now. Thank you, because I couldn't find another way to do it! Ignore @sc0rp9 - he didn't understand anyways. Itchugged away for a while, but it worked 100% thank you for sharing!

I was inspired by your posting, Patryk. I also had a similar experience, my PS3 drive broke, so I bought the PC version on steam, not realising it no longer had any songs in it. The original songs I could download, but they didn't work. I learned how to get other customs to work, but the originals for my two games Rocksmith2014 and Rocksmith2012, didn't work. I used your snippet and it processed songs.psarc as promised, but it didn't work on rs1compatibilitydisc_p.psarc.

Anyway I added Gemini Code Assist, and he modified it for me to do EITHER songs.psarc or rs1compatibilitydisc_p.psarc. (rs1compatibilitydisc_p.psarc is laid out differently inside - and it is cool to have seperate songs - so that one is able to exclude all the songs you'll never play anyways...)

I ran it, took all the single outputted files away, changed the songsFilePath to the other .psarc and run it again...

Press any key to continue ...

Done!

Enjoy!

- Patryk marchewek87

Here is Gemini's modified code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;

namespace RSRenamer
{
    internal static class Program
    {
        static void Main()
        {
            // set the variables
            var toolkitDirectory = @"D:\ROCKSMITH TOOLKIT\RocksmithToolkit";
            var songsFilePath = @"songs.psarc";  // var songsFilePath = @"rs1compatibilitydisc_p.psarc"; // this is the other file it will now work on
            //var songsFilePath = @"C:\Users\Patryk\Desktop\RS2014\songs.psarc";
            var songsFilePlatform = "Pc"; // "Xbox" or "PS3"
            // end of variables

            // roughly check if the variables are set correctly
            var packer = Path.Combine(toolkitDirectory, "packer.exe");
            var sng2014 = Path.Combine(toolkitDirectory, "sng2014.exe");
            var packageCreator = Path.Combine(toolkitDirectory, "packagecreator.exe");
            var ultrasoulGlyphsFile = "lyrics_ultrasoul.glyphs.xml";
            if (!Directory.Exists(toolkitDirectory) || !File.Exists(packer) || !File.Exists(sng2014) || !File.Exists(packageCreator))
            {
                Console.WriteLine("Incorrect rocksmith toolkit directory or missing crucial files.");
                return;
            }
            if (!File.Exists(songsFilePath))
            {
                Console.WriteLine("Songs file doesn't exist.");
                return;
            }

            // Get directories before unpacking
            var directoriesBeforeUnpack = new HashSet<string>(Directory.GetDirectories(Directory.GetCurrentDirectory()));

            Console.WriteLine("Unpacking the songs file, it may take a couple of minutes...");
            Run(packer, $"-u -i \"{songsFilePath}\" -o \".\" -v RS2014 -f {songsFilePlatform}");
            Console.WriteLine();

            // Get directories after unpacking
            var directoriesAfterUnpack = new HashSet<string>(Directory.GetDirectories(Directory.GetCurrentDirectory()));

            // Find the newly created directory
            directoriesAfterUnpack.ExceptWith(directoriesBeforeUnpack); // Remove directories that existed before

            if (directoriesAfterUnpack.Count == 0)
            {
                Console.WriteLine("No new directory was created by the packer.exe.");
                return;
            }
            if (directoriesAfterUnpack.Count > 1)
            {
                Console.WriteLine("More than one new directory was created by the packer.exe. Cannot determine the extracted directory.");
                return;
            }
            var extractedDirectory = directoriesAfterUnpack.Single();

            // sadly, unpacking fails on ultrasoul's jvocals arrangement (I assume it's not discovered as vocals)
            // and consequently for all subsequent arrangements their respective xml files are missing and therefore:
            // removing the faulty file...
            var faultyFile = Path.Combine(extractedDirectory, "songs", "arr", "ultrasoul_jvocals.sng.xml");
            if (File.Exists(faultyFile))
                File.Delete(faultyFile);

            // ...and regenerating all xml files manually.
            // We search recursively in bin and detect the manifest folder to support different PSARC structures.
            Console.WriteLine("Regenerating all xml files, it may also take some time...");

            // Dynamically find the directory containing manifest JSON files
            var firstManifestFile = Directory.GetFiles(extractedDirectory, "*.json", SearchOption.AllDirectories).FirstOrDefault();
            if (firstManifestFile == null)
            {
                Console.WriteLine("Could not find any manifest (.json) files in the extracted directory.");
                return;
            }
            var manifestsDirectory = Path.GetDirectoryName(firstManifestFile);

            foreach (var arrangementSng in Directory.GetFiles(extractedDirectory, "*.sng", SearchOption.AllDirectories))
            {
                var arrangementName = Path.GetFileNameWithoutExtension(arrangementSng);
                var arrangementType = "Guitar";
                if (arrangementName.EndsWith("_bass", StringComparison.InvariantCultureIgnoreCase))
                    arrangementType = "Bass";
                else if (arrangementName.EndsWith("_vocals", StringComparison.InvariantCultureIgnoreCase)
                    || arrangementName.EndsWith("_jvocals", StringComparison.InvariantCultureIgnoreCase))
                    arrangementType = "Vocal";

                Console.Write($"{arrangementName}: ");
                var manifest = Path.Combine(manifestsDirectory, $"{arrangementName}.json");
                Run(sng2014, $"-x -i \"{arrangementSng}\" -m \"{manifest}\" -a {arrangementType}");

                // move the generated xml file
                var arrangement = Path.Combine(Path.GetDirectoryName(arrangementSng), $"{arrangementName}.xml");
                var arrangementTargetDir = Path.Combine(extractedDirectory, "songs", "arr");
                Directory.CreateDirectory(arrangementTargetDir);
                File.Move(arrangement, Path.Combine(arrangementTargetDir, Path.GetFileName(arrangement)), true);
            }
            Console.WriteLine();

            // random for generating new arrangement IDs
            // using fixed seed so that everyone running the code ends with the same IDs
            var random = new Random(549799200);
            var outputDirectory = "temp";

            var nsongsDirectory = Directory.GetDirectories(extractedDirectory, "nsongs", SearchOption.AllDirectories).FirstOrDefault();
            if (nsongsDirectory == null)
            {
                Console.WriteLine("Could not find 'nsongs' directory inside the extracted files.");
                return;
            }

            var songKeys = Directory.GetFiles(nsongsDirectory, "*") // get all xblock files
                .Select(Path.GetFileNameWithoutExtension) // take the song key
                .Where(s => !s.EndsWith("_fcp_disk", StringComparison.InvariantCultureIgnoreCase)) // skip those with missing files (RS1 previews?)
                .OrderBy(s => s); // sort alphabetically
            foreach (var songKey in songKeys)
            {
                Console.WriteLine($"Preparing files for {songKey}...");

                // must be different from songKey, otherwise the game will freeze on play!
                var songDirectory = $"{songKey}dlc";
                var workingDirectory = Path.Combine(outputDirectory, songDirectory);

                // copy instead of move, because some files are used more than once
                var filesToCopy = new List<string>();

                // work on an empty workspace; delete if anything is here
                if (Directory.Exists(workingDirectory))
                    Directory.Delete(workingDirectory, true);
                Directory.CreateDirectory(workingDirectory);

                // audio files
                var bnkFiles = Directory.GetFiles(extractedDirectory, "*.bnk", SearchOption.AllDirectories)
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals($"song_{songKey}", StringComparison.OrdinalIgnoreCase) ||
                               name.Equals($"song_{songKey}_preview", StringComparison.OrdinalIgnoreCase);
                    }).ToArray();
                filesToCopy.AddRange(bnkFiles);

                var wemFiles = bnkFiles.Select(f =>
                {
                    var buffer = new byte[8];
                    using (var stream = File.Open(f, FileMode.Open, FileAccess.Read))
                    {
                        // well, it's more complex than this in general, but it works for songs.psarc...
                        stream.Seek(44, SeekOrigin.Begin);
                        stream.Read(buffer, 0, 8);
                        var span = buffer.AsSpan(0, 8);
                        var wemNumber = BitConverter.ToInt64(span);

                        // Search for the WEM file recursively as it might not be in /audio/windows/
                        return Directory.GetFiles(extractedDirectory, $"{wemNumber}.wem", SearchOption.AllDirectories).FirstOrDefault()
                            ?? throw new FileNotFoundException($"Could not find audio file {wemNumber}.wem");
                    }
                });
                filesToCopy.AddRange(wemFiles);

                // album art
                var albumArtSongKey = songKey;

                // the Rocksmith training songs don't have their album art, so reusing the rocksmith intro's
                if (songKey == "rs2arpeggios" || songKey == "rs2chordnamestress" || songKey == "rs2levelbreak" || songKey == "rs2tails")
                    albumArtSongKey = "rocksmithintro";

                filesToCopy.AddRange(Directory.GetFiles(extractedDirectory, "*.dds", SearchOption.AllDirectories)
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals($"album_{albumArtSongKey}", StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"album_{albumArtSongKey}_", StringComparison.OrdinalIgnoreCase);
                    }));

                // arrangements
                filesToCopy.AddRange(Directory.GetFiles(Path.Combine(extractedDirectory, "songs", "arr"), "*")
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f).Replace(".sng", "");
                        return name.Equals(songKey, StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"{songKey}_", StringComparison.OrdinalIgnoreCase);
                    }));

                // manifests
                var manifests = Directory.GetFiles(manifestsDirectory, "*.json")
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals(songKey, StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"{songKey}_", StringComparison.OrdinalIgnoreCase);
                    }).ToArray();
                filesToCopy.AddRange(manifests);

                // lyrics assets
                if (songKey == "ultrasoul")
                {
                    if (!File.Exists(ultrasoulGlyphsFile))
                    {
                        Console.WriteLine("Warning: lyrics_ultrasoul.glyphs.xml is missing. Ultrasoul might not package correctly.");
                        continue;
                    }

                    filesToCopy.AddRange(Directory.GetFiles(Path.Combine(extractedDirectory, "assets", "ui", "lyrics", songKey)));
                    filesToCopy.Add(ultrasoulGlyphsFile);
                }

                // other files (hsan, model files and graph) are not needed

                // copy all files around
                foreach (var file in filesToCopy)
                {
                    var destFileName = Path.Combine(workingDirectory, Path.GetFileName(file));
                    File.Copy(file, destFileName);
                }

                // modify arrangement IDs (otherwise the game will not even discover the songs!)
                var movedManifests = manifests.Select(m => Path.Combine(workingDirectory, Path.GetFileName(m)));
                foreach (var manifest in movedManifests)
                {
                    var newArrangementID = random.NextGuid().ToString("N");

                    // this probably could be done nicer... I'm not JSON manipulation expert
                    var content = JObject.Parse(File.ReadAllText(manifest));
                    var entry = (JProperty)content["Entries"].Single();

                    // rename the entry itself...
                    entry.Replace(new JProperty(newArrangementID, entry.Value));

                    // ...and its PersistentID attribute
                    content["Entries"][newArrangementID]["Attributes"]["PersistentID"] = new JValue(newArrangementID);

                    File.WriteAllText(manifest, content.ToString());
                }
            }
            Console.WriteLine();

            // build the packages
            Console.WriteLine("Preparing packages, this will again take some time...");
            Run(packageCreator, outputDirectory);

            // move the psarc files up
            foreach (var file in Directory.GetFiles(outputDirectory))
            {
                File.Move(file, Path.GetFileName(file), true);
            }
            Console.WriteLine("Done!");
            Console.WriteLine();

            // cleanup
            Directory.Delete(outputDirectory, true);
            Directory.Delete(extractedDirectory, true);

            // credits
            Console.WriteLine("Enjoy!");
            Console.WriteLine("- Patryk marchewek87");
            Console.WriteLine();
        }

        private static void Run(string program, string arguments)
        {
            var programProcess = Process.Start(program, arguments);
            programProcess.WaitForExit();

            // none of the programs ever failed for me, but just in case...
            if (programProcess.ExitCode != 0)
            {
                throw new Exception($"Something went wrong when running: {Path.GetFileName(program)} {arguments}");
            }
        }

        private static Guid NextGuid(this Random @this)
        {
            Span<byte> bytes = stackalloc byte[16];
            @this.NextBytes(bytes);
            return new Guid(bytes);
        }

        static void OriginalRun()
        {
            var path = @"C:\Users\Patryk\Desktop\RS2014\test\songs_psarc_RS2014_Pc";
            var outpath = @"C:\Users\Patryk\Desktop\RS2014\test\final";
            foreach (var xblock in Directory.GetFiles(Path.Combine(path, "gamexblocks", "nsongs")))
            {
                try
                {
                    var song = Path.GetFileNameWithoutExtension(xblock);

                    // ignore songs with different/missing structure (rs1 previews?)
                    if (song.EndsWith("_fcp_disk", StringComparison.InvariantCultureIgnoreCase))
                    {
                        Console.WriteLine($"Skipping {song}");
                        continue;
                    }

                    Console.WriteLine($"Generating {song}...");

                    // create an empty workspace (delete if anything was there)
                    var outPath = Path.Combine(outpath, song);
                    if (Directory.Exists(outPath))
                    {
                        Directory.Delete(outPath, true);
                    }
                    Directory.CreateDirectory(outPath);

                    // xblock file
                    var dest = Path.Combine(outPath, "gamexblocks", "nsongs");
                    Directory.CreateDirectory(dest);
                    File.Copy(xblock, Path.Combine(dest, Path.GetFileName(xblock)));

                    // audio files
                    dest = Path.Combine(outPath, "audio", "windows");
                    Directory.CreateDirectory(dest);
                    var songFiles = new[]
                    {
                        Path.Combine(path, "audio", "windows", $"song_{song}.bnk"),
                        Path.Combine(path, "audio", "windows", $"song_{song}_preview.bnk")
                    };

                    foreach (var songFile in songFiles)
                    {
                        File.Copy(songFile, Path.Combine(dest, Path.GetFileName(songFile)));

                        var buffer = new byte[8];
                        using (var fo = File.Open(songFile, FileMode.Open, FileAccess.Read))
                        {
                            // well, it's more complex than that in general but it works in songs.psarc file
                            fo.Seek(44, SeekOrigin.Begin);
                            fo.Read(buffer, 0, 8);
                            var span = buffer.AsSpan(0, 8);
                            var no = BitConverter.ToInt64(span);

                            var wemFile = Path.Combine(path, "audio", "windows", $"{no}.wem");
                            File.Copy(wemFile, Path.Combine(dest, Path.GetFileName(wemFile)));
                        }
                    }

                    // album art
                    dest = Path.Combine(outPath, "gfxassets", "album_art");
                    Directory.CreateDirectory(dest);
                    foreach (var file in Directory.GetFiles(Path.Combine(path, "gfxassets", "album_art"), $"album_{song}*"))
                    {
                        File.Copy(file, Path.Combine(dest, Path.GetFileName(file)));
                    }

                    // manifests and songs
                    var manifestsDir = Path.GetFileName(Directory.GetDirectories(Path.Combine(path, "manifests")).Single());
                    var additionalDirs = new[]
                    {
                        Path.Combine("manifests", manifestsDir),
                        Path.Combine("songs", "arr"),
                        Path.Combine("songs", "bin", "generic")
                    };

                    foreach (var additionalDir in additionalDirs)
                    {
                        dest = Path.Combine(outPath, additionalDir);
                        Directory.CreateDirectory(dest);

                        foreach (var file in Directory.GetFiles(Path.Combine(path, additionalDir), $"{song}*"))
                        {
                            File.Copy(file, Path.Combine(dest, Path.GetFileName(file)));
                        }
                    }

                    // lyrics assets; basically ultrasoul only
                    var lyrics = Path.Combine(path, "assets", "ui", "lyrics", song);
                    if (Directory.Exists(lyrics))
                    {
                        dest = Path.Combine(outPath, "assets", "ui", "lyrics", song);
                        Directory.CreateDirectory(dest);

                        foreach (var file in Directory.GetFiles(lyrics))
                            File.Copy(file, Path.Combine(dest, Path.GetFileName(file)));
                    }

                    // hsan - extract the current song only
                    var psarcHsanFileName = Directory.GetFiles(Path.Combine(path, "manifests"), "*.hsan", SearchOption.AllDirectories).Single();
                    var psarcHsan = JObject.Parse(File.ReadAllText(psarcHsanFileName));
                    var entriesToBeRemoved = psarcHsan["Entries"].Where(c => !((string)c.Children()["Attributes"]["SongKey"].Single()).Equals(song, StringComparison.InvariantCultureIgnoreCase)).ToList();
                    foreach (var entryToBeRemoved in entriesToBeRemoved)
                        entryToBeRemoved.Remove();

                    var hsanout = Path.Combine(outPath, "manifests", manifestsDir, Path.GetFileName(psarcHsanFileName));
                    File.WriteAllText(hsanout, psarcHsan.ToString());

                    // model files and graph not needed
                }
                catch (Exception e)
                {
                    var color = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine($"Failed with error: {e}");
                    Console.ForegroundColor = color;
                }
            }
        }
    }
}

So, if you would care to take things one step further:

1: With the modified code above, one can extract all the Rocksmith2014 original songs as separate .psarc files from the original game's songs.psarc (about 960Mb)

2: Also, it will also extract all the Rocksmith2012 original songs as seperate .psarc files from the compatibility pack's rs1compatibilitydisc_p.psarc (about 222Mb)

What it cannot do, however, is to extract all the Rocksmith2012 DLC songs as seperate .psarc from the compatability pack's rs1compatibilitydlc_p.psarc (about 57Mb). The reason is because they put all the DLC's audio files within the 960Mb songs.psarc file. That's one of the main reasons that the DLC doesn't work with the Learn & Play edition - it only has a 3Mb songs.psarc file!

So, if you're interested in extracting all the Rocksmith2012 DLC songs as seperate .psarc from the compatability pack's rs1compatibilitydlc_p.psarc file, you need to modify Patryk's code further still -> so that it gets BOTH the 960Mb songs.psarc and the 57Mb rs1compatibilitydlc_p.psarc pointed to it. It can then use the metadata from the small file and the audio files from the large file, and using the Rocksmith SongCreator Toolkit, it will automate building all the data together in seperate .psarc files for each son contained within the Rocksmith2012's DLC pack.

Think of it as having a document in a .zip file that references another document in a different .zip file -> both need to be unzipped before the reference can ever be fulfilled...

The only downside of this version below, is that you need to press any key... ath the end between packings. The reason is that it breaks the tool if it's all done at once - this was the easiest workaround. All the songs came through except 20th century boy...

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;

namespace RSRenamer
{
    internal static class Program
    {
        static void Main()
        {
            // set the variables
            var toolkitDirectory = @"D:\ROCKSMITH TOOLKIT\RocksmithToolkit"; // Path to your Rocksmith Toolkit installation
            var rs2014SongsPsarcPath = @"songs.psarc"; // Path to your Rocksmith 2014 songs.psarc file
            var songsFilePath = @"rs1compatibilitydlc_p.psarc";  // var songsFilePath = @"rs1compatibilitydisc_p.psarc"; // this is the other file it will now work on
            //var songsFilePath = @"C:\Users\Patryk\Desktop\RS2014\songs.psarc";
            var songsFilePlatform = "Pc"; // "Xbox" or "PS3"
            // end of variables
            // roughly check if the variables are set correctly
            var packer = Path.Combine(toolkitDirectory, "packer.exe");
            var sng2014 = Path.Combine(toolkitDirectory, "sng2014.exe");
            var packageCreator = Path.Combine(toolkitDirectory, "packagecreator.exe");
            var ultrasoulGlyphsFile = "lyrics_ultrasoul.glyphs.xml";
            if (!Directory.Exists(toolkitDirectory) || !File.Exists(packer) || !File.Exists(sng2014) || !File.Exists(packageCreator))
            {
                Console.WriteLine("Incorrect rocksmith toolkit directory or missing crucial files.");
                return;
            }
            if (!File.Exists(songsFilePath))
            {
                Console.WriteLine("Songs file doesn't exist.");
                return;
            }
            if (!File.Exists(rs2014SongsPsarcPath))
            {
                Console.WriteLine("Rocksmith 2014 songs.psarc file doesn't exist at the specified path.");
                return;
            }

            // Get directories before unpacking
            var directoriesBeforeUnpack = new HashSet<string>(Directory.GetDirectories(Directory.GetCurrentDirectory()));

            Console.WriteLine($"Unpacking the compatibility songs file ('{songsFilePath}'), it may take a couple of minutes...");
            Run(packer, $"-u -i \"{songsFilePath}\" -o \".\" -v RS2014 -f {songsFilePlatform}");
            Console.WriteLine();

            // Get directories after unpacking
            var directoriesAfterUnpack = new HashSet<string>(Directory.GetDirectories(Directory.GetCurrentDirectory()));

            // Find the newly created directory for the compatibility pack
            var newMetadataDirs = new HashSet<string>(directoriesAfterUnpack);
            newMetadataDirs.ExceptWith(directoriesBeforeUnpack);

            string extractedMetadataDirectory = null;
            if (newMetadataDirs.Count == 1)
            {
                extractedMetadataDirectory = newMetadataDirs.Single();
            }
            else
            {
                // Fallback: If dynamic detection failed, try naming patterns
                var fileName = Path.GetFileNameWithoutExtension(songsFilePath);
                var patterns = new List<string> {
                    $"{fileName}_RS2014_{songsFilePlatform}",
                    $"{Path.GetFileName(songsFilePath).Replace('.', '_')}_RS2014_{songsFilePlatform}"
                };

                // Handle cases where platform suffix (like _p) is stripped
                if (fileName.Length > 2 && fileName[fileName.Length - 2] == '_')
                    patterns.Insert(0, $"{fileName.Substring(0, fileName.Length - 2)}_RS2014_{songsFilePlatform}");

                extractedMetadataDirectory = patterns.FirstOrDefault(p => Directory.Exists(p));
            }

            if (string.IsNullOrEmpty(extractedMetadataDirectory))
            {
                Console.WriteLine($"Could not find the extracted metadata directory for '{songsFilePath}'.");
                return;
            }

            var directoriesBeforeAudioUnpack = new HashSet<string>(Directory.GetDirectories(Directory.GetCurrentDirectory()));
            Console.WriteLine($"Unpacking the Rocksmith 2014 songs file ('{rs2014SongsPsarcPath}'), this will also take some time...");
            Run(packer, $"-u -i \"{rs2014SongsPsarcPath}\" -o \".\" -v RS2014 -f {songsFilePlatform}");
            Console.WriteLine();

            var directoriesAfterAudioUnpack = new HashSet<string>(Directory.GetDirectories(Directory.GetCurrentDirectory()));
            var newAudioDirs = new HashSet<string>(directoriesAfterAudioUnpack);
            newAudioDirs.ExceptWith(directoriesBeforeAudioUnpack);

            string extractedAudioDirectory = null;
            if (newAudioDirs.Count == 1)
            {
                extractedAudioDirectory = newAudioDirs.Single();
            }
            else
            {
                var fileName = Path.GetFileNameWithoutExtension(rs2014SongsPsarcPath);
                var patterns = new[] {
                    $"{Path.GetFileName(rs2014SongsPsarcPath).Replace('.', '_')}_RS2014_{songsFilePlatform}",
                    $"{fileName}_RS2014_{songsFilePlatform}"
                };
                extractedAudioDirectory = patterns.FirstOrDefault(p => Directory.Exists(p));
            }

            if (string.IsNullOrEmpty(extractedAudioDirectory))
            {
                Console.WriteLine($"Could not find the extracted audio directory for '{rs2014SongsPsarcPath}'.");
                return;
            }

            // sadly, unpacking fails on ultrasoul's jvocals arrangement (I assume it's not discovered as vocals)
            // and consequently for all subsequent arrangements their respective xml files are missing and therefore:
            // removing the faulty file... (This file is in the metadata directory)
            var faultyFile = Path.Combine(extractedMetadataDirectory, "songs", "arr", "ultrasoul_jvocals.sng.xml");
            if (File.Exists(faultyFile))
                File.Delete(faultyFile);

            // ...and regenerating all xml files manually.
            // We search recursively in bin and detect the manifest folder to support different PSARC structures.
            Console.WriteLine("Regenerating all xml files, it may also take some time...");

            // Dynamically find the directory containing manifest JSON files (in the metadata directory)
            var firstManifestFile = Directory.GetFiles(extractedMetadataDirectory, "*.json", SearchOption.AllDirectories).FirstOrDefault();
            if (firstManifestFile == null)
            {
                Console.WriteLine("Could not find any manifest (.json) files in the extracted directory.");
                return;
            }
            var manifestsDirectory = Path.GetDirectoryName(firstManifestFile);

            foreach (var arrangementSng in Directory.GetFiles(extractedMetadataDirectory, "*.sng", SearchOption.AllDirectories))
            {
                var arrangementName = Path.GetFileNameWithoutExtension(arrangementSng);
                var arrangementType = "Guitar";
                if (arrangementName.EndsWith("_bass", StringComparison.InvariantCultureIgnoreCase))
                    arrangementType = "Bass";
                else if (arrangementName.EndsWith("_vocals", StringComparison.InvariantCultureIgnoreCase)
                    || arrangementName.EndsWith("_jvocals", StringComparison.InvariantCultureIgnoreCase))
                    arrangementType = "Vocal";

                Console.Write($"{arrangementName}: ");
                var manifest = Path.Combine(manifestsDirectory, $"{arrangementName}.json");
                Run(sng2014, $"-x -i \"{arrangementSng}\" -m \"{manifest}\" -a {arrangementType}");

                // move the generated xml file
                var arrangement = Path.Combine(Path.GetDirectoryName(arrangementSng), $"{arrangementName}.xml"); // This is where sng2014 outputs the XML
                var arrangementTargetDir = Path.Combine(extractedMetadataDirectory, "songs", "arr");
                Directory.CreateDirectory(arrangementTargetDir);
                File.Move(arrangement, Path.Combine(arrangementTargetDir, Path.GetFileName(arrangement)), true);
            }
            Console.WriteLine();

            // random for generating new arrangement IDs
            // using fixed seed so that everyone running the code ends with the same IDs
            var random = new Random(549799200);
            var outputDirectory = "temp";
            var processedDirectory = "processed";

            if (Directory.Exists(processedDirectory))
                Directory.Delete(processedDirectory, true);

            // Find the HSAN file in the metadata manifests directory
            var hsanSourceFile = Directory.GetFiles(manifestsDirectory, "*.hsan").FirstOrDefault();
            if (hsanSourceFile == null)
            {
                Console.WriteLine("Warning: No .hsan file found. Song list might be incomplete.");
            }

            var nsongsDirectory = Directory.GetDirectories(extractedMetadataDirectory, "nsongs", SearchOption.AllDirectories).FirstOrDefault();
            if (nsongsDirectory == null)
            {
                Console.WriteLine("Could not find 'nsongs' directory inside the extracted files.");
                return;
            }

            var xblockFiles = Directory.GetFiles(nsongsDirectory, "*")
                .OrderBy(f => f)
                .ToArray();

            foreach (var xblockFile in xblockFiles)
            {
                var originalSongKey = Path.GetFileNameWithoutExtension(xblockFile);

                // Ignore songs with missing structures (usually RS1 previews)
                if (originalSongKey.EndsWith("_fcp_disk", StringComparison.OrdinalIgnoreCase))
                {
                    Console.WriteLine($"Skipping {originalSongKey} (RS1 disk file)");
                    continue;
                }

                // Normalize song key (strip RS1 compatibility suffixes if present)
                var songKey = originalSongKey.Replace("_fcp_dlc", "", StringComparison.OrdinalIgnoreCase);

                Console.WriteLine($"Preparing files for {songKey}...");

                // must be different from songKey, otherwise the game will freeze on play!
                var songDirectory = $"{songKey}dlc";
                var workingDirectory = Path.Combine(outputDirectory, songDirectory);

                // Recreate the internal PSARC directory structure
                var audioDest = Path.Combine(workingDirectory, "audio", "windows");
                var xblockDest = Path.Combine(workingDirectory, "gamexblocks", "nsongs");
                var albumArtDest = Path.Combine(workingDirectory, "gfxassets", "album_art");
                var arrDest = Path.Combine(workingDirectory, "songs", "arr");
                var binDest = Path.Combine(workingDirectory, "songs", "bin", "generic");
                var manifestDest = Path.Combine(workingDirectory, "manifests", "songs");

                // audio files selection
                var bnkFiles = Directory.GetFiles(extractedAudioDirectory, "*.bnk", SearchOption.AllDirectories)
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals($"song_{songKey}", StringComparison.OrdinalIgnoreCase) ||
                               name.Equals($"song_{songKey}_preview", StringComparison.OrdinalIgnoreCase);
                    }).ToList();

                if (bnkFiles.Count == 0)
                {
                    Console.WriteLine($"  Skipping {songKey}: No audio files (.bnk) found. Rocksmith Toolkit requires audio to build a package.");
                    continue;
                }

                // work on an empty workspace; delete if anything is here
                if (Directory.Exists(workingDirectory))
                    Directory.Delete(workingDirectory, true);
                Directory.CreateDirectory(workingDirectory);
                Directory.CreateDirectory(audioDest);
                Directory.CreateDirectory(xblockDest);
                Directory.CreateDirectory(albumArtDest);
                Directory.CreateDirectory(arrDest);
                Directory.CreateDirectory(binDest);
                Directory.CreateDirectory(manifestDest);

                // Add required root files for the toolkit
                File.WriteAllText(Path.Combine(workingDirectory, "appid.appid"), "221680");

                // Copy flatmodels if they exist
                var flatmodelsSource = Path.Combine(extractedMetadataDirectory, "flatmodels");
                if (Directory.Exists(flatmodelsSource))
                {
                    CopyDirectory(flatmodelsSource, Path.Combine(workingDirectory, "flatmodels"));
                }

                // Copy the xblock file
                File.Copy(xblockFile, Path.Combine(xblockDest, Path.GetFileName(xblockFile)));
                foreach (var bnk in bnkFiles) File.Copy(bnk, Path.Combine(audioDest, Path.GetFileName(bnk)));

                var wemFiles = bnkFiles.SelectMany(f =>
                {
                    var results = new List<string>();
                    var bnkName = Path.GetFileNameWithoutExtension(f);

                    // 1. Check for WEM with same name as BNK (common in RS1 compatibility)
                    var namedWem = Directory.GetFiles(extractedAudioDirectory, $"{bnkName}.wem", SearchOption.AllDirectories).FirstOrDefault();
                    if (namedWem != null) results.Add(namedWem);

                    // 2. Check for WEM by ID inside BNK
                    var buffer = new byte[8];
                    using (var stream = File.Open(f, FileMode.Open, FileAccess.Read))
                    {
                        // well, it's more complex than this in general, but it works for songs.psarc...
                        if (stream.Length < 52) return results;
                        stream.Seek(44, SeekOrigin.Begin);
                        stream.Read(buffer, 0, 8);
                        var span = buffer.AsSpan(0, 8);
                        var wemNumber = BitConverter.ToInt64(span);

                        // Search for the WEM file recursively as it might not be in /audio/windows/
                        var idWem = Directory.GetFiles(extractedAudioDirectory, $"{wemNumber}.wem", SearchOption.AllDirectories).FirstOrDefault();
                        if (idWem != null) results.Add(idWem);
                    }
                    return results;
                }).Distinct().ToList();
                foreach (var wem in wemFiles) File.Copy(wem, Path.Combine(audioDest, Path.GetFileName(wem)));

                // album art
                var albumArtSongKey = songKey;

                // the Rocksmith training songs don't have their album art, so reusing the rocksmith intro's
                if (songKey == "rs2arpeggios" || songKey == "rs2chordnamestress" || songKey == "rs2levelbreak" || songKey == "rs2tails")
                    albumArtSongKey = "rocksmithintro";

                var ddsFiles = Directory.GetFiles(extractedMetadataDirectory, "*.dds", SearchOption.AllDirectories)
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals($"album_{albumArtSongKey}", StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"album_{albumArtSongKey}_", StringComparison.OrdinalIgnoreCase);
                    });
                foreach (var dds in ddsFiles) File.Copy(dds, Path.Combine(albumArtDest, Path.GetFileName(dds)));

                // arrangements (xml)
                var arrFiles = Directory.GetFiles(Path.Combine(extractedMetadataDirectory, "songs", "arr"), "*")
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f).Replace(".sng", "");
                        return name.Equals(songKey, StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"{songKey}_", StringComparison.OrdinalIgnoreCase);
                    });
                foreach (var arr in arrFiles) File.Copy(arr, Path.Combine(arrDest, Path.GetFileName(arr)));

                // arrangements (sng - crucial for the toolkit to avoid rebuilding everything)
                var sngFiles = Directory.GetFiles(extractedMetadataDirectory, "*.sng", SearchOption.AllDirectories)
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals(songKey, StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"{songKey}_", StringComparison.OrdinalIgnoreCase);
                    });
                foreach (var sng in sngFiles) File.Copy(sng, Path.Combine(binDest, Path.GetFileName(sng)));

                // manifests (json)
                var manifests = Directory.GetFiles(manifestsDirectory, "*.json")
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals(songKey, StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"{songKey}_", StringComparison.OrdinalIgnoreCase);
                    });
                foreach (var manifest in manifests) File.Copy(manifest, Path.Combine(manifestDest, Path.GetFileName(manifest)));

                // lyrics assets
                if (songKey == "ultrasoul")
                {
                    var lyricsDir = Path.Combine(extractedMetadataDirectory, "assets", "ui", "lyrics", songKey);
                    if (Directory.Exists(lyricsDir))
                    {
                        var lyricsDest = Path.Combine(workingDirectory, "assets", "ui", "lyrics", songKey);
                        Directory.CreateDirectory(lyricsDest);
                        foreach (var file in Directory.GetFiles(lyricsDir))
                            File.Copy(file, Path.Combine(lyricsDest, Path.GetFileName(file)));

                        if (File.Exists(ultrasoulGlyphsFile))
                            File.Copy(ultrasoulGlyphsFile, Path.Combine(lyricsDest, Path.GetFileName(ultrasoulGlyphsFile)));
                        else
                            Console.WriteLine("Warning: lyrics_ultrasoul.glyphs.xml is missing.");
                    }
                }

                // modify arrangement IDs (otherwise the game will not even discover the songs!)
                var movedManifests = Directory.GetFiles(manifestDest, "*.json");
                var idMapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

                foreach (var manifest in movedManifests)
                {
                    var content = JObject.Parse(File.ReadAllText(manifest));
                    var entries = content["Entries"] as JObject;
                    if (entries == null || entries.Count == 0) continue;

                    var entry = entries.Properties().First();
                    var oldID = entry.Name;
                    var newArrangementID = random.NextGuid().ToString("N").ToUpper();

                    idMapping[oldID] = newArrangementID;

                    var value = entry.Value;
                    value["Attributes"]["PersistentID"] = newArrangementID;

                    entries.Remove(oldID);
                    entries.Add(newArrangementID, value);

                    File.WriteAllText(manifest, content.ToString());
                }

                // Filter and update the HSAN file to match the new manifest IDs
                if (hsanSourceFile != null)
                {
                    var hsan = JObject.Parse(File.ReadAllText(hsanSourceFile));
                    var entries = hsan["Entries"] as JObject;
                    if (entries != null)
                    {
                        var keysToRemove = entries.Properties()
                            .Where(p => !((string)p.Value["Attributes"]["SongKey"]).Equals(songKey, StringComparison.OrdinalIgnoreCase))
                            .Select(p => p.Name).ToList();
                        foreach (var key in keysToRemove) entries.Remove(key);

                        foreach (var mapping in idMapping)
                        {
                            if (entries[mapping.Key] != null)
                            {
                                var val = entries[mapping.Key];
                                val["Attributes"]["PersistentID"] = mapping.Value;
                                entries.Remove(mapping.Key);
                                entries.Add(mapping.Value, val);
                            }
                        }
                    }
                    File.WriteAllText(Path.Combine(manifestDest, "songs.hsan"), hsan.ToString());
                }

                // Small delay to ensure Windows has flushed all file handles and 
                // directory structures before the toolkit starts scanning.
                System.Threading.Thread.Sleep(500);

                // Build the package for this specific song to avoid OutOfMemory errors
                // Isolated process execution ensures memory is released after each song
                Console.WriteLine($"Building package for {songKey}...");
                Run(packageCreator, outputDirectory);

                // Move the generated psarc file up to the root
                foreach (var file in Directory.GetFiles(outputDirectory, "*.psarc"))
                {
                    File.Move(file, Path.GetFileName(file), true);
                }

                // Move the folder out of 'temp' so the toolkit doesn't re-scan it in the next loop,
                // but keep it in 'processed' so you can still inspect the files.
                Directory.CreateDirectory(processedDirectory);
                Directory.Move(workingDirectory, Path.Combine(processedDirectory, Path.GetFileName(workingDirectory)));
            }
            Console.WriteLine();
            Console.WriteLine("Done!");
            Console.WriteLine();

            // cleanup
            if (Directory.Exists(outputDirectory))
                Directory.Delete(outputDirectory, true);

            // Optionally clean up metadata and audio extraction to save space
            Directory.Delete(extractedMetadataDirectory, true); // Clean up metadata directory
            Directory.Delete(extractedAudioDirectory, true); // Clean up audio directory

            // credits
            Console.WriteLine("Enjoy!");
            Console.WriteLine("- Patryk marchewek87");
            Console.WriteLine();
        }

        private static void Run(string program, string arguments)
        {
            var programProcess = Process.Start(program, arguments);
            programProcess.WaitForExit();

            // none of the programs ever failed for me, but just in case...
            if (programProcess.ExitCode != 0)
            {
                throw new Exception($"Something went wrong when running: {Path.GetFileName(program)} {arguments}");
            }
        }

        private static Guid NextGuid(this Random @this)
        {
            Span<byte> bytes = stackalloc byte[16];
            @this.NextBytes(bytes);
            return new Guid(bytes);
        }

        private static void CopyDirectory(string sourceDir, string destinationDir)
        {
            var dir = new DirectoryInfo(sourceDir);
            if (!dir.Exists) return;

            Directory.CreateDirectory(destinationDir);

            foreach (FileInfo file in dir.GetFiles())
            {
                file.CopyTo(Path.Combine(destinationDir, file.Name), true);
            }

            foreach (DirectoryInfo subDir in dir.GetDirectories())
            {
                CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
            }
        }
    }
}
On 6/1/2026 at 6:19 PM, AnderPants said:

So, if you would care to take things one step further:

1: With the modified code above, one can extract all the Rocksmith2014 original songs as separate .psarc files from the original game's songs.psarc (about 960Mb)

2: Also, it will also extract all the Rocksmith2012 original songs as seperate .psarc files from the compatibility pack's rs1compatibilitydisc_p.psarc (about 222Mb)

What it cannot do, however, is to extract all the Rocksmith2012 DLC songs as seperate .psarc from the compatability pack's rs1compatibilitydlc_p.psarc (about 57Mb). The reason is because they put all the DLC's audio files within the 960Mb songs.psarc file. That's one of the main reasons that the DLC doesn't work with the Learn & Play edition - it only has a 3Mb songs.psarc file!

So, if you're interested in extracting all the Rocksmith2012 DLC songs as seperate .psarc from the compatability pack's rs1compatibilitydlc_p.psarc file, you need to modify Patryk's code further still -> so that it gets BOTH the 960Mb songs.psarc and the 57Mb rs1compatibilitydlc_p.psarc pointed to it. It can then use the metadata from the small file and the audio files from the large file, and using the Rocksmith SongCreator Toolkit, it will automate building all the data together in seperate .psarc files for each son contained within the Rocksmith2012's DLC pack.

Think of it as having a document in a .zip file that references another document in a different .zip file -> both need to be unzipped before the reference can ever be fulfilled...

The only downside of this version below, is that you need to press any key... ath the end between packings. The reason is that it breaks the tool if it's all done at once - this was the easiest workaround. All the songs came through except 20th century boy...

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;

namespace RSRenamer
{
    internal static class Program
    {
        static void Main()
        {
            // set the variables
            var toolkitDirectory = @"D:\ROCKSMITH TOOLKIT\RocksmithToolkit"; // Path to your Rocksmith Toolkit installation
            var rs2014SongsPsarcPath = @"songs.psarc"; // Path to your Rocksmith 2014 songs.psarc file
            var songsFilePath = @"rs1compatibilitydlc_p.psarc";  // var songsFilePath = @"rs1compatibilitydisc_p.psarc"; // this is the other file it will now work on
            //var songsFilePath = @"C:\Users\Patryk\Desktop\RS2014\songs.psarc";
            var songsFilePlatform = "Pc"; // "Xbox" or "PS3"
            // end of variables
            // roughly check if the variables are set correctly
            var packer = Path.Combine(toolkitDirectory, "packer.exe");
            var sng2014 = Path.Combine(toolkitDirectory, "sng2014.exe");
            var packageCreator = Path.Combine(toolkitDirectory, "packagecreator.exe");
            var ultrasoulGlyphsFile = "lyrics_ultrasoul.glyphs.xml";
            if (!Directory.Exists(toolkitDirectory) || !File.Exists(packer) || !File.Exists(sng2014) || !File.Exists(packageCreator))
            {
                Console.WriteLine("Incorrect rocksmith toolkit directory or missing crucial files.");
                return;
            }
            if (!File.Exists(songsFilePath))
            {
                Console.WriteLine("Songs file doesn't exist.");
                return;
            }
            if (!File.Exists(rs2014SongsPsarcPath))
            {
                Console.WriteLine("Rocksmith 2014 songs.psarc file doesn't exist at the specified path.");
                return;
            }

            // Get directories before unpacking
            var directoriesBeforeUnpack = new HashSet<string>(Directory.GetDirectories(Directory.GetCurrentDirectory()));

            Console.WriteLine($"Unpacking the compatibility songs file ('{songsFilePath}'), it may take a couple of minutes...");
            Run(packer, $"-u -i \"{songsFilePath}\" -o \".\" -v RS2014 -f {songsFilePlatform}");
            Console.WriteLine();

            // Get directories after unpacking
            var directoriesAfterUnpack = new HashSet<string>(Directory.GetDirectories(Directory.GetCurrentDirectory()));

            // Find the newly created directory for the compatibility pack
            var newMetadataDirs = new HashSet<string>(directoriesAfterUnpack);
            newMetadataDirs.ExceptWith(directoriesBeforeUnpack);

            string extractedMetadataDirectory = null;
            if (newMetadataDirs.Count == 1)
            {
                extractedMetadataDirectory = newMetadataDirs.Single();
            }
            else
            {
                // Fallback: If dynamic detection failed, try naming patterns
                var fileName = Path.GetFileNameWithoutExtension(songsFilePath);
                var patterns = new List<string> {
                    $"{fileName}_RS2014_{songsFilePlatform}",
                    $"{Path.GetFileName(songsFilePath).Replace('.', '_')}_RS2014_{songsFilePlatform}"
                };

                // Handle cases where platform suffix (like _p) is stripped
                if (fileName.Length > 2 && fileName[fileName.Length - 2] == '_')
                    patterns.Insert(0, $"{fileName.Substring(0, fileName.Length - 2)}_RS2014_{songsFilePlatform}");

                extractedMetadataDirectory = patterns.FirstOrDefault(p => Directory.Exists(p));
            }

            if (string.IsNullOrEmpty(extractedMetadataDirectory))
            {
                Console.WriteLine($"Could not find the extracted metadata directory for '{songsFilePath}'.");
                return;
            }

            var directoriesBeforeAudioUnpack = new HashSet<string>(Directory.GetDirectories(Directory.GetCurrentDirectory()));
            Console.WriteLine($"Unpacking the Rocksmith 2014 songs file ('{rs2014SongsPsarcPath}'), this will also take some time...");
            Run(packer, $"-u -i \"{rs2014SongsPsarcPath}\" -o \".\" -v RS2014 -f {songsFilePlatform}");
            Console.WriteLine();

            var directoriesAfterAudioUnpack = new HashSet<string>(Directory.GetDirectories(Directory.GetCurrentDirectory()));
            var newAudioDirs = new HashSet<string>(directoriesAfterAudioUnpack);
            newAudioDirs.ExceptWith(directoriesBeforeAudioUnpack);

            string extractedAudioDirectory = null;
            if (newAudioDirs.Count == 1)
            {
                extractedAudioDirectory = newAudioDirs.Single();
            }
            else
            {
                var fileName = Path.GetFileNameWithoutExtension(rs2014SongsPsarcPath);
                var patterns = new[] {
                    $"{Path.GetFileName(rs2014SongsPsarcPath).Replace('.', '_')}_RS2014_{songsFilePlatform}",
                    $"{fileName}_RS2014_{songsFilePlatform}"
                };
                extractedAudioDirectory = patterns.FirstOrDefault(p => Directory.Exists(p));
            }

            if (string.IsNullOrEmpty(extractedAudioDirectory))
            {
                Console.WriteLine($"Could not find the extracted audio directory for '{rs2014SongsPsarcPath}'.");
                return;
            }

            // sadly, unpacking fails on ultrasoul's jvocals arrangement (I assume it's not discovered as vocals)
            // and consequently for all subsequent arrangements their respective xml files are missing and therefore:
            // removing the faulty file... (This file is in the metadata directory)
            var faultyFile = Path.Combine(extractedMetadataDirectory, "songs", "arr", "ultrasoul_jvocals.sng.xml");
            if (File.Exists(faultyFile))
                File.Delete(faultyFile);

            // ...and regenerating all xml files manually.
            // We search recursively in bin and detect the manifest folder to support different PSARC structures.
            Console.WriteLine("Regenerating all xml files, it may also take some time...");

            // Dynamically find the directory containing manifest JSON files (in the metadata directory)
            var firstManifestFile = Directory.GetFiles(extractedMetadataDirectory, "*.json", SearchOption.AllDirectories).FirstOrDefault();
            if (firstManifestFile == null)
            {
                Console.WriteLine("Could not find any manifest (.json) files in the extracted directory.");
                return;
            }
            var manifestsDirectory = Path.GetDirectoryName(firstManifestFile);

            foreach (var arrangementSng in Directory.GetFiles(extractedMetadataDirectory, "*.sng", SearchOption.AllDirectories))
            {
                var arrangementName = Path.GetFileNameWithoutExtension(arrangementSng);
                var arrangementType = "Guitar";
                if (arrangementName.EndsWith("_bass", StringComparison.InvariantCultureIgnoreCase))
                    arrangementType = "Bass";
                else if (arrangementName.EndsWith("_vocals", StringComparison.InvariantCultureIgnoreCase)
                    || arrangementName.EndsWith("_jvocals", StringComparison.InvariantCultureIgnoreCase))
                    arrangementType = "Vocal";

                Console.Write($"{arrangementName}: ");
                var manifest = Path.Combine(manifestsDirectory, $"{arrangementName}.json");
                Run(sng2014, $"-x -i \"{arrangementSng}\" -m \"{manifest}\" -a {arrangementType}");

                // move the generated xml file
                var arrangement = Path.Combine(Path.GetDirectoryName(arrangementSng), $"{arrangementName}.xml"); // This is where sng2014 outputs the XML
                var arrangementTargetDir = Path.Combine(extractedMetadataDirectory, "songs", "arr");
                Directory.CreateDirectory(arrangementTargetDir);
                File.Move(arrangement, Path.Combine(arrangementTargetDir, Path.GetFileName(arrangement)), true);
            }
            Console.WriteLine();

            // random for generating new arrangement IDs
            // using fixed seed so that everyone running the code ends with the same IDs
            var random = new Random(549799200);
            var outputDirectory = "temp";
            var processedDirectory = "processed";

            if (Directory.Exists(processedDirectory))
                Directory.Delete(processedDirectory, true);

            // Find the HSAN file in the metadata manifests directory
            var hsanSourceFile = Directory.GetFiles(manifestsDirectory, "*.hsan").FirstOrDefault();
            if (hsanSourceFile == null)
            {
                Console.WriteLine("Warning: No .hsan file found. Song list might be incomplete.");
            }

            var nsongsDirectory = Directory.GetDirectories(extractedMetadataDirectory, "nsongs", SearchOption.AllDirectories).FirstOrDefault();
            if (nsongsDirectory == null)
            {
                Console.WriteLine("Could not find 'nsongs' directory inside the extracted files.");
                return;
            }

            var xblockFiles = Directory.GetFiles(nsongsDirectory, "*")
                .OrderBy(f => f)
                .ToArray();

            foreach (var xblockFile in xblockFiles)
            {
                var originalSongKey = Path.GetFileNameWithoutExtension(xblockFile);

                // Ignore songs with missing structures (usually RS1 previews)
                if (originalSongKey.EndsWith("_fcp_disk", StringComparison.OrdinalIgnoreCase))
                {
                    Console.WriteLine($"Skipping {originalSongKey} (RS1 disk file)");
                    continue;
                }

                // Normalize song key (strip RS1 compatibility suffixes if present)
                var songKey = originalSongKey.Replace("_fcp_dlc", "", StringComparison.OrdinalIgnoreCase);

                Console.WriteLine($"Preparing files for {songKey}...");

                // must be different from songKey, otherwise the game will freeze on play!
                var songDirectory = $"{songKey}dlc";
                var workingDirectory = Path.Combine(outputDirectory, songDirectory);

                // Recreate the internal PSARC directory structure
                var audioDest = Path.Combine(workingDirectory, "audio", "windows");
                var xblockDest = Path.Combine(workingDirectory, "gamexblocks", "nsongs");
                var albumArtDest = Path.Combine(workingDirectory, "gfxassets", "album_art");
                var arrDest = Path.Combine(workingDirectory, "songs", "arr");
                var binDest = Path.Combine(workingDirectory, "songs", "bin", "generic");
                var manifestDest = Path.Combine(workingDirectory, "manifests", "songs");

                // audio files selection
                var bnkFiles = Directory.GetFiles(extractedAudioDirectory, "*.bnk", SearchOption.AllDirectories)
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals($"song_{songKey}", StringComparison.OrdinalIgnoreCase) ||
                               name.Equals($"song_{songKey}_preview", StringComparison.OrdinalIgnoreCase);
                    }).ToList();

                if (bnkFiles.Count == 0)
                {
                    Console.WriteLine($"  Skipping {songKey}: No audio files (.bnk) found. Rocksmith Toolkit requires audio to build a package.");
                    continue;
                }

                // work on an empty workspace; delete if anything is here
                if (Directory.Exists(workingDirectory))
                    Directory.Delete(workingDirectory, true);
                Directory.CreateDirectory(workingDirectory);
                Directory.CreateDirectory(audioDest);
                Directory.CreateDirectory(xblockDest);
                Directory.CreateDirectory(albumArtDest);
                Directory.CreateDirectory(arrDest);
                Directory.CreateDirectory(binDest);
                Directory.CreateDirectory(manifestDest);

                // Add required root files for the toolkit
                File.WriteAllText(Path.Combine(workingDirectory, "appid.appid"), "221680");

                // Copy flatmodels if they exist
                var flatmodelsSource = Path.Combine(extractedMetadataDirectory, "flatmodels");
                if (Directory.Exists(flatmodelsSource))
                {
                    CopyDirectory(flatmodelsSource, Path.Combine(workingDirectory, "flatmodels"));
                }

                // Copy the xblock file
                File.Copy(xblockFile, Path.Combine(xblockDest, Path.GetFileName(xblockFile)));
                foreach (var bnk in bnkFiles) File.Copy(bnk, Path.Combine(audioDest, Path.GetFileName(bnk)));

                var wemFiles = bnkFiles.SelectMany(f =>
                {
                    var results = new List<string>();
                    var bnkName = Path.GetFileNameWithoutExtension(f);

                    // 1. Check for WEM with same name as BNK (common in RS1 compatibility)
                    var namedWem = Directory.GetFiles(extractedAudioDirectory, $"{bnkName}.wem", SearchOption.AllDirectories).FirstOrDefault();
                    if (namedWem != null) results.Add(namedWem);

                    // 2. Check for WEM by ID inside BNK
                    var buffer = new byte[8];
                    using (var stream = File.Open(f, FileMode.Open, FileAccess.Read))
                    {
                        // well, it's more complex than this in general, but it works for songs.psarc...
                        if (stream.Length < 52) return results;
                        stream.Seek(44, SeekOrigin.Begin);
                        stream.Read(buffer, 0, 8);
                        var span = buffer.AsSpan(0, 8);
                        var wemNumber = BitConverter.ToInt64(span);

                        // Search for the WEM file recursively as it might not be in /audio/windows/
                        var idWem = Directory.GetFiles(extractedAudioDirectory, $"{wemNumber}.wem", SearchOption.AllDirectories).FirstOrDefault();
                        if (idWem != null) results.Add(idWem);
                    }
                    return results;
                }).Distinct().ToList();
                foreach (var wem in wemFiles) File.Copy(wem, Path.Combine(audioDest, Path.GetFileName(wem)));

                // album art
                var albumArtSongKey = songKey;

                // the Rocksmith training songs don't have their album art, so reusing the rocksmith intro's
                if (songKey == "rs2arpeggios" || songKey == "rs2chordnamestress" || songKey == "rs2levelbreak" || songKey == "rs2tails")
                    albumArtSongKey = "rocksmithintro";

                var ddsFiles = Directory.GetFiles(extractedMetadataDirectory, "*.dds", SearchOption.AllDirectories)
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals($"album_{albumArtSongKey}", StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"album_{albumArtSongKey}_", StringComparison.OrdinalIgnoreCase);
                    });
                foreach (var dds in ddsFiles) File.Copy(dds, Path.Combine(albumArtDest, Path.GetFileName(dds)));

                // arrangements (xml)
                var arrFiles = Directory.GetFiles(Path.Combine(extractedMetadataDirectory, "songs", "arr"), "*")
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f).Replace(".sng", "");
                        return name.Equals(songKey, StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"{songKey}_", StringComparison.OrdinalIgnoreCase);
                    });
                foreach (var arr in arrFiles) File.Copy(arr, Path.Combine(arrDest, Path.GetFileName(arr)));

                // arrangements (sng - crucial for the toolkit to avoid rebuilding everything)
                var sngFiles = Directory.GetFiles(extractedMetadataDirectory, "*.sng", SearchOption.AllDirectories)
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals(songKey, StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"{songKey}_", StringComparison.OrdinalIgnoreCase);
                    });
                foreach (var sng in sngFiles) File.Copy(sng, Path.Combine(binDest, Path.GetFileName(sng)));

                // manifests (json)
                var manifests = Directory.GetFiles(manifestsDirectory, "*.json")
                    .Where(f =>
                    {
                        var name = Path.GetFileNameWithoutExtension(f);
                        return name.Equals(songKey, StringComparison.OrdinalIgnoreCase) ||
                               name.StartsWith($"{songKey}_", StringComparison.OrdinalIgnoreCase);
                    });
                foreach (var manifest in manifests) File.Copy(manifest, Path.Combine(manifestDest, Path.GetFileName(manifest)));

                // lyrics assets
                if (songKey == "ultrasoul")
                {
                    var lyricsDir = Path.Combine(extractedMetadataDirectory, "assets", "ui", "lyrics", songKey);
                    if (Directory.Exists(lyricsDir))
                    {
                        var lyricsDest = Path.Combine(workingDirectory, "assets", "ui", "lyrics", songKey);
                        Directory.CreateDirectory(lyricsDest);
                        foreach (var file in Directory.GetFiles(lyricsDir))
                            File.Copy(file, Path.Combine(lyricsDest, Path.GetFileName(file)));

                        if (File.Exists(ultrasoulGlyphsFile))
                            File.Copy(ultrasoulGlyphsFile, Path.Combine(lyricsDest, Path.GetFileName(ultrasoulGlyphsFile)));
                        else
                            Console.WriteLine("Warning: lyrics_ultrasoul.glyphs.xml is missing.");
                    }
                }

                // modify arrangement IDs (otherwise the game will not even discover the songs!)
                var movedManifests = Directory.GetFiles(manifestDest, "*.json");
                var idMapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

                foreach (var manifest in movedManifests)
                {
                    var content = JObject.Parse(File.ReadAllText(manifest));
                    var entries = content["Entries"] as JObject;
                    if (entries == null || entries.Count == 0) continue;

                    var entry = entries.Properties().First();
                    var oldID = entry.Name;
                    var newArrangementID = random.NextGuid().ToString("N").ToUpper();

                    idMapping[oldID] = newArrangementID;

                    var value = entry.Value;
                    value["Attributes"]["PersistentID"] = newArrangementID;

                    entries.Remove(oldID);
                    entries.Add(newArrangementID, value);

                    File.WriteAllText(manifest, content.ToString());
                }

                // Filter and update the HSAN file to match the new manifest IDs
                if (hsanSourceFile != null)
                {
                    var hsan = JObject.Parse(File.ReadAllText(hsanSourceFile));
                    var entries = hsan["Entries"] as JObject;
                    if (entries != null)
                    {
                        var keysToRemove = entries.Properties()
                            .Where(p => !((string)p.Value["Attributes"]["SongKey"]).Equals(songKey, StringComparison.OrdinalIgnoreCase))
                            .Select(p => p.Name).ToList();
                        foreach (var key in keysToRemove) entries.Remove(key);

                        foreach (var mapping in idMapping)
                        {
                            if (entries[mapping.Key] != null)
                            {
                                var val = entries[mapping.Key];
                                val["Attributes"]["PersistentID"] = mapping.Value;
                                entries.Remove(mapping.Key);
                                entries.Add(mapping.Value, val);
                            }
                        }
                    }
                    File.WriteAllText(Path.Combine(manifestDest, "songs.hsan"), hsan.ToString());
                }

                // Small delay to ensure Windows has flushed all file handles and 
                // directory structures before the toolkit starts scanning.
                System.Threading.Thread.Sleep(500);

                // Build the package for this specific song to avoid OutOfMemory errors
                // Isolated process execution ensures memory is released after each song
                Console.WriteLine($"Building package for {songKey}...");
                Run(packageCreator, outputDirectory);

                // Move the generated psarc file up to the root
                foreach (var file in Directory.GetFiles(outputDirectory, "*.psarc"))
                {
                    File.Move(file, Path.GetFileName(file), true);
                }

                // Move the folder out of 'temp' so the toolkit doesn't re-scan it in the next loop,
                // but keep it in 'processed' so you can still inspect the files.
                Directory.CreateDirectory(processedDirectory);
                Directory.Move(workingDirectory, Path.Combine(processedDirectory, Path.GetFileName(workingDirectory)));
            }
            Console.WriteLine();
            Console.WriteLine("Done!");
            Console.WriteLine();

            // cleanup
            if (Directory.Exists(outputDirectory))
                Directory.Delete(outputDirectory, true);

            // Optionally clean up metadata and audio extraction to save space
            Directory.Delete(extractedMetadataDirectory, true); // Clean up metadata directory
            Directory.Delete(extractedAudioDirectory, true); // Clean up audio directory

            // credits
            Console.WriteLine("Enjoy!");
            Console.WriteLine("- Patryk marchewek87");
            Console.WriteLine();
        }

        private static void Run(string program, string arguments)
        {
            var programProcess = Process.Start(program, arguments);
            programProcess.WaitForExit();

            // none of the programs ever failed for me, but just in case...
            if (programProcess.ExitCode != 0)
            {
                throw new Exception($"Something went wrong when running: {Path.GetFileName(program)} {arguments}");
            }
        }

        private static Guid NextGuid(this Random @this)
        {
            Span<byte> bytes = stackalloc byte[16];
            @this.NextBytes(bytes);
            return new Guid(bytes);
        }

        private static void CopyDirectory(string sourceDir, string destinationDir)
        {
            var dir = new DirectoryInfo(sourceDir);
            if (!dir.Exists) return;

            Directory.CreateDirectory(destinationDir);

            foreach (FileInfo file in dir.GetFiles())
            {
                file.CopyTo(Path.Combine(destinationDir, file.Name), true);
            }

            foreach (DirectoryInfo subDir in dir.GetDirectories())
            {
                CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
            }
        }
    }
}

I am so glad for this initiative, but I'm getting this error, right at Pumped Up Kicks:

System.Exception: 'Something went wrong when running: sng2014.exe -x -i "rs1compatibilitydlc_RS2014_Pc\songs\bin\generic\pumpedupkicks_combo.sng" -m "rs1compatibilitydlc_RS2014_Pc\manifests\songs_rs1dlc\pumpedupkicks_combo.json" -a Guitar'

Eccezione non gestita: Newtonsoft.Json.JsonSerializationException: Error converting value {null} to type 'System.Int32'. Path 'Entries.C7A72C01B0BF25D683906F6BF171040A.Attributes.LeaderboardChallengeRating', line 602, position 40. ---> System.InvalidCastException: Impossibile convertire l'oggetto null in un tipo di valore.
in System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType)
--- Fine della traccia dello stack dell'eccezione interna ---
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateDictionary(IDictionary dictionary, JsonReader reader, JsonDictionaryContract contract, JsonProperty containerProperty, String id)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateDictionary(IDictionary dictionary, JsonReader reader, JsonDictionaryContract contract, JsonProperty containerProperty, String id)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolvePropertyAndCreatorValues(JsonObjectContract contract, JsonProperty containerProperty, JsonReader reader, Type objectType)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ObjectConstructor`1 creator, String id)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
in Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
in Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
in Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
in Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value)
in RocksmithToolkitLib.DLCPackage.Manifest2014.Manifest2014`1.LoadFromFile(String manifestRS2014FilePath)
in sng2014.Program.Main(String[] args)

Can the code be fixed? Thanks a lot in advance!

  • 2 weeks later...
  • Author

Hi there @Noiritsole@strings420@AnderPants! Thanks for your kind words and I'm glad it worked for you!

I know the solution is somewhat technical - as I mentioned my initial goal was simply to solve the problem for myself rather than to create a user-friendly conversion tool for everyone 😅 only once I solved it I thought to clean the code a bit (believe me, it was a complete mess with dozens of commented lines, different variants, etc.) and share it with the community 😊 if I had more spare time, maybe I'd aim for making a tool... So I am aware it required some work on your end and I'm glad it worked for you too!

I don't remember if I warned about it in the original post - the source code is really C-like, no object oriented paradigm here (it is a proof-of-concept-, not production-grade) so by any chance please don't treat it as a C# programming lesson 😅

A complaint one could have is that since the code is basically calling other exes and moving files around with only some slight file modifications, then why isn't it a powershell script? As I mentioned, I initially started with DLC Builder and using its libraries in C# (way easier in C# than in powershell) and so I stayed in C# till the end. Surely, now that RS Toolikt is used instead, powershell might be a better pick if one was to create a conversion tool.

On 5/31/2026 at 2:52 PM, AnderPants said:

but it didn't work on rs1compatibilitydisc_p.psarc

Indeed, it was not in my scope - I only own the PS4 2014 Remastered edition and never owned the original PS3 version, so I wasn't planning on doing it. During the process, however, I did notice that there are some files in the songs.psarc that lack some other files to complete repacking and I assumed they must be from the original version, but I simply filtered them out 😅

I'd assume that if you unpacked the rs1compatibilitydisc_p.psarc to the same directory as songs.psarc and removed that one line, it could work just fine (worst case some renaming would be required) ☺️

.Where(s => !s.EndsWith("_fcp_disk", StringComparison.InvariantCultureIgnoreCase)) // skip those with missing files (RS1 previews?)
  • Author
On 6/6/2026 at 4:47 PM, Nexodon said:

Can the code be fixed? Thanks a lot in advance!

Even though it was not in my original scope (so I don't feel any urge to support it 😅), I'll still try to help a bit.

It seems that the toolkit expects a numerical value (Int32 is an integer) where it encounters a missing one (null) under LeaderboardChallengeRating attribute in some json file.

I don't know that exactly fails - in sng2014.Program.Main(String[] args) is not showing the line number, so you might be running your application in Release mode - if you ran it in Debug mode, the code wouldn't be optimized and some more details would have been shown.

You could run the code with F5 (debugging mode) and then you could read some context details to help you find where the problem is.

As for the fix this should probably work: open the file found in the above step, find the LeaderboardChallengeRating somewhere under C7A72C01B0BF25D683906F6BF171040A and either remove it completely or add some number; however, I have no idea what the allowed range is (0? 1? 10?), so personally I would start with its removal - it doesn't sound like a crucial parameter 😛

  • Author

By the way, looking at this post today, just a half a year later, I cannot imagine that I spent all that time to solve that problem 😂 I mean I literally haven't started RS even once since then 😅 not that I don't play, quite the opposite - I started playing with friends and I quickly found out that I have to learn a song (or its simplified version) by memory 😅 I should revisit RS as it holds a special place in my heart, but that's a different story 😉

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

Recently Browsing 0

  • No registered users viewing this page.


Important Information

By using this site, you agree to our Guidelines. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. - Privacy Policy

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.