Skip to content

Instantly share code, notes, and snippets.

@luser
Created August 13, 2025 17:49
Show Gist options
  • Save luser/43e68c509b7406c123530e594a4245c9 to your computer and use it in GitHub Desktop.
Save luser/43e68c509b7406c123530e594a4245c9 to your computer and use it in GitHub Desktop.
Very old C# code for reading a binary iTunes Library file
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