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, 8) !=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; 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; } 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; } }; 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; };