palmfile.b 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. implement Palmfile;
  2. #
  3. # Copyright © 2001-2002 Vita Nuova Holdings Limited. All rights reserved.
  4. #
  5. # Based on ``Palm® File Format Specification'', Document Number 3008-004, 1 May 2001, by Palm Inc.
  6. # Doc compression based on description by Paul Lucas, 18 August 1998
  7. #
  8. include "sys.m";
  9. sys: Sys;
  10. include "daytime.m";
  11. daytime: Daytime;
  12. include "bufio.m";
  13. bufio: Bufio;
  14. Iobuf: import bufio;
  15. include "palmfile.m";
  16. Dbhdrlen: con 72+6;
  17. Datahdrsize: con 4+1+3;
  18. Resourcehdrsize: con 4+2+4;
  19. # Exact value of "Jan 1, 1970 0:00:00 GMT" - "Jan 1, 1904 0:00:00 GMT"
  20. Epochdelta: con 2082844800;
  21. tzoff := 0;
  22. init(): string
  23. {
  24. sys = load Sys Sys->PATH;
  25. bufio = load Bufio Bufio->PATH;
  26. daytime = load Daytime Daytime->PATH;
  27. if(bufio == nil || daytime == nil)
  28. return "can't load required module";
  29. tzoff = daytime->local(0).tzoff;
  30. return nil;
  31. }
  32. Eshort: con "file format error: too small";
  33. Pfile.open(name: string, mode: int): (ref Pfile, string)
  34. {
  35. if(mode != Sys->OREAD)
  36. return (nil, "invalid mode");
  37. fd := sys->open(name, mode);
  38. if(fd == nil)
  39. return (nil, sys->sprint("%r"));
  40. pf := mkpfile(name, mode);
  41. (ok, d) := sys->fstat(fd);
  42. if(ok < 0)
  43. return (nil, sys->sprint("%r"));
  44. length := int d.length;
  45. if(length == 0)
  46. return (nil, "empty file");
  47. f := bufio->fopen(fd, mode); # automatically closed if open fails
  48. p := array[Dbhdrlen] of byte;
  49. if(f.read(p, Dbhdrlen) != Dbhdrlen)
  50. return (nil, "invalid file header: too short");
  51. ip := pf.info;
  52. ip.name = gets(p[0:32]);
  53. ip.attr = get2(p[32:]);
  54. ip.version = get2(p[34:]);
  55. ip.ctime = pilot2epoch(get4(p[36:]));
  56. ip.mtime = pilot2epoch(get4(p[40:]));
  57. ip.btime = pilot2epoch(get4(p[44:]));
  58. ip.modno = get4(p[48:]);
  59. ip.appinfo = get4(p[52:]);
  60. ip.sortinfo = get4(p[56:]);
  61. if(ip.appinfo < 0 || ip.sortinfo < 0 || (ip.appinfo|ip.sortinfo)&1)
  62. return (nil, "invalid header: bad offset");
  63. ip.dtype = xs(get4(p[60:]));
  64. ip.creator = xs(get4(p[64:]));
  65. pf.uidseed = ip.uidseed = get4(p[68:]);
  66. if(get4(p[72:]) != 0)
  67. return (nil, "chained headers not supported"); # Palm says to reject such files
  68. nrec := get2(p[76:]);
  69. if(nrec < 0)
  70. return (nil, sys->sprint("invalid header: bad record count: %d", nrec));
  71. esize := Datahdrsize;
  72. if(ip.attr & Fresource)
  73. esize = Resourcehdrsize;
  74. dataoffset := length;
  75. pf.entries = array[nrec] of ref Entry;
  76. if(nrec > 0){
  77. laste: ref Entry;
  78. buf := array[esize] of byte;
  79. for(i := 0; i < nrec; i++){
  80. if(f.read(buf, len buf) != len buf)
  81. return (nil, Eshort);
  82. e := ref Entry;
  83. if(ip.attr & Fresource){
  84. # resource entry: type[4], id[2], offset[4]
  85. e.name = get4(buf);
  86. e.id = get2(buf[4:]);
  87. e.offset = get4(buf[6:]);
  88. e.attr = 0;
  89. }else{
  90. # record entry: offset[4], attr[1], id[3]
  91. e.offset = get4(buf);
  92. e.attr = int buf[4];
  93. e.id = get3(buf[5:]);
  94. e.name = 0;
  95. }
  96. if(laste != nil)
  97. laste.size = e.offset - laste.offset;
  98. laste = e;
  99. pf.entries[i] = e;
  100. }
  101. if(laste != nil)
  102. laste.size = length - laste.offset;
  103. dataoffset = pf.entries[0].offset;
  104. }else{
  105. if(f.read(p, 2) != 2)
  106. return (nil, Eshort); # discard placeholder bytes
  107. }
  108. n := 0;
  109. if(ip.appinfo > 0){
  110. n = ip.appinfo - int f.offset();
  111. while(--n >= 0)
  112. f.getb();
  113. if(ip.sortinfo)
  114. n = ip.sortinfo - ip.appinfo;
  115. else
  116. n = dataoffset - ip.appinfo;
  117. pf.appinfo = array[n] of byte;
  118. if(f.read(pf.appinfo, n) != n)
  119. return (nil, Eshort);
  120. }
  121. if(ip.sortinfo > 0){
  122. n = ip.sortinfo - int f.offset();
  123. while(--n >= 0)
  124. f.getb();
  125. n = (dataoffset-ip.sortinfo)/2;
  126. pf.sortinfo = array[n] of int;
  127. tmp := array[2*n] of byte;
  128. if(f.read(tmp, len tmp) != len tmp)
  129. return (nil, Eshort);
  130. for(i := 0; i < n; i++)
  131. pf.sortinfo[i] = get2(tmp[2*i:]);
  132. }
  133. pf.f = f; # safe to save open file reference
  134. return (pf, nil);
  135. }
  136. Pfile.close(pf: self ref Pfile): int
  137. {
  138. if(pf.f != nil){
  139. pf.f.close();
  140. pf.f = nil;
  141. }
  142. return 0;
  143. }
  144. Pfile.stat(pf: self ref Pfile): ref DBInfo
  145. {
  146. return ref *pf.info;
  147. }
  148. Pfile.read(pf: self ref Pfile, i: int): (ref Record, string)
  149. {
  150. if(i < 0 || i >= len pf.entries){
  151. if(i == len pf.entries)
  152. return (nil, nil); # treat as end-of-file
  153. return (nil, "index out of range");
  154. }
  155. e := pf.entries[i];
  156. r := ref Record;
  157. r.index = i;
  158. nb := e.size;
  159. r.data = array[nb] of byte;
  160. pf.f.seek(big e.offset, 0);
  161. if(pf.f.read(r.data, nb) != nb)
  162. return (nil, sys->sprint("%r"));
  163. r.cat = e.attr & 16r0F;
  164. r.attr = e.attr & 16rF0;
  165. r.id = e.id;
  166. r.name = e.name;
  167. return (r, nil);
  168. }
  169. #Pfile.create(name: string, info: ref DBInfo): ref Pfile
  170. #{
  171. #}
  172. #Pfile.wstat(pf: self ref Pfile, ip: ref DBInfo): string
  173. #{
  174. # if(pf.mode != Sys->OWRITE)
  175. # return "not open for writing";
  176. # if((ip.attr & Fresource) != (pf.info.attr & Fresource))
  177. # return "cannot change file type";
  178. # # copy only a subset
  179. # pf.info.name = ip.name;
  180. # pf.info.attr = ip.attr;
  181. # pf.info.version = ip.version;
  182. # pf.info.ctime = ip.ctime;
  183. # pf.info.mtime = ip.mtime;
  184. # pf.info.btime = ip.btime;
  185. # pf.info.modno = ip.modno;
  186. # pf.info.dtype = ip.dtype;
  187. # pf.info.creator = ip.creator;
  188. # return nil;
  189. #}
  190. #Pfile.setappinfo(pf: self ref Pfile, data: array of byte): string
  191. #{
  192. # if(pf.mode != Sys->OWRITE)
  193. # return "not open for writing";
  194. # pf.appinfo = array[len data] of byte;
  195. # pf.appinfo[0:] = data;
  196. #}
  197. #Pfile.setsortinfo(pf: self ref Pfile, sort: array of int): string
  198. #{
  199. # if(pf.mode != Sys->OWRITE)
  200. # return "not open for writing";
  201. # pf.sortinfo = array[len sort] of int;
  202. # pf.sortinfo[0:] = sort;
  203. #}
  204. #
  205. # internal function to extend entry list if necessary, and return a
  206. # pointer to the next available slot
  207. #
  208. entryensure(pf: ref Pfile, i: int): ref Entry
  209. {
  210. if(i < len pf.entries)
  211. return pf.entries[i];
  212. e := ref Entry(0, -1, 0, 0, 0);
  213. n := len pf.entries;
  214. if(n == 0)
  215. n = 64;
  216. else
  217. n = (i+63) & ~63;
  218. a := array[n] of ref Entry;
  219. a[0:] = pf.entries;
  220. a[i] = e;
  221. pf.entries = a;
  222. return e;
  223. }
  224. writefilehdr(pf: ref Pfile, mode: int, perm: int): string
  225. {
  226. if(len pf.entries >= 64*1024)
  227. return "too many records for Palm file"; # is there a way to extend it?
  228. if((f := bufio->create(pf.fname, mode, perm)) == nil)
  229. return sys->sprint("%r");
  230. ip := pf.info;
  231. esize := Datahdrsize;
  232. if(ip.attr & Fresource)
  233. esize = Resourcehdrsize;
  234. offset := Dbhdrlen + esize*len pf.entries + 2;
  235. offset += 2; # placeholder bytes or gap bytes
  236. ip.appinfo = 0;
  237. if(len pf.appinfo > 0){
  238. ip.appinfo = offset;
  239. offset += len pf.appinfo;
  240. }
  241. ip.sortinfo = 0;
  242. if(len pf.sortinfo > 0){
  243. ip.sortinfo = offset;
  244. offset += 2*len pf.sortinfo; # 2-byte entries
  245. }
  246. p := array[Dbhdrlen] of byte; # bigger than any entry as well
  247. puts(p[0:32], ip.name);
  248. put2(p[32:], ip.attr);
  249. put2(p[34:], ip.version);
  250. put4(p[36:], epoch2pilot(ip.ctime));
  251. put4(p[40:], epoch2pilot(ip.mtime));
  252. put4(p[44:], epoch2pilot(ip.btime));
  253. put4(p[48:], ip.modno);
  254. put4(p[52:], ip.appinfo);
  255. put4(p[56:], ip.sortinfo);
  256. put4(p[60:], sx(ip.dtype));
  257. put4(p[64:], sx(ip.creator));
  258. put4(p[68:], pf.uidseed);
  259. put4(p[72:], 0); # next record list ID
  260. put2(p[76:], len pf.entries);
  261. if(f.write(p, Dbhdrlen) != Dbhdrlen)
  262. return ewrite(f);
  263. if(len pf.entries > 0){
  264. for(i := 0; i < len pf.entries; i++) {
  265. e := pf.entries[i];
  266. e.offset = offset;
  267. if(ip.attr & Fresource) {
  268. put4(p, e.name);
  269. put2(p[4:], e.id);
  270. put4(p[6:], e.offset);
  271. } else {
  272. put4(p, e.offset);
  273. p[4] = byte e.attr;
  274. put3(p[5:], e.id);
  275. }
  276. if(f.write(p, esize) != esize)
  277. return ewrite(f);
  278. offset += e.size;
  279. }
  280. }
  281. f.putb(byte 0); # placeholder bytes (figure 1.4) or gap bytes (p. 15)
  282. f.putb(byte 0);
  283. if(ip.appinfo != 0){
  284. if(f.write(pf.appinfo, len pf.appinfo) != len pf.appinfo)
  285. return ewrite(f);
  286. }
  287. if(ip.sortinfo != 0){
  288. tmp := array[2*len pf.sortinfo] of byte;
  289. for(i := 0; i < len pf.sortinfo; i++)
  290. put2(tmp[2*i:], pf.sortinfo[i]);
  291. if(f.write(tmp, len tmp) != len tmp)
  292. return ewrite(f);
  293. }
  294. if(f.flush() != 0)
  295. return ewrite(f);
  296. return nil;
  297. }
  298. ewrite(f: ref Iobuf): string
  299. {
  300. e := sys->sprint("write error: %r");
  301. f.close();
  302. return e;
  303. }
  304. Doc.open(file: ref Pfile): (ref Doc, string)
  305. {
  306. if(file.info.dtype != "TEXt" || file.info.creator != "REAd")
  307. return (nil, "not a Doc file: wrong type or creator");
  308. (r, err) := file.read(0);
  309. if(r == nil){
  310. if(err == nil)
  311. err = "no directory record";
  312. return (nil, sys->sprint("not a valid Doc file: %s", err));
  313. }
  314. a := r.data;
  315. if(len a < 16)
  316. return (nil, sys->sprint("not a valid Doc file: bad length: %d", len a));
  317. maxrec := len file.entries-1;
  318. d := ref Doc;
  319. d.file = file;
  320. d.version = get2(a);
  321. if(d.version != 1 && d.version != 2)
  322. err = "unknown Docfile version";
  323. # a[2:] is spare
  324. d.length = get4(a[4:]);
  325. d.nrec = get2(a[8:]);
  326. if(maxrec >= 0 && d.nrec > maxrec){
  327. d.nrec = maxrec;
  328. err = "invalid record count";
  329. }
  330. d.recsize = get2(a[10:]);
  331. d.position = get4(a[12:]);
  332. return (d, sys->sprint("unexpected Doc file format: %s", err));
  333. }
  334. Doc.iscompressed(d: self ref Doc): int
  335. {
  336. return (d.version&7) == 2; # high-order bits are sometimes used, ignore them
  337. }
  338. Doc.read(doc: self ref Doc, index: int): (string, string)
  339. {
  340. (r, err) := doc.file.read(index+1);
  341. if(r == nil)
  342. return (nil, err);
  343. (s, serr) := doc.unpacktext(r.data);
  344. if(s == nil)
  345. return (nil, serr);
  346. return (s, nil);
  347. }
  348. Doc.unpacktext(doc: self ref Doc, a: array of byte): (string, string)
  349. {
  350. nb := len a;
  351. s: string;
  352. if(!doc.iscompressed()){
  353. for(i := 0; i < nb; i++)
  354. s[len s] = int a[i]; # assumes Latin-1
  355. return (s, nil);
  356. }
  357. o := 0;
  358. for(i := 0; i < nb;){
  359. c := int a[i++];
  360. if(c >= 9 && c <= 16r7F || c == 0)
  361. s[o++] = c;
  362. else if(c >= 1 && c <= 8){
  363. if(i+c > nb)
  364. return (nil, "missing data in record");
  365. while(--c >= 0)
  366. s[o++] = int a[i++];
  367. }else if(c >= 16rC0 && c <= 16rFF){
  368. s[o] = ' ';
  369. s[o+1] = c & 16r7F;
  370. o += 2;
  371. }else{ # c >= 0x80 && c <= 16rBF
  372. v := int a[i++];
  373. m := ((c & 16r3F)<<5)|(v>>3);
  374. n := (v&7) + 3;
  375. if(m == 0 || m > o)
  376. return (nil, sys->sprint("data is corrupt: m=%d n=%d o=%d", m, n, o));
  377. for(; --n >= 0; o++)
  378. s[o] = s[o-m];
  379. }
  380. }
  381. return (s, nil);
  382. }
  383. Doc.textlength(doc: self ref Doc, a: array of byte): int
  384. {
  385. nb := len a;
  386. if(!doc.iscompressed())
  387. return nb;
  388. o := 0;
  389. for(i := 0; i < nb;){
  390. c := int a[i++];
  391. if(c >= 9 && c <= 16r7F || c == 0)
  392. o++;
  393. else if(c >= 1 && c <= 8){
  394. if(i+c > nb)
  395. return -1;
  396. o += c;
  397. i += c;
  398. }else if(c >= 16rC0 && c <= 16rFF){
  399. o += 2;
  400. }else{ # c >= 0x80 && c <= 16rBF
  401. v := int a[i++];
  402. m := ((c & 16r3F)<<5)|(v>>3);
  403. n := (v&7) + 3;
  404. if(m == 0 || m > o)
  405. return -1;
  406. o += n;
  407. }
  408. }
  409. return o;
  410. }
  411. xs(i: int): string
  412. {
  413. if(i == 0)
  414. return "";
  415. if(i & int 16r80808080)
  416. return sys->sprint("%8.8ux", i);
  417. return sys->sprint("%c%c%c%c", (i>>24)&16rFF, (i>>16)&16rFF, (i>>8)&16rFF, i&16rFF);
  418. }
  419. sx(s: string): int
  420. {
  421. n := 0;
  422. for(i := 0; i < 4; i++){
  423. c := 0;
  424. if(i < len s)
  425. c = s[i] & 16rFF;
  426. n = (n<<8) | c;
  427. }
  428. return n;
  429. }
  430. mkpfile(name: string, mode: int): ref Pfile
  431. {
  432. pf := ref Pfile;
  433. pf.mode = mode;
  434. pf.fname = name;
  435. pf.appinfo = array[0] of byte; # making it non-nil saves having to check each access
  436. pf.sortinfo = array[0] of int;
  437. pf.uidseed = 0;
  438. pf.info = DBInfo.new(name, 0, nil, 0, nil);
  439. return pf;
  440. }
  441. DBInfo.new(name: string, attr: int, dtype: string, version: int, creator: string): ref DBInfo
  442. {
  443. info := ref DBInfo;
  444. info.name = name;
  445. info.attr = attr;
  446. info.version = version;
  447. info.ctime = daytime->now();
  448. info.mtime = daytime->now();
  449. info.btime = 0;
  450. info.modno = 0;
  451. info.appinfo = 0;
  452. info.sortinfo = 0;
  453. info.dtype = dtype;
  454. info.creator = creator;
  455. info.uidseed = 0;
  456. info.index = 0;
  457. info.more = 0;
  458. return info;
  459. }
  460. Categories.new(labels: array of string): ref Categories
  461. {
  462. c := ref Categories;
  463. c.renamed = 0;
  464. c.lastuid = 0;
  465. c.labels = array[16] of string;
  466. c.uids = array[] of {0 to 15 => 0};
  467. for(i := 0; i < len labels && i < 16; i++){
  468. c.labels[i] = labels[i];
  469. c.lastuid = 16r80 + i;
  470. c.uids[i] = c.lastuid;
  471. }
  472. return c;
  473. }
  474. Categories.unpack(a: array of byte): ref Categories
  475. {
  476. if(len a < 16r114)
  477. return nil; # doesn't match the structure
  478. c := ref Categories;
  479. c.renamed = get2(a);
  480. c.labels = array[16] of string;
  481. c.uids = array[16] of int;
  482. j := 2;
  483. for(i := 0; i < 16; i++){
  484. c.labels[i] = latin1(a[j:j+16], 0);
  485. j += 16;
  486. c.uids[i] = int a[16r102+i];
  487. }
  488. c.lastuid = int a[16r112];
  489. # one byte of padding is shown on p. 26, but
  490. # two more are invariably used in practice
  491. # before application specific data.
  492. if(len a > 16r116)
  493. c.appdata = a[16r116:];
  494. return c;
  495. }
  496. Categories.pack(c: self ref Categories): array of byte
  497. {
  498. a := array[16r116 + len c.appdata] of byte;
  499. put2(a, c.renamed);
  500. j := 2;
  501. for(i := 0; i < 16; i++){
  502. puts(a[j:j+16], c.labels[i]);
  503. j += 16;
  504. a[16r102+i] = byte c.uids[i];
  505. }
  506. a[16r112] = byte c.lastuid;
  507. a[16r113] = byte 0; # pad shown on p. 26
  508. a[16r114] = byte 0; # extra two bytes of padding used in practice
  509. a[16r115] = byte 0;
  510. if(c.appdata != nil)
  511. a[16r116:] = c.appdata;
  512. return a;
  513. }
  514. Categories.mkidmap(c: self ref Categories): array of int
  515. {
  516. a := array[256] of {* => 0};
  517. for(i := 0; i < len c.uids; i++)
  518. a[c.uids[i]] = i;
  519. return a;
  520. }
  521. #
  522. # because PalmOS treats all times as local times, and doesn't associate
  523. # them with time zones, we'll convert using local time on Plan 9 and Inferno
  524. #
  525. pilot2epoch(t: int): int
  526. {
  527. if(t == 0)
  528. return 0; # we'll assume it's not set
  529. return t - Epochdelta + tzoff;
  530. }
  531. epoch2pilot(t: int): int
  532. {
  533. if(t == 0)
  534. return t;
  535. return t - tzoff + Epochdelta;
  536. }
  537. #
  538. # map Palm name to string, assuming iso-8859-1,
  539. # but remap space and /
  540. #
  541. latin1(a: array of byte, remap: int): string
  542. {
  543. s := "";
  544. for(i := 0; i < len a; i++){
  545. c := int a[i];
  546. if(c == 0)
  547. break;
  548. if(remap){
  549. if(c == ' ')
  550. c = 16r00A0; # unpaddable space
  551. else if(c == '/')
  552. c = 16r2215; # division /
  553. }
  554. s[len s] = c;
  555. }
  556. return s;
  557. }
  558. #
  559. # map from Unicode to Palm name
  560. #
  561. filename(name: string): string
  562. {
  563. s := "";
  564. for(i := 0; i < len name; i++){
  565. c := name[i];
  566. if(c == ' ')
  567. c = 16r00A0; # unpaddable space
  568. else if(c == '/')
  569. c = 16r2215; # division solidus
  570. s[len s] = c;
  571. }
  572. return s;
  573. }
  574. dbname(name: string): string
  575. {
  576. s := "";
  577. for(i := 0; i < len name; i++){
  578. c := name[i];
  579. case c {
  580. 0 => c = ' '; # unlikely, but just in case
  581. 16r2215 => c = '/';
  582. 16r00A0 => c = ' ';
  583. }
  584. s[len s] = c;
  585. }
  586. return s;
  587. }
  588. #
  589. # string conversion: can't use (string a) because
  590. # the bytes are Latin1, not Unicode
  591. #
  592. gets(a: array of byte): string
  593. {
  594. s := "";
  595. for(i := 0; i < len a; i++)
  596. s[len s] = int a[i];
  597. return s;
  598. }
  599. puts(a: array of byte, s: string)
  600. {
  601. for(i := 0; i < len a-1 && i < len s; i++)
  602. a[i] = byte s[i];
  603. for(; i < len a; i++)
  604. a[i] = byte 0;
  605. }
  606. #
  607. # big-endian packing
  608. #
  609. get4(p: array of byte): int
  610. {
  611. return (((((int p[0] << 8) | int p[1]) << 8) | int p[2]) << 8) | int p[3];
  612. }
  613. get3(p: array of byte): int
  614. {
  615. return (((int p[0] << 8) | int p[1]) << 8) | int p[2];
  616. }
  617. get2(p: array of byte): int
  618. {
  619. return (int p[0]<<8) | int p[1];
  620. }
  621. put4(p: array of byte, v: int)
  622. {
  623. p[0] = byte (v>>24);
  624. p[1] = byte (v>>16);
  625. p[2] = byte (v>>8);
  626. p[3] = byte (v & 16rFF);
  627. }
  628. put3(p: array of byte, v: int)
  629. {
  630. p[0] = byte (v>>16);
  631. p[1] = byte (v>>8);
  632. p[2] = byte (v & 16rFF);
  633. }
  634. put2(p: array of byte, v: int)
  635. {
  636. p[0] = byte (v>>8);
  637. p[1] = byte (v & 16rFF);
  638. }