Steam Library Shortcuts

Source mods under /sourcemods/, when configured correctly, show up on Steam Library after a restart. Source Mods placed in |all_source_engine_paths|, GoldSrc mods or even Source 2 mods don't show up by default.
= Steam shortcuts file =
Any game/mod could by added to the Steam Library by creating a shortcut. The shortcuts file for a given user are located under the directory steam_install_dir\\userdata\\, and the path ends with config. The shortcuts are stored inside this directory, in a file named shortcuts.vdf.
= Shortcut format =
* string AppName
* string Exe
* string StartDir
* string Icon
* string ShortcutPath
* string LaunchOptions
* bool Hidden
* array<string> Tags
= Reading the shortcuts.vdf =
 List<Shortcut> result = new List<Shortcut>();
 int start = shortcutsString.IndexOf("\u0000shortcuts\u0000") + "\u0000shortcuts\u0000".Length;
 int end = shortcutsString.LastIndexOf("\u0008\u0008");
 shortcutsString = shortcutsString.Substring(start, end - start);
 Shortcut shortcut = null;
 string word = "";
 string key = "";
 bool readingTags = false;
 int tagId = -1;
 foreach (char c in shortcutsString.ToCharArray())
 {
 if (c == '\u0000')
 {
 if (word.EndsWith("\u0001appname"))
 {
 if (shortcut != null)
 result.Add(shortcut);
 // New shortcut
 shortcut = new Shortcut();
 key = "\u0001appname";
 }
 else if (
 word == "\u0001exe" ||
 word == "\u0001StartDir" ||
 word == "\u0001icon" ||
 word == "\u0001ShortcutPath" ||
 word == "\u0001LaunchOptions" ||
 word == "\u0002hidden"
 )
 {
 key = word;
 }
 else if (word == "tags")
 {
 readingTags = true;
 }
 else if (key != "")
 {
 switch (key)
 {
 case "\u0001appname":
 shortcut.AppName = word;
 break;
 case "\u0001exe":
 shortcut.Exe = word.Trim('"');
 break;
 case "\u0001StartDir":
 shortcut.StartDir = word.Trim('"');
 break;
 case "\u0001icon":
 shortcut.Icon = word;
 break;
 case "\u0001ShortcutPath":
 shortcut.ShortcutPath = word;
 break;
 case "\u0001LaunchOptions":
 shortcut.LaunchOptions = word;
 break;
 case "\u0002hidden":
 shortcut.Hidden = (word == "\u0001");
 break;
 default:
 break;
 }
 key = "";
 }
 else if (readingTags)
 {
 if (word.StartsWith("\u0001"))
 {
 tagId = int.Parse(word.Substring("\u0001".Length));
 }
 else if (tagId >= 0)
 {
 shortcut.Tags.Add(word);
 tagId = -1;
 }
 else
 {
 readingTags = false;
 }
 }
 word = "";
 }
 else
 {
 word += c;
 }
 }
 if (shortcut != null)
 result.Add(shortcut);
 return result;
= Writing the shortcuts.vdf =
 private static string BuildShortcuts(List<Shortcut> shortcuts)
 {
 string shortcutsString = "\u0000shortcuts\u0000";
 for (int i = 0; i < shortcuts.Count; i++)
 {
 shortcutsString += "\u0000" + i + "\u0000";
 shortcutsString += BuildShortcut(shortcuts[i]);
 shortcutsString += "\u0008";
 }
 shortcutsString += "\u0008\u0008";
 return shortcutsString;
 }
 private static string BuildShortcut(Shortcut shortcut)
 {
 string shortcutString = "";
 //shortcutString += "\u0002appid\u0000" + shortcut.GetAppID() + "\u0000";
 shortcutString += "\u0001appname\u0000" + shortcut.AppName + "\u0000";
 shortcutString += "\u0001exe\u0000\"" + shortcut.Exe + "\"\u0000";
 shortcutString += "\u0001StartDir\u0000\"" + shortcut.StartDir + "\"\u0000";
 shortcutString += "\u0001icon\u0000" + shortcut.Icon + "\u0000";
 shortcutString += "\u0001ShortcutPath\u0000" + shortcut.ShortcutPath + "\u0000";
 shortcutString += "\u0001LaunchOptions\u0000" + shortcut.LaunchOptions + "\u0000";
 shortcutString += "\u0002hidden\u0000" + (shortcut.Hidden ? "\u0001" : "\u0000") + "\u0000\u0000\u0000";
 shortcutString += buildTags(shortcut.Tags);
 return shortcutString;
 }
 private static string buildTags(List<string> tags)
 {
 var tagString = "\u0000tags\u0000";
 for (var i = 0; i < tags.Count; ++i)
 {
 tagString += "\u0001" + i + "\u0000" + tags[i] + "\u0000";
 }
 tagString += "\u0008";
 return tagString;
 }
= Get AppId for a shotcut =
To display an image in the library, the app id of the shortcut must be known. What has been documented so far is that the app id is given by the following (untested) method:
 var longValue = new Long(crcValue, crcValue, true);
 longValue = longValue.or(0x80000000);
 longValue = longValue.shl(32);
 longValue = longValue.or(0x02000000);
 return result.ToString();
 private string GetSHA1()
 {
 var data = Encoding.ASCII.GetBytes(Exe);
 var hashData = new SHA1Managed().ComputeHash(data);
 var hash = string.Empty;
 foreach (var b in hashData)
 {
 hash += b.ToString("X2");
 }
 return hash;
 }