mirror of
https://github.com/horsicq/Detect-It-Easy.git
synced 2026-06-24 01:54:08 +00:00
120 lines
No EOL
6.1 KiB
JavaScript
120 lines
No EOL
6.1 KiB
JavaScript
// Detect It Easy: detection rule file
|
||
// Author: Kaens (TG @kaens)
|
||
// TODO parse PQA too, why not
|
||
/* beautify ignore:start */
|
||
|
||
meta("format", "Palm OS file ");
|
||
|
||
function detect() {
|
||
//ref https://jichu4n.github.io/palm-pdb/assets/Palm%20File%20Format%20Specification.pdf
|
||
// & https://github.com/jichu4n/palm-os-sdk/blob/master/sdk-3.1/include/Core/System/DataPrv.h
|
||
if (/S98[0-3]/.test(X.SA(0, 4)) && X.U32(4) <= 0x20 && !X.U32(0xC)
|
||
&& (!X.U32(0x10) || isWithin(X.U32(0x10), 0x20, 0x800000))
|
||
&& X.U32(0x14) < 0x20000
|
||
&& (!X.U32(0x18) || isWithin(X.U32(0x18), X.U32(0x14), 0x800000))
|
||
&& X.U32(0x1C) <= 0x40) return //too many FPs with the S98 chiptunes
|
||
var bad = '', sus = 0;
|
||
|
||
function palmDateToStr(dt, desc) {
|
||
// Palm OS date starts from Jan 1, 1904......
|
||
if (typeof desc != 'string') desc = '';
|
||
var msecs = new Date(1904, 0, 1).getTime() + dt * 1000;
|
||
var date = new Date(msecs);
|
||
if (!isWithin(date.getFullYear(), 1996, 2040) //...but not many file makers seem to have actually known that lol
|
||
&& date.getFullYear() != 1907) { // 1907 is courtesy Graffiti Sniper - Golgo 13 xmas cards. The game's from 2001
|
||
msecs = new Date(0).getTime() + (dt * 1000); date = new Date(msecs)
|
||
}
|
||
if (!isWithin(date.getFullYear(), 1996, 2040) && date.getFullYear() != 1907)
|
||
if (isWithin(dt, 1996, 2040))
|
||
return 'y' + dt // some don't even realise it has a format and think it's just the year. (ZioGolf's zgrsc.pdb :D)
|
||
else return ''; //thankfully even with the special snowflakes it's tight enough for detection
|
||
var r = date.toISOString().slice(0, 19);
|
||
return r.slice(0, 4) == '1907' ? '2001' + r.slice(4, 19) : r
|
||
}
|
||
|
||
const dmHdrAttrBackup = 1, dmHdrAttrLaunchableData = 2;
|
||
var name = X.SA(0, 0x20), nc = charStat(name, true);
|
||
if (nc.indexOf('asc') < 0) return;
|
||
var attr = X.U16(0x20, _BE), v = X.U16(0x22, _BE), dtCre = X.U32(0x24, _BE), dtMod = X.U32(0x28, _BE),
|
||
dtBak = X.U32(0x2C, _BE), modnum = X.U32(0x30, _BE), appinfo = X.U32(0x34, _BE),
|
||
sortinfo = X.U32(0x38, _BE), tp = 'PDB', tp_ = X.SA(0x3C, 4), by = X.SA(0x40, 4);
|
||
var idseed = X.U32(0x44, _BE), nextrec = X.U16(0x48, _BE), numrec = X.U16(0x4C, _BE),
|
||
p = numrec ? 0x4E : 0x50;
|
||
if (!numrec) sus += 3; //useless pdb, or, more realistically, a FP
|
||
if (numrec > 0x7FFF) sus += 2; //haven't seen this many yet...
|
||
if (modnum > 0xFFF) sus += 2; //can't be that many versions, right?
|
||
if (nextrec) { sus++; bad = bad.addIfNone('!baddbdir') } //Palm specs say reject this db (artefacts of pre-v4 OSes)
|
||
if (idseed > 0xFFFFFF) sus++;
|
||
if (!/\w{3,}/.test(by)) return;
|
||
switch (tp_) {
|
||
case 'appl': tp = 'PRC'; break;
|
||
case 'pqa ': if (by == 'clpr') tp = 'PQA'; else return;
|
||
default:
|
||
if (charStat(X.readBytes(p, 4), true).indexOf('allasc') >= 0) {
|
||
tp = 'PRC'; bad = bad.addIfNone('!badtype' + X.SA(p, 4))
|
||
}
|
||
}
|
||
var dt = palmDateToStr(dtMod, 'mod'), endp, reclen = 8, m = i = 0, mhk = '', uniq = [];
|
||
if (dtMod && dt === '') { sus += 2; bad = bad.addIfNone('!nodate') }
|
||
if (dtCre && palmDateToStr(dtCre, 'cre') === '') sus++;
|
||
if (dtBak && palmDateToStr(dtBak, 'bak') === '') sus++;
|
||
|
||
if (tp === 'PRC') for (reclen = 10, endp = p + reclen * numrec; p < endp; p += reclen) { // PRC RsrcEntryType
|
||
if (p + 10 > X.Sz()) { /*debug('lookahead shorted out');*/ return }
|
||
var hkhd = X.SA(p, 4), hkid = X.U16(p + 4, _BE), hkofs = X.U32(p + 6, _BE);
|
||
if (!isWithin(hkofs, p, X.Sz()) || !/\w{3,}/.test(hkhd) || uniq.indexOf(hkofs) >= 0) return;
|
||
uniq.push(hkofs); if (m < hkofs) { m = hkofs; mhk = hkhd }
|
||
if (hkhd === 'tver') sVersion = 'v' + X.SA(m, 0x100).trim()
|
||
}
|
||
else if (tp === 'PDB') for (reclen = 8, endp = p + reclen * numrec; p < endp; p += reclen) { // PDB RecordEntryType
|
||
if (p + 8 > X.Sz()) return;
|
||
var hkofs = X.U32(p, _BE), hkattr = X.U8(p + 4), hkid = X.U24(p + 5, _BE); //the ID is unique. Not in reality though
|
||
if (!isWithin(hkofs, p, X.Sz()) || uniq.indexOf(hkofs) >= 0) return;
|
||
uniq.push(hkofs); if (m < hkofs) m = hkofs
|
||
}
|
||
//else TODO parse PQA reclist
|
||
if (appinfo && !isWithin(appinfo, 0x4E + reclen * numrec, X.Sz())
|
||
|| sortinfo && !isWithin(sortinfo, 0x4C + reclen * numrec, X.Sz())
|
||
|| appinfo && sortinfo && sortinfo < appinfo) {
|
||
/*debug('appinfo/sortinfo bad address');*/ return
|
||
}
|
||
|
||
if (sVersion != '') sVersion = tp_.appendS(sVersion, ' '); else sVersion = tp_;
|
||
if (appinfo && Math.abs(appinfo - p) > 0x10) {
|
||
//_l2r('Palm OS file',p,'appinfo should be '+Hex(appinfo));
|
||
return
|
||
}
|
||
if (sus > 3) return;
|
||
if (tp == 'PRC')
|
||
if (['taic', 'tAIN', 'tver', 'tSTR'].indexOf(mhk) >= 0)
|
||
sz = outSz(X.fSig(m, -1, '00') + 1); //text fields, most end with 0. Some do have 0 padding...
|
||
else if (mhk == 'tSTL') {
|
||
var ll = X.U8(m + 2, _BE), z; m += 4;
|
||
for (i = 0; i < ll && m <= X.Sz(); i++) {
|
||
z = X.fSig(m, TOEOF, "00");
|
||
if (z < 0) { bad = bad.addIfNone('!short'); break }
|
||
m = z + 1
|
||
}
|
||
sz = outSz(m)
|
||
}
|
||
else if (['pref'].indexOf(mhk) >= 0) sz = outSz(m + 10);
|
||
else if (X.c("'RIFF'........'WAVE'", m)) sz = outSz(m + X.U32(m + 4) + 8);
|
||
else {
|
||
sz = Hex(Math.max(m, p, appinfo, sortinfo)) + '+' + mhk;
|
||
if (mhk === 'code' && !X.c("4E75", X.Sz() - 2)) bad = bad.addIfNone("!noRTSatEoF")
|
||
}
|
||
else // PDB? PQA?
|
||
sz = Math.max(m, p) + '+' + mhk;
|
||
|
||
sName = "Palm OS file (." + tp + ")"; bDetected = true;
|
||
if (sus) bad += '!sus×' + sus;
|
||
if (bad != '') sVersion = sVersion.appendS('malformed' + bad, '/');
|
||
if (X.isVerbose()) {
|
||
sOption(name); sOption(by, 'by:'); sOption(dt, 'lastmod' + (modnum > 1 ? ' (×' + modnum + ')' : '') + ':');
|
||
sOption(Hex(idseed), "idseed:")
|
||
if (appinfo) sOption('appinfo'); if (sortinfo) sOption('sortinfo'); sOption(numrec, 'res:'); sOption(sz, 'sz:')
|
||
}
|
||
|
||
return result()
|
||
}
|
||
/* beautify ignore:end */ |