hedgewars/uUtils.pas
branchhedgeroid
changeset 15532 7030706266df
parent 14969 f27f00fabc0d
child 15897 d7b53d08ad9c
--- a/hedgewars/uUtils.pas	Sun Oct 28 15:18:26 2012 +0100
+++ b/hedgewars/uUtils.pas	Fri Dec 06 22:20:53 2019 +0100
@@ -1,6 +1,6 @@
 (*
  * Hedgewars, a free turn based strategy game
- * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
+ * 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
@@ -13,7 +13,7 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  *)
 
 {$INCLUDE "options.inc"}
@@ -21,29 +21,42 @@
 unit uUtils;
 
 interface
-uses uTypes, uFloat, GLunit;
+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 SplitByChar(var a, b: ansistring; c: char);
+procedure SplitByCharA(var a, b: ansistring; c: char);
 
-{$IFNDEF PAS2C}
+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;
-{$ENDIF}
+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): GLfloat; inline;
+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;
@@ -68,64 +81,221 @@
 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}
 
-procedure initModule(isGame: boolean);
+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, {$ENDIF}Math, uConsts, uVariables, SysUtils;
+uses {$IFNDEF PAS2C}typinfo, SDLh, {$ENDIF}Math, uConsts, uVariables, uPhysFSLayer, uDebug;
 
 {$IFDEF DEBUGFILE}
-var f: textfile;
+var logFile: PFSFile;
 {$IFDEF USE_VIDEO_RECORDING}
-    logMutex: TRTLCriticalSection; // mutex for debug file
+    logMutex: PSDL_mutex; // mutex for debug file
 {$ENDIF}
 {$ENDIF}
-var CharArray: array[byte] of Char;
+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;
 
-// should this include "strtolower()" for the split string?
 procedure SplitByChar(var a, b: shortstring; c : char);
-var i, t: LongInt;
+var i: LongInt;
 begin
 i:= Pos(c, a);
 if i > 0 then
     begin
-    for t:= 1 to Pred(i) do
-        if (a[t] >= 'A')and(a[t] <= 'Z') then
-            Inc(a[t], 32);
     b:= copy(a, i + 1, Length(a) - i);
     a[0]:= char(Pred(i))
+    {$IFDEF PAS2C}
+       a[i] := 0;
+    {$ENDIF}
     end
 else
     b:= '';
 end;
 
-procedure SplitByChar(var a, b: ansistring; c: char);
+{$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);
-    setlength(a, Pred(i));
+    SetLengthA(a, Pred(i));
     end else b:= '';
-end;
+end; { SplitByCharA }
 
-{$IFNDEF PAS2C}
+// 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))
@@ -141,6 +311,11 @@
 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))
@@ -150,7 +325,22 @@
 begin
 EnumToStr := GetEnumName(TypeInfo(TCapGroup), ord(en))
 end;
-{$ENDIF}
+
+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
@@ -160,6 +350,14 @@
     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
@@ -174,10 +372,20 @@
 str(n, IntToStr)
 end;
 
-function  StrToInt(s: shortstring): LongInt;
-var c: LongInt;
+// 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, c)
+val(s, StrToInt);
 end;
 
 function FloatToStr(n: hwFloat): shortstring;
@@ -186,7 +394,7 @@
 end;
 
 
-function DxDy2Angle(const _dY, _dX: hwFloat): GLfloat; inline;
+function DxDy2Angle(const _dY, _dX: hwFloat): real; inline;
 var dY, dX: Extended;
 begin
 dY:= hwFloat2Float(_dY);
@@ -238,7 +446,7 @@
 
 function DecodeBase64(s: shortstring): shortstring;
 const table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
-var i, t, c: Longword;
+var i, t, c: LongInt;
 begin
 c:= 0;
 for i:= 1 to Length(s) do
@@ -271,10 +479,14 @@
 
 
 function Str2PChar(const s: shortstring): PChar;
+var i :Integer ;
 begin
-CharArray:= s;
-CharArray[Length(s)]:= #0;
-Str2PChar:= @CharArray
+   for i:= 1 to Length(s) do
+      begin
+      CharArray[i - 1] := s[i];
+      end;
+   CharArray[Length(s)]:= #0;
+   Str2PChar:= @(CharArray[0]);
 end;
 
 
@@ -293,30 +505,51 @@
 
 procedure AddFileLog(s: shortstring);
 begin
-s:= s;
+// s:= s;
 {$IFDEF DEBUGFILE}
+
 {$IFDEF USE_VIDEO_RECORDING}
-EnterCriticalSection(logMutex);
+if SDL_LockMutex(logMutex) <> 0 then
+    OutError('Logging mutex could not be locked!', true);
 {$ENDIF}
-writeln(f, inttostr(GameTicks)  + ': ' + s);
-flush(f);
+if logFile <> nil then
+    pfsWriteLn(logFile, inttostr(GameTicks)  + ': ' + s)
+else
+    WriteLn(stdout, inttostr(GameTicks)  + ': ' + s);
+
 {$IFDEF USE_VIDEO_RECORDING}
-LeaveCriticalSection(logMutex);
+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}
-EnterCriticalSection(logMutex);
+if SDL_LockMutex(logMutex) <> 0 then
+    OutError('Logging mutex could not be locked!', true);
 {$ENDIF}
-write(f, s);
-flush(f);
+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}
-LeaveCriticalSection(logMutex);
+if SDL_UnlockMutex(logMutex) <> 0 then
+    OutError('Logging mutex could not be unlocked!', true);
+{$ENDIF}
 {$ENDIF}
 {$ENDIF}
 end;
@@ -334,7 +567,7 @@
 {$ENDIF}
     exit;
 
-l:= Utf8ToUnicode(@tmpstr, Str2PChar(s), length(s))-1;
+l:= Utf8ToUnicode(PWideChar(@tmpstr), PChar(s), min(length(tmpstr), length(s)))-1;
 i:= 0;
 
 while i < l do
@@ -343,15 +576,15 @@
     if (#$1100  <= u) and  (
                            (u <= #$11FF )  or // Hangul Jamo
        ((#$2E80  <= u) and (u <= #$2FDF))  or // CJK Radicals Supplement / Kangxi Radicals
-       ((#$2FF0  <= u) and (u <= #$303F))  or // Ideographic Description Characters / CJK Radicals Supplement
-       ((#$3130  <= u) and (u <= #$318F))  or // Hangul Compatibility Jamo
+       ((#$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
+       ((#$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)))    // CJK Compatibility Forms
-       then 
+       ((#$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;
@@ -366,6 +599,7 @@
 
 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
@@ -376,6 +610,7 @@
 
 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
@@ -384,6 +619,31 @@
         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);
@@ -399,65 +659,209 @@
 begin
 system.writeln(f, s)
 end;
+
+function StrLength(s: PChar): Longword;
+begin
+    StrLength:= length(s)
+end;
 {$ENDIF}
 
-procedure initModule(isGame: boolean);
+// 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;
-{$IFNDEF MOBILE}var i: LongInt;{$ENDIF}
+    i: LongInt;
 {$ENDIF}
 begin
 {$IFDEF DEBUGFILE}
-    if isGame then
+    if isNotPreview then
     begin
         if GameType = gmtRecord then
             logfileBase:= 'rec'
         else
-            logfileBase:= 'game';
+        {$IFDEF PAS2C}
+        logfileBase:= 'game_pas2c';
+        {$ELSE}
+        logfileBase:= 'game';
+        {$ENDIF}
     end
     else
+        {$IFDEF PAS2C}
+        logfileBase:= 'preview_pas2c';
+        {$ELSE}
         logfileBase:= 'preview';
+        {$ENDIF}
 {$IFDEF USE_VIDEO_RECORDING}
-    InitCriticalSection(logMutex);
+    logMutex:= SDL_CreateMutex();
+    if (logMutex = nil) then
+        OutError('Could not create mutex for logging', true);
 {$ENDIF}
-{$I-}
-{$IFDEF MOBILE}
-    {$IFDEF IPHONEOS} Assign(f,'../Documents/hw-' + logfileBase + '.log'); {$ENDIF}
-    {$IFDEF ANDROID} Assign(f,pathPrefix + '/' + logfileBase + '.log'); {$ENDIF}
-    Rewrite(f);
+    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}
-    if (UserPathPrefix <> '') then
-        begin
-            i:= 0;
-            while(i < 7) do
-            begin
-                assign(f, UserPathPrefix + '/Logs/' + logfileBase + inttostr(i) + '.log');
-                rewrite(f);
-                if IOResult = 0 then
-                    break;
-                inc(i)
-            end;
-            if i = 7 then
-                f:= stderr; // if everything fails, write to stderr
-        end
-    else
-        f:= stderr;
-{$ENDIF}
-{$I+}
+    mobileRecord.PerformRumble:= nil;
+    mobileRecord.GameLoading:= nil;
+    mobileRecord.GameLoaded:= nil;
+    mobileRecord.SaveLoadingEnded:= nil;
 {$ENDIF}
 
 end;
 
 procedure freeModule;
 begin
-recordFileName:= '';
-
 {$IFDEF DEBUGFILE}
-    writeln(f, 'halt at ' + inttostr(GameTicks) + ' ticks. TurnTimeLeft = ' + inttostr(TurnTimeLeft));
-    flush(f);
-    close(f);
+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}
-    DoneCriticalSection(logMutex);
+    SDL_DestroyMutex(logMutex);
 {$ENDIF}
 {$ENDIF}
 end;