mirror of
https://github.com/horsicq/Detect-It-Easy.git
synced 2026-06-24 01:54:08 +00:00
An attempt to tighten the detection.
Also, it now starts with a check to ensure it's not a .S98 — too many FPs 👽
115 lines
5.4 KiB
JavaScript
115 lines
5.4 KiB
JavaScript
// https://github.com/horsicq/Detect-It-Easy signature file
|
||
// Author: Kaens (TG @kaens)
|
||
// TODO parse PQA too, why not
|
||
/* beautify ignore:start */
|
||
|
||
init("format", "Palm OS file ");
|
||
includeScript("read");
|
||
|
||
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(readU8Array(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 */
|