diff --git a/dbxrecover.d b/dbxrecover.d
new file mode 100644
index 0000000..3801f5a
--- /dev/null
+++ b/dbxrecover.d
@@ -0,0 +1,297 @@
+import std.stream;
+import std.stdio;
+import std.c.stdlib;
+import std.system;
+import std.c.string;
+import std.c.time;
+import std.regexp;
+import std.string;
+
+struct Header
+{
+ align(4)
+ {
+ uint id;
+ uint chunksize;
+ uint datasize;
+ uint next;
+ }
+};
+
+struct ChunkInfo
+{
+ uint next;
+ uint datasize;
+ long fileoffset;
+};
+
+class Chunk
+{
+ static bool differ(ChunkInfo info1, ChunkInfo info2, Stream s)
+ {
+ if (memcmp(&info1, &info2, ChunkInfo.sizeof) !=0) return true;
+ if (data(info1, s) != data(info2, s)) return true;
+ return false;
+ }
+
+ static char[] data(ChunkInfo info, Stream s)
+ {
+ long p = s.position();
+ s.seekSet(info.fileoffset);
+ char[] o = new char[info.datasize];
+ o.length = s.readBlock(cast(void*)o, info.datasize);
+ s.seekSet(p);
+ return o;
+ }
+}
+
+class ChainFinder
+{
+ private:
+ Stream s;
+ ChunkInfo[][uint] infos;
+ bool[uint] first;
+
+ public:
+ struct ChunkStatistics
+ {
+ uint count;
+ uint duplicates;
+ uint id_duplicates;
+ };
+ struct ChainStatistics
+ {
+ uint count;
+ uint loops;
+ uint dropped;
+ uint broken;
+ uint progress_total;
+ uint progress_current;
+ };
+
+ ChunkStatistics chunkstats;
+ ChainStatistics chainstats;
+
+ this(Stream s)
+ {
+ this.s = s;
+ }
+
+ void add(uint id, ChunkInfo info)
+ {
+ chunkstats.count++;
+ if (id in infos) {
+ foreach (ChunkInfo info2; infos[id])
+ {
+ if (!Chunk.differ(info, info2, s))
+ {
+ chunkstats.duplicates++;
+ return;
+ }
+ }
+ }
+ else
+ {
+ ChunkInfo[] tmp;
+ infos[id] = tmp;
+ }
+ int len = (infos[id].length = infos[id].length + 1);
+ infos[id][len-1] = info;
+ if (len > 1) chunkstats.id_duplicates++;
+ if (!(id in first)) first[id] = true;
+ first[info.next] = false;
+ }
+
+ int opApply(int delegate(inout Message) dg)
+ {
+ uint[] ids = first.keys;
+ chainstats.progress_total = ids.length+1;
+ chainstats.progress_current = 1;
+
+ foreach(uint id; ids)
+ {
+ if (first[id])
+ {
+ int result = walk(id, dg);
+ if (result) return result;
+ }
+ chainstats.progress_current++;
+ }
+ return 0;
+ }
+
+ int walk(uint id, int delegate(inout Message) dg)
+ {
+ auto m = new Message(s);
+ return walk(id, &infos[id], dg, m, 1);
+ }
+
+ int walk(uint id, ChunkInfo[]* infosp, int delegate(inout Message) dg, Message m, uint combinations)
+ {
+ if (combinations > 16)
+ {
+ chainstats.dropped++;
+ return 0;
+ }
+ foreach(ChunkInfo info; *infosp)
+ {
+ m.push(id, info);
+ bool seen = m.seen(info.next);
+ ChunkInfo[]* nextp = info.next in infos;
+ if (nextp && (!seen))
+ {
+ walk(info.next, nextp, dg, m, combinations * nextp.length);
+ }
+ else
+ {
+ if (seen) chainstats.loops++;
+ if (m.broken()) chainstats.broken++;
+ chainstats.count++;
+ int result = dg(m);
+ if (result) return result;
+ }
+ m.pop(id);
+ }
+ return 0;
+ }
+
+};
+
+class Message
+{
+ private:
+ ChunkInfo infos[];
+ bool[uint] seen_tab;
+ Stream s;
+ public:
+ this(Stream s)
+ {
+ this.s = s;
+ }
+
+ void push(uint id, ChunkInfo info)
+ {
+ infos.length = infos.length + 1;
+ infos[infos.length-1] = info;
+ seen_tab[id] = true;
+ }
+
+ void pop(uint id)
+ {
+ seen_tab.remove(id);
+ infos.length = infos.length - 1;
+ }
+
+ bool seen(uint id)
+ {
+ return (id in seen_tab != null);
+ }
+
+ bool broken()
+ {
+ return (infos[infos.length-1].next != 0);
+ }
+
+ int opApply(int delegate(inout char[]) dg)
+ {
+ char[] line = "";
+ foreach (ChunkInfo info; infos)
+ {
+ int index;
+ int index2;
+ char[] data = Chunk.data(info, s);
+ while ((index2 = 1+std.string.find(data[index..length], 10)) != 0)
+ {
+
+ line ~= data[index..index+index2];
+ int result = dg(line);
+ if (result) return result;
+ line.length = 0;
+ index += index2;
+ }
+ line ~= data[index..length];
+ }
+ int result = dg(line);
+ if (result) return result;
+ return 0;
+ }
+};
+
+int main (char[][] args)
+{
+ if (args.length != 2)
+ {
+ fwritef(stderr, "Usage: %s input.dbx >output.mbox\n", args[0]);
+ exit(1);
+ }
+ auto input = new EndianStream(new BufferedFile(args[1], FileMode.In), Endian.LittleEndian);
+ auto cf = new ChainFinder(input);
+
+ void chunkstats() {
+ fwritef(stderr,"Bytes: %d; Chunks: %d; Duplicates: %d; Duplicate IDs: %d; %d%% done\r",
+ input.position(), cf.chunkstats.count, cf.chunkstats.duplicates, cf.chunkstats.id_duplicates, (input.position()+1)*100/(input.size+1));
+ }
+
+ time_t time1;
+ void every(uint seconds, lazy void func)
+ {
+ time_t time2 = time(null);
+ if (time2 - time1 >= seconds)
+ {
+ time1 = time2;
+ func();
+ }
+ }
+
+ fwritef(stderr,"Pass 1/2...\n");
+ while(true)
+ {
+ Header h;
+ if (input.readBlock(&h, 16) != 16) break;
+ input.fixBlockBO(&h, 4, 4);
+ with(h)
+ {
+ if (
+ (chunksize == 512) &&
+ (datasize <= chunksize) &&
+ (datasize > 0) &&
+ (id != 0) &&
+ ((next == 0) || (datasize == chunksize))
+ )
+ {
+ ChunkInfo info;
+ info.next = h.next;
+ info.datasize = h.datasize;
+ info.fileoffset = input.position();
+ input.seekCur(chunksize);
+ cf.add(h.id, info);
+ every(2, chunkstats());
+ }
+ else input.seekCur(-12);
+ }
+ }
+ chunkstats();
+ fwritef(stderr,"\n");
+
+ void chainstats() {
+ fwritef(stderr,"Chains: %d; Broken: %d; Loops: %d; Dropped: %d; %d%% done\r",
+ cf.chainstats.count, cf.chainstats.broken, cf.chainstats.loops, cf.chainstats.dropped, cf.chainstats.progress_current*100/cf.chainstats.progress_total);
+ }
+
+ fwritef(stderr,"Pass 2/2...\n");
+ foreach (Message m; cf)
+ {
+ writef("From unknown@unknown.invalid Mon Jan 1 00:00:00 1970\r\n");
+ foreach (char[] line; m)
+ {
+ line = sub(line, "^>*From ", ">$&");
+ fwrite(cast(void*)line, line.length, char.sizeof, stdout);
+ }
+ writef("\r\n\r\n");
+ every(2, chainstats());
+ }
+ chainstats();
+ fwritef(stderr,"\n");
+
+ return 0;
+};