hedgewars/uUtils.pas
author Simon McVittie <smcv@debian.org>
Mon, 12 Sep 2022 10:40:53 -0400
branch1.0.0
changeset 15881 7b1d6dfa3173
parent 14969 f27f00fabc0d
child 15897 d7b53d08ad9c
permissions -rw-r--r--
Remove FindSDL2 find-module, use sdl2-config.cmake instead This requires SDL >= 2.0.4. Since <https://bugzilla.libsdl.org/show_bug.cgi?id=2464> was fixed in SDL 2.0.4, SDL behaves as a CMake "config-file package", even if it was not itself built using CMake: it installs a sdl2-config.cmake file to ${libdir}/cmake/SDL2, which tells CMake where to find SDL's headers and library, analogous to a pkg-config .pc file. As a result, we no longer need to copy/paste a "find-module package" to be able to find a system copy of SDL >= 2.0.4 with find_package(SDL2). Find-module packages are now discouraged by the CMake developers, in favour of having upstream projects behave as config-file packages. This results in a small API change: FindSDL2 used to set SDL2_INCLUDE_DIR and SDL2_LIBRARY, but the standard behaviour for config-file packages is to set <name>_INCLUDE_DIRS and <name>_LIBRARIES. Use the CONFIG keyword to make sure we search in config-file package mode, and will not find a FindSDL2.cmake in some other directory that implements the old interface. In addition to deleting redundant code, this avoids some assumptions in FindSDL2 about the layout of a SDL installation. The current libsdl2-dev package in Debian breaks those assumptions; this is considered a bug and will hopefully be fixed soon, but it illustrates how fragile these assumptions can be. We can be more robust against different installation layouts by relying on SDL's own CMake integration. When linking to a copy of CMake in a non-standard location, users can now set the SDL2_DIR or CMAKE_PREFIX_PATH environment variable to point to it; previously, these users would have used the SDL2DIR environment variable. This continues to be unnecessary if using matching system-wide installations of CMake and SDL2, for example both from Debian.

(*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
 *
 * 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
 * the Free Software Foundation; version 2 of the License
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *)

{$INCLUDE "options.inc"}

unit uUtils;

interface
uses uTypes, uFloat;

// returns s with whitespaces (chars <= #32) removed form both ends
function Trim(s: shortstring) : shortstring;

procedure SplitBySpace(var a, b: shortstring);
procedure SplitByChar(var a, b: shortstring; c: char);
procedure SplitByCharA(var a, b: ansistring; c: char);

procedure EscapeCharA(var a: ansistring; e: char);
procedure UnEscapeCharA(var a: ansistring; e: char);

function ExtractFileDir(s: shortstring) : shortstring;
function ExtractFileName(s: shortstring) : shortstring;

function  EnumToStr(const en : TGearType) : shortstring; overload;
function  EnumToStr(const en : TVisualGearType) : shortstring; overload;
function  EnumToStr(const en : TSound) : shortstring; overload;
function  EnumToStr(const en : TAmmoType) : shortstring; overload;
function  EnumToStr(const en : TStatInfoType) : shortstring; overload;
function  EnumToStr(const en : THogEffect) : shortstring; overload;
function  EnumToStr(const en : TCapGroup) : shortstring; overload;
function  EnumToStr(const en : TSprite) : shortstring; overload;
function  EnumToStr(const en : TMapGen) : shortstring; overload;
function  EnumToStr(const en : TWorldEdge) : shortstring; overload;

function  Min(a, b: LongInt): LongInt; inline;
function  MinD(a, b: double) : double; inline;
function  Max(a, b: LongInt): LongInt; inline;

function  IntToStr(n: LongInt): shortstring;
function  StrToInt(s: shortstring): LongInt;
//function  StrToInt(s: shortstring; var success: boolean): LongInt;
function  FloatToStr(n: hwFloat): shortstring;

function  DxDy2Angle(const _dY, _dX: hwFloat): real; inline;
function  DxDy2Angle32(const _dY, _dX: hwFloat): LongInt;
function  DxDy2AttackAngle(const _dY, _dX: hwFloat): LongInt;
function  DxDy2AttackAnglef(const _dY, _dX: extended): LongInt;

procedure SetLittle(var r: hwFloat);

function  Str2PChar(const s: shortstring): PChar;
function  DecodeBase64(s: shortstring): shortstring;

function  isPowerOf2(i: Longword): boolean;
function  toPowerOf2(i: Longword): Longword; inline;

function  endian(independent: LongWord): LongWord; inline;

function  CheckCJKFont(s: ansistring; font: THWFont): THWFont;

procedure AddFileLog(s: shortstring);
procedure AddFileLogRaw(s: pchar); cdecl;

function  CheckNoTeamOrHH: boolean; inline;

function  GetLaunchX(at: TAmmoType; dir: LongInt; angle: LongInt): LongInt;
function  GetLaunchY(at: TAmmoType; angle: LongInt): LongInt;

function CalcWorldWrap(X, radius: LongInt): LongInt;

procedure updateVolumeDelta(precise: boolean);
procedure updateCursorMovementDelta(precise: boolean; dir: LongInt; var cursorVar: LongInt);

function read1stLn(filePath: shortstring): shortstring;
function readValueFromINI(key, filePath: shortstring): shortstring;

{$IFNDEF PAS2C}
procedure Write(var f: textfile; s: shortstring);
procedure WriteLn(var f: textfile; s: shortstring);
function StrLength(s: PChar): Longword;
procedure SetLengthA(var s: ansistring; len: Longword);
{$ENDIF}

function  isPhone: Boolean; inline;

{$IFDEF IPHONEOS}
procedure startLoadingIndicator; cdecl; external;
procedure stopLoadingIndicator; cdecl; external;
procedure saveFinishedSynching; cdecl; external;
function  isApplePhone: Boolean; cdecl; external;
procedure AudioServicesPlaySystemSound(num: LongInt); cdecl; external;
{$ENDIF}

function  sanitizeForLog(s: shortstring): shortstring;
function  sanitizeCharForLog(c: char): shortstring;

procedure initModule(isNotPreview: boolean);
procedure freeModule;


implementation
uses {$IFNDEF PAS2C}typinfo, SDLh, {$ENDIF}Math, uConsts, uVariables, uPhysFSLayer, uDebug;

{$IFDEF DEBUGFILE}
var logFile: PFSFile;
{$IFDEF USE_VIDEO_RECORDING}
    logMutex: PSDL_mutex; // mutex for debug file
{$ENDIF}
{$ENDIF}
var CharArray: array[0..255] of Char;

// All leading/tailing characters with ordinal values less than or equal to 32 (a space) are stripped.
function Trim(s: shortstring) : shortstring;
var len, left, right: integer;
begin

len:= Length(s);

if len = 0 then
    exit(s);

// find first non-whitespace
left:= 1;
while left <= len do
    begin
    if s[left] > #32 then
        break;
    inc(left);
    end;

// find last non-whitespace
right:= len;
while right >= 1 do
    begin
    if s[right] > #32 then
        break;
    dec(right);
    end;

// string is whitespace only
if left > right then
    exit('');

// get string without surrounding whitespace
len:= right - left + 1;

Trim:= copy(s, left, len);

end;

function GetLastSlashPos(var s: shortString) : integer;
var lslash: integer;
    c: char;
begin

// find last slash
lslash:= Length(s);
while lslash >= 1 do
    begin
    c:= s[lslash];
    if (c = #47) or (c = #92) then
        break;
    dec(lslash); end;

GetLastSlashPos:= lslash;
end;

function ExtractFileDir(s: shortstring) : shortstring;
var lslash: byte;
begin

if Length(s) = 0 then
    exit(s);

lslash:= GetLastSlashPos(s);

if lslash <= 1 then
    exit('');

s[0]:= char(lslash - 1);

ExtractFileDir:= s;
end;

function ExtractFileName(s: shortstring) : shortstring;
var lslash, len: byte;
begin

len:= Length(s);

if len = 0 then
    exit(s);

lslash:= GetLastSlashPos(s);

if lslash < 1 then
    exit(s);

if lslash = len then
    exit('');

len:= len - lslash;
ExtractFilename:= copy(s, lslash + 1, len);
end;

procedure SplitBySpace(var a,b: shortstring);
begin
SplitByChar(a,b,' ');
end;

procedure SplitByChar(var a, b: shortstring; c : char);
var i: LongInt;
begin
i:= Pos(c, a);
if i > 0 then
    begin
    b:= copy(a, i + 1, Length(a) - i);
    a[0]:= char(Pred(i))
    {$IFDEF PAS2C}
       a[i] := 0;
    {$ENDIF}
    end
else
    b:= '';
end;

{$IFNDEF PAS2C}
procedure SetLengthA(var s: ansistring; len: Longword);
begin
    SetLength(s, len)
end;
{$ENDIF}

procedure SplitByCharA(var a, b: ansistring; c: char);
var i: LongInt;
begin
i:= Pos(c, a);
if i > 0 then
    begin
    b:= copy(a, i + 1, Length(a) - i);
    SetLengthA(a, Pred(i));
    end else b:= '';
end; { SplitByCharA }

// In the ansistring a, escapes all instances of
// '\e' with an ASCII ESC character, where e is
// a char chosen by you.
procedure EscapeCharA(var a: ansistring; e: char);
var i: LongInt;
begin
repeat
    i:= Pos(e, a);
    if (i > 1) and (a[i - 1] = '\') then
        begin
        a[i]:= Char($1B); // ASCII ESC
        Delete(a, i - 1, 1);
        end
    else
        break;
until (i <= 0);
end; { EscapeCharA }

// Unescapes a previously escaped string and inserts
// e back into the string. e is a char chosen by you.
procedure UnEscapeCharA(var a: ansistring; e: char);
var i: LongInt;
begin
repeat
    i:= Pos(Char($1B), a); // ASCII ESC
    if (i > 0) then
        begin
        a[i]:= e;
        end
    else
        break;
until (i <= 0);
end; { UnEscapeCharA }

function EnumToStr(const en : TGearType) : shortstring; overload;
begin
EnumToStr:= GetEnumName(TypeInfo(TGearType), ord(en))
end;

function EnumToStr(const en : TVisualGearType) : shortstring; overload;
begin
EnumToStr:= GetEnumName(TypeInfo(TVisualGearType), ord(en))
end;

function EnumToStr(const en : TSound) : shortstring; overload;
begin
EnumToStr:= GetEnumName(TypeInfo(TSound), ord(en))
end;

function EnumToStr(const en : TAmmoType) : shortstring; overload;
begin
EnumToStr:= GetEnumName(TypeInfo(TAmmoType), ord(en))
end;

function EnumToStr(const en : TStatInfoType) : shortstring; overload;
begin
EnumToStr:= GetEnumName(TypeInfo(TStatInfoType), ord(en))
end;

function EnumToStr(const en: THogEffect) : shortstring; overload;
begin
EnumToStr := GetEnumName(TypeInfo(THogEffect), ord(en))
end;

function EnumToStr(const en: TCapGroup) : shortstring; overload;
begin
EnumToStr := GetEnumName(TypeInfo(TCapGroup), ord(en))
end;

function EnumToStr(const en: TSprite) : shortstring; overload;
begin
EnumToStr := GetEnumName(TypeInfo(TSprite), ord(en))
end;

function EnumToStr(const en: TMapGen) : shortstring; overload;
begin
EnumToStr := GetEnumName(TypeInfo(TMapGen), ord(en))
end;

function EnumToStr(const en: TWorldEdge) : shortstring; overload;
begin
EnumToStr := GetEnumName(TypeInfo(TWorldEdge), ord(en))
end;


function Min(a, b: LongInt): LongInt;
begin
if a < b then
    Min:= a
else
    Min:= b
end;

function MinD(a, b: double): double;
begin
if a < b then
    MinD:= a
else
    MinD:= b
end;

function Max(a, b: LongInt): LongInt;
begin
if a > b then
    Max:= a
else
    Max:= b
end;


function IntToStr(n: LongInt): shortstring;
begin
str(n, IntToStr)
end;

// Convert string to longint, with error checking.
// Success will be set to false when conversion failed.
// See documentation on Val procedure for syntax of s
//function StrToInt(s: shortstring; var success: boolean): LongInt;
//var Code: Word;
//begin
//val(s, StrToInt, Code);
//success:= Code = 0;
//end;

// Convert string to longint, without error checking
function StrToInt(s: shortstring): LongInt;
begin
val(s, StrToInt);
end;

function FloatToStr(n: hwFloat): shortstring;
begin
FloatToStr:= cstr(n) + '_' + inttostr(Lo(n.QWordValue))
end;


function DxDy2Angle(const _dY, _dX: hwFloat): real; inline;
var dY, dX: Extended;
begin
dY:= hwFloat2Float(_dY);
dX:= hwFloat2Float(_dX);
DxDy2Angle:= arctan2(dY, dX) * 180 / pi
end;

function DxDy2Angle32(const _dY, _dX: hwFloat): LongInt;
const _16divPI: Extended = 16/pi;
var dY, dX: Extended;
begin
dY:= hwFloat2Float(_dY);
dX:= hwFloat2Float(_dX);
DxDy2Angle32:= trunc(arctan2(dY, dX) * _16divPI) and $1f
end;

function DxDy2AttackAngle(const _dY, _dX: hwFloat): LongInt;
const MaxAngleDivPI: Extended = cMaxAngle/pi;
var dY, dX: Extended;
begin
dY:= hwFloat2Float(_dY);
dX:= hwFloat2Float(_dX);
DxDy2AttackAngle:= trunc(arctan2(dY, dX) * MaxAngleDivPI)
end;

function DxDy2AttackAnglef(const _dY, _dX: extended): LongInt; inline;
begin
DxDy2AttackAnglef:= trunc(arctan2(_dY, _dX) * (cMaxAngle/pi))
end;


procedure SetLittle(var r: hwFloat);
begin
r:= SignAs(cLittle, r)
end;


function isPowerOf2(i: Longword): boolean;
begin
isPowerOf2:= (i and (i - 1)) = 0
end;

function toPowerOf2(i: Longword): Longword;
begin
toPowerOf2:= 1;
while (toPowerOf2 < i) do toPowerOf2:= toPowerOf2 shl 1
end;


function DecodeBase64(s: shortstring): shortstring;
const table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var i, t, c: LongInt;
begin
c:= 0;
for i:= 1 to Length(s) do
    begin
    t:= Pos(s[i], table);
    if s[i] = '=' then
        inc(c);
    if t > 0 then
        s[i]:= char(t - 1)
    else
        s[i]:= #0
    end;

i:= 1;
t:= 1;
while i <= length(s) do
    begin
    DecodeBase64[t    ]:= char((byte(s[i    ]) shl 2) or (byte(s[i + 1]) shr 4));
    DecodeBase64[t + 1]:= char((byte(s[i + 1]) shl 4) or (byte(s[i + 2]) shr 2));
    DecodeBase64[t + 2]:= char((byte(s[i + 2]) shl 6) or (byte(s[i + 3])      ));
    inc(t, 3);
    inc(i, 4)
    end;

if c < 3 then
    t:= t - c;

DecodeBase64[0]:= char(t - 1)
end;


function Str2PChar(const s: shortstring): PChar;
var i :Integer ;
begin
   for i:= 1 to Length(s) do
      begin
      CharArray[i - 1] := s[i];
      end;
   CharArray[Length(s)]:= #0;
   Str2PChar:= @(CharArray[0]);
end;


function endian(independent: LongWord): LongWord; inline;
begin
{$IFDEF ENDIAN_LITTLE}
endian:= independent;
{$ELSE}
endian:= (((independent and $FF000000) shr 24) or
          ((independent and $00FF0000) shr 8) or
          ((independent and $0000FF00) shl 8) or
          ((independent and $000000FF) shl 24))
{$ENDIF}
end;


procedure AddFileLog(s: shortstring);
begin
// s:= s;
{$IFDEF DEBUGFILE}

{$IFDEF USE_VIDEO_RECORDING}
if SDL_LockMutex(logMutex) <> 0 then
    OutError('Logging mutex could not be locked!', true);
{$ENDIF}
if logFile <> nil then
    pfsWriteLn(logFile, inttostr(GameTicks)  + ': ' + s)
else
    WriteLn(stdout, inttostr(GameTicks)  + ': ' + s);

{$IFDEF USE_VIDEO_RECORDING}
if SDL_UnlockMutex(logMutex) <> 0 then
    OutError('Logging mutex could not be unlocked!', true);
{$ENDIF}

{$ENDIF}
end;

procedure AddFileLogRaw(s: pchar); cdecl;
var msgLine: PChar;
begin
s:= s;
{$IFNDEF PAS2C}
{$IFDEF DEBUGFILE}
{$IFDEF USE_VIDEO_RECORDING}
if SDL_LockMutex(logMutex) <> 0 then
    OutError('Logging mutex could not be locked!', true);
{$ENDIF}
msgLine:= Str2PChar(IntToStr(GameTicks) + ': ');
if (logFile <> nil) then
    begin
    pfsWriteRaw(logFile, msgLine, StrLen(msgLine));
    pfsWriteRaw(logFile, s, StrLen(s));
    end
else
    begin
    Write(stdout, msgLine);
    Flush(stdout);
    end;
{$IFDEF USE_VIDEO_RECORDING}
if SDL_UnlockMutex(logMutex) <> 0 then
    OutError('Logging mutex could not be unlocked!', true);
{$ENDIF}
{$ENDIF}
{$ENDIF}
end;

function CheckCJKFont(s: ansistring; font: THWFont): THWFont;
var l, i : LongInt;
    u: WideChar;
    tmpstr: array[0..256] of WideChar;
begin
CheckCJKFont:= font;

{$IFNDEF MOBILE}
// remove chinese fonts for now
if (font >= CJKfnt16) or (length(s) = 0) then
{$ENDIF}
    exit;

l:= Utf8ToUnicode(PWideChar(@tmpstr), PChar(s), min(length(tmpstr), length(s)))-1;
i:= 0;

while i < l do
    begin
    u:= tmpstr[i];
    if (#$1100  <= u) and  (
                           (u <= #$11FF )  or // Hangul Jamo
       ((#$2E80  <= u) and (u <= #$2FDF))  or // CJK Radicals Supplement / Kangxi Radicals
       ((#$2FF0  <= u) and (u <= #$31FF))  or // Ideographic Description Characters / CJK Radicals Supplement / Hiragana / Hangul Compatibility Jamo / Katakana
       ((#$31C0  <= u) and (u <= #$31EF))  or // CJK Strokes
       ((#$3200  <= u) and (u <= #$4DBF))  or // Enclosed CJK Letters and Months / CJK Compatibility / CJK Unified Ideographs Extension A / Circled Katakana
       ((#$4E00  <= u) and (u <= #$9FFF))  or // CJK Unified Ideographs
       ((#$AC00  <= u) and (u <= #$D7AF))  or // Hangul Syllables
       ((#$F900  <= u) and (u <= #$FAFF))  or // CJK Compatibility Ideographs
       ((#$FE30  <= u) and (u <= #$FE4F))  or // CJK Compatibility Forms
       ((#$FF00  <= u) and (u <= #$FFEF)))    // half- and fullwidth characters
       then
        begin
            CheckCJKFont:=  THWFont( ord(font) + ((ord(High(THWFont))+1) div 2) );
            exit;
        end;
    inc(i)
    end;
(* two more to check. pascal WideChar is only 16 bit though
       ((#$20000 <= u) and (u >= #$2A6DF)) or // CJK Unified Ideographs Extension B
       ((#$2F800 <= u) and (u >= #$2FA1F)))   // CJK Compatibility Ideographs Supplement *)
end;


function GetLaunchX(at: TAmmoType; dir: LongInt; angle: LongInt): LongInt;
begin
at:= at; dir:= dir; angle:= angle; // parameter hint suppression because code below is currently disabled
GetLaunchX:= 0
(*
    if (Ammoz[at].ejectX <> 0) or (Ammoz[at].ejectY <> 0) then
        GetLaunchX:= sign(dir) * (8 + hwRound(AngleSin(angle) * Ammoz[at].ejectX) + hwRound(AngleCos(angle) * Ammoz[at].ejectY))
    else
        GetLaunchX:= 0 *)
end;

function GetLaunchY(at: TAmmoType; angle: LongInt): LongInt;
begin
at:= at; angle:= angle; // parameter hint suppression because code below is currently disabled
GetLaunchY:= 0
(*
    if (Ammoz[at].ejectX <> 0) or (Ammoz[at].ejectY <> 0) then
        GetLaunchY:= hwRound(AngleSin(angle) * Ammoz[at].ejectY) - hwRound(AngleCos(angle) * Ammoz[at].ejectX) - 2
    else
        GetLaunchY:= 0*)
end;

// Takes an X coordinate and corrects if according to the world edge rules
// Wrap-around: X will be wrapped
// Bouncy: X will be kept inside the legal land (taking radius into account)
// Other world edges: Just returns X
// radius is a radius (gear radius) tolerance for an appropriate distance from bouncy world edges.
// Set radius to 0 if you don't care.
function CalcWorldWrap(X, radius: LongInt): LongInt;
begin
    if WorldEdge = weWrap then
        begin
        if X < leftX then
             X:= X + (rightX - leftX)
        else if X > rightX then
             X:= X - (rightX - leftX);
        end
    else if WorldEdge = weBounce then
        begin
        if (X + radius) < leftX then
            X:= leftX + radius
        else if (X - radius) > rightX then
            X:= rightX - radius;
        end;
    CalcWorldWrap:= X;
end;

function CheckNoTeamOrHH: boolean;
begin
CheckNoTeamOrHH:= (CurrentTeam = nil) or (CurrentHedgehog^.Gear = nil);
end;

{$IFNDEF PAS2C}
procedure Write(var f: textfile; s: shortstring);
begin
system.write(f, s)
end;

procedure WriteLn(var f: textfile; s: shortstring);
begin
system.writeln(f, s)
end;

function StrLength(s: PChar): Longword;
begin
    StrLength:= length(s)
end;
{$ENDIF}

// this function is just to determine whether we are running on a limited screen device
function isPhone: Boolean; inline;
begin
    isPhone:= false;
{$IFDEF IPHONEOS}
    isPhone:= isApplePhone();
{$ENDIF}
{$IFDEF ANDROID}
    //nasty nasty hack. TODO: implement callback to java to have a unified way of determining if it is a tablet
    if (cScreenWidth < 1000) and (cScreenHeight < 500) then
        isPhone:= true;
{$ENDIF}
end;


function  sanitizeForLog(s: shortstring): shortstring;
var i: byte;
    r: shortstring;
begin
    r[0]:= s[0];
    for i:= 1 to length(s) do
        if (s[i] < #32) or (s[i] > #127) then
            r[i]:= '?'
            else
            r[i]:= s[i];

    sanitizeForLog:= r
end;

function  sanitizeCharForLog(c: char): shortstring;
var r: shortstring;
begin
    if (c < #32) or (c > #127) then
        r:= '#' + inttostr(byte(c))
        else
        begin
        // some magic for pas2c
        r[0]:= #1;
        r[1]:= c;
        end;

    sanitizeCharForLog:= r
end;

// helper function for volume change controls
procedure updateVolumeDelta(precise: boolean);
begin
if cVolumeUpKey and (not cVolumeDownKey) then
    if precise then
        cVolumeDelta:= 1
    else
        cVolumeDelta:= 3
else if cVolumeDownKey and (not cVolumeUpKey) then
    if precise then
        cVolumeDelta:= -1
    else
        cVolumeDelta:= -3
else
    cVolumeDelta:= 0;
end;

// helper function for cursor movement change controls
procedure updateCursorMovementDelta(precise: boolean; dir: LongInt; var cursorVar: LongInt);
begin
if dir > 0 then
    if precise then
        cursorVar:= cameraKeyboardSpeedSlow
    else
        cursorVar:= cameraKeyboardSpeed
else if dir < 0 then
    if precise then
        cursorVar:= - cameraKeyboardSpeedSlow
    else
        cursorVar:= - cameraKeyboardSpeed
else
    cursorVar:= 0;
end;

function read1stLn(filePath: shortstring): shortstring;
var f: pfsFile;
begin
    read1stLn:= '';
    if pfsExists(filePath) then
        begin
        f:= pfsOpenRead(filePath);
        if (not pfsEOF(f)) and allOK then
            pfsReadLn(f, read1stLn);
        pfsClose(f);
        f:= nil;
        end;
end;

function readValueFromINI(key, filePath: shortstring): shortstring;
var f: pfsFile;
	s: shortstring;
	i: LongInt;
begin
    s:= '';
	readValueFromINI:= '';

    if pfsExists(filePath) then
        begin
        f:= pfsOpenRead(filePath);

        while (not pfsEOF(f)) and allOK do
			begin pfsReadLn(f, s);
			if Length(s) = 0 then
				continue;
			if s[1] = ';' then
				continue;

			i:= Pos('=', s);
			if Trim(Copy(s, 1, Pred(i))) = key then
				begin
				Delete(s, 1, i);
				readValueFromINI:= s;
				end;
			end;
        pfsClose(f);
        f:= nil;
        end;
end;

procedure initModule(isNotPreview: boolean);
{$IFDEF DEBUGFILE}
var logfileBase: shortstring;
    i: LongInt;
{$ENDIF}
begin
{$IFDEF DEBUGFILE}
    if isNotPreview then
    begin
        if GameType = gmtRecord then
            logfileBase:= 'rec'
        else
        {$IFDEF PAS2C}
        logfileBase:= 'game_pas2c';
        {$ELSE}
        logfileBase:= 'game';
        {$ENDIF}
    end
    else
        {$IFDEF PAS2C}
        logfileBase:= 'preview_pas2c';
        {$ELSE}
        logfileBase:= 'preview';
        {$ENDIF}
{$IFDEF USE_VIDEO_RECORDING}
    logMutex:= SDL_CreateMutex();
    if (logMutex = nil) then
        OutError('Could not create mutex for logging', true);
{$ENDIF}
    if not pfsExists('/Logs') then
        pfsMakeDir('/Logs');
    // if log is locked, write to the next one
    i:= 0;
    while(i < 7) do
    begin
        logFile:= pfsOpenWrite('/Logs/' + logfileBase + inttostr(i) + '.log');
        if logFile <> nil then
            break;
        inc(i)
    end;

    if logFile = nil then
        WriteLn(stdout, '[WARNING] Could not open log file for writing. Log will be written to stdout!');
{$ENDIF}

    //mobile stuff
{$IFDEF IPHONEOS}
    mobileRecord.PerformRumble:= @AudioServicesPlaySystemSound;
    mobileRecord.GameLoading:= @startLoadingIndicator;
    mobileRecord.GameLoaded:= @stopLoadingIndicator;
    mobileRecord.SaveLoadingEnded:= @saveFinishedSynching;
{$ELSE}
    mobileRecord.PerformRumble:= nil;
    mobileRecord.GameLoading:= nil;
    mobileRecord.GameLoaded:= nil;
    mobileRecord.SaveLoadingEnded:= nil;
{$ENDIF}

end;

procedure freeModule;
begin
{$IFDEF DEBUGFILE}
if logFile <> nil then
    begin
    pfsWriteLn(logFile, 'halt at ' + inttostr(GameTicks) + ' ticks. TurnTimeLeft = ' + inttostr(TurnTimeLeft));
    pfsFlush(logFile);
    pfsClose(logFile);
    end
else
    WriteLn(stdout, 'halt at ' + inttostr(GameTicks) + ' ticks. TurnTimeLeft = ' + inttostr(TurnTimeLeft));
{$IFDEF USE_VIDEO_RECORDING}
    SDL_DestroyMutex(logMutex);
{$ENDIF}
{$ENDIF}
end;

end.