[Windows Mobile Apps and Games] daveams:
CSV parser for call list backup

I've written an html/javascript page to convert the csv call log backup file into a readable format. It converts the 8-byte hex dates, giving a readable date/time and duration. Basically it goes from this:

to this:

(sorry, couldn't embed the table in the post)

The date conversion may not be exact...Basically I'm only taking the 5 most significant bytes (the rest doesn't seem to make any difference). I empirically came up with scale and offset factors to adjust the integer value to the unix-time used by javascript (milliseconds since midnight 1/1/1970). In my tests it's pretty accurate.

Here's the code for the page:

<html>
<head>
<title>converter</title>
<STYLE type="text/css">

table { width:100%; border-collapse:collapse}
table, td, tr {
border-width:1px; border-style:solid;
margin:0px; padding:0 5px;
border-color:black; vertical-align:top; }
</style>

<script type="text/javascript">
function PIMbToHuman(inputtext){
var retout = true;
if(inputtext===undefined) {
inputtext = (document.PIMb.TimeStamp.value);
retout = false;
}
//0x80,0x20,0xcc,0x35,0xd4,0x6b,0xcb,0x01
//document.getElementById('debug').innerHTML = inputtext +'<br>';
var inpar = inputtext.split(',');
if(inpar.length<5) var inpar = inputtext.split(' ');
inputtext = 0;
for(var i=1;i<6;i++) {
var j = inpar.length-i;
inputtext += parseInt(inpar[j],16) * Math.pow(256,5-i);
}
//document.getElementById('debug').innerHTML += inputtext +'<br>';

inputtext -= 6941147583;
inputtext *= 1678.812472;
//document.getElementById('debug').innerHTML += inputtext +'<br>';
var outputtext = "";
var extraInfo = 0;
var datum = new Date(inputtext);
outputtext += datum.toLocaleString();
if(retout) return datum;
else document.getElementById('result').innerHTML = outputtext;
}

function bin2table() {
var inputtext = (document.PIMb.PIMlog.value);
var out = '';
var br = '<br>\n';
var t = '<table>\n'; var tc = '</table>\n';
var tr = '<tr>'; var trc = '</tr>\n';
var td = '<td>\n'; var tdc = '</td>\n';
var ctype = new Array();
ctype[4] = ctype[4+2048] = 'Missed';
ctype[5] = ctype[5+2048] = 'OutNC';
ctype[6] = ctype[6+2048] = 'Inc';
ctype[7] = ctype[7+2048] = 'Out';

// each record begins with:
var c1 = '40 00 02 00 '; // +8, begin time
var c2 = '40 00 03 00 '; // +8, end time
var c3 = '03 00 04 00 '; // +8, call type
var c4 = '1F 00 06 00 '; // +16, +20, number
var c5 = '1F 00 07 00 '; // +16, +20, name
var c6 = '1F 00 0A 00 '; // +16, +20, location
var c7 = '66 00 00 10 '; // +8, call order

var rows = inputtext.split(c1);

out+=t + tr + td + "Date/Time" + tdc
+ td + "Duration" + tdc
+ td + "Type" + tdc
+ td + "Number" + tdc
+ td + "Name" + tdc
+ td + "Loc" + tdc
+ td + "Order" + tdc + trc;

var l, dts, dte, cti, numlen, cnum, cname, cloc, cind;
for(i=0;i<rows.length;i++) {
// start/end date/time
dts = PIMbToHuman(rows[i].substr(4*3,23));
l = rows[i].search(c2);
if(l<0) continue;
dte = PIMbToHuman(rows[i].substr(l+8*3,23));

// call type
l = rows[i].search(c3);
cti = parseInt(rows[i].substr(l+8*3,2),16);

// phone number
l = rows[i].search(c4);
numlen = parseInt(rows[i].substr(l+16*3,2),16)*3;
cnum = hex2asc(rows[i].substr(l+20*3,numlen));

// name
l = rows[i].search(c5);
if(l>0) {
namelen = parseInt(rows[i].substr(l+16*3,2),16)*3;
cname = hex2asc(rows[i].substr(l+20*3,namelen));
} else cname = '';

// location
l = rows[i].search(c6);
if(l>0) {
namelen = parseInt(rows[i].substr(l+16*3,2),16)*3;
cloc = hex2asc(rows[i].substr(l+20*3,namelen));
} else cloc = '';

// call index
l = rows[i].search(c7);
numlen = rows[i].substr(l+8*3,11).split(' ');
cind = 0;
for(var j=0;j<numlen.length;j++) {
if(numlen[j]=='') continue;
cind += parseInt(numlen[j],16) * Math.pow(256,j);
}

out += tr+td + dtf(dts) + tdc;
out += td + dtf(dts,dte) + tdc;
out += td + ctype[cti] + tdc;
out += td + cnum + tdc;
out += td + cname + tdc;
out += td + cloc + tdc;
out += td + cind + tdc + trc;

// document.getElementById('debug').innerHTML += cloc +' | ';
// document.getElementById('debug').innerHTML += '<br>';
}
out += tc;
document.getElementById('table').innerHTML = out;

}
function hex2asc(hex) {
hex = hex.replace('00 ','');
hexa = hex.split(' ');
var asc = '';
for(var i=0;i<hexa.length;i++) {
if(hexa[i]=='') continue;
var j = parseInt(hexa[i],16);
if(j==0) continue;
asc += String.fromCharCode(j);
}
return asc;
}

/*
Fields:
first - 'N' Field Count
... - Headings for all fields
N+1 - always '1'?
N+2 - Start DateTime
N+3 - End DateTime
N+4 - Call type:
4	Missed
5	OutNC
6	Incoming
7	Outgoing
+2048 if in Phone Book
N+5 - Phone Number
... - [N=9, Contact Name], [N=8, File name]
... - [N=9, Contact phone type]
L-1 - always '0'?
last - sequential call index
*/
function log2table(){
var inputtext = (document.PIMb.PIMlog.value);
var br = '<br>\n';
var t = '<table>\n'; var tc = '</table>\n';
var tr = '<tr>'; var trc = '</tr>\n';
var td = '<td>\n'; var tdc = '</td>\n';
var out = '';
var ctype = new Array();
ctype[4] = ctype[4+2048] = 'Missed';
ctype[5] = ctype[5+2048] = 'OutNC';
ctype[6] = ctype[6+2048] = 'Inc';
ctype[7] = ctype[7+2048] = 'Out';
var rows = inputtext.split('\n');
for(var i=0;i<rows.length;i++) {
rows[i] = rows[i].split(';');
}
rows.sort(sortRow);
out+=t + tr + td + "Date/Time" + tdc
+ td + "Duration" + tdc
+ td + "Type" + tdc
+ td + "Number" + tdc
+ td + "Name" + tdc
+ td + "Loc" + tdc
+ td + "Order" + tdc + trc;

for(var i=0;i<rows.length;i++) {
var n = rows[i].length;
if(n<7) continue;
var n = parseInt(rows[i][0].substr(1,4),16);
if(n>10 || n<7) continue;
var dts = PIMbToHuman(rows[i][n+2]);
var dte = PIMbToHuman(rows[i][n+3]);
out += tr+td + dtf(dts) + tdc;
//out += td + dtf(dte) + tdc;
out += td + dtf(dts,dte) + tdc;
out += td + ctype[rows[i][n+4]] + tdc;
out += td + rows[i][n+5] + tdc;
var name = ' ';
var cloc = ' ';
if(n>8) {
name = rows[i][n+6];
cloc = rows[i][n+7];
}
out += td + name + tdc;
out += td + cloc + tdc;
out += td + rows[i][rows[i].length-2] + tdc + trc;
}
out += tc;

document.getElementById('table').innerHTML = out;
//document.PIMb.PIMlog.value = out;
}
function dtf(dtv,dtv2) {
if(dtv2!==undefined) {
dtv = dtv2.getTime()-dtv.getTime();
dtv = Math.ceil(dtv/1000);
h = Math.floor(dtv/3600);
m = Math.floor(dtv/60-h*60);
s = Math.ceil(dtv-m*60-h*3600);
retval = h +":"+ lpad(m) +":"+ lpad(s);
} else {
var dow = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
retval = dow[dtv.getDay()] +' '+ lpad(dtv.getMonth()+1) +'/'+ lpad(dtv.getDate()) +'/'+
dtv.getFullYear().toString().substr(2,2) +' '+
lpad(dtv.getHours()) +':'+ lpad(dtv.getMinutes()+(dtv.getSeconds()>30?1:0));
}
return retval;
}
function sortRow(a,b) {
return b[b.length-2] - a[a.length-2];
}
function lpad(num, len) {
if(len===undefined) len=2;
var str = '' + num;
while (str.length < len) {
str = '0' + str;
}
return str;
document.getElementById('debug').innerHTML += str +' dtf<br>';
}
</script>
</head>
<body>
<form name="PIMb" onSubmit="return false;">
<textarea name="PIMlog" style="width:100%;height:200px;"></textarea><br>
<input type="button" class="button" title="PIM log to table" value="PIM log to table" onClick="log2table();">
<input type="button" class="button" title="Binary PIM log to table" value="Binary PIM log to table" onClick="bin2table();"><br>
<input type="text" size=100 name="TimeStamp" value="0"><br>
<input type="button" class="button" title="PIMb to date" value="Timestamp to date" onClick="PIMbToHuman();">
<br><br>
</form>
<div id=result></div>
<div id=table></div>
<br><br>
<div id=debug></div>
</body>
</head>

EDIT: I added some functionality to read the binary call log files too. I had several months' worth of calls backed up in the binary format, and wanted to find some call records in them. In order to use this feature, you'll need to open the binary file in a hex editor, and copy and paste the hex into the large text entry box. I used HxD.