DEADSOFTWARE

0cedb13e86a64643fb396cfd646d5d589fb30ae9
[d2df-sdl.git] / src / shared / utils.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE a_modes.inc}
16 unit utils;
18 interface
20 uses
21 SysUtils, Classes, md5;
24 // ////////////////////////////////////////////////////////////////////////// //
25 type
26 SSArray = array of ShortString;
29 // ////////////////////////////////////////////////////////////////////////// //
30 type
31 TUtf8DecoderFast = packed record
32 public
33 const Replacement = $FFFD; // replacement char for invalid unicode
34 const Accept = 0;
35 const Reject = 12;
37 private
38 state: LongWord;
40 public
41 codepoint: LongWord; // decoded codepoint (valid only when decoder is in "complete" state)
43 public
44 constructor Create (v: Boolean{fuck you, fpc});
46 procedure reset (); inline;
48 function complete (): Boolean; inline; // is current character complete? take `codepoint` then
49 function invalid (): Boolean; inline;
50 function completeOrInvalid (): Boolean; inline;
52 // process one byte, return `true` if codepoint is ready
53 function decode (b: Byte): Boolean; inline; overload;
54 function decode (c: AnsiChar): Boolean; inline; overload;
55 end;
58 // ////////////////////////////////////////////////////////////////////////// //
59 function getFilenameExt (const fn: AnsiString): AnsiString;
60 function setFilenameExt (const fn, ext: AnsiString): AnsiString;
61 function forceFilenameExt (const fn, ext: AnsiString): AnsiString;
63 // strips out name from `fn`, leaving trailing slash
64 function getFilenamePath (const fn: AnsiString): AnsiString;
66 // ends with '/' or '\'?
67 function isFilenamePath (const fn: AnsiString): Boolean;
69 // strips extra trailing slashes in `path, and extra leading slashes in `fn`
70 // will add slash to `path`, even if `fn` is empty!
71 function filenameConcat (const path, fn: AnsiString): AnsiString;
73 // does filename have one of ".wad", ".pk3", ".zip" extensions?
74 function hasWadExtension (const fn: AnsiString): Boolean;
76 // does filepath have ".XXX:\" in it?
77 function isWadPath (const fn: AnsiString): Boolean;
79 // adds ".wad" extension if filename doesn't have one of ".wad", ".pk3", ".zip"
80 function addWadExtension (const fn: AnsiString): AnsiString;
82 // check wad signature
83 function isWadData (data: Pointer; len: LongWord): Boolean;
85 // convert number to strig with nice commas
86 function int64ToStrComma (i: Int64): AnsiString;
88 function upcase1251 (ch: AnsiChar): AnsiChar; inline;
89 function locase1251 (ch: AnsiChar): AnsiChar; inline;
90 function IsValid1251 (ch: Word): Boolean;
91 function IsPrintable1251 (ch: AnsiChar): Boolean;
93 function toLowerCase1251 (const s: AnsiString): AnsiString;
95 // `true` if strings are equal; ignoring case for cp1251
96 function strEquCI1251 (const s0, s1: AnsiString): Boolean;
98 function utf8Valid (const s: AnsiString): Boolean;
100 function utf8to1251 (s: AnsiString): AnsiString;
102 // findFileCI eats case-insensitive path, traverses it and rewrites it to a
103 // case-sensetive. result value means success.
104 // if file/dir not founded than pathname is in undefined state!
105 function findFileCI (var pathname: AnsiString; lastIsDir: Boolean=false): Boolean;
107 // findDiskWad tries to find wad file and rewrites extension if needed
108 // result is new filename or empty string
109 function findDiskWad (fname: AnsiString): AnsiString;
110 // slashes must be normalized!
111 function isWadNamesEqu (wna, wnb: AnsiString): Boolean;
113 // they throws
114 function openDiskFileRO (pathname: AnsiString): TStream;
115 function createDiskFile (pathname: AnsiString): TStream;
116 // creates file if necessary
117 function openDiskFileRW (pathname: AnsiString): TStream;
119 // little endian
120 procedure writeSign (st: TStream; const sign: AnsiString);
121 function checkSign (st: TStream; const sign: AnsiString): Boolean;
123 procedure writeBool (st: TStream; b: Boolean);
124 function readBool (st: TStream): Boolean;
126 procedure writeStr (st: TStream; const str: AnsiString; maxlen: LongWord=65535);
127 function readStr (st: TStream; maxlen: LongWord=65535): AnsiString;
129 procedure writeInt (st: TStream; v: Byte); overload;
130 procedure writeInt (st: TStream; v: ShortInt); overload;
131 procedure writeInt (st: TStream; v: Word); overload;
132 procedure writeInt (st: TStream; v: SmallInt); overload;
133 procedure writeInt (st: TStream; v: LongWord); overload;
134 procedure writeInt (st: TStream; v: LongInt); overload;
135 procedure writeInt (st: TStream; v: Int64); overload;
136 procedure writeInt (st: TStream; v: UInt64); overload;
138 function readByte (st: TStream): Byte;
139 function readShortInt (st: TStream): ShortInt;
140 function readWord (st: TStream): Word;
141 function readSmallInt (st: TStream): SmallInt;
142 function readLongWord (st: TStream): LongWord;
143 function readLongInt (st: TStream): LongInt;
144 function readInt64 (st: TStream): Int64;
145 function readUInt64 (st: TStream): UInt64;
147 // big endian
148 procedure writeIntBE (st: TStream; v: Byte); overload;
149 procedure writeIntBE (st: TStream; v: ShortInt); overload;
150 procedure writeIntBE (st: TStream; v: Word); overload;
151 procedure writeIntBE (st: TStream; v: SmallInt); overload;
152 procedure writeIntBE (st: TStream; v: LongWord); overload;
153 procedure writeIntBE (st: TStream; v: LongInt); overload;
154 procedure writeIntBE (st: TStream; v: Int64); overload;
155 procedure writeIntBE (st: TStream; v: UInt64); overload;
157 function readByteBE (st: TStream): Byte;
158 function readShortIntBE (st: TStream): ShortInt;
159 function readWordBE (st: TStream): Word;
160 function readSmallIntBE (st: TStream): SmallInt;
161 function readLongWordBE (st: TStream): LongWord;
162 function readLongIntBE (st: TStream): LongInt;
163 function readInt64BE (st: TStream): Int64;
164 function readUInt64BE (st: TStream): UInt64;
167 function nmin (a, b: Byte): Byte; inline; overload;
168 function nmin (a, b: ShortInt): ShortInt; inline; overload;
169 function nmin (a, b: Word): Word; inline; overload;
170 function nmin (a, b: SmallInt): SmallInt; inline; overload;
171 function nmin (a, b: LongWord): LongWord; inline; overload;
172 function nmin (a, b: LongInt): LongInt; inline; overload;
173 function nmin (a, b: Int64): Int64; inline; overload;
174 function nmin (a, b: UInt64): UInt64; inline; overload;
175 function nmin (a, b: Single): Single; inline; overload;
176 function nmin (a, b: Double): Double; inline; overload;
177 {$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)}
178 function nmin (a, b: Extended): Extended; inline; overload;
179 {$ENDIF}
181 function nmax (a, b: Byte): Byte; inline; overload;
182 function nmax (a, b: ShortInt): ShortInt; inline; overload;
183 function nmax (a, b: Word): Word; inline; overload;
184 function nmax (a, b: SmallInt): SmallInt; inline; overload;
185 function nmax (a, b: LongWord): LongWord; inline; overload;
186 function nmax (a, b: LongInt): LongInt; inline; overload;
187 function nmax (a, b: Int64): Int64; inline; overload;
188 function nmax (a, b: UInt64): UInt64; inline; overload;
189 function nmax (a, b: Single): Single; inline; overload;
190 function nmax (a, b: Double): Double; inline; overload;
191 {$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)}
192 function nmax (a, b: Extended): Extended; inline; overload;
193 {$ENDIF}
194 function nclamp (v, a, b: Byte): Byte; inline; overload;
195 function nclamp (v, a, b: ShortInt): ShortInt; inline; overload;
196 function nclamp (v, a, b: Word): Word; inline; overload;
197 function nclamp (v, a, b: SmallInt): SmallInt; inline; overload;
198 function nclamp (v, a, b: LongWord): LongWord; inline; overload;
199 function nclamp (v, a, b: LongInt): LongInt; inline; overload;
200 function nclamp (v, a, b: Int64): Int64; inline; overload;
201 function nclamp (v, a, b: UInt64): UInt64; inline; overload;
202 function nclamp (v, a, b: Single): Single; inline; overload;
203 function nclamp (v, a, b: Double): Double; inline; overload;
204 {$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)}
205 function nclamp (v, a, b: Extended): Extended; inline; overload;
206 {$ENDIF}
208 type
209 TFormatStrFCallback = procedure (constref buf; len: SizeUInt);
211 // returns formatted string if `writerCB` is `nil`, empty string otherwise
212 function formatstrf (const fmt: AnsiString; const args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString;
214 function wchar2win (wc: WideChar): AnsiChar; inline;
215 function utf2win (const s: AnsiString): AnsiString;
216 function win2utf (const s: AnsiString): AnsiString;
217 function digitInBase (ch: AnsiChar; base: Integer): Integer;
219 // returns string in single or double quotes
220 // single quotes supports only pascal-style '' for single quote char
221 // double quotes supports c-style escapes
222 // function will select quote mode automatically
223 function quoteStr (const s: AnsiString): AnsiString;
226 type
227 generic TSimpleList<ItemT> = class
228 private
229 //type PItemT = ^ItemT;
230 type TItemArr = array of ItemT;
232 public
233 type
234 TEnumerator = record
235 private
236 mItems: TItemArr;
237 mCount: Integer;
238 mCurrent: Integer;
239 public
240 constructor Create (const aitems: TItemArr; acount: Integer);
241 function MoveNext: Boolean;
242 function getCurrent (): ItemT;
243 property Current: ItemT read getCurrent;
244 end;
246 private
247 mItems: TItemArr;
248 mCount: Integer; // can be less than `mItems` size
250 private
251 function getAt (idx: Integer): ItemT; inline;
252 procedure setAt (idx: Integer; const it: ItemT); inline;
254 function getCapacity (): Integer; inline;
255 procedure setCapacity (v: Integer); inline;
257 public
258 constructor Create (acapacity: Integer=-1);
259 destructor Destroy (); override;
261 //WARNING! don't change list contents in `for ... in`!
262 function GetEnumerator (): TEnumerator;
264 procedure reset (); inline; // won't resize `mItems`
265 procedure clear (); inline;
267 procedure append (constref it: ItemT); inline;
268 procedure delete (idx: Integer); inline;
269 function remove (idx: Integer): ItemT; inline;
271 public
272 property count: Integer read mCount;
273 property capacity: Integer read getCapacity write setCapacity;
274 property at[idx: Integer]: ItemT read getAt write setAt; default;
275 end;
278 procedure FillMemory (Dest: Pointer; Len: LongWord; Ch: Byte); inline;
279 procedure CopyMemory (Dest: Pointer; Src: Pointer; Len: LongWord); inline;
280 procedure ZeroMemory (Dest: Pointer; Len: LongWord); inline;
283 type
284 TDiskFileInfo = record
285 diskName: AnsiString;
286 size: LongInt;
287 age: LongInt;
288 // not changed by info getter; used in other parts of the code
289 userName: AnsiString;
290 tag: Integer;
291 hash: TMD5Digest;
292 udata: Pointer;
293 end;
295 function GetDiskFileInfo (fname: AnsiString; var info: TDiskFileInfo): Boolean;
298 implementation
300 uses
301 xstreams;
303 const wadExtensions: array [0..6] of AnsiString = (
304 '.dfz',
305 '.wad',
306 '.dfwad',
307 '.pk3',
308 '.pak',
309 '.zip',
310 '.dfzip'
311 );
314 // ////////////////////////////////////////////////////////////////////////// //
315 procedure CopyMemory (Dest: Pointer; Src: Pointer; Len: LongWord); inline;
316 begin
317 Move(Src^, Dest^, Len);
318 end;
320 procedure FillMemory (Dest: Pointer; Len: LongWord; Ch: Byte); inline;
321 begin
322 FillChar(Dest^, Len, Ch);
323 end;
325 procedure ZeroMemory (Dest: Pointer; Len: LongWord); inline;
326 begin
327 FillChar(Dest^, Len, 0);
328 end;
331 // ////////////////////////////////////////////////////////////////////////// //
332 constructor TSimpleList.TEnumerator.Create (const aitems: TItemArr; acount: Integer);
333 begin
334 mItems := aitems;
335 mCurrent := -1;
336 mCount := acount;
337 end;
339 function TSimpleList.TEnumerator.MoveNext: Boolean;
340 begin
341 Inc(mCurrent);
342 result := (mCurrent < mCount);
343 end;
345 function TSimpleList.TEnumerator.getCurrent (): ItemT;
346 begin
347 result := mItems[mCurrent];
348 end;
351 // ////////////////////////////////////////////////////////////////////////// //
352 constructor TSimpleList.Create (acapacity: Integer=-1);
353 begin
354 mItems := nil;
355 if (acapacity > 0) then SetLength(mItems, acapacity);
356 mCount := 0;
357 end;
360 destructor TSimpleList.Destroy ();
361 begin
362 mItems := nil;
363 inherited;
364 end;
367 function TSimpleList.getCapacity (): Integer; inline;
368 begin
369 result := Length(mItems);
370 end;
373 procedure TSimpleList.setCapacity (v: Integer); inline;
374 begin
375 if (v < mCount) then v := mCount;
376 if (v <> Length(mItems)) then SetLength(mItems, v);
377 end;
380 function TSimpleList.GetEnumerator (): TEnumerator;
381 begin
382 if (Length(mItems) > 0) then result := TEnumerator.Create(mItems, mCount)
383 else result := TEnumerator.Create(nil, -1);
384 end;
387 procedure TSimpleList.reset (); inline;
388 begin
389 mCount := 0;
390 end;
393 procedure TSimpleList.clear (); inline;
394 begin
395 mItems := nil;
396 mCount := 0;
397 end;
400 function TSimpleList.getAt (idx: Integer): ItemT; inline;
401 begin
402 if (idx >= 0) and (idx < mCount) then result := mItems[idx] else result := Default(ItemT);
403 end;
406 procedure TSimpleList.setAt (idx: Integer; const it: ItemT); inline;
407 begin
408 if (idx >= 0) and (idx < mCount) then mItems[idx] := it;
409 end;
412 procedure TSimpleList.append (constref it: ItemT); inline;
413 var
414 newsz: Integer;
415 begin
416 if (mCount >= Length(mItems)) then
417 begin
418 newsz := mCount+(mCount div 3)+128;
419 SetLength(mItems, newsz);
420 end;
421 mItems[mCount] := it;
422 Inc(mCount);
423 end;
426 procedure TSimpleList.delete (idx: Integer); inline;
427 var
428 f: Integer;
429 begin
430 if (idx >= 0) and (idx < mCount) then
431 begin
432 for f := idx+1 to mCount-1 do mItems[f-1] := mItems[f];
433 end;
434 end;
437 function TSimpleList.remove (idx: Integer): ItemT; inline;
438 var
439 f: Integer;
440 begin
441 if (idx >= 0) and (idx < mCount) then
442 begin
443 result := mItems[idx];
444 for f := idx+1 to mCount-1 do mItems[f-1] := mItems[f];
445 end
446 else
447 begin
448 result := Default(ItemT);
449 end;
450 end;
453 // ////////////////////////////////////////////////////////////////////////// //
454 var
455 wc2shitmap: array[0..65535] of AnsiChar;
456 wc2shitmapInited: Boolean = false;
459 // ////////////////////////////////////////////////////////////////////////// //
460 const
461 cp1251: array[0..127] of Word = (
462 $0402,$0403,$201A,$0453,$201E,$2026,$2020,$2021,$20AC,$2030,$0409,$2039,$040A,$040C,$040B,$040F,
463 $0452,$2018,$2019,$201C,$201D,$2022,$2013,$2014,$003F,$2122,$0459,$203A,$045A,$045C,$045B,$045F,
464 $00A0,$040E,$045E,$0408,$00A4,$0490,$00A6,$00A7,$0401,$00A9,$0404,$00AB,$00AC,$00AD,$00AE,$0407,
465 $00B0,$00B1,$0406,$0456,$0491,$00B5,$00B6,$00B7,$0451,$2116,$0454,$00BB,$0458,$0405,$0455,$0457,
466 $0410,$0411,$0412,$0413,$0414,$0415,$0416,$0417,$0418,$0419,$041A,$041B,$041C,$041D,$041E,$041F,
467 $0420,$0421,$0422,$0423,$0424,$0425,$0426,$0427,$0428,$0429,$042A,$042B,$042C,$042D,$042E,$042F,
468 $0430,$0431,$0432,$0433,$0434,$0435,$0436,$0437,$0438,$0439,$043A,$043B,$043C,$043D,$043E,$043F,
469 $0440,$0441,$0442,$0443,$0444,$0445,$0446,$0447,$0448,$0449,$044A,$044B,$044C,$044D,$044E,$044F
470 );
473 procedure initShitMap ();
474 var
475 f: Integer;
476 begin
477 for f := 0 to High(wc2shitmap) do wc2shitmap[f] := '?';
478 for f := 0 to 127 do wc2shitmap[f] := AnsiChar(f);
479 for f := 0 to 127 do wc2shitmap[cp1251[f]] := AnsiChar(f+128);
480 wc2shitmapInited := true;
481 end;
484 // ////////////////////////////////////////////////////////////////////////// //
485 // fast state-machine based UTF-8 decoder; using 8 bytes of memory
486 // code points from invalid range will never be valid, this is the property of the state machine
487 const
488 // see http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
489 utf8dfa: array[0..$16c-1] of Byte = (
490 // maps bytes to character classes
491 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 00-0f
492 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 10-1f
493 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 20-2f
494 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 30-3f
495 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 40-4f
496 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 50-5f
497 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 60-6f
498 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 70-7f
499 $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01, // 80-8f
500 $09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09, // 90-9f
501 $07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07, // a0-af
502 $07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07, // b0-bf
503 $08,$08,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02, // c0-cf
504 $02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02, // d0-df
505 $0a,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$04,$03,$03, // e0-ef
506 $0b,$06,$06,$06,$05,$08,$08,$08,$08,$08,$08,$08,$08,$08,$08,$08, // f0-ff
507 // maps a combination of a state of the automaton and a character class to a state
508 $00,$0c,$18,$24,$3c,$60,$54,$0c,$0c,$0c,$30,$48,$0c,$0c,$0c,$0c, // 100-10f
509 $0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$00,$0c,$0c,$0c,$0c,$0c,$00, // 110-11f
510 $0c,$00,$0c,$0c,$0c,$18,$0c,$0c,$0c,$0c,$0c,$18,$0c,$18,$0c,$0c, // 120-12f
511 $0c,$0c,$0c,$0c,$0c,$0c,$0c,$18,$0c,$0c,$0c,$0c,$0c,$18,$0c,$0c, // 130-13f
512 $0c,$0c,$0c,$0c,$0c,$18,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$24, // 140-14f
513 $0c,$24,$0c,$0c,$0c,$24,$0c,$0c,$0c,$0c,$0c,$24,$0c,$24,$0c,$0c, // 150-15f
514 $0c,$24,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c);
517 // ////////////////////////////////////////////////////////////////////////// //
518 constructor TUtf8DecoderFast.Create (v: Boolean{fuck you, fpc}); begin state := Accept; codepoint := 0; end;
520 procedure TUtf8DecoderFast.reset (); inline; begin state := Accept; codepoint := 0; end;
522 function TUtf8DecoderFast.complete (): Boolean; inline; begin result := (state = Accept); end;
523 function TUtf8DecoderFast.invalid (): Boolean; inline; begin result := (state = Reject); end;
524 function TUtf8DecoderFast.completeOrInvalid (): Boolean; inline; begin result := (state = Accept) or (state = Reject); end;
526 function TUtf8DecoderFast.decode (c: AnsiChar): Boolean; inline; overload; begin result := decode(Byte(c)); end;
528 function TUtf8DecoderFast.decode (b: Byte): Boolean; inline; overload;
529 var
530 tp: LongWord;
531 begin
532 if (state = Reject) then begin state := Accept; codepoint := 0; end;
533 tp := utf8dfa[b];
534 if (state <> Accept) then codepoint := (b and $3f) or (codepoint shl 6) else codepoint := ($ff shr tp) and b;
535 state := utf8dfa[256+state+tp];
536 if (state = Reject) then begin codepoint := Replacement; state := Accept; end;
537 result := (state = Accept);
538 end;
541 // ////////////////////////////////////////////////////////////////////////// //
542 function wchar2win (wc: WideChar): AnsiChar; inline;
543 begin
544 if not wc2shitmapInited then initShitMap();
545 if (LongWord(wc) > 65535) then result := '?' else result := wc2shitmap[LongWord(wc)];
546 end;
549 // ////////////////////////////////////////////////////////////////////////// //
550 function utf2win (const s: AnsiString): AnsiString;
551 var
552 f, c: Integer;
553 ud: TUtf8DecoderFast;
554 begin
555 for f := 1 to Length(s) do
556 begin
557 if (Byte(s[f]) > 127) then
558 begin
559 ud := TUtf8DecoderFast.Create(true);
560 result := '';
561 for c := 1 to Length(s) do
562 begin
563 if ud.decode(s[c]) then result += wchar2win(WideChar(ud.codepoint));
564 end;
565 exit;
566 end;
567 end;
568 result := s;
569 end;
572 function win2utf (const s: AnsiString): AnsiString;
573 var
574 f, c: Integer;
576 function utf8Encode (code: Integer): AnsiString;
577 begin
578 if (code < 0) or (code > $10FFFF) then begin result := '?'; exit; end;
579 if (code <= $7f) then
580 begin
581 result := AnsiChar(code and $ff);
582 end
583 else if (code <= $7FF) then
584 begin
585 result := AnsiChar($C0 or (code shr 6));
586 result += AnsiChar($80 or (code and $3F));
587 end
588 else if (code <= $FFFF) then
589 begin
590 result := AnsiChar($E0 or (code shr 12));
591 result += AnsiChar($80 or ((code shr 6) and $3F));
592 result += AnsiChar($80 or (code and $3F));
593 end
594 else if (code <= $10FFFF) then
595 begin
596 result := AnsiChar($F0 or (code shr 18));
597 result += AnsiChar($80 or ((code shr 12) and $3F));
598 result += AnsiChar($80 or ((code shr 6) and $3F));
599 result += AnsiChar($80 or (code and $3F));
600 end
601 else
602 begin
603 result := '?';
604 end;
605 end;
607 begin
608 for f := 1 to Length(s) do
609 begin
610 if (Byte(s[f]) > 127) then
611 begin
612 result := '';
613 for c := 1 to Length(s) do
614 begin
615 if (Byte(s[c]) < 128) then
616 begin
617 result += s[c];
618 end
619 else
620 begin
621 result += utf8Encode(cp1251[Byte(s[c])-128])
622 end;
623 end;
624 exit;
625 end;
626 end;
627 result := s;
628 end;
631 // ////////////////////////////////////////////////////////////////////////// //
632 function digitInBase (ch: AnsiChar; base: Integer): Integer;
633 begin
634 result := -1;
635 if (base < 1) or (base > 36) then exit;
636 if (ch < '0') then exit;
637 if (base <= 10) then
638 begin
639 if (Integer(ch) >= 48+base) then exit;
640 result := Integer(ch)-48;
641 end
642 else
643 begin
644 if (ch >= '0') and (ch <= '9') then begin result := Integer(ch)-48; exit; end;
645 if (ch >= 'a') and (ch <= 'z') then Dec(ch, 32); // poor man's tolower()
646 if (ch < 'A') or (Integer(ch) >= 65+(base-10)) then exit;
647 result := Integer(ch)-65+10;
648 end;
649 end;
652 // ////////////////////////////////////////////////////////////////////////// //
653 function quoteStr (const s: AnsiString): AnsiString;
655 function squote (const s: AnsiString): AnsiString;
656 var
657 f: Integer;
658 begin
659 result := '''';
660 for f := 1 to Length(s) do
661 begin
662 if (s[f] = '''') then result += '''';
663 result += s[f];
664 end;
665 result += '''';
666 end;
668 function dquote (const s: AnsiString): AnsiString;
669 var
670 f: Integer;
671 ch: AnsiChar;
672 begin
673 result := '"';
674 for f := 1 to Length(s) do
675 begin
676 ch := s[f];
677 if (ch = #0) then result += '\z'
678 else if (ch = #9) then result += '\t'
679 else if (ch = #10) then result += '\n'
680 else if (ch = #13) then result += '\r'
681 else if (ch = #27) then result += '\e'
682 else if (ch < ' ') or (ch = #127) then
683 begin
684 result += '\x';
685 result += LowerCase(IntToHex(Integer(ch), 2));
686 end
687 else if (ch = '"') or (ch = '\') then
688 begin
689 result += '\';
690 result += ch;
691 end
692 else
693 begin
694 result += ch;
695 end;
696 end;
697 result += '"';
698 end;
700 var
701 needSingle: Boolean = false;
702 f: Integer;
703 begin
704 for f := 1 to Length(s) do
705 begin
706 if (s[f] = '''') then begin needSingle := true; continue; end;
707 if (s[f] < ' ') or (s[f] = #127) then begin result := dquote(s); exit; end;
708 end;
709 if needSingle then result := squote(s) else result := ''''+s+'''';
710 end;
713 // ////////////////////////////////////////////////////////////////////////// //
714 function getFilenameExt (const fn: AnsiString): AnsiString;
715 var
716 pos: Integer;
717 ch: AnsiChar;
718 begin
719 pos := Length(fn);
720 while (pos > 0) do
721 begin
722 ch := fn[pos];
723 if (ch = '.') then
724 begin
725 if (pos = Length(fn)) then result := '' else result := Copy(fn, pos, Length(fn)-pos+1);
726 exit;
727 end;
728 if (ch = '/') or (ch = '\') then break;
729 Dec(pos);
730 end;
731 result := ''; // no extension
732 end;
735 function setFilenameExt (const fn, ext: AnsiString): AnsiString;
736 var
737 pos: Integer;
738 ch: AnsiChar;
739 begin
740 result := fn;
741 if (Length(ext) = 0) or (ext = '.') then exit;
742 pos := Length(fn);
743 while (pos > 0) do
744 begin
745 ch := fn[pos];
746 if (ch = '.') then exit;
747 if (ch = '/') or (ch = '\') then break;
748 Dec(pos);
749 end;
750 if (ext[1] <> '.') then result += '.';
751 result += ext;
752 end;
755 function forceFilenameExt (const fn, ext: AnsiString): AnsiString;
756 var
757 pos: Integer;
758 ch: AnsiChar;
759 begin
760 result := fn;
761 pos := Length(fn);
762 while (pos > 0) do
763 begin
764 ch := fn[pos];
765 if (ch = '.') then
766 begin
767 if (Length(ext) = 0) or (ext = '.') then
768 begin
769 result := Copy(fn, 1, pos-1);
770 end
771 else
772 begin
773 if (ext[1] = '.') then result := Copy(fn, 1, pos-1) else result := Copy(fn, 1, pos);
774 result += ext;
775 exit;
776 end;
777 end;
778 if (ch = '/') or (ch = '\') then break;
779 Dec(pos);
780 end;
781 if (Length(ext) > 0) then
782 begin
783 if (ext[1] <> '.') then result += '.';
784 result += ext;
785 end;
786 end;
789 // strips out name from `fn`, leaving trailing slash
790 function getFilenamePath (const fn: AnsiString): AnsiString;
791 var
792 pos: Integer;
793 ch: AnsiChar;
794 begin
795 if (Length(fn) = 0) then begin result := './'; exit; end;
796 if (fn[Length(fn)] = '/') or (fn[Length(fn)] = '\') then begin result := fn; exit; end;
797 pos := Length(fn);
798 while (pos > 0) do
799 begin
800 ch := fn[pos];
801 if (ch = '/') or (ch = '\') then begin result := Copy(fn, 1, pos); exit; end;
802 Dec(pos);
803 end;
804 result := './'; // no path -> current dir
805 end;
808 // ends with '/' or '\'?
809 function isFilenamePath (const fn: AnsiString): Boolean;
810 begin
811 if (Length(fn) = 0) then
812 begin
813 result := false;
814 end
815 else
816 begin
817 result := (fn[Length(fn)] = '/') or (fn[Length(fn)] = '\');
818 end;
819 end;
822 // strips extra trailing slashes in `path, and extra leading slashes in `fn`
823 // will add slash to `path`, even if `fn` is empty!
824 function filenameConcat (const path, fn: AnsiString): AnsiString;
825 var
826 pos: Integer;
827 begin
828 pos := 1;
829 while (pos <= Length(fn)) and ((fn[pos] = '/') or (fn[pos] = '\')) do Inc(pos);
830 result := path;
831 if (Length(result) > 0) and ((result[Length(result)] <> '/') and (result[Length(result)] <> '\')) then result += '/';
832 if (pos <= Length(fn)) then
833 begin
834 result += Copy(fn, pos, Length(fn)-pos+1);
835 //FIXME: make this faster!
836 while (Length(result) > 0) and ((result[Length(result)] = '/') or (result[Length(result)] = '\')) do
837 begin
838 Delete(result, Length(result), 1);
839 end;
840 if (fn[Length(fn)] = '/') or (fn[Length(fn)] = '\') then result += '/';
841 end;
842 end;
845 function hasWadExtension (const fn: AnsiString): Boolean;
846 var
847 ext, newExt: AnsiString;
848 begin
849 ext := getFilenameExt(fn);
850 result := true;
851 for newExt in wadExtensions do if (StrEquCI1251(ext, newExt)) then exit;
852 result := false;
853 //result := StrEquCI1251(ext, '.wad') or StrEquCI1251(ext, '.pk3') or StrEquCI1251(ext, '.zip') or StrEquCI1251(ext, '.dfz');
854 end;
857 function addWadExtension (const fn: AnsiString): AnsiString;
858 begin
859 result := fn;
860 if not hasWadExtension(result) then result := result+'.wad';
861 end;
864 function isWadData (data: Pointer; len: LongWord): Boolean;
865 var p: PChar;
866 begin
867 p := PChar(data);
868 Result :=
869 (* ZIP *)
870 ((len > 3) and (p[0] = 'P') and (p[1] = 'K') and (p[2] = #03) and (p[3] = #04)) or
871 ((len > 3) and (p[0] = 'P') and (p[1] = 'K') and (p[2] = #05) and (p[3] = #06)) or
872 (* PACK *)
873 ((len > 3) and (p[0] = 'P') and (p[1] = 'A') and (p[2] = 'C') and (p[3] = 'K')) or
874 ((len > 3) and (p[0] = 'S') and (p[1] = 'P') and (p[2] = 'A') and (p[3] = 'K')) or
875 (* DFWAD *)
876 ((len > 5) and (p[0] = 'D') and (p[1] = 'F') and (p[2] = 'W') and (p[3] = 'A') and (p[4] = 'D') and (p[5] = #01))
877 end;
880 function isWadPath (const fn: AnsiString): Boolean;
881 var
882 pos: Integer;
883 s: AnsiString;
884 begin
885 result := false;
886 pos := 1;
887 while (pos <= Length(fn)) do
888 begin
889 if (fn[pos] = ':') then
890 begin
891 if (Length(fn)-pos < 1) then break;
892 if (pos-4 > 1) and (fn[pos-4] = '.') and ((fn[pos+1] = '\') or (fn[pos+1] = '/')) then
893 begin
894 s := Copy(fn, pos-4, 4);
895 if StrEquCI1251(s, '.wad') or StrEquCI1251(s, '.pk3') or StrEquCI1251(s, '.zip') or StrEquCI1251(s, '.dfz') then
896 begin
897 result := true;
898 exit;
899 end;
900 end;
901 end;
902 Inc(pos);
903 end;
904 end;
907 function int64ToStrComma (i: Int64): AnsiString;
908 var
909 f: Integer;
910 begin
911 Str(i, result);
912 f := Length(result)+1;
913 while f > 4 do
914 begin
915 Dec(f, 3); Insert(',', result, f);
916 end;
917 end;
920 function upcase1251 (ch: AnsiChar): AnsiChar; inline;
921 begin
922 if ch < #128 then
923 begin
924 if (ch >= 'a') and (ch <= 'z') then Dec(ch, 32);
925 end
926 else
927 begin
928 if (ch >= #224) and (ch <= #255) then
929 begin
930 Dec(ch, 32);
931 end
932 else
933 begin
934 case ch of
935 #184, #186, #191: Dec(ch, 16);
936 #162, #179: Dec(ch);
937 end;
938 end;
939 end;
940 result := ch;
941 end;
944 function locase1251 (ch: AnsiChar): AnsiChar; inline;
945 begin
946 if ch < #128 then
947 begin
948 if (ch >= 'A') and (ch <= 'Z') then Inc(ch, 32);
949 end
950 else
951 begin
952 if (ch >= #192) and (ch <= #223) then
953 begin
954 Inc(ch, 32);
955 end
956 else
957 begin
958 case ch of
959 #168, #170, #175: Inc(ch, 16);
960 #161, #178: Inc(ch);
961 end;
962 end;
963 end;
964 result := ch;
965 end;
967 function IsValid1251 (ch: Word): Boolean;
968 begin
969 result := (ch = Ord('?')) or (wc2shitmap[ch] <> '?')
970 end;
972 function IsPrintable1251 (ch: AnsiChar): Boolean;
973 begin
974 result := (ch >= #32) and (ch <> #127)
975 end;
978 function strEquCI1251 (const s0, s1: AnsiString): Boolean;
979 var
980 i: Integer;
981 begin
982 result := false;
983 if length(s0) <> length(s1) then exit;
984 for i := 1 to length(s0) do if UpCase1251(s0[i]) <> UpCase1251(s1[i]) then exit;
985 result := true;
986 end;
989 function toLowerCase1251 (const s: AnsiString): AnsiString;
990 var
991 f: Integer;
992 ch: AnsiChar;
993 begin
994 for ch in s do
995 begin
996 if (ch <> LoCase1251(ch)) then
997 begin
998 result := '';
999 SetLength(result, Length(s));
1000 for f := 1 to Length(s) do result[f] := LoCase1251(s[f]);
1001 exit;
1002 end;
1003 end;
1004 // nothing to do
1005 result := s;
1006 end;
1009 // ////////////////////////////////////////////////////////////////////////// //
1010 // utils
1011 // `ch`: utf8 start
1012 // -1: invalid utf8
1013 function utf8CodeLen (ch: Word): Integer;
1014 begin
1015 if ch < $80 then begin result := 1; exit; end;
1016 if (ch and $FE) = $FC then begin result := 6; exit; end;
1017 if (ch and $FC) = $F8 then begin result := 5; exit; end;
1018 if (ch and $F8) = $F0 then begin result := 4; exit; end;
1019 if (ch and $F0) = $E0 then begin result := 3; exit; end;
1020 if (ch and $E0) = $C0 then begin result := 2; exit; end;
1021 result := -1; // invalid
1022 end;
1025 function utf8Valid (const s: AnsiString): Boolean;
1026 var
1027 pos, len: Integer;
1028 begin
1029 result := false;
1030 pos := 1;
1031 while pos <= length(s) do
1032 begin
1033 len := utf8CodeLen(Byte(s[pos]));
1034 if len < 1 then exit; // invalid sequence start
1035 if pos+len-1 > length(s) then exit; // out of chars in string
1036 Dec(len);
1037 Inc(pos);
1038 // check other sequence bytes
1039 while len > 0 do
1040 begin
1041 if (Byte(s[pos]) and $C0) <> $80 then exit;
1042 Dec(len);
1043 Inc(pos);
1044 end;
1045 end;
1046 result := true;
1047 end;
1050 // ////////////////////////////////////////////////////////////////////////// //
1051 const
1052 uni2wint: array [128..255] of Word = (
1053 $0402,$0403,$201A,$0453,$201E,$2026,$2020,$2021,$20AC,$2030,$0409,$2039,$040A,$040C,$040B,$040F,
1054 $0452,$2018,$2019,$201C,$201D,$2022,$2013,$2014,$003F,$2122,$0459,$203A,$045A,$045C,$045B,$045F,
1055 $00A0,$040E,$045E,$0408,$00A4,$0490,$00A6,$00A7,$0401,$00A9,$0404,$00AB,$00AC,$00AD,$00AE,$0407,
1056 $00B0,$00B1,$0406,$0456,$0491,$00B5,$00B6,$00B7,$0451,$2116,$0454,$00BB,$0458,$0405,$0455,$0457,
1057 $0410,$0411,$0412,$0413,$0414,$0415,$0416,$0417,$0418,$0419,$041A,$041B,$041C,$041D,$041E,$041F,
1058 $0420,$0421,$0422,$0423,$0424,$0425,$0426,$0427,$0428,$0429,$042A,$042B,$042C,$042D,$042E,$042F,
1059 $0430,$0431,$0432,$0433,$0434,$0435,$0436,$0437,$0438,$0439,$043A,$043B,$043C,$043D,$043E,$043F,
1060 $0440,$0441,$0442,$0443,$0444,$0445,$0446,$0447,$0448,$0449,$044A,$044B,$044C,$044D,$044E,$044F
1061 );
1064 function decodeUtf8Char (s: AnsiString; var pos: Integer): AnsiChar;
1065 var
1066 b, c: Integer;
1067 begin
1068 (* The following encodings are valid, except for the 5 and 6 byte
1069 * combinations:
1070 * 0xxxxxxx
1071 * 110xxxxx 10xxxxxx
1072 * 1110xxxx 10xxxxxx 10xxxxxx
1073 * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1074 * 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1075 * 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1076 *)
1077 result := '?';
1078 if pos > length(s) then exit;
1080 b := Byte(s[pos]);
1081 Inc(pos);
1082 if b < $80 then begin result := AnsiChar(b); exit; end;
1084 // mask out unused bits
1085 if (b and $FE) = $FC then b := b and $01
1086 else if (b and $FC) = $F8 then b := b and $03
1087 else if (b and $F8) = $F0 then b := b and $07
1088 else if (b and $F0) = $E0 then b := b and $0F
1089 else if (b and $E0) = $C0 then b := b and $1F
1090 else exit; // invalid utf8
1092 // now continue
1093 while pos <= length(s) do
1094 begin
1095 c := Byte(s[pos]);
1096 if (c and $C0) <> $80 then break; // no more
1097 b := b shl 6;
1098 b := b or (c and $3F);
1099 Inc(pos);
1100 end;
1102 // done, try 1251
1103 for c := 128 to 255 do if uni2wint[c] = b then begin result := AnsiChar(c and $FF); exit; end;
1104 // alas
1105 end;
1108 function utf8to1251 (s: AnsiString): AnsiString;
1109 var
1110 pos: Integer;
1111 begin
1112 if not utf8Valid(s) then begin result := s; exit; end;
1113 pos := 1;
1114 while pos <= length(s) do
1115 begin
1116 if Byte(s[pos]) >= $80 then break;
1117 Inc(pos);
1118 end;
1119 if pos > length(s) then begin result := s; exit; end; // nothing to do here
1120 result := '';
1121 pos := 1;
1122 while pos <= length(s) do result := result+decodeUtf8Char(s, pos);
1123 end;
1126 // ////////////////////////////////////////////////////////////////////////// //
1127 // findFileCI eats case-insensitive path, traverses it and rewrites it to a
1128 // case-sensetive. result value means success.
1129 // if file/dir not founded than pathname is in undefined state!
1130 function findFileCI (var pathname: AnsiString; lastIsDir: Boolean=false): Boolean;
1131 var
1132 sr: TSearchRec;
1133 npt: AnsiString;
1134 newname: AnsiString = '';
1135 curname: AnsiString;
1136 wantdir: Boolean;
1137 attr: LongInt;
1138 foundher: Boolean;
1139 begin
1140 npt := pathname;
1141 result := (length(npt) > 0);
1142 if (length(npt) > 0) and ((npt[1] = '/') or (npt[1] = '\')) then newname := '/';
1143 while length(npt) > 0 do
1144 begin
1145 // remove trailing slashes
1146 while (length(npt) > 0) and ((npt[1] = '/') or (npt[1] = '\')) do Delete(npt, 1, 1);
1147 if length(npt) = 0 then break;
1148 // extract name
1149 curname := '';
1150 while (length(npt) > 0) and (npt[1] <> '/') and (npt[1] <> '\') do
1151 begin
1152 curname := curname+npt[1];
1153 Delete(npt, 1, 1);
1154 end;
1155 // remove trailing slashes again
1156 while (length(npt) > 0) and ((npt[1] = '/') or (npt[1] = '\')) do Delete(npt, 1, 1);
1157 wantdir := lastIsDir or (length(npt) > 0); // do we want directory here?
1158 //writeln(Format('npt=[%s]; newname=[%s]; curname=[%s]; wantdir=%d', [npt, newname, curname, Integer(wantdir)]));
1159 // try the easiest case first
1160 attr := FileGetAttr(newname+curname);
1161 if attr <> -1 then
1162 begin
1163 if wantdir = ((attr and faDirectory) <> 0) then
1164 begin
1165 // i found her!
1166 newname := newname+curname;
1167 if wantdir then newname := newname+'/';
1168 continue;
1169 end;
1170 end;
1171 //writeln(Format('npt=[%s]; newname=[%s]; curname=[%s]; wantdir=%d', [npt, newname, curname, Integer(wantdir)]));
1172 // alas, either not found, or invalid attributes
1173 foundher := false;
1174 try
1175 if FindFirst(newname+'*', faAnyFile, sr) = 0 then
1176 repeat
1177 if (wantdir = ((sr.attr and faDirectory) <> 0)) and StrEquCI1251(sr.name, curname) then
1178 begin
1179 // i found her!
1180 newname := newname+sr.name;
1181 if wantdir then newname := newname+'/';
1182 foundher := true;
1183 break;
1184 end;
1185 until FindNext(sr) <> 0;
1186 finally
1187 FindClose(sr);
1188 end;
1189 if not foundher then begin newname := ''; result := false; break; end;
1190 end;
1191 if result then pathname := newname;
1192 end;
1195 function isWadNamesEqu (wna, wnb: AnsiString): Boolean;
1196 var
1197 ext, newExt: AnsiString;
1198 found: Boolean;
1199 begin
1200 result := StrEquCI1251(wna, wnb);
1201 if result then exit;
1202 // check first ext
1203 ext := getFilenameExt(wna);
1204 found := false;
1205 for newExt in wadExtensions do if (StrEquCI1251(ext, newExt)) then begin found := true; break; end;
1206 if not found then exit;
1207 // check second ext
1208 ext := getFilenameExt(wnb);
1209 found := false;
1210 for newExt in wadExtensions do if (StrEquCI1251(ext, newExt)) then begin found := true; break; end;
1211 if not found then exit;
1212 wna := forceFilenameExt(wna, '');
1213 wnb := forceFilenameExt(wnb, '');
1214 result := StrEquCI1251(wna, wnb);
1215 end;
1217 function findDiskWad (fname: AnsiString): AnsiString;
1218 var
1219 origExt: AnsiString = '';
1220 newExt: AnsiString = '';
1221 begin
1222 result := '';
1223 //writeln('findDiskWad00: fname=<', fname, '>');
1224 if (findFileCI(fname)) then begin result := fname; exit; end;
1225 origExt := getFilenameExt(fname);
1226 fname := forceFilenameExt(fname, '');
1227 //writeln(' findDiskWad01: fname=<', fname, '>; origExt=<', origExt, '>');
1228 for newExt in wadExtensions do
1229 begin
1230 //writeln(' findDiskWad02: fname=<', fname, '>; origExt=<', origExt, '>; newExt=<', newExt, '>');
1231 if (StrEquCI1251(newExt, origExt)) then
1232 begin
1233 //writeln(' SKIP');
1234 continue;
1235 end;
1236 result := fname+newExt;
1237 if (findFileCI(result)) then exit;
1238 end;
1239 result := '';
1240 end;
1243 function openDiskFileRO (pathname: AnsiString): TStream;
1244 begin
1245 if not findFileCI(pathname) then raise EFileNotFoundException.Create('can''t open file "'+pathname+'"');
1246 result := TFileStream.Create(pathname, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone);
1247 end;
1249 function createDiskFile (pathname: AnsiString): TStream;
1250 var
1251 path: AnsiString;
1252 begin
1253 path := ExtractFilePath(pathname);
1254 if length(path) > 0 then
1255 begin
1256 if not findFileCI(path, true) then raise Exception.Create('can''t create file "'+pathname+'"');
1257 end;
1258 result := TFileStream.Create(path+ExtractFileName(pathname), fmCreate);
1259 end;
1262 function openDiskFileRW (pathname: AnsiString): TStream;
1263 var
1264 path: AnsiString;
1265 oldname: AnsiString;
1266 begin
1267 //writeln('*** TRYING R/W FILE "', pathname, '"');
1268 path := ExtractFilePath(pathname);
1269 if length(path) > 0 then
1270 begin
1271 if not findFileCI(path, true) then raise Exception.Create('can''t create file "'+pathname+'"');
1272 end;
1273 oldname := pathname;
1274 if findFileCI(oldname) then
1275 begin
1276 //writeln('*** found old file "', oldname, '"');
1277 result := TFileStream.Create(oldname, fmOpenReadWrite or fmShareDenyWrite);
1278 end
1279 else
1280 begin
1281 result := TFileStream.Create(path+ExtractFileName(pathname), fmCreate);
1282 end;
1283 end;
1286 procedure writeIntegerLE (st: TStream; vp: Pointer; size: Integer);
1287 {$IFDEF ENDIAN_LITTLE}
1288 begin
1289 st.writeBuffer(vp^, size);
1290 end;
1291 {$ELSE}
1292 var
1293 p: PByte;
1294 begin
1295 p := PByte(vp)+size-1;
1296 while size > 0 do
1297 begin
1298 st.writeBuffer(p^, 1);
1299 Dec(size);
1300 Dec(p);
1301 end;
1302 end;
1303 {$ENDIF}
1305 procedure writeIntegerBE (st: TStream; vp: Pointer; size: Integer);
1306 {$IFDEF ENDIAN_LITTLE}
1307 var
1308 p: PByte;
1309 begin
1310 p := PByte(vp)+size-1;
1311 while size > 0 do
1312 begin
1313 st.writeBuffer(p^, 1);
1314 Dec(size);
1315 Dec(p);
1316 end;
1317 end;
1318 {$ELSE}
1319 begin
1320 st.writeBuffer(vp^, size);
1321 end;
1322 {$ENDIF}
1324 procedure writeSign (st: TStream; const sign: AnsiString);
1325 begin
1326 if (Length(sign) > 0) then st.WriteBuffer(sign[1], Length(sign));
1327 end;
1329 function checkSign (st: TStream; const sign: AnsiString): Boolean;
1330 var
1331 buf: packed array[0..7] of AnsiChar;
1332 f: Integer;
1333 begin
1334 result := false;
1335 if (Length(sign) > 0) then
1336 begin
1337 if (Length(sign) <= 8) then
1338 begin
1339 st.ReadBuffer(buf[0], Length(sign));
1340 for f := 1 to Length(sign) do if (buf[f-1] <> sign[f]) then exit;
1341 end
1342 else
1343 begin
1344 for f := 1 to Length(sign) do
1345 begin
1346 st.ReadBuffer(buf[0], 1);
1347 if (buf[0] <> sign[f]) then exit;
1348 end;
1349 end;
1350 end;
1351 result := true;
1352 end;
1354 procedure writeInt (st: TStream; v: Byte); overload; begin writeIntegerLE(st, @v, 1); end;
1355 procedure writeInt (st: TStream; v: ShortInt); overload; begin writeIntegerLE(st, @v, 1); end;
1356 procedure writeInt (st: TStream; v: Word); overload; begin writeIntegerLE(st, @v, 2); end;
1357 procedure writeInt (st: TStream; v: SmallInt); overload; begin writeIntegerLE(st, @v, 2); end;
1358 procedure writeInt (st: TStream; v: LongWord); overload; begin writeIntegerLE(st, @v, 4); end;
1359 procedure writeInt (st: TStream; v: LongInt); overload; begin writeIntegerLE(st, @v, 4); end;
1360 procedure writeInt (st: TStream; v: Int64); overload; begin writeIntegerLE(st, @v, 8); end;
1361 procedure writeInt (st: TStream; v: UInt64); overload; begin writeIntegerLE(st, @v, 8); end;
1363 procedure writeIntBE (st: TStream; v: Byte); overload; begin writeIntegerBE(st, @v, 1); end;
1364 procedure writeIntBE (st: TStream; v: ShortInt); overload; begin writeIntegerBE(st, @v, 1); end;
1365 procedure writeIntBE (st: TStream; v: Word); overload; begin writeIntegerBE(st, @v, 2); end;
1366 procedure writeIntBE (st: TStream; v: SmallInt); overload; begin writeIntegerBE(st, @v, 2); end;
1367 procedure writeIntBE (st: TStream; v: LongWord); overload; begin writeIntegerBE(st, @v, 4); end;
1368 procedure writeIntBE (st: TStream; v: LongInt); overload; begin writeIntegerBE(st, @v, 4); end;
1369 procedure writeIntBE (st: TStream; v: Int64); overload; begin writeIntegerBE(st, @v, 8); end;
1370 procedure writeIntBE (st: TStream; v: UInt64); overload; begin writeIntegerBE(st, @v, 8); end;
1372 procedure writeBool (st: TStream; b: Boolean); begin writeInt(st, Byte(b)); end;
1373 function readBool (st: TStream): Boolean; begin result := (readByte(st) <> 0); end;
1376 procedure writeStr (st: TStream; const str: AnsiString; maxlen: LongWord=65535);
1377 begin
1378 if (Length(str) > maxlen) then raise XStreamError.Create('string too long');
1379 if (maxlen <= 65535) then writeInt(st, Word(Length(str))) else writeInt(st, LongWord(Length(str)));
1380 if (Length(str) > 0) then st.WriteBuffer(str[1], Length(str));
1381 end;
1383 function readStr (st: TStream; maxlen: LongWord=65535): AnsiString;
1384 var
1385 len: Integer;
1386 begin
1387 result := '';
1388 if (maxlen <= 65535) then len := readWord(st) else len := Integer(readLongWord(st));
1389 if (len < 0) or (len > maxlen) then raise XStreamError.Create('string too long');
1390 if (len > 0) then
1391 begin
1392 SetLength(result, len);
1393 st.ReadBuffer(result[1], len);
1394 end;
1395 end;
1398 procedure readIntegerLE (st: TStream; vp: Pointer; size: Integer);
1399 {$IFDEF ENDIAN_LITTLE}
1400 begin
1401 st.readBuffer(vp^, size);
1402 end;
1403 {$ELSE}
1404 var
1405 p: PByte;
1406 begin
1407 p := PByte(vp)+size-1;
1408 while size > 0 do
1409 begin
1410 st.readBuffer(p^, 1);
1411 Dec(size);
1412 Dec(p);
1413 end;
1414 end;
1415 {$ENDIF}
1417 procedure readIntegerBE (st: TStream; vp: Pointer; size: Integer);
1418 {$IFDEF ENDIAN_LITTLE}
1419 var
1420 p: PByte;
1421 begin
1422 p := PByte(vp)+size-1;
1423 while size > 0 do
1424 begin
1425 st.readBuffer(p^, 1);
1426 Dec(size);
1427 Dec(p);
1428 end;
1429 end;
1430 {$ELSE}
1431 begin
1432 st.readBuffer(vp^, size);
1433 end;
1434 {$ENDIF}
1436 function readByte (st: TStream): Byte; begin readIntegerLE(st, @result, 1); end;
1437 function readShortInt (st: TStream): ShortInt; begin readIntegerLE(st, @result, 1); end;
1438 function readWord (st: TStream): Word; begin readIntegerLE(st, @result, 2); end;
1439 function readSmallInt (st: TStream): SmallInt; begin readIntegerLE(st, @result, 2); end;
1440 function readLongWord (st: TStream): LongWord; begin readIntegerLE(st, @result, 4); end;
1441 function readLongInt (st: TStream): LongInt; begin readIntegerLE(st, @result, 4); end;
1442 function readInt64 (st: TStream): Int64; begin readIntegerLE(st, @result, 8); end;
1443 function readUInt64 (st: TStream): UInt64; begin readIntegerLE(st, @result, 8); end;
1445 function readByteBE (st: TStream): Byte; begin readIntegerBE(st, @result, 1); end;
1446 function readShortIntBE (st: TStream): ShortInt; begin readIntegerBE(st, @result, 1); end;
1447 function readWordBE (st: TStream): Word; begin readIntegerBE(st, @result, 2); end;
1448 function readSmallIntBE (st: TStream): SmallInt; begin readIntegerBE(st, @result, 2); end;
1449 function readLongWordBE (st: TStream): LongWord; begin readIntegerBE(st, @result, 4); end;
1450 function readLongIntBE (st: TStream): LongInt; begin readIntegerBE(st, @result, 4); end;
1451 function readInt64BE (st: TStream): Int64; begin readIntegerBE(st, @result, 8); end;
1452 function readUInt64BE (st: TStream): UInt64; begin readIntegerBE(st, @result, 8); end;
1455 // ////////////////////////////////////////////////////////////////////////// //
1456 function nmin (a, b: Byte): Byte; inline; overload; begin if (a < b) then result := a else result := b; end;
1457 function nmin (a, b: ShortInt): ShortInt; inline; overload; begin if (a < b) then result := a else result := b; end;
1458 function nmin (a, b: Word): Word; inline; overload; begin if (a < b) then result := a else result := b; end;
1459 function nmin (a, b: SmallInt): SmallInt; inline; overload; begin if (a < b) then result := a else result := b; end;
1460 function nmin (a, b: LongWord): LongWord; inline; overload; begin if (a < b) then result := a else result := b; end;
1461 function nmin (a, b: LongInt): LongInt; inline; overload; begin if (a < b) then result := a else result := b; end;
1462 function nmin (a, b: Int64): Int64; inline; overload; begin if (a < b) then result := a else result := b; end;
1463 function nmin (a, b: UInt64): UInt64; inline; overload; begin if (a < b) then result := a else result := b; end;
1464 function nmin (a, b: Single): Single; inline; overload; begin if (a < b) then result := a else result := b; end;
1465 function nmin (a, b: Double): Double; inline; overload; begin if (a < b) then result := a else result := b; end;
1466 {$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)}
1467 function nmin (a, b: Extended): Extended; inline; overload; begin if (a < b) then result := a else result := b; end;
1468 {$ENDIF}
1470 function nmax (a, b: Byte): Byte; inline; overload; begin if (a > b) then result := a else result := b; end;
1471 function nmax (a, b: ShortInt): ShortInt; inline; overload; begin if (a > b) then result := a else result := b; end;
1472 function nmax (a, b: Word): Word; inline; overload; begin if (a > b) then result := a else result := b; end;
1473 function nmax (a, b: SmallInt): SmallInt; inline; overload; begin if (a > b) then result := a else result := b; end;
1474 function nmax (a, b: LongWord): LongWord; inline; overload; begin if (a > b) then result := a else result := b; end;
1475 function nmax (a, b: LongInt): LongInt; inline; overload; begin if (a > b) then result := a else result := b; end;
1476 function nmax (a, b: Int64): Int64; inline; overload; begin if (a > b) then result := a else result := b; end;
1477 function nmax (a, b: UInt64): UInt64; inline; overload; begin if (a > b) then result := a else result := b; end;
1478 function nmax (a, b: Single): Single; inline; overload; begin if (a > b) then result := a else result := b; end;
1479 function nmax (a, b: Double): Double; inline; overload; begin if (a > b) then result := a else result := b; end;
1480 {$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)}
1481 function nmax (a, b: Extended): Extended; inline; overload; begin if (a > b) then result := a else result := b; end;
1482 {$ENDIF}
1484 function nclamp (v, a, b: Byte): Byte; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1485 function nclamp (v, a, b: ShortInt): ShortInt; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1486 function nclamp (v, a, b: Word): Word; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1487 function nclamp (v, a, b: SmallInt): SmallInt; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1488 function nclamp (v, a, b: LongWord): LongWord; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1489 function nclamp (v, a, b: LongInt): LongInt; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1490 function nclamp (v, a, b: Int64): Int64; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1491 function nclamp (v, a, b: UInt64): UInt64; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1492 function nclamp (v, a, b: Single): Single; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1493 function nclamp (v, a, b: Double): Double; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1494 {$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)}
1495 function nclamp (v, a, b: Extended): Extended; inline; overload; begin if (v < a) then result := a else if (v > b) then result := b else result := v; end;
1496 {$ENDIF}
1498 // ////////////////////////////////////////////////////////////////////////// //
1499 {$IFDEF WINDOWS}
1500 function snprintf (buf: PAnsiChar; bufsize: SizeUInt; const fmt: PAnsiChar): SizeUInt; cdecl; varargs; external 'msvcrt.dll' name '_snprintf';
1501 {$ELSE}
1502 function snprintf (buf: PAnsiChar; bufsize: SizeUInt; const fmt: PAnsiChar): SizeUInt; cdecl; varargs; external 'libc' name 'snprintf';
1503 {$ENDIF}
1506 (*
1507 procedure conwriter (constref buf; len: SizeUInt);
1508 var
1509 ss: ShortString;
1510 slen: Integer;
1511 b: PByte;
1512 begin
1513 if (len < 1) then exit;
1514 b := PByte(@buf);
1515 while (len > 0) do
1516 begin
1517 if (len > 255) then slen := 255 else slen := Integer(len);
1518 Move(b^, ss[1], len);
1519 ss[0] := AnsiChar(slen);
1520 write(ss);
1521 b += slen;
1522 len -= slen;
1523 end;
1524 end;
1525 *)
1528 function formatstrf (const fmt: AnsiString; const args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString;
1529 const
1530 PadSpaces: AnsiString = ' ';
1531 PadZeroes: AnsiString = '00000000000000000000000000000000000000000000000000000000000000000000000';
1532 var
1533 curarg: Integer = 0; // current arg in `args`
1534 sign, fmtch: AnsiChar;
1535 zeropad: Boolean;
1536 width, prec: Integer; // width and precision
1537 spos, epos: Integer;
1538 ch: AnsiChar;
1539 strbuf: array[0..256] of AnsiChar;
1540 strblen: SizeUInt;
1541 fmtbuf: array[0..256] of AnsiChar;
1542 fmtblen: Integer;
1543 pclen: Integer;
1544 pc: PAnsiChar;
1545 ccname: ShortString;
1547 procedure writer (constref buf; len: SizeUInt);
1548 var
1549 ss: ShortString;
1550 slen: Integer;
1551 b: PByte;
1552 begin
1553 if (len < 1) then exit;
1554 b := PByte(@buf);
1555 if assigned(writerCB) then
1556 begin
1557 writerCB(b^, len);
1558 end
1559 else
1560 begin
1561 while (len > 0) do
1562 begin
1563 if (len > 255) then slen := 255 else slen := Integer(len);
1564 Move(b^, ss[1], slen);
1565 ss[0] := AnsiChar(slen);
1566 result += ss;
1567 b += slen;
1568 len -= slen;
1569 end;
1570 end;
1571 end;
1573 procedure xwrite (const s: AnsiString);
1574 begin
1575 if (Length(s) > 0) then writer(PAnsiChar(s)^, Length(s));
1576 end;
1578 procedure putFmtChar (ch: AnsiChar);
1579 begin
1580 fmtbuf[fmtblen] := ch;
1581 Inc(fmtblen);
1582 end;
1584 procedure putFmtInt (n: Integer);
1585 var
1586 len: SizeUInt;
1587 begin
1588 len := snprintf(@fmtbuf[fmtblen], Length(fmtbuf)-fmtblen, '%d', n);
1589 if (len > 0) then Inc(fmtblen, len);
1590 end;
1592 procedure buildCFormat (const pfx: AnsiString='');
1593 var
1594 f: Integer;
1595 begin
1596 fmtblen := 0;
1597 for f := 1 to Length(pfx) do putFmtChar(pfx[f]);
1598 putFmtChar('%');
1599 if (sign <> ' ') then putFmtChar(sign);
1600 if (width >= 0) then
1601 begin
1602 if (zeropad) then putFmtChar('0');
1603 putFmtInt(width);
1604 if (prec >= 0) then
1605 begin
1606 putFmtChar('.');
1607 putFmtInt(prec);
1608 end;
1609 end;
1610 putFmtChar(fmtch);
1611 fmtbuf[fmtblen] := #0;
1612 end;
1614 procedure writeStrBuf ();
1615 begin
1616 if (strblen > 0) then writer(strbuf, strblen);
1617 end;
1619 function i642str (n: Int64; hex: Boolean; hexup: Boolean): PAnsiChar;
1620 var
1621 neg: Boolean;
1622 xpos: Integer;
1623 begin
1624 if (n = $8000000000000000) then
1625 begin
1626 if hex then snprintf(@strbuf[0], Length(strbuf), '-8000000000000000')
1627 else snprintf(@strbuf[0], Length(strbuf), '-9223372036854775808');
1628 result := @strbuf[0];
1629 end
1630 else
1631 begin
1632 neg := (n < 0);
1633 if neg then n := -n;
1634 xpos := High(strbuf);
1635 strbuf[xpos] := #0; Dec(xpos);
1636 repeat
1637 if not hex then
1638 begin
1639 strbuf[xpos] := AnsiChar((n mod 10)+48);
1640 Dec(xpos);
1641 n := n div 10;
1642 end
1643 else
1644 begin
1645 if (n mod 16 > 9) then
1646 begin
1647 strbuf[xpos] := AnsiChar((n mod 16)+48+7);
1648 if not hexup then Inc(strbuf[xpos], 32);
1649 end
1650 else strbuf[xpos] := AnsiChar((n mod 16)+48);
1651 Dec(xpos);
1652 n := n div 16;
1653 end;
1654 until (n = 0);
1655 if neg then begin strbuf[xpos] := '-'; Dec(xpos); end;
1656 result := @strbuf[xpos+1];
1657 end;
1658 end;
1660 function ui642str (n: UInt64; hex: Boolean; hexup: Boolean): PAnsiChar;
1661 var
1662 xpos: Integer;
1663 begin
1664 xpos := High(strbuf);
1665 strbuf[xpos] := #0; Dec(xpos);
1666 repeat
1667 if not hex then
1668 begin
1669 strbuf[xpos] := AnsiChar((n mod 10)+48);
1670 Dec(xpos);
1671 n := n div 10;
1672 end
1673 else
1674 begin
1675 if (n mod 16 > 9) then
1676 begin
1677 strbuf[xpos] := AnsiChar((n mod 16)+48+7);
1678 if not hexup then Inc(strbuf[xpos], 32);
1679 end
1680 else strbuf[xpos] := AnsiChar((n mod 16)+48);
1681 Dec(xpos);
1682 n := n div 16;
1683 end;
1684 until (n = 0);
1685 result := @strbuf[xpos+1];
1686 end;
1688 procedure indent (len: Integer);
1689 var
1690 ilen: Integer;
1691 begin
1692 while (len > 0) do
1693 begin
1694 if (len > Length(PadSpaces)) then ilen := Length(PadSpaces) else ilen := len;
1695 writer(PAnsiChar(PadSpaces)^, ilen);
1696 Dec(len, ilen);
1697 end;
1698 end;
1700 procedure indent0 (len: Integer);
1701 var
1702 ilen: Integer;
1703 begin
1704 while (len > 0) do
1705 begin
1706 if (len > Length(PadZeroes)) then ilen := Length(PadZeroes) else ilen := len;
1707 writer(PAnsiChar(PadZeroes)^, ilen);
1708 Dec(len, ilen);
1709 end;
1710 end;
1712 begin
1713 result := '';
1714 spos := 1;
1715 while (spos <= Length(fmt)) do
1716 begin
1717 // print literal part
1718 epos := spos;
1719 while (epos <= Length(fmt)) and (fmt[epos] <> '%') do Inc(epos);
1720 // output literal part
1721 if (epos > spos) then
1722 begin
1723 if (epos > Length(fmt)) then
1724 begin
1725 writer((PAnsiChar(fmt)+spos-1)^, epos-spos);
1726 break;
1727 end;
1728 if (epos+1 > Length(fmt)) then Inc(epos) // last percent, output literally
1729 else if (fmt[epos+1] = '%') then // special case
1730 begin
1731 Inc(epos);
1732 writer((PAnsiChar(fmt)+spos-1)^, epos-spos);
1733 spos := epos+1;
1734 end
1735 else
1736 begin
1737 writer((PAnsiChar(fmt)+spos-1)^, epos-spos);
1738 spos := epos;
1739 end;
1740 continue;
1741 end;
1742 // check if we have argument for this format string
1743 if (curarg > High(args)) then
1744 begin
1745 xwrite('<OUT OF ARGS>');
1746 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1747 break;
1748 end;
1749 // skip percent
1750 if (spos+1 > Length(fmt)) then break; // oops
1751 assert(fmt[spos] = '%');
1752 Inc(spos);
1753 // parse format; check for sign
1754 if (fmt[spos] = '-') then begin sign := '-'; Inc(spos); end
1755 else if (fmt[spos] = '+') then begin sign := '+'; Inc(spos); end
1756 else sign := ' ';
1757 // parse width
1758 if (spos > Length(fmt)) then begin xwrite('<INVALID FORMAT>'); break; end;
1759 if (sign <> ' ') or ((fmt[spos] >= '0') and (fmt[spos] <= '9')) then
1760 begin
1761 if (fmt[spos] < '0') or (fmt[spos] > '9') then begin xwrite('<INVALID FORMAT>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1762 zeropad := (fmt[spos] = '0');
1763 width := 0;
1764 while (spos <= Length(fmt)) do
1765 begin
1766 ch := fmt[spos];
1767 if (ch < '0') or (ch > '9') then break;
1768 width := width*10+Integer(ch)-48;
1769 Inc(spos);
1770 end;
1771 end
1772 else
1773 begin
1774 width := -1;
1775 zeropad := false;
1776 end;
1777 // parse precision
1778 prec := -1;
1779 if (spos <= Length(fmt)) and (fmt[spos] = '.') then
1780 begin
1781 Inc(spos);
1782 if (spos > Length(fmt)) then begin xwrite('<INVALID FORMAT>'); break; end;
1783 if (fmt[spos] < '0') or (fmt[spos] > '9') then begin xwrite('<INVALID FORMAT>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1784 prec := 0;
1785 while (spos <= Length(fmt)) do
1786 begin
1787 ch := fmt[spos];
1788 if (ch < '0') or (ch > '9') then break;
1789 prec := prec*10+Integer(ch)-48;
1790 Inc(spos);
1791 end;
1792 end;
1793 // get format char
1794 if (spos > Length(fmt)) then begin xwrite('<INVALID FORMAT>'); break; end;
1795 fmtch := fmt[spos];
1796 Inc(spos);
1797 // done parsing format, check for valid format chars
1798 if not (fmtch in ['s','u','d','x','X','p','f','g','c']) then begin xwrite('<INVALID FORMAT CHAR>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1799 // now write formatted string
1800 case args[curarg].VType of
1801 vtInteger: // args[curarg].VInteger
1802 begin
1803 if not (fmtch in ['s','u','d','x','X']) then begin xwrite('<INVALID FORMAT CHAR>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1804 if (fmtch = 's') then fmtch := 'd';
1805 buildCFormat();
1806 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], args[curarg].VInteger);
1807 writeStrBuf();
1808 end;
1809 vtBoolean: // args[curarg].VBoolean
1810 case fmtch of
1811 's':
1812 begin
1813 buildCFormat();
1814 if args[curarg].VBoolean then strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], 'true')
1815 else strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], 'false');
1816 writeStrBuf();
1817 end;
1818 'c':
1819 begin
1820 buildCFormat();
1821 if args[curarg].VBoolean then strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], AnsiChar('t'))
1822 else strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], AnsiChar('f'));
1823 writeStrBuf();
1824 end;
1825 'u', 'd', 'x', 'X':
1826 begin
1827 buildCFormat();
1828 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], Integer(args[curarg].VBoolean));
1829 writeStrBuf();
1830 end;
1831 else
1832 begin
1833 xwrite('<INVALID FORMAT CHAR>');
1834 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1835 break;
1836 end;
1837 end;
1838 vtChar: // args[curarg].VChar
1839 case fmtch of
1840 's', 'c':
1841 begin
1842 fmtch := 'c';
1843 buildCFormat();
1844 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], args[curarg].VChar);
1845 writeStrBuf();
1846 end;
1847 'u', 'd', 'x', 'X':
1848 begin
1849 buildCFormat();
1850 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], Integer(args[curarg].VChar));
1851 writeStrBuf();
1852 end;
1853 else
1854 begin
1855 xwrite('<INVALID FORMAT CHAR>');
1856 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1857 break;
1858 end;
1859 end;
1860 //vtWideChar: begin end; // args[curarg].VWideChar (WideChar)
1861 vtExtended: // args[curarg].VExtended^
1862 case fmtch of
1863 's', 'g':
1864 begin
1865 fmtch := 'g';
1866 buildCFormat();
1867 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], Double(args[curarg].VExtended^));
1868 writeStrBuf();
1869 end;
1870 'f':
1871 begin
1872 buildCFormat();
1873 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], Double(args[curarg].VExtended^));
1874 writeStrBuf();
1875 end;
1876 'd':
1877 begin
1878 buildCFormat();
1879 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], Integer(trunc(args[curarg].VExtended^)));
1880 writeStrBuf();
1881 end;
1882 'u', 'x', 'X':
1883 begin
1884 buildCFormat();
1885 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], LongWord(trunc(args[curarg].VExtended^)));
1886 writeStrBuf();
1887 end;
1888 else
1889 begin
1890 xwrite('<INVALID FORMAT CHAR>');
1891 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1892 break;
1893 end;
1894 end;
1895 vtString: // args[curarg].VString^ (PShortString)
1896 begin
1897 if (sign <> '-') then indent(width-Length(args[curarg].VString^));
1898 writer(args[curarg].VString^[1], Length(args[curarg].VString^));
1899 if (sign = '-') then indent(width-Length(args[curarg].VString^));
1900 end;
1901 vtPointer: // args[curarg].VPointer
1902 case fmtch of
1903 's':
1904 begin
1905 fmtch := 'x';
1906 if (width < 8) then width := 8;
1907 zeropad := true;
1908 buildCFormat('0x');
1909 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], PtrUInt(args[curarg].VPointer));
1910 writeStrBuf();
1911 end;
1912 'u', 'd', 'x', 'p', 'X':
1913 begin
1914 if (fmtch = 'p') then fmtch := 'x';
1915 if (width < 8) then width := 8;
1916 zeropad := true;
1917 buildCFormat('0x');
1918 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], PtrUInt(args[curarg].VPointer));
1919 writeStrBuf();
1920 end;
1921 else
1922 begin
1923 xwrite('<INVALID FORMAT CHAR>');
1924 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1925 break;
1926 end;
1927 end;
1928 vtPChar: // args[curarg].VPChar
1929 if (args[curarg].VPChar = nil) then
1930 begin
1931 if (sign <> '-') then indent(width-3);
1932 xwrite('nil');
1933 if (sign = '-') then indent(width-3);
1934 end
1935 else
1936 begin
1937 pclen := 0;
1938 while (args[curarg].VPChar[pclen] <> #0) do Inc(pclen);
1939 if (sign <> '-') then indent(width-pclen);
1940 writer(args[curarg].VPChar^, pclen);
1941 if (sign = '-') then indent(width-pclen);
1942 end;
1943 vtObject: // args[curarg].VObject.Classname (TObject)
1944 begin
1945 if (args[curarg].VObject <> nil) then ccname := args[curarg].VObject.Classname else ccname := '<nil>';
1946 if (sign <> '-') then indent(width-Length(ccname));
1947 xwrite(ccname);
1948 if (sign = '-') then indent(width-Length(ccname));
1949 end;
1950 vtClass: // args[curarg].VClass.Classname (TClass)
1951 begin
1952 if (args[curarg].VClass <> nil) then ccname := args[curarg].VClass.Classname else ccname := '<nil>';
1953 if (sign <> '-') then indent(width-Length(ccname));
1954 xwrite(ccname);
1955 if (sign = '-') then indent(width-Length(ccname));
1956 end;
1957 //vtPWideChar: begin end; // args[curarg].VPWideChar (PWideChar)
1958 vtAnsiString: // AnsiString(args[curarg].VAnsiString) (Pointer)
1959 begin
1960 if (sign <> '-') then indent(width-Length(AnsiString(args[curarg].VAnsiString)));
1961 xwrite(AnsiString(args[curarg].VAnsiString));
1962 if (sign = '-') then indent(width-Length(AnsiString(args[curarg].VAnsiString)));
1963 end;
1964 //vtCurrency: begin end; // args[curarg].VCurrency (PCurrency)
1965 //vtVariant: begin end; // args[curarg].VVariant^ (PVariant)
1966 //vtInterface: begin end; // args[curarg].VInterface (Pointer);
1967 //vtWideString: begin end; // args[curarg].VWideString (Pointer);
1968 vtInt64: // args[curarg].VInt64^ (PInt64)
1969 begin
1970 case fmtch of
1971 's','d','u': pc := i642str(args[curarg].VInt64^, false, false);
1972 'x': pc := i642str(args[curarg].VInt64^, true, false);
1973 'X': pc := i642str(args[curarg].VInt64^, true, true);
1974 else begin xwrite('<INVALID FORMAT CHAR>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1975 end;
1976 pclen := 0;
1977 while (pc[pclen] <> #0) do Inc(pclen);
1978 if (sign <> '-') and (width > pclen) then
1979 begin
1980 if zeropad then
1981 begin
1982 if (pc[0] = '-') or (pc[0] = '+') then
1983 begin
1984 writer(pc^, 1);
1985 indent0(width-pclen-1);
1986 Inc(pc);
1987 Dec(pclen);
1988 end
1989 else
1990 begin
1991 indent0(width-pclen);
1992 end;
1993 end
1994 else
1995 begin
1996 indent(width-pclen);
1997 end;
1998 end;
1999 writer(pc^, pclen);
2000 if (sign = '-') then indent(width-pclen);
2001 end;
2002 vtQWord: // args[curarg].VQWord^ (PQWord)
2003 begin
2004 case fmtch of
2005 's','d','u': pc := ui642str(args[curarg].VInt64^, false, false);
2006 'x': pc := ui642str(args[curarg].VInt64^, true, false);
2007 'X': pc := ui642str(args[curarg].VInt64^, true, true);
2008 else begin xwrite('<INVALID FORMAT CHAR>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
2009 end;
2010 pclen := 0;
2011 while (pc[pclen] <> #0) do Inc(pclen);
2012 if (sign <> '-') then begin if zeropad then indent0(width-pclen) else indent(width-pclen); end;
2013 writer(pc^, pclen);
2014 if (sign = '-') then indent(width-pclen);
2015 end;
2016 else
2017 begin
2018 xwrite('<INVALID TYPE>');
2019 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
2020 break;
2021 end;
2022 end;
2023 Inc(curarg);
2024 end;
2025 end;
2028 function GetDiskFileInfo (fname: AnsiString; var info: TDiskFileInfo): Boolean;
2029 var
2030 age: LongInt;
2031 size: LongInt;
2032 handle: THandle;
2033 begin
2034 result := false;
2035 if (length(fname) = 0) then exit;
2036 if not findFileCI(fname) then exit;
2037 // get age
2038 age := FileAge(fname);
2039 if (age = -1) then exit;
2040 // get size
2041 handle := FileOpen(fname, fmOpenRead or fmShareDenyNone);
2042 if (handle = THandle(-1)) then exit;
2043 size := FileSeek(handle, 0, fsFromEnd);
2044 FileClose(handle);
2045 if (size = -1) then exit;
2046 // fill info
2047 info.diskName := fname;
2048 info.size := size;
2049 info.age := age;
2050 result := true;
2051 end;
2054 (*
2055 var
2056 ss: ShortString;
2057 ls: AnsiString;
2058 i64: Int64 = -$A000000000;
2059 ui64: UInt64 = $A000000000;
2060 begin
2061 writef(conwriter, 'test int:<%s> bool:<%s:%02d:%c> bool:<%s:%02d:%c>; char:<%2s;%c;%d>!'#10, [42, true, true, true, false, false, false, 'A', 'A', 'A']);
2062 writef(conwriter, 'test float:<%s;%u;%f;%g>'#10, [666.6942, 666.6942, 666.6942, 666.6942]);
2063 ss := 'fuckit';
2064 ls := 'FUCKIT';
2065 writef(conwriter, 'test ss:<%5s;%040s>'#10, [ss, ss]);
2066 writef(conwriter, 'test ls:<%5s;%040s>'#10, [ls, ls]);
2067 writef(conwriter, 'test pointer:<%s;%x;%p>'#10, [@ss, @ss, @ss]);
2068 writef(conwriter, 'test i64:<%s;%x;%015d;%u;%X>'#10, [i64, i64, i64, i64, i64]);
2069 writef(conwriter, 'test ui64:<%s;%x;%15d;%015u;%X>'#10, [ui64, ui64, ui64, ui64, ui64]);
2070 *)
2071 end.