Index: src/udcutils.pas
===================================================================
--- src/udcutils.pas	(revision 8402)
+++ src/udcutils.pas	(working copy)
@@ -192,6 +192,7 @@
    Strings must have tailing zeros (#0).
 }
 function StrFloatCmpW(str1, str2: PWideChar; CaseSensitivity: TCaseSensitivity): PtrInt;
+function StrFloatCmp(const str1, str2: String; CaseSensitivity: TCaseSensitivity): PtrInt;
 
 function EstimateRemainingTime(StartValue, CurrentValue, EndValue: Int64;
                                StartTime: TDateTime; CurrentTime: TDateTime;
@@ -807,7 +808,8 @@
 function CompareStrings(const s1, s2: String; Natural: Boolean; CaseSensitivity: TCaseSensitivity): PtrInt; inline;
 begin
   if Natural then
-    Result:= StrFloatCmpW(PWideChar(UTF8Decode(s1)), PWideChar(UTF8Decode(s2)), CaseSensitivity)
+//    Result:= StrFloatCmpW(PWideChar(UTF8Decode(s1)), PWideChar(UTF8Decode(s2)), CaseSensitivity)
+    Result:= StrFloatCmp(s1, s2, CaseSensitivity)
   else
     begin
       case CaseSensitivity of
@@ -1021,6 +1023,160 @@
   end;
 end;
 
+const SORT_NATURAL = True;
+const SORT_SPECIAL = False;
+
+function StrFloatCmp(const str1, str2: String; CaseSensitivity: TCaseSensitivity): PtrInt;
+
+type
+  TCategory = (cNone, cNumber, cSpecial, cString);
+
+  TChunk = record
+    FullStr: String;
+    Str: String;
+    Category: TCategory;
+    PosStart: Integer;
+    PosEnd: Integer;
+  end;
+
+var
+  Chunk1, Chunk2: TChunk;
+
+  function Categorize(c: Char): TCategory; inline;
+  begin
+    if SORT_NATURAL and (c in ['0'..'9']) then
+      Result := cNumber
+    else if SORT_SPECIAL and (c in [' '..'/', ':'..'@', '['..'`', '{'..'~']) then
+      Result := cSpecial
+    else
+      Result := cString;
+  end;
+
+  procedure NextChunkInit(var Chunk: TChunk); inline;
+  begin
+    Chunk.PosStart := Chunk.PosEnd;
+    if Chunk.PosStart > Length(Chunk.FullStr) then
+      Chunk.Category := cNone
+    else
+      Chunk.Category := Categorize(Chunk.FullStr[Chunk.PosStart]);
+  end;
+
+  procedure FindChunk(var Chunk: TChunk); inline;
+  begin
+    Chunk.PosEnd := Chunk.PosStart;
+    repeat
+      inc(Chunk.PosEnd);
+    until (Chunk.PosEnd > Length(Chunk.FullStr)) or
+          (Categorize(Chunk.FullStr[Chunk.PosEnd]) <> Chunk.Category);
+  end;
+
+  procedure FindSameCategoryChunks; inline;
+  begin
+    Chunk1.PosEnd := Chunk1.PosStart;
+    Chunk2.PosEnd := Chunk2.PosStart;
+    repeat
+      inc(Chunk1.PosEnd);
+      inc(Chunk2.PosEnd);
+    until (Chunk1.PosEnd > Length(Chunk1.FullStr)) or
+          (Chunk2.PosEnd > Length(Chunk2.FullStr)) or
+          (Categorize(Chunk1.FullStr[Chunk1.PosEnd]) <> Chunk1.Category) or
+          (Categorize(Chunk2.FullStr[Chunk2.PosEnd]) <> Chunk2.Category);
+  end;
+
+  procedure PrepareChunk(var Chunk: TChunk); inline;
+  begin
+    Chunk.Str := Copy(Chunk.FullStr, Chunk.PosStart, Chunk.PosEnd - Chunk.PosStart);
+  end;
+
+  procedure PrepareNumberChunk(var Chunk: TChunk); inline;
+  begin
+    while (Chunk.PosStart <= Length(Chunk.FullStr)) and
+          (Chunk.FullStr[Chunk.PosStart] = '0') do
+      inc(Chunk.PosStart);
+    PrepareChunk(Chunk);
+  end;
+
+begin
+  Chunk1.FullStr := str1;
+  Chunk2.FullStr := str2;
+  Chunk1.PosEnd := 1;
+  Chunk2.PosEnd := 1;
+
+  NextChunkInit(Chunk1);
+  NextChunkInit(Chunk2);
+
+  if (Chunk1.Category = cSpecial) and (Chunk2.Category = cSpecial) then
+    FindSameCategoryChunks
+  else
+  begin
+    FindChunk(Chunk1);
+    FindChunk(Chunk2);
+  end;
+
+  if Chunk1.Category <> Chunk2.Category then
+    Chunk1.Category := cString;
+
+  while True do
+  begin
+
+    case Chunk1.Category of
+      cString:
+        begin
+          PrepareChunk(Chunk1);
+          PrepareChunk(Chunk2);
+          case CaseSensitivity of
+            cstNotSensitive: Result := WideCompareText(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str));
+            cstLocale:       Result := WideCompareStr(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str));
+            cstCharValue:    Result := WideStrComp(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str));
+            else
+              raise Exception.Create('Invalid CaseSensitivity parameter');
+          end;
+          if Result <> 0 then
+            Exit;
+        end;
+      cNumber:
+        begin
+          PrepareNumberChunk(Chunk1);
+          PrepareNumberChunk(Chunk2);
+          Result := Length(Chunk1.Str) - Length(Chunk2.Str);
+          if Result <> 0 then
+            Exit;
+          Result := CompareStr(Chunk1.Str, Chunk2.Str);
+          if Result <> 0 then
+            Exit;
+        end;
+      cSpecial:
+        begin
+          PrepareChunk(Chunk1);
+          PrepareChunk(Chunk2);
+          Result := CompareStr(Chunk1.Str, Chunk2.Str);
+          if Result <> 0 then
+            Exit;
+        end;
+      cNone:
+        Exit(WideStrComp(UTF8Decode(str1), UTF8Decode(str2)));
+    end;
+
+    NextChunkInit(Chunk1);
+    NextChunkInit(Chunk2);
+
+    if Chunk1.Category <> Chunk2.Category then
+      if Chunk1.Category < Chunk2.Category then
+        Exit(-1)
+      else
+        Exit(1);
+
+    if Chunk1.Category = cSpecial then
+      FindSameCategoryChunks
+    else
+    begin
+      FindChunk(Chunk1);
+      FindChunk(Chunk2);
+    end;
+
+  end;
+end;
+
 function EstimateRemainingTime(StartValue, CurrentValue, EndValue: Int64;
                                StartTime: TDateTime; CurrentTime: TDateTime;
                                out SpeedPerSecond: Int64): TDateTime;
