Created
August 13, 2025 17:49
-
-
Save luser/43e68c509b7406c123530e594a4245c9 to your computer and use it in GitHub Desktop.
Very old C# code for reading a binary iTunes Library file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Windows.Forms; | |
| using System.IO; | |
| using System.Text; | |
| namespace iTunesDBBrowser | |
| { | |
| public delegate void UpdateFormDelegate(Song song, long bytes, long totalbytes); | |
| public struct Song | |
| { | |
| public string Title; | |
| public string Artist; | |
| public string Album; | |
| public string Filename; | |
| public void Clear() | |
| { | |
| Title = Artist = Album = Filename = ""; | |
| } | |
| public void SetField(Code c, string s) | |
| { | |
| switch(c) | |
| { | |
| case Code.Title: | |
| Title = s; | |
| break; | |
| case Code.Filename: | |
| Filename = s; | |
| break; | |
| case Code.Artist: | |
| Artist = s; | |
| break; | |
| case Code.Album: | |
| Album = s; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| public enum Code | |
| { | |
| Title = 1, | |
| Filename = 2, | |
| Album = 3, | |
| Artist = 4, | |
| Genre = 5, | |
| Filetype = 6, | |
| Unknown = 7, | |
| Comment = 8, | |
| Unknown2 = 100 | |
| } | |
| public struct fsbbStruct | |
| { | |
| public string pad; // 2 bytes | |
| public string code; // 2 bytes | |
| public uint jump; | |
| public uint myLen; | |
| public uint count; | |
| } | |
| struct stringParam | |
| { | |
| public uint res1; | |
| public uint strlength; | |
| public uint[] res2; // = new uint[2]; | |
| } | |
| struct propertyParam | |
| { | |
| public uint item_id; | |
| public uint[] res1; // = new uint[3] | |
| } | |
| /* | |
| struct playlistParam | |
| { | |
| public uint[] res1; // = new uint[2]; | |
| public uint item_ref; | |
| public ushort[] res2; // = new ushort[2]; | |
| } | |
| */ | |
| /// <summary> | |
| /// Loads an iTunesDB file | |
| /// </summary> | |
| public class iTunesDBLoader | |
| { | |
| private FileInfo file; | |
| private FileStream fs; | |
| private BinaryReader br; | |
| private MainForm mf; | |
| private UpdateFormDelegate ufd; | |
| private string fileBase; | |
| private bool debug = false; | |
| private StreamWriter sw = null; | |
| public iTunesDBLoader(FileInfo file, MainForm mf, UpdateFormDelegate ufd) | |
| { | |
| this.file = file; | |
| this.mf = mf; | |
| this.ufd = ufd; | |
| // determine fileBase | |
| fileBase = file.Directory.Root.ToString(); | |
| } | |
| public bool Debug | |
| { | |
| get | |
| { | |
| return debug; | |
| } | |
| set | |
| { | |
| debug = value; | |
| } | |
| } | |
| private fsbbStruct ReadHeader() | |
| { | |
| fsbbStruct buf; | |
| Encoding ascii = System.Text.Encoding.ASCII; | |
| byte[] bytes; | |
| bytes = br.ReadBytes(2); | |
| buf.pad = ascii.GetString(bytes); | |
| bytes = br.ReadBytes(2); | |
| buf.code = ascii.GetString(bytes); | |
| buf.jump = br.ReadUInt32(); | |
| buf.myLen = br.ReadUInt32(); | |
| buf.count = br.ReadUInt32(); | |
| return buf; | |
| } | |
| private propertyParam ReadPropertyParam() | |
| { | |
| propertyParam pp; | |
| pp.item_id = br.ReadUInt32(); | |
| pp.res1 = new uint[3]; | |
| for(int i=0;i<3;i++) | |
| pp.res1[i] = br.ReadUInt32(); | |
| return pp; | |
| } | |
| private stringParam ReadStringParam() | |
| { | |
| stringParam sp; | |
| sp.res1 = br.ReadUInt32(); | |
| sp.strlength = br.ReadUInt32(); | |
| sp.res2 = new uint[2]; | |
| sp.res2[0] = br.ReadUInt32(); | |
| sp.res2[1] = br.ReadUInt32(); | |
| return sp; | |
| } | |
| private void log(string format, params object[] args) | |
| { | |
| if(debug && sw != null) | |
| sw.WriteLine(format, args); | |
| } | |
| public void Load() | |
| { | |
| long fileSize = file.Length; | |
| long bytesRead = 0; | |
| if(debug) | |
| sw = File.CreateText(Path.Combine(Application.StartupPath,"debug.log")); | |
| using(fs = file.OpenRead()) | |
| { | |
| Encoding utf16 = System.Text.Encoding.GetEncoding("UTF-16"); | |
| br = new BinaryReader(fs, utf16); | |
| Song curSong = new Song(); | |
| uint numSongs = 0, songsLeft = 0, numProps = 0; | |
| while(fs.Position <= fileSize) | |
| { | |
| bytesRead = fs.Position; | |
| fsbbStruct fsbb = ReadHeader(); | |
| if( fsbb.code == "bd" ) // Start code | |
| { | |
| log("{0:x6} Beginning of database", bytesRead); | |
| } | |
| else if( fsbb.code == "sd") // a list starter | |
| { | |
| if( fsbb.count == 1) | |
| log("{0:x6} List of Songs:", bytesRead); | |
| else if( fsbb.count == 2 ) | |
| log("{0:x6} List of Playlists:", bytesRead); | |
| else | |
| log("{0:x6} Unknown {1}:", bytesRead, fsbb.count); | |
| } | |
| else if( fsbb.code == "lt" ) // a list starter | |
| { | |
| log("{0:x6} {1}-item list:", bytesRead, fsbb.myLen); | |
| // number of songs to read | |
| songsLeft = numSongs = fsbb.myLen; | |
| } | |
| else if( fsbb.code == "ip" ) // a playlist item | |
| { | |
| //playlistParam playlist; | |
| //memcpy( &playlist, &contents[filepos+16], 16 ); | |
| //log("{0:x6} itemref ({1}): XXX", bytesRead, playlist.item_ref); | |
| log("{0:x6} playlist item", bytesRead); | |
| } | |
| else if( fsbb.code == "it" ) // a song list item | |
| { | |
| // item_id | |
| propertyParam prop = ReadPropertyParam(); | |
| log("{0:x6} {1}-property item ({2}):", bytesRead, fsbb.count, prop.item_id); | |
| // number of props on this song | |
| numProps = fsbb.count; | |
| curSong.Clear(); | |
| } | |
| else if( fsbb.code == "od" ) // a unicode string, usually | |
| { | |
| stringParam sp; | |
| string str; | |
| log("{0:x6} Unicode String({1}) {2} {3} {4}", bytesRead, fsbb.code, fsbb.jump, fsbb.myLen, fsbb.count ); | |
| sp = ReadStringParam(); | |
| if( fsbb.myLen == 0 ) // Bad something, skip outta here | |
| continue; | |
| if( sp.strlength != 0 ) | |
| { | |
| // ??? | |
| // memcpy( buf, &contents[filepos+40], fsbb.myLen ); | |
| // buf[sizeof(buf)-1] = '\0'; | |
| // printf("\t%s\n", buf ); | |
| } | |
| else | |
| { | |
| // ??? filepos+40 below, only 32 bytes read | |
| // 16 in fsbb, 16 in sp | |
| br.ReadBytes(8); | |
| str = utf16.GetString(br.ReadBytes((int)fsbb.myLen - 40)); | |
| Code c = (Code)fsbb.count; | |
| log("{0}: \"{1}\"", c, str); | |
| if(c == Code.Filename) | |
| { | |
| // strip the leading :, replace : with the right path separator, | |
| // and prepend the root directory | |
| curSong.SetField(c, fileBase + str.Substring(1).Replace(":",Path.DirectorySeparatorChar.ToString())); | |
| } | |
| else | |
| { | |
| curSong.SetField(c, str); | |
| } | |
| numProps--; | |
| if(numProps == 0) // done with this song | |
| { | |
| songsLeft--; | |
| object[] args = new object[3]{curSong, numSongs - songsLeft, numSongs}; | |
| mf.BeginInvoke(ufd, args); | |
| if(songsLeft == 0) // done with all the songs | |
| break; | |
| } | |
| } | |
| fsbb.jump = fsbb.myLen; | |
| } | |
| else | |
| { | |
| log("{0:x6} code:{1} jump:{2} myLen:{3} count:{4}", bytesRead, fsbb.code, fsbb.jump, fsbb.myLen, fsbb.count); | |
| } | |
| if((bytesRead + fsbb.jump) > fs.Position) | |
| { | |
| fs.Seek(bytesRead + fsbb.jump - fs.Position, SeekOrigin.Current); | |
| } | |
| } | |
| } | |
| if(debug && sw != null) | |
| sw.Close(); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment