X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fshared%2Futils.pas;h=89ecb9097fa605ac105f6b1e9cb4b5d6409f39ce;hb=a8ae9a0f86f53daa34053a6fd142935594a8da68;hp=1caa1f3338c12bb30327a45ac45a4abff404199b;hpb=b0369ee9442a79c9ace3454e7e1709cd61ed6a8e;p=d2df-sdl.git diff --git a/src/shared/utils.pas b/src/shared/utils.pas index 1caa1f3..89ecb90 100644 --- a/src/shared/utils.pas +++ b/src/shared/utils.pas @@ -1,4 +1,4 @@ -(* Copyright (C) DooM 2D:Forever Developers +(* Copyright (C) Doom 2D: Forever Developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +22,11 @@ uses SysUtils, Classes; +// ////////////////////////////////////////////////////////////////////////// // +type + SSArray = array of ShortString; + + // ////////////////////////////////////////////////////////////////////////// // type TUtf8DecoderFast = packed record @@ -76,13 +81,15 @@ function isWadPath (const fn: AnsiString): Boolean; function addWadExtension (const fn: AnsiString): AnsiString; // convert number to strig with nice commas -function Int64ToStrComma (i: Int64): AnsiString; +function int64ToStrComma (i: Int64): AnsiString; -function UpCase1251 (ch: Char): Char; -function LoCase1251 (ch: Char): Char; +function upcase1251 (ch: AnsiChar): AnsiChar; inline; +function locase1251 (ch: AnsiChar): AnsiChar; inline; + +function toLowerCase1251 (const s: AnsiString): AnsiString; // `true` if strings are equal; ignoring case for cp1251 -function StrEquCI1251 (const s0, s1: AnsiString): Boolean; +function strEquCI1251 (const s0, s1: AnsiString): Boolean; function utf8Valid (const s: AnsiString): Boolean; @@ -93,11 +100,23 @@ function utf8to1251 (s: AnsiString): AnsiString; // nobody cares about shitdoze, so i'll use the same code path for it function findFileCI (var pathname: AnsiString; lastIsDir: Boolean=false): Boolean; +// return fixed AnsiString or empty AnsiString +function findDiskWad (fname: AnsiString): AnsiString; + // they throws function openDiskFileRO (pathname: AnsiString): TStream; function createDiskFile (pathname: AnsiString): TStream; // little endian +procedure writeSign (st: TStream; const sign: AnsiString); +function checkSign (st: TStream; const sign: AnsiString): Boolean; + +procedure writeBool (st: TStream; b: Boolean); +function readBool (st: TStream): Boolean; + +procedure writeStr (st: TStream; const str: AnsiString; maxlen: LongWord=65535); +function readStr (st: TStream; maxlen: LongWord=65535): AnsiString; + procedure writeInt (st: TStream; v: Byte); overload; procedure writeInt (st: TStream; v: ShortInt); overload; procedure writeInt (st: TStream; v: Word); overload; @@ -146,7 +165,9 @@ function nmin (a, b: Int64): Int64; inline; overload; function nmin (a, b: UInt64): UInt64; inline; overload; function nmin (a, b: Single): Single; inline; overload; function nmin (a, b: Double): Double; inline; overload; +{$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)} function nmin (a, b: Extended): Extended; inline; overload; +{$ENDIF} function nmax (a, b: Byte): Byte; inline; overload; function nmax (a, b: ShortInt): ShortInt; inline; overload; @@ -158,8 +179,9 @@ function nmax (a, b: Int64): Int64; inline; overload; function nmax (a, b: UInt64): UInt64; inline; overload; function nmax (a, b: Single): Single; inline; overload; function nmax (a, b: Double): Double; inline; overload; +{$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)} function nmax (a, b: Extended): Extended; inline; overload; - +{$ENDIF} function nclamp (v, a, b: Byte): Byte; inline; overload; function nclamp (v, a, b: ShortInt): ShortInt; inline; overload; function nclamp (v, a, b: Word): Word; inline; overload; @@ -170,14 +192,15 @@ function nclamp (v, a, b: Int64): Int64; inline; overload; function nclamp (v, a, b: UInt64): UInt64; inline; overload; function nclamp (v, a, b: Single): Single; inline; overload; function nclamp (v, a, b: Double): Double; inline; overload; +{$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)} function nclamp (v, a, b: Extended): Extended; inline; overload; - +{$ENDIF} type TFormatStrFCallback = procedure (constref buf; len: SizeUInt); // returns formatted string if `writerCB` is `nil`, empty string otherwise -function formatstrf (const fmt: AnsiString; args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString; +function formatstrf (const fmt: AnsiString; const args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString; function wchar2win (wc: WideChar): AnsiChar; inline; function utf2win (const s: AnsiString): AnsiString; @@ -233,6 +256,8 @@ type procedure clear (); inline; procedure append (constref it: ItemT); inline; + procedure delete (idx: Integer); inline; + function remove (idx: Integer): ItemT; inline; public property count: Integer read mCount; @@ -241,8 +266,39 @@ type end; +procedure FillMemory (Dest: Pointer; Len: LongWord; Ch: Byte); inline; +procedure CopyMemory (Dest: Pointer; Src: Pointer; Len: LongWord); inline; +procedure ZeroMemory (Dest: Pointer; Len: LongWord); inline; + + implementation + uses + xstreams, StrUtils, e_Log; + + const + {$IFDEF GO32V2} + DirSep = '\'; + {$ELSE} + DirSep = '/'; + {$ENDIF} + +// ////////////////////////////////////////////////////////////////////////// // +procedure CopyMemory (Dest: Pointer; Src: Pointer; Len: LongWord); inline; +begin + Move(Src^, Dest^, Len); +end; + +procedure FillMemory (Dest: Pointer; Len: LongWord; Ch: Byte); inline; +begin + FillChar(Dest^, Len, Ch); +end; + +procedure ZeroMemory (Dest: Pointer; Len: LongWord); inline; +begin + FillChar(Dest^, Len, 0); +end; + // ////////////////////////////////////////////////////////////////////////// // constructor TSimpleList.TEnumerator.Create (const aitems: TItemArr; acount: Integer); @@ -326,16 +382,46 @@ end; procedure TSimpleList.append (constref it: ItemT); inline; +var + newsz: Integer; begin - if (mCount = Length(mItems)) then + if (mCount >= Length(mItems)) then begin - if (mCount = 0) then SetLength(mItems, 128) else SetLength(mItems, mCount*2); + newsz := mCount+(mCount div 3)+128; + SetLength(mItems, newsz); end; mItems[mCount] := it; Inc(mCount); end; +procedure TSimpleList.delete (idx: Integer); inline; +var + f: Integer; +begin + if (idx >= 0) and (idx < mCount) then + begin + for f := idx+1 to mCount-1 do mItems[f-1] := mItems[f]; + end; +end; + + +function TSimpleList.remove (idx: Integer): ItemT; inline; +var + f: Integer; +begin + if (idx >= 0) and (idx < mCount) then + begin + result := mItems[idx]; + for f := idx+1 to mCount-1 do mItems[f-1] := mItems[f]; + end + else + begin + result := Default(ItemT); + end; +end; + + // ////////////////////////////////////////////////////////////////////////// // var wc2shitmap: array[0..65535] of AnsiChar; @@ -464,25 +550,25 @@ var if (code < 0) or (code > $10FFFF) then begin result := '?'; exit; end; if (code <= $7f) then begin - result := Char(code and $ff); + result := AnsiChar(code and $ff); end else if (code <= $7FF) then begin - result := Char($C0 or (code shr 6)); - result += Char($80 or (code and $3F)); + result := AnsiChar($C0 or (code shr 6)); + result += AnsiChar($80 or (code and $3F)); end else if (code <= $FFFF) then begin - result := Char($E0 or (code shr 12)); - result += Char($80 or ((code shr 6) and $3F)); - result += Char($80 or (code and $3F)); + result := AnsiChar($E0 or (code shr 12)); + result += AnsiChar($80 or ((code shr 6) and $3F)); + result += AnsiChar($80 or (code and $3F)); end else if (code <= $10FFFF) then begin - result := Char($F0 or (code shr 18)); - result += Char($80 or ((code shr 12) and $3F)); - result += Char($80 or ((code shr 6) and $3F)); - result += Char($80 or (code and $3F)); + result := AnsiChar($F0 or (code shr 18)); + result += AnsiChar($80 or ((code shr 12) and $3F)); + result += AnsiChar($80 or ((code shr 6) and $3F)); + result += AnsiChar($80 or (code and $3F)); end else begin @@ -678,7 +764,7 @@ var pos: Integer; ch: AnsiChar; begin - if (Length(fn) = 0) then begin result := './'; exit; end; + if (Length(fn) = 0) then begin result := '.' + DirSep; exit; end; if (fn[Length(fn)] = '/') or (fn[Length(fn)] = '\') then begin result := fn; exit; end; pos := Length(fn); while (pos > 0) do @@ -687,7 +773,7 @@ begin if (ch = '/') or (ch = '\') then begin result := Copy(fn, 1, pos); exit; end; Dec(pos); end; - result := './'; // no path -> current dir + result := '.' + DirSep; // no path -> current dir end; @@ -714,7 +800,7 @@ begin pos := 1; while (pos <= Length(fn)) and ((fn[pos] = '/') or (fn[pos] = '\')) do Inc(pos); result := path; - if (Length(result) > 0) and ((result[Length(result)] <> '/') and (result[Length(result)] <> '\')) then result += '/'; + if (Length(result) > 0) and ((result[Length(result)] <> '/') and (result[Length(result)] <> '\')) then result += DirSep; if (pos <= Length(fn)) then begin result += Copy(fn, pos, Length(fn)-pos+1); @@ -723,7 +809,7 @@ begin begin Delete(result, Length(result), 1); end; - if (fn[Length(fn)] = '/') or (fn[Length(fn)] = '\') then result += '/'; + if (fn[Length(fn)] = '/') or (fn[Length(fn)] = '\') then result += DirSep; end; end; @@ -733,7 +819,7 @@ var ext: AnsiString; begin ext := getFilenameExt(fn); - result := StrEquCI1251(ext, '.wad') or StrEquCI1251(ext, '.pk3') or StrEquCI1251(ext, '.zip'); + result := StrEquCI1251(ext, '.wad') or StrEquCI1251(ext, '.pk3') or StrEquCI1251(ext, '.zip') or StrEquCI1251(ext, '.dfz'); end; @@ -759,7 +845,7 @@ begin if (pos-4 > 1) and (fn[pos-4] = '.') and ((fn[pos+1] = '\') or (fn[pos+1] = '/')) then begin s := Copy(fn, pos-4, 4); - if StrEquCI1251(s, '.wad') or StrEquCI1251(s, '.pk3') or StrEquCI1251(s, '.zip') then + if StrEquCI1251(s, '.wad') or StrEquCI1251(s, '.pk3') or StrEquCI1251(s, '.zip') or StrEquCI1251(s, '.dfz') then begin result := true; exit; @@ -771,7 +857,7 @@ begin end; -function Int64ToStrComma (i: Int64): AnsiString; +function int64ToStrComma (i: Int64): AnsiString; var f: Integer; begin @@ -784,7 +870,7 @@ begin end; -function UpCase1251 (ch: Char): Char; +function upcase1251 (ch: AnsiChar): AnsiChar; inline; begin if ch < #128 then begin @@ -808,7 +894,7 @@ begin end; -function LoCase1251 (ch: Char): Char; +function locase1251 (ch: AnsiChar): AnsiChar; inline; begin if ch < #128 then begin @@ -832,7 +918,7 @@ begin end; -function StrEquCI1251 (const s0, s1: AnsiString): Boolean; +function strEquCI1251 (const s0, s1: AnsiString): Boolean; var i: Integer; begin @@ -843,6 +929,26 @@ begin end; +function toLowerCase1251 (const s: AnsiString): AnsiString; +var + f: Integer; + ch: AnsiChar; +begin + for ch in s do + begin + if (ch <> LoCase1251(ch)) then + begin + result := ''; + SetLength(result, Length(s)); + for f := 1 to Length(s) do result[f] := LoCase1251(s[f]); + exit; + end; + end; + // nothing to do + result := s; +end; + + // ////////////////////////////////////////////////////////////////////////// // // utils // `ch`: utf8 start @@ -898,7 +1004,7 @@ const ); -function decodeUtf8Char (s: AnsiString; var pos: Integer): char; +function decodeUtf8Char (s: AnsiString; var pos: Integer): AnsiChar; var b, c: Integer; begin @@ -916,7 +1022,7 @@ begin b := Byte(s[pos]); Inc(pos); - if b < $80 then begin result := char(b); exit; end; + if b < $80 then begin result := AnsiChar(b); exit; end; // mask out unused bits if (b and $FE) = $FC then b := b and $01 @@ -937,7 +1043,7 @@ begin end; // done, try 1251 - for c := 128 to 255 do if uni2wint[c] = b then begin result := char(c and $FF); exit; end; + for c := 128 to 255 do if uni2wint[c] = b then begin result := AnsiChar(c and $FF); exit; end; // alas end; @@ -976,7 +1082,7 @@ var begin npt := pathname; result := (length(npt) > 0); - if (length(npt) > 0) and ((npt[1] = '/') or (npt[1] = '\')) then newname := '/'; + if (length(npt) > 0) and ((npt[1] = '/') or (npt[1] = '\')) then newname := DirSep; while length(npt) > 0 do begin // remove trailing slashes @@ -992,7 +1098,7 @@ begin // remove trailing slashes again while (length(npt) > 0) and ((npt[1] = '/') or (npt[1] = '\')) do Delete(npt, 1, 1); wantdir := lastIsDir or (length(npt) > 0); // do we want directory here? - //writeln(Format('npt=[%s]; newname=[%s]; curname=[%s]; wantdir=%d', [npt, newname, curname, Integer(wantdir)])); + //e_LogWritefln('npt=[%s]; newname=[%s]; curname=[%s]; wantdir=%d', [npt, newname, curname, Integer(wantdir)]); // try the easiest case first attr := FileGetAttr(newname+curname); if attr <> -1 then @@ -1001,11 +1107,11 @@ begin begin // i found her! newname := newname+curname; - if wantdir then newname := newname+'/'; + if wantdir then newname := newname + DirSep; continue; end; end; - //writeln(Format('npt=[%s]; newname=[%s]; curname=[%s]; wantdir=%d', [npt, newname, curname, Integer(wantdir)])); + //e_LogWritefLn('npt=[%s]; newname=[%s]; curname=[%s]; wantdir=%d', [npt, newname, curname, Integer(wantdir)]); // alas, either not found, or invalid attributes foundher := false; try @@ -1015,7 +1121,7 @@ begin begin // i found her! newname := newname+sr.name; - if wantdir then newname := newname+'/'; + if wantdir then newname := newname + DirSep; foundher := true; break; end; @@ -1029,8 +1135,52 @@ begin end; +(** Replace slashes to backslashes for DOS **) +function FixFileName (filename: AnsiString): AnsiString; +begin + {$IFDEF GO32V2} + Result := StringReplace(filename, '/', '\', [rfReplaceAll, rfIgnoreCase]) + {$ELSE} + Result := filename + {$ENDIF} +end; + + +const fileExtensions: array [0..6] of AnsiString = ('.wad', '.dfzip', '.dfwad', '.pk3', '.pak', '.zip', '.dfz'); + +function findDiskWad (fname: AnsiString): AnsiString; +var + origExt: AnsiString = ''; + newExt: AnsiString = ''; +begin + result := ''; +{$IFDEF GO32V2} + // FIXIT: it didn't work under MSDOS for some reason, so i just cut extension replacement + result := FixFileName(fname); +{$ELSE} + //e_LogWriteLn('findDiskWad00: fname=<' + fname + '>'); + if (findFileCI(fname)) then begin result := fname; exit; end; + origExt := getFilenameExt(fname); + fname := forceFilenameExt(fname, ''); + //e_LogWriteLn(' findDiskWad01: fname=<' + fname + '>; origExt=<' + origExt + '>'); + for newExt in fileExtensions do + begin + //e_LogWriteLn(' findDiskWad02: fname=<' + fname + '>; origExt=<' + origExt + '>; newExt=<' + newExt + '>'); + if (StrEquCI1251(newExt, origExt)) then + begin + //e_LogWriteLn(' SKIP'); + continue; + end; + result := fname+newExt; + if (findFileCI(result)) then exit; + end; + result := ''; +{$ENDIF} +end; + function openDiskFileRO (pathname: AnsiString): TStream; begin + pathname := FixFileName(pathname); if not findFileCI(pathname) then raise Exception.Create('can''t open file "'+pathname+'"'); result := TFileStream.Create(pathname, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone); end; @@ -1039,6 +1189,7 @@ function createDiskFile (pathname: AnsiString): TStream; var path: AnsiString; begin + pathname := FixFileName(pathname); path := ExtractFilePath(pathname); if length(path) > 0 then begin @@ -1086,6 +1237,36 @@ begin end; {$ENDIF} +procedure writeSign (st: TStream; const sign: AnsiString); +begin + if (Length(sign) > 0) then st.WriteBuffer(sign[1], Length(sign)); +end; + +function checkSign (st: TStream; const sign: AnsiString): Boolean; +var + buf: packed array[0..7] of AnsiChar; + f: Integer; +begin + result := false; + if (Length(sign) > 0) then + begin + if (Length(sign) <= 8) then + begin + st.ReadBuffer(buf[0], Length(sign)); + for f := 1 to Length(sign) do if (buf[f-1] <> sign[f]) then exit; + end + else + begin + for f := 1 to Length(sign) do + begin + st.ReadBuffer(buf[0], 1); + if (buf[0] <> sign[f]) then exit; + end; + end; + end; + result := true; +end; + procedure writeInt (st: TStream; v: Byte); overload; begin writeIntegerLE(st, @v, 1); end; procedure writeInt (st: TStream; v: ShortInt); overload; begin writeIntegerLE(st, @v, 1); end; procedure writeInt (st: TStream; v: Word); overload; begin writeIntegerLE(st, @v, 2); end; @@ -1104,6 +1285,31 @@ procedure writeIntBE (st: TStream; v: LongInt); overload; begin writeIntegerBE(s procedure writeIntBE (st: TStream; v: Int64); overload; begin writeIntegerBE(st, @v, 8); end; procedure writeIntBE (st: TStream; v: UInt64); overload; begin writeIntegerBE(st, @v, 8); end; +procedure writeBool (st: TStream; b: Boolean); begin writeInt(st, Byte(b)); end; +function readBool (st: TStream): Boolean; begin result := (readByte(st) <> 0); end; + + +procedure writeStr (st: TStream; const str: AnsiString; maxlen: LongWord=65535); +begin + if (Length(str) > maxlen) then raise XStreamError.Create('string too long'); + if (maxlen <= 65535) then writeInt(st, Word(Length(str))) else writeInt(st, LongWord(Length(str))); + if (Length(str) > 0) then st.WriteBuffer(str[1], Length(str)); +end; + +function readStr (st: TStream; maxlen: LongWord=65535): AnsiString; +var + len: Integer; +begin + result := ''; + if (maxlen <= 65535) then len := readWord(st) else len := Integer(readLongWord(st)); + if (len < 0) or (len > maxlen) then raise XStreamError.Create('string too long'); + if (len > 0) then + begin + SetLength(result, len); + st.ReadBuffer(result[1], len); + end; +end; + procedure readIntegerLE (st: TStream; vp: Pointer; size: Integer); {$IFDEF ENDIAN_LITTLE} @@ -1173,7 +1379,9 @@ function nmin (a, b: Int64): Int64; inline; overload; begin if (a < b) then resu function nmin (a, b: UInt64): UInt64; inline; overload; begin if (a < b) then result := a else result := b; end; function nmin (a, b: Single): Single; inline; overload; begin if (a < b) then result := a else result := b; end; function nmin (a, b: Double): Double; inline; overload; begin if (a < b) then result := a else result := b; end; +{$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)} function nmin (a, b: Extended): Extended; inline; overload; begin if (a < b) then result := a else result := b; end; +{$ENDIF} function nmax (a, b: Byte): Byte; inline; overload; begin if (a > b) then result := a else result := b; end; function nmax (a, b: ShortInt): ShortInt; inline; overload; begin if (a > b) then result := a else result := b; end; @@ -1185,7 +1393,9 @@ function nmax (a, b: Int64): Int64; inline; overload; begin if (a > b) then resu function nmax (a, b: UInt64): UInt64; inline; overload; begin if (a > b) then result := a else result := b; end; function nmax (a, b: Single): Single; inline; overload; begin if (a > b) then result := a else result := b; end; function nmax (a, b: Double): Double; inline; overload; begin if (a > b) then result := a else result := b; end; +{$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)} function nmax (a, b: Extended): Extended; inline; overload; begin if (a > b) then result := a else result := b; end; +{$ENDIF} 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; 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; @@ -1197,14 +1407,19 @@ function nclamp (v, a, b: Int64): Int64; inline; overload; begin if (v < a) then 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; 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; 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; +{$IF DEFINED(CPU386) OR DEFINED(CPUAMD64)} 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; - +{$ENDIF} // ////////////////////////////////////////////////////////////////////////// // {$IFDEF WINDOWS} function snprintf (buf: PAnsiChar; bufsize: SizeUInt; const fmt: PAnsiChar): SizeUInt; cdecl; varargs; external 'msvcrt.dll' name '_snprintf'; {$ELSE} + {$IFDEF GO32V2} +function snprintf (buf: PAnsiChar; bufsize: SizeUInt; const fmt: PAnsiChar): SizeUInt; cdecl; varargs; external; + {$ELSE} function snprintf (buf: PAnsiChar; bufsize: SizeUInt; const fmt: PAnsiChar): SizeUInt; cdecl; varargs; external 'libc' name 'snprintf'; + {$ENDIF} {$ENDIF} @@ -1230,7 +1445,7 @@ end; *) -function formatstrf (const fmt: AnsiString; args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString; +function formatstrf (const fmt: AnsiString; const args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString; const PadSpaces: AnsiString = ' '; PadZeroes: AnsiString = '00000000000000000000000000000000000000000000000000000000000000000000000'; @@ -1339,7 +1554,7 @@ var xpos := High(strbuf); strbuf[xpos] := #0; Dec(xpos); repeat - if hex then + if not hex then begin strbuf[xpos] := AnsiChar((n mod 10)+48); Dec(xpos); @@ -1369,7 +1584,7 @@ var xpos := High(strbuf); strbuf[xpos] := #0; Dec(xpos); repeat - if hex then + if not hex then begin strbuf[xpos] := AnsiChar((n mod 10)+48); Dec(xpos);