From 678cc132f942ff4d84a803550eedf96acc543bca Mon Sep 17 00:00:00 2001
From: tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>
Date: Sun, 23 May 2010 09:07:15 +0000
Subject: update to trunk rev. 2391

git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@2401 b956fd51-792f-4845-bead-9b4dfca2ff2c
---
 cmake/src/base/TextGL.pas           |  393 +++------
 cmake/src/base/TextGLFreetype.pas   |  222 -----
 cmake/src/base/UBeatTimer.pas       |  459 ++++++----
 cmake/src/base/UCatCovers.pas       |  131 +--
 cmake/src/base/UCommandLine.pas     |   25 +-
 cmake/src/base/UCommon.pas          |  346 +++-----
 cmake/src/base/UConfig.pas          |   30 +-
 cmake/src/base/UCovers.pas          |   56 +-
 cmake/src/base/UDLLManager.pas      |  292 -------
 cmake/src/base/UDataBase.pas        |  458 ++++++----
 cmake/src/base/UDraw.pas            |  374 ++------
 cmake/src/base/UEditorLyrics.pas    |    4 +-
 cmake/src/base/UFiles.pas           |  221 +++--
 cmake/src/base/UFilesystem.pas      |  692 +++++++++++++++
 cmake/src/base/UFont.pas            |  849 ++++++++++++------
 cmake/src/base/UGraphic.pas         |  186 ++--
 cmake/src/base/UImage.pas           |  194 +++--
 cmake/src/base/UIni.pas             |  451 +++++-----
 cmake/src/base/ULanguage.pas        |  220 +++--
 cmake/src/base/ULog.pas             |   44 +-
 cmake/src/base/ULyrics.pas          |    4 +-
 cmake/src/base/UMain.pas            |  302 ++++---
 cmake/src/base/UMusic.pas           |  178 ++--
 cmake/src/base/UNote.pas            |    9 +-
 cmake/src/base/UParty.pas           | 1057 ++++++++++++++++++-----
 cmake/src/base/UPath.pas            | 1615 +++++++++++++++++++++++++++++++----
 cmake/src/base/UPathUtils.pas       |  196 +++++
 cmake/src/base/UPlatform.pas        |   95 +--
 cmake/src/base/UPlatformLinux.pas   |  102 +--
 cmake/src/base/UPlatformMacOSX.pas  |  198 ++---
 cmake/src/base/UPlatformWindows.pas |  232 ++---
 cmake/src/base/UPlaylist.pas        |  261 +++---
 cmake/src/base/URecord.pas          |   67 +-
 cmake/src/base/USingScores.pas      |  175 +++-
 cmake/src/base/USkins.pas           |  145 ++--
 cmake/src/base/USong.pas            | 1101 ++++++++++++++----------
 cmake/src/base/USongs.pas           |  455 +++++-----
 cmake/src/base/UTextEncoding.pas    |  279 ++++--
 cmake/src/base/UTexture.pas         |   50 +-
 cmake/src/base/UThemes.pas          |  227 +++--
 cmake/src/base/UTime.pas            |  158 ++--
 cmake/src/base/UUnicodeUtils.pas    |  670 +++++++++++++++
 cmake/src/base/UXMLSong.pas         |   97 ++-
 43 files changed, 8527 insertions(+), 4793 deletions(-)
 delete mode 100644 cmake/src/base/TextGLFreetype.pas
 delete mode 100644 cmake/src/base/UDLLManager.pas
 create mode 100644 cmake/src/base/UFilesystem.pas
 create mode 100644 cmake/src/base/UPathUtils.pas
 create mode 100644 cmake/src/base/UUnicodeUtils.pas

(limited to 'cmake/src/base')

diff --git a/cmake/src/base/TextGL.pas b/cmake/src/base/TextGL.pas
index c8de4e28..c354a500 100644
--- a/cmake/src/base/TextGL.pas
+++ b/cmake/src/base/TextGL.pas
@@ -33,304 +33,189 @@ interface
 
 {$I switches.inc}
 
-// as long as the transition to freetype is not finished
-// use the old implementation
-{$IFDEF UseFreetype}
-  {$INCLUDE TextGLFreetype.pas}
-{$ELSE}
 uses
   gl,
+  glext,
   SDL,
+  Classes,
   UTexture,
+  UFont,
+  UPath,
   ULog;
 
-procedure BuildFont;                          // build our bitmap font
-procedure KillFont;                           // delete the font
-function  glTextWidth(const text: string): real; // returns text width
-procedure glPrint(const text: string);        // custom GL "Print" routine
+type
+  PGLFont = ^TGLFont;
+  TGLFont = record
+    Font:     TScalableFont;
+    Outlined: boolean;
+    X, Y, Z:  real;
+  end;
+
+const
+  ftNormal   = 0;
+  ftBold     = 1;
+  ftOutline1 = 2;
+  ftOutline2 = 3;
+
+var
+  Fonts:   array of TGLFont;
+  ActFont: integer;
+
+procedure BuildFonts;                         // builds all fonts
+procedure KillFonts;                          // deletes all font
+function  glTextWidth(const text: UTF8String): real; // returns text width
+procedure glPrint(const text: UTF8String);    // custom GL "Print" routine
 procedure ResetFont();                        // reset font settings of active font
 procedure SetFontPos(X, Y: real);             // sets X and Y
 procedure SetFontZ(Z: real);                  // sets Z
 procedure SetFontSize(Size: real);
 procedure SetFontStyle(Style: integer);       // sets active font style (normal, bold, etc)
 procedure SetFontItalic(Enable: boolean);     // sets italic type letter (works for all fonts)
-procedure SetFontAspectW(Aspect: real);
 procedure SetFontReflection(Enable:boolean;Spacing: real); // enables/disables text reflection
 
-//function NextPowerOfTwo(Value: integer): integer;
-// Checks if the ttf exists, if yes then a SDL_ttf is returned
-//function LoadFont(FileName: PAnsiChar; PointSize: integer):PTTF_Font;
-// Does the renderstuff, color is in $ffeecc style
-//function RenderText(font: PTTF_Font; Text:PAnsiChar; Color: Cardinal):PSDL_Surface;
-
-type
-  TTextGL = record
-    X:        real;
-    Y:        real;
-    Z:        real;
-    Text:     string;
-    Size:     real;
-    ColR:     real;
-    ColG:     real;
-    ColB:     real;
-  end;
-
-  PFont = ^TFont;
-  TFont = record
-    Tex:      TTexture;
-    Width:    array[0..255] of byte;
-    AspectW:  real;
-    Centered: boolean;
-    Outline:  real;
-    Italic:   boolean;
-    Reflection: boolean;
-    ReflectionSpacing: real;
-  end;
-
-
-var
-  Fonts:      array of TFont;
-  ActFont:    integer;
-
-
 implementation
 
 uses
-  Classes,
+  UTextEncoding,
   SysUtils,
   IniFiles,
   UCommon,
-  UGraphic,
   UMain,
-  UPath;
-
-var
-  // Colours for the reflection
-  TempColor:    array[0..3] of GLfloat;
+  UPathUtils;
 
 {**
- * Load font info.
- * FontFile is the name of the image (.png) not the data (.dat) file
+ * Returns either Filename if it is absolute or a path relative to FontPath.
  *}
-procedure LoadFontInfo(FontID: integer; const FontFile: string);
-var
-  Stream:  TFileStream;
-  DatFile: string;
+function FindFontFile(const Filename: string): IPath;
 begin
-  DatFile := ChangeFileExt(FontFile, '.dat');
-  FillChar(Fonts[FontID].Width[0], Length(Fonts[FontID].Width), 0);
+  Result := FontPath.Append(Filename);
+  // if path does not exist, try as an absolute path
+  if (not Result.IsFile) then
+    Result := Path(Filename);
+end;
 
-  Stream := nil;
-  try
-    Stream := TFileStream.Create(DatFile, fmOpenRead);
-    Stream.Read(Fonts[FontID].Width, 256);
-  except
-    Log.LogError('Error while reading font['+ inttostr(FontID) +']', 'LoadFontInfo');
+procedure AddFontFallbacks(FontIni: TMemIniFile; Font: TFont);
+var
+  FallbackFont: IPath;
+  IdentName: string;
+  I: Integer;
+begin
+  // evaluate the ini-file's 'Fallbacks' section
+  for I := 1 to 10 do
+  begin
+    IdentName := 'File' + IntToStr(I);
+    FallbackFont := FindFontFile(FontIni.ReadString('Fallbacks', IdentName, ''));
+    if (FallbackFont.Equals(PATH_NONE)) then
+      Continue;
+    try
+      Font.AddFallback(FallbackFont);
+    except
+      on E: EFontError do
+        Log.LogError('Setting font fallback ''' + FallbackFont.ToNative() + ''' failed: ' + E.Message);
+    end;
   end;
-  Stream.Free;
 end;
 
-// Builds bitmap fonts
-procedure BuildFont;
+const
+  FONT_NAMES: array [0..3] of string = (
+    'Normal', 'Bold', 'Outline1', 'Outline2'
+  );
+
+procedure BuildFonts;
 var
-  Count: integer;
+  I: integer;
   FontIni: TMemIniFile;
-  FontFile: string;     // filename of the image (with .png/... ending)
+  FontFile: IPath;
+  Outline: single;
+  Embolden: single;
+  OutlineFont: TFTScalableOutlineFont;
+  SectionName: string;
 begin
   ActFont := 0;
 
-  SetLength(Fonts, 4);
-  FontIni := TMemIniFile.Create(FontPath + 'fonts.ini');
-
-  // Normal
-
-  FontFile := FontPath + FontIni.ReadString('Normal', 'File', '');
-
-  Fonts[0].Tex := Texture.LoadTexture(true, FontFile, TEXTURE_TYPE_TRANSPARENT, 0);
-  Fonts[0].Tex.H := 30;
-  Fonts[0].AspectW := 0.9;
-  Fonts[0].Outline := 0;
-
-  LoadFontInfo(0, FontFile);
-
-  // Bold
-
-  FontFile := FontPath + FontIni.ReadString('Bold', 'File', '');
-
-  Fonts[1].Tex := Texture.LoadTexture(true, FontFile, TEXTURE_TYPE_TRANSPARENT, 0);
-  Fonts[1].Tex.H := 30;
-  Fonts[1].AspectW := 1;
-  Fonts[1].Outline := 0;
-
-  LoadFontInfo(1, FontFile);
-  for Count := 0 to 255 do
-    Fonts[1].Width[Count] := Fonts[1].Width[Count] div 2;
-
-  // Outline1
-
-  FontFile := FontPath + FontIni.ReadString('Outline1', 'File', '');
-
-  Fonts[2].Tex := Texture.LoadTexture(true, FontFile, TEXTURE_TYPE_TRANSPARENT, 0);
-  Fonts[2].Tex.H := 30;
-  Fonts[2].AspectW := 0.95;
-  Fonts[2].Outline := 5;
-
-  LoadFontInfo(2, FontFile);
-  for Count := 0 to 255 do
-    Fonts[2].Width[Count] := Fonts[2].Width[Count] div 2 + 2;
-
-  // Outline2
+  SetLength(Fonts, Length(FONT_NAMES));
 
-  FontFile := FontPath + FontIni.ReadString('Outline2', 'File', '');
-
-  Fonts[3].Tex := Texture.LoadTexture(true, FontFile, TEXTURE_TYPE_TRANSPARENT, 0);
-  Fonts[3].Tex.H := 30;
-  Fonts[3].AspectW := 0.95;
-  Fonts[3].Outline := 4;
-
-  LoadFontInfo(3, FontFile);
-  for Count := 0 to 255 do
-    Fonts[3].Width[Count] := Fonts[3].Width[Count] + 1;
+  FontIni := TMemIniFile.Create(FontPath.Append('fonts.ini').ToNative);
 
+  try
+    for I := 0 to High(FONT_NAMES) do
+    begin
+      SectionName := 'Font_'+FONT_NAMES[I];
+
+      FontFile := FindFontFile(FontIni.ReadString(SectionName , 'File', ''));
+
+      // create either outlined or normal font
+      Outline := FontIni.ReadFloat(SectionName, 'Outline', 0.0);
+      if (Outline > 0.0) then
+      begin
+        // outlined font
+        OutlineFont := TFTScalableOutlineFont.Create(FontFile, 64, Outline);
+        OutlineFont.SetOutlineColor(
+          FontIni.ReadFloat(SectionName, 'OutlineColorR',  0.0),
+          FontIni.ReadFloat(SectionName, 'OutlineColorG',  0.0),
+          FontIni.ReadFloat(SectionName, 'OutlineColorB',  0.0),
+          FontIni.ReadFloat(SectionName, 'OutlineColorA', -1.0)
+        );
+        Fonts[I].Font := OutlineFont;
+        Fonts[I].Outlined := true;
+      end
+      else
+      begin
+        // normal font
+        Embolden := FontIni.ReadFloat(SectionName, 'Embolden', 0.0);
+        Fonts[I].Font := TFTScalableFont.Create(FontFile, 64, Embolden);
+        Fonts[I].Outlined := false;
+      end;
+
+      Fonts[I].Font.GlyphSpacing := FontIni.ReadFloat(SectionName, 'GlyphSpacing', 0.0);
+      Fonts[I].Font.Stretch := FontIni.ReadFloat(SectionName, 'Stretch', 1.0);
+
+      AddFontFallbacks(FontIni, Fonts[I].Font);
+    end;
+  except
+    on E: EFontError do
+      Log.LogCritical(E.Message, 'BuildFont');
+  end;
 
   // close ini-file
   FontIni.Free;
 end;
 
-// Deletes the font
-procedure KillFont;
-begin
-  // delete all characters
-  //glDeleteLists(..., 256);
-end;
 
-function glTextWidth(const text: string): real;
+// Deletes the font
+procedure KillFonts;
 var
-  Letter: char;
-  i: integer;
-  Font: PFont;
+  I: integer;
 begin
-  Result := 0;
-  Font := @Fonts[ActFont];
-
-  for i := 1 to Length(text) do
-  begin
-    Letter := Text[i];
-    Result := Result + Font.Width[Ord(Letter)] * Font.Tex.H / 30 * Font.AspectW;
-  end;
-
-  if ((Result > 0) and Font.Italic) then
-    Result := Result + 12 * Font.Tex.H / 60 * Font.AspectW;
+  for I := 0 to High(Fonts) do
+    Fonts[I].Font.Free;
 end;
 
-procedure glPrintLetter(Letter: char);
+function glTextWidth(const text: UTF8String): real;
 var
-  TexX, TexY:        real;
-  TexR, TexB:        real;
-  TexHeight:         real;
-  FWidth:            real;
-  PL, PT:            real;
-  PR, PB:            real;
-  XItal:             real; // X shift for italic type letter
-  ReflectionSpacing: real; // Distance of the reflection
-  Font:              PFont;
-  Tex:               PTexture;
+  Bounds: TBoundsDbl;
 begin
-  Font := @Fonts[ActFont];
-  Tex := @Font.Tex;
-
-  FWidth := Font.Width[Ord(Letter)];
-
-  Tex.W := FWidth * (Tex.H/30) * Font.AspectW;
-
-  // set texture positions
-  TexX := (ord(Letter) mod 16) * 1/16 + 1/32 - FWidth/1024 - Font.Outline/1024;
-  TexY := (ord(Letter) div 16) * 1/16 + 2/1024;
-  TexR := (ord(Letter) mod 16) * 1/16 + 1/32 + FWidth/1024 + Font.Outline/1024;
-  TexB := (1 + ord(Letter) div 16) * 1/16 - 2/1024;
-
-  TexHeight := TexB - TexY;
-
-  // set vector positions
-  PL := Tex.X - Font.Outline * (Tex.H/30) * Font.AspectW /2;
-  PT := Tex.Y;
-  PR := PL + Tex.W + Font.Outline * (Tex.H/30) * Font.AspectW;
-  PB := PT + Tex.H;
-
-  if (not Font.Italic) then
-    XItal := 0
-  else
-    XItal := 12;
-
-  glEnable(GL_BLEND);
-  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
-  glEnable(GL_TEXTURE_2D);
-  glBindTexture(GL_TEXTURE_2D, Tex.TexNum);
-  
-  glBegin(GL_QUADS);
-    glTexCoord2f(TexX, TexY); glVertex2f(PL+XItal,  PT);
-    glTexCoord2f(TexX, TexB); glVertex2f(PL,        PB);
-    glTexCoord2f(TexR, TexB); glVertex2f(PR,        PB);
-    glTexCoord2f(TexR, TexY); glVertex2f(PR+XItal,  PT);
-  glEnd;
-
-  // <mog> Reflection
-  // Yes it would make sense to put this in an extra procedure,
-  // but this works, doesn't take much lines, and is almost lightweight
-  if Font.Reflection then
-  begin
-    ReflectionSpacing := Font.ReflectionSpacing + Tex.H/2;
-
-    glDepthRange(0, 10);
-    glDepthFunc(GL_LEQUAL);
-    glEnable(GL_DEPTH_TEST);
-
-    glBegin(GL_QUADS);
-      glColor4f(TempColor[0], TempColor[1], TempColor[2], 0);
-      glTexCoord2f(TexX, TexY + TexHeight/2);
-      glVertex3f(PL, PB + ReflectionSpacing - Tex.H/2, Tex.z);
-
-      glColor4f(TempColor[0], TempColor[1], TempColor[2], Tex.Alpha-0.3);
-      glTexCoord2f(TexX, TexB );
-      glVertex3f(PL + XItal, PT + ReflectionSpacing, Tex.z);
-
-      glTexCoord2f(TexR, TexB );
-      glVertex3f(PR + XItal, PT + ReflectionSpacing, Tex.z);
-
-      glColor4f(TempColor[0], TempColor[1], TempColor[2], 0);
-      glTexCoord2f(TexR, TexY + TexHeight/2);
-      glVertex3f(PR, PB + ReflectionSpacing - Tex.H/2, Tex.z);
-    glEnd;
-
-    glDisable(GL_DEPTH_TEST);
-  end; // reflection
-
-  glDisable(GL_TEXTURE_2D);
-  glDisable(GL_BLEND);
-
-  Tex.X := Tex.X + Tex.W;
-
-  //write the colour back
-  glColor4fv(@TempColor);
+  Bounds := Fonts[ActFont].Font.BBox(Text, true);
+  Result := Bounds.Right - Bounds.Left;
 end;
 
 // Custom GL "Print" Routine
-procedure glPrint(const Text: string);
+procedure glPrint(const Text: UTF8String);
 var
-  Pos: integer;
+  GLFont: PGLFont;
 begin
   // if there is no text do nothing
   if (Text = '') then
     Exit;
 
-  //Save the actual color and alpha (for reflection)
-  glGetFloatv(GL_CURRENT_COLOR, @TempColor);
+  GLFont := @Fonts[ActFont];
 
-  for Pos := 1 to Length(Text) do
-  begin
-    glPrintLetter(Text[Pos]);
-  end;
+  glPushMatrix();
+    // set font position
+    glTranslatef(GLFont.X, GLFont.Y + GLFont.Font.Ascender, GLFont.Z);
+    // draw string
+    GLFont.Font.Print(Text);
+  glPopMatrix();
 end;
 
 procedure ResetFont();
@@ -343,18 +228,18 @@ end;
 
 procedure SetFontPos(X, Y: real);
 begin
-  Fonts[ActFont].Tex.X := X;
-  Fonts[ActFont].Tex.Y := Y;
+  Fonts[ActFont].X := X;
+  Fonts[ActFont].Y := Y;
 end;
 
 procedure SetFontZ(Z: real);
 begin
-  Fonts[ActFont].Tex.Z := Z;
+  Fonts[ActFont].Z := Z;
 end;
 
 procedure SetFontSize(Size: real);
 begin
-  Fonts[ActFont].Tex.H := Size;
+  Fonts[ActFont].Font.Height := Size;
 end;
 
 procedure SetFontStyle(Style: integer);
@@ -364,21 +249,19 @@ end;
 
 procedure SetFontItalic(Enable: boolean);
 begin
-  Fonts[ActFont].Italic := Enable;
-end;
-
-procedure SetFontAspectW(Aspect: real);
-begin
-  Fonts[ActFont].AspectW := Aspect;
+  if (Enable) then
+    Fonts[ActFont].Font.Style := Fonts[ActFont].Font.Style + [Italic]
+  else
+    Fonts[ActFont].Font.Style := Fonts[ActFont].Font.Style - [Italic]
 end;
 
 procedure SetFontReflection(Enable: boolean; Spacing: real);
 begin
-  Fonts[ActFont].Reflection        := Enable;
-  Fonts[ActFont].ReflectionSpacing := Spacing;
+  if (Enable) then
+    Fonts[ActFont].Font.Style := Fonts[ActFont].Font.Style + [Reflect]
+  else
+    Fonts[ActFont].Font.Style := Fonts[ActFont].Font.Style - [Reflect];
+  Fonts[ActFont].Font.ReflectionSpacing := Spacing - Fonts[ActFont].Font.Descender;
 end;
 
 end.
-
-{$ENDIF}
-
diff --git a/cmake/src/base/TextGLFreetype.pas b/cmake/src/base/TextGLFreetype.pas
deleted file mode 100644
index 61b26693..00000000
--- a/cmake/src/base/TextGLFreetype.pas
+++ /dev/null
@@ -1,222 +0,0 @@
-{* UltraStar Deluxe - Karaoke Game
- *
- * UltraStar Deluxe is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * 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; either version 2
- * of the License, or (at your option) any later version.
- *
- * 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; see the file COPYING. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- *
- * $URL: https://ultrastardx.svn.sourceforge.net/svnroot/ultrastardx/trunk/src/base/TextGL.pas $
- * $Id: TextGL.pas 1483 2008-10-28 19:01:20Z tobigun $
- *}
-
-(*
-unit TextGL;
-
-interface
-
-{$IFDEF FPC}
-  {$MODE Delphi}
-{$ENDIF}
-
-{$I switches.inc}
-*)
-
-uses
-  gl,
-  glext,
-  SDL,
-  UTexture,
-  UFont,
-  Classes,
-  ULog;
-
-type
-  PGLFont = ^TGLFont;
-  TGLFont = record
-    Font:     TScalableFont;
-    X, Y, Z:  real;
-  end;
-
-var
-  Fonts:   array of TGLFont;
-  ActFont: integer;
-
-procedure BuildFont;                          // build our bitmap font
-procedure KillFont;                           // delete the font
-function  glTextWidth(const text: string): real; // returns text width
-procedure glPrint(const text: string);        // custom GL "Print" routine
-procedure ResetFont();                        // reset font settings of active font
-procedure SetFontPos(X, Y: real);             // sets X and Y
-procedure SetFontZ(Z: real);                  // sets Z
-procedure SetFontSize(Size: real);
-procedure SetFontStyle(Style: integer);       // sets active font style (normal, bold, etc)
-procedure SetFontItalic(Enable: boolean);     // sets italic type letter (works for all fonts)
-procedure SetFontReflection(Enable:boolean;Spacing: real); // enables/disables text reflection
-
-implementation
-
-uses
-  UMain,
-  UCommon,
-  UTextEncoding,
-  SysUtils,
-  IniFiles;
-
-function FindFontFile(FontIni: TCustomIniFile; Font: string): string;
-var
-  Filename: string;
-begin
-  Filename := FontIni.ReadString(Font, 'File', '');
-  Result := FontPath + Filename;
-  // if path does not exist, try as an absolute path
-  if (not FileExists(Result)) then
-    Result := Filename;
-end;
-
-procedure BuildFont;
-var
-  FontIni: TMemIniFile;
-  //BitmapFont: TBitmapFont;
-  FontFile: string;
-begin
-  ActFont := 0;
-
-  SetLength(Fonts, 4);
-  FontIni := TMemIniFile.Create(FontPath + 'fontsTTF.ini');
-  //FontIni := TMemIniFile.Create(FontPath + 'fonts.ini');
-
-  try
-
-    // Normal
-    FontFile := FindFontFile(FontIni, 'Normal');
-    Fonts[0].Font := TFTScalableFont.Create(FontFile, 64);
-    //Fonts[0].Font.GlyphSpacing := 1.4;
-    //Fonts[0].Font.Aspect := 1.2;
-
-    {
-    BitmapFont := TBitmapFont.Create(FontFile, 0, 19, 35, -10);
-    BitmapFont.CorrectWidths(2, 0);
-    Fonts[0].Font := TScalableFont.Create(BitmapFont, false);
-    }
-
-    //Fonts[0].Font.Aspect := 0.9;
-
-    // Bold
-    FontFile := FindFontFile(FontIni, 'Bold');
-    Fonts[1].Font := TFTScalableFont.Create(FontFile, 64);
-
-    // Outline1
-    FontFile := FindFontFile(FontIni, 'Outline1');
-    Fonts[2].Font := TFTScalableOutlineFont.Create(FontFile, 64, 0.06);
-    //TFTScalableOutlineFont(Fonts[2].Font).SetOutlineColor(0.3, 0.3, 0.3);
-
-    // Outline2
-    FontFile := FindFontFile(FontIni, 'Outline2');
-    Fonts[3].Font := TFTScalableOutlineFont.Create(FontFile, 64, 0.08);
-
-  except on E: Exception do
-    Log.LogCritical(E.Message, 'BuildFont');
-  end;
-
-  // close ini-file
-  FontIni.Free;
-end;
-
-
-// Deletes the font
-procedure KillFont;
-begin
-  // delete all characters
-  //glDeleteLists(..., 256);
-end;
-
-function glTextWidth(const text: string): real;
-var
-  Bounds: TBoundsDbl;
-begin
-  // FIXME: remove conversion
-  Bounds := Fonts[ActFont].Font.BBox(RecodeString(Text, encCP1252), true);
-  Result := Bounds.Right - Bounds.Left;
-end;
-
-// Custom GL "Print" Routine
-procedure glPrint(const Text: string);
-var
-  GLFont: PGLFont;
-begin
-  // if there is no text do nothing
-  if (Text = '') then
-    Exit;
-
-  GLFont := @Fonts[ActFont];
-
-  glPushMatrix();
-    // set font position
-    glTranslatef(GLFont.X, GLFont.Y + GLFont.Font.Ascender, GLFont.Z);
-    // draw string
-    // FIXME: remove conversion
-    GLFont.Font.Print(RecodeString(Text, encCP1252));
-  glPopMatrix();
-end;
-
-procedure ResetFont();
-begin
-  SetFontPos(0, 0);
-  SetFontZ(0);
-  SetFontItalic(False);
-  SetFontReflection(False, 0);
-end;
-
-procedure SetFontPos(X, Y: real);
-begin
-  Fonts[ActFont].X := X;
-  Fonts[ActFont].Y := Y;
-end;
-
-procedure SetFontZ(Z: real);
-begin
-  Fonts[ActFont].Z := Z;
-end;
-
-procedure SetFontSize(Size: real);
-begin
-  Fonts[ActFont].Font.Height := Size;
-end;
-
-procedure SetFontStyle(Style: integer);
-begin
-  ActFont := Style;
-end;
-
-procedure SetFontItalic(Enable: boolean);
-begin
-  if (Enable) then
-    Fonts[ActFont].Font.Style := Fonts[ActFont].Font.Style + [Italic]
-  else
-    Fonts[ActFont].Font.Style := Fonts[ActFont].Font.Style - [Italic]
-end;
-
-procedure SetFontReflection(Enable: boolean; Spacing: real);
-begin
-  if (Enable) then
-    Fonts[ActFont].Font.Style := Fonts[ActFont].Font.Style + [Reflect]
-  else
-    Fonts[ActFont].Font.Style := Fonts[ActFont].Font.Style - [Reflect];
-  Fonts[ActFont].Font.ReflectionSpacing := Spacing - Fonts[ActFont].Font.Descender;
-end;
-
-end.
diff --git a/cmake/src/base/UBeatTimer.pas b/cmake/src/base/UBeatTimer.pas
index a47a06f9..bc03de76 100644
--- a/cmake/src/base/UBeatTimer.pas
+++ b/cmake/src/base/UBeatTimer.pas
@@ -1,170 +1,299 @@
-                      {* UltraStar Deluxe - Karaoke Game
- *
- * UltraStar Deluxe is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * 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; either version 2
- * of the License, or (at your option) any later version.
- *
- * 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; see the file COPYING. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- *
- * $URL: https://ultrastardx.svn.sourceforge.net/svnroot/ultrastardx/trunk/src/base/USingNotes.pas $
- * $Id: USingNotes.pas 1406 2008-09-23 21:43:52Z k-m_schindler $
- *}
+{* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *}
+
+unit UBeatTimer;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  UTime;
+
+type
+  (**
+   * TLyricsState contains all information concerning the
+   * state of the lyrics, e.g. the current beat or duration of the lyrics.
+   *)
+  TLyricsState = class
+    private
+      fTimer:        TRelativeTimer; // keeps track of the current time
+      fSyncSource:   TSyncSource;
+      fAvgSyncDiff:  real;
+      fLastClock:    real;       // last master clock value
+      // Note: do not use Timer.GetState() to check if lyrics are paused as
+      // Timer.Pause() is used for synching.
+      fPaused:       boolean;
+
+      function Synchronize(LyricTime: real): real;
+    public
+      OldBeat:      integer;    // previous discovered beat
+      CurrentBeat:  integer;    // current beat (rounded)
+      MidBeat:      real;       // current beat (float)
+
+      // now we use this for super synchronization!
+      // only used when analyzing voice
+      // TODO: change ...D to ...Detect(ed)
+      OldBeatD:     integer;    // previous discovered beat
+      CurrentBeatD: integer;    // current discovered beat (rounded)
+      MidBeatD:     real;       // current discovered beat (float)
+
+      // we use this for audible clicks
+      // TODO: Change ...C to ...Click
+      OldBeatC:     integer;    // previous discovered beat
+      CurrentBeatC: integer;
+      MidBeatC:     real;       // like CurrentBeatC
+
+      OldLine:      integer;    // previous displayed sentence
+
+      StartTime:    real;       // time till start of lyrics (= Gap)
+      TotalTime:    real;       // total song time
+
+      constructor Create();
+
+      {**
+       * Resets the LyricsState state.
+       *}
+      procedure Reset();
+
+      procedure UpdateBeats();
+
+      {**
+       * Sets a master clock for this LyricsState. If no sync-source is set
+       * or SyncSource is nil the internal timer is used.
+       *}
+      procedure SetSyncSource(SyncSource: TSyncSource);
+
+      {**
+       * Starts the timer. This is either done
+       * - immediately if WaitForTrigger is false or
+       * - after the first call to GetCurrentTime()/SetCurrentTime() or Start(false)
+       *}
+      procedure Start(WaitForTrigger: boolean = false);
+
+      {**
+       * Pauses the timer.
+       * The counter is preserved and can be resumed by a call to Start().
+       *}
+      procedure Pause();
+
+      {**
+       * Stops the timer.
+       * The counter is reset to 0.
+       *}
+      procedure Stop();
+
+      (**
+       * Returns/Sets the current song time (in seconds) used as base-timer for lyrics etc.
+       * If GetCurrentTime()/SetCurrentTime() if Start() was called
+       *)
+      function GetCurrentTime(): real;
+      procedure SetCurrentTime(Time: real);
+  end;
+
+implementation
+
+uses
+  UNote,
+  ULog,
+  SysUtils,
+  Math;
+
+
+constructor TLyricsState.Create();
+begin
+  // create a triggered timer, so we can Pause() it, set the time
+  // and Resume() it afterwards for better synching.
+  fTimer := TRelativeTimer.Create();
+
+  // reset state
+  Reset();
+end;
+
+procedure TLyricsState.Pause();
+begin
+  fTimer.Pause();
+  fPaused := true;
+end;
+
+procedure TLyricsState.Start(WaitForTrigger: boolean);
+begin
+  fTimer.Start(WaitForTrigger);
+  fPaused := false;
+  fLastClock := -1;
+  fAvgSyncDiff := -1;
+end;
+
+procedure TLyricsState.Stop();
+begin
+  fTimer.Stop();
+  fPaused := false;
+end;
+
+procedure TLyricsState.SetCurrentTime(Time: real);
+begin
+  fTimer.SetTime(Time);
+  fLastClock := -1;
+  fAvgSyncDiff := -1;
+end;
+
+{.$DEFINE LOG_SYNC}
+
+function TLyricsState.Synchronize(LyricTime: real): real;
+var
+  MasterClock: real;
+  TimeDiff: real;
+const
+  AVG_HISTORY_FACTOR = 0.7;
+  PAUSE_THRESHOLD = 0.010; // 10ms
+  FORWARD_THRESHOLD = 0.010; // 10ms
+begin
+  MasterClock := fSyncSource.GetClock();
+  Result := LyricTime;
 
-unit UBeatTimer;
+  // do not sync if lyrics are paused externally or if the timestamp is old
+  if (fPaused or (MasterClock = fLastClock)) then
+    Exit;
+
+  // calculate average time difference (some sort of weighted mean).
+  // The bigger AVG_HISTORY_FACTOR is, the smoother is the average diff.
+  // This is done as some timestamps might be wrong or even lower
+  // than their predecessor.
+  TimeDiff := MasterClock - LyricTime;
+  if (fAvgSyncDiff = -1) then
+    fAvgSyncDiff := TimeDiff
+  else
+    fAvgSyncDiff := TimeDiff * (1-AVG_HISTORY_FACTOR) +
+                    fAvgSyncDiff * AVG_HISTORY_FACTOR;
 
-interface
+  {$IFDEF LOG_SYNC}
+  //Log.LogError(Format('TimeDiff: %.3f', [TimeDiff]));
+  {$ENDIF}
 
-{$IFDEF FPC}
-  {$MODE Delphi}
-{$ENDIF}
-
-{$I switches.inc}
-
-uses
-  UTime;
-
-type
-  (**
-   * TLyricsState contains all information concerning the
-   * state of the lyrics, e.g. the current beat or duration of the lyrics.
-   *)
-  TLyricsState = class
-    private
-      Timer:        TRelativeTimer; // keeps track of the current time
-    public
-      OldBeat:      integer;    // previous discovered beat
-      CurrentBeat:  integer;    // current beat (rounded)
-      MidBeat:      real;       // current beat (float)
-
-      // now we use this for super synchronization!
-      // only used when analyzing voice
-      // TODO: change ...D to ...Detect(ed)
-      OldBeatD:     integer;    // previous discovered beat
-      CurrentBeatD: integer;    // current discovered beat (rounded)
-      MidBeatD:     real;       // current discovered beat (float)
-
-      // we use this for audible clicks
-      // TODO: Change ...C to ...Click
-      OldBeatC:     integer;    // previous discovered beat
-      CurrentBeatC: integer;
-      MidBeatC:     real;       // like CurrentBeatC
-
-      OldLine:      integer;    // previous displayed sentence
-
-      StartTime:    real;       // time till start of lyrics (= Gap)
-      TotalTime:    real;       // total song time
-
-      constructor Create();
-      procedure Pause();
-      procedure Resume();
-
-      procedure Reset();
-      procedure UpdateBeats();
-
-      (**
-       * current song time (in seconds) used as base-timer for lyrics etc.
-       *)
-      function GetCurrentTime(): real;
-      procedure SetCurrentTime(Time: real);
-  end;
-
-implementation
-uses UNote, Math;
-
-
-constructor TLyricsState.Create();
-begin
-  // create a triggered timer, so we can Pause() it, set the time
-  // and Resume() it afterwards for better synching.
-  Timer := TRelativeTimer.Create(true);
-
-  // reset state
-  Reset();
-end;
-
-procedure TLyricsState.Pause();
-begin
-  Timer.Pause();
-end;
-
-procedure TLyricsState.Resume();
-begin
-  Timer.Resume();
-end;
-
-procedure TLyricsState.SetCurrentTime(Time: real);
-begin
-  // do not start the timer (if not started already),
-  // after setting the current time
-  Timer.SetTime(Time, false);
-end;
-
-function TLyricsState.GetCurrentTime(): real;
-begin
-  Result := Timer.GetTime();
+  // do not go backwards in time as this could mess up the score
+  if (fAvgSyncDiff > FORWARD_THRESHOLD) then
+  begin
+    {$IFDEF LOG_SYNC}
+    Log.LogError('Sync: ' + floatToStr(MasterClock) + ' > ' + floatToStr(LyricTime));
+    {$ENDIF}
+
+    Result := LyricTime + fAvgSyncDiff;
+    fTimer.SetTime(Result);
+    fTimer.Start();
+    fAvgSyncDiff := -1;
+  end
+  else if (fAvgSyncDiff < -PAUSE_THRESHOLD) then
+  begin
+    // wait until timer and master clock are in sync (> 10ms)
+    fTimer.Pause();
+
+    {$IFDEF LOG_SYNC}
+    Log.LogError('Pause: ' + floatToStr(MasterClock) + ' < ' + floatToStr(LyricTime));
+    {$ENDIF}
+  end
+  else if (fTimer.GetState = rtsPaused) and (fAvgSyncDiff >= 0) then
+  begin
+    fTimer.Start();
+
+    {$IFDEF LOG_SYNC}
+    Log.LogError('Unpause: ' + floatToStr(LyricTime));
+    {$ENDIF}
+  end;
+  fLastClock := MasterClock;
 end;
-
-(**
- * Resets the timer and state of the lyrics.
- * The timer will be stopped afterwards so you have to call Resume()
- * to start the lyrics timer. 
- *)
-procedure TLyricsState.Reset();
-begin
-  Pause();
-  SetCurrentTime(0);
-
-  StartTime := 0;
-  TotalTime := 0;
-
-  OldBeat      := -1;
-  MidBeat      := -1;
-  CurrentBeat  := -1;
-
-  OldBeatC     := -1;
-  MidBeatC     := -1;
-  CurrentBeatC := -1;
-
-  OldBeatD     := -1;
-  MidBeatD     := -1;
-  CurrentBeatD := -1;
-end;
-
-(**
- * Updates the beat information (CurrentBeat/MidBeat/...) according to the
- * current lyric time.
- *)
-procedure TLyricsState.UpdateBeats();
-var
-  CurLyricsTime: real;
-begin
-  CurLyricsTime := GetCurrentTime();
-
-  OldBeat := CurrentBeat;
-  MidBeat := GetMidBeat(CurLyricsTime - StartTime / 1000);
-  CurrentBeat := Floor(MidBeat);
-
-  OldBeatC := CurrentBeatC;
-  MidBeatC := GetMidBeat(CurLyricsTime - StartTime / 1000);
-  CurrentBeatC := Floor(MidBeatC);
-
-  OldBeatD := CurrentBeatD;
-  // MidBeatD = MidBeat with additional GAP
-  MidBeatD := -0.5 + GetMidBeat(CurLyricsTime - (StartTime + 120 + 20) / 1000);
-  CurrentBeatD := Floor(MidBeatD);
-end;
-
+
+function TLyricsState.GetCurrentTime(): real;
+var
+  LyricTime: real;
+begin
+  LyricTime := fTimer.GetTime();
+  if Assigned(fSyncSource) then
+    Result := Synchronize(LyricTime)
+  else
+    Result := LyricTime;
+end;
+
+procedure TLyricsState.SetSyncSource(SyncSource: TSyncSource);
+begin
+  fSyncSource := SyncSource;
+end;
+
+(**
+ * Resets the timer and state of the lyrics.
+ * The timer will be stopped afterwards so you have to call Resume()
+ * to start the lyrics timer. 
+ *)
+procedure TLyricsState.Reset();
+begin
+  Stop();
+  fPaused := false;
+
+  fSyncSource := nil;
+
+  StartTime := 0;
+  TotalTime := 0;
+
+  OldBeat      := -1;
+  MidBeat      := -1;
+  CurrentBeat  := -1;
+
+  OldBeatC     := -1;
+  MidBeatC     := -1;
+  CurrentBeatC := -1;
+
+  OldBeatD     := -1;
+  MidBeatD     := -1;
+  CurrentBeatD := -1;
+end;
+
+(**
+ * Updates the beat information (CurrentBeat/MidBeat/...) according to the
+ * current lyric time.
+ *)
+procedure TLyricsState.UpdateBeats();
+var
+  CurLyricsTime: real;
+begin
+  CurLyricsTime := GetCurrentTime();
+
+  OldBeat := CurrentBeat;
+  MidBeat := GetMidBeat(CurLyricsTime - StartTime / 1000);
+  CurrentBeat := Floor(MidBeat);
+
+  OldBeatC := CurrentBeatC;
+  MidBeatC := GetMidBeat(CurLyricsTime - StartTime / 1000);
+  CurrentBeatC := Floor(MidBeatC);
+
+  OldBeatD := CurrentBeatD;
+  // MidBeatD = MidBeat with additional GAP
+  MidBeatD := -0.5 + GetMidBeat(CurLyricsTime - (StartTime + 120 + 20) / 1000);
+  CurrentBeatD := Floor(MidBeatD);
+end;
+
 end.
\ No newline at end of file
diff --git a/cmake/src/base/UCatCovers.pas b/cmake/src/base/UCatCovers.pas
index 6ef81b68..85cb850f 100644
--- a/cmake/src/base/UCatCovers.pas
+++ b/cmake/src/base/UCatCovers.pas
@@ -24,10 +24,6 @@
  *}
 
 unit UCatCovers;
-/////////////////////////////////////////////////////////////////////////
-//                   UCatCovers by Whiteshark                          //
-//          Class for listing and managing the Category Covers         //
-/////////////////////////////////////////////////////////////////////////
 
 interface
 
@@ -38,20 +34,21 @@ interface
 {$I switches.inc}
 
 uses
-  UIni;
+  UIni,
+  UPath;
 
 type
   TCatCovers = class
     protected
-      cNames:    array [0..high(ISorting)] of array of string;
-      cFiles:    array [0..high(ISorting)] of array of string;
+      cNames:    array [TSortingType] of array of UTF8String;
+      cFiles:    array [TSortingType] of array of IPath;
     public
       constructor Create;
       procedure Load; //Load Cover aus Cover.ini and Cover Folder
-      procedure LoadPath(const CoversPath: string);
-      procedure Add(Sorting: integer; Name, Filename: string); //Add a Cover
-      function  CoverExists(Sorting: integer; Name: string): boolean; //Returns True when a cover with the given Name exists
-      function  GetCover(Sorting: integer; Name: string): string; //Returns the Filename of a Cover
+      procedure LoadPath(const CoversPath: IPath);
+      procedure Add(Sorting: TSortingType; const Name: UTF8String; const Filename: IPath); //Add a Cover
+      function  CoverExists(Sorting: TSortingType; const Name: UTF8String): boolean; //Returns True when a cover with the given Name exists
+      function  GetCover(Sorting: TSortingType; const Name: UTF8String): IPath; //Returns the Filename of a Cover
   end;
 
 var
@@ -63,10 +60,11 @@ uses
   IniFiles,
   SysUtils,
   Classes,
-  // UFiles,
+  UFilesystem,
   ULog,
   UMain,
-  UPath;
+  UUnicodeUtils,
+  UPathUtils;
 
 constructor TCatCovers.Create;
 begin
@@ -79,90 +77,96 @@ var
   I: integer;
 begin
   for I := 0 to CoverPaths.Count-1 do
-    LoadPath(CoverPaths[I]);
+    LoadPath(CoverPaths[I] as IPath);
 end;
 
 (**
  * Load Cover from Cover.ini and Cover Folder
  *)
-procedure TCatCovers.LoadPath(const CoversPath: string);
+procedure TCatCovers.LoadPath(const CoversPath: IPath);
 var
   Ini: TMemIniFile;
-  SR:  TSearchRec;
   List: TStringlist;
-  I, J: Integer;
-  Name, Filename, Temp: string;
+  I: Integer;
+  SortType: TSortingType;
+  Filename: IPath;
+  Name, TmpName: UTF8String;
+  CatCover: IPath;
+  Iter: IFileIterator;
+  FileInfo: TFileInfo;
 begin
   Ini := nil;
   List := nil;
 
   try
-    Ini  := TMemIniFile.Create(CoversPath + 'covers.ini');
+    Ini  := TMemIniFile.Create(CoversPath.Append('covers.ini').ToNative);
     List := TStringlist.Create;
 
     //Add every Cover in Covers Ini for Every Sorting option
-    for I := 0 to High(ISorting) do
+    for SortType := Low(TSortingType) to High(TSortingType) do
     begin
-      Ini.ReadSection(ISorting[I], List);
+      Ini.ReadSection(ISorting[Ord(SortType)], List);
 
-      for J := 0 to List.Count - 1 do
-        Add(I, List.Strings[J], CoversPath + Ini.ReadString(ISorting[I], List.Strings[J], 'NoCover.jpg'));
+      for I := 0 to List.Count - 1 do
+      begin
+        CatCover := Path(Ini.ReadString(ISorting[Ord(SortType)], List.Strings[I], 'NoCover.jpg'));
+        Add(SortType, List.Strings[I], CoversPath.Append(CatCover));
+      end;
     end;
   finally
     Ini.Free;
     List.Free;
   end;
 
-  try
-    //Add Covers from Folder
-    if (FindFirst (CoversPath + '*.jpg', faAnyFile, SR) = 0) then
-      repeat
-        //Add Cover if it doesn't exist for every Section
-        Name := SR.Name;
-        Filename := CoversPath + Name;
-        Delete (Name, length(Name) - 3, 4);
-
-        for I := 0 to high(ISorting) do
-        begin
-          Temp := Name;
-          if ((I = sTitle) or (I = sTitle2)) and (Pos ('Title', Temp) <> 0) then
-            Delete (Temp, Pos ('Title', Temp), 5)
-          else if (I = sArtist) or (I = sArtist2) and (Pos ('Artist', Temp) <> 0) then
-            Delete (Temp, Pos ('Artist', Temp), 6);
-
-          if not CoverExists(I, Temp) then
-            Add (I, Temp, Filename);
-        end;
-      until FindNext (SR) <> 0;
-  finally
-    FindClose (SR);
+  //Add Covers from Folder
+  Iter := FileSystem.FileFind(CoversPath.Append('*.jpg'), 0);
+  while Iter.HasNext do
+  begin
+    FileInfo := Iter.Next;
+
+    //Add Cover if it doesn't exist for every Section
+    Filename := CoversPath.Append(FileInfo.Name);
+    Name := FileInfo.Name.SetExtension('').ToUTF8;
+
+    for SortType := Low(TSortingType) to High(TSortingType) do
+    begin
+      TmpName := Name;
+      if (SortType = sTitle) and (UTF8Pos('Title', TmpName) <> 0) then
+        UTF8Delete(TmpName, UTF8Pos('Title', TmpName), 5)
+      else if (SortType = sArtist) and (UTF8Pos('Artist', TmpName) <> 0) then
+        UTF8Delete(TmpName, UTF8Pos('Artist', TmpName), 6);
+
+      if not CoverExists(SortType, TmpName) then
+        Add(SortType, TmpName, Filename);
+    end;
   end;
 end;
 
   //Add a Cover
-procedure TCatCovers.Add(Sorting: integer; Name, Filename: string);
+procedure TCatCovers.Add(Sorting: TSortingType; const Name: UTF8String; const Filename: IPath);
 begin
-  if FileExists (Filename) then //If Exists -> Add
+  if Filename.IsFile then //If Exists -> Add
   begin
-    SetLength (CNames[Sorting], Length(CNames[Sorting]) + 1);
-    SetLength (CFiles[Sorting], Length(CNames[Sorting]) + 1);
+    SetLength(CNames[Sorting], Length(CNames[Sorting]) + 1);
+    SetLength(CFiles[Sorting], Length(CNames[Sorting]) + 1);
 
-    CNames[Sorting][high(cNames[Sorting])] := Uppercase(Name);
+    CNames[Sorting][high(cNames[Sorting])] := UTF8Uppercase(Name);
     CFiles[Sorting][high(cNames[Sorting])] := FileName;
   end;
 end;
 
   //Returns True when a cover with the given Name exists
-function TCatCovers.CoverExists(Sorting: integer; Name: string): boolean;
+function TCatCovers.CoverExists(Sorting: TSortingType; const Name: UTF8String): boolean;
 var
   I: Integer;
+  UpperName: UTF8String;
 begin
   Result := False;
-  Name := Uppercase(Name); //Case Insensitiv
+  UpperName := UTF8Uppercase(Name); //Case Insensitiv
 
   for I := 0 to high(cNames[Sorting]) do
   begin
-    if (cNames[Sorting][I] = Name) then //Found Name
+    if (cNames[Sorting][I] = UpperName) then //Found Name
     begin
       Result := true;
       break; //Break For Loop
@@ -171,16 +175,18 @@ begin
 end;
 
   //Returns the Filename of a Cover
-function TCatCovers.GetCover(Sorting: integer; Name: string): string;
+function TCatCovers.GetCover(Sorting: TSortingType; const Name: UTF8String): IPath;
 var
   I: Integer;
+  UpperName: UTF8String;
+  NoCoverPath: IPath;
 begin
-  Result := '';
-  Name := Uppercase(Name);
+  Result := PATH_NONE;
+  UpperName := UTF8Uppercase(Name);
 
   for I := 0 to high(cNames[Sorting]) do
   begin
-    if cNames[Sorting][I] = Name then
+    if cNames[Sorting][I] = UpperName then
     begin
       Result := cFiles[Sorting][I];
       Break;
@@ -188,17 +194,18 @@ begin
   end;
 
   //No Cover
-  if (Result = '') then
+  if (Result.IsUnset) then
   begin
     for I := 0 to CoverPaths.Count-1 do
     begin
-      if (FileExists(CoverPaths[I] + 'NoCover.jpg')) then
+      NoCoverPath := (CoverPaths[I] as IPath).Append('NoCover.jpg');
+      if (NoCoverPath.IsFile) then
       begin
-        Result := CoverPaths[I] + 'NoCover.jpg';
+        Result := NoCoverPath;
         Break;
       end;
     end;
   end;
 end;
 
-end.
+end.
\ No newline at end of file
diff --git a/cmake/src/base/UCommandLine.pas b/cmake/src/base/UCommandLine.pas
index 281a480d..ac0db2c2 100644
--- a/cmake/src/base/UCommandLine.pas
+++ b/cmake/src/base/UCommandLine.pas
@@ -33,6 +33,9 @@ interface
 
 {$I switches.inc}
 
+uses
+  UPath;
+
 type
   TScreenMode = (scmDefault, scmFullscreen, scmWindowed);
 
@@ -64,9 +67,9 @@ type
       Screens:    integer;
 
       // some strings set when reading infos {Length=0: Not Set}
-      SongPath:   string;
-      ConfigFile: string;
-      ScoreFile:  string;
+      SongPath:   IPath;
+      ConfigFile: IPath;
+      ScoreFile:  IPath;
 
       // pseudo integer values
       property Language:      integer read GetLanguage;
@@ -144,9 +147,9 @@ begin
   Screens     := -1;
 
   // some strings set when reading infos {Length=0 Not Set}
-  SongPath    := '';
-  ConfigFile  := '';
-  ScoreFile   := '';
+  SongPath    := PATH_NONE;
+  ConfigFile  := PATH_NONE;
+  ScoreFile   := PATH_NONE;
 end;
 
 {**
@@ -248,7 +251,7 @@ begin
         if (PCount > I) then
         begin
           // write value to string
-          SongPath := ParamStr(I + 1);
+          SongPath := Path(ParamStr(I + 1));
         end;
       end
 
@@ -258,11 +261,11 @@ begin
         if (PCount > I) then
         begin
           // write value to string
-          ConfigFile := ParamStr(I + 1);
+          ConfigFile := Path(ParamStr(I + 1));
 
           // is this a relative path -> then add gamepath
-          if Not ((Length(ConfigFile) > 2) AND (ConfigFile[2] = ':')) then
-            ConfigFile := ExtractFilePath(ParamStr(0)) + Configfile;
+          if (not ConfigFile.IsAbsolute) then
+            ConfigFile := Platform.GetExecutionDir().Append(ConfigFile);
         end;
       end
 
@@ -272,7 +275,7 @@ begin
         if (PCount > I) then
         begin
           // write value to string
-          ScoreFile := ParamStr(I + 1);
+          ScoreFile := Path(ParamStr(I + 1));
         end;
       end;
 
diff --git a/cmake/src/base/UCommon.pas b/cmake/src/base/UCommon.pas
index d729b6dd..18022337 100644
--- a/cmake/src/base/UCommon.pas
+++ b/cmake/src/base/UCommon.pas
@@ -39,9 +39,29 @@ uses
   {$IFDEF MSWINDOWS}
   Windows,
   {$ENDIF}
-  sdl,
   UConfig,
-  ULog;
+  ULog,
+  UPath;
+
+type
+  TStringDynArray = array of string;
+  TUTF8StringDynArray = array of UTF8String;
+
+const
+  SepWhitespace = [#9, #10, #13, ' ']; // tab, lf, cr, space
+
+{**
+ * Splits a string into pieces separated by Separators.
+ * MaxCount specifies the max. number of pieces. If it is <= 0 the number is
+ * not limited. If > 0 the last array element will hold the rest of the string
+ * (with leading separators removed).
+ *
+ * Examples:
+ *   SplitString(' split  me now ', 0) -> ['split', 'me', 'now']
+ *   SplitString(' split  me now ', 1) -> ['split', 'me now']
+ *}
+function SplitString(const Str: string; MaxCount: integer = 0; Separators: TSysCharSet = SepWhitespace): TStringDynArray;
+
 
 type
   TMessageType = (mtInfo, mtError);
@@ -50,49 +70,27 @@ procedure ShowMessage(const msg: string; msgType: TMessageType = mtInfo);
 
 procedure ConsoleWriteLn(const msg: string);
 
-function RWopsFromStream(Stream: TStream): PSDL_RWops;
-
 {$IFDEF FPC}
 function RandomRange(aMin: integer; aMax: integer): integer;
 {$ENDIF}
 
-function StringReplaceW(text: WideString; search, rep: WideChar): WideString;
-function AdaptFilePaths(const aPath: WideString): WideString;
-
 procedure DisableFloatingPointExceptions();
 procedure SetDefaultNumericLocale();
 procedure RestoreNumericLocale();
 
 {$IFNDEF MSWINDOWS}
-  procedure ZeroMemory(Destination: pointer; Length: dword);
-  function MakeLong(a, b: word): longint;
-  (*
-  #define LOBYTE(a) (BYTE)(a)
-  #define HIBYTE(a) (BYTE)((a)>>8)
-  #define LOWORD(a) (WORD)(a)
-  #define HIWORD(a) (WORD)((a)>>16)
-  #define MAKEWORD(a,b) (WORD)(((a)&0xff)|((b)<<8))
-  *)
+procedure ZeroMemory(Destination: pointer; Length: dword);
+function MakeLong(a, b: word): longint;
 {$ENDIF}
 
-function FileExistsInsensitive(var FileName: string): boolean;
-
-(*
- * Character classes
- *)
-
-function IsAlphaChar(ch: WideChar): boolean;
-function IsNumericChar(ch: WideChar): boolean;
-function IsAlphaNumericChar(ch: WideChar): boolean;
-function IsPunctuationChar(ch: WideChar): boolean;
-function IsControlChar(ch: WideChar): boolean;
-
 // A stable alternative to TList.Sort() (use TList.Sort() if applicable, see below)
 procedure MergeSort(List: TList; CompareFunc: TListSortCompare);
 
 function GetAlignedMem(Size: cardinal; Alignment: integer): pointer;
 procedure FreeAlignedMem(P: pointer);
 
+function GetArrayIndex(const SearchArray: array of UTF8String; Value: string; CaseInsensitiv: boolean = false): integer;
+
 
 implementation
 
@@ -101,8 +99,63 @@ uses
   {$IFDEF Delphi}
   Dialogs,
   {$ENDIF}
-  UMain;
+  sdl,
+  UFilesystem,
+  UMain,
+  UUnicodeUtils;
 
+function SplitString(const Str: string; MaxCount: integer; Separators: TSysCharSet): TStringDynArray;
+
+  {*
+   * Adds Str[StartPos..Endpos-1] to the result array.
+   *}
+  procedure AddSplit(StartPos, EndPos: integer);
+  begin
+    SetLength(Result, Length(Result)+1);
+    Result[High(Result)] := Copy(Str, StartPos, EndPos-StartPos);
+  end;
+
+var
+  I: integer;
+  Start: integer;
+  Last: integer;
+begin
+  Start := 0;
+  SetLength(Result, 0);
+
+  for I := 1 to Length(Str) do
+  begin
+    if (Str[I] in Separators) then
+    begin
+      // end of component found
+      if (Start > 0) then
+      begin
+        AddSplit(Start, I);
+        Start := 0;
+      end;
+    end
+    else if (Start = 0) then
+    begin
+      // mark beginning of component
+      Start := I;
+      // check if this is the last component
+      if (Length(Result) = MaxCount-1) then
+      begin
+        // find last non-separator char
+        Last := Length(Str);
+        while (Str[Last] in Separators) do
+          Dec(Last);
+        // add component up to last non-separator
+        AddSplit(Start, Last);
+        Exit;
+      end;
+    end;
+  end;
+
+  // last component
+  if (Start > 0) then
+    AddSplit(Start, Length(Str)+1);
+end;
 
 // data used by the ...Locale() functions
 {$IF Defined(Linux) or Defined(FreeBSD)}
@@ -224,39 +277,6 @@ begin
                     exOverflow, exUnderflow, exPrecision]);
 end;
 
-function StringReplaceW(text: WideString; search, rep: WideChar): WideString;
-var
-  iPos:  integer;
-//  sTemp: WideString;
-begin
-(*
-  result := text;
-  iPos   := Pos(search, result);
-  while (iPos > 0) do
-  begin
-    sTemp  := copy(result, iPos + length(search), length(result));
-    result := copy(result, 1, iPos - 1) + rep + sTEmp;
-    iPos   := Pos(search, result);
-  end;
-*)
-  result := text;
-
-  if search = rep then
-    exit;
-
-  for iPos := 1 to length(result) do
-  begin
-    if result[iPos] = search then
-      result[iPos] := rep;
-  end;
-end;
-
-function AdaptFilePaths(const aPath: WideString): WideString;
-begin
-  result := StringReplaceW(aPath, '\', PathDelim);//, [rfReplaceAll]);
-end;
-
-
 {$IFNDEF MSWINDOWS}
 procedure ZeroMemory(Destination: pointer; Length: dword);
 begin
@@ -268,135 +288,8 @@ begin
   Result := (LongInt(B) shl 16) + A;
 end;
 
-(*
-function QueryPerformanceCounter(lpPerformanceCount:TLARGEINTEGER): Bool;
-
-  // From http://en.wikipedia.org/wiki/RDTSC
-  function RDTSC: Int64; register;
-  asm
-    rdtsc
-  end;
-
-begin
-  // Use clock_gettime(CLOCK_REALTIME, ...) here (but not from the libc unit)
-  lpPerformanceCount := RDTSC();
-  result := true;
-end;
-
-function QueryPerformanceFrequency(lpFrequency:TLARGEINTEGER):Bool;
-begin
-  // clock_getres(CLOCK_REALTIME, ...)
-  lpFrequency := 0;
-  result := true;
-end;
-*)
 {$ENDIF}
 
-// Checks if a regular files or directory with the given name exists.
-// The comparison is case insensitive.
-function FileExistsInsensitive(var FileName: string): boolean;
-var
-  FilePath, LocalFileName: string;
-  SearchInfo: TSearchRec;
-begin
-{$IF Defined(Linux) or Defined(FreeBSD)}
-  // speed up standard case
-  if FileExists(FileName) then
-  begin
-    Result := true;
-    exit;
-  end;
-
-  Result := false;
-
-  FilePath := ExtractFilePath(FileName);
-  if (FindFirst(FilePath + '*', faAnyFile, SearchInfo) = 0) then
-  begin
-    LocalFileName := ExtractFileName(FileName);
-    repeat
-      if (AnsiSameText(LocalFileName, SearchInfo.Name)) then
-      begin
-        FileName := FilePath + SearchInfo.Name;
-        Result := true;
-        break;
-      end;
-    until (FindNext(SearchInfo) <> 0);
-  end;
-  FindClose(SearchInfo);
-{$ELSE}
-  // Windows and Mac OS X do not have case sensitive file systems
-  Result := FileExists(FileName);
-{$IFEND}
-end;
-
-// +++++++++++++++++++++ helpers for RWOpsFromStream() +++++++++++++++
-function SdlStreamSeek(context: PSDL_RWops; offset: integer; whence: integer): integer; cdecl;
-var
-  stream: TStream;
-  origin: word;
-begin
-  stream := TStream(context.unknown);
-  if (stream = nil) then
-    raise EInvalidContainer.Create('SDLStreamSeek on nil');
-  case whence of
-    0 : origin := soFromBeginning; //	Offset is from the beginning of the resource. Seek moves to the position Offset. Offset must be >= 0.
-    1 : origin := soFromCurrent; //	Offset is from the current position in the resource. Seek moves to Position + Offset.
-    2 : origin := soFromEnd;
-  else
-    origin := soFromBeginning; // just in case
-  end;
-  Result := stream.Seek(offset, origin);
-end;
-  
-function SdlStreamRead(context: PSDL_RWops; Ptr: pointer; size: integer; maxnum: integer): integer; cdecl;
-var
-  stream: TStream;
-begin
-  stream := TStream(context.unknown);
-  if (stream = nil) then
-    raise EInvalidContainer.Create('SDLStreamRead on nil');
-  try
-    Result := stream.read(Ptr^, Size * maxnum) div size;
-  except
-    Result := -1;
-  end;
-end;
-  
-function SDLStreamClose(context: PSDL_RWops): integer; cdecl;
-var
-  stream: TStream;
-begin
-  stream := TStream(context.unknown);
-  if (stream = nil) then
-    raise EInvalidContainer.Create('SDLStreamClose on nil');
-  stream.Free;
-  Result := 1;
-end;
-// -----------------------------------------------
-
-(*
- * Creates an SDL_RWops handle from a TStream.
- * The stream and RWops must be freed by the user after usage.
- * Use SDL_FreeRW(...) to free the RWops data-struct. 
- *)
-function RWopsFromStream(Stream: TStream): PSDL_RWops;
-begin
-  Result := SDL_AllocRW();
-  if (Result = nil) then
-    Exit;
-
-  // set RW-callbacks
-  with Result^ do
-  begin
-    unknown := TUnknown(Stream);
-    seek    := SDLStreamSeek;
-    read    := SDLStreamRead;
-    write   := nil;
-    close   := SDLStreamClose;
-    type_   := 2;
-  end;
-end;
-
 {$IFDEF FPC}
 function RandomRange(aMin: integer; aMax: integer): integer;
 begin
@@ -541,59 +434,6 @@ begin
 {$IFEND}
 end;
 
-function IsAlphaChar(ch: WideChar): boolean;
-begin
-  // TODO: add chars > 255 when unicode-fonts work?
-  case ch of
-    'A'..'Z',  // A-Z
-    'a'..'z',  // a-z
-    #170,#181,#186,
-    #192..#214,
-    #216..#246,
-    #248..#255:
-      Result := true;
-    else
-      Result := false;
-  end;
-end;
-
-function IsNumericChar(ch: WideChar): boolean;
-begin
-  case ch of
-    '0'..'9':
-      Result := true;
-    else
-      Result := false;
-  end;
-end;
-
-function IsAlphaNumericChar(ch: WideChar): boolean;
-begin
-  Result := (IsAlphaChar(ch) or IsNumericChar(ch));
-end;
-
-function IsPunctuationChar(ch: WideChar): boolean;
-begin
-  // TODO: add chars outside of Latin1 basic (0..127)?
-  case ch of
-    ' '..'/',':'..'@','['..'`','{'..'~':
-      Result := true;
-    else
-      Result := false;
-  end;
-end;
-
-function IsControlChar(ch: WideChar): boolean;
-begin
-  case ch of
-    #0..#31,
-    #127..#159:
-      Result := true;
-    else
-      Result := false;
-  end;
-end;
-
 (*
  * Recursive part of the MergeSort algorithm.
  * OutList will be either InList or TempList and will be swapped in each
@@ -677,6 +517,28 @@ begin
   TempList.Free;
 end;
 
+(**
+ * Returns the index of Value in SearchArray
+ * or -1 if Value is not in SearchArray.
+ *)
+function GetArrayIndex(const SearchArray: array of UTF8String; Value: string;
+    CaseInsensitiv: boolean = false): integer;
+var
+  i: integer;
+begin
+  Result := -1;
+
+  for i := 0 to High(SearchArray) do
+  begin
+    if (SearchArray[i] = Value) or
+       (CaseInsensitiv and (CompareText(SearchArray[i], Value) = 0)) then
+    begin
+      Result := i;
+      Break;
+    end;
+  end;
+end;
+
 
 type
   // stores the unaligned pointer of data allocated by GetAlignedMem()
diff --git a/cmake/src/base/UConfig.pas b/cmake/src/base/UConfig.pas
index dfb51d54..c0980de4 100644
--- a/cmake/src/base/UConfig.pas
+++ b/cmake/src/base/UConfig.pas
@@ -58,7 +58,7 @@ unit UConfig;
 //   not possible to use the version-numbers in this uses-clause.
 //   Example:
 //     interface
-//     uses 
+//     uses
 //       versions, // include this file
 //       {$IF USE_UNIT_XYZ}xyz;{$IFEND}      // Error: USE_UNIT_XYZ not defined
 //     const
@@ -68,13 +68,13 @@ unit UConfig;
 //
 //   Even if this file was an include-file no constants could be declared
 //   before the interface's uses clause.
-//   In FPC macros {$DEFINE VER:= 3} could be used to declare the version-numbers 
+//   In FPC macros {$DEFINE VER:= 3} could be used to declare the version-numbers
 //   but this is incompatible to Delphi. In addition macros do not allow expand
-//   arithmetic expressions. Although you can define 
+//   arithmetic expressions. Although you can define
 //     {$DEFINE FPC_VER:= FPC_VERSION*1000000+FPC_RELEASE*1000+FPC_PATCH}
 //   the following check would fail:
 //     {$IF FPC_VERSION_INT >= 002002000}
-//   would fail because FPC_VERSION_INT is interpreted as a string. 
+//   would fail because FPC_VERSION_INT is interpreted as a string.
 //
 // PLEASE consider this if you use version numbers in $IF compiler-
 // directives. Otherwise you might break portability.
@@ -88,9 +88,9 @@ interface
 {$ENDIF}
 
 {$I switches.inc}
-   
+
 uses
-  Sysutils;
+  SysUtils;
 
 const
   // IMPORTANT:
@@ -107,7 +107,7 @@ const
 
   // include config-file (defines + constants)
   {$IF Defined(MSWindows)}
-    {$I config-win.inc}
+    {$I ..\config-win.inc}
   {$ELSEIF Defined(Linux)}
     {$I config-linux.inc}
   {$ELSEIF Defined(FreeBSD)}
@@ -130,7 +130,7 @@ const
   USDX_VERSION_MAJOR   = 1;
   USDX_VERSION_MINOR   = 1;
   USDX_VERSION_RELEASE = 0;
-  USDX_VERSION_STATE   = 'Alpha';
+  USDX_VERSION_STATE   = 'Beta';
   USDX_STRING = 'UltraStar Deluxe';
 
   (*
@@ -151,11 +151,17 @@ const
   FPC_RELEASE = 0;
   FPC_PATCH   = 0;
   {$ENDIF}
-  
+
   FPC_VERSION_INT = (FPC_VERSION * VERSION_MAJOR) +
                     (FPC_RELEASE * VERSION_MINOR) +
                     (FPC_PATCH * VERSION_RELEASE);
 
+  // FPC 2.2.0 unicode support is very buggy. The cwstring unit for example
+  // always crashes whenever UTF8ToAnsi() is called on a non UTF8 encoded string
+  // what is fixed in 2.2.2.
+  {$IF Defined(FPC) and (FPC_VERSION_INT < 2002002)} // < 2.2.2
+    {$MESSAGE FATAL 'FPC >= 2.2.2 required!'}
+  {$IFEND}
 
   {$IFDEF HaveFFmpeg}
 
@@ -179,13 +185,13 @@ const
 
   {$ENDIF}
 
-  {$IFDEF HaveProjectM}  
+  {$IFDEF HaveProjectM}
   PROJECTM_VERSION = (PROJECTM_VERSION_MAJOR * VERSION_MAJOR) +
                      (PROJECTM_VERSION_MINOR * VERSION_MINOR) +
                      (PROJECTM_VERSION_RELEASE * VERSION_RELEASE);
   {$ENDIF}
 
-  {$IFDEF HavePortaudio}  
+  {$IFDEF HavePortaudio}
   PORTAUDIO_VERSION = (PORTAUDIO_VERSION_MAJOR * VERSION_MAJOR) +
                       (PORTAUDIO_VERSION_MINOR * VERSION_MINOR) +
                       (PORTAUDIO_VERSION_RELEASE * VERSION_RELEASE);
@@ -223,4 +229,4 @@ begin
     ' Build';
 end;
 
-end.
+end.
\ No newline at end of file
diff --git a/cmake/src/base/UCovers.pas b/cmake/src/base/UCovers.pas
index a1705674..6c7c9e48 100644
--- a/cmake/src/base/UCovers.pas
+++ b/cmake/src/base/UCovers.pas
@@ -50,7 +50,8 @@ uses
   SysUtils,
   Classes,
   UImage,
-  UTexture;
+  UTexture,
+  UPath;
 
 type
   ECoverDBException = class(Exception)
@@ -59,9 +60,9 @@ type
   TCover = class
     private
       ID: int64;
-      Filename: WideString;
+      Filename: IPath;
     public
-      constructor Create(ID: int64; Filename: WideString);
+      constructor Create(ID: int64; Filename: IPath);
       function GetPreviewTexture(): TTexture;
       function GetTexture(): TTexture;
   end;
@@ -76,19 +77,19 @@ type
     private
       DB: TSQLiteDatabase;
       procedure InitCoverDatabase();
-      function CreateThumbnail(const Filename: WideString; var Info: TThumbnailInfo): PSDL_Surface;
+      function CreateThumbnail(const Filename: IPath; var Info: TThumbnailInfo): PSDL_Surface;
       function LoadCover(CoverID: int64): TTexture;
       procedure DeleteCover(CoverID: int64);
-      function FindCoverIntern(const Filename: WideString): int64;
+      function FindCoverIntern(const Filename: IPath): int64;
       procedure Open();
       function GetVersion(): integer;
       procedure SetVersion(Version: integer);
     public
       constructor Create();
       destructor Destroy; override;
-      function AddCover(const Filename: WideString): TCover;
-      function FindCover(const Filename: WideString): TCover;
-      function CoverExists(const Filename: WideString): boolean;
+      function AddCover(const Filename: IPath): TCover;
+      function FindCover(const Filename: IPath): TCover;
+      function CoverExists(const Filename: IPath): boolean;
       function GetMaxCoverSize(): integer;
       procedure SetMaxCoverSize(Size: integer);
   end;
@@ -111,7 +112,7 @@ uses
   DateUtils;
 
 const
-  COVERDB_FILENAME = 'cover.db';
+  COVERDB_FILENAME: UTF8String = 'cover.db';
   COVERDB_VERSION = 01; // 0.1
   COVER_TBL = 'Cover';
   COVER_THUMBNAIL_TBL = 'CoverThumbnail';
@@ -141,7 +142,7 @@ end;
 
 { TCover }
 
-constructor TCover.Create(ID: int64; Filename: WideString);
+constructor TCover.Create(ID: int64; Filename: IPath);
 begin
   Self.ID := ID;
   Self.Filename := Filename;
@@ -210,11 +211,11 @@ end;
 procedure TCoverDatabase.Open();
 var
   Version: integer;
-  Filename: string;
+  Filename: IPath;
 begin
-  Filename := UTF8Encode(Platform.GetGameUserPath() + COVERDB_FILENAME);
+  Filename := Platform.GetGameUserPath().Append(COVERDB_FILENAME);
 
-  DB := TSQLiteDatabase.Create(Filename);
+  DB := TSQLiteDatabase.Create(Filename.ToUTF8());
   Version := GetVersion();
 
   // check version, if version is too old/new, delete database file
@@ -223,10 +224,10 @@ begin
     Log.LogInfo('Outdated cover-database file found', 'TCoverDatabase.Open');
     // close and delete outdated file
     DB.Free;
-    if (not DeleteFile(Filename)) then
-      raise ECoverDBException.Create('Could not delete ' + Filename);
+    if (not Filename.DeleteFile()) then
+      raise ECoverDBException.Create('Could not delete ' + Filename.ToNative);
     // reopen
-    DB := TSQLiteDatabase.Create(Filename);
+    DB := TSQLiteDatabase.Create(Filename.ToUTF8());
     Version := 0;
   end;
 
@@ -266,14 +267,14 @@ begin
              ')');
 end;
 
-function TCoverDatabase.FindCoverIntern(const Filename: WideString): int64;
+function TCoverDatabase.FindCoverIntern(const Filename: IPath): int64;
 begin
   Result := DB.GetTableValue('SELECT [ID] FROM ['+COVER_TBL+'] ' +
                              'WHERE [Filename] = ?',
-                             [UTF8Encode(Filename)]);
+                             [Filename.ToUTF8]);
 end;
 
-function TCoverDatabase.FindCover(const Filename: WideString): TCover;
+function TCoverDatabase.FindCover(const Filename: IPath): TCover;
 var
   CoverID: int64;
 begin
@@ -287,7 +288,7 @@ begin
   end;
 end;
 
-function TCoverDatabase.CoverExists(const Filename: WideString): boolean;
+function TCoverDatabase.CoverExists(const Filename: IPath): boolean;
 begin
   Result := false;
   try
@@ -297,7 +298,7 @@ begin
   end;
 end;
 
-function TCoverDatabase.AddCover(const Filename: WideString): TCover;
+function TCoverDatabase.AddCover(const Filename: IPath): TCover;
 var
   CoverID: int64;
   Thumbnail: PSDL_Surface;
@@ -329,7 +330,7 @@ begin
     DB.ExecSQL('INSERT INTO ['+COVER_TBL+'] ' +
                '([Filename], [Date], [Width], [Height]) VALUES' +
                '(?, ?, ?, ?)',
-               [UTF8Encode(Filename), DateTimeToUnixTime(FileDate),
+               [Filename.ToUTF8, DateTimeToUnixTime(FileDate),
                 Info.CoverWidth, Info.CoverHeight]);
 
     // get auto-generated cover ID
@@ -358,7 +359,7 @@ var
   PixelFmt: TImagePixelFmt;
   Data: PChar;
   DataSize: integer;
-  Filename: WideString;
+  Filename: IPath;
   Table: TSQLiteUniTable;
 begin
   Table := nil;
@@ -371,7 +372,7 @@ begin
         'USING(ID) ' +
       'WHERE [ID] = %d', [CoverID]));
 
-    Filename := UTF8Decode(Table.FieldAsString(0));
+    Filename := Path(Table.FieldAsString(0));
     PixelFmt := TImagePixelFmt(Table.FieldAsInteger(1));
     Width    := Table.FieldAsInteger(2);
     Height   := Table.FieldAsInteger(3);
@@ -384,6 +385,9 @@ begin
     end
     else
     begin
+      // FillChar() does not decrement the ref-count of ref-counted fields
+      // -> reset Name field manually
+      Result.Name := nil;
       FillChar(Result, SizeOf(TTexture), 0);
     end;
   except on E: Exception do
@@ -403,7 +407,7 @@ end;
  * Returns a pointer to an array of bytes containing the texture data in the
  * requested size
  *)
-function TCoverDatabase.CreateThumbnail(const Filename: WideString; var Info: TThumbnailInfo): PSDL_Surface;
+function TCoverDatabase.CreateThumbnail(const Filename: IPath; var Info: TThumbnailInfo): PSDL_Surface;
 var
   //TargetAspect, SourceAspect: double;
   //TargetWidth, TargetHeight: integer;
@@ -417,7 +421,7 @@ begin
   Thumbnail := LoadImage(Filename);
   if (not assigned(Thumbnail)) then
   begin
-    Log.LogError('Could not load cover: "'+ Filename +'"', 'TCoverDatabase.AddCover');
+    Log.LogError('Could not load cover: "'+ Filename.ToNative +'"', 'TCoverDatabase.AddCover');
     Exit;
   end;
 
diff --git a/cmake/src/base/UDLLManager.pas b/cmake/src/base/UDLLManager.pas
deleted file mode 100644
index 3faa15bf..00000000
--- a/cmake/src/base/UDLLManager.pas
+++ /dev/null
@@ -1,292 +0,0 @@
-{* UltraStar Deluxe - Karaoke Game
- *
- * UltraStar Deluxe is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * 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; either version 2
- * of the License, or (at your option) any later version.
- *
- * 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; see the file COPYING. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- *
- * $URL$
- * $Id$
- *}
-
-unit UDLLManager;
-
-interface
-
-{$IFDEF FPC}
-  {$MODE Delphi}
-{$ENDIF}
-
-{$I switches.inc}
-
-uses
-  ModiSDK,
-  UFiles;
-
-type
-  TDLLMan = class
-    private
-      hLib:     THandle;
-      P_Init:   fModi_Init;
-      P_Draw:   fModi_Draw;
-      P_Finish: fModi_Finish;
-      P_RData:  pModi_RData;
-    public
-      Plugins: array of TPluginInfo;
-      PluginPaths: array of string;
-      Selected: ^TPluginInfo;
-
-      constructor Create;
-
-      procedure GetPluginList;
-      procedure ClearPluginInfo(No: cardinal);
-      function  LoadPluginInfo(Filename: string; No: cardinal): boolean;
-
-      function  LoadPlugin(No: cardinal): boolean;
-      procedure UnLoadPlugin;
-
-      function  PluginInit   (const TeamInfo:   TTeamInfo; 
-                              var   Playerinfo: TPlayerinfo;
-			      const Sentences:  TSentences;
-			      const LoadTex:    fModi_LoadTex;
-			      const Print:      fModi_Print;
-			            LoadSound:  fModi_LoadSound;
-				    PlaySound:  pModi_PlaySound)
-			     : boolean;
-      function  PluginDraw   (var Playerinfo: TPlayerinfo; const CurSentence: cardinal): boolean;
-      function  PluginFinish (var Playerinfo: TPlayerinfo): byte;
-      procedure PluginRData  (handle: HSTREAM; buffer: Pointer; len: dword; user: dword);
-  end;
-
-var
-  DLLMan: TDLLMan;
-
-const
-{$IF Defined(MSWINDOWS)}
-  DLLExt  = '.dll';
-{$ELSEIF Defined(DARWIN)}
-  DLLExt  = '.dylib';
-{$ELSEIF Defined(UNIX)}
-  DLLExt  = '.so';
-{$IFEND}
-
-implementation
-
-uses
-  {$IFDEF MSWINDOWS}
-  windows,
-  {$ELSE}
-  dynlibs,
-  {$ENDIF}
-  UPath,
-  ULog,
-  SysUtils;
-
-
-constructor TDLLMan.Create;
-begin
-  inherited;
-  SetLength(Plugins, 0);
-  SetLength(PluginPaths, Length(Plugins));
-  GetPluginList;
-end;
-
-procedure TDLLMan.GetPluginList;
-var
-  SearchRecord: TSearchRec;
-begin
-
-  if FindFirst(PluginPath + '*' + DLLExt, faAnyFile, SearchRecord) = 0 then
-  begin
-    repeat
-      SetLength(Plugins, Length(Plugins)+1);
-      SetLength(PluginPaths, Length(Plugins));
-
-      if LoadPluginInfo(SearchRecord.Name, High(Plugins)) then // loaded succesful
-      begin
-        PluginPaths[High(PluginPaths)] := SearchRecord.Name;
-      end
-      else // error loading
-      begin
-        SetLength(Plugins, Length(Plugins)-1);
-        SetLength(PluginPaths, Length(Plugins));
-      end;
-
-    until FindNext(SearchRecord) <> 0;
-    FindClose(SearchRecord);
-  end;
-end;
-
-procedure TDLLMan.ClearPluginInfo(No: cardinal);
-begin
-// set to party modi plugin
-  Plugins[No].Typ := 8;
-
-  Plugins[No].Name := 'unknown';
-  Plugins[No].NumPlayers := 0;
-
-  Plugins[No].Creator := 'Nobody';
-  Plugins[No].PluginDesc := 'NO_PLUGIN_DESC';
-
-  Plugins[No].LoadSong  := true;
-  Plugins[No].ShowScore := true;
-  Plugins[No].ShowBars  := true;
-  Plugins[No].ShowNotes := true;
-  Plugins[No].LoadVideo := true;
-  Plugins[No].LoadBack  := true;
-
-  Plugins[No].TeamModeOnly := true;
-  Plugins[No].GetSoundData := true;
-  Plugins[No].Dummy := true;
-
-
-  Plugins[No].BGShowFull   := true;
-  Plugins[No].BGShowFull_O := true;
-
-  Plugins[No].ShowRateBar   := true;
-  Plugins[No].ShowRateBar_O := true;
-
-  Plugins[No].EnLineBonus   := true;
-  Plugins[No].EnLineBonus_O := true;
-end;
-
-function TDLLMan.LoadPluginInfo(Filename: string; No: cardinal): boolean;
-var
-  hLibg: THandle;
-  Info: pModi_PluginInfo;
-//  I: integer;
-begin
-  Result := true;
-// clear plugin info
-  ClearPluginInfo(No);
-
-{
-// workaround plugins loaded 2 times
-  for i := low(pluginpaths) to high(pluginpaths) do
-    if (pluginpaths[i] = filename) then
-      exit;
-}
-
-// load libary
-  hLibg := LoadLibrary(PChar(PluginPath + Filename));
-// if loaded
-  if (hLibg <> 0) then
-  begin
-// load info procedure
-    @Info := GetProcAddress(hLibg, PChar('PluginInfo'));
-
-// if loaded
-    if (@Info <> nil) then
-    begin
-// load plugininfo
-      Info(Plugins[No]);
-      Result := true;
-    end
-    else
-      Log.LogError('Could not load plugin "' + Filename + '": Info procedure not found');
-
-    FreeLibrary (hLibg);
-  end
-  else
-    Log.LogError('Could not load plugin "' + Filename + '": Libary not loaded');
-end;
-
-function TDLLMan.LoadPlugin(No: cardinal): boolean;
-begin
-  Result := true;
-// load libary
-  hLib := LoadLibrary(PChar(PluginPath + PluginPaths[No]));
-// if loaded
-  if (hLib <> 0) then
-  begin
-// load info procedure
-    @P_Init := GetProcAddress (hLib, 'Init');
-    @P_Draw := GetProcAddress (hLib, 'Draw');
-    @P_Finish := GetProcAddress (hLib, 'Finish');
-
-// if loaded
-    if (@P_Init <> nil) and (@P_Draw <> nil) and (@P_Finish <> nil) then
-    begin
-      Selected := @Plugins[No]; 
-      Result := true;
-    end
-    else
-    begin
-      Log.LogError('Could not load plugin "' + PluginPaths[No] + '": Procedures not found');
-    end;
-  end
-  else
-    Log.LogError('Could not load plugin "' + PluginPaths[No] + '": Libary not loaded');
-end;
-
-procedure TDLLMan.UnLoadPlugin;
-begin
-  if (hLib <> 0) then
-    FreeLibrary (hLib);
-
-//  Selected := nil;
-  @P_Init   := nil;
-  @P_Draw   := nil;
-  @P_Finish := nil;
-  @P_RData  := nil;
-end;
-
-function TDLLMan.PluginInit (const TeamInfo:   TTeamInfo;
-                             var   Playerinfo: TPlayerinfo;
-			     const Sentences:  TSentences;
-			     const LoadTex:    fModi_LoadTex;
-			     const Print:      fModi_Print;
-			           LoadSound:  fModi_LoadSound;
-			           PlaySound:  pModi_PlaySound)
-			    : boolean;
-var
-  Methods: TMethodRec;
-begin
-  Methods.LoadTex := LoadTex;
-  Methods.Print := Print;
-  Methods.LoadSound := LoadSound;
-  Methods.PlaySound := PlaySound;
-  
-  if (@P_Init <> nil) then
-    Result := P_Init (TeamInfo, PlayerInfo, Sentences, Methods)
-  else
-    Result := true
-end;
-
-function TDLLMan.PluginDraw   (var Playerinfo: TPlayerinfo; const CurSentence: cardinal): boolean;
-begin
-  if (@P_Draw <> nil) then
-    Result := P_Draw (PlayerInfo, CurSentence)
-  else
-    Result := true
-end;
-
-function TDLLMan.PluginFinish (var Playerinfo: TPlayerinfo): byte;
-begin
-  if (@P_Finish <> nil) then
-    Result := P_Finish (PlayerInfo)
-  else
-    Result := 0;
-end;
-
-procedure TDLLMan.PluginRData  (handle: HStream; buffer: Pointer; len: dword; user: dword);
-begin
-if (@P_RData <> nil) then
-  P_RData (handle, buffer, len, user);
-end;
-
-end.
diff --git a/cmake/src/base/UDataBase.pas b/cmake/src/base/UDataBase.pas
index 0f9d88a7..cccedc69 100644
--- a/cmake/src/base/UDataBase.pas
+++ b/cmake/src/base/UDataBase.pas
@@ -34,20 +34,22 @@ interface
 {$I switches.inc}
 
 uses
-  USongs,
-  USong,
   Classes,
-  SQLiteTable3;
+  SQLiteTable3,
+  UPath,
+  USong,
+  USongs,
+  UTextEncoding;
 
 //--------------------
-//DataBaseSystem - Class including all DB Methods
+//DataBaseSystem - Class including all DB methods
 //--------------------
 type
   TStatType = (
-    stBestScores,   // Best Scores
-    stBestSingers,  // Best Singers
-    stMostSungSong, // Most sung Songs
-    stMostPopBand   // Most popular Band
+    stBestScores,   // Best scores
+    stBestSingers,  // Best singers
+    stMostSungSong, // Most sung songs
+    stMostPopBand   // Most popular band
   );
 
   // abstract super-class for statistic results
@@ -58,54 +60,57 @@ type
 
   TStatResultBestScores = class(TStatResult)
     public
-      Singer:       WideString;
-      Score:        Word;
-      Difficulty:   Byte;
-      SongArtist:   WideString;
-      SongTitle:    WideString;
+      Singer:       UTF8String;
+      Score:        word;
+      Difficulty:   byte;
+      SongArtist:   UTF8String;
+      SongTitle:    UTF8String;
+      Date:         UTF8String;
   end;
 
   TStatResultBestSingers = class(TStatResult)
     public
-      Player:       WideString;
-      AverageScore: Word;
+      Player:       UTF8String;
+      AverageScore: word;
   end;
 
   TStatResultMostSungSong = class(TStatResult)
     public
-      Artist:       WideString;
-      Title:        WideString;
-      TimesSung:    Word;
+      Artist:       UTF8String;
+      Title:        UTF8String;
+      TimesSung:    word;
   end;
 
   TStatResultMostPopBand = class(TStatResult)
     public
-      ArtistName:   WideString;
-      TimesSungTot: Word;
+      ArtistName:   UTF8String;
+      TimesSungTot: word;
   end;
 
-  
+
   TDataBaseSystem = class
     private
-      ScoreDB: TSQLiteDatabase;
-      fFilename: string;
+      ScoreDB:      TSQLiteDatabase;
+      fFilename: IPath;
 
       function GetVersion(): integer;
       procedure SetVersion(Version: integer);
     public
-      property Filename: string read fFilename;
+      property Filename: IPath read fFilename;
 
       destructor Destroy; override;
 
-      procedure Init(const Filename: string);
+      procedure Init(const Filename: IPath);
+      procedure ConvertFrom101To110();
       procedure ReadScore(Song: TSong);
-      procedure AddScore(Song: TSong; Level: integer; const Name: WideString; Score: integer);
+      procedure AddScore(Song: TSong; Level: integer; const Name: UTF8String; Score: integer);
       procedure WriteScore(Song: TSong);
 
-      function GetStats(Typ: TStatType; Count: Byte; Page: Cardinal; Reversed: Boolean): TList;
+      function GetStats(Typ: TStatType; Count: byte; Page: cardinal; Reversed: boolean): TList;
       procedure FreeStats(StatList: TList);
-      function GetTotalEntrys(Typ: TStatType): Cardinal;
+      function GetTotalEntrys(Typ: TStatType): cardinal;
       function GetStatReset: TDateTime;
+      function FormatDate(time_stamp: integer): UTF8String;
   end;
 
 var
@@ -114,53 +119,72 @@ var
 implementation
 
 uses
-  ULog,
   DateUtils,
+  ULanguage,
   StrUtils,
-  SysUtils;
-
+  SysUtils,
+  ULog;
+
+{
+ cDBVersion - history
+ 0 = USDX 1.01 or no Database
+ 01 = USDX 1.1
+}
 const
   cDBVersion = 01; // 0.1
   cUS_Scores = 'us_scores';
   cUS_Songs  = 'us_songs';
-  cUS_Statistics_Info  = 'us_statistics_info';
+  cUS_Statistics_Info = 'us_statistics_info';
 
 (**
- * Opens Database and Create Tables if not Exist
+ * Open database and create tables if they do not exist
  *)
-procedure TDataBaseSystem.Init(const Filename: string);
+procedure TDataBaseSystem.Init(const Filename: IPath);
 var
-  Version: integer;
+  Version:            integer;
+  finalizeConversion: boolean;
 begin
   if Assigned(ScoreDB) then
     Exit;
 
-  Log.LogStatus('Initializing database: "'+Filename+'"', 'TDataBaseSystem.Init');
+  Log.LogStatus('Initializing database: "' + Filename.ToNative + '"', 'TDataBaseSystem.Init');
 
   try
-  
-    // Open Database
-    ScoreDB   := TSQLiteDatabase.Create(Filename);
+
+    // open database
+    ScoreDB   := TSQLiteDatabase.Create(Filename.ToUTF8);
     fFilename := Filename;
 
-    // Close and delete outdated file
     Version := GetVersion();
-    if ((Version <> 0) and (Version <> cDBVersion)) then
+
+    // add Table cUS_Statistics_Info
+    // needed in the conversion from 1.01 to 1.1
+    if not ScoreDB.TableExists(cUS_Statistics_Info) then
     begin
-      Log.LogInfo('Outdated cover-database file found', 'TDataBaseSystem.Init');
-      // Close and delete outdated file
-      ScoreDB.Free;
-      if (not DeleteFile(Filename)) then
-        raise Exception.Create('Could not delete ' + Filename);
-      // Reopen
-      ScoreDB := TSQLiteDatabase.Create(Filename);
-      Version := 0;
+      Log.LogInfo('Outdated song database found - missing table"' + cUS_Statistics_Info + '"', 'TDataBaseSystem.Init');
+      ScoreDB.ExecSQL('CREATE TABLE IF NOT EXISTS [' + cUS_Statistics_Info + '] (' +
+                      '[ResetTime] INTEGER' +
+                      ');');
+      // insert creation timestamp
+      ScoreDB.ExecSQL(Format('INSERT INTO [' + cUS_Statistics_Info + '] ' +
+                             '([ResetTime]) VALUES(%d);',
+                             [DateTimeToUnix(Now())]));
     end;
-    
+
+    // convert data from 1.01 to 1.1
+    // part #1 - prearrangement
+    finalizeConversion := false;
+    if (Version = 0) AND ScoreDB.TableExists('US_Scores') then
+    begin
+      // rename old tables - to be able to insert new table structures
+      ScoreDB.ExecSQL('ALTER TABLE US_Scores RENAME TO us_scores_101;');
+      ScoreDB.ExecSQL('ALTER TABLE US_Songs RENAME TO us_songs_101;');
+      finalizeConversion := true; // means: conversion has to be done!
+    end;
+
     // Set version number after creation
     if (Version = 0) then
       SetVersion(cDBVersion);
-    
 
     // SQLite does not handle VARCHAR(n) or INT(n) as expected.
     // Texts do not have a restricted length, no matter which type is used,
@@ -169,30 +193,44 @@ begin
     // types are used (especially FieldAsInteger). Also take care to write the
     // types in upper-case letters although SQLite does not care about this -
     // SQLiteTable3 is very sensitive in this regard.
-     
-    ScoreDB.ExecSQL('CREATE TABLE IF NOT EXISTS ['+cUS_Scores+'] (' +
+    ScoreDB.ExecSQL('CREATE TABLE IF NOT EXISTS [' + cUS_Scores + '] (' +
                       '[SongID] INTEGER NOT NULL, ' +
                       '[Difficulty] INTEGER NOT NULL, ' +
                       '[Player] TEXT NOT NULL, ' +
-                      '[Score] INTEGER NOT NULL' +
+                      '[Score] INTEGER NOT NULL, ' +
+                      '[Date] INTEGER NULL' +
                     ');');
 
-    ScoreDB.ExecSQL('CREATE TABLE IF NOT EXISTS ['+cUS_Songs+'] (' +
+    ScoreDB.ExecSQL('CREATE TABLE IF NOT EXISTS [' + cUS_Songs + '] (' +
                       '[ID] INTEGER PRIMARY KEY, ' +
                       '[Artist] TEXT NOT NULL, ' +
                       '[Title] TEXT NOT NULL, ' +
-                      '[TimesPlayed] INTEGER NOT NULL' +
+                      '[TimesPlayed] INTEGER NOT NULL, ' +
+                      '[Rating] INTEGER NULL' +
                     ');');
 
-    if not ScoreDB.TableExists(cUS_Statistics_Info) then
+    //add column date to cUS-Scores
+    if not ScoreDB.ContainsColumn(cUS_Scores, 'Date') then
     begin
-      ScoreDB.ExecSQL('CREATE TABLE IF NOT EXISTS ['+cUS_Statistics_Info+'] (' +
-                        '[ResetTime] INTEGER' +
-                      ');');
-      // insert creation timestamp
-      ScoreDB.ExecSQL(Format('INSERT INTO ['+cUS_Statistics_Info+'] ' +
-                            '([ResetTime]) VALUES(%d);',
-                            [DateTimeToUnix(Now())]));
+      Log.LogInfo('adding column date to "' + cUS_Scores + '"', 'TDataBaseSystem.Init');
+      ScoreDB.ExecSQL('ALTER TABLE ' + cUS_Scores + ' ADD COLUMN [Date] INTEGER NULL');
+    end;
+
+    // add column rating to cUS_Songs
+    // just for users of nightly builds and developers!
+    if not ScoreDB.ContainsColumn(cUS_Songs, 'Rating') then
+    begin
+      Log.LogInfo('Outdated song database found - adding column rating to "' + cUS_Songs + '"', 'TDataBaseSystem.Init');
+      ScoreDB.ExecSQL('ALTER TABLE ' + cUS_Songs + ' ADD COLUMN [Rating] INTEGER NULL');
+    end;
+
+    // convert data from previous versions
+    // part #2 - accomplishment
+    if finalizeConversion then
+    begin
+      //convert data from 1.01 to 1.1
+      if ScoreDB.TableExists('us_scores_101') then
+        ConvertFrom101To110();
     end;
 
   except
@@ -205,6 +243,115 @@ begin
 
 end;
 
+(**
+ * Convert Database from 1.01 to 1.1
+ *)
+procedure TDataBaseSystem.ConvertFrom101To110();
+var
+  TableData:      TSQLiteUniTable;
+  tempUTF8String: UTF8String;
+begin
+  if not ScoreDB.ContainsColumn('us_scores_101', 'Date') then
+  begin
+    Log.LogInfo(
+      'Outdated song database found - ' +
+      'begin conversion from V1.01 to V1.1', 'TDataBaseSystem.Convert101To110');
+
+    // insert old values into new db-schemes (/tables)
+    ScoreDB.ExecSQL(
+      'INSERT INTO ' + cUS_Scores +
+      ' SELECT  SongID, Difficulty, Player, Score FROM us_scores_101;');
+  end else
+  begin
+    Log.LogInfo(
+      'Outdated song database  found - ' +
+      'begin conversion from V1.01 Challenge Mod to V1.1', 'TDataBaseSystem.Convert101To110');
+
+    // insert old values into new db-schemes (/tables)
+    ScoreDB.ExecSQL(
+      'INSERT INTO ' + cUS_Scores +
+      ' SELECT  SongID, Difficulty, Player, Score, Date FROM us_scores_101;');
+  end;
+
+    ScoreDB.ExecSQL(
+      'INSERT INTO ' + cUS_Songs +
+      ' SELECT  ID, Artist, Title, TimesPlayed, ''NULL'' FROM us_songs_101;');
+
+    // now we have to convert all the texts for unicode support:
+
+    // player names
+    TableData := nil;
+    try
+      TableData := ScoreDB.GetUniTable(
+        'SELECT [rowid], [Player] ' +
+        'FROM [' + cUS_Scores + '];');
+
+      // Go through all Entrys
+      while (not TableData.EOF) do
+      begin
+        // Convert name into UTF8 and alter all entrys
+        DecodeStringUTF8(TableData.FieldByName['Player'], tempUTF8String, encCP1252);
+        ScoreDB.ExecSQL(
+          'UPDATE [' + cUS_Scores + '] ' +
+          'SET [Player] = ? ' +
+          'WHERE [rowid] = ? ',
+          [tempUTF8String,
+          TableData.FieldAsInteger(TableData.FieldIndex['rowid'])]);
+
+        TableData.Next;
+      end; // while
+
+    except
+      on E: Exception do
+        Log.LogError(E.Message, 'TDataBaseSystem.Convert101To110');
+    end;
+
+    TableData.Free;
+
+    // song artist and song title
+    TableData := nil;
+    try
+      TableData := ScoreDB.GetUniTable(
+        'SELECT [ID], [Artist], [Title] ' +
+        'FROM [' + cUS_Songs + '];');
+
+      // Go through all Entrys
+      while (not TableData.EOF) do
+      begin
+        // Convert Artist into UTF8 and alter all entrys
+        DecodeStringUTF8(TableData.FieldByName['Artist'], tempUTF8String, encCP1252);
+        //Log.LogError(TableData.FieldByName['Artist']+' -> '+tempUTF8String+' (encCP1252)');
+        ScoreDB.ExecSQL(
+          'UPDATE [' + cUS_Songs + '] ' +
+          'SET [Artist] = ? ' +
+          'WHERE [ID] = ?',
+          [tempUTF8String,
+          TableData.FieldAsInteger(TableData.FieldIndex['ID'])]);
+
+        // Convert Title into UTF8 and alter all entrys
+        DecodeStringUTF8(TableData.FieldByName['Title'], tempUTF8String, encCP1252);
+        ScoreDB.ExecSQL(
+          'UPDATE [' + cUS_Songs + '] ' +
+          'SET [Title] = ? ' +
+          'WHERE [ID] = ? ',
+          [tempUTF8String,
+          TableData.FieldAsInteger(TableData.FieldIndex['ID'])]);
+
+        TableData.Next;
+      end; // while
+
+    except
+      on E: Exception do
+        Log.LogError(E.Message, 'TDataBaseSystem.Convert101To110');
+    end;
+
+    TableData.Free;
+
+    //now drop old tables
+    ScoreDB.ExecSQL('DROP TABLE us_scores_101;');
+    ScoreDB.ExecSQL('DROP TABLE us_songs_101;');
+end;
+
 (**
  * Frees Database
  *)
@@ -215,34 +362,56 @@ begin
   inherited;
 end;
 
+(**
+ * Format a UNIX-Timestamp into DATE (If 0 then '')
+ *)
+function TDataBaseSystem.FormatDate(time_stamp: integer): UTF8String;
+var
+  Year, Month, Day: word;
+begin
+  Result:='';
+  try
+    if time_stamp<>0 then
+    begin
+      DecodeDate(UnixToDateTime(time_stamp), Year, Month, Day);
+      Result := Format(Language.Translate('STAT_FORMAT_DATE'), [Day, Month, Year]);
+    end;
+  except
+    on E: EConvertError do
+    Log.LogError('Error Parsing FormatString "STAT_FORMAT_DATE": ' + E.Message);
+  end;
+end;
+
+
 (**
  * Read Scores into SongArray
  *)
 procedure TDataBaseSystem.ReadScore(Song: TSong);
 var
-  TableData: TSQLiteUniTable;
-  Difficulty: Integer;
+  TableData:  TSQLiteUniTable;
+  Difficulty: integer;
+  I: integer;
+  PlayerListed: boolean;
 begin
   if not Assigned(ScoreDB) then
     Exit;
 
   TableData := nil;
-
   try
     // Search Song in DB
     TableData := ScoreDB.GetUniTable(
-      'SELECT [Difficulty], [Player], [Score] FROM ['+cUS_Scores+'] ' +
+      'SELECT [Difficulty], [Player], [Score], [Date] FROM [' + cUS_Scores + '] ' +
       'WHERE [SongID] = (' +
-        'SELECT [ID] FROM ['+cUS_Songs+'] ' +
+        'SELECT [ID] FROM [' + cUS_Songs + '] ' +
         'WHERE [Artist] = ? AND [Title] = ? ' +
         'LIMIT 1) ' +
-      'ORDER BY [Score] DESC  LIMIT 15',
-      [UTF8Encode(Song.Artist), UTF8Encode(Song.Title)]);
+      'ORDER BY [Score] DESC;', //no LIMIT! see filter below!
+      [Song.Artist, Song.Title]);
 
     // Empty Old Scores
-    SetLength(Song.Score[0], 0);
-    SetLength(Song.Score[1], 0);
-    SetLength(Song.Score[2], 0);
+    SetLength(Song.Score[0], 0); //easy
+    SetLength(Song.Score[1], 0); //medium
+    SetLength(Song.Score[2], 0); //hard
 
     // Go through all Entrys
     while (not TableData.EOF) do
@@ -252,12 +421,31 @@ begin
       if ((Difficulty >= 0) and (Difficulty <= 2)) and
          (Length(Song.Score[Difficulty]) < 5) then
       begin
-        SetLength(Song.Score[Difficulty], Length(Song.Score[Difficulty]) + 1);
+        //filter player
+        PlayerListed:=false;
+        if (Length(Song.Score[Difficulty])>0) then
+        begin
+          for I := 0 to Length(Song.Score[Difficulty]) - 1 do
+          begin
+            if (Song.Score[Difficulty, I].Name = TableData.FieldByName['Player']) then
+            begin
+              PlayerListed:=true;
+              break;
+            end;
+          end;
+        end;
+
+        if not PlayerListed then
+        begin
+          SetLength(Song.Score[Difficulty], Length(Song.Score[Difficulty]) + 1);
 
-        Song.Score[Difficulty, High(Song.Score[Difficulty])].Name  :=
-            UTF8Decode(TableData.FieldByName['Player']);
-        Song.Score[Difficulty, High(Song.Score[Difficulty])].Score :=
+          Song.Score[Difficulty, High(Song.Score[Difficulty])].Name  :=
+            TableData.FieldByName['Player'];
+          Song.Score[Difficulty, High(Song.Score[Difficulty])].Score :=
             TableData.FieldAsInteger(TableData.FieldIndex['Score']);
+          Song.Score[Difficulty, High(Song.Score[Difficulty])].Date :=
+            FormatDate(TableData.FieldAsInteger(TableData.FieldIndex['Date']));
+        end;
       end;
 
       TableData.Next;
@@ -277,70 +465,43 @@ end;
 (**
  * Adds one new score to DB
  *)
-procedure TDataBaseSystem.AddScore(Song: TSong; Level: integer; const Name: WideString; Score: integer);
+procedure TDataBaseSystem.AddScore(Song: TSong; Level: integer; const Name: UTF8String; Score: integer);
 var
-  ID: Integer;
+  ID:        integer;
   TableData: TSQLiteTable;
 begin
   if not Assigned(ScoreDB) then
     Exit;
 
-  // Prevent 0 Scores from being added
-  if (Score <= 0) then
-    Exit;
+  // Prevent 0 Scores from being added EDIT: ==> UScreenTop5.pas!
+  //if (Score <= 0) then
+  //  Exit;
 
   TableData := nil;
 
   try
 
     ID := ScoreDB.GetTableValue(
-        'SELECT [ID] FROM ['+cUS_Songs+'] ' +
+        'SELECT [ID] FROM [' + cUS_Songs + '] ' +
         'WHERE [Artist] = ? AND [Title] = ?',
-        [UTF8Encode(Song.Artist), UTF8Encode(Song.Title)]);
+        [Song.Artist, Song.Title]);
     if (ID = 0) then
     begin
       // Create song if it does not exist
       ScoreDB.ExecSQL(
-          'INSERT INTO ['+cUS_Songs+'] ' +
+          'INSERT INTO [' + cUS_Songs + '] ' +
           '([ID], [Artist], [Title], [TimesPlayed]) VALUES ' +
           '(NULL, ?, ?, 0);',
-          [UTF8Encode(Song.Artist), UTF8Encode(Song.Title)]);
+          [Song.Artist, Song.Title]);
       // Get song-ID
       ID := ScoreDB.GetLastInsertRowID();
     end;
     // Create new entry
     ScoreDB.ExecSQL(
-        'INSERT INTO ['+cUS_Scores+'] ' +
-        '([SongID] ,[Difficulty], [Player], [Score]) VALUES ' +
-        '(?, ?, ?, ?);',
-        [ID, Level, UTF8Encode(Name), Score]);
-
-    // Delete last position when there are more than 5 entrys.
-    // Fixes crash when there are > 5 ScoreEntrys
-    // Note: GetUniTable is not applicable here, as the results are used while
-    // table entries are deleted.
-    TableData := ScoreDB.GetTable(
-        'SELECT [Player], [Score] FROM ['+cUS_Scores+'] ' +
-        'WHERE [SongID] = ' + InttoStr(ID) + ' AND ' +
-              '[Difficulty] = ' + InttoStr(Level) +' ' +
-        'ORDER BY [Score] DESC LIMIT -1 OFFSET 5');
-
-    while (not TableData.EOF) do
-    begin
-      // Note: Score is an int-value, so in contrast to Player, we do not bind
-      // this value. Otherwise we had to convert the string to an int to avoid
-      // an automatic cast of this field to the TEXT type (although it might even
-      // work that way).
-      ScoreDB.ExecSQL(
-          'DELETE FROM ['+cUS_Scores+'] ' +
-          'WHERE [SongID] = ' + InttoStr(ID) + ' AND ' +
-                '[Difficulty] = ' + InttoStr(Level) +' AND ' +
-                '[Player] = ? AND ' +
-                '[Score] = ' + TableData.FieldByName['Score'],
-          [TableData.FieldByName['Player']]);
-
-      TableData.Next;
-    end;
+      'INSERT INTO [' + cUS_Scores + '] ' +
+      '([SongID] ,[Difficulty], [Player], [Score], [Date]) VALUES ' +
+      '(?, ?, ?, ?, ?);',
+      [ID, Level, Name, Score, DateTimeToUnix(Now())]);
 
   except on E: Exception do
     Log.LogError(E.Message, 'TDataBaseSystem.AddScore');
@@ -350,21 +511,21 @@ begin
 end;
 
 (**
- * Not needed with new System.
- * Used for increment played count
+ * Not needed with new system.
+ * Used to increment played count
  *)
 procedure TDataBaseSystem.WriteScore(Song: TSong);
 begin
   if not Assigned(ScoreDB) then
     Exit;
-    
+
   try
     // Increase TimesPlayed
     ScoreDB.ExecSQL(
-        'UPDATE ['+cUS_Songs+'] ' +
+        'UPDATE [' + cUS_Songs + '] ' +
         'SET [TimesPlayed] = [TimesPlayed] + 1 ' +
         'WHERE [Title] = ? AND [Artist] = ?;',
-        [UTF8Encode(Song.Title), UTF8Encode(Song.Artist)]);
+        [Song.Title, Song.Artist]);
   except on E: Exception do
     Log.LogError(E.Message, 'TDataBaseSystem.WriteScore');
   end;
@@ -376,11 +537,11 @@ end;
  * entries.
  * Free the result-list with FreeStats() after usage to avoid memory leaks.
  *)
-function TDataBaseSystem.GetStats(Typ: TStatType; Count: Byte; Page: Cardinal; Reversed: Boolean): TList;
+function TDataBaseSystem.GetStats(Typ: TStatType; Count: byte; Page: cardinal; Reversed: boolean): TList;
 var
-  Query: String;
+  Query:     string;
   TableData: TSQLiteUniTable;
-  Stat: TStatResult;
+  Stat:      TStatResult;
 begin
   Result := nil;
 
@@ -392,19 +553,19 @@ begin
   // Create query
   case Typ of
     stBestScores: begin
-      Query := 'SELECT [Player], [Difficulty], [Score], [Artist], [Title] FROM ['+cUS_Scores+'] ' +
-               'INNER JOIN ['+cUS_Songs+'] ON ([SongID] = [ID]) ORDER BY [Score]';
+      Query := 'SELECT [Player], [Difficulty], [Score], [Artist], [Title], [Date] FROM [' + cUS_Scores + '] ' +
+               'INNER JOIN [' + cUS_Songs + '] ON ([SongID] = [ID]) ORDER BY [Score]';
     end;
     stBestSingers: begin
-      Query := 'SELECT [Player], ROUND(AVG([Score])) FROM ['+cUS_Scores+'] ' +
+      Query := 'SELECT [Player], ROUND(AVG([Score])) FROM [' + cUS_Scores + '] ' +
                'GROUP BY [Player] ORDER BY AVG([Score])';
     end;
     stMostSungSong: begin
-      Query := 'SELECT [Artist], [Title], [TimesPlayed] FROM ['+cUS_Songs+'] ' +
+      Query := 'SELECT [Artist], [Title], [TimesPlayed] FROM [' + cUS_Songs + '] ' +
                'ORDER BY [TimesPlayed]';
     end;
     stMostPopBand: begin
-      Query := 'SELECT [Artist], SUM([TimesPlayed]) FROM ['+cUS_Songs+'] ' +
+      Query := 'SELECT [Artist], SUM([TimesPlayed]) FROM [' + cUS_Songs + '] ' +
                'GROUP BY [Artist] ORDER BY SUM([TimesPlayed])';
     end;
   end;
@@ -437,18 +598,19 @@ begin
         Stat := TStatResultBestScores.Create;
         with TStatResultBestScores(Stat) do
         begin
-          Singer := UTF8Decode(TableData.Fields[0]);
+          Singer := TableData.Fields[0];
           Difficulty := TableData.FieldAsInteger(1);
           Score := TableData.FieldAsInteger(2);
-          SongArtist := UTF8Decode(TableData.Fields[3]);
-          SongTitle := UTF8Decode(TableData.Fields[4]);
+          SongArtist := TableData.Fields[3];
+          SongTitle := TableData.Fields[4];
+          Date := FormatDate(TableData.FieldAsInteger(5));
         end;
       end;
       stBestSingers: begin
         Stat := TStatResultBestSingers.Create;
         with TStatResultBestSingers(Stat) do
         begin
-          Player := UTF8Decode(TableData.Fields[0]);
+          Player := TableData.Fields[0];
           AverageScore := TableData.FieldAsInteger(1);
         end;
       end;
@@ -456,8 +618,8 @@ begin
         Stat := TStatResultMostSungSong.Create;
         with TStatResultMostSungSong(Stat) do
         begin
-          Artist := UTF8Decode(TableData.Fields[0]);
-          Title  := UTF8Decode(TableData.Fields[1]);
+          Artist := TableData.Fields[0];
+          Title  := TableData.Fields[1];
           TimesSung  := TableData.FieldAsInteger(2);
         end;
       end;
@@ -465,7 +627,7 @@ begin
         Stat := TStatResultMostPopBand.Create;
         with TStatResultMostPopBand(Stat) do
         begin
-          ArtistName := UTF8Decode(TableData.Fields[0]);
+          ArtistName := TableData.Fields[0];
           TimesSungTot := TableData.FieldAsInteger(1);
         end;
       end
@@ -484,21 +646,21 @@ end;
 
 procedure TDataBaseSystem.FreeStats(StatList: TList);
 var
-  I: integer;
+  Index: integer;
 begin
   if (StatList = nil) then
     Exit;
-  for I := 0 to StatList.Count-1 do
-    TStatResult(StatList[I]).Free;
+  for Index := 0 to StatList.Count-1 do
+    TStatResult(StatList[Index]).Free;
   StatList.Free;
 end;
 
 (**
  * Gets total number of entrys for a stats query
  *)
-function  TDataBaseSystem.GetTotalEntrys(Typ: TStatType): Cardinal;
+function TDataBaseSystem.GetTotalEntrys(Typ: TStatType): cardinal;
 var
-  Query: String;
+  Query: string;
 begin
   Result := 0;
 
@@ -509,13 +671,13 @@ begin
     // Create query
     case Typ of
       stBestScores:
-        Query := 'SELECT COUNT([SongID]) FROM ['+cUS_Scores+'];';
+        Query := 'SELECT COUNT([SongID]) FROM [' + cUS_Scores + '];';
       stBestSingers:
-        Query := 'SELECT COUNT(DISTINCT [Player]) FROM ['+cUS_Scores+'];';
+        Query := 'SELECT COUNT(DISTINCT [Player]) FROM [' + cUS_Scores + '];';
       stMostSungSong:
-        Query := 'SELECT COUNT([ID]) FROM ['+cUS_Songs+'];';
+        Query := 'SELECT COUNT([ID]) FROM [' + cUS_Songs + '];';
       stMostPopBand:
-        Query := 'SELECT COUNT(DISTINCT [Artist]) FROM ['+cUS_Songs+'];';
+        Query := 'SELECT COUNT(DISTINCT [Artist]) FROM [' + cUS_Songs + '];';
     end;
 
     Result := ScoreDB.GetTableValue(Query);
@@ -538,7 +700,7 @@ begin
     Exit;
 
   try
-    Query := 'SELECT [ResetTime] FROM ['+cUS_Statistics_Info+'];';
+    Query := 'SELECT [ResetTime] FROM [' + cUS_Statistics_Info + '];';
     Result := UnixToDateTime(ScoreDB.GetTableValue(Query));
   except on E: Exception do
     Log.LogError(E.Message, 'TDataBaseSystem.GetStatReset');
diff --git a/cmake/src/base/UDraw.pas b/cmake/src/base/UDraw.pas
index 1783986f..308526b8 100644
--- a/cmake/src/base/UDraw.pas
+++ b/cmake/src/base/UDraw.pas
@@ -35,11 +35,9 @@ interface
 
 uses
   UThemes,
-  ModiSDK,
   UGraphicClasses;
 
 procedure SingDraw;
-procedure SingModiDraw (PlayerInfo: TPlayerInfo);
 procedure SingDrawBackground;
 procedure SingDrawOscilloscope(X, Y, W, H: real; NrSound: integer);
 procedure SingDrawNoteLines(Left, Top, Right: real; Space: integer);
@@ -86,7 +84,6 @@ uses
   Math,
   gl,
   TextGL,
-  UDLLManager,
   UDrawTexture,
   UGraphic,
   UIni,
@@ -96,7 +93,6 @@ uses
   UMusic,
   URecord,
   UScreenSing,
-  UScreenSingModi,
   UTexture;
 
 procedure SingDrawBackground;
@@ -258,19 +254,21 @@ begin
 // So we exploit this behavior a bit - we give NrLines the playernumber, keep it in playernumber - and then we set NrLines to zero
 // This could also come quite in handy when we do the duet mode, cause just the notes for the player that has to sing should be drawn then
 // BUT this is not implemented yet, all notes are drawn! :D
+  if (ScreenSing.settings.NotesVisible and (1 shl NrLines) <> 0) then
+  begin
 
-  PlayerNumber := NrLines + 1; // Player 1 is 0
-  NrLines     := 0;
+    PlayerNumber := NrLines + 1; // Player 1 is 0
+    NrLines     := 0;
 
-// exploit done
+  // exploit done
 
-  glColor3f(1, 1, 1);
-  glEnable(GL_TEXTURE_2D);
-  glEnable(GL_BLEND);
-  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    glColor3f(1, 1, 1);
+    glEnable(GL_TEXTURE_2D);
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
-  lTmpA := (Right-Left);
-  lTmpB := (Lines[NrLines].Line[Lines[NrLines].Current].End_ - Lines[NrLines].Line[Lines[NrLines].Current].Note[0].Start);
+    lTmpA := (Right-Left);
+    lTmpB := (Lines[NrLines].Line[Lines[NrLines].Current].End_ - Lines[NrLines].Line[Lines[NrLines].Current].Note[0].Start);
 
   if ( lTmpA > 0 ) and ( lTmpB > 0 ) then
     TempR := lTmpA / lTmpB
@@ -285,16 +283,17 @@ begin
       begin
         if NoteType <> ntFreestyle then
 	begin
-          if Ini.EffectSing = 0 then
-          // If Golden note Effect of then Change not Color
-          begin
-            case NoteType of
-              ntNormal: glColor4f(1, 1, 1, 1);   // We set alpha to 1, cause we can control the transparency through the png itself
-              ntGolden: glColor4f(1, 1, 0.3, 1); // no stars, paint yellow -> glColor4f(1, 1, 0.3, 0.85); - we could
+
+            if Ini.EffectSing = 0 then
+              // If Golden note Effect of then Change not Color
+            begin
+              case NoteType of
+                ntNormal: glColor4f(1, 1, 1, 1);   // We set alpha to 1, cause we can control the transparency through the png itself
+                ntGolden: glColor4f(1, 1, 0.3, 1); // no stars, paint yellow -> glColor4f(1, 1, 0.3, 0.85); - we could
             end; // case
-          end //Else all Notes same Color
-          else
-            glColor4f(1, 1, 1, 1);        // We set alpha to 1, cause we can control the transparency through the png itself
+            end //Else all Notes same Color
+            else
+              glColor4f(1, 1, 1, 1);        // We set alpha to 1, cause we can control the transparency through the png itself
 
           // left part
           Rec.Left  := (Start-Lines[NrLines].Line[Lines[NrLines].Current].Note[0].Start) * TempR + Left + 0.5 + 10*ScreenX;
@@ -309,35 +308,35 @@ begin
             glTexCoord2f(1, 0); glVertex2f(Rec.Right, Rec.Top);
           glEnd;
 
-          //We keep the postion of the top left corner b4 it's overwritten
+            //We keep the postion of the top left corner b4 it's overwritten
             GoldenStarPos := Rec.Left;
-          //done
+            //done
 
-         // middle part
-        Rec.Left  := Rec.Right;
-        Rec.Right := (Start+Length-Lines[NrLines].Line[Lines[NrLines].Current].Note[0].Start) * TempR + Left - NotesW - 0.5 + 10*ScreenX;
+            // middle part
+            Rec.Left := Rec.Right;
+            Rec.Right := (Start+Length-Lines[NrLines].Line[Lines[NrLines].Current].Note[0].Start) * TempR + Left - NotesW - 0.5 + 10*ScreenX;
 
-        glBindTexture(GL_TEXTURE_2D, Tex_plain_Mid[PlayerNumber].TexNum);
-        glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
-        glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
-        glBegin(GL_QUADS);
-          glTexCoord2f(0, 0); glVertex2f(Rec.Left,  Rec.Top);
-          glTexCoord2f(0, 1); glVertex2f(Rec.Left,  Rec.Bottom);
-          glTexCoord2f(round((Rec.Right-Rec.Left)/32), 1); glVertex2f(Rec.Right, Rec.Bottom);
-          glTexCoord2f(round((Rec.Right-Rec.Left)/32), 0); glVertex2f(Rec.Right, Rec.Top);
-        glEnd;
+            glBindTexture(GL_TEXTURE_2D, Tex_plain_Mid[PlayerNumber].TexNum);
+            glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
+            glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
+            glBegin(GL_QUADS);
+              glTexCoord2f(0, 0); glVertex2f(Rec.Left,  Rec.Top);
+              glTexCoord2f(0, 1); glVertex2f(Rec.Left,  Rec.Bottom);
+              glTexCoord2f(round((Rec.Right-Rec.Left)/32), 1); glVertex2f(Rec.Right, Rec.Bottom);
+              glTexCoord2f(round((Rec.Right-Rec.Left)/32), 0); glVertex2f(Rec.Right, Rec.Top);
+            glEnd;
 
         // right part
         Rec.Left  := Rec.Right;
         Rec.Right := Rec.Right + NotesW;
 
-        glBindTexture(GL_TEXTURE_2D, Tex_plain_Right[PlayerNumber].TexNum);
-        glBegin(GL_QUADS);
-          glTexCoord2f(0, 0); glVertex2f(Rec.Left,  Rec.Top);
-          glTexCoord2f(0, 1); glVertex2f(Rec.Left,  Rec.Bottom);
-          glTexCoord2f(1, 1); glVertex2f(Rec.Right, Rec.Bottom);
-          glTexCoord2f(1, 0); glVertex2f(Rec.Right, Rec.Top);
-        glEnd;
+            glBindTexture(GL_TEXTURE_2D, Tex_plain_Right[PlayerNumber].TexNum);
+            glBegin(GL_QUADS);
+              glTexCoord2f(0, 0); glVertex2f(Rec.Left,  Rec.Top);
+              glTexCoord2f(0, 1); glVertex2f(Rec.Left,  Rec.Bottom);
+              glTexCoord2f(1, 1); glVertex2f(Rec.Right, Rec.Bottom);
+              glTexCoord2f(1, 0); glVertex2f(Rec.Right, Rec.Top);
+            glEnd;
 
           // Golden Star Patch
           if (NoteType = ntGolden) and (Ini.EffectSing=1) then
@@ -345,13 +344,14 @@ begin
             GoldenRec.SaveGoldenStarsRec(GoldenStarPos, Rec.Top, Rec.Right, Rec.Bottom);
           end;
 
-        end; // if not FreeStyle
-      end; // with
-    end; // for
-  end; // with
+          end; // if not FreeStyle
+        end; // with
+      end; // for
+    end; // with
 
-  glDisable(GL_BLEND);
-  glDisable(GL_TEXTURE_2D);
+    glDisable(GL_BLEND);
+    glDisable(GL_TEXTURE_2D);
+  end;
 end;
 
 // draw sung notes
@@ -481,7 +481,7 @@ var
   W, H:           real;
   lTmpA, lTmpB:   real;
 begin
-  if (Player[PlayerIndex].ScoreTotalInt >= 0) then
+  if (ScreenSing.settings.NotesVisible and (1 shl PlayerIndex) <> 0) then
   begin
     glColor4f(1, 1, 1, sqrt((1+sin( AudioPlayback.Position * 3))/4)/ 2 + 0.5 );
     glEnable(GL_TEXTURE_2D);
@@ -683,20 +683,25 @@ begin
 
   // draw note-lines
 
-  if (PlayersPlay = 1) and (Ini.NoteLines = 1) then
+  // to-do : needs fix when party mode works w/ 2 screens 
+  if (PlayersPlay = 1) and (Ini.NoteLines = 1) and (ScreenSing.settings.NotesVisible and (1) <> 0) then
     SingDrawNoteLines(NR.Left + 10*ScreenX, Skin_P2_NotesB - 105, NR.Right + 10*ScreenX, 15);
 
   if ((PlayersPlay = 2) or (PlayersPlay = 4)) and (Ini.NoteLines = 1) then
   begin
-    SingDrawNoteLines(NR.Left + 10*ScreenX, Skin_P1_NotesB - 105, NR.Right + 10*ScreenX, 15);
-    SingDrawNoteLines(NR.Left + 10*ScreenX, Skin_P2_NotesB - 105, NR.Right + 10*ScreenX, 15);
+    if (ScreenSing.settings.NotesVisible and (1 shl 0) <> 0) then
+      SingDrawNoteLines(Nr.Left + 10*ScreenX, Skin_P1_NotesB - 105, Nr.Right + 10*ScreenX, 15);
+    if (ScreenSing.settings.NotesVisible and (1 shl 1) <> 0) then
+      SingDrawNoteLines(Nr.Left + 10*ScreenX, Skin_P2_NotesB - 105, Nr.Right + 10*ScreenX, 15);
   end;
 
-  if ((PlayersPlay = 3) or (PlayersPlay = 6)) and (Ini.NoteLines = 1) then
-  begin
-    SingDrawNoteLines(NR.Left + 10*ScreenX, 120, NR.Right + 10*ScreenX, 12);
-    SingDrawNoteLines(NR.Left + 10*ScreenX, 245, NR.Right + 10*ScreenX, 12);
-    SingDrawNoteLines(NR.Left + 10*ScreenX, 370, NR.Right + 10*ScreenX, 12);
+  if ((PlayersPlay = 3) or (PlayersPlay = 6)) and (Ini.NoteLines = 1) then begin
+    if (ScreenSing.settings.NotesVisible and (1 shl 0) <> 0) then
+      SingDrawNoteLines(Nr.Left + 10*ScreenX, 120, Nr.Right + 10*ScreenX, 12);
+    if (ScreenSing.settings.NotesVisible and (1 shl 1) <> 0) then
+      SingDrawNoteLines(Nr.Left + 10*ScreenX, 245, Nr.Right + 10*ScreenX, 12);
+    if (ScreenSing.settings.NotesVisible and (1 shl 2) <> 0) then
+      SingDrawNoteLines(Nr.Left + 10*ScreenX, 370, Nr.Right + 10*ScreenX, 12);
   end;
 
   // draw Lyrics
@@ -895,259 +900,6 @@ begin
   glDisable(GL_TEXTURE_2D);
 end;
 
-// q'n'd for using the game mode dll's
-procedure SingModiDraw (PlayerInfo: TPlayerInfo);
-var
-  NR: TRecR;
-begin
-  // positions
-  if Ini.SingWindow = 0 then
-  begin
-    NR.Left := 120;
-  end
-  else
-  begin
-    NR.Left := 20;
-  end;
-
-  NR.Right := 780;
-  NR.Width := NR.Right - NR.Left;
-  NR.WMid  := NR.Width / 2;
-  NR.Mid   := NR.Left + NR.WMid;
-
-  // time bar
-  SingDrawTimeBar();
-
-  if DLLMan.Selected.ShowNotes then
-  begin
-    if PlayersPlay = 1 then
-      SingDrawNoteLines(NR.Left + 10*ScreenX, Skin_P2_NotesB - 105, NR.Right + 10*ScreenX, 15);
-    if (PlayersPlay = 2) or (PlayersPlay = 4) then
-    begin
-      SingDrawNoteLines(NR.Left + 10*ScreenX, Skin_P1_NotesB - 105, NR.Right + 10*ScreenX, 15);
-      SingDrawNoteLines(NR.Left + 10*ScreenX, Skin_P2_NotesB - 105, NR.Right + 10*ScreenX, 15);
-    end;
-
-    if (PlayersPlay = 3) or (PlayersPlay = 6) then
-    begin
-      SingDrawNoteLines(NR.Left + 10*ScreenX, 120, NR.Right + 10*ScreenX, 12);
-      SingDrawNoteLines(NR.Left + 10*ScreenX, 245, NR.Right + 10*ScreenX, 12);
-      SingDrawNoteLines(NR.Left + 10*ScreenX, 370, NR.Right + 10*ScreenX, 12);
-    end;
-  end;
-
-  // Draw Lyrics
-  ScreenSingModi.Lyrics.Draw(LyricsState.MidBeat);
-  // TODO: Lyrics helper
-
-  // oscilloscope | the thing that moves when you yell into your mic (imho)
-  if (((Ini.Oscilloscope = 1) and (DLLMan.Selected.ShowRateBar_O)) and (not DLLMan.Selected.ShowRateBar)) then
-  begin
-    if PlayersPlay = 1 then
-      if PlayerInfo.Playerinfo[0].Enabled then
-        SingDrawOscilloscope(190 + 10*ScreenX, 55, 180, 40, 0);
-
-    if PlayersPlay = 2 then
-    begin
-      if PlayerInfo.Playerinfo[0].Enabled then
-        SingDrawOscilloscope(190 + 10*ScreenX, 55, 180, 40, 0);
-      if PlayerInfo.Playerinfo[1].Enabled then
-        SingDrawOscilloscope(425 + 10*ScreenX, 55, 180, 40, 1);
-    end;
-
-    if PlayersPlay = 4 then
-    begin
-      if ScreenAct = 1 then
-      begin
-        if PlayerInfo.Playerinfo[0].Enabled then
-          SingDrawOscilloscope(190 + 10*ScreenX, 55, 180, 40, 0);
-        if PlayerInfo.Playerinfo[1].Enabled then
-          SingDrawOscilloscope(425 + 10*ScreenX, 55, 180, 40, 1);
-      end;
-      if ScreenAct = 2 then
-      begin
-        if PlayerInfo.Playerinfo[2].Enabled then
-          SingDrawOscilloscope(190 + 10*ScreenX, 55, 180, 40, 2);
-        if PlayerInfo.Playerinfo[3].Enabled then
-          SingDrawOscilloscope(425 + 10*ScreenX, 55, 180, 40, 3);
-      end;
-    end;
-
-    if PlayersPlay = 3 then
-    begin
-      if PlayerInfo.Playerinfo[0].Enabled then
-        SingDrawOscilloscope( 75 + 10*ScreenX, 95, 100, 20, 0);
-      if PlayerInfo.Playerinfo[1].Enabled then
-        SingDrawOscilloscope(370 + 10*ScreenX, 95, 100, 20, 1);
-      if PlayerInfo.Playerinfo[2].Enabled then
-        SingDrawOscilloscope(670 + 10*ScreenX, 95, 100, 20, 2);
-    end;
-
-    if PlayersPlay = 6 then
-    begin
-      if ScreenAct = 1 then
-      begin
-        if PlayerInfo.Playerinfo[0].Enabled then
-          SingDrawOscilloscope( 75 + 10*ScreenX, 95, 100, 20, 0);
-        if PlayerInfo.Playerinfo[1].Enabled then
-          SingDrawOscilloscope(370 + 10*ScreenX, 95, 100, 20, 1);
-        if PlayerInfo.Playerinfo[2].Enabled then
-          SingDrawOscilloscope(670 + 10*ScreenX, 95, 100, 20, 2);
-      end;
-      if ScreenAct = 2 then
-      begin
-        if PlayerInfo.Playerinfo[3].Enabled then
-          SingDrawOscilloscope( 75 + 10*ScreenX, 95, 100, 20, 3);
-        if PlayerInfo.Playerinfo[4].Enabled then
-          SingDrawOscilloscope(370 + 10*ScreenX, 95, 100, 20, 4);
-        if PlayerInfo.Playerinfo[5].Enabled then
-          SingDrawOscilloscope(670 + 10*ScreenX, 95, 100, 20, 5);
-      end;
-    end;
-
-  end;
-
-// resize the notes according to the difficulty level
-  case Ini.Difficulty of
-    0:
-      begin
-        NotesH := 11; // 9
-        NotesW := 6; // 5
-      end;
-    1:
-      begin
-        NotesH := 8; // 7
-        NotesW := 4; // 4
-      end;
-    2:
-      begin
-        NotesH := 5;
-        NotesW := 3;
-      end;
-  end;
-
-  if (DLLMAn.Selected.ShowNotes and DLLMan.Selected.LoadSong) then
-  begin
-    if (PlayersPlay = 1) and PlayerInfo.Playerinfo[0].Enabled then
-    begin
-      SingDrawPlayerBGLine(NR.Left + 20, Skin_P2_NotesB, NR.Right - 20, 0, 0, 15);
-      SingDrawLine(NR.Left + 20, Skin_P2_NotesB, NR.Right - 20, 0, 15);
-      SingDrawPlayerLine(NR.Left + 20, Skin_P2_NotesB, NR.Width - 40, 0, 15);
-    end;
-
-    if PlayersPlay = 2 then
-    begin
-      if PlayerInfo.Playerinfo[0].Enabled then
-      begin
-        SingDrawPlayerBGLine(NR.Left + 20, Skin_P1_NotesB, NR.Right - 20, 0, 0, 15);
-        SingDrawLine(NR.Left + 20, Skin_P1_NotesB, NR.Right - 20, 0, 15);
-        SingDrawPlayerLine(NR.Left + 20, Skin_P1_NotesB, NR.Width - 40, 0, 15);
-      end;
-      if PlayerInfo.Playerinfo[1].Enabled then
-      begin
-        SingDrawPlayerBGLine(NR.Left + 20, Skin_P2_NotesB, NR.Right - 20, 0, 1, 15);
-        SingDrawLine(NR.Left + 20, Skin_P2_NotesB, NR.Right - 20, 0, 15);
-        SingDrawPlayerLine(NR.Left + 20, Skin_P2_NotesB, NR.Width - 40, 1, 15);
-      end;
-
-    end;
-
-    if PlayersPlay = 3 then
-    begin
-      NotesW := NotesW * 0.8;
-      NotesH := NotesH * 0.8;
-
-      if PlayerInfo.Playerinfo[0].Enabled then
-      begin
-        SingDrawPlayerBGLine(NR.Left + 20, 120+95, NR.Right - 20, 0, 0, 12);
-        SingDrawLine(NR.Left + 20, 120+95, NR.Right - 20, 0, 12);
-        SingDrawPlayerLine(NR.Left + 20, 120+95, NR.Width - 40, 0, 12);
-      end;
-
-      if PlayerInfo.Playerinfo[1].Enabled then
-      begin
-        SingDrawPlayerBGLine(NR.Left + 20, 245+95, NR.Right - 20, 0, 1, 12);
-        SingDrawLine(NR.Left + 20, 245+95, NR.Right - 20, 0, 12);
-        SingDrawPlayerLine(NR.Left + 20, 245+95, NR.Width - 40, 1, 12);
-      end;
-
-      if PlayerInfo.Playerinfo[2].Enabled then
-      begin
-        SingDrawPlayerBGLine(NR.Left + 20, 370+95, NR.Right - 20, 0, 2, 12);
-        SingDrawLine(NR.Left + 20, 370+95, NR.Right - 20, 0, 12);
-        SingDrawPlayerLine(NR.Left + 20, 370+95, NR.Width - 40, 2, 12);
-      end;
-    end;
-
-    if PlayersPlay = 4 then
-    begin
-      if ScreenAct = 1 then
-      begin
-        SingDrawPlayerBGLine(NR.Left + 20, Skin_P1_NotesB, NR.Right - 20, 0, 0, 15);
-        SingDrawPlayerBGLine(NR.Left + 20, Skin_P2_NotesB, NR.Right - 20, 0, 1, 15);
-      end;
-      if ScreenAct = 2 then
-      begin
-        SingDrawPlayerBGLine(NR.Left + 20, Skin_P1_NotesB, NR.Right - 20, 0, 2, 15);
-        SingDrawPlayerBGLine(NR.Left + 20, Skin_P2_NotesB, NR.Right - 20, 0, 3, 15);
-      end;
-
-      SingDrawLine(NR.Left + 20, Skin_P1_NotesB, NR.Right - 20, 0, 15);
-      SingDrawLine(NR.Left + 20, Skin_P2_NotesB, NR.Right - 20, 0, 15);
-
-      if ScreenAct = 1 then
-      begin
-        SingDrawPlayerLine(NR.Left + 20, Skin_P1_NotesB, NR.Width - 40, 0, 15);
-        SingDrawPlayerLine(NR.Left + 20, Skin_P2_NotesB, NR.Width - 40, 1, 15);
-      end;
-      if ScreenAct = 2 then
-      begin
-        SingDrawPlayerLine(NR.Left + 20, Skin_P1_NotesB, NR.Width - 40, 2, 15);
-        SingDrawPlayerLine(NR.Left + 20, Skin_P2_NotesB, NR.Width - 40, 3, 15);
-      end;
-    end;
-
-    if PlayersPlay = 6 then
-    begin
-      NotesW := NotesW * 0.8;
-      NotesH := NotesH * 0.8;
-
-      if ScreenAct = 1 then
-      begin
-        SingDrawPlayerBGLine(NR.Left + 20, 120+95, NR.Right - 20, 0, 0, 12);
-        SingDrawPlayerBGLine(NR.Left + 20, 245+95, NR.Right - 20, 0, 1, 12);
-        SingDrawPlayerBGLine(NR.Left + 20, 370+95, NR.Right - 20, 0, 2, 12);
-      end;
-      if ScreenAct = 2 then
-      begin
-        SingDrawPlayerBGLine(NR.Left + 20, 120+95, NR.Right - 20, 0, 3, 12);
-        SingDrawPlayerBGLine(NR.Left + 20, 245+95, NR.Right - 20, 0, 4, 12);
-        SingDrawPlayerBGLine(NR.Left + 20, 370+95, NR.Right - 20, 0, 5, 12);
-      end;
-
-      SingDrawLine(NR.Left + 20, 120+95, NR.Right - 20, 0, 12);
-      SingDrawLine(NR.Left + 20, 245+95, NR.Right - 20, 0, 12);
-      SingDrawLine(NR.Left + 20, 370+95, NR.Right - 20, 0, 12);
-
-      if ScreenAct = 1 then
-      begin
-        SingDrawPlayerLine(NR.Left + 20, 120+95, NR.Width - 40, 0, 12);
-        SingDrawPlayerLine(NR.Left + 20, 245+95, NR.Width - 40, 1, 12);
-        SingDrawPlayerLine(NR.Left + 20, 370+95, NR.Width - 40, 2, 12);
-      end;
-      if ScreenAct = 2 then
-      begin
-        SingDrawPlayerLine(NR.Left + 20, 120+95, NR.Width - 40, 3, 12);
-        SingDrawPlayerLine(NR.Left + 20, 245+95, NR.Width - 40, 4, 12);
-        SingDrawPlayerLine(NR.Left + 20, 370+95, NR.Width - 40, 5, 12);
-      end;
-    end;
-  end;
-
-  glDisable(GL_BLEND);
-  glDisable(GL_TEXTURE_2D);
-end;
-
 {//SingBar Mod
 procedure SingDrawSingbar(X, Y, W, H: real; Percent: integer);
 var
diff --git a/cmake/src/base/UEditorLyrics.pas b/cmake/src/base/UEditorLyrics.pas
index ef9d8dd6..0eacd1f9 100644
--- a/cmake/src/base/UEditorLyrics.pas
+++ b/cmake/src/base/UEditorLyrics.pas
@@ -74,7 +74,7 @@ type
       procedure SetSize(Value: real);
       procedure SetSelected(Value: integer);
       procedure SetFontStyle(Value: integer);
-      procedure AddWord(Text: string);
+      procedure AddWord(Text: UTF8String);
       procedure Refresh;
     public
       ColR:   real;
@@ -179,7 +179,7 @@ begin
   FontStyleI := Value;
 end;
 
-procedure TEditorLyrics.AddWord(Text: string);
+procedure TEditorLyrics.AddWord(Text: UTF8String);
 var
   WordNum: integer;
 begin
diff --git a/cmake/src/base/UFiles.pas b/cmake/src/base/UFiles.pas
index 0495dfbb..5a258e3e 100644
--- a/cmake/src/base/UFiles.pas
+++ b/cmake/src/base/UFiles.pas
@@ -34,24 +34,23 @@ interface
 
 uses
   SysUtils,
+  Classes,
   ULog,
   UMusic,
   USongs,
-  USong;
+  USong,
+  UPath;
 
 procedure ResetSingTemp;
 
-function  SaveSong(Song: TSong; Lines: TLines; Name: string; Relative: boolean): boolean;
+type
+  TSaveSongResult = (ssrOK, ssrFileError, ssrEncodingError);
 
-var
-  SongFile: TextFile;   // all procedures in this unit operates on this file
-  FileLineNo: integer;  //Line which is readed at Last, for error reporting
-
-  // variables available for all procedures
-  Base    : array[0..1] of integer;
-  Rel     : array[0..1] of integer;
-  Mult    : integer = 1;
-  MultBPM : integer = 4;
+{**
+ * Throws a TEncodingException if the song's fields cannot be encoded in the
+ * requested encoding.
+ *}
+function SaveSong(const Song: TSong; const Lines: TLines; const Name: IPath; Relative: boolean): TSaveSongResult;
 
 implementation
 
@@ -59,7 +58,9 @@ uses
   TextGL,
   UIni,
   UNote,
-  UPlatform;
+  UPlatform,
+  UUnicodeUtils,
+  UTextEncoding;
 
 //--------------------
 // Resets the temporary Sentence Arrays for each Player and some other Variables
@@ -77,101 +78,135 @@ begin
     Player[Count].LengthNote := 0;
     Player[Count].HighNote := -1;
   end;
-
-  (* FIXME
-  //Reset Path and Filename Values to Prevent Errors in Editor
-  if assigned( CurrentSong ) then
-  begin
-    SetLength(CurrentSong.BPM, 0);
-    CurrentSong.Path := '';
-    CurrentSong.FileName := '';
-  end;
-  *)
-  
-//  CurrentSong := nil;
 end;
 
-
 //--------------------
 // Saves a Song
 //--------------------
-function SaveSong(Song: TSong; Lines: TLines; Name: string; Relative: boolean): boolean;
+function SaveSong(const Song: TSong; const Lines: TLines; const Name: IPath; Relative: boolean): TSaveSongResult;
 var
   C:      integer;
   N:      integer;
-  S:      string;
+  S:      AnsiString;
   B:      integer;
-  RelativeSubTime:    integer;
-  NoteState: String;
+  RelativeSubTime: integer;
+  NoteState: AnsiString;
+  SongFile: TTextFileStream;
 
-begin
-//  Relative := true; // override (idea - use shift+S to save with relative)
-  AssignFile(SongFile, Name);
-  Rewrite(SongFile);
-
-  Writeln(SongFile, '#TITLE:' + Song.Title + '');
-  Writeln(SongFile, '#ARTIST:' + Song.Artist);
-
-  if Song.Creator     <> '' then    Writeln(SongFile, '#CREATOR:'     + Song.Creator);
-  if Song.Edition     <> 'Unknown' then Writeln(SongFile, '#EDITION:' + Song.Edition);
-  if Song.Genre       <> 'Unknown' then   Writeln(SongFile, '#GENRE:' + Song.Genre);
-  if Song.Language    <> 'Unknown' then    Writeln(SongFile, '#LANGUAGE:'    + Song.Language);
-
-  Writeln(SongFile, '#MP3:' + Song.Mp3);
-
-  if Song.Cover       <> '' then    Writeln(SongFile, '#COVER:'       + Song.Cover);
-  if Song.Background  <> '' then    Writeln(SongFile, '#BACKGROUND:'  + Song.Background);
-  if Song.Video       <> '' then    Writeln(SongFile, '#VIDEO:'       + Song.Video);
-  if Song.VideoGAP    <> 0  then    Writeln(SongFile, '#VIDEOGAP:'    + FloatToStr(Song.VideoGAP));
-  if Song.Resolution  <> 4  then    Writeln(SongFile, '#RESOLUTION:'  + IntToStr(Song.Resolution));
-  if Song.NotesGAP    <> 0  then    Writeln(SongFile, '#NOTESGAP:'    + IntToStr(Song.NotesGAP));
-  if Song.Start       <> 0  then    Writeln(SongFile, '#START:'       + FloatToStr(Song.Start));
-  if Song.Finish      <> 0  then    Writeln(SongFile, '#END:'         + IntToStr(Song.Finish));
-  if Relative               then    Writeln(SongFile, '#RELATIVE:yes');
-
-  Writeln(SongFile, '#BPM:' + FloatToStr(Song.BPM[0].BPM / 4));
-  Writeln(SongFile, '#GAP:' + FloatToStr(Song.GAP));
-
-  RelativeSubTime := 0;
-  for B := 1 to High(CurrentSong.BPM) do
-    Writeln(SongFile, 'B ' + FloatToStr(CurrentSong.BPM[B].StartBeat) + ' ' + FloatToStr(CurrentSong.BPM[B].BPM/4));
-
-  for C := 0 to Lines.High do begin
-    for N := 0 to Lines.Line[C].HighNote do begin
-      with Lines.Line[C].Note[N] do begin
-
-
-        //Golden + Freestyle Note Patch
-        case Lines.Line[C].Note[N].NoteType of
-          ntFreestyle: NoteState := 'F ';
-          ntNormal: NoteState := ': ';
-          ntGolden: NoteState := '* ';
-        end; // case
-        S := NoteState + IntToStr(Start-RelativeSubTime) + ' ' + IntToStr(Length) + ' ' + IntToStr(Tone) + ' ' + Text;
-
-
-        Writeln(SongFile, S);
-      end; // with
-    end; // N
-
-    if C < Lines.High then begin      // don't write end of last sentence
-      if not Relative then
-        S := '- ' + IntToStr(Lines.Line[C+1].Start)
-      else begin
-        S := '- ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime) +
-          ' ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime);
-        RelativeSubTime := Lines.Line[C+1].Start;
-      end;
-      Writeln(SongFile, S);
-    end;
+  function EncodeToken(const Str: UTF8String): RawByteString;
+  var
+    Success: boolean;
+  begin
+    Success := EncodeStringUTF8(Str, Result, Song.Encoding);
+    if (not Success) then
+      SaveSong := ssrEncodingError;
+  end;
 
-  end; // C
+  procedure WriteCustomTags;
+    var
+      I: integer;
+      Line: RawByteString;
+  begin
+    for I := 0 to High(Song.CustomTags) do
+    begin
+      Line := EncodeToken(Song.CustomTags[I].Content);
+      if (Length(Song.CustomTags[I].Tag) > 0) then
+        Line := EncodeToken(Song.CustomTags[I].Tag) + ':' + Line;
 
+      SongFile.WriteLine('#' + Line);
+    end;
 
-  Writeln(SongFile, 'E');
-  CloseFile(SongFile);
+  end;
 
-  Result := true;
+begin
+  //  Relative := true; // override (idea - use shift+S to save with relative)
+  Result := ssrOK;
+
+  try
+    SongFile := TMemTextFileStream.Create(Name, fmCreate);
+    try
+      // to-do: should we really write the BOM?
+      //        it causes problems w/ older versions
+      //        e.g. usdx 1.0.1a or ultrastar < 0.7.0
+      if (Song.Encoding = encUTF8) then
+        SongFile.WriteString(UTF8_BOM);
+
+      SongFile.WriteLine('#ENCODING:' + EncodingName(Song.Encoding));
+      SongFile.WriteLine('#TITLE:'    + EncodeToken(Song.Title));
+      SongFile.WriteLine('#ARTIST:'   + EncodeToken(Song.Artist));
+
+      if Song.Creator     <> ''        then SongFile.WriteLine('#CREATOR:'   + EncodeToken(Song.Creator));
+      if Song.Edition     <> 'Unknown' then SongFile.WriteLine('#EDITION:'   + EncodeToken(Song.Edition));
+      if Song.Genre       <> 'Unknown' then SongFile.WriteLine('#GENRE:'     + EncodeToken(Song.Genre));
+      if Song.Language    <> 'Unknown' then SongFile.WriteLine('#LANGUAGE:'  + EncodeToken(Song.Language));
+      if Song.Year        <> 0         then SongFile.WriteLine('#YEAR:'      + IntToStr(Song.Year));
+
+      SongFile.WriteLine('#MP3:' + EncodeToken(Song.Mp3.ToUTF8));
+      if Song.Cover.IsSet      then    SongFile.WriteLine('#COVER:'       + EncodeToken(Song.Cover.ToUTF8));
+      if Song.Background.IsSet then    SongFile.WriteLine('#BACKGROUND:'  + EncodeToken(Song.Background.ToUTF8));
+      if Song.Video.IsSet      then    SongFile.WriteLine('#VIDEO:'       + EncodeToken(Song.Video.ToUTF8));
+
+      if Song.VideoGAP    <> 0  then    SongFile.WriteLine('#VIDEOGAP:'    + FloatToStr(Song.VideoGAP));
+      if Song.Resolution  <> 4  then    SongFile.WriteLine('#RESOLUTION:'  + IntToStr(Song.Resolution));
+      if Song.NotesGAP    <> 0  then    SongFile.WriteLine('#NOTESGAP:'    + IntToStr(Song.NotesGAP));
+      if Song.Start       <> 0  then    SongFile.WriteLine('#START:'       + FloatToStr(Song.Start));
+      if Song.Finish      <> 0  then    SongFile.WriteLine('#END:'         + IntToStr(Song.Finish));
+      if Relative               then    SongFile.WriteLine('#RELATIVE:yes');
+
+      SongFile.WriteLine('#BPM:' + FloatToStr(Song.BPM[0].BPM / 4));
+      SongFile.WriteLine('#GAP:' + FloatToStr(Song.GAP));
+
+      // write custom header tags
+      WriteCustomTags;
+
+      RelativeSubTime := 0;
+      for B := 1 to High(Song.BPM) do
+        SongFile.WriteLine('B ' + FloatToStr(Song.BPM[B].StartBeat) + ' '
+                                + FloatToStr(Song.BPM[B].BPM/4));
+
+      for C := 0 to Lines.High do
+      begin
+        for N := 0 to Lines.Line[C].HighNote do
+        begin
+          with Lines.Line[C].Note[N] do
+          begin
+            //Golden + Freestyle Note Patch
+            case Lines.Line[C].Note[N].NoteType of
+              ntFreestyle: NoteState := 'F ';
+              ntNormal: NoteState := ': ';
+              ntGolden: NoteState := '* ';
+            end; // case
+            S := NoteState + IntToStr(Start-RelativeSubTime) + ' '
+                           + IntToStr(Length) + ' '
+                           + IntToStr(Tone) + ' '
+                           + EncodeToken(Text);
+
+            SongFile.WriteLine(S);
+          end; // with
+        end; // N
+
+        if C < Lines.High then // don't write end of last sentence
+        begin
+          if not Relative then
+            S := '- ' + IntToStr(Lines.Line[C+1].Start)
+          else
+          begin
+            S := '- ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime) +
+              ' ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime);
+            RelativeSubTime := Lines.Line[C+1].Start;
+          end;
+          SongFile.WriteLine(S);
+        end;
+      end; // C
+
+      SongFile.WriteLine('E');
+    finally
+      SongFile.Free;
+    end;
+  except
+    Result := ssrFileError;
+  end;
 end;
 
 end.
+
diff --git a/cmake/src/base/UFilesystem.pas b/cmake/src/base/UFilesystem.pas
new file mode 100644
index 00000000..805bcfe5
--- /dev/null
+++ b/cmake/src/base/UFilesystem.pas
@@ -0,0 +1,692 @@
+{* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *}
+
+unit UFilesystem;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  SysUtils,
+  Classes,
+  {$IFDEF MSWINDOWS}
+  Windows,
+  TntSysUtils,
+  {$ENDIF}
+  UPath;
+
+type
+  {$IFDEF MSWINDOWS}
+  TSytemSearchRec = TSearchRecW;
+  {$ELSE}
+  TSytemSearchRec = TSearchRec;
+  {$ENDIF}
+
+  TFileInfo = record
+    Time: integer;  // timestamp
+    Size: int64;    // file size (byte)
+    Attr: integer;  // file attributes
+    Name: IPath;    // basename with extension
+  end;
+
+  {**
+   * Iterates through the search results retrieved by FileFind().
+   * Example usage:
+   *   while(Iter.HasNext()) do
+   *     SearchRec := Iter.Next();
+   *}
+  IFileIterator = interface
+    function HasNext(): boolean;
+    function Next(): TFileInfo;
+  end;
+
+  {**
+   * Wrapper for SysUtils file functions.
+   * For documentation and examples, check the SysUtils equivalent.
+   *}
+  IFileSystem = interface
+    function ExpandFileName(const FileName: IPath): IPath;
+    function FileCreate(const FileName: IPath): TFileHandle;
+    function DirectoryCreate(const Dir: IPath): boolean;
+    function FileOpen(const FileName: IPath; Mode: longword): TFileHandle;
+    function FileAge(const FileName: IPath): integer; overload;
+    function FileAge(const FileName: IPath; out FileDateTime: TDateTime): boolean; overload;
+
+    function DirectoryExists(const Name: IPath): boolean;
+
+    {**
+     * On Windows: returns true only for files (not directories)
+     * On Apple/Unix: returns true for all kind of files (even directories)
+     * @seealso SysUtils.FileExists()
+     *}
+    function FileExists(const Name: IPath): boolean;
+
+    function FileGetAttr(const FileName: IPath): Cardinal;
+    function FileSetAttr(const FileName: IPath; Attr: integer): boolean;
+    function FileIsReadOnly(const FileName: IPath): boolean;
+    function FileSetReadOnly(const FileName: IPath; ReadOnly: boolean): boolean;
+    function FileIsAbsolute(const FileName: IPath): boolean;
+    function ForceDirectories(const Dir: IPath): boolean;
+    function RenameFile(const OldName, NewName: IPath): boolean;
+    function DeleteFile(const FileName: IPath): boolean;
+    function RemoveDir(const Dir: IPath): boolean;
+
+    {**
+     * Copies file Source to Target. If FailIfExists is true, the file is not
+     * copied if it already exists.
+     * Returns true if the file was successfully copied.
+     *}
+    function CopyFile(const Source, Target: IPath; FailIfExists: boolean): boolean;
+
+    function ExtractFileDrive(const FileName: IPath): IPath;
+    function ExtractFilePath(const FileName: IPath): IPath;
+    function ExtractFileDir(const FileName: IPath): IPath;
+    function ExtractFileName(const FileName: IPath): IPath;
+    function ExtractFileExt(const FileName: IPath): IPath;
+    function ExtractRelativePath(const BaseName: IPath; const FileName: IPath): IPath;
+
+    function ChangeFileExt(const FileName: IPath; const Extension: IPath): IPath;
+
+    function IncludeTrailingPathDelimiter(const FileName: IPath): IPath;
+    function ExcludeTrailingPathDelimiter(const FileName: IPath): IPath;
+
+    {**
+     * Searches for a file with filename Name in the directories given in DirList.
+     *}
+    function FileSearch(const Name: IPath; DirList: array of IPath): IPath;
+
+    {**
+     * More convenient version of FindFirst/Next/Close with iterator support.
+     *}
+    function FileFind(const FilePattern: IPath; Attr: integer): IFileIterator;
+
+    {**
+     * Old style search functions. Use FileFind() instead.
+     *}
+    function FindFirst(const FilePattern: IPath; Attr: integer; var F: TSytemSearchRec): integer;
+    function FindNext(var F: TSytemSearchRec): integer;
+    procedure FindClose(var F: TSytemSearchRec);
+
+    function GetCurrentDir: IPath;
+    function SetCurrentDir(const Dir: IPath): boolean;
+
+    {**
+     * Returns true if the filesystem is case-sensitive.
+     *}
+    function IsCaseSensitive(): boolean;
+  end;
+
+  function FileSystem(): IFileSystem;
+
+implementation
+
+type
+  TFileSystemImpl = class(TInterfacedObject, IFileSystem)
+  public
+    function ExpandFileName(const FileName: IPath): IPath;
+    function FileCreate(const FileName: IPath): TFileHandle;
+    function DirectoryCreate(const Dir: IPath): boolean;
+    function FileOpen(const FileName: IPath; Mode: longword): TFileHandle;
+    function FileAge(const FileName: IPath): integer; overload;
+    function FileAge(const FileName: IPath; out FileDateTime: TDateTime): boolean; overload;
+    function DirectoryExists(const Name: IPath): boolean;
+    function FileExists(const Name: IPath): boolean;
+    function FileGetAttr(const FileName: IPath): Cardinal;
+    function FileSetAttr(const FileName: IPath; Attr: integer): boolean;
+    function FileIsReadOnly(const FileName: IPath): boolean;
+    function FileSetReadOnly(const FileName: IPath; ReadOnly: boolean): boolean;
+    function FileIsAbsolute(const FileName: IPath): boolean;
+    function ForceDirectories(const Dir: IPath): boolean;
+    function RenameFile(const OldName, NewName: IPath): boolean;
+    function DeleteFile(const FileName: IPath): boolean;
+    function RemoveDir(const Dir: IPath): boolean;
+    function CopyFile(const Source, Target: IPath; FailIfExists: boolean): boolean;
+
+    function ExtractFileDrive(const FileName: IPath): IPath;
+    function ExtractFilePath(const FileName: IPath): IPath;
+    function ExtractFileDir(const FileName: IPath): IPath;
+    function ExtractFileName(const FileName: IPath): IPath;
+    function ExtractFileExt(const FileName: IPath): IPath;
+    function ExtractRelativePath(const BaseName: IPath; const FileName: IPath): IPath;
+    function ChangeFileExt(const FileName: IPath; const Extension: IPath): IPath;
+    function IncludeTrailingPathDelimiter(const FileName: IPath): IPath;
+    function ExcludeTrailingPathDelimiter(const FileName: IPath): IPath;
+
+    function FileSearch(const Name: IPath; DirList: array of IPath): IPath;
+    function FileFind(const FilePattern: IPath; Attr: integer): IFileIterator;
+
+    function FindFirst(const FilePattern: IPath; Attr: integer; var F: TSytemSearchRec): integer;
+    function FindNext(var F: TSytemSearchRec): integer;
+    procedure FindClose(var F: TSytemSearchRec);
+
+    function GetCurrentDir: IPath;
+    function SetCurrentDir(const Dir: IPath): boolean;
+
+    function IsCaseSensitive(): boolean;
+  end;
+
+  TFileIterator = class(TInterfacedObject, IFileIterator)
+  private
+    fHasNext: boolean;
+    fSearchRec: TSytemSearchRec;
+  public
+    constructor Create(const FilePattern: IPath; Attr: integer);
+    destructor Destroy(); override;
+
+    function HasNext(): boolean;
+    function Next(): TFileInfo;
+  end;
+
+
+var
+  FileSystem_Singleton: IFileSystem;
+
+function FileSystem(): IFileSystem;
+begin
+  Result := FileSystem_Singleton;
+end;
+
+function TFileSystemImpl.FileFind(const FilePattern: IPath; Attr: integer): IFileIterator;
+begin
+  Result := TFileIterator.Create(FilePattern, Attr);
+end;
+
+function TFileSystemImpl.IsCaseSensitive(): boolean;
+begin
+  // Windows and Mac OS X do not have case sensitive file systems
+  {$IF Defined(MSWINDOWS) or Defined(DARWIN)}
+    Result := false;
+  {$ELSE}
+    Result := true;
+  {$IFEND}
+end;
+
+function TFileSystemImpl.FileIsAbsolute(const FileName: IPath): boolean;
+var
+  NameStr: UTF8String;
+begin
+  Result := true;
+  NameStr := FileName.ToUTF8();
+
+  {$IFDEF MSWINDOWS}
+    // check if drive is given 'C:...'
+    if (FileName.GetDrive().ToUTF8 <> '') then
+      Exit;
+    // check if path starts with '\\'
+    if (Length(NameStr) >= 2) and
+       (NameStr[1] = PathDelim) and (NameStr[2] = PathDelim) then
+      Exit;
+  {$ELSE} // Unix based systems
+    // check if root dir given '/...'
+    if (Length(NameStr) >= 1) and (NameStr[1] = PathDelim) then
+      Exit;
+  {$ENDIF}
+
+  Result := false;
+end;
+
+{$IFDEF MSWINDOWS}
+
+function TFileSystemImpl.ExpandFileName(const FileName: IPath): IPath;
+begin
+  Result := Path(WideExpandFileName(FileName.ToWide()));
+end;
+
+function TFileSystemImpl.FileCreate(const FileName: IPath): TFileHandle;
+begin
+  Result := WideFileCreate(FileName.ToWide());
+end;
+
+function TFileSystemImpl.DirectoryCreate(const Dir: IPath): boolean;
+begin
+  Result := WideCreateDir(Dir.ToWide());
+end;
+
+function TFileSystemImpl.FileOpen(const FileName: IPath; Mode: longword): TFileHandle;
+begin
+  Result := WideFileOpen(FileName.ToWide(), Mode);
+end;
+
+function TFileSystemImpl.FileAge(const FileName: IPath): integer;
+begin
+  Result := WideFileAge(FileName.ToWide());
+end;
+
+function TFileSystemImpl.FileAge(const FileName: IPath; out FileDateTime: TDateTime): boolean;
+begin
+  Result := WideFileAge(FileName.ToWide(), FileDateTime);
+end;
+
+function TFileSystemImpl.DirectoryExists(const Name: IPath): boolean;
+begin
+  Result := WideDirectoryExists(Name.ToWide());
+end;
+
+function TFileSystemImpl.FileExists(const Name: IPath): boolean;
+begin
+  Result := WideFileExists(Name.ToWide());
+end;
+
+function TFileSystemImpl.FileGetAttr(const FileName: IPath): Cardinal;
+begin
+  Result := WideFileGetAttr(FileName.ToWide());
+end;
+
+function TFileSystemImpl.FileSetAttr(const FileName: IPath; Attr: integer): boolean;
+begin
+  Result := WideFileSetAttr(FileName.ToWide(), Attr);
+end;
+
+function TFileSystemImpl.FileIsReadOnly(const FileName: IPath): boolean;
+begin
+  Result := WideFileIsReadOnly(FileName.ToWide());
+end;
+
+function TFileSystemImpl.FileSetReadOnly(const FileName: IPath; ReadOnly: boolean): boolean;
+begin
+  Result := WideFileSetReadOnly(FileName.ToWide(), ReadOnly);
+end;
+
+function TFileSystemImpl.ForceDirectories(const Dir: IPath): boolean;
+begin
+  Result := WideForceDirectories(Dir.ToWide());
+end;
+
+function TFileSystemImpl.FileSearch(const Name: IPath; DirList: array of IPath): IPath;
+var
+  I: integer;
+  DirListStr: WideString;
+begin
+  DirListStr := '';
+  for I := 0 to High(DirList) do
+  begin
+    if (I > 0) then
+      DirListStr := DirListStr + PathSep;
+    DirListStr := DirListStr + DirList[I].ToWide();
+  end;
+  Result := Path(WideFileSearch(Name.ToWide(), DirListStr));
+end;
+
+function TFileSystemImpl.RenameFile(const OldName, NewName: IPath): boolean;
+begin
+  Result := WideRenameFile(OldName.ToWide(), NewName.ToWide());
+end;
+
+function TFileSystemImpl.DeleteFile(const FileName: IPath): boolean;
+begin
+  Result := WideDeleteFile(FileName.ToWide());
+end;
+
+function TFileSystemImpl.RemoveDir(const Dir: IPath): boolean;
+begin
+  Result := WideRemoveDir(Dir.ToWide());
+end;
+
+function TFileSystemImpl.CopyFile(const Source, Target: IPath; FailIfExists: boolean): boolean;
+begin
+  Result := WideCopyFile(Source.ToWide(), Target.ToWide(), FailIfExists);
+end;
+
+function TFileSystemImpl.ExtractFileDrive(const FileName: IPath): IPath;
+begin
+  Result := Path(WideExtractFileDrive(FileName.ToWide()));
+end;
+
+function TFileSystemImpl.ExtractFilePath(const FileName: IPath): IPath;
+begin
+  Result := Path(WideExtractFilePath(FileName.ToWide()));
+end;
+
+function TFileSystemImpl.ExtractFileDir(const FileName: IPath): IPath;
+begin
+  Result := Path(WideExtractFileDir(FileName.ToWide()));
+end;
+
+function TFileSystemImpl.ExtractFileName(const FileName: IPath): IPath;
+begin
+  Result := Path(WideExtractFileName(FileName.ToWide()));
+end;
+
+function TFileSystemImpl.ExtractFileExt(const FileName: IPath): IPath;
+begin
+  Result := Path(WideExtractFileExt(FileName.ToWide()));
+end;
+
+function TFileSystemImpl.ExtractRelativePath(const BaseName: IPath; const FileName: IPath): IPath;
+begin
+  Result := Path(WideExtractRelativePath(BaseName.ToWide(), FileName.ToWide()));
+end;
+
+function TFileSystemImpl.ChangeFileExt(const FileName: IPath; const Extension: IPath): IPath;
+begin
+  Result := Path(WideChangeFileExt(FileName.ToWide(), Extension.ToWide()));
+end;
+
+function TFileSystemImpl.IncludeTrailingPathDelimiter(const FileName: IPath): IPath;
+begin
+  Result := Path(WideIncludeTrailingPathDelimiter(FileName.ToWide()));
+end;
+
+function TFileSystemImpl.ExcludeTrailingPathDelimiter(const FileName: IPath): IPath;
+begin
+  Result := Path(WideExcludeTrailingPathDelimiter(FileName.ToWide()));
+end;
+
+function TFileSystemImpl.FindFirst(const FilePattern: IPath; Attr: integer; var F: TSytemSearchRec): integer;
+begin
+  Result := WideFindFirst(FilePattern.ToWide(), Attr, F);
+end;
+
+function TFileSystemImpl.FindNext(var F: TSytemSearchRec): integer;
+begin
+  Result := WideFindNext(F);
+end;
+
+procedure TFileSystemImpl.FindClose(var F: TSytemSearchRec);
+begin
+  WideFindClose(F);
+end;
+
+function TFileSystemImpl.GetCurrentDir: IPath;
+begin
+  Result := Path(WideGetCurrentDir());
+end;
+
+function TFileSystemImpl.SetCurrentDir(const Dir: IPath): boolean;
+begin
+  Result := WideSetCurrentDir(Dir.ToWide());
+end;
+
+{$ELSE} // UNIX
+
+function TFileSystemImpl.ExpandFileName(const FileName: IPath): IPath;
+begin
+  Result := Path(SysUtils.ExpandFileName(FileName.ToNative()));
+end;
+
+function TFileSystemImpl.FileCreate(const FileName: IPath): TFileHandle;
+begin
+  Result := SysUtils.FileCreate(FileName.ToNative());
+end;
+
+function TFileSystemImpl.DirectoryCreate(const Dir: IPath): boolean;
+begin
+  Result := SysUtils.CreateDir(Dir.ToNative());
+end;
+
+function TFileSystemImpl.FileOpen(const FileName: IPath; Mode: longword): TFileHandle;
+begin
+  Result := SysUtils.FileOpen(FileName.ToNative(), Mode);
+end;
+
+function TFileSystemImpl.FileAge(const FileName: IPath): integer;
+begin
+  Result := SysUtils.FileAge(FileName.ToNative());
+end;
+
+function TFileSystemImpl.FileAge(const FileName: IPath; out FileDateTime: TDateTime): boolean;
+var
+  FileDate: integer;
+begin
+  FileDate := SysUtils.FileAge(FileName.ToNative());
+  Result := (FileDate <> -1);
+  if (Result) then
+    FileDateTime := FileDateToDateTime(FileDate);
+end;
+
+function TFileSystemImpl.DirectoryExists(const Name: IPath): boolean;
+begin
+  Result := SysUtils.DirectoryExists(Name.ToNative());
+end;
+
+function TFileSystemImpl.FileExists(const Name: IPath): boolean;
+begin
+  Result := SysUtils.FileExists(Name.ToNative());
+end;
+
+function TFileSystemImpl.FileGetAttr(const FileName: IPath): Cardinal;
+begin
+  Result := SysUtils.FileGetAttr(FileName.ToNative());
+end;
+
+function TFileSystemImpl.FileSetAttr(const FileName: IPath; Attr: integer): boolean;
+begin
+  Result := (SysUtils.FileSetAttr(FileName.ToNative(), Attr) = 0);
+end;
+
+function TFileSystemImpl.FileIsReadOnly(const FileName: IPath): boolean;
+begin
+  Result := SysUtils.FileIsReadOnly(FileName.ToNative());
+end;
+
+function TFileSystemImpl.FileSetReadOnly(const FileName: IPath; ReadOnly: boolean): boolean;
+begin
+  Result := (SysUtils.FileSetAttr(FileName.ToNative(), faReadOnly) = 0);
+end;
+
+function TFileSystemImpl.ForceDirectories(const Dir: IPath): boolean;
+begin
+  Result := SysUtils.ForceDirectories(Dir.ToNative());
+end;
+
+function TFileSystemImpl.FileSearch(const Name: IPath; DirList: array of IPath): IPath;
+var
+  I: integer;
+  DirListStr: AnsiString;
+begin
+  DirListStr := '';
+  for I := 0 to High(DirList) do
+  begin
+    if (I > 0) then
+      DirListStr := DirListStr + PathSep;
+    DirListStr := DirListStr + DirList[I].ToNative();
+  end;
+  Result := Path(SysUtils.FileSearch(Name.ToNative(), DirListStr));
+end;
+
+function TFileSystemImpl.RenameFile(const OldName, NewName: IPath): boolean;
+begin
+  Result := SysUtils.RenameFile(OldName.ToNative(), NewName.ToNative());
+end;
+
+function TFileSystemImpl.DeleteFile(const FileName: IPath): boolean;
+begin
+  Result := SysUtils.DeleteFile(FileName.ToNative());
+end;
+
+function TFileSystemImpl.RemoveDir(const Dir: IPath): boolean;
+begin
+  Result := SysUtils.RemoveDir(Dir.ToNative());
+end;
+
+function TFileSystemImpl.CopyFile(const Source, Target: IPath; FailIfExists: boolean): boolean;
+const
+  COPY_BUFFER_SIZE = 4096; // a good tradeoff between speed and memory consumption
+var
+  SourceFile, TargetFile: TFileStream;
+  FileCopyBuffer: array [0..COPY_BUFFER_SIZE-1] of byte; // temporary copy-buffer.
+  NumberOfBytes: integer; // number of bytes read from SourceFile
+begin
+  Result := false;
+  SourceFile := nil;
+  TargetFile := nil;
+
+  // if overwrite is disabled return if the target file already exists
+  if (FailIfExists and FileExists(Target)) then
+    Exit;
+
+  try
+    try
+      // open source and target file (might throw an exception on error)
+      SourceFile := TFileStream.Create(Source.ToNative(), fmOpenRead);
+      TargetFile := TFileStream.Create(Target.ToNative(), fmCreate or fmOpenWrite);
+
+      while true do
+      begin
+        // read a block from the source file and check for errors or EOF
+        NumberOfBytes := SourceFile.Read(FileCopyBuffer, SizeOf(FileCopyBuffer));
+        if (NumberOfBytes <= 0) then
+          Break;
+        // write block to target file and check if everything was written
+        if (TargetFile.Write(FileCopyBuffer, NumberOfBytes) <> NumberOfBytes) then
+          Exit;
+      end;
+    except
+      Exit;
+    end;
+  finally
+    SourceFile.Free;
+    TargetFile.Free;
+  end;
+
+  Result := true;
+end;
+
+function TFileSystemImpl.ExtractFileDrive(const FileName: IPath): IPath;
+begin
+  Result := Path(SysUtils.ExtractFileDrive(FileName.ToNative()));
+end;
+
+function TFileSystemImpl.ExtractFilePath(const FileName: IPath): IPath;
+begin
+  Result := Path(SysUtils.ExtractFilePath(FileName.ToNative()));
+end;
+
+function TFileSystemImpl.ExtractFileDir(const FileName: IPath): IPath;
+begin
+  Result := Path(SysUtils.ExtractFileDir(FileName.ToNative()));
+end;
+
+function TFileSystemImpl.ExtractFileName(const FileName: IPath): IPath;
+begin
+  Result := Path(SysUtils.ExtractFileName(FileName.ToNative()));
+end;
+
+function TFileSystemImpl.ExtractFileExt(const FileName: IPath): IPath;
+begin
+  Result := Path(SysUtils.ExtractFileExt(FileName.ToNative()));
+end;
+
+function TFileSystemImpl.ExtractRelativePath(const BaseName: IPath; const FileName: IPath): IPath;
+begin
+  Result := Path(SysUtils.ExtractRelativePath(BaseName.ToNative(), FileName.ToNative()));
+end;
+
+function TFileSystemImpl.ChangeFileExt(const FileName: IPath; const Extension: IPath): IPath;
+begin
+  Result := Path(SysUtils.ChangeFileExt(FileName.ToNative(), Extension.ToNative()));
+end;
+
+function TFileSystemImpl.IncludeTrailingPathDelimiter(const FileName: IPath): IPath;
+begin
+  Result := Path(SysUtils.IncludeTrailingPathDelimiter(FileName.ToNative()));
+end;
+
+function TFileSystemImpl.ExcludeTrailingPathDelimiter(const FileName: IPath): IPath;
+begin
+  Result := Path(SysUtils.ExcludeTrailingPathDelimiter(FileName.ToNative()));
+end;
+
+function TFileSystemImpl.FindFirst(const FilePattern: IPath; Attr: integer; var F: TSytemSearchRec): integer;
+begin
+  Result := SysUtils.FindFirst(FilePattern.ToNative(), Attr, F);
+end;
+
+function TFileSystemImpl.FindNext(var F: TSytemSearchRec): integer;
+begin
+  Result := SysUtils.FindNext(F);
+end;
+
+procedure TFileSystemImpl.FindClose(var F: TSytemSearchRec);
+begin
+  SysUtils.FindClose(F);
+end;
+
+function TFileSystemImpl.GetCurrentDir: IPath;
+begin
+  Result := Path(SysUtils.GetCurrentDir());
+end;
+
+function TFileSystemImpl.SetCurrentDir(const Dir: IPath): boolean;
+begin
+  Result := SysUtils.SetCurrentDir(Dir.ToNative());
+end;
+
+{$ENDIF}
+
+
+{ TFileIterator }
+
+constructor TFileIterator.Create(const FilePattern: IPath; Attr: integer);
+begin
+  inherited Create();
+  fHasNext := (FileSystem.FindFirst(FilePattern, Attr, fSearchRec) = 0);
+end;
+
+destructor TFileIterator.Destroy();
+begin
+  FileSystem.FindClose(fSearchRec);
+  inherited;
+end;
+
+function TFileIterator.HasNext(): boolean;
+begin
+  Result := fHasNext;
+end;
+
+function TFileIterator.Next(): TFileInfo;
+begin
+  if (not fHasNext) then
+  begin
+    // Note: do not use FillChar() on records with ref-counted fields
+    Result.Time := 0;
+    Result.Size := 0;
+    Result.Attr := 0;
+    Result.Name := nil;
+    Exit;
+  end;
+
+  Result.Time := fSearchRec.Time;
+  Result.Size := fSearchRec.Size;
+  Result.Attr := fSearchRec.Attr;
+  Result.Name := Path(fSearchRec.Name);
+
+  // fetch next entry
+  fHasNext := (FileSystem.FindNext(fSearchRec) = 0);
+end;
+
+
+initialization
+  FileSystem_Singleton := TFileSystemImpl.Create;
+
+finalization
+  FileSystem_Singleton := nil;
+
+end.
diff --git a/cmake/src/base/UFont.pas b/cmake/src/base/UFont.pas
index a72bca21..49a19a1a 100644
--- a/cmake/src/base/UFont.pas
+++ b/cmake/src/base/UFont.pas
@@ -41,18 +41,23 @@ interface
   {$DEFINE BITMAP_FONT}
 {$ENDIF}
 
+// Enables the Freetype font cache
+{$DEFINE ENABLE_FT_FACE_CACHE}
+
 uses
   FreeType,
   gl,
   glext,
   glu,
   sdl,
+  Math,
+  Classes,
+  SysUtils,
+  UUnicodeUtils,
   {$IFDEF BITMAP_FONT}
   UTexture,
   {$ENDIF}
-  Math,
-  Classes,
-  SysUtils;
+  UPath;
 
 type
 
@@ -60,7 +65,7 @@ type
   TGLubyteArray = array[0 .. (MaxInt div SizeOf(GLubyte))-1] of GLubyte;
   TGLubyteDynArray = array of GLubyte;
 
-  TWideStringArray = array of WideString;
+  TUCS4StringArray = array of UCS4String;
 
   TGLColor = packed record
     case byte of
@@ -86,6 +91,8 @@ type
     Width, Height: integer;
   end;
 
+  EFontError = class(Exception);
+
   {**
    * Abstract base class representing a glyph.
    *}
@@ -117,6 +124,7 @@ type
       procedure ResetIntern();
 
     protected
+      fFilename: IPath;
       fStyle: TFontStyle;
       fUseKerning: boolean;       
       fLineSpacing: single;       // must be inited by subclass
@@ -126,34 +134,34 @@ type
 
       {**
        * Splits lines in Text seperated by newline (char-code #13).
-       * @param Text   UTF-8 encoded string
-       * @param Lines  splitted WideString lines
+       * @param Text   UCS-4 encoded string
+       * @param Lines  splitted UCS4String lines
        *}
-      procedure SplitLines(const Text: UTF8String; var Lines: TWideStringArray);
+      procedure SplitLines(const Text: UCS4String; var Lines: TUCS4StringArray);
 
       {**
-       * Print an array of WideStrings. Each array-item is a line of text.
+       * Print an array of UCS4Strings. Each array-item is a line of text.
        * Lines of text are seperated by the line-spacing.
        * This is the base function for all text drawing.
        *}
-      procedure Print(const Text: TWideStringArray); overload; virtual;
+      procedure Print(const Text: TUCS4StringArray); overload; virtual;
 
       {**
        * Draws an underline.
        *}
-      procedure DrawUnderline(const Text: WideString); virtual;
+      procedure DrawUnderline(const Text: UCS4String); virtual;
 
       {**
        * Renders (one) line of text.
        *}
-      procedure Render(const Text: WideString); virtual; abstract;
+      procedure Render(const Text: UCS4String); virtual; abstract;
 
       {**
        * Returns the bounds of text-lines contained in Text.
        * @param(Advance  if true the right bound is set to the advance instead
        *   of the minimal right bound.)
        *}
-      function BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; overload; virtual; abstract;
+      function BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; overload; virtual; abstract;
 
       {**
        * Resets all user settings to default values.
@@ -182,15 +190,17 @@ type
       property ReflectionPass: boolean read fReflectionPass write SetReflectionPass;
 
     public
-      constructor Create();
+      constructor Create(const Filename: IPath);
       destructor Destroy(); override;
 
       {**
        * Prints a text.
        *}
+      procedure Print(const Text: UCS4String); overload;
+      {** UTF-16 version of @link(Print) }
       procedure Print(const Text: WideString); overload;
       {** UTF-8 version of @link(Print) }
-      procedure Print(const Text: string); overload;
+      procedure Print(const Text: UTF8String); overload;
 
       {**
        * Calculates the bounding box (width and height) around Text.
@@ -203,10 +213,18 @@ type
        * bigger than the text's width as it additionally contains the advance
        * and glyph-spacing of the last character.
        *}
+      function BBox(const Text: UCS4String; Advance: boolean = true): TBoundsDbl; overload;
+      {** UTF-16 version of @link(BBox) }
       function BBox(const Text: WideString; Advance: boolean = true): TBoundsDbl; overload;
       {** UTF-8 version of @link(BBox) }
       function BBox(const Text: UTF8String; Advance: boolean = true): TBoundsDbl; overload;
 
+      {**
+       * Adds a new font that is used if the default font misses a glyph
+       * @raises EFontError  if the fallback could not be initialized
+       *}
+      procedure AddFallback(const Filename: IPath); virtual; abstract;
+
       {** Font height }
       property Height: single read GetHeight;
       {** Vertical distance from baseline to top of glyph }
@@ -223,6 +241,8 @@ type
       property Style: TFontStyle read GetStyle write SetStyle;
       {** If set to true (default) kerning will be used if available }
       property UseKerning: boolean read GetUseKerning write SetUseKerning;
+      {** Filename }
+      property Filename: IPath read fFilename;
   end;
 
 const
@@ -242,16 +262,16 @@ type
       procedure ResetIntern();
 
     protected
-      fScale: single;        //**< current height to base-font height ratio 
-      fAspect: single;       //**< width to height aspect
+      fScale: single;        //**< current height to base-font height ratio
+      fStretch: single;      //**< stretch factor for width (Width * fStretch)
       fBaseFont: TFont;      //**< shortcut for fMipmapFonts[0]
       fUseMipmaps: boolean;  //**< true if mipmap fonts are generated
       /// Mipmap fonts (size[level+1] = size[level]/2)
       fMipmapFonts: array[0..cMaxMipmapLevel] of TFont;
 
-      procedure Render(const Text: WideString); override;
-      procedure Print(const Text: TWideStringArray); override;
-      function BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; override;
+      procedure Render(const Text: UCS4String); override;
+      procedure Print(const Text: TUCS4StringArray); override;
+      function BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; override;
 
       {**
        * Callback called for creation of each mipmap font.
@@ -280,8 +300,8 @@ type
 
       procedure SetHeight(Height: single); virtual;
       function GetHeight(): single; override;
-      procedure SetAspect(Aspect: single); virtual;
-      function GetAspect(): single; virtual;
+      procedure SetStretch(Stretch: single); virtual;
+      function GetStretch(): single; virtual;
       function GetAscender(): single; override;
       function GetDescender(): single; override;
       procedure SetLineSpacing(Spacing: single); override;
@@ -316,13 +336,13 @@ type
 
       {** Font height }
       property Height: single read GetHeight write SetHeight;
-      {** Factor for font stretching (NewWidth = Width*Aspect), 1.0 by default }
-      property Aspect: single read GetAspect write SetAspect;
+      {** Factor for font stretching (NewWidth = Width*Stretch), 1.0 by default }
+      property Stretch: single read GetStretch write SetStretch;
   end;
 
   {**
    * Table for storage of max. 256 glyphs.
-   * Used for the second cache level. Indexed by the LSB of the WideChar
+   * Used for the second cache level. Indexed by the LSB of the UCS4Char
    * char-code.
    *}
   PGlyphTable = ^TGlyphTable;
@@ -332,7 +352,7 @@ type
    * Cache for glyphs of a single font.
    * The cached glyphs are stored inside a hash-list.
    * Hashing is performed in two steps:
-   * 1. the least significant byte (LSB) of the WideChar character code
+   * 1. the least significant byte (LSB) of the UCS4Char character code
    * is removed (shr 8) and the result (we call it BaseCode here) looked up in
    * the hash-list.
    * 2. Each entry of the hash-list contains a table with max. 256 entries.
@@ -359,22 +379,22 @@ type
        * Add glyph Glyph with char-code ch to the cache.
        * @returns @true on success, @false otherwise
        *}
-      function AddGlyph(ch: WideChar; const Glyph: TGlyph): boolean;
+      function AddGlyph(ch: UCS4Char; const Glyph: TGlyph): boolean;
 
       {**
        * Removes the glyph with char-code ch from the cache.
        *}
-      procedure DeleteGlyph(ch: WideChar);
+      procedure DeleteGlyph(ch: UCS4Char);
 
       {**
        * Removes the glyph with char-code ch from the cache.
        *}
-      function GetGlyph(ch: WideChar): TGlyph;
+      function GetGlyph(ch: UCS4Char): TGlyph;
 
       {**
        * Checks if a glyph with char-code ch is cached.
        *}
-      function HasGlyph(ch: WideChar): boolean;
+      function HasGlyph(ch: UCS4Char): boolean;
 
       {**
        * Remove and free all cached glyphs. If KeepBaseSet is set to
@@ -408,16 +428,16 @@ type
        * Retrieves a cached glyph with char-code ch from cache.
        * If the glyph is not already cached, it is loaded with LoadGlyph().
        *}
-      function GetGlyph(ch: WideChar): TGlyph;
+      function GetGlyph(ch: UCS4Char): TGlyph;
 
       {**
        * Callback to create (load) a glyph with char-code ch.
        * Implemented by subclasses.
        *}
-      function LoadGlyph(ch: WideChar): TGlyph; virtual; abstract;
+      function LoadGlyph(ch: UCS4Char): TGlyph; virtual; abstract;
 
     public
-      constructor Create();
+      constructor Create(const Filename: IPath);
       destructor Destroy(); override;
 
       {**
@@ -430,12 +450,56 @@ type
 
   TFTFont = class;
 
+  {**
+   * Freetype font face class.
+   *}
+  TFTFontFace = class
+    private
+      fFilename: IPath;             //**< filename of the font-file
+      fFace: FT_Face;               //**< Holds the height of the font
+      fFontUnitScale: TPositionDbl; //**< FT font-units to pixel ratio
+      fSize: integer;
+
+    public
+      {**
+       * @raises EFontError  if the glyph could not be initialized
+       *}
+      constructor Create(const Filename: IPath; Size: integer);
+      
+      destructor Destroy(); override;
+
+      property Filename: IPath read fFilename;
+      property Data: FT_Face read fFace;
+      property FontUnitScale: TPositionDbl read fFontUnitScale;
+      property Size: integer read fSize;
+  end;
+
+  {**
+   * Loading font faces with freetype is a slow process.
+   * Especially loading a font (e.g. fallback fonts) more than once is a waste
+   * of time. Just cache already loaded faces here.
+   *}
+  TFTFontFaceCache = class
+    private
+      fFaces:       array of TFTFontFace;
+      fFacesRefCnt: array of integer;      
+    public
+      {**
+       * @raises EFontError  if the font could not be initialized
+       *}
+      function LoadFace(const Filename: IPath; Size: integer): TFTFontFace;
+
+      procedure UnloadFace(Face: TFTFontFace);
+  end;
+
   {**
    * Freetype glyph.
    * Each glyph stores a texture with the glyph's image.
    *}
   TFTGlyph = class(TGlyph)
     private
+      fCharCode:  UCS4Char;     //**< Char code
+      fFace: TFTFontFace;       //**< Freetype face used for this glyph
       fCharIndex: FT_UInt;      //**< Freetype specific char-index (<> char-code)
       fDisplayList: GLuint;     //**< Display-list ID
       fTexture: GLuint;         //**< Texture ID
@@ -458,13 +522,13 @@ type
        * The bitmap must be 2* pixels wider and higher than the
        * original glyph's bitmap with the latter centered in it.
        *}
-      procedure Extrude(var TexBuffer: TGLubyteDynArray; Outset: single);
+      procedure StrokeBorder(var Glyph: FT_Glyph);
 
       {**
        * Creates an OpenGL texture (and display list) for the glyph.
        * The glyph's and bitmap's metrics are set correspondingly.
        * @param  LoadFlags  flags passed to FT_Load_Glyph()
-       * @raises Exception  if the glyph could not be initialized
+       * @raises EFontError  if the glyph could not be initialized
        *}
       procedure CreateTexture(LoadFlags: FT_Int32);
 
@@ -477,7 +541,7 @@ type
        * Creates a glyph with char-code ch from font Font.
        * @param LoadFlags  flags passed to FT_Load_Glyph()
        *}
-      constructor Create(Font: TFTFont; ch: WideChar; Outset: single;
+      constructor Create(Font: TFTFont; ch: UCS4Char; Outset: single;
                          LoadFlags: FT_Int32);
       destructor Destroy(); override;
 
@@ -488,29 +552,36 @@ type
 
       {** Freetype specific char-index (<> char-code) }
       property CharIndex: FT_UInt read fCharIndex;
+      
+      {** Freetype face used for this glyph }
+      property Face: TFTFontFace read fFace;
   end;
 
+  TFontPart = ( fpNone, fpInner, fpOutline );
+  TFTFontFaceArray = array of TFTFontFace;
+
   {**
    * Freetype font class.
    *}
   TFTFont = class(TCachedFont)
     private
       procedure ResetIntern();
+      class function GetFaceCache(): TFTFontFaceCache;
 
     protected
-      fFilename: string;            //**< filename of the font-file
+      fFace: TFTFontFace;           //**< Default font face
       fSize: integer;               //**< Font base size (in pixels)
       fOutset: single;              //**< size of outset extrusion (in pixels)
-      fFace: FT_Face;               //**< Holds the height of the font
       fLoadFlags: FT_Int32;         //**< FT glpyh load-flags
-      fFontUnitScale: TPositionDbl; //**< FT font-units to pixel ratio
       fUseDisplayLists: boolean;    //**< true: use display-lists, false: direct drawing
+      fPart: TFontPart;             //**< indicates the part of an outline font
+      fFallbackFaces: TFTFontFaceArray; //**< available fallback faces, ordered by priority
 
       {** @seealso TCachedFont.LoadGlyph }
-      function LoadGlyph(ch: WideChar): TGlyph; override;
+      function LoadGlyph(ch: UCS4Char): TGlyph; override;
 
-      procedure Render(const Text: WideString); override;
-      function BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; override;
+      procedure Render(const Text: UCS4String); override;
+      function BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; override;
 
       function GetHeight(): single; override;
       function GetAscender(): single; override;
@@ -518,17 +589,15 @@ type
       function GetUnderlinePosition(): single; override;
       function GetUnderlineThickness(): single; override;
 
-      property Face: FT_Face read fFace;
-
     public
       {**
        * Creates a font of size Size (in pixels) from the file Filename.
        * If Outset (in pixels) is set to a value > 0 the glyphs will be extruded
        * at their borders. Use it for e.g. a bold effect.
        * @param  LoadFlags  flags passed to FT_Load_Glyph()
-       * @raises Exception  if the font-file could not be loaded
+       * @raises EFontError  if the font-file could not be loaded
        *}
-      constructor Create(const Filename: string;
+      constructor Create(const Filename: IPath;
                          Size: integer; Outset: single = 0.0;
                          LoadFlags: FT_Int32 = FT_LOAD_DEFAULT);
 
@@ -539,11 +608,19 @@ type
 
       {** @seealso TFont.Reset }
       procedure Reset(); override;
-      
+
+      procedure AddFallback(const Filename: IPath); override;
+
       {** Size of the base font }
       property Size: integer read fSize;
       {** Outset size }
       property Outset: single read fOutset;
+      {** The part (inner/outline/none) this font represents in a composite font }
+      property Part: TFontPart read fPart write fPart;
+      {** Freetype face of this font }
+      property DefaultFace: TFTFontFace read fFace;
+      {** Available freetype fallback faces, ordered by priority }
+      property FallbackFaces: TFTFontFaceArray read fFallbackFaces;
   end;
 
   TFTScalableFont = class(TScalableFont)
@@ -557,11 +634,27 @@ type
        * OutsetAmount is the ratio of the glyph extrusion.
        * The extrusion in pixels is Size*OutsetAmount
        * (0.0 -> no extrusion, 0.1 -> 10%).
+       *
+       * The memory size (in bytes) consumed by a scalable font
+       * - with UseMipmaps=false:
+       *  mem = size^2 * #cached_glyphs
+       * - with UseMipmaps=true (all mipmap levels):
+       *  mem = size^2 * #cached_glyphs * Sum[i=1..cMaxMipmapLevel](1/i^2)
+       * - with UseMipmaps=true (5 <= cMaxMipmapLevel <= 10):
+       *  mem ~= size^2 * #cached_glyphs * 1.5
+       *
+       * Examples (for 128 cached glyphs):
+       * - Size: 64 pixels: 768 KB (mipmapped) or 512 KB (non-mipmapped).
+       * - Size 128 pixels: 3 MB (mipmapped) or 2 MB (non-mipmapped)
+       *
+       * Note: once a glyph is cached there will
        *}
-      constructor Create(const Filename: string;
+      constructor Create(const Filename: IPath;
                          Size: integer; OutsetAmount: single = 0.0;
                          UseMipmaps: boolean = true);
 
+      procedure AddFallback(const Filename: IPath); override;
+                         
       {** @seealso TGlyphCache.FlushCache }
       procedure FlushCache(KeepBaseSet: boolean);
 
@@ -576,7 +669,6 @@ type
    *}
   TFTOutlineFont = class(TFont)
     private
-      fFilename: string;
       fSize: integer;
       fOutset: single;
       fInnerFont, fOutlineFont: TFTFont;
@@ -585,9 +677,9 @@ type
       procedure ResetIntern();
       
   protected
-      procedure DrawUnderline(const Text: WideString); override;
-      procedure Render(const Text: WideString); override;
-      function BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; override;
+      procedure DrawUnderline(const Text: UCS4String); override;
+      procedure Render(const Text: UCS4String); override;
+      function BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; override;
 
       function GetHeight(): single; override;
       function GetAscender(): single; override;
@@ -603,7 +695,7 @@ type
       procedure SetReflectionPass(Enable: boolean); override;
 
     public
-      constructor Create(const Filename: string;
+      constructor Create(const Filename: IPath;
                          Size: integer; Outset: single;
                          LoadFlags: FT_Int32 = FT_LOAD_DEFAULT);
       destructor Destroy; override;
@@ -618,6 +710,8 @@ type
       {** @seealso TGlyphCache.FlushCache }
       procedure FlushCache(KeepBaseSet: boolean);
 
+      procedure AddFallback(const Filename: IPath); override;
+      
       {** @seealso TFont.Reset }
       procedure Reset(); override;
 
@@ -637,7 +731,7 @@ type
       function CreateMipmap(Level: integer; Scale: single): TFont; override;
 
     public
-      constructor Create(const Filename: string;
+      constructor Create(const Filename: IPath;
                          Size: integer; OutsetAmount: single;
                          UseMipmaps: boolean = true);
 
@@ -647,6 +741,8 @@ type
       {** @seealso TGlyphCache.FlushCache }
       procedure FlushCache(KeepBaseSet: boolean);
 
+      procedure AddFallback(const Filename: IPath); override;
+      
       {** Outset size }
       property Outset: single read GetOutset;
   end;
@@ -672,18 +768,18 @@ type
 
       procedure ResetIntern();
 
-      procedure RenderChar(ch: WideChar; var AdvanceX: real);
+      procedure RenderChar(ch: UCS4Char; var AdvanceX: real);
 
       {**
        * Load font widths from an info file.
        * @param  InfoFile  the name of the info (.dat) file
-       * @raises Exception if the file is corrupted
+       * @raises EFontError if the file is corrupted
        *}
-      procedure LoadFontInfo(const InfoFile: string);
+      procedure LoadFontInfo(const InfoFile: IPath);
 
     protected
-      procedure Render(const Text: WideString); override;
-      function BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; override;
+      procedure Render(const Text: UCS4String); override;
+      function BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; override;
 
       function GetHeight(): single; override;
       function GetAscender(): single; override;
@@ -699,7 +795,7 @@ type
        *        (y-axis up) and from the lower edge of the glyphs bounding box)
        * @param(Ascender  pixels from baseline to top of highest glyph)
        *}
-      constructor Create(const Filename: string; Outline: integer;
+      constructor Create(const Filename: IPath; Outline: integer;
                          Baseline, Ascender, Descender: integer);
       destructor Destroy(); override;
 
@@ -711,6 +807,8 @@ type
 
       {** @seealso TFont.Reset }
       procedure Reset(); override;
+
+      procedure AddFallback(const Filename: IPath); override;
   end;
 
 {$ENDIF BITMAP_FONT}
@@ -720,7 +818,7 @@ type
       {**
        * Returns a pointer to the freetype library singleton.
        * If non exists, freetype will be initialized.
-       * @raises Exception if initialization failed
+       * @raises EFontError if initialization failed
        *}
       class function GetLibrary(): FT_Library;
       class procedure FreeLibrary();
@@ -773,9 +871,10 @@ end;
  * TFont
  *}
 
-constructor TFont.Create();
+constructor TFont.Create(const Filename: IPath);
 begin
-  inherited;
+  inherited Create();
+  fFilename := Filename;
   ResetIntern();
 end;
 
@@ -801,37 +900,61 @@ begin
   ResetIntern();
 end;
 
-procedure TFont.SplitLines(const Text: UTF8String; var Lines: TWideStringArray);
+procedure TFont.SplitLines(const Text: UCS4String; var Lines: TUCS4StringArray);
 var
-  LineList: TStringList;
-  LineIndex: integer;
+  CharIndex: integer;
+  LineStart: integer;
+  LineLength: integer;
+  EOT: boolean; // End-Of-Text
 begin
-  // split lines on newline (there is no WideString version of ExtractStrings)
-  LineList := TStringList.Create();
-  ExtractStrings([#13], [], PChar(Text), LineList);
+  // split lines on newline
+  SetLength(Lines, 0);
+  EOT := false;
+  LineStart := 0;
 
-  // create an array of WideStrins from the UTF-8 string-list
-  SetLength(Lines, LineList.Count);
-  for LineIndex := 0 to LineList.Count-1 do
-    Lines[LineIndex] := UTF8Decode(LineList[LineIndex]);
-  LineList.Free();
+  for CharIndex := 0 to High(Text) do
+  begin
+    // check for end of text (UCS4Strings are zero-terminated)
+    if (CharIndex = High(Text)) then
+      EOT := true;
+
+    // check for newline (carriage return (#13)) or end of text
+    if (Text[CharIndex] = 13) or EOT then
+    begin
+      LineLength := CharIndex - LineStart;
+      // check if last character was a newline
+      if (EOT and (LineLength = 0)) then
+        Break;      
+
+      // copy line (even if LineLength is 0)
+      SetLength(Lines, Length(Lines)+1);
+      Lines[High(Lines)] := UCS4Copy(Text, LineStart, LineLength);
+
+      LineStart := CharIndex+1;
+    end;
+  end;
 end;
 
-function TFont.BBox(const Text: UTF8String; Advance: boolean): TBoundsDbl;
+function TFont.BBox(const Text: UCS4String; Advance: boolean): TBoundsDbl;
 var
-  LineArray: TWideStringArray;
+  LineArray: TUCS4StringArray;
 begin
   SplitLines(Text, LineArray);
   Result := BBox(LineArray, Advance);
   SetLength(LineArray, 0);
 end;
 
+function TFont.BBox(const Text: UTF8String; Advance: boolean): TBoundsDbl;
+begin
+  Result := BBox(UTF8Decode(Text), Advance);
+end;
+
 function TFont.BBox(const Text: WideString; Advance: boolean): TBoundsDbl;
 begin
-  Result := BBox(UTF8Encode(Text), Advance);
+  Result := BBox(WideStringToUCS4String(Text), Advance);
 end;
 
-procedure TFont.Print(const Text: TWideStringArray);
+procedure TFont.Print(const Text: TUCS4StringArray);
 var
   LineIndex: integer;
 begin
@@ -912,21 +1035,26 @@ begin
   glPopAttrib();
 end;
 
-procedure TFont.Print(const Text: string);
+procedure TFont.Print(const Text: UCS4String);
 var
-  LineArray: TWideStringArray;
+  LineArray: TUCS4StringArray;
 begin
   SplitLines(Text, LineArray);
   Print(LineArray);
   SetLength(LineArray, 0);
 end;
 
+procedure TFont.Print(const Text: UTF8String);
+begin
+  Print(UTF8Decode(Text));
+end;
+
 procedure TFont.Print(const Text: WideString);
 begin
-  Print(UTF8Encode(Text));
+  Print(WideStringToUCS4String(Text));
 end;
 
-procedure TFont.DrawUnderline(const Text: WideString);
+procedure TFont.DrawUnderline(const Text: UCS4String);
 var
   UnderlineY1, UnderlineY2: single;
   Bounds: TBoundsDbl;
@@ -1001,7 +1129,7 @@ constructor TScalableFont.Create(Font: TFont; UseMipmaps: boolean);
 var
   MipmapLevel: integer;
 begin
-  inherited Create();
+  inherited Create(Font.Filename);
   
   fBaseFont := Font;
   fMipmapFonts[0] := Font;
@@ -1033,7 +1161,7 @@ end;
 procedure TScalableFont.ResetIntern();
 begin
   fScale := 1.0;
-  fAspect := 1.0;
+  fStretch := 1.0;
 end;
 
 procedure TScalableFont.Reset();
@@ -1049,7 +1177,7 @@ end;
 
 {**
  * Returns the mipmap level to use with regard to the current projection
- * and modelview matrix, font scale and aspect.
+ * and modelview matrix, font scale and stretch.
  *
  * Note:
  * - for Freetype fonts, hinting and grid-fitting must be disabled, otherwise
@@ -1088,7 +1216,7 @@ var
   ModelMatrix, ProjMatrix: T16dArray;
   WinCoords: array[0..2, 0..2] of GLdouble;
   ViewPortArray: TViewPortArray;
-  Dist, Dist2: double;
+  Dist, Dist2, DistSum: double;
   WidthScale, HeightScale: double;
 const
   // width/height of square used for determining the scale
@@ -1128,12 +1256,24 @@ begin
   // projected width ||(x1, y1) - (x2, y1)||
   Dist  := (WinCoords[0][0] - WinCoords[1][0]);
   Dist2 := (WinCoords[0][1] - WinCoords[1][1]);
-  WidthScale := cTestSize / Sqrt(Dist*Dist + Dist2*Dist2);
+
+  WidthScale := 1;
+  DistSum := Dist*Dist + Dist2*Dist2;
+  if (DistSum > 0) then
+  begin
+    WidthScale := cTestSize / Sqrt(DistSum);
+  end;
 
   // projected height ||(x1, y1) - (x1, y2)||
   Dist  := (WinCoords[0][0] - WinCoords[2][0]);
   Dist2 := (WinCoords[0][1] - WinCoords[2][1]);
-  HeightScale := cTestSize / Sqrt(Dist*Dist + Dist2*Dist2);
+
+  HeightScale := 1;
+  DistSum := Dist*Dist + Dist2*Dist2;
+  if (DistSum > 0) then
+  begin
+    HeightScale := cTestSize / Sqrt(DistSum);
+  end;
 
   //writeln(Format('Scale %f, %f', [WidthScale, HeightScale]));
 
@@ -1194,12 +1334,12 @@ begin
   glScalef(MipmapScale, MipmapScale, 0);
 end;
 
-procedure TScalableFont.Print(const Text: TWideStringArray);
+procedure TScalableFont.Print(const Text: TUCS4StringArray);
 begin
   glPushMatrix();
 
   // set scale and stretching
-  glScalef(fScale * fAspect, fScale, 0);
+  glScalef(fScale * fStretch, fScale, 0);
 
   // print text
   if (fUseMipmaps) then
@@ -1210,16 +1350,16 @@ begin
   glPopMatrix();
 end;
 
-procedure TScalableFont.Render(const Text: WideString);
+procedure TScalableFont.Render(const Text: UCS4String);
 begin
   Assert(false, 'Unused TScalableFont.Render() was called');
 end;
 
-function TScalableFont.BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl;
+function TScalableFont.BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl;
 begin
   Result := fBaseFont.BBox(Text, Advance);
-  Result.Left   := Result.Left * fScale * fAspect;
-  Result.Right  := Result.Right * fScale * fAspect;
+  Result.Left   := Result.Left * fScale * fStretch;
+  Result.Right  := Result.Right * fScale * fStretch;
   Result.Top    := Result.Top * fScale;
   Result.Bottom := Result.Bottom * fScale;
 end;
@@ -1234,14 +1374,14 @@ begin
   Result := fBaseFont.GetHeight() * fScale;
 end;
 
-procedure TScalableFont.SetAspect(Aspect: single);
+procedure TScalableFont.SetStretch(Stretch: single);
 begin
-  fAspect := Aspect;
+  fStretch := Stretch;
 end;
 
-function TScalableFont.GetAspect(): single;
+function TScalableFont.GetStretch(): single;
 begin
-  Result := fAspect;
+  Result := fStretch;
 end;
 
 function TScalableFont.GetAscender(): single;
@@ -1287,7 +1427,7 @@ var
   Level: integer;
 begin
   for Level := 0 to High(fMipmapFonts) do
-    if (fMipmapFonts[Level] <> nil) then
+    if ((fMipmapFonts[Level] <> nil) AND (GetMipmapScale(Level) > 0)) then
       fMipmapFonts[Level].SetReflectionSpacing(Spacing / GetMipmapScale(Level));
 end;
 
@@ -1334,9 +1474,9 @@ end;
  * TCachedFont
  *}
 
-constructor TCachedFont.Create();
+constructor TCachedFont.Create(const Filename: IPath);
 begin
-  inherited;
+  inherited Create(Filename);
   fCache := TGlyphCache.Create();
 end;
 
@@ -1346,7 +1486,7 @@ begin
   inherited;
 end;
 
-function TCachedFont.GetGlyph(ch: WideChar): TGlyph;
+function TCachedFont.GetGlyph(ch: UCS4Char): TGlyph;
 begin
   Result := fCache.GetGlyph(ch);
   if (Result = nil) then
@@ -1362,60 +1502,155 @@ begin
   fCache.FlushCache(KeepBaseSet);
 end;
 
+{*
+ * TFTFontFaceCache
+ *}
 
 {*
- * TFTFont
+ * TFTFontFace
  *}
 
-constructor TFTFont.Create(
-    const Filename: string;
-    Size: integer; Outset: single;
-    LoadFlags: FT_Int32);
-var
-  i: WideChar;
+constructor TFTFontFace.Create(const Filename: IPath; Size: integer);
 begin
   inherited Create();
 
   fFilename := Filename;
   fSize := Size;
-  fOutset := Outset;
-  fLoadFlags := LoadFlags;
-  fUseDisplayLists := true;
 
   // load font information
-  if (FT_New_Face(TFreeType.GetLibrary(), PChar(Filename), 0, fFace) <> 0) then
-    raise Exception.Create('FT_New_Face: Could not load font '''  + Filename + '''');
+  if (FT_New_Face(TFreeType.GetLibrary(), PChar(Filename.ToNative), 0, fFace) <> 0) then
+    raise EFontError.Create('FT_New_Face: Could not load font '''  + Filename.ToNative + '''');
 
   // support scalable fonts only
   if (not FT_IS_SCALABLE(fFace)) then
-    raise Exception.Create('Font is not scalable');
+    raise EFontError.Create('Font is not scalable');
 
   if (FT_Set_Pixel_Sizes(fFace, 0, Size) <> 0) then
-    raise Exception.Create('FT_Set_Pixel_Sizes failes');
+    raise EFontError.Create('FT_Set_Pixel_Sizes failes');
 
   // get scale factor for font-unit to pixel-size transformation
   fFontUnitScale.X := fFace.size.metrics.x_ppem / fFace.units_per_EM;
   fFontUnitScale.Y := fFace.size.metrics.y_ppem / fFace.units_per_EM;
+end;
+
+destructor TFTFontFace.Destroy();
+begin
+  // free face data
+  FT_Done_Face(fFace);
+  inherited;
+end;
+
+
+{*
+ * TFTFontFaceCache
+ *}
+
+function TFTFontFaceCache.LoadFace(const Filename: IPath; Size: integer): TFTFontFace;
+var
+  I: Integer;
+  Face: TFTFontFace;
+begin
+  {$IFDEF ENABLE_FT_FACE_CACHE}
+  for I := 0 to High(fFaces) do
+  begin
+    Face := fFaces[I];
+    // check if we have this file in our cache
+    if ((Face.Filename.Equals(Filename)) and (Face.Size = Size)) then
+    begin
+      // true -> return cached face and increment ref-count
+      Inc(fFacesRefCnt[I]);
+      Result := Face;
+      Exit;
+    end;
+  end;
+  {$ENDIF}
+
+  // face not in cache -> load it
+  Face := TFTFontFace.Create(Filename, Size);
+
+  // add face to cache
+  SetLength(fFaces, Length(fFaces)+1);
+  SetLength(fFacesRefCnt, Length(fFaces)+1);
+  fFaces[High(fFaces)] := Face;
+  fFacesRefCnt[High(fFaces)] := 1;
+
+  Result := Face;
+end;
+
+procedure TFTFontFaceCache.UnloadFace(Face: TFTFontFace);
+var
+  I: Integer;
+begin
+  for I := 0 to High(fFaces) do
+  begin
+    // search face in cache
+    if (fFaces[I] = Face) then
+    begin
+      // decrement ref-count and free face if ref-count is 0
+      Dec(fFacesRefCnt[I]);
+      if (fFacesRefCnt[I] <= 0) then
+        fFaces[I].Free;
+      Exit;
+    end;
+  end;
+end;
+
+
+{*
+ * TFTFont
+ *}
+
+constructor TFTFont.Create(
+    const Filename: IPath;
+    Size: integer; Outset: single;
+    LoadFlags: FT_Int32);
+var
+  ch: UCS4Char;
+begin
+  inherited Create(Filename);
+
+  fSize := Size;
+  fOutset := Outset;
+  fLoadFlags := LoadFlags;
+  fUseDisplayLists := true;
+  fPart := fpNone;
+
+  fFace := GetFaceCache.LoadFace(Filename, Size);
 
   ResetIntern();
 
   // pre-cache some commonly used glyphs (' ' - '~')
-  for i := #32 to #126 do
-    fCache.AddGlyph(i, TFTGlyph.Create(Self, i, Outset, LoadFlags));
+  for ch := 32 to 126 do
+    fCache.AddGlyph(ch, TFTGlyph.Create(Self, ch, Outset, LoadFlags));
 end;
 
 destructor TFTFont.Destroy();
+var
+  I: integer;
 begin
-  // free face
-  FT_Done_Face(fFace);
+  // free faces
+  GetFaceCache.UnloadFace(fFace);
+  for I := 0 to High(fFallbackFaces) do
+    GetFaceCache.UnloadFace(fFallbackFaces[I]);    
+
   inherited;
 end;
 
+var
+  FontFaceCache: TFTFontFaceCache = nil;
+
+class function TFTFont.GetFaceCache(): TFTFontFaceCache;
+begin
+  if (FontFaceCache = nil) then
+    FontFaceCache := TFTFontFaceCache.Create;
+  Result := FontFaceCache;
+end;
+
 procedure TFTFont.ResetIntern();
 begin
   // Note: outset and non outset fonts use same spacing
-  fLineSpacing := fFace.height * fFontUnitScale.Y;
-  fReflectionSpacing := -2*fFace.descender * fFontUnitScale.Y;
+  fLineSpacing := fFace.Data.height * fFace.FontUnitScale.Y;
+  fReflectionSpacing := -2*fFace.Data.descender * fFace.FontUnitScale.Y;
 end;
 
 procedure TFTFont.Reset();
@@ -1424,15 +1659,24 @@ begin
   ResetIntern();
 end;
 
-function TFTFont.LoadGlyph(ch: WideChar): TGlyph;
+procedure TFTFont.AddFallback(const Filename: IPath);
+var
+  FontFace: TFTFontFace;
+begin
+  FontFace := GetFaceCache.LoadFace(Filename, Size);
+  SetLength(fFallbackFaces, Length(fFallbackFaces) + 1);
+  fFallbackFaces[High(fFallbackFaces)] := FontFace;
+end;
+
+function TFTFont.LoadGlyph(ch: UCS4Char): TGlyph;
 begin
   Result := TFTGlyph.Create(Self, ch, Outset, fLoadFlags);
 end;
 
-function TFTFont.BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl;
+function TFTFont.BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl;
 var
   Glyph, PrevGlyph: TFTGlyph;
-  TextLine: WideString;
+  TextLine: UCS4String;
   LineYOffset: single;
   LineIndex, CharIndex: integer;
   LineBounds: TBoundsDbl;
@@ -1462,17 +1706,17 @@ begin
     LineBounds.Top    := 0;
 
     // for each glyph image, compute its bounding box
-    for CharIndex := 1 to Length(TextLine) do
+    for CharIndex := 0 to LengthUCS4(TextLine)-1 do
     begin
       Glyph := TFTGlyph(GetGlyph(TextLine[CharIndex]));
       if (Glyph <> nil) then
       begin
         // get kerning
-        if (fUseKerning and FT_HAS_KERNING(fFace) and (PrevGlyph <> nil)) then
+        if (fUseKerning and FT_HAS_KERNING(fFace.Data) and (PrevGlyph <> nil)) then
         begin
-          FT_Get_Kerning(fFace, PrevGlyph.CharIndex, Glyph.CharIndex,
+          FT_Get_Kerning(fFace.Data, PrevGlyph.CharIndex, Glyph.CharIndex,
                          FT_KERNING_UNSCALED, KernDelta);
-          LineBounds.Right := LineBounds.Right + KernDelta.x * fFontUnitScale.X;
+          LineBounds.Right := LineBounds.Right + KernDelta.x * fFace.FontUnitScale.X;
         end;
 
         // update left bound (must be done before right bound is updated)
@@ -1480,9 +1724,9 @@ begin
           LineBounds.Left := LineBounds.Right + Glyph.Bounds.Left;
 
         // update right bound
-        if (CharIndex < Length(TextLine)) or  // not the last character
-           (TextLine[CharIndex] = ' ') or     // on space char (Bounds.Right = 0)
-           Advance then                       // or in advance mode
+        if (CharIndex < LengthUCS4(TextLine)-1) or  // not the last character
+           (TextLine[CharIndex] = Ord(' ')) or      // on space char (Bounds.Right = 0)
+           Advance then                             // or in advance mode
         begin
           // add advance and glyph spacing
           LineBounds.Right := LineBounds.Right + Glyph.Advance.x + GlyphSpacing
@@ -1534,13 +1778,13 @@ begin
   end;
 
   // if left or bottom bound was not set, set them to 0
-  if (Result.Left = Infinity) then
+  if (IsInfinite(Result.Left)) then
     Result.Left := 0.0;
-  if (Result.Bottom = Infinity) then
+  if (IsInfinite(Result.Bottom)) then
     Result.Bottom := 0.0;
 end;
 
-procedure TFTFont.Render(const Text: WideString);
+procedure TFTFont.Render(const Text: UCS4String);
 var
   CharIndex: integer;
   Glyph, PrevGlyph: TFTGlyph;
@@ -1550,17 +1794,17 @@ begin
   PrevGlyph := nil;
 
   // draw current line
-  for CharIndex := 1 to Length(Text) do
+  for CharIndex := 0 to LengthUCS4(Text)-1 do
   begin
     Glyph := TFTGlyph(GetGlyph(Text[CharIndex]));
     if (Assigned(Glyph)) then
     begin
       // get kerning
-      if (fUseKerning and FT_HAS_KERNING(fFace) and (PrevGlyph <> nil)) then
+      if (fUseKerning and FT_HAS_KERNING(fFace.Data) and (PrevGlyph <> nil)) then
       begin
-        FT_Get_Kerning(fFace, PrevGlyph.CharIndex, Glyph.CharIndex,
+        FT_Get_Kerning(fFace.Data, PrevGlyph.CharIndex, Glyph.CharIndex,
                        FT_KERNING_UNSCALED, KernDelta);
-        glTranslatef(KernDelta.x * fFontUnitScale.X, 0, 0);
+        glTranslatef(KernDelta.x * fFace.FontUnitScale.X, 0, 0);
       end;
 
       if (ReflectionPass) then
@@ -1582,23 +1826,23 @@ end;
 
 function TFTFont.GetAscender(): single;
 begin
-  Result := fFace.ascender * fFontUnitScale.Y + Outset*2;
+  Result := fFace.Data.ascender * fFace.FontUnitScale.Y + Outset*2;
 end;
 
 function TFTFont.GetDescender(): single;
 begin
   // Note: outset is not part of the descender as the baseline is lifted
-  Result := fFace.descender * fFontUnitScale.Y;
+  Result := fFace.Data.descender * fFace.FontUnitScale.Y;
 end;
 
 function TFTFont.GetUnderlinePosition(): single;
 begin
-  Result := fFace.underline_position * fFontUnitScale.Y - Outset;
+  Result := fFace.Data.underline_position * fFace.FontUnitScale.Y - Outset;
 end;
 
 function TFTFont.GetUnderlineThickness(): single;
 begin
-  Result := fFace.underline_thickness * fFontUnitScale.Y + Outset*2;
+  Result := fFace.Data.underline_thickness * fFace.FontUnitScale.Y + Outset*2;
 end;
 
 
@@ -1606,7 +1850,7 @@ end;
  * TFTScalableFont
  *}
 
-constructor TFTScalableFont.Create(const Filename: string;
+constructor TFTScalableFont.Create(const Filename: IPath;
                    Size: integer; OutsetAmount: single;
                    UseMipmaps: boolean);
 var
@@ -1637,8 +1881,8 @@ begin
   // do not create mipmap fonts < 8 pixels
   if (ScaledSize < 8) then
     Exit;
-  Result := TFTFont.Create(BaseFont.fFilename,
-      ScaledSize, BaseFont.fOutset * Scale,
+  Result := TFTFont.Create(BaseFont.Filename,
+      ScaledSize, BaseFont.Outset * Scale,
       FT_LOAD_DEFAULT or FT_LOAD_NO_HINTING);
 end;
 
@@ -1647,6 +1891,15 @@ begin
   Result := TFTFont(fBaseFont).Outset * fScale;
 end;
 
+procedure TFTScalableFont.AddFallback(const Filename: IPath);
+var
+  Level: integer;
+begin
+  for Level := 0 to High(fMipmapFonts) do
+    if (fMipmapFonts[Level] <> nil) then
+      TFTFont(fMipmapFonts[Level]).AddFallback(Filename);
+end;
+
 procedure TFTScalableFont.FlushCache(KeepBaseSet: boolean);
 var
   Level: integer;
@@ -1662,18 +1915,19 @@ end;
  *}
 
 constructor TFTOutlineFont.Create(
-    const Filename: string;
+    const Filename: IPath;
     Size: integer; Outset: single;
     LoadFlags: FT_Int32);
 begin
-  inherited Create();
+  inherited Create(Filename);
 
-  fFilename := Filename;
   fSize := Size;
   fOutset := Outset;
 
   fInnerFont := TFTFont.Create(Filename, Size, 0.0, LoadFlags);
+  fInnerFont.Part := fpInner;
   fOutlineFont := TFTFont.Create(Filename, Size, Outset, LoadFlags);
+  fOutlineFont.Part := fpOutline;
 
   ResetIntern();
 end;
@@ -1705,7 +1959,7 @@ begin
   ResetIntern();
 end;
 
-procedure TFTOutlineFont.DrawUnderline(const Text: WideString);
+procedure TFTOutlineFont.DrawUnderline(const Text: UCS4String);
 var
   CurrentColor: TGLColor;
   OutlineColor: TGLColor;
@@ -1730,7 +1984,7 @@ begin
   glPopMatrix();
 end;
 
-procedure TFTOutlineFont.Render(const Text: WideString);
+procedure TFTOutlineFont.Render(const Text: UCS4String);
 var
   CurrentColor: TGLColor;
   OutlineColor: TGLColor;
@@ -1770,7 +2024,13 @@ begin
   fInnerFont.FlushCache(KeepBaseSet);
 end;
 
-function TFTOutlineFont.BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl;
+procedure TFTOutlineFont.AddFallback(const Filename: IPath);
+begin
+  fOutlineFont.AddFallback(Filename);
+  fInnerFont.AddFallback(Filename);
+end;
+
+function TFTOutlineFont.BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl;
 begin
   Result := fOutlineFont.BBox(Text, Advance);
 end;
@@ -1852,7 +2112,7 @@ end;
  *}
 
 constructor TFTScalableOutlineFont.Create(
-    const Filename: string;
+    const Filename: IPath;
     Size: integer; OutsetAmount: single;
     UseMipmaps: boolean);
 var
@@ -1906,6 +2166,15 @@ begin
       TFTOutlineFont(fMipmapFonts[Level]).FlushCache(KeepBaseSet);
 end;
 
+procedure TFTScalableOutlineFont.AddFallback(const Filename: IPath);
+var
+  Level: integer;
+begin
+  for Level := 0 to High(fMipmapFonts) do
+    if (fMipmapFonts[Level] <> nil) then
+      TFTOutlineFont(fMipmapFonts[Level]).AddFallback(Filename);
+end;
+
 
 {*
  * TFTGlyph
@@ -1935,82 +2204,119 @@ const
    *}
   cTexSmoothBorder = 1;
 
-procedure TFTGlyph.Extrude(var TexBuffer: TGLubyteDynArray; Outset: single);
+procedure TFTGlyph.StrokeBorder(var Glyph: FT_Glyph);
+var
+  Outline: PFT_Outline;
+  OuterStroker, InnerStroker:  FT_Stroker;
+  OuterNumPoints, InnerNumPoints, GlyphNumPoints: FT_UInt;
+  OuterNumContours, InnerNumContours, GlyphNumContours: FT_UInt;
+  OuterBorder, InnerBorder: FT_StrokerBorder;
+  OutlineFlags: FT_Int;
+  UseStencil: boolean;
+begin
+  // It is possible to extrude the borders of a glyph with FT_Glyph_Stroke
+  // but it will extrude the border to the outside and the inside of a glyph
+  // although we just want to extrude to the outside.
+  // FT_Glyph_StrokeBorder extrudes to the outside but also fills the interior
+  // (this is what we need for bold fonts).
+  // In both cases the inner font and outline font (border) will overlap.
+  // Normally this does not matter but it does if alpha blending is active.
+  // In this case if e.g. the inner color is set to white, the outline to red
+  // and alpha to 0.5 the inner part will not be white it will be pink.
+
+  InnerStroker := nil;
+  OuterStroker := nil;
+
+  // If we are to create the interior of an outlined font (fInner = true)
+  // we have to create two borders:
+  // - one extruded to the outside by fOutset pixels and
+  // - one extruded to the inside by almost 0 zero pixels.
+  // The second one is used as a stencil for the first one, clearing the
+  // interiour of the glyph.
+  // The stencil is not needed to create bold fonts.
+  UseStencil := (fFont.Part = fpInner);
+
+  // we cannot extrude bitmaps, only vector based glyphs.
+  // Check for FT_GLYPH_FORMAT_OUTLINE otherwise a cast to FT_OutlineGlyph is
+  // invalid and FT_Stroker_ParseOutline() will crash
+  if (Glyph.format <> FT_GLYPH_FORMAT_OUTLINE) then
+    Exit;
+
+  Outline := @FT_OutlineGlyph(Glyph).outline;
+  
+  OuterBorder := FT_Outline_GetOutsideBorder(Outline);
+  if (OuterBorder = FT_STROKER_BORDER_LEFT) then
+    InnerBorder := FT_STROKER_BORDER_RIGHT
+  else
+    InnerBorder := FT_STROKER_BORDER_LEFT;
+
+  { extrude outer border }
+
+  if (FT_Stroker_New(Glyph.library_, OuterStroker) <> 0) then
+    raise EFontError.Create('FT_Stroker_New failed!');
+  FT_Stroker_Set(
+      OuterStroker,
+      Round(fOutset * 64),
+      FT_STROKER_LINECAP_ROUND,
+      FT_STROKER_LINEJOIN_BEVEL,
+      0);
+
+  // similar to FT_Glyph_StrokeBorder(inner = FT_FALSE) but it is possible to
+  // use FT_Stroker_ExportBorder() afterwards to combine inner and outer borders
+  if (FT_Stroker_ParseOutline(OuterStroker, Outline, FT_FALSE) <> 0) then
+    raise EFontError.Create('FT_Stroker_ParseOutline failed!');
 
-  procedure SetToMax(var Val1: GLubyte; Val2: GLubyte); {$IFDEF HasInline}inline;{$ENDIF}
+  FT_Stroker_GetBorderCounts(OuterStroker, OuterBorder, OuterNumPoints, OuterNumContours);
+
+  { extrude inner border (= stencil) }
+
+  if (UseStencil) then
   begin
-    if (Val1 < Val2) then
-      Val1 := Val2;
+    if (FT_Stroker_New(Glyph.library_, InnerStroker) <> 0) then
+      raise EFontError.Create('FT_Stroker_New failed!');
+    FT_Stroker_Set(
+        InnerStroker,
+        63, // extrude at most one pixel to avoid a black border
+        FT_STROKER_LINECAP_ROUND,
+        FT_STROKER_LINEJOIN_BEVEL,
+        0);
+
+    if (FT_Stroker_ParseOutline(InnerStroker, Outline, FT_FALSE) <> 0) then
+      raise EFontError.Create('FT_Stroker_ParseOutline failed!');
+
+    FT_Stroker_GetBorderCounts(InnerStroker, InnerBorder, InnerNumPoints, InnerNumContours);
+  end else begin
+    InnerNumPoints := 0;
+    InnerNumContours := 0;
   end;
 
-var
-  I, X, Y: integer;
-  SrcBuffer,TmpBuffer: TGLubyteDynArray;
-  TexLine, TexLinePrev, TexLineNext: PGLubyteArray;
-  SrcLine: PGLubyteArray;
-  AlphaScale: single;
-  Value, ValueNeigh, ValueDiag: GLubyte;
-const
-  // square-root of 2 used for diagonal neighbor pixels
-  cSqrt2 = 1.4142;
-  // number of ignored pixels on each edge of the bitmap. Consists of:
-  // - border used for font smoothing and
-  // - outer (extruded) bitmap pixel (because it is just written but never read)
-  cBorder = cTexSmoothBorder + 1;
-begin
-  // allocate memory for temporary buffer
-  SetLength(SrcBuffer, Length(TexBuffer));
-  FillChar(SrcBuffer[0], Length(TexBuffer), 0);
-
-  // extrude pixel by pixel
-  for I := 1 to Ceil(Outset) do
-  begin
-    // swap arrays
-    TmpBuffer := TexBuffer;
-    TexBuffer := SrcBuffer;
-    SrcBuffer := TmpBuffer;
-
-    // as long as we add an entire pixel of outset, use a solid color.
-    // If the fractional part is reached blend, e.g. outline=3.2 -> 3 solid
-    // pixels and one blended with alpha=0.2.
-    // For the fractional part I = Ceil(Outset) is always true.
-    if (I <= Outset) then
-      AlphaScale := 1
-    else
-      AlphaScale := Outset - Trunc(Outset);
-
-    // copy data to the expanded bitmap.
-    for Y := cBorder to fTexSize.Height - 2*cBorder do
-    begin
-      TexLine     := @TexBuffer[Y*fTexSize.Width];
-      TexLinePrev := @TexBuffer[(Y-1)*fTexSize.Width];
-      TexLineNext := @TexBuffer[(Y+1)*fTexSize.Width];
-      SrcLine     := @SrcBuffer[Y*fTexSize.Width];
+  { combine borders (subtract: OuterBorder - InnerBorder) }
 
-      // expand current line's pixels
-      for X := cBorder to fTexSize.Width - 2*cBorder do
-      begin
-        Value := SrcLine[X];
-        ValueNeigh := Round(Value * AlphaScale);
-        ValueDiag := Round(ValueNeigh / cSqrt2);
+  GlyphNumPoints := InnerNumPoints + OuterNumPoints;
+  GlyphNumContours := InnerNumContours + OuterNumContours;
 
-        SetToMax(TexLine[X],   Value);
-        SetToMax(TexLine[X-1], ValueNeigh);
-        SetToMax(TexLine[X+1], ValueNeigh);
+  // save flags before deletion (TODO: set them on the resulting outline)
+  OutlineFlags := Outline.flags;
 
-        SetToMax(TexLinePrev[X],   ValueNeigh);
-        SetToMax(TexLinePrev[X-1], ValueDiag);
-        SetToMax(TexLinePrev[X+1], ValueDiag);
+  // resize glyph outline to hold inner and outer border
+  FT_Outline_Done(Glyph.Library_, Outline);
+  if (FT_Outline_New(Glyph.Library_, GlyphNumPoints, GlyphNumContours, Outline) <> 0) then
+    raise EFontError.Create('FT_Outline_New failed!');
 
-        SetToMax(TexLineNext[X],   ValueNeigh);
-        SetToMax(TexLineNext[X-1], ValueDiag);
-        SetToMax(TexLineNext[X+1], ValueDiag);
-      end;
-    end;
-  end;
+  Outline.n_points := 0;
+  Outline.n_contours := 0;
 
-  TmpBuffer := nil;
-  SetLength(SrcBuffer, 0);
+  // add points to outline. The inner-border is used as a stencil.
+  FT_Stroker_ExportBorder(OuterStroker, OuterBorder, Outline);
+  if (UseStencil) then
+    FT_Stroker_ExportBorder(InnerStroker, InnerBorder, Outline);
+  if (FT_Outline_Check(outline) <> 0) then
+    raise EFontError.Create('FT_Stroker_ExportBorder failed!');
+
+  if (InnerStroker <> nil) then
+    FT_Stroker_Done(InnerStroker);
+  if (OuterStroker <> nil) then
+    FT_Stroker_Done(OuterStroker);
 end;
 
 procedure TFTGlyph.CreateTexture(LoadFlags: FT_Int32);
@@ -2025,17 +2331,26 @@ var
   TexLine:       PGLubyteArray;
   CBox:          FT_BBox;
 begin
+  // we need vector data for outlined glyphs so do not load bitmaps.
+  // This is necessary for mixed fonts that contain bitmap versions of smaller
+  // glyphs, for example in CJK fonts.
+  if (fOutset > 0) then
+    LoadFlags := LoadFlags or FT_LOAD_NO_BITMAP;
+
   // load the Glyph for our character
-  if (FT_Load_Glyph(fFont.Face, fCharIndex, LoadFlags) <> 0) then
-    raise Exception.Create('FT_Load_Glyph failed');
+  if (FT_Load_Glyph(fFace.Data, fCharIndex, LoadFlags) <> 0) then
+    raise EFontError.Create('FT_Load_Glyph failed');
 
   // move the face's glyph into a Glyph object
-  if (FT_Get_Glyph(fFont.Face^.glyph, Glyph) <> 0) then
-    raise Exception.Create('FT_Get_Glyph failed');
+  if (FT_Get_Glyph(fFace.Data^.glyph, Glyph) <> 0) then
+    raise EFontError.Create('FT_Get_Glyph failed');
+
+  if (fOutset > 0) then
+    StrokeBorder(Glyph);
 
   // store scaled advance width/height in glyph-object
-  fAdvance.X := fFont.Face^.glyph^.advance.x / 64 + fOutset*2;
-  fAdvance.Y := fFont.Face^.glyph^.advance.y / 64 + fOutset*2;
+  fAdvance.X := fFace.Data^.glyph^.advance.x / 64 + fOutset*2;
+  fAdvance.Y := fFace.Data^.glyph^.advance.y / 64 + fOutset*2;
 
   // get the contour's bounding box (in 1/64th pixels, not font-units)
   FT_Glyph_Get_CBox(Glyph, FT_GLYPH_BBOX_UNSCALED, CBox);
@@ -2114,9 +2429,6 @@ begin
     end;
   end;
 
-  if (fOutset > 0) then
-    Extrude(TexBuffer, fOutset);
-
   // allocate resources for textures and display lists
   glGenTextures(1, @fTexture);
 
@@ -2151,16 +2463,36 @@ begin
   FT_Done_Glyph(Glyph);
 end;
 
-constructor TFTGlyph.Create(Font: TFTFont; ch: WideChar; Outset: single;
+constructor TFTGlyph.Create(Font: TFTFont; ch: UCS4Char; Outset: single;
     LoadFlags: FT_Int32);
+var
+  I: integer;
 begin
   inherited Create();
 
   fFont := Font;
   fOutset := Outset;
+  fCharCode := ch;
 
-  // get the Freetype char-index (use default UNICODE charmap)
-  fCharIndex := FT_Get_Char_Index(Font.fFace, FT_ULONG(ch));
+  // Note: the default face is also used if no face (neither default nor fallback)
+  // contains a glyph for the given char.
+  fFace := Font.DefaultFace;
+
+  // search the Freetype char-index (use default UNICODE charmap) in the default face
+  fCharIndex := FT_Get_Char_Index(fFace.Data, FT_ULONG(ch));
+  if (fCharIndex = 0) then
+  begin
+    // glyph not in default font, search in fallback font faces
+    for I := 0 to High(Font.FallbackFaces) do
+    begin
+      fCharIndex := FT_Get_Char_Index(Font.FallbackFaces[I].Data, FT_ULONG(ch));
+      if (fCharIndex <> 0) then
+      begin
+        fFace := Font.FallbackFaces[I];
+        Break;
+      end;
+    end;
+  end;
 
   CreateTexture(LoadFlags);
 end;
@@ -2336,7 +2668,7 @@ begin
   InsertPos := fHash.Count;
 end;
 
-function TGlyphCache.AddGlyph(ch: WideChar; const Glyph: TGlyph): boolean;
+function TGlyphCache.AddGlyph(ch: UCS4Char; const Glyph: TGlyph): boolean;
 var
   BaseCode:  cardinal;
   GlyphCode: integer;
@@ -2346,7 +2678,7 @@ var
 begin
   Result := false;
 
-  BaseCode := cardinal(ch) shr 8;
+  BaseCode := Ord(ch) shr 8;
   GlyphTable := FindGlyphTable(BaseCode, InsertPos);
   if (GlyphTable = nil) then
   begin
@@ -2356,7 +2688,7 @@ begin
   end;
 
   // get glyph table offset
-  GlyphCode := cardinal(ch) and $FF;
+  GlyphCode := Ord(ch) and $FF;
   // insert glyph into table if not present
   if (GlyphTable[GlyphCode] = nil) then
   begin
@@ -2365,19 +2697,19 @@ begin
   end;
 end;
 
-procedure TGlyphCache.DeleteGlyph(ch: WideChar);
+procedure TGlyphCache.DeleteGlyph(ch: UCS4Char);
 var
   Table: PGlyphTable;
   TableIndex, GlyphIndex: integer;
   TableEmpty: boolean;
 begin
   // find table
-  Table := FindGlyphTable(cardinal(ch) shr 8, TableIndex);
+  Table := FindGlyphTable(Ord(ch) shr 8, TableIndex);
   if (Table = nil) then
     Exit;
 
   // find glyph    
-  GlyphIndex := cardinal(ch) and $FF;
+  GlyphIndex := Ord(ch) and $FF;
   if (Table[GlyphIndex] <> nil) then
   begin
     // destroy glyph
@@ -2402,19 +2734,19 @@ begin
   end;
 end;
 
-function TGlyphCache.GetGlyph(ch: WideChar): TGlyph;
+function TGlyphCache.GetGlyph(ch: UCS4Char): TGlyph;
 var
   InsertPos: integer;
   Table: PGlyphTable;
 begin
-  Table := FindGlyphTable(cardinal(ch) shr 8, InsertPos);
+  Table := FindGlyphTable(Ord(ch) shr 8, InsertPos);
   if (Table = nil) then
     Result := nil
   else
-    Result := Table[cardinal(ch) and $FF];
+    Result := Table[Ord(ch) and $FF];
 end;
 
-function TGlyphCache.HasGlyph(ch: WideChar): boolean;
+function TGlyphCache.HasGlyph(ch: UCS4Char): boolean;
 begin
   Result := (GetGlyph(ch) <> nil);
 end;
@@ -2464,7 +2796,7 @@ begin
   begin
     // initialize freetype
     if (FT_Init_FreeType(LibraryInst) <> 0) then
-      raise Exception.Create('FT_Init_FreeType failed');
+      raise EFontError.Create('FT_Init_FreeType failed');
   end;
   Result := LibraryInst;
 end;
@@ -2482,10 +2814,10 @@ end;
  * TBitmapFont
  *}
 
-constructor TBitmapFont.Create(const Filename: string; Outline: integer;
+constructor TBitmapFont.Create(const Filename: IPath; Outline: integer;
     Baseline, Ascender, Descender: integer);
 begin
-  inherited Create();
+  inherited Create(Filename);
 
   fTex := Texture.LoadTexture(true, Filename, TEXTURE_TYPE_TRANSPARENT, 0);
   fTexSize := 1024;
@@ -2494,7 +2826,7 @@ begin
   fAscender  := Ascender;
   fDescender := Descender;
 
-  LoadFontInfo(ChangeFileExt(Filename, '.dat'));
+  LoadFontInfo(Filename.SetExtension('.dat'));
 
   ResetIntern();
 end;
@@ -2516,6 +2848,11 @@ begin
   ResetIntern();
 end;
 
+procedure TBitmapFont.AddFallback(const Filename: IPath);
+begin
+  // no support for fallbacks
+end;
+
 procedure TBitmapFont.CorrectWidths(WidthMult: real; WidthAdd: integer);
 var
   Count: integer;
@@ -2524,27 +2861,27 @@ begin
     fWidths[Count] := Round(fWidths[Count] * WidthMult) + WidthAdd;
 end;
 
-procedure TBitmapFont.LoadFontInfo(const InfoFile: string);
+procedure TBitmapFont.LoadFontInfo(const InfoFile: IPath);
 var
-  Stream:  TFileStream;
+  Stream: TStream;
 begin
   FillChar(fWidths[0], Length(fWidths), 0);
 
   Stream := nil;
   try
-    Stream := TFileStream.Create(InfoFile, fmOpenRead);
+    Stream := TBinaryFileStream.Create(InfoFile, fmOpenRead);
     Stream.Read(fWidths, 256);
   except
-    raise Exception.Create('Could not read font info file ''' +  InfoFile + '''');
+    raise EFontError.Create('Could not read font info file ''' +  InfoFile.ToNative + '''');
   end;
   Stream.Free;
 end;
 
-function TBitmapFont.BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl;
+function TBitmapFont.BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl;
 var
   LineIndex, CharIndex: integer;
   CharCode: cardinal;
-  Line: WideString;
+  Line: UCS4String;
   LineWidth: double;
 begin
   Result.Left := 0;
@@ -2556,7 +2893,7 @@ begin
   begin
     Line := Text[LineIndex];
     LineWidth := 0;
-    for CharIndex := 1 to Length(Line) do
+    for CharIndex := 0 to LengthUCS4(Line)-1 do
     begin
       CharCode := Ord(Line[CharIndex]);
       if (CharCode < Length(fWidths)) then
@@ -2567,7 +2904,7 @@ begin
   end;
 end;
 
-procedure TBitmapFont.RenderChar(ch: WideChar; var AdvanceX: real);
+procedure TBitmapFont.RenderChar(ch: UCS4Char; var AdvanceX: real);
 var
   TexX, TexY:        real;
   TexR, TexB:        real;
@@ -2659,20 +2996,20 @@ begin
   AdvanceX := AdvanceX + GlyphWidth;
 end;
 
-procedure TBitmapFont.Render(const Text: WideString);
+procedure TBitmapFont.Render(const Text: UCS4String);
 var
   CharIndex: integer;
   AdvanceX: real;
 begin
   // if there is no text do nothing
-  if (Text = '') then
+  if (Text = nil) or (Text[0] = 0) then
     Exit;
 
   //Save the current color and alpha (for reflection)
   glGetFloatv(GL_CURRENT_COLOR, @fTempColor);
 
   AdvanceX := 0;
-  for CharIndex := 1 to Length(Text) do
+  for CharIndex := 0 to LengthUCS4(Text)-1 do
   begin
     RenderChar(Text[CharIndex], AdvanceX);
   end;
diff --git a/cmake/src/base/UGraphic.pas b/cmake/src/base/UGraphic.pas
index 818e49aa..4f0c8c77 100644
--- a/cmake/src/base/UGraphic.pas
+++ b/cmake/src/base/UGraphic.pas
@@ -45,7 +45,6 @@ uses
   UImage,
   UMusic,
   UScreenLoading,
-  UScreenWelcome,
   UScreenMain,
   UScreenName,
   UScreenLevel,
@@ -71,12 +70,12 @@ uses
   UScreenSongMenu,
   UScreenSongJumpto,
   {Party Screens}
-  UScreenSingModi,
   UScreenPartyNewRound,
   UScreenPartyScore,
   UScreenPartyOptions,
   UScreenPartyWin,
   UScreenPartyPlayer,
+  UScreenPartyRounds,
   {Stats Screens}
   UScreenStatMain,
   UScreenStatDetail,
@@ -107,7 +106,6 @@ var
   ScreenX:    integer;
 
   ScreenLoading:      TScreenLoading;
-  ScreenWelcome:      TScreenWelcome;
   ScreenMain:         TScreenMain;
   ScreenName:         TScreenName;
   ScreenLevel:        TScreenLevel;
@@ -133,12 +131,13 @@ var
   ScreenSongJumpto:     TScreenSongJumpto;
 
   //Party Screens
-  ScreenSingModi:         TScreenSingModi;
+  //ScreenSingModi:         TScreenSingModi;
   ScreenPartyNewRound:    TScreenPartyNewRound;
   ScreenPartyScore:       TScreenPartyScore;
   ScreenPartyWin:         TScreenPartyWin;
   ScreenPartyOptions:     TScreenPartyOptions;
   ScreenPartyPlayer:      TScreenPartyPlayer;
+  ScreenPartyRounds:      TScreenPartyRounds;
 
   //StatsScreens
   ScreenStatMain:         TScreenStatMain;
@@ -150,11 +149,12 @@ var
   //popup mod
   ScreenPopupCheck: TScreenPopupCheck;
   ScreenPopupError: TScreenPopupError;
+  ScreenPopupInfo:  TScreenPopupInfo;
 
   //Notes
-  Tex_Left:        array[0..6] of TTexture;   //rename to tex_note_left
-  Tex_Mid:         array[0..6] of TTexture;   //rename to tex_note_mid
-  Tex_Right:       array[0..6] of TTexture;   //rename to tex_note_right
+  Tex_Left:        array[1..6] of TTexture;   //rename to tex_note_left
+  Tex_Mid:         array[1..6] of TTexture;   //rename to tex_note_mid
+  Tex_Right:       array[1..6] of TTexture;   //rename to tex_note_right
 
   Tex_plain_Left:  array[1..6] of TTexture;   //rename to tex_notebg_left
   Tex_plain_Mid:   array[1..6] of TTexture;   //rename to tex_notebg_mid
@@ -206,6 +206,10 @@ var
   // textures for software mouse cursor
     Tex_Cursor_Unpressed: TTexture;
     Tex_Cursor_Pressed:   TTexture;
+
+
+  PboSupported: boolean;
+
 const
   Skin_BGColorR = 1;
   Skin_BGColorG = 1;
@@ -261,6 +265,7 @@ const
   Skin_P2_ScoreL = 640;
 
 procedure Initialize3D (Title: string);
+procedure Finalize3D;
 procedure Reinitialize3D;
 procedure SwapBuffers;
 
@@ -268,7 +273,7 @@ procedure LoadTextures;
 procedure InitializeScreen;
 procedure LoadLoadingScreen;
 procedure LoadScreens;
-procedure UnLoadScreens;
+procedure UnloadScreens;
 
 function LoadingThreadFunction: integer;
 
@@ -281,12 +286,18 @@ uses
   UIni,
   UDisplay,
   UCommandLine,
-  UPath;
+  UPathUtils;
 
 procedure LoadFontTextures;
 begin
   Log.LogStatus('Building Fonts', 'LoadTextures');
-  BuildFont;
+  BuildFonts;
+end;
+
+procedure UnloadFontTextures;
+begin
+  Log.LogStatus('Kill Fonts', 'UnloadFontTextures');
+  KillFonts;
 end;
 
 procedure LoadTextures;
@@ -298,15 +309,6 @@ var
 begin
   Log.LogStatus('Loading Textures', 'LoadTextures');
 
-  // FIXME: do we need this? (REMOVE otherwise)
-  Tex_Left[0]  := Texture.LoadTexture(Skin.GetTextureFileName('GrayLeft'),  TEXTURE_TYPE_TRANSPARENT, 0);
-  // FIXME: do we need this? (REMOVE otherwise)
-  Tex_Mid[0]   := Texture.LoadTexture(Skin.GetTextureFileName('GrayMid'),   TEXTURE_TYPE_PLAIN, 0);
-  // FIXME: do we need this? (REMOVE otherwise)
-  Tex_Right[0] := Texture.LoadTexture(Skin.GetTextureFileName('GrayRight'), TEXTURE_TYPE_TRANSPARENT, 0);
-
-  Log.LogStatus('Loading Textures - A', 'LoadTextures');
-  
   // P1-6
   // TODO... do it once for each player... this is a bit crappy !!
   //                                       can we make it any better !?
@@ -314,7 +316,29 @@ begin
   begin
     LoadColor(R, G, B, 'P' + IntToStr(P) + 'Light');
     Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255);
-    
+
+    { some colors for tests
+	Col := $10000 * Round(0.02*255) + $100 * Round(0.6 *255) + Round(0.8 *255); //blue
+	Col := $10000 * Round(0.8 *255)                                           ; //red
+	Col :=                            $100 * Round(0.85*255)                  ; //green
+	Col := $10000 * 255             + $100 * Round(0.52*255)                  ; //orange
+	Col := $10000 *            255  + $100 *            255                   ; //yellow
+	Col := $10000 * Round(0.82*255) +                   255                   ; //purple
+	Col := $10000 * Round(0.22*255) + $100 * Round(0.39*255) + Round(0.64*255); //dark blue
+	Col := $10000 * Round(0   *255) + $100 * Round(0   *255) + Round(0   *255); //black
+	Col := $10000 * Round(1.0 *255) + $100 * Round(0.43*255) + Round(0.70*255); //pink
+	Col := 0;       //black
+	Col := $FFFFFF; //white
+	Col := $FF0000; //red
+	Col := $00FF00; //green
+	Col := $002200; //light green
+	Col := $002222; //light greenblue
+	Col := $222200; //light yellow
+	Col := $340000; //red
+	Col := $FF6EB4; //pink
+	Col := $333333; //grey
+    }
+
     Tex_Left[P]         := Texture.LoadTexture(Skin.GetTextureFileName('GrayLeft'),  TEXTURE_TYPE_COLORIZED, Col);
     Tex_Mid[P]          := Texture.LoadTexture(Skin.GetTextureFileName('GrayMid'),   TEXTURE_TYPE_COLORIZED, Col);
     Tex_Right[P]        := Texture.LoadTexture(Skin.GetTextureFileName('GrayRight'), TEXTURE_TYPE_COLORIZED, Col);
@@ -340,7 +364,7 @@ begin
 
   Tex_Cursor_Unpressed  := Texture.LoadTexture(Skin.GetTextureFileName('Cursor'), TEXTURE_TYPE_TRANSPARENT, 0);
 
-  if (Skin.GetTextureFileName('Cursor_Pressed') <> '') then
+  if (Skin.GetTextureFileName('Cursor_Pressed').IsSet) then
     Tex_Cursor_Pressed    := Texture.LoadTexture(Skin.GetTextureFileName('Cursor_Pressed'), TEXTURE_TYPE_TRANSPARENT, 0)
   else
     Tex_Cursor_Pressed.TexNum := 0;
@@ -389,14 +413,14 @@ begin
       End;
 
       Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255);
-      Tex_SingLineBonusBack[P] :=  Texture.LoadTexture(pchar(Skin.GetTextureFileName('LineBonusBack')), TEXTURE_TYPE_COLORIZED, Col);
+      Tex_SingLineBonusBack[P] :=  Texture.LoadTexture(Skin.GetTextureFileName('LineBonusBack'), TEXTURE_TYPE_COLORIZED, Col);
     end;
 
 //## backgrounds for the scores ##
   for P := 0 to 5 do begin
     LoadColor(R, G, B, 'P' + IntToStr(P+1) + 'Light');
     Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255);
-    Tex_ScoreBG[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreBG')), TEXTURE_TYPE_COLORIZED, Col);
+    Tex_ScoreBG[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreBG'), TEXTURE_TYPE_COLORIZED, Col);
   end;
 
 
@@ -411,23 +435,23 @@ begin
 //NoteBar ScoreBar
     LoadColor(R, G, B, 'P' + IntToStr(P) + 'Dark');
     Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255);
-    Tex_Score_NoteBarLevel_Dark[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Dark')), TEXTURE_TYPE_COLORIZED, Col);
-    Tex_Score_NoteBarRound_Dark[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Dark_Round')), TEXTURE_TYPE_COLORIZED, Col);
+    Tex_Score_NoteBarLevel_Dark[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Dark'), TEXTURE_TYPE_COLORIZED, Col);
+    Tex_Score_NoteBarRound_Dark[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Dark_Round'), TEXTURE_TYPE_COLORIZED, Col);
 //LineBonus ScoreBar
     LoadColor(R, G, B, 'P' + IntToStr(P) + 'Light');
     Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255);
-    Tex_Score_NoteBarLevel_Light[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Light')), TEXTURE_TYPE_COLORIZED, Col);
-    Tex_Score_NoteBarRound_Light[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Light_Round')), TEXTURE_TYPE_COLORIZED, Col);
+    Tex_Score_NoteBarLevel_Light[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Light'), TEXTURE_TYPE_COLORIZED, Col);
+    Tex_Score_NoteBarRound_Light[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Light_Round'), TEXTURE_TYPE_COLORIZED, Col);
 //GoldenNotes ScoreBar
     LoadColor(R, G, B, 'P' + IntToStr(P) + 'Lightest');
     Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255);
-    Tex_Score_NoteBarLevel_Lightest[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Lightest')), TEXTURE_TYPE_COLORIZED, Col);
-    Tex_Score_NoteBarRound_Lightest[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Lightest_Round')), TEXTURE_TYPE_COLORIZED, Col);
+    Tex_Score_NoteBarLevel_Lightest[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Lightest'), TEXTURE_TYPE_COLORIZED, Col);
+    Tex_Score_NoteBarRound_Lightest[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Lightest_Round'), TEXTURE_TYPE_COLORIZED, Col);
   end;
 
 //## rating pictures that show a picture according to your rate ##
     for P := 0 to 7 do begin
-    Tex_Score_Ratings[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('Rating_'+IntToStr(P))), TEXTURE_TYPE_TRANSPARENT, 0);
+    Tex_Score_Ratings[P] := Texture.LoadTexture(Skin.GetTextureFileName('Rating_'+IntToStr(P)), TEXTURE_TYPE_TRANSPARENT, 0);
   end;
 
   Log.LogStatus('Loading Textures - Done', 'LoadTextures');
@@ -448,6 +472,12 @@ begin
   // Other extensions e.g. OpenGL 1.3-2.0 or Framebuffer-Object might be loaded here
   // ...
   //Load_GL_EXT_framebuffer_object();
+
+  // PBO functions are loaded with VBO
+  //PboSupported := Load_GL_ARB_pixel_buffer_object()
+  //    and Load_GL_ARB_vertex_buffer_object();
+  //Log.LogWarn('PBOSupported: ' + BoolToStr(PboSupported, true), 'LoadOpenGLExtensions');
+  PboSupported := false;
 end;
 
 const
@@ -464,12 +494,17 @@ begin
   end;
 
   // load icon image (must be 32x32 for win32)
-  Icon := LoadImage(ResourcesPath + WINDOW_ICON);
+  Icon := LoadImage(ResourcesPath.Append(WINDOW_ICON));
   if (Icon <> nil) then
-    SDL_WM_SetIcon(Icon, 0);
+    SDL_WM_SetIcon(Icon, nil);
 
   SDL_WM_SetCaption(PChar(Title), nil);
 
+  { center window }
+  SDL_putenv('SDL_VIDEO_WINDOW_POS=center');
+  { workaround for buggy Intel 3D driver on Linux }
+  SDL_putenv('texture_tiling=false');
+
   //Log.BenchmarkStart(2);
 
   InitializeScreen;
@@ -573,6 +608,13 @@ begin
   glMatrixMode(GL_MODELVIEW);
 end;
 
+procedure Finalize3D;
+begin
+  // TODO: finalize other stuff
+  UnloadFontTextures;
+  SDL_QuitSubSystem(SDL_INIT_VIDEO);
+end;
+
 procedure Reinitialize3D;
 begin
   InitializeScreen;
@@ -667,7 +709,7 @@ end;
 procedure LoadLoadingScreen;
 begin
   ScreenLoading := TScreenLoading.Create;
-  ScreenLoading.onShow;
+  ScreenLoading.OnShow;
   
   Display.CurrentScreen := @ScreenLoading;
 
@@ -682,15 +724,13 @@ end;
 procedure LoadScreens;
 begin
 {  ScreenLoading := TScreenLoading.Create;
-  ScreenLoading.onShow;
+  ScreenLoading.OnShow;
   Display.CurrentScreen := @ScreenLoading;
   ScreenLoading.Draw;
   Display.Draw;
   SwapBuffers;
 }
   Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Loading', 3); Log.BenchmarkStart(3);
-{ ScreenWelcome :=          TScreenWelcome.Create; //'BG', 4, 3);
-  Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Welcome', 3); Log.BenchmarkStart(3);}
   ScreenMain :=             TScreenMain.Create;
   Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Main', 3); Log.BenchmarkStart(3);
   ScreenName :=             TScreenName.Create;
@@ -733,8 +773,8 @@ begin
 //  Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Edit Header', 3); Log.BenchmarkStart(3);
   ScreenOpen :=             TScreenOpen.Create;
   Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Open', 3); Log.BenchmarkStart(3);
-  ScreenSingModi :=         TScreenSingModi.Create;
-  Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Sing with Modi support', 3); Log.BenchmarkStart(3);
+  //ScreenSingModi :=         TScreenSingModi.Create;
+  //Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Sing with Modi support', 3); Log.BenchmarkStart(3);
   ScreenSongMenu :=         TScreenSongMenu.Create;
   Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen SongMenu', 3); Log.BenchmarkStart(3);
   ScreenSongJumpto :=         TScreenSongJumpto.Create;
@@ -743,6 +783,8 @@ begin
   Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Popup (Check)', 3); Log.BenchmarkStart(3);
   ScreenPopupError := TScreenPopupError.Create;
   Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Popup (Error)', 3); Log.BenchmarkStart(3);
+  ScreenPopupInfo := TScreenPopupInfo.Create;
+  Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Popup (Info)', 3); Log.BenchmarkStart(3);
   ScreenPartyNewRound :=    TScreenPartyNewRound.Create;
   Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen PartyNewRound', 3); Log.BenchmarkStart(3);
   ScreenPartyScore :=       TScreenPartyScore.Create;
@@ -753,6 +795,8 @@ begin
   Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen PartyOptions', 3); Log.BenchmarkStart(3);
   ScreenPartyPlayer :=      TScreenPartyPlayer.Create;
   Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen PartyPlayer', 3); Log.BenchmarkStart(3);
+  ScreenPartyRounds :=      TScreenPartyRounds.Create;
+  Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen PartyRounds', 3); Log.BenchmarkStart(3);
   ScreenStatMain :=         TScreenStatMain.Create;
   Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Stat Main', 3); Log.BenchmarkStart(3);
   ScreenStatDetail :=       TScreenStatDetail.Create;
@@ -768,39 +812,41 @@ begin
   Result:= 1;
 end;
 
-procedure UnLoadScreens;
+procedure UnloadScreens;
 begin
-  ScreenMain.Destroy;
-  ScreenName.Destroy;
-  ScreenLevel.Destroy;
-  ScreenSong.Destroy;
-  ScreenSing.Destroy;
-  ScreenScore.Destroy;
-  ScreenTop5.Destroy;
-  ScreenOptions.Destroy;
-  ScreenOptionsGame.Destroy;
-  ScreenOptionsGraphics.Destroy;
-  ScreenOptionsSound.Destroy;
-  ScreenOptionsLyrics.Destroy;
-//  ScreenOptionsThemes.Destroy;
-  ScreenOptionsRecord.Destroy;
-  ScreenOptionsAdvanced.Destroy;
-  ScreenEditSub.Destroy;
-  ScreenEdit.Destroy;
-  ScreenEditConvert.Destroy;
-  ScreenOpen.Destroy;
-  ScreenSingModi.Destroy;
-  ScreenSongMenu.Destroy;
-  ScreenSongJumpto.Destroy;
-  ScreenPopupCheck.Destroy;
-  ScreenPopupError.Destroy;
-  ScreenPartyNewRound.Destroy;
-  ScreenPartyScore.Destroy;
-  ScreenPartyWin.Destroy;
-  ScreenPartyOptions.Destroy;
-  ScreenPartyPlayer.Destroy;
-  ScreenStatMain.Destroy;
-  ScreenStatDetail.Destroy;
+  ScreenMain.Free;
+  ScreenName.Free;
+  ScreenLevel.Free;
+  ScreenSong.Free;
+  ScreenSing.Free;
+  ScreenScore.Free;
+  ScreenTop5.Free;
+  ScreenOptions.Free;
+  ScreenOptionsGame.Free;
+  ScreenOptionsGraphics.Free;
+  ScreenOptionsSound.Free;
+  ScreenOptionsLyrics.Free;
+//  ScreenOptionsThemes.Free;
+  ScreenOptionsRecord.Free;
+  ScreenOptionsAdvanced.Free;
+  ScreenEditSub.Free;
+  ScreenEdit.Free;
+  ScreenEditConvert.Free;
+  ScreenOpen.Free;
+  //ScreenSingModi.Free;
+  ScreenSongMenu.Free;
+  ScreenSongJumpto.Free;
+  ScreenPopupCheck.Free;
+  ScreenPopupError.Free;
+  ScreenPopupInfo.Free;
+  ScreenPartyNewRound.Free;
+  ScreenPartyScore.Free;
+  ScreenPartyWin.Free;
+  ScreenPartyOptions.Free;
+  ScreenPartyPlayer.Free;
+  ScreenPartyRounds.Free;
+  ScreenStatMain.Free;
+  ScreenStatDetail.Free;
 end;
 
 end.
diff --git a/cmake/src/base/UImage.pas b/cmake/src/base/UImage.pas
index 60b0a3a2..1866316e 100644
--- a/cmake/src/base/UImage.pas
+++ b/cmake/src/base/UImage.pas
@@ -34,7 +34,8 @@ interface
 {$I switches.inc}
 
 uses
-  SDL;
+  SDL,
+  UPath;
 
 {$DEFINE HavePNG}
 {$DEFINE HaveBMP}
@@ -131,20 +132,20 @@ type
  *******************************************************)
 
 {$IFDEF HavePNG}
-function WritePNGImage(const FileName: string; Surface: PSDL_Surface): boolean;
+function WritePNGImage(const FileName: IPath; Surface: PSDL_Surface): boolean;
 {$ENDIF}
 {$IFDEF HaveBMP}
-function WriteBMPImage(const FileName: string; Surface: PSDL_Surface): boolean;
+function WriteBMPImage(const FileName: IPath; Surface: PSDL_Surface): boolean;
 {$ENDIF}
 {$IFDEF HaveJPG}
-function WriteJPGImage(const FileName: string; Surface: PSDL_Surface; Quality: integer): boolean;
+function WriteJPGImage(const FileName: IPath; Surface: PSDL_Surface; Quality: integer): boolean;
 {$ENDIF}
 
 (*******************************************************
  * Image loading
  *******************************************************)
 
-function LoadImage(const Filename: string): PSDL_Surface;
+function LoadImage(const Filename: IPath): PSDL_Surface;
 
 (*******************************************************
  * Image manipulation
@@ -181,6 +182,7 @@ uses
   zlib,
   sdl_image,
   sdlutils,
+  sdlstreams,
   UCommon,
   ULog;
 
@@ -282,26 +284,26 @@ end;
 
 procedure user_read_data(png_ptr: png_structp; data: png_bytep; length: png_size_t); cdecl;
 var
-  inFile: TFileStream;
+  inFile: TStream;
 begin
-  inFile := TFileStream(png_get_io_ptr(png_ptr));
+  inFile := TStream(png_get_io_ptr(png_ptr));
   inFile.Read(data^, length);
 end;
 
 procedure user_write_data(png_ptr: png_structp; data: png_bytep; length: png_size_t); cdecl;
 var
-  outFile: TFileStream;
+  outFile: TStream;
 begin
-  outFile := TFileStream(png_get_io_ptr(png_ptr));
+  outFile := TStream(png_get_io_ptr(png_ptr));
   outFile.Write(data^, length);
 end;
 
 procedure user_flush_data(png_ptr: png_structp); cdecl;
 //var
-//  outFile: TFileStream;
+//  outFile: TStream;
 begin
   // binary files are flushed automatically, Flush() works with Text-files only
-  //outFile := TFileStream(png_get_io_ptr(png_ptr));
+  //outFile := TStream(png_get_io_ptr(png_ptr));
   //outFile.Flush();
 end;
 
@@ -323,11 +325,11 @@ end;
 (*
  * ImageData must be in RGB-format
  *)
-function WritePNGImage(const FileName: string; Surface: PSDL_Surface): boolean;
+function WritePNGImage(const FileName: IPath; Surface: PSDL_Surface): boolean;
 var
   png_ptr:   png_structp;
   info_ptr:  png_infop;
-  pngFile:   TFileStream;
+  pngFile:   TStream;
   row:       integer;
   rowData:   array of png_bytep;
 //  rowStride: integer;
@@ -339,9 +341,9 @@ begin
 
   // open file for writing
   try
-    pngFile := TFileStream.Create(FileName, fmCreate);
+    pngFile := TBinaryFileStream.Create(FileName, fmCreate);
   except
-    Log.LogError('Could not open file: "' + FileName + '"', 'WritePngImage');
+    Log.LogError('Could not open file: "' + FileName.ToNative + '"', 'WritePngImage');
     Exit;
   end;
 
@@ -500,9 +502,9 @@ type
 (*
  * ImageData must be in BGR-format
  *)
-function WriteBMPImage(const FileName: string; Surface: PSDL_Surface): boolean;
+function WriteBMPImage(const FileName: IPath; Surface: PSDL_Surface): boolean;
 var
-  bmpFile:    TFileStream;
+  bmpFile:    TStream;
   FileInfo:   BITMAPINFOHEADER;
   FileHeader: BITMAPFILEHEADER;
   Converted:  boolean;
@@ -513,9 +515,9 @@ begin
 
   // open file for writing
   try
-    bmpFile := TFileStream.Create(FileName, fmCreate);
+    bmpFile := TBinaryFileStream.Create(FileName, fmCreate);
   except
-    Log.LogError('Could not open file: "' + FileName + '"', 'WriteBMPImage');
+    Log.LogError('Could not open file: "' + FileName.ToNative + '"', 'WriteBMPImage');
     Exit;
   end;
 
@@ -579,7 +581,7 @@ begin
 
     Result := true;
   finally
-    Log.LogError('Could not write file: "' + FileName + '"', 'WriteBMPImage');
+    Log.LogError('Could not write file: "' + FileName.ToNative + '"', 'WriteBMPImage');
   end;
 
   if (Converted) then
@@ -597,20 +599,21 @@ end;
 
 {$IFDEF HaveJPG}
 
-function WriteJPGImage(const FileName: string; Surface: PSDL_Surface; Quality: integer): boolean;
+function WriteJPGImage(const FileName: IPath; Surface: PSDL_Surface; Quality: integer): boolean;
 var
   {$IFDEF Delphi}
-  Bitmap:    TBitmap;
+  Bitmap:     TBitmap;
   BitmapInfo: TBitmapInfo;
-  Jpeg:      TJpegImage;
-  row:       integer;
+  Jpeg:       TJpegImage;
+  row:        integer;
+  FileStream: TBinaryFileStream;
   {$ELSE}
   cinfo:     jpeg_compress_struct;
   jerr :     jpeg_error_mgr;
-  jpgFile:   TFileStream;
+  jpgFile:   TBinaryFileStream;
   rowPtr:    array[0..0] of JSAMPROW;
   {$ENDIF}
-  converted: boolean;
+  converted:  boolean;
 begin
   Result := false;
 
@@ -669,19 +672,32 @@ begin
       SDL_UnlockSurface(Surface);
 
     // assign Bitmap to JPEG and store the latter
-    Jpeg := TJPEGImage.Create;
-    Jpeg.Assign(Bitmap);
-    Bitmap.Free;
-    Jpeg.CompressionQuality := Quality;
     try
-      // compress image (don't forget this line, otherwise it won't be compressed)
-      Jpeg.Compress();
-      Jpeg.SaveToFile(FileName);
+      // init with nil so Free() will not fail if an exception occurs
+      Jpeg := nil;
+      Bitmap := nil;
+      FileStream := nil;
+
+      try
+        Jpeg := TJPEGImage.Create;
+        Jpeg.Assign(Bitmap);
+
+        // compress image (don't forget this line, otherwise it won't be compressed)
+        Jpeg.CompressionQuality := Quality;
+        Jpeg.Compress();
+
+        // Note: FileStream needed for unicode filename support
+        FileStream := TBinaryFileStream.Create(Filename, fmCreate);
+        Jpeg.SaveToStream(FileStream);
+      finally
+        FileStream.Free;
+        Bitmap.Free;
+        Jpeg.Free;
+      end;
     except
-      Log.LogError('Could not save file: "' + FileName + '"', 'WriteJPGImage');
+      Log.LogError('Could not save file: "' + FileName.ToNative + '"', 'WriteJPGImage');
       Exit;
     end;
-    Jpeg.Free;
   {$ELSE}
     // based on example.pas in FPC's packages/base/pasjpeg directory
 
@@ -703,9 +719,9 @@ begin
 
     // open file for writing
     try
-      jpgFile := TFileStream.Create(FileName, fmCreate);
+      jpgFile := TBinaryFileStream.Create(FileName, fmCreate);
     except
-      Log.LogError('Could not open file: "' + FileName + '"', 'WriteJPGImage');
+      Log.LogError('Could not open file: "' + FileName.ToNative + '"', 'WriteJPGImage');
       Exit;
     end;
 
@@ -763,27 +779,29 @@ end;
 (*
  * Loads an image from the given file
  *)
-function LoadImage(const Filename: string): PSDL_Surface;
+function LoadImage(const Filename: IPath): PSDL_Surface;
 var
-  FilenameFound: string;
+  FilenameCaseAdj: IPath;
+  FileStream: TBinaryFileStream;
+  SDLStream: PSDL_RWops;
 begin
-  Result   := nil;
+  Result := nil;
 
-  // FileExistsInsensitive() requires a var-arg
-  FilenameFound := Filename;
-
-  // try to find the file case insensitive
-  if (not FileExistsInsensitive(FilenameFound)) then
+  // try to adjust filename's case and check if it exists
+  FilenameCaseAdj := Filename.AdjustCase(false);
+  if (not FilenameCaseAdj.IsFile) then
   begin
-    Log.LogError('Image-File does not exist "'+FilenameFound+'"', 'LoadImage');
+    Log.LogError('Image-File does not exist "' + FilenameCaseAdj.ToNative + '"', 'LoadImage');
     Exit;
   end;
 
   // load from file
   try
-    Result := IMG_Load(PChar(FilenameFound));
+    SDLStream := SDLStreamSetup(TBinaryFileStream.Create(FilenameCaseAdj, fmOpenRead));
+    Result := IMG_Load_RW(SDLStream, 1);
+    // Note: TBinaryFileStream is freed by SDLStream. SDLStream by IMG_Load_RW().
   except
-    Log.LogError('Could not load from file "'+FilenameFound+'"', 'LoadImage');
+    Log.LogError('Could not load from file "' + FilenameCaseAdj.ToNative + '"', 'LoadImage');
     Exit;
   end;
 end;
@@ -794,17 +812,13 @@ end;
 
 function PixelFormatEquals(fmt1, fmt2: PSDL_PixelFormat): boolean;
 begin
-  if (fmt1^.BitsPerPixel = fmt2^.BitsPerPixel) and
-     (fmt1^.BytesPerPixel = fmt2^.BytesPerPixel) and
-     (fmt1^.Rloss = fmt2^.Rloss) and (fmt1^.Gloss = fmt2^.Gloss) and
-     (fmt1^.Bloss = fmt2^.Bloss) and (fmt1^.Rmask = fmt2^.Rmask) and
-     (fmt1^.Gmask = fmt2^.Gmask) and (fmt1^.Bmask = fmt2^.Bmask) and
-     (fmt1^.Rshift = fmt2^.Rshift) and (fmt1^.Gshift = fmt2^.Gshift) and
-     (fmt1^.Bshift = fmt2^.Bshift)
-  then
-    Result := true
-  else
-    Result := false;
+  Result := 
+    (fmt1^.BitsPerPixel  = fmt2^.BitsPerPixel)  and
+    (fmt1^.BytesPerPixel = fmt2^.BytesPerPixel) and
+    (fmt1^.Rloss = fmt2^.Rloss)   and (fmt1^.Gloss = fmt2^.Gloss)   and (fmt1^.Bloss = fmt2^.Bloss)   and
+    (fmt1^.Rmask = fmt2^.Rmask)   and (fmt1^.Gmask = fmt2^.Gmask)   and (fmt1^.Bmask = fmt2^.Bmask)   and
+    (fmt1^.Rshift = fmt2^.Rshift) and (fmt1^.Gshift = fmt2^.Gshift) and (fmt1^.Bshift = fmt2^.Bshift)
+  ;
 end;
 
 procedure ScaleImage(var ImgSurface: PSDL_Surface; Width, Height: cardinal);
@@ -885,7 +899,7 @@ begin
 end;
 *)
 
-procedure ColorizeImage(ImgSurface: PSDL_Surface; NewColor: cardinal);
+procedure ColorizeImage(ImgSurface: PSDL_Surface; NewColor: longword);
 
   // First, the rgb colors are converted to hsv, second hue is replaced by
   // the NewColor, saturation and value remain unchanged, finally this
@@ -893,7 +907,7 @@ procedure ColorizeImage(ImgSurface: PSDL_Surface; NewColor: cardinal);
   // For the conversion algorithms of colors from rgb to hsv space
   // and back simply check the wikipedia.
   // In order to speed up starting time of USDX the division of reals is 
-  // replaced by division of longwords, shifted by 10 bits to keep 
+  // replaced by division of longints, shifted by 10 bits to keep 
   // digits.
 
   // The use of longwards leeds to some type size mismatch warnings
@@ -904,8 +918,8 @@ procedure ColorizeImage(ImgSurface: PSDL_Surface; NewColor: cardinal);
   function ColorToHue(const Color: longword): longword;
   // returns hue within the range [0.0-6.0] but shl 10, ie. times 1024
   var
-    Red, Green, Blue: longword;
-    Min, Max, Delta: longword;
+    Red, Green, Blue: longint;
+    Min, Max, Delta:  longint;
     Hue: double;
   begin
     // extract the colors
@@ -933,6 +947,8 @@ procedure ColorizeImage(ImgSurface: PSDL_Surface; NewColor: cardinal);
       // The division by Delta is done separately afterwards.
       // Necessary because Delphi did not do the type conversion from
       // longword to double as expected.
+      // After the change to longint, we may not need it, but left for now
+      // Something to check
       if      (Max = Red  ) then Hue :=             Green - Blue
       else if (Max = Green) then Hue := 2.0*Delta + Blue  - Red
       else if (Max = Blue ) then Hue := 4.0*Delta + Red   - Green;
@@ -940,6 +956,8 @@ procedure ColorizeImage(ImgSurface: PSDL_Surface; NewColor: cardinal);
       if (Hue < 0.0) then
         Hue := Hue + 6.0;
       Result := trunc(Hue*1024);           // '*1024' is shl 10
+ //     if NewColor = $000000 then
+ //       Log.LogError ('Hue: ' +  FloatToStr(Hue), 'ColorToHue');
     end;
   end;
 
@@ -952,6 +970,8 @@ var
   Min, Max, Delta: longword;
   HueInteger: longword;
   f, p, q, t: longword;
+  GreyReal: real;
+  Grey: byte;
 begin
 
   Pixel := ImgSurface^.Pixels;
@@ -965,8 +985,48 @@ begin
     Log.LogError ('ColorizeImage: The pixel size should be 4, but it is '
                    + IntToStr(ImgSurface^.format.BytesPerPixel));
 
+  // Check whether the new color is white, grey or black, 
+  // because a greyscale must be created in a different
+  // way.
+  
+  Red   := ((NewColor and $ff0000) shr 16); // R
+  Green := ((NewColor and   $ff00) shr  8); // G
+  Blue  :=  (NewColor and     $ff)        ; // B
+  
+  if (Red = Green) and (Green = Blue) then // greyscale image
+  begin
+    // According to these recommendations (ITU-R BT.709-5)
+    // the conversion parameters for rgb to greyscale are
+    // 0.299, 0.587, 0.114
+    for PixelIndex := 0 to (ImgSurface^.W * ImgSurface^.H)-1 do
+    begin
+      PixelColors := PByteArray(Pixel);
+      {$IFDEF FPC_BIG_ENDIAN}
+      GreyReal := 0.299*PixelColors[3] + 0.587*PixelColors[2] + 0.114*PixelColors[1];
+      //       PixelColors[0] is alpha and remains untouched
+      {$ELSE}
+      GreyReal := 0.299*PixelColors[0] + 0.587*PixelColors[1] + 0.114*PixelColors[2];
+      //       PixelColors[3] is alpha and remains untouched
+      {$ENDIF}
+      Grey := round(GreyReal);
+      {$IFDEF FPC_BIG_ENDIAN}
+      PixelColors[3] := Grey;
+      PixelColors[2] := Grey;
+      PixelColors[1] := Grey;
+      //       PixelColors[0] is alpha and remains untouched
+      {$ELSE}
+      PixelColors[0] := Grey;
+      PixelColors[1] := Grey;
+      PixelColors[2] := Grey;
+      //       PixelColors[3] is alpha and remains untouched
+      {$ENDIF}
+      Inc(Pixel, ImgSurface^.format.BytesPerPixel);
+    end;
+    exit; // we are done with a greyscale image.
+  end;
+
   Hue := ColorToHue(NewColor);   // Hue is shl 10
-  f := Hue and $3ff;             // f is the dezimal part of hue
+  f   := Hue and $3ff;           // f is the dezimal part of hue
   HueInteger := Hue shr 10;
 
   for PixelIndex := 0 to (ImgSurface^.W * ImgSurface^.H)-1 do
@@ -1036,9 +1096,9 @@ begin
         // shr 10 corrects that Sat and f are shl 10
         // the resulting p, q and t are unshifted
 
-        p := (Max*(1024-Sat)) shr 10;
-        q := (Max*(1024-(Sat*f) shr 10)) shr 10;
-        t := (Max*(1024-(Sat*(1024-f)) shr 10)) shr 10;
+        p := (Max * (1024 -  Sat                     )) shr 10;
+        q := (Max * (1024 - (Sat *  f        ) shr 10)) shr 10;
+        t := (Max * (1024 - (Sat * (1024 - f)) shr 10)) shr 10;
 
         // The above 3 lines give type size mismatch warning, but all variables are longword and the ranges should be ok.
 
diff --git a/cmake/src/base/UIni.pas b/cmake/src/base/UIni.pas
index 2bcc7305..a4c85a3b 100644
--- a/cmake/src/base/UIni.pas
+++ b/cmake/src/base/UIni.pas
@@ -36,8 +36,12 @@ interface
 uses
   Classes,
   IniFiles,
+  SysUtils,
+  UCommon,
   ULog,
-  SysUtils;
+  UTextEncoding,
+  UFilesystem,
+  UPath;
 
 type
   // TInputDeviceConfig stores the configuration for an input device.
@@ -59,9 +63,13 @@ type
   TInputDeviceConfig = record
     Name:               string;
     Input:              integer;
+    Latency:            integer; //**< latency in ms, or LATENCY_AUTODETECT for default
     ChannelToPlayerMap: array of integer;
   end;
 
+const
+  LATENCY_AUTODETECT = -1;
+
 type
 
 //Options
@@ -70,11 +78,9 @@ type
   TBackgroundMusicOption = (bmoOff, bmoOn);
   TIni = class
     private
-      function RemoveFileExt(FullName: string): string;
       function ExtractKeyIndex(const Key, Prefix, Suffix: string): integer;
       function GetMaxKeyIndex(Keys: TStringList; const Prefix, Suffix: string): integer;
-      function GetArrayIndex(const SearchArray: array of string; Value: string; CaseInsensitiv: boolean = false): integer;
-      function ReadArrayIndex(const SearchArray: array of string; IniFile: TCustomIniFile;
+      function ReadArrayIndex(const SearchArray: array of UTF8String; IniFile: TCustomIniFile;
           IniSection: string; IniProperty: string; Default: integer): integer;
 
       procedure TranslateOptionValues;
@@ -85,14 +91,14 @@ type
       procedure LoadScreenModes(IniFile: TCustomIniFile);
 
     public
-      Name:           array[0..11] of string;
+      Name:           array[0..11] of UTF8String;
 
       // Templates for Names Mod
-      NameTeam:       array[0..2] of string;
-      NameTemplate:   array[0..11] of string;
+      NameTeam:       array[0..2] of UTF8String;
+      NameTemplate:   array[0..11] of UTF8String;
 
       //Filename of the opened iniFile
-      Filename:       string;
+      Filename:       IPath;
 
       // Game
       Players:        integer;
@@ -125,6 +131,8 @@ type
       AudioOutputBufferSizeIndex: integer;
       VoicePassthrough: integer;
 
+      SyncTo: integer;
+
       //Song Preview
       PreviewVolume:  integer;
       PreviewFading:  integer;
@@ -132,7 +140,6 @@ type
       // Lyrics
       LyricsFont:     integer;
       LyricsEffect:   integer;
-      Solmization:    integer;
       NoteLines:      integer;
 
       // Themes
@@ -165,173 +172,205 @@ type
 
 var
   Ini:         TIni;
-  IResolution: array of string;
-  ILanguage:   array of string;
-  ITheme:      array of string;
-  ISkin:       array of string;
+  IResolution: TUTF8StringDynArray;
+  ILanguage:   TUTF8StringDynArray;
+  ITheme:      TUTF8StringDynArray;
+  ISkin:       TUTF8StringDynArray;
+
+{*
+ * Options
+ *}
 
 const
-  IPlayers:     array[0..4] of string  = ('1', '2', '3', '4', '6');
-  IPlayersVals: array[0..4] of integer = ( 1 ,  2 ,  3 ,  4 ,  6 );
+  IPlayers:     array[0..4] of UTF8String = ('1', '2', '3', '4', '6');
+  IPlayersVals: array[0..4] of integer    = ( 1 ,  2 ,  3 ,  4 ,  6 );
 
-  IDifficulty:  array[0..2] of string  = ('Easy', 'Medium', 'Hard');
-  ITabs:        array[0..1] of string  = ('Off', 'On');
+  IDifficulty:  array[0..2] of UTF8String = ('Easy', 'Medium', 'Hard');
+  ITabs:        array[0..1] of UTF8String = ('Off', 'On');
 
-  ISorting:     array[0..7] of string  = ('Edition', 'Genre', 'Language', 'Folder', 'Title', 'Artist', 'Title2', 'Artist2');
-  sEdition  = 0;
-  sGenre    = 1;
-  sLanguage = 2;
-  sFolder   = 3;
-  sTitle    = 4;
-  sArtist   = 5;
-  sTitle2   = 6;
-  sArtist2  = 7;
+const
+  ISorting:     array[0..6] of UTF8String = ('Edition', 'Genre', 'Language', 'Folder', 'Title', 'Artist', 'Artist2');
+type
+  TSortingType = (sEdition, sGenre, sLanguage, sFolder, sTitle, sArtist, sArtist2);
 
-  IDebug:            array[0..1] of string  = ('Off', 'On');
+const  
+  IDebug:            array[0..1] of UTF8String  = ('Off', 'On');
 
-  IScreens:          array[0..1] of string  = ('1', '2');
-  IFullScreen:       array[0..1] of string  = ('Off', 'On');
-  IDepth:            array[0..1] of string  = ('16 bit', '32 bit');
-  IVisualizer:       array[0..2] of string  = ('Off', 'WhenNoVideo','On');
+  IScreens:          array[0..1] of UTF8String  = ('1', '2');
+  IFullScreen:       array[0..1] of UTF8String  = ('Off', 'On');
+  IDepth:            array[0..1] of UTF8String  = ('16 bit', '32 bit');
+  IVisualizer:       array[0..2] of UTF8String  = ('Off', 'WhenNoVideo','On');
 
-  IBackgroundMusic:  array[0..1] of string  = ('Off', 'On');
+  IBackgroundMusic:  array[0..1] of UTF8String  = ('Off', 'On');
 
-  ITextureSize:      array[0..3] of string  = ('64', '128', '256', '512');
-  ITextureSizeVals:  array[0..3] of integer = ( 64,   128,   256,   512);
+  ITextureSize:      array[0..3] of UTF8String  = ('64', '128', '256', '512');
+  ITextureSizeVals:  array[0..3] of integer     = ( 64,   128,   256,   512);
 
-  ISingWindow:       array[0..1] of string  = ('Small', 'Big');
+  ISingWindow:       array[0..1] of UTF8String  = ('Small', 'Big');
 
   //SingBar Mod
-  IOscilloscope:     array[0..1] of string  = ('Off', 'On');
+  IOscilloscope:     array[0..1] of UTF8String  = ('Off', 'On');
 
-  ISpectrum:         array[0..1] of string  = ('Off', 'On');
-  ISpectrograph:     array[0..1] of string  = ('Off', 'On');
-  IMovieSize:        array[0..2] of string  = ('Half', 'Full [Vid]', 'Full [BG+Vid]');
+  ISpectrum:         array[0..1] of UTF8String  = ('Off', 'On');
+  ISpectrograph:     array[0..1] of UTF8String  = ('Off', 'On');
+  IMovieSize:        array[0..2] of UTF8String  = ('Half', 'Full [Vid]', 'Full [BG+Vid]');
 
-  IClickAssist:      array[0..1] of string  = ('Off', 'On');
-  IBeatClick:        array[0..1] of string  = ('Off', 'On');
-  ISavePlayback:     array[0..1] of string  = ('Off', 'On');
+  IClickAssist:      array[0..1] of UTF8String  = ('Off', 'On');
+  IBeatClick:        array[0..1] of UTF8String  = ('Off', 'On');
+  ISavePlayback:     array[0..1] of UTF8String  = ('Off', 'On');
 
-  IThreshold:        array[0..3] of string  = ('5%', '10%', '15%', '20%');
+  IThreshold:        array[0..3] of UTF8String  = ('5%', '10%', '15%', '20%');
   IThresholdVals:    array[0..3] of single  = (0.05, 0.10,  0.15,  0.20);
 
-  IVoicePassthrough: array[0..1] of string  = ('Off', 'On');
+  IVoicePassthrough: array[0..1] of UTF8String  = ('Off', 'On');
+
+const
+  ISyncTo: array[0..2] of UTF8String  = ('Music', 'Lyrics', 'Off');
+type
+  TSyncToType = (stMusic, stLyrics, stOff);
 
-  IAudioOutputBufferSize:     array[0..9] of string  = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
-  IAudioOutputBufferSizeVals: array[0..9] of integer = ( 0,      256,   512 ,  1024 ,  2048 ,  4096 ,  8192 ,  16384 ,  32768 ,  65536 );
+const  
+  IAudioOutputBufferSize:     array[0..9] of UTF8String  = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
+  IAudioOutputBufferSizeVals: array[0..9] of integer     = ( 0,      256,   512 ,  1024 ,  2048 ,  4096 ,  8192 ,  16384 ,  32768 ,  65536 );
 
-  IAudioInputBufferSize:      array[0..9] of string  = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
-  IAudioInputBufferSizeVals:  array[0..9] of integer = ( 0,      256,   512 ,  1024 ,  2048 ,  4096 ,  8192 ,  16384 ,  32768 ,  65536 );
+  IAudioInputBufferSize:      array[0..9] of UTF8String  = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
+  IAudioInputBufferSizeVals:  array[0..9] of integer     = ( 0,      256,   512 ,  1024 ,  2048 ,  4096 ,  8192 ,  16384 ,  32768 ,  65536 );
 
   //Song Preview
-  IPreviewVolume:             array[0..10] of string = ('Off', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%');
-  IPreviewVolumeVals:         array[0..10] of single = ( 0,   0.10,  0.20,  0.30,  0.40,  0.50,  0.60,  0.70,  0.80,  0.90,   1.00  );
+  IPreviewVolume:             array[0..10] of UTF8String = ('Off', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%');
+  IPreviewVolumeVals:         array[0..10] of single     = ( 0,   0.10,  0.20,  0.30,  0.40,  0.50,  0.60,  0.70,  0.80,  0.90,   1.00  );
 
-  IPreviewFading:             array[0..5] of string  = ('Off', '1 Sec', '2 Secs', '3 Secs', '4 Secs', '5 Secs');
-  IPreviewFadingVals:         array[0..5] of integer = ( 0,     1,       2,        3,        4,        5      );
+  IPreviewFading:             array[0..5] of UTF8String  = ('Off', '1 Sec', '2 Secs', '3 Secs', '4 Secs', '5 Secs');
+  IPreviewFadingVals:         array[0..5] of integer     = ( 0,     1,       2,        3,        4,        5      );
 
-  ILyricsFont:    array[0..2] of string = ('Plain', 'OLine1', 'OLine2');
-  ILyricsEffect:  array[0..4] of string = ('Simple', 'Zoom', 'Slide', 'Ball', 'Shift');
-  ISolmization:   array[0..3] of string = ('Off', 'Euro', 'Jap', 'American');
-  INoteLines:     array[0..1] of string = ('Off', 'On');
+  ILyricsFont:    array[0..2] of UTF8String = ('Plain', 'OLine1', 'OLine2');
+  ILyricsEffect:  array[0..4] of UTF8String = ('Simple', 'Zoom', 'Slide', 'Ball', 'Shift');
+  INoteLines:     array[0..1] of UTF8String = ('Off', 'On');
 
-  IColor:         array[0..8] of string = ('Blue', 'Green', 'Pink', 'Red', 'Violet', 'Orange', 'Yellow', 'Brown', 'Black');
+  IColor:         array[0..8] of UTF8String = ('Blue', 'Green', 'Pink', 'Red', 'Violet', 'Orange', 'Yellow', 'Brown', 'Black');
 
   // Advanced
-  ILoadAnimation: array[0..1] of string = ('Off', 'On');
-  IEffectSing:    array[0..1] of string = ('Off', 'On');
-  IScreenFade:    array[0..1] of string = ('Off', 'On');
-  IAskbeforeDel:  array[0..1] of string = ('Off', 'On');
-  IOnSongClick:   array[0..2] of string = ('Sing', 'Select Players', 'Open Menu');
-  ILineBonus:     array[0..1] of string = ('Off', 'On');
-  IPartyPopup:    array[0..1] of string = ('Off', 'On');
+  ILoadAnimation: array[0..1] of UTF8String = ('Off', 'On');
+  IEffectSing:    array[0..1] of UTF8String = ('Off', 'On');
+  IScreenFade:    array[0..1] of UTF8String = ('Off', 'On');
+  IAskbeforeDel:  array[0..1] of UTF8String = ('Off', 'On');
+  IOnSongClick:   array[0..2] of UTF8String = ('Sing', 'Select Players', 'Open Menu');
+  sStartSing = 0;
+  sSelectPlayer = 1;
+  sOpenMenu = 2;
+
+  ILineBonus:     array[0..1] of UTF8String = ('Off', 'On');
+  IPartyPopup:    array[0..1] of UTF8String = ('Off', 'On');
 
-  IJoypad:        array[0..1] of string = ('Off', 'On');
-  IMouse:         array[0..2] of string = ('Off', 'Hardware Cursor', 'Software Cursor');
+  IJoypad:        array[0..1] of UTF8String = ('Off', 'On');
+  IMouse:         array[0..2] of UTF8String = ('Off', 'Hardware Cursor', 'Software Cursor');
 
   // Recording options
-  IChannelPlayer: array[0..6] of string = ('Off', '1', '2', '3', '4', '5', '6');
-  IMicBoost:      array[0..3] of string = ('Off', '+6dB', '+12dB', '+18dB');
+  IChannelPlayer: array[0..6] of UTF8String = ('Off', '1', '2', '3', '4', '5', '6');
+  IMicBoost:      array[0..3] of UTF8String = ('Off', '+6dB', '+12dB', '+18dB');
+
+{*
+ * Translated options
+ *}
 
 var
-  IDifficultyTranslated:       array[0..2] of string  = ('Easy', 'Medium', 'Hard');
-  ITabsTranslated:             array[0..1] of string  = ('Off', 'On');
+  ILanguageTranslated:         array of UTF8String;
 
-  ISortingTranslated:          array[0..7] of string  = ('Edition', 'Genre', 'Language', 'Folder', 'Title', 'Artist', 'Title2', 'Artist2');
+  IDifficultyTranslated:       array[0..2] of UTF8String  = ('Easy', 'Medium', 'Hard');
+  ITabsTranslated:             array[0..1] of UTF8String  = ('Off', 'On');
 
-  IDebugTranslated:            array[0..1] of string  = ('Off', 'On');
+  ISortingTranslated:          array[0..6] of UTF8String  = ('Edition', 'Genre', 'Language', 'Folder', 'Title', 'Artist', 'Artist2');
 
-  IFullScreenTranslated:       array[0..1] of string  = ('Off', 'On');
-  IVisualizerTranslated:       array[0..2] of string  = ('Off', 'WhenNoVideo','On');
+  IDebugTranslated:            array[0..1] of UTF8String  = ('Off', 'On');
 
-  IBackgroundMusicTranslated:  array[0..1] of string  = ('Off', 'On');
-  ISingWindowTranslated:       array[0..1] of string  = ('Small', 'Big');
+  IFullScreenTranslated:       array[0..1] of UTF8String  = ('Off', 'On');
+  IVisualizerTranslated:       array[0..2] of UTF8String  = ('Off', 'WhenNoVideo','On');
+
+  IBackgroundMusicTranslated:  array[0..1] of UTF8String  = ('Off', 'On');
+  ISingWindowTranslated:       array[0..1] of UTF8String  = ('Small', 'Big');
 
   //SingBar Mod
-  IOscilloscopeTranslated:     array[0..1] of string  = ('Off', 'On');
+  IOscilloscopeTranslated:     array[0..1] of UTF8String  = ('Off', 'On');
+
+  ISpectrumTranslated:         array[0..1] of UTF8String  = ('Off', 'On');
+  ISpectrographTranslated:     array[0..1] of UTF8String  = ('Off', 'On');
+  IMovieSizeTranslated:        array[0..2] of UTF8String  = ('Half', 'Full [Vid]', 'Full [BG+Vid]');
 
-  ISpectrumTranslated:         array[0..1] of string  = ('Off', 'On');
-  ISpectrographTranslated:     array[0..1] of string  = ('Off', 'On');
-  IMovieSizeTranslated:        array[0..2] of string  = ('Half', 'Full [Vid]', 'Full [BG+Vid]');
+  IClickAssistTranslated:      array[0..1] of UTF8String  = ('Off', 'On');
+  IBeatClickTranslated:        array[0..1] of UTF8String  = ('Off', 'On');
+  ISavePlaybackTranslated:     array[0..1] of UTF8String  = ('Off', 'On');
 
-  IClickAssistTranslated:      array[0..1] of string  = ('Off', 'On');
-  IBeatClickTranslated:        array[0..1] of string  = ('Off', 'On');
-  ISavePlaybackTranslated:     array[0..1] of string  = ('Off', 'On');
+  IVoicePassthroughTranslated: array[0..1] of UTF8String  = ('Off', 'On');
 
-  IVoicePassthroughTranslated: array[0..1] of string  = ('Off', 'On');
+  ISyncToTranslated:           array[0..2] of UTF8String  = ('Music', 'Lyrics', 'Off');
 
   //Song Preview
-  IPreviewVolumeTranslated:    array[0..10] of string = ('Off', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%');
+  IPreviewVolumeTranslated:    array[0..10] of UTF8String = ('Off', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%');
 
-  IAudioOutputBufferSizeTranslated: array[0..9] of string  = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
+  IAudioOutputBufferSizeTranslated: array[0..9] of UTF8String  = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
 
-  IAudioInputBufferSizeTranslated:  array[0..9] of string  = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
+  IAudioInputBufferSizeTranslated:  array[0..9] of UTF8String  = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
 
-  IPreviewFadingTranslated:    array[0..5] of string  = ('Off', '1 Sec', '2 Secs', '3 Secs', '4 Secs', '5 Secs');
+  IPreviewFadingTranslated:    array[0..5] of UTF8String  = ('Off', '1 Sec', '2 Secs', '3 Secs', '4 Secs', '5 Secs');
 
-  ILyricsFontTranslated:       array[0..2] of string = ('Plain', 'OLine1', 'OLine2');
-  ILyricsEffectTranslated:     array[0..4] of string = ('Simple', 'Zoom', 'Slide', 'Ball', 'Shift');
-  ISolmizationTranslated:      array[0..3] of string = ('Off', 'Euro', 'Jap', 'American');
-  INoteLinesTranslated:        array[0..1] of string = ('Off', 'On');
+  ILyricsFontTranslated:       array[0..2] of UTF8String = ('Plain', 'OLine1', 'OLine2');
+  ILyricsEffectTranslated:     array[0..4] of UTF8String = ('Simple', 'Zoom', 'Slide', 'Ball', 'Shift');
+  INoteLinesTranslated:        array[0..1] of UTF8String = ('Off', 'On');
 
-  IColorTranslated:            array[0..8] of string = ('Blue', 'Green', 'Pink', 'Red', 'Violet', 'Orange', 'Yellow', 'Brown', 'Black');
+  IColorTranslated:            array[0..8] of UTF8String = ('Blue', 'Green', 'Pink', 'Red', 'Violet', 'Orange', 'Yellow', 'Brown', 'Black');
 
   // Advanced
-  ILoadAnimationTranslated:    array[0..1] of string = ('Off', 'On');
-  IEffectSingTranslated:       array[0..1] of string = ('Off', 'On');
-  IScreenFadeTranslated:       array[0..1] of string = ('Off', 'On');
-  IAskbeforeDelTranslated:     array[0..1] of string = ('Off', 'On');
-  IOnSongClickTranslated:      array[0..2] of string = ('Sing', 'Select Players', 'Open Menu');
-  ILineBonusTranslated:        array[0..1] of string = ('Off', 'On');
-  IPartyPopupTranslated:       array[0..1] of string = ('Off', 'On');
+  ILoadAnimationTranslated:    array[0..1] of UTF8String = ('Off', 'On');
+  IEffectSingTranslated:       array[0..1] of UTF8String = ('Off', 'On');
+  IScreenFadeTranslated:       array[0..1] of UTF8String = ('Off', 'On');
+  IAskbeforeDelTranslated:     array[0..1] of UTF8String = ('Off', 'On');
+  IOnSongClickTranslated:      array[0..2] of UTF8String = ('Sing', 'Select Players', 'Open Menu');
+  ILineBonusTranslated:        array[0..1] of UTF8String = ('Off', 'On');
+  IPartyPopupTranslated:       array[0..1] of UTF8String = ('Off', 'On');
 
-  IJoypadTranslated:           array[0..1] of string = ('Off', 'On');
-  IMouseTranslated:            array[0..2] of string = ('Off', 'Hardware Cursor', 'Software Cursor');
+  IJoypadTranslated:           array[0..1] of UTF8String = ('Off', 'On');
+  IMouseTranslated:            array[0..2] of UTF8String = ('Off', 'Hardware Cursor', 'Software Cursor');
 
   // Recording options
-  IChannelPlayerTranslated:    array[0..6] of string = ('Off', '1', '2', '3', '4', '5', '6');
-  IMicBoostTranslated:         array[0..3] of string = ('Off', '+6dB', '+12dB', '+18dB');
+  IChannelPlayerTranslated:    array[0..6] of UTF8String = ('Off', '1', '2', '3', '4', '5', '6');
+  IMicBoostTranslated:         array[0..3] of UTF8String = ('Off', '+6dB', '+12dB', '+18dB');
 
 implementation
 
 uses
   StrUtils,
-  UMain,
   SDL,
+  UCommandLine,
   ULanguage,
   UPlatform,
-  USkins,
+  UMain,
   URecord,
-  UCommandLine,
-  UPath;
+  USkins,
+  UThemes,
+  UPathUtils,
+  UUnicodeUtils;
 
 (**
  * Translate and set the values of options, which need translation. 
  *)
 procedure TIni.TranslateOptionValues;
+var
+  I: integer;
 begin
-  ULanguage.Language.ChangeLanguage(ILanguage[Language]);
-  
+  // Load Languagefile
+  if (Params.Language <> -1) then
+    ULanguage.Language.ChangeLanguage(ILanguage[Params.Language])
+  else
+    ULanguage.Language.ChangeLanguage(ILanguage[Ini.Language]);
+
+  SetLength(ILanguageTranslated, Length(ILanguage));
+  for I := 0 to High(ILanguage) do
+  begin
+    ILanguageTranslated[I] := ULanguage.Language.Translate(
+      'OPTION_VALUE_' + UpperCase(ILanguage[I])
+    );
+  end;
+
   IDifficultyTranslated[0]            := ULanguage.Language.Translate('OPTION_VALUE_EASY');
   IDifficultyTranslated[1]            := ULanguage.Language.Translate('OPTION_VALUE_MEDIUM');
   IDifficultyTranslated[2]            := ULanguage.Language.Translate('OPTION_VALUE_HARD');
@@ -345,8 +384,7 @@ begin
   ISortingTranslated[3]               := ULanguage.Language.Translate('OPTION_VALUE_FOLDER');
   ISortingTranslated[4]               := ULanguage.Language.Translate('OPTION_VALUE_TITLE');
   ISortingTranslated[5]               := ULanguage.Language.Translate('OPTION_VALUE_ARTIST');
-  ISortingTranslated[6]               := ULanguage.Language.Translate('OPTION_VALUE_TITLE2');
-  ISortingTranslated[7]               := ULanguage.Language.Translate('OPTION_VALUE_ARTIST2');
+  ISortingTranslated[6]               := ULanguage.Language.Translate('OPTION_VALUE_ARTIST2');
 
   IDebugTranslated[0]                 := ULanguage.Language.Translate('OPTION_VALUE_OFF');
   IDebugTranslated[1]                 := ULanguage.Language.Translate('OPTION_VALUE_ON');
@@ -389,6 +427,10 @@ begin
   IVoicePassthroughTranslated[0]      := ULanguage.Language.Translate('OPTION_VALUE_OFF');
   IVoicePassthroughTranslated[1]      := ULanguage.Language.Translate('OPTION_VALUE_ON');
 
+  ISyncToTranslated[Ord(stMusic)]     := ULanguage.Language.Translate('OPTION_VALUE_MUSIC');
+  ISyncToTranslated[Ord(stLyrics)]    := ULanguage.Language.Translate('OPTION_VALUE_LYRICS');
+  ISyncToTranslated[Ord(stOff)]       := ULanguage.Language.Translate('OPTION_VALUE_OFF');
+
   ILyricsFontTranslated[0]            := ULanguage.Language.Translate('OPTION_VALUE_PLAIN');
   ILyricsFontTranslated[1]            := ULanguage.Language.Translate('OPTION_VALUE_OLINE1');
   ILyricsFontTranslated[2]            := ULanguage.Language.Translate('OPTION_VALUE_OLINE2');
@@ -399,11 +441,6 @@ begin
   ILyricsEffectTranslated[3]          := ULanguage.Language.Translate('OPTION_VALUE_BALL');
   ILyricsEffectTranslated[4]          := ULanguage.Language.Translate('OPTION_VALUE_SHIFT');
 
-  ISolmizationTranslated[0]           := ULanguage.Language.Translate('OPTION_VALUE_OFF');
-  ISolmizationTranslated[1]           := ULanguage.Language.Translate('OPTION_VALUE_EURO');
-  ISolmizationTranslated[2]           := ULanguage.Language.Translate('OPTION_VALUE_JAPAN');
-  ISolmizationTranslated[3]           := ULanguage.Language.Translate('OPTION_VALUE_AMERICAN');
-
   INoteLinesTranslated[0]             := ULanguage.Language.Translate('OPTION_VALUE_OFF');
   INoteLinesTranslated[1]             := ULanguage.Language.Translate('OPTION_VALUE_ON');
 
@@ -415,7 +452,7 @@ begin
   IColorTranslated[5]                 := ULanguage.Language.Translate('OPTION_VALUE_ORANGE');
   IColorTranslated[6]                 := ULanguage.Language.Translate('OPTION_VALUE_YELLOW');
   IColorTranslated[7]                 := ULanguage.Language.Translate('OPTION_VALUE_BROWN');
-  IColorTranslated[8]                 := ULanguage.Language.Translate('OPTION_VALUE_BALCK');
+  IColorTranslated[8]                 := ULanguage.Language.Translate('OPTION_VALUE_BLACK');
 
   // Advanced
   ILoadAnimationTranslated[0]         := ULanguage.Language.Translate('OPTION_VALUE_OFF');
@@ -507,14 +544,6 @@ begin
 
 end;
 
-(**
- * Returns the filename without its fileextension
- *)
-function TIni.RemoveFileExt(FullName: string): string;
-begin
-  Result := ChangeFileExt(FullName, '');
-end;
-
 (**
  * Extracts an index of a key that is surrounded by a Prefix/Suffix pair.
  * Example: ExtractKeyIndex('MyKey[1]', '[', ']') will return 1.
@@ -561,35 +590,13 @@ begin
   end;
 end;
 
-(**
- * Returns the index of Value in SearchArray
- * or -1 if Value is not in SearchArray.
- *)
-function TIni.GetArrayIndex(const SearchArray: array of string; Value: string;
-    CaseInsensitiv: boolean = false): integer;
-var
-  i: integer;
-begin
-  Result := -1;
-
-  for i := 0 to High(SearchArray) do
-  begin
-    if (SearchArray[i] = Value) or
-       (CaseInsensitiv and (UpperCase(SearchArray[i]) = UpperCase(Value))) then
-    begin
-      Result := i;
-      Break;
-    end;
-  end;
-end;
-
 (**
  * Reads the property IniSeaction:IniProperty from IniFile and
  * finds its corresponding index in SearchArray.
  * If SearchArray does not contain the property value, the default value is
  * returned.
  *)
-function TIni.ReadArrayIndex(const SearchArray: array of string; IniFile: TCustomIniFile;
+function TIni.ReadArrayIndex(const SearchArray: array of UTF8String; IniFile: TCustomIniFile;
     IniSection: string; IniProperty: string; Default: integer): integer;
 var
   StrValue: string;
@@ -625,7 +632,7 @@ begin
     if (DeviceIndex >= 0) then
     begin
       if not IniFile.ValueExists('Record', Format('DeviceName[%d]', [DeviceIndex])) then
-        break;
+        Continue;
 
       // resize list
       SetLength(InputDeviceConfig, Length(InputDeviceConfig)+1);
@@ -637,6 +644,7 @@ begin
       DeviceCfg := @InputDeviceConfig[High(InputDeviceConfig)];
       DeviceCfg.Name := IniFile.ReadString('Record', Format('DeviceName[%d]', [DeviceIndex]), '');
       DeviceCfg.Input := IniFile.ReadInteger('Record', Format('Input[%d]', [DeviceIndex]), 0);
+      DeviceCfg.Latency := IniFile.ReadInteger('Record', Format('Latency[%d]', [DeviceIndex]), LATENCY_AUTODETECT);
 
       // find the largest channel-number of the current device in the ini-file
       ChannelCount := GetMaxKeyIndex(RecordKeys, 'Channel', Format('[%d]', [DeviceIndex]));
@@ -675,6 +683,8 @@ begin
                         InputDeviceConfig[DeviceIndex].Name);
     IniFile.WriteInteger('Record', Format('Input[%d]', [DeviceIndex+1]),
                         InputDeviceConfig[DeviceIndex].Input);
+    IniFile.WriteInteger('Record', Format('Latency[%d]', [DeviceIndex+1]),
+                        InputDeviceConfig[DeviceIndex].Latency);
 
     // Channel-to-Player Mapping
     for ChannelIndex := 0 to High(InputDeviceConfig[DeviceIndex].ChannelToPlayerMap) do
@@ -702,9 +712,9 @@ begin
   // Load song-paths
   for I := 0 to PathStrings.Count-1 do
   begin
-    if (AnsiStartsText('SongDir', PathStrings[I])) then
+    if (Pos('SONGDIR', UpperCase(PathStrings[I])) = 1) then
     begin
-      AddSongPath(IniFile.ReadString('Directories', PathStrings[I], ''));
+      AddSongPath(Path(IniFile.ReadString('Directories', PathStrings[I], '')));
     end;
   end;
 
@@ -712,38 +722,7 @@ begin
 end;
 
 procedure TIni.LoadThemes(IniFile: TCustomIniFile);
-var
-  SearchResult: TSearchRec;
-  ThemeIni:     TMemIniFile;
-  ThemeName:    string;
-  I: integer;
 begin
-  // Theme
-  SetLength(ITheme, 0);
-  Log.LogStatus('Searching for Theme : ' + ThemePath + '*.ini', 'Theme');
-
-  FindFirst(ThemePath + '*.ini',faAnyFile, SearchResult);
-  Repeat
-    Log.LogStatus('Found Theme: ' + SearchResult.Name, 'Theme');
-
-    //Read Themename from Theme
-    ThemeIni := TMemIniFile.Create(SearchResult.Name);
-    ThemeName := UpperCase(ThemeIni.ReadString('Theme','Name', RemoveFileExt(SearchResult.Name)));
-    ThemeIni.Free;
-
-    //Search for Skins for this Theme
-    for I := Low(Skin.Skin) to High(Skin.Skin) do
-    begin
-      if UpperCase(Skin.Skin[I].Theme) = ThemeName then
-      begin
-        SetLength(ITheme, Length(ITheme)+1);
-        ITheme[High(ITheme)] := RemoveFileExt(SearchResult.Name);
-        break;
-      end;
-    end;
-  until FindNext(SearchResult) <> 0;
-  FindClose(SearchResult);
-
   // No Theme Found
   if (Length(ITheme) = 0) then
   begin
@@ -757,13 +736,22 @@ begin
   // Skin
   Skin.onThemeChange;
 
-  SkinNo := GetArrayIndex(ISkin, IniFile.ReadString('Themes',    'Skin',   ISkin[0]));
+  SkinNo := GetArrayIndex(ISkin, IniFile.ReadString('Themes',    'Skin',   ISkin[UThemes.Theme.Themes[Theme].DefaultSkin]));
+
+  { there may be a not existing skin in the ini file
+    e.g. due to manual edit or corrupted file.
+    in this case we load the first Skin }
+  if SkinNo = -1 then
+    SkinNo := 0;
+
+  // Color
+  Color := GetArrayIndex(IColor, IniFile.ReadString('Themes',    'Color', IColor[Skin.GetDefaultColor(SkinNo)]));
 end;
 
 procedure TIni.LoadScreenModes(IniFile: TCustomIniFile);
 
   // swap two strings
-  procedure swap(var s1, s2: string);
+  procedure swap(var s1, s2: UTF8String);
   var
     s3: string;
   begin
@@ -800,17 +788,25 @@ begin
   else if (Modes = PPSDL_Rect(-1)) then
   begin
     // Fallback to some standard resolutions
-    SetLength(IResolution, 10);
+    SetLength(IResolution, 18);
     IResolution[0] := '640x480';
     IResolution[1] := '800x600';
     IResolution[2] := '1024x768';
-    IResolution[3] := '1152x864';
-    IResolution[4] := '1280x800';
-    IResolution[5] := '1280x960';
-    IResolution[6] := '1400x1050';
-    IResolution[7] := '1440x900';
-    IResolution[8] := '1600x1200';
-    IResolution[9] := '1680x1050';
+    IResolution[3] := '1152x666';;
+    IResolution[4] := '1152x864';
+    IResolution[5] := '1280x800';
+    IResolution[6] := '1280x960';
+    IResolution[7] := '1280x1024';
+    IResolution[8] := '1366x768';
+    IResolution[9] := '1400x1050';
+    IResolution[10] := '1440x900';
+    IResolution[11] := '1600x900';
+    IResolution[12] := '1600x1200';
+    IResolution[13] := '1680x1050';
+    IResolution[14] := '1920x1080';
+    IResolution[15] := '1920x1200';
+    IResolution[16] := '2048x1152';
+    IResolution[17] := '2560x1600';
 
     Resolution := GetArrayIndex(IResolution, IniFile.ReadString('Graphics', 'Resolution', '800x600'));
     if Resolution = -1 then
@@ -872,19 +868,15 @@ var
 begin
   GamePath := Platform.GetGameUserPath;
 
-  Log.LogStatus( 'GamePath : ' +GamePath , '' );
+  Log.LogStatus( 'GamePath : ' +GamePath.ToNative , '' );
 
-  if (Params.ConfigFile <> '') then
-    try
-      FileName := Params.ConfigFile;
-    except
-      FileName := GamePath + 'config.ini';
-    end
+  if (Params.ConfigFile.IsSet) then
+    FileName := Params.ConfigFile
   else
-    FileName := GamePath + 'config.ini';
+    FileName := GamePath.Append('config.ini');
 
-  Log.LogStatus( 'Using config : ' + FileName , 'Ini');
-  IniFile := TMemIniFile.Create( FileName );
+  Log.LogStatus('Using config : ' + FileName.ToNative, 'Ini');
+  IniFile := TMemIniFile.Create(FileName.ToNative);
 
   // Name
   for I := 0 to 11 do
@@ -904,22 +896,24 @@ begin
 
   // Language
   Language := GetArrayIndex(ILanguage, IniFile.ReadString('Game', 'Language', 'English'));
-  //Language.ChangeLanguage(ILanguage[Language]);
 
   // Tabs
   Tabs := GetArrayIndex(ITabs, IniFile.ReadString('Game', 'Tabs', ITabs[0]));
   TabsAtStartup := Tabs;	//Tabs at Startup fix
 
   // Song Sorting
-  Sorting := GetArrayIndex(ISorting, IniFile.ReadString('Game', 'Sorting', ISorting[0]));
+  Sorting := GetArrayIndex(ISorting, IniFile.ReadString('Game', 'Sorting', ISorting[Ord(sEdition)]));
 
   // Debug
   Debug := GetArrayIndex(IDebug, IniFile.ReadString('Game', 'Debug', IDebug[0]));
 
   LoadScreenModes(IniFile);
 
-  // TextureSize
-  TextureSize := GetArrayIndex(ITextureSize, IniFile.ReadString('Graphics', 'TextureSize', ITextureSize[1]));
+  // TextureSize (aka CachedCoverSize)
+  // Note: a default cached cover size of 128 pixels is big enough,
+  // 256 pixels are already noticeably slow with 180 covers in the song-screen
+  // displayed at once. In additon the covers.db will be too big.
+  TextureSize := GetArrayIndex(ITextureSize, IniFile.ReadString('Graphics', 'TextureSize', '128'));
 
   // SingWindow
   SingWindow := GetArrayIndex(ISingWindow, IniFile.ReadString('Graphics', 'SingWindow', 'Big'));
@@ -961,19 +955,13 @@ begin
   LyricsFont := GetArrayIndex(ILyricsFont, IniFile.ReadString('Lyrics', 'LyricsFont', ILyricsFont[0]));
 
   // Lyrics Effect
-  LyricsEffect := GetArrayIndex(ILyricsEffect, IniFile.ReadString('Lyrics', 'LyricsEffect', ILyricsEffect[2]));
-
-  // Solmization
-  Solmization := GetArrayIndex(ISolmization, IniFile.ReadString('Lyrics', 'Solmization', ISolmization[0]));
+  LyricsEffect := GetArrayIndex(ILyricsEffect, IniFile.ReadString('Lyrics', 'LyricsEffect', ILyricsEffect[4]));
 
   // NoteLines
   NoteLines := GetArrayIndex(INoteLines, IniFile.ReadString('Lyrics', 'NoteLines', INoteLines[1]));
 
   LoadThemes(IniFile);
 
-  // Color
-  Color := GetArrayIndex(IColor, IniFile.ReadString('Themes',    'Color',   IColor[0]));
-
   LoadInputDeviceCfg(IniFile);
 
   // LoadAnimation
@@ -993,7 +981,7 @@ begin
 {**
  * Background music
  *}
-  BackgroundMusicOption := GetArrayIndex(IBackgroundMusic, IniFile.ReadString('Sound', 'BackgroundMusic', 'Off'));
+  BackgroundMusicOption := GetArrayIndex(IBackgroundMusic, IniFile.ReadString('Sound', 'BackgroundMusic', 'On'));
 
   // EffectSing
   EffectSing := GetArrayIndex(IEffectSing, IniFile.ReadString('Advanced', 'EffectSing', 'On'));
@@ -1010,6 +998,9 @@ begin
   // PartyPopup
   PartyPopup := GetArrayIndex(IPartyPopup, IniFile.ReadString('Advanced', 'PartyPopup', 'On'));
 
+  // SyncTo
+  SyncTo := GetArrayIndex(ISyncTo, IniFile.ReadString('Advanced', 'SyncTo', ISyncTo[Ord(stMusic)]));
+
   // Joypad
   Joypad := GetArrayIndex(IJoypad, IniFile.ReadString('Controller',    'Joypad',   IJoypad[0]));
 
@@ -1027,13 +1018,13 @@ procedure TIni.Save;
 var
   IniFile: TIniFile;
 begin
-  if (FileExists(Filename) and FileIsReadOnly(Filename)) then
+  if (Filename.IsFile and Filename.IsReadOnly) then
   begin
     Log.LogError('Config-file is read-only', 'TIni.Save');
     Exit;
   end;
 
-  IniFile := TIniFile.Create(Filename);
+  IniFile := TIniFile.Create(Filename.ToNative);
 
   // Players
   IniFile.WriteString('Game', 'Players', IPlayers[Players]);
@@ -1116,9 +1107,6 @@ begin
   // Lyrics Effect
   IniFile.WriteString('Lyrics', 'LyricsEffect', ILyricsEffect[LyricsEffect]);
 
-  // Solmization
-  IniFile.WriteString('Lyrics', 'Solmization', ISolmization[Solmization]);
-
   // NoteLines
   IniFile.WriteString('Lyrics', 'NoteLines', INoteLines[NoteLines]);
 
@@ -1154,6 +1142,9 @@ begin
   //Party Popup
   IniFile.WriteString('Advanced', 'PartyPopup', IPartyPopup[PartyPopup]);
 
+  //SyncTo
+  IniFile.WriteString('Advanced', 'SyncTo', ISyncTo[SyncTo]);
+
   // Joypad
   IniFile.WriteString('Controller', 'Joypad', IJoypad[Joypad]);
 
@@ -1173,17 +1164,17 @@ var
   IniFile: TIniFile;
   I:       integer;
 begin
-  if not FileIsReadOnly(Filename) then
+  if not Filename.IsReadOnly() then
   begin
-    IniFile := TIniFile.Create(Filename);
+    IniFile := TIniFile.Create(Filename.ToNative);
 
     //Name Templates for Names Mod
-    for I := 1 to 12 do
-      IniFile.WriteString('Name', 'P' + IntToStr(I), Name[I-1]);
-    for I := 1 to 3 do
-      IniFile.WriteString('NameTeam', 'T' + IntToStr(I), NameTeam[I-1]);
-    for I := 1 to 12 do
-      IniFile.WriteString('NameTemplate', 'Name' + IntToStr(I), NameTemplate[I-1]);
+    for I := 0 to High(Name) do
+      IniFile.WriteString('Name', 'P' + IntToStr(I+1), Name[I]);
+    for I := 0 to High(NameTeam) do
+      IniFile.WriteString('NameTeam', 'T' + IntToStr(I+1), NameTeam[I]);
+    for I := 0 to High(NameTemplate) do
+      IniFile.WriteString('NameTemplate', 'Name' + IntToStr(I+1), NameTemplate[I]);
 
     IniFile.Free;
   end;
@@ -1193,9 +1184,9 @@ procedure TIni.SaveLevel;
 var
   IniFile: TIniFile;
 begin
-  if not FileIsReadOnly(Filename) then
+  if not Filename.IsReadOnly() then
   begin
-    IniFile := TIniFile.Create(Filename);
+    IniFile := TIniFile.Create(Filename.ToNative);
 
     // Difficulty
     IniFile.WriteString('Game', 'Difficulty', IDifficulty[Difficulty]);
diff --git a/cmake/src/base/ULanguage.pas b/cmake/src/base/ULanguage.pas
index 02cd7712..5f8a2692 100644
--- a/cmake/src/base/ULanguage.pas
+++ b/cmake/src/base/ULanguage.pas
@@ -33,33 +33,41 @@ interface
 
 {$I switches.inc}
 
+uses
+  UUnicodeUtils;
+
 type
   TLanguageEntry = record
-    ID:     string;
-    Text:   string;
+    ID:     AnsiString;  //**< identifier (ASCII)
+    Text:   UTF8String;  //**< translation (UTF-8)
   end;
 
   TLanguageList = record
-    Name:     string;
-    {FileName: string; }
+    Name:     AnsiString;  //**< language name (ASCII)
   end;
 
+  TLanguageEntryArray = array of TLanguageEntry;
+
   TLanguage = class
-    public
-      Entry:  array of TLanguageEntry; //Entrys of Chosen Language
-      SEntry: array of TLanguageEntry; //Entrys of Standard Language
-      CEntry: array of TLanguageEntry; //Constant Entrys e.g. Version
-      Implode_Glue1, Implode_Glue2: String;
-    public
+    private
       List:   array of TLanguageList;
 
-      constructor Create;
+      Entry:        TLanguageEntryArray; //**< Entrys of Chosen Language
+      EntryDefault: TLanguageEntryArray; //**< Entrys of Standard Language
+      EntryConst:   TLanguageEntryArray; //**< Constant Entrys e.g. Version
+
+      Implode_Glue1, Implode_Glue2: UTF8String;
+
       procedure LoadList;
-      function Translate(Text: String): String;
-      procedure ChangeLanguage(Language: String);
-      procedure AddConst(ID, Text: String);
-      procedure ChangeConst(ID, Text: String);
-      function Implode(Pieces: Array of String): String;
+      function FindID(const ID: AnsiString; const EntryList: TLanguageEntryArray): integer;
+
+    public
+      constructor Create;
+      function Translate(const Text: RawByteString): UTF8String;
+      procedure ChangeLanguage(const Language: AnsiString);
+      procedure AddConst(const ID: AnsiString; const Text: UTF8String);
+      procedure ChangeConst(const ID: AnsiString; const Text: UTF8String);
+      function Implode(const Pieces: array of UTF8String): UTF8String;
   end;
 
 var
@@ -69,20 +77,18 @@ implementation
 
 uses
   UMain,
-  // UFiles,
   UIni,
   IniFiles,
   Classes,
   SysUtils,
-  {$IFDEF win32}
-    Windows,
-  {$ENDIF}
   ULog,
-  UPath;
+  UPath,
+  UFilesystem,
+  UPathUtils;
 
-//----------
-//Create - Construct Class then LoadList + Standard Language + Set Standard Implode Glues
-//----------
+{**
+ * LoadList, set default language, set standard implode glues
+ *}
 constructor TLanguage.Create;
 var
   I, J: Integer;
@@ -107,9 +113,9 @@ begin
     begin
       ChangeLanguage('English');
 
-      SetLength(SEntry, Length(Entry));
-      for J := low(Entry) to high(Entry) do
-        SEntry[J] := Entry[J];
+      SetLength(EntryDefault, Length(Entry));
+      for J := 0 to high(Entry) do
+        EntryDefault[J] := Entry[J];
 
       SetLength(Entry, 0);
       
@@ -123,41 +129,44 @@ begin
   
 end;
 
-//----------
-//LoadList - Parse the Language Dir searching Translations
-//----------
+{**
+ * Parse the Language Dir searching Translations
+ *}
 procedure TLanguage.LoadList;
 var
-  SR:     TSearchRec;   // for parsing directory
+  Iter: IFileIterator;
+  IniInfo: TFileInfo;
+  LangName: string;
 begin
   SetLength(List, 0);
   SetLength(ILanguage, 0);
 
-  if FindFirst(LanguagesPath + '*.ini', 0, SR) = 0 then begin
-    repeat
-      SetLength(List, Length(List)+1);
-      SetLength(ILanguage, Length(ILanguage)+1);
-      SR.Name := ChangeFileExt(SR.Name, '');
+  Iter := FileSystem.FileFind(LanguagesPath.Append('*.ini'), 0);
+  while(Iter.HasNext) do
+  begin
+    IniInfo := Iter.Next;
+
+    LangName := IniInfo.Name.SetExtension('').ToUTF8;
 
-      List[High(List)].Name := SR.Name;
-      ILanguage[High(ILanguage)] := SR.Name;
+    SetLength(List, Length(List)+1);
+    List[High(List)].Name := LangName;
 
-    until FindNext(SR) <> 0;
-  SysUtils.FindClose(SR);
-  end; // if FindFirst
+    SetLength(ILanguage, Length(ILanguage)+1);
+    ILanguage[High(ILanguage)] := LangName;
+  end;
 end;
 
-//----------
-//ChangeLanguage - Load the specified LanguageFile
-//----------
-procedure TLanguage.ChangeLanguage(Language: String);
+{**
+ * Load the specified LanguageFile
+ *}
+procedure TLanguage.ChangeLanguage(const Language: AnsiString);
 var
-  IniFile:    TIniFile;
+  IniFile:    TUnicodeMemIniFile;
   E:          integer; // entry
   S:          TStringList;
 begin
   SetLength(Entry, 0);
-  IniFile := TIniFile.Create(LanguagesPath + Language + '.ini');
+  IniFile := TUnicodeMemIniFile.Create(LanguagesPath.Append(Language + '.ini'));
   S := TStringList.Create;
 
   IniFile.ReadSectionValues('Text', S);
@@ -177,80 +186,107 @@ begin
   IniFile.Free;
 end;
 
-//----------
-//Translate - Translate the Text
-//----------
-Function TLanguage.Translate(Text: String): String;
+{**
+ * Find the index of ID an array of language entries.
+ * @returns the index on success, -1 otherwise.
+ *}
+function TLanguage.FindID(const ID: AnsiString; const EntryList: TLanguageEntryArray): integer;
+var
+  Index: integer;
+begin
+  for Index := 0 to High(EntryList) do
+  begin
+    if ID = EntryList[Index].ID then
+    begin
+      Result := Index;
+      Exit;
+    end;
+  end;
+  Result := -1;
+end;
+
+{**
+ * Translate the Text.
+ * If Text is an ID, text will be translated according to the current language
+ * setting. If Text is not a known ID, it will be returned as is. 
+ * @param Text either an ID or an UTF-8 encoded string 
+ *}
+function TLanguage.Translate(const Text: RawByteString): UTF8String;
 var
-  E:    integer; // entry
+  E:  integer; // entry
+  ID: AnsiString;
+  EntryIndex: integer;
 begin
+  // fallback result in case Text is not a known ID
   Result := Text;
-  Text := Uppercase(Result);
+
+  // normalize ID case
+  ID := UpperCase(Text);
+
+  // Check if ID exists
 
   //Const Mod
-  for E := 0 to high(CEntry) do
-    if Text = CEntry[E].ID then
-    begin
-     Result := CEntry[E].Text;
-     exit;
-    end;
-  //Const Mod End
+  EntryIndex := FindID(ID, EntryConst);
+  if (EntryIndex >= 0) then
+  begin
+    Result := EntryConst[EntryIndex].Text;
+    Exit;
+  end;
 
-  for E := 0 to high(Entry) do
-    if Text = Entry[E].ID then
-    begin
-     Result := Entry[E].Text;
-     exit;
-    end;
+  EntryIndex := FindID(ID, Entry);
+  if (EntryIndex >= 0) then
+  begin
+    Result := Entry[EntryIndex].Text;
+    Exit;
+  end;
 
   //Standard Language (If a Language File is Incomplete)
   //Then use Standard Language
-    for E := low(SEntry) to high(SEntry) do
-      if Text = SEntry[E].ID then
-      begin
-        Result := SEntry[E].Text;
-        Break;
-      end;
-  //Standard Language END
+  EntryIndex := FindID(ID, EntryDefault);
+  if (EntryIndex >= 0) then
+  begin
+    Result := EntryDefault[EntryIndex].Text;
+    Exit;
+  end;
 end;
 
-//----------
-//AddConst - Add a Constant ID that will be Translated but not Loaded from the LanguageFile
-//----------
-procedure TLanguage.AddConst (ID, Text: String);
+{**
+ * Add a Constant ID that will be Translated but not Loaded from the LanguageFile
+ *}
+procedure TLanguage.AddConst(const ID: AnsiString; const Text: UTF8String);
 begin
-  SetLength (CEntry, Length(CEntry) + 1);
-  CEntry[high(CEntry)].ID := ID;
-  CEntry[high(CEntry)].Text := Text;
+  SetLength (EntryConst, Length(EntryConst) + 1);
+  EntryConst[high(EntryConst)].ID := ID;
+  EntryConst[high(EntryConst)].Text := Text;
 end;
 
-//----------
-//ChangeConst - Change a Constant Value by ID
-//----------
-procedure TLanguage.ChangeConst(ID, Text: String);
+{**
+ * Change a Constant Value by ID
+ *}
+procedure TLanguage.ChangeConst(const ID: AnsiString; const Text: UTF8String);
 var
   I: Integer;
 begin
-  for I := 0 to high(CEntry) do
+  for I := 0 to high(EntryConst) do
   begin
-    if CEntry[I].ID = ID then
+    if EntryConst[I].ID = ID then
     begin
-     CEntry[I].Text := Text;
+     EntryConst[I].Text := Text;
      Break;
     end;
   end;
 end;
 
-//----------
-//Implode - Connect an Array of Strings with ' and ' or ', ' to one String
-//----------
-function TLanguage.Implode(Pieces: Array of String): String;
+{**
+ * Connect an array of strings with ' and ' or ', ' to one string
+ *}
+function TLanguage.Implode(const Pieces: array of UTF8String): UTF8String;
 var
   I: Integer;
 begin
   Result := '';
   //Go through Pieces
-  for I := low(Pieces) to high(Pieces) do
+  for I := 0 to high(Pieces) do
   begin
     //Add Value
     Result := Result + Pieces[I];
diff --git a/cmake/src/base/ULog.pas b/cmake/src/base/ULog.pas
index a872729a..e4ff4862 100644
--- a/cmake/src/base/ULog.pas
+++ b/cmake/src/base/ULog.pas
@@ -34,7 +34,8 @@ interface
 {$I switches.inc}
 
 uses
-  Classes;
+  Classes,
+  UPath;
 
 (*
  * LOG_LEVEL_[TYPE] defines the "minimum" index for logs of type TYPE. Each
@@ -115,7 +116,7 @@ type
     // voice
     procedure LogVoice(SoundNr: integer);
     // buffer
-    procedure LogBuffer(const buf : Pointer; const bufLength : Integer; const filename : string);
+    procedure LogBuffer(const buf : Pointer; const bufLength : Integer; const filename : IPath);
   end;
 
 procedure DebugWriteln(const aString: String);
@@ -133,7 +134,7 @@ uses
   UTime,
   UCommon,
   UCommandLine,
-  UPath;
+  UPathUtils;
 
 (*
  * Write to console if in debug mode (Thread-safe).
@@ -198,7 +199,7 @@ begin
     if not BenchmarkFileOpened then
     begin
       BenchmarkFileOpened := true;
-      AssignFile(BenchmarkFile, LogPath + 'Benchmark.log');
+      AssignFile(BenchmarkFile, LogPath.Append('Benchmark.log').ToNative);
       {$I-}
       Rewrite(BenchmarkFile);
       if IOResult = 0 then
@@ -270,7 +271,7 @@ procedure TLog.LogToFile(const Text: string);
 begin
   if (FileOutputEnabled and not LogFileOpened) then
   begin
-    AssignFile(LogFile, LogPath + 'Error.log');
+    AssignFile(LogFile, LogPath.Append('Error.log').ToNative);
     {$I-}
     Rewrite(LogFile);
     if IOResult = 0 then
@@ -399,20 +400,19 @@ end;
 
 procedure TLog.LogVoice(SoundNr: integer);
 var
-  FS:           TFileStream;
-  FileName:     string;
+  FS:           TBinaryFileStream;
+  Prefix:       string;
+  FileName:     IPath;
   Num:          integer;
 begin
   for Num := 1 to 9999 do begin
-    FileName := IntToStr(Num);
-    while Length(FileName) < 4 do
-      FileName := '0' + FileName;
-    FileName := LogPath + 'Voice' + FileName + '.raw';
-    if not FileExists(FileName) then
+    Prefix := Format('Voice%.4d', [Num]);
+    FileName := LogPath.Append(Prefix + '.raw');
+    if not FileName.Exists() then
       break
   end;
 
-  FS := TFileStream.Create(FileName, fmCreate);
+  FS := TBinaryFileStream.Create(FileName, fmCreate);
 
   AudioInputProcessor.Sound[SoundNr].LogBuffer.Seek(0, soBeginning);
   FS.CopyFrom(AudioInputProcessor.Sound[SoundNr].LogBuffer, AudioInputProcessor.Sound[SoundNr].LogBuffer.Size);
@@ -420,21 +420,19 @@ begin
   FS.Free;
 end;
 
-procedure TLog.LogBuffer(const buf: Pointer; const bufLength: Integer; const filename: string);
+procedure TLog.LogBuffer(const buf: Pointer; const bufLength: Integer; const filename: IPath);
 var
-  f : TFileStream;
+  f : TBinaryFileStream;
 begin
-  f := nil;
-
   try
-    f := TFileStream.Create( filename, fmCreate);
-    f.Write( buf^, bufLength);
-    f.Free;
-  except
-    on e : Exception do begin
-      Log.LogError('TLog.LogBuffer: Failed to log buffer into file "' + filename + '". ErrMsg: ' + e.Message);
+    f := TBinaryFileStream.Create( filename, fmCreate);
+    try
+      f.Write( buf^, bufLength);
+    finally
       f.Free;
     end;
+  except on e : Exception do
+    Log.LogError('TLog.LogBuffer: Failed to log buffer into file "' + filename.ToNative + '". ErrMsg: ' + e.Message);
   end;
 end;
 
diff --git a/cmake/src/base/ULyrics.pas b/cmake/src/base/ULyrics.pas
index 82982981..3f62db9c 100644
--- a/cmake/src/base/ULyrics.pas
+++ b/cmake/src/base/ULyrics.pas
@@ -52,14 +52,14 @@ type
     Width:      real;     // width
     Start:      cardinal; // start of the word in quarters (beats)
     Length:     cardinal; // length of the word in quarters
-    Text:       string;   // text
+    Text:       UTF8String; // text
     Freestyle:  boolean;  // is freestyle?
   end;
   TLyricWordArray = array of TLyricWord;
 
   TLyricLine = class
     public
-      Text:           string;       // text
+      Text:           UTF8String;   // text
       Width:          real;         // width
       Height:         real;         // height
       Words:          TLyricWordArray;   // words in this line
diff --git a/cmake/src/base/UMain.pas b/cmake/src/base/UMain.pas
index 275510fc..0d479420 100644
--- a/cmake/src/base/UMain.pas
+++ b/cmake/src/base/UMain.pas
@@ -37,13 +37,9 @@ uses
   SysUtils,
   SDL;
 
-var
-  Done:    boolean;
-  Restart: boolean;
-
 procedure Main;
 procedure MainLoop;
-procedure CheckEvents;
+function CheckEvents: boolean;
 
 type
   TMainThreadExecProc = procedure(Data: Pointer);
@@ -73,22 +69,30 @@ uses
   UCovers,
   UDataBase,
   UDisplay,
-  UDLLManager,
   UGraphic,
   UGraphicClasses,
   UIni,
   UJoystick,
   ULanguage,
   ULog,
-  UPath,
+  UPathUtils,
   UPlaylist,
   UMusic,
+  URecord,
   UBeatTimer,
   UPlatform,
   USkins,
   USongs,
   UThemes,
   UParty,
+  ULuaCore,
+  UHookableEvent,
+  ULuaGl,
+  ULuaLog,
+  ULuaTexture,
+  ULuaTextGL,
+  ULuaParty,
+  ULuaScreenSing,
   UTime;
 
 procedure Main;
@@ -124,6 +128,10 @@ begin
     SDL_Init(SDL_INIT_VIDEO or SDL_INIT_TIMER);
     SDL_EnableUnicode(1);
 
+    // create luacore first so other classes can register their events
+    LuaCore := TLuaCore.Create;
+
+
     USTime := TTime.Create;
     VideoBGTimer := TRelativeTimer.Create;
 
@@ -148,15 +156,6 @@ begin
     Log.BenchmarkEnd(1);
     Log.LogBenchmark('Loading Language', 1);
 
-{
-    // SDL_ttf (Not used yet, maybe in version 1.5)
-    Log.BenchmarkStart(1);
-    Log.LogStatus('Initialize SDL_ttf', 'Initialization');
-    TTF_Init();
-    Log.BenchmarkEnd(1);
-    Log.LogBenchmark('Initializing SDL_ttf', 1);
-}
-
     // Skin
     Log.BenchmarkStart(1);
     Log.LogStatus('Loading Skin List', 'Initialization');
@@ -164,6 +163,12 @@ begin
     Log.BenchmarkEnd(1);
     Log.LogBenchmark('Loading Skin List', 1);
 
+    Log.BenchmarkStart(1);
+    Log.LogStatus('Loading Theme List', 'Initialization');
+    Theme := TTheme.Create;
+    Log.BenchmarkEnd(1);
+    Log.LogBenchmark('Loading Theme List', 1);
+
     // Ini + Paths
     Log.BenchmarkStart(1);
     Log.LogStatus('Load Ini', 'Initialization');
@@ -174,12 +179,6 @@ begin
     Log.LogStatus('Write Ini', 'Initialization');
     Ini.Save;
 
-    // Load Languagefile
-    if (Params.Language <> -1) then
-      Language.ChangeLanguage(ILanguage[Params.Language])
-    else
-      Language.ChangeLanguage(ILanguage[Ini.Language]);
-
     Log.BenchmarkEnd(1);
     Log.LogBenchmark('Loading Ini', 1);
 
@@ -195,10 +194,10 @@ begin
 
     // Theme
     Log.BenchmarkStart(1);
-    Log.LogStatus('Load Themes', 'Initialization');
-    Theme := TTheme.Create(ThemePath + ITheme[Ini.Theme] + '.ini', Ini.Color);
+    Log.LogStatus('Load Theme', 'Initialization');
+    Theme.LoadTheme(Ini.Theme, Ini.Color);
     Log.BenchmarkEnd(1);
-    Log.LogBenchmark('Loading Themes', 1);
+    Log.LogBenchmark('Loading Theme', 1);
 
     // Covers Cache
     Log.BenchmarkStart(1);
@@ -226,20 +225,6 @@ begin
     Log.BenchmarkEnd(1);
     Log.LogBenchmark('Loading Songs', 1);
 
-    // PluginManager
-    Log.BenchmarkStart(1);
-    Log.LogStatus('PluginManager', 'Initialization');
-    DLLMan := TDLLMan.Create;   // Load PluginList
-    Log.BenchmarkEnd(1);
-    Log.LogBenchmark('Loading PluginManager', 1);
-
-    // Party Mode Manager
-    Log.BenchmarkStart(1);
-    Log.LogStatus('PartySession Manager', 'Initialization');
-    PartySession := TPartySession.Create;   //Load PartySession
-    Log.BenchmarkEnd(1);
-    Log.LogBenchmark('Loading PartySession Manager', 1);
-
     // Graphics
     Log.BenchmarkStart(1);
     Log.LogStatus('Initialize 3D', 'Initialization');
@@ -252,10 +237,10 @@ begin
     Log.LogStatus('DataBase System', 'Initialization');
     DataBase := TDataBaseSystem.Create;
 
-    if (Params.ScoreFile = '') then
-      DataBase.Init (Platform.GetGameUserPath + 'Ultrastar.db')
+    if (Params.ScoreFile.IsUnset) then
+      DataBase.Init(Platform.GetGameUserPath.Append('Ultrastar.db'))
     else
-      DataBase.Init (Params.ScoreFile);
+      DataBase.Init(Params.ScoreFile);
 
     Log.BenchmarkEnd(1);
     Log.LogBenchmark('Loading DataBase System', 1);
@@ -284,22 +269,43 @@ begin
       Log.LogBenchmark('Initializing Joystick', 1);
     end;
 
+    // Lua
+    Log.BenchmarkStart(1);
+    Party := TPartyGame.Create;
+    Log.BenchmarkEnd(1);
+    Log.LogBenchmark('Initializing Party Manager', 1);
+
+    Log.BenchmarkStart(1);
+    LuaCore.RegisterModule('Log', ULuaLog_Lib_f);
+    LuaCore.RegisterModule('Gl', ULuaGl_Lib_f);
+    LuaCore.RegisterModule('TextGl', ULuaTextGl_Lib_f);
+    LuaCore.RegisterModule('Party', ULuaParty_Lib_f);
+    LuaCore.RegisterModule('ScreenSing', ULuaScreenSing_Lib_f);
+
+    Log.BenchmarkEnd(1);
+    Log.LogBenchmark('Initializing LuaCore', 1);
+
+    Log.BenchmarkStart(1);
+    LuaCore.LoadPlugins;
+    Log.BenchmarkEnd(1);
+    Log.LogBenchmark('Loading Lua Plugins', 1);
+
+    LuaCore.DumpPlugins;
+
     Log.BenchmarkEnd(0);
     Log.LogBenchmark('Loading Time', 0);
 
-    Log.LogStatus('Creating Core', 'Initialization');
-{
-    Core := TCore.Create(
-      USDXShortVersionStr,
-      MakeVersion(USDX_VERSION_MAJOR,
-                  USDX_VERSION_MINOR,
-                  USDX_VERSION_RELEASE,
-                  chr(0))
-    );
-}
+    { prepare software cursor }
+    Display.SetCursor;
+
+    {**
+      * Start background music
+      *}
+    SoundLib.StartBgMusic;
 
-    Log.LogStatus('Running Core', 'Initialization');
-    //Core.Run;
+    // check microphone settings, goto record options if they are corrupt
+    if (not AudioInputProcessor.ValidateSettings) then
+      Display.CurrentScreen^.FadeTo( @ScreenOptionsRecord );
 
     //------------------------------
     // Start Mainloop
@@ -318,62 +324,58 @@ begin
     // call an uninitialize routine for every initialize step
     // or at least use the corresponding Free methods
 
+    Log.LogStatus('Finalize Media', 'Finalization');
     FinalizeMedia();
 
-    //TTF_Quit();
+    Log.LogStatus('Uninitialize 3D', 'Finalization');
+    Finalize3D();
+
+    Log.LogStatus('Finalize SDL', 'Finalization');
     SDL_Quit();
 
-    if assigned(Log) then
-    begin
-      Log.LogStatus('Main Loop', 'Finished');
-      Log.Free;
-    end;
+    Log.LogStatus('Finalize Log', 'Finalization');
+    Log.Free;
   {$IFNDEF Debug}
   end;
   {$ENDIF}
 end;
 
 procedure MainLoop;
-var
-  Delay: integer;
 const
   MAX_FPS = 100;
+var
+  Delay:            integer;
+  TicksCurrent:     cardinal;
+  TicksBeforeFrame: cardinal;
+  Continue:         boolean;
 begin
   SDL_EnableKeyRepeat(125, 125);
 
   CountSkipTime();  // JB - for some reason this seems to be needed when we use the SDL Timer functions.
-  while not Done do
+  while Continue do
   begin
+    TicksBeforeFrame := SDL_GetTicks;
+    
     // joypad
     if (Ini.Joypad = 1) or (Params.Joypad) then
       Joy.Update;
 
     // keyboard events
-    CheckEvents;
+    Continue := CheckEvents;
 
     // display
-    Done := not Display.Draw;
+    Continue := Display.Draw;
     SwapBuffers;
 
-    // delay
-    CountMidTime;
-
-    Delay := Floor(1000 / MAX_FPS - 1000 * TimeMid);
-    Log.LogError ('MainLoop', 'Delay: ' + intToStr(Delay));
+    // FPS limiter
+    TicksCurrent := SDL_GetTicks;
+    Delay := 1000 div MAX_FPS - (TicksCurrent - TicksBeforeFrame);
 
     if Delay >= 1 then
       SDL_Delay(Delay); // dynamic, maximum is 100 fps
-    Log.LogError ('MainLoop', 'Delay: ok ' + intToStr(Delay));
 
     CountSkipTime;
 
-    // reinitialization of graphics
-    if Restart then
-    begin
-      Reinitialize3D;
-      Restart := false;
-    end;
-
   end;
 end;
 
@@ -392,15 +394,13 @@ begin
   end;
 end;
 
-procedure CheckEvents;
+function CheckEvents: boolean;
 var
   Event:     TSDL_event;
   mouseDown: boolean;
   mouseBtn:  integer;
 begin
-  if Assigned(Display.NextScreen) then
-    Exit;
-
+  Result := true;
   while (SDL_PollEvent(@Event) <> 0) do
   begin
     case Event.type_ of
@@ -425,29 +425,39 @@ begin
             begin
               mouseDown := true;
               mouseBtn  := Event.button.button;
+              
+              if (mouseBtn = SDL_BUTTON_LEFT) or (mouseBtn = SDL_BUTTON_RIGHT) then
+                Display.OnMouseButton(true);
             end;
             SDL_MOUSEBUTTONUP:
             begin
               mouseDown := false;
               mouseBtn  := Event.button.button;
+
+              if (mouseBtn = SDL_BUTTON_LEFT) or (mouseBtn = SDL_BUTTON_RIGHT) then
+                Display.OnMouseButton(false);
             end;
           end;
 
-          Display.MoveCursor(Event.button.X * 800 / Screen.w,
-                             Event.button.Y * 600 / Screen.h,
-                             mouseDown and ((mouseBtn <> SDL_BUTTON_WHEELDOWN) or (mouseBtn <> SDL_BUTTON_WHEELUP)));
-
-          if (ScreenPopupError <> nil) and (ScreenPopupError.Visible) then
-            done := not ScreenPopupError.ParseMouse(mouseBtn, mouseDown, Event.button.x, Event.button.y)
-          else if (ScreenPopupCheck <> nil) and (ScreenPopupCheck.Visible) then
-            done := not ScreenPopupCheck.ParseMouse(mouseBtn, mouseDown, Event.button.x, Event.button.y)
-          else
-          begin
-            done := not Display.CurrentScreen^.ParseMouse(mouseBtn, mouseDown, Event.button.x, Event.button.y);
-
-            // if screen wants to exit
-            if done then
-              DoQuit;
+          Display.MoveCursor(Event.button.X * 800 * Screens / ScreenW,
+                             Event.button.Y * 600 / ScreenH);
+
+          if not Assigned(Display.NextScreen) then
+          begin //drop input when changing screens
+            if (ScreenPopupError <> nil) and (ScreenPopupError.Visible) then
+              Result := ScreenPopupError.ParseMouse(mouseBtn, mouseDown, Event.button.x, Event.button.y)
+            else if (ScreenPopupInfo <> nil) and (ScreenPopupInfo.Visible) then
+              Result := ScreenPopupInfo.ParseMouse(mouseBtn, mouseDown, Event.button.x, Event.button.y)
+            else if (ScreenPopupCheck <> nil) and (ScreenPopupCheck.Visible) then
+              Result := ScreenPopupCheck.ParseMouse(mouseBtn, mouseDown, Event.button.x, Event.button.y)
+            else
+            begin
+              Result := Display.CurrentScreen^.ParseMouse(mouseBtn, mouseDown, Event.button.x, Event.button.y);
+
+              // if screen wants to exit
+              if not Result then
+                DoQuit;
+            end;
           end;
         end;
       end;
@@ -459,6 +469,12 @@ begin
         // This would create a new OpenGL render-context and all texture data
         // would be invalidated.
         // On Linux the mode MUST be reset, otherwise graphics will be corrupted.
+        // Update: It seems to work now without creating a new OpenGL context. At least
+        // with Win7 and SDL 1.2.14. Maybe it generally works now with SDL 1.2.14 and we
+        // can switch it on for windows.
+        // Important: Unless SDL_SetVideoMode() is called (it is not on Windows), Screen.w
+        // and Screen.h are not valid after a resize and still contain the old size. Use
+        // ScreenW and ScreenH instead.
         {$IF Defined(Linux) or Defined(FreeBSD)}
         if boolean( Ini.FullScreen ) then
           SDL_SetVideoMode(ScreenW, ScreenH, (Ini.Depth+1) * 16, SDL_OPENGL or SDL_FULLSCREEN)
@@ -468,52 +484,72 @@ begin
       end;
       SDL_KEYDOWN:
         begin
+          // translate CTRL-A (ASCII 1) - CTRL-Z (ASCII 26) to correct charcodes.
+          // keysyms (SDLK_A, ...) could be used instead but they ignore the
+          // current key mapping (if 'a' is pressed on a French keyboard the
+          // .unicode field will be 'a' and .sym SDLK_Q).
+          // IMPORTANT: if CTRL is pressed with a key different than 'A'-'Z' SDL
+          // will set .unicode to 0. There is no possibility to obtain a
+          // translated charcode. Use keysyms instead.
+          //if (Event.key.keysym.unicode in [1 .. 26]) then
+          //  Event.key.keysym.unicode := Ord('A') + Event.key.keysym.unicode - 1;
+
           // remap the "keypad enter" key to the "standard enter" key
           if (Event.key.keysym.sym = SDLK_KP_ENTER) then
             Event.key.keysym.sym := SDLK_RETURN;
 
-          if (Event.key.keysym.sym = SDLK_F11) or
-             ((Event.key.keysym.sym = SDLK_RETURN) and
-              ((Event.key.keysym.modifier and KMOD_ALT) <> 0)) then // toggle full screen
-          begin
-            Ini.FullScreen := integer( not boolean( Ini.FullScreen ) );
-
-            // FIXME: SDL_SetVideoMode creates a new OpenGL RC so we have to
-            // reload all texture data (-> whitescreen bug).
-            // Only Linux and FreeBSD are able to handle screen-switching this way.
-            {$IF Defined(Linux) or Defined(FreeBSD)}
-            if boolean( Ini.FullScreen ) then
+          if not Assigned(Display.NextScreen) then
+          begin //drop input when changing screens
+            { to-do : F11 was used for fullscreen toggle, too here
+                      but we also use the key in screenname and some other
+                      screens. It is droped although fullscreen toggle doesn't
+                      even work on windows.
+                      should we add (Event.key.keysym.sym = SDLK_F11) here
+                      anyway? }
+            if ((Event.key.keysym.sym = SDLK_RETURN) and
+               ((Event.key.keysym.modifier and KMOD_ALT) <> 0)) then // toggle full screen
             begin
-              SDL_SetVideoMode(ScreenW, ScreenH, (Ini.Depth+1) * 16, SDL_OPENGL or SDL_FULLSCREEN);
+              Ini.FullScreen := integer( not boolean( Ini.FullScreen ) );
+
+              // FIXME: SDL_SetVideoMode creates a new OpenGL RC so we have to
+              // reload all texture data (-> whitescreen bug).
+              // Only Linux and FreeBSD are able to handle screen-switching this way.
+              {$IF Defined(Linux) or Defined(FreeBSD)}
+              if boolean( Ini.FullScreen ) then
+              begin
+                SDL_SetVideoMode(ScreenW, ScreenH, (Ini.Depth+1) * 16, SDL_OPENGL or SDL_FULLSCREEN);
+              end
+              else
+              begin
+                SDL_SetVideoMode(ScreenW, ScreenH, (Ini.Depth+1) * 16, SDL_OPENGL or SDL_RESIZABLE);
+              end;
+
+              Display.SetCursor;
+
+              glViewPort(0, 0, ScreenW, ScreenH);
+              {$IFEND}
             end
+            // if print is pressed -> make screenshot and save to screenshot path
+            else if (Event.key.keysym.sym = SDLK_SYSREQ) or (Event.key.keysym.sym = SDLK_PRINT) then
+              Display.SaveScreenShot
+            // if there is a visible popup then let it handle input instead of underlying screen
+            // shoud be done in a way to be sure the topmost popup has preference (maybe error, then check)
+            else if (ScreenPopupError <> nil) and (ScreenPopupError.Visible) then
+              Result := ScreenPopupError.ParseInput(Event.key.keysym.sym, Event.key.keysym.unicode, true)
+            else if (ScreenPopupInfo <> nil) and (ScreenPopupInfo.Visible) then
+              Result := ScreenPopupInfo.ParseInput(Event.key.keysym.sym, Event.key.keysym.unicode, true)
+            else if (ScreenPopupCheck <> nil) and (ScreenPopupCheck.Visible) then
+              Result := ScreenPopupCheck.ParseInput(Event.key.keysym.sym, Event.key.keysym.unicode, true)
             else
             begin
-              SDL_SetVideoMode(ScreenW, ScreenH, (Ini.Depth+1) * 16, SDL_OPENGL or SDL_RESIZABLE);
-            end;
+              // check if screen wants to exit
+              Result := Display.ParseInput(Event.key.keysym.sym, Event.key.keysym.unicode, true);
 
-            Display.SetCursor;
-
-            glViewPort(0, 0, ScreenW, ScreenH);
-            {$IFEND}
-          end
-          // if print is pressed -> make screenshot and save to screenshot path
-          else if (Event.key.keysym.sym = SDLK_SYSREQ) or (Event.key.keysym.sym = SDLK_PRINT) then
-            Display.SaveScreenShot
-          // if there is a visible popup then let it handle input instead of underlying screen
-          // shoud be done in a way to be sure the topmost popup has preference (maybe error, then check)
-          else if (ScreenPopupError <> nil) and (ScreenPopupError.Visible) then
-            Done := not ScreenPopupError.ParseInput(Event.key.keysym.sym, WideChar(Event.key.keysym.unicode), true)
-          else if (ScreenPopupCheck <> nil) and (ScreenPopupCheck.Visible) then
-            Done := not ScreenPopupCheck.ParseInput(Event.key.keysym.sym, WideChar(Event.key.keysym.unicode), true)
-          else
-          begin
-            // check if screen wants to exit
-            Done := not Display.CurrentScreen^.ParseInput(Event.key.keysym.sym, WideChar(Event.key.keysym.unicode), true);
-
-            // if screen wants to exit
-            if Done then
-              DoQuit;
+              // if screen wants to exit
+              if not Result then
+                DoQuit;
 
+            end;
           end;
         end;
       SDL_JOYAXISMOTION:
diff --git a/cmake/src/base/UMusic.pas b/cmake/src/base/UMusic.pas
index 19c54bee..7f2b3e30 100644
--- a/cmake/src/base/UMusic.pas
+++ b/cmake/src/base/UMusic.pas
@@ -34,10 +34,11 @@ interface
 {$I switches.inc}
 
 uses
-  UTime,
   SysUtils,
   Classes,
-  UBeatTimer;
+  UTime,
+  UBeatTimer,
+  UPath;
 
 type
   TNoteType = (ntFreestyle, ntNormal, ntGolden);
@@ -62,7 +63,7 @@ type
     Start:      integer;    // beat the fragment starts at
     Length:     integer;    // length in beats
     Tone:       integer;    // full range tone
-    Text:       string;     // text assigned to this fragment (a syllable, word, etc.)
+    Text:       UTF8String; // text assigned to this fragment (a syllable, word, etc.)
     NoteType:   TNoteType;  // note-type: golden-note/freestyle etc.
   end;
 
@@ -73,7 +74,7 @@ type
   PLine = ^TLine;
   TLine = record
     Start:      integer; // the start beat of this line (<> start beat of the first note of this line)
-    Lyric:      string;
+    Lyric:      UTF8String;
     //LyricWidth: real;    // @deprecated: width of the line in pixels.
                          // Do not use this as the width is not correct.
                          // Use TLyricsEngine.GetUpperLine().Width instead. 
@@ -187,10 +188,6 @@ type
   end;
 
 type
-  TSyncSource = class
-    function GetClock(): real; virtual; abstract;
-  end;
-
   TAudioProcessingStream = class;
   TOnCloseHandler = procedure(Stream: TAudioProcessingStream);
 
@@ -249,8 +246,8 @@ type
 
   TAudioPlaybackStream = class(TAudioProcessingStream)
     protected
+      AvgSyncDiff: double;  //** average difference between stream and sync clock
       SyncSource: TSyncSource;
-      AvgSyncDiff: double;
       SourceStream: TAudioSourceStream;
 
       function GetLatency(): double; virtual; abstract;
@@ -259,7 +256,7 @@ type
       procedure SetVolume(Volume: single);  virtual; abstract;
       function Synchronize(BufferSize: integer; FormatInfo: TAudioFormatInfo): integer;
       procedure FillBufferWithFrame(Buffer: PByteArray; BufferSize: integer; Frame: PByteArray; FrameSize: integer);
-   public
+    public
       (**
        * Opens a SourceStream for playback.
        * Note that the caller (not the TAudioPlaybackStream) is responsible to
@@ -315,7 +312,7 @@ type
   // soundcard output-devices information
   TAudioOutputDevice = class
     public
-      Name: string; // soundcard name
+      Name: UTF8String; // soundcard name
   end;
   TAudioOutputDeviceList = array of TAudioOutputDevice;
 
@@ -323,28 +320,33 @@ type
   IGenericPlayback = Interface
   ['{63A5EBC3-3F4D-4F23-8DFB-B5165FCE33DD}']
       function GetName: String;
+  end;
 
-      function Open(const Filename: string): boolean; // true if succeed
-      procedure Close;
-
+  IVideo = interface
+  ['{58DFC674-9168-41EA-B59D-A61307242B80}']
       procedure Play;
       procedure Pause;
       procedure Stop;
 
+      procedure SetLoop(Enable: boolean);
+      function GetLoop(): boolean;
+
       procedure SetPosition(Time: real);
       function GetPosition: real;
 
+      procedure GetFrame(Time: Extended);
+      procedure DrawGL(Screen: integer);
+
+      property Loop: boolean read GetLoop write SetLoop;
       property Position: real read GetPosition write SetPosition;
   end;
 
   IVideoPlayback = Interface( IGenericPlayback )
   ['{3574C40C-28AE-4201-B3D1-3D1F0759B131}']
-    function Init(): boolean;
-    function Finalize: boolean;
-
-    procedure GetFrame(Time: Extended); // WANT TO RENAME THESE TO BE MORE GENERIC
-    procedure DrawGL(Screen: integer);  // WANT TO RENAME THESE TO BE MORE GENERIC
+      function Init(): boolean;
+      function Finalize: boolean;
 
+      function Open(const FileName : IPath): IVideo;
   end;
 
   IVideoVisualization = Interface( IVideoPlayback )
@@ -369,6 +371,18 @@ type
       function  Finished: boolean;
       function  Length: real;
 
+      function Open(const Filename: IPath): boolean; // true if succeed
+      procedure Close;
+
+      procedure Play;
+      procedure Pause;
+      procedure Stop;
+
+      procedure SetPosition(Time: real);
+      function GetPosition: real;
+
+      property Position: real read GetPosition write SetPosition;
+
       // Sounds
       // TODO:
       // add a TMediaDummyPlaybackStream implementation that will
@@ -376,7 +390,7 @@ type
       // nil-pointers is not neccessary anymore.
       // PlaySound/StopSound will be removed then, OpenSound will be renamed to
       // CreateSound.
-      function OpenSound(const Filename: String): TAudioPlaybackStream;
+      function OpenSound(const Filename: IPath): TAudioPlaybackStream;
       procedure PlaySound(Stream: TAudioPlaybackStream);
       procedure StopSound(Stream: TAudioPlaybackStream);
 
@@ -391,7 +405,7 @@ type
 
   IGenericDecoder = Interface
   ['{557B0E9A-604D-47E4-B826-13769F3E10B7}']
-      function GetName(): String;
+      function GetName(): string;
       function InitializeDecoder(): boolean;
       function FinalizeDecoder(): boolean;
       //function IsSupported(const Filename: string): boolean;
@@ -400,13 +414,13 @@ type
   (*
   IVideoDecoder = Interface( IGenericDecoder )
   ['{2F184B2B-FE69-44D5-9031-0A2462391DCA}']
-      function Open(const Filename: string): TVideoDecodeStream;
+      function Open(const Filename: IPath): TVideoDecodeStream;
   end;
   *)
 
   IAudioDecoder = Interface( IGenericDecoder )
   ['{AB47B1B6-2AA9-4410-BF8C-EC79561B5478}']
-      function Open(const Filename: string): TAudioDecodeStream;
+      function Open(const Filename: IPath): TAudioDecodeStream;
   end;
 
   IAudioInput = Interface
@@ -456,7 +470,7 @@ const
   SOUNDID_CLICK    = 5;
   LAST_SOUNDID = SOUNDID_CLICK;
 
-  BaseSoundFilenames: array[0..LAST_SOUNDID] of string = (
+  BaseSoundFilenames: array[0..LAST_SOUNDID] of IPath = (
     '%SOUNDPATH%/Common start.mp3',                 // Start
     '%SOUNDPATH%/Common back.mp3',                  // Back
     '%SOUNDPATH%/menu swoosh.mp3',                  // Swoosh
@@ -497,7 +511,7 @@ type
       procedure StartBgMusic();
       procedure PauseBgMusic();
       // TODO
-      //function AddSound(Filename: string): integer;
+      //function AddSound(Filename: IPath): integer;
       //procedure RemoveSound(ID: integer);
       //function GetSound(ID: integer): TAudioPlaybackStream;
       //property Sound[ID: integer]: TAudioPlaybackStream read GetSound; default;
@@ -533,7 +547,7 @@ uses
   UCommandLine,
   URecord,
   ULog,
-  UPath;
+  UPathUtils;
 
 var
   DefaultVideoPlayback : IVideoPlayback;
@@ -654,7 +668,7 @@ begin
   FilterInterfaceList(IAudioDecoder, MediaManager, InterfaceList);
   for i := 0 to InterfaceList.Count-1 do
   begin
-    CurrentAudioDecoder := IAudioDecoder(InterfaceList[i]);
+    CurrentAudioDecoder := InterfaceList[i] as IAudioDecoder;
     if (not CurrentAudioDecoder.InitializeDecoder()) then
     begin
       Log.LogError('Initialize failed, Removing - '+ CurrentAudioDecoder.GetName);
@@ -671,7 +685,7 @@ begin
   FilterInterfaceList(IAudioPlayback, MediaManager, InterfaceList);
   for i := 0 to InterfaceList.Count-1 do
   begin
-    CurrentAudioPlayback := IAudioPlayback(InterfaceList[i]);
+    CurrentAudioPlayback := InterfaceList[i] as IAudioPlayback;
     if (CurrentAudioPlayback.InitializePlayback()) then
     begin
       DefaultAudioPlayback := CurrentAudioPlayback;
@@ -686,7 +700,7 @@ begin
   FilterInterfaceList(IAudioInput, MediaManager, InterfaceList);
   for i := 0 to InterfaceList.Count-1 do
   begin
-    CurrentAudioInput := IAudioInput(InterfaceList[i]);
+    CurrentAudioInput := InterfaceList[i] as IAudioInput;
     if (CurrentAudioInput.InitializeRecord()) then
     begin
       DefaultAudioInput := CurrentAudioInput;
@@ -719,7 +733,7 @@ begin
   FilterInterfaceList(IVideoPlayback, MediaManager, InterfaceList);
   for i := 0 to InterfaceList.Count-1 do
   begin
-    VideoInterface := IVideoPlayback(InterfaceList[i]);
+    VideoInterface := InterfaceList[i] as IVideoPlayback;
     if (VideoInterface.Init()) then
     begin
       DefaultVideoPlayback := VideoInterface;
@@ -734,7 +748,7 @@ begin
   FilterInterfaceList(IVideoVisualization, MediaManager, InterfaceList);
   for i := 0 to InterfaceList.Count-1 do
   begin
-    VisualInterface := IVideoVisualization(InterfaceList[i]);
+    VisualInterface := InterfaceList[i] as IVideoVisualization;
     if (VisualInterface.Init()) then
     begin
       DefaultVisualization := VisualInterface;
@@ -748,7 +762,7 @@ begin
 
   // now that we have all interfaces, we can dump them
   // TODO: move this to another place
-  if FindCmdLineSwitch( cMediaInterfaces ) then
+  if FindCmdLineSwitch(cMediaInterfaces) then
   begin
     DumpMediaInterfaces();
     halt;
@@ -772,27 +786,27 @@ begin
   // finalize audio playback interfaces (should be done before the decoders)
   FilterInterfaceList(IAudioPlayback, MediaManager, InterfaceList);
   for i := 0 to InterfaceList.Count-1 do
-    IAudioPlayback(InterfaceList[i]).FinalizePlayback();
+    (InterfaceList[i] as IAudioPlayback).FinalizePlayback();
 
   // finalize audio input interfaces
   FilterInterfaceList(IAudioInput, MediaManager, InterfaceList);
   for i := 0 to InterfaceList.Count-1 do
-    IAudioInput(InterfaceList[i]).FinalizeRecord();
+    (InterfaceList[i] as IAudioInput).FinalizeRecord();
 
   // finalize audio decoder interfaces
   FilterInterfaceList(IAudioDecoder, MediaManager, InterfaceList);
   for i := 0 to InterfaceList.Count-1 do
-    IAudioDecoder(InterfaceList[i]).FinalizeDecoder();
+    (InterfaceList[i] as IAudioDecoder).FinalizeDecoder();
 
   // finalize video interfaces
   FilterInterfaceList(IVideoPlayback, MediaManager, InterfaceList);
   for i := 0 to InterfaceList.Count-1 do
-    IVideoPlayback(InterfaceList[i]).Finalize();
+    (InterfaceList[i] as IVideoPlayback).Finalize();
 
   // finalize audio decoder interfaces
   FilterInterfaceList(IVideoVisualization, MediaManager, InterfaceList);
   for i := 0 to InterfaceList.Count-1 do
-    IVideoVisualization(InterfaceList[i]).Finalize();
+    (InterfaceList[i] as IVideoVisualization).Finalize();
 
   InterfaceList.Free;
 
@@ -813,12 +827,6 @@ begin
   if (AudioInput <> nil) then
     AudioInput.CaptureStop;
 
-  if (VideoPlayback <> nil) then
-    VideoPlayback.Close;
-
-  if (Visualization <> nil) then
-    Visualization.Close;
-
   UnloadMediaModules();
 end;
 
@@ -855,14 +863,14 @@ procedure TSoundLibrary.LoadSounds();
 begin
   UnloadSounds();
 
-  Start   := AudioPlayback.OpenSound(SoundPath + 'Common start.mp3');
-  Back    := AudioPlayback.OpenSound(SoundPath + 'Common back.mp3');
-  Swoosh  := AudioPlayback.OpenSound(SoundPath + 'menu swoosh.mp3');
-  Change  := AudioPlayback.OpenSound(SoundPath + 'select music change music 50.mp3');
-  Option  := AudioPlayback.OpenSound(SoundPath + 'option change col.mp3');
-  Click   := AudioPlayback.OpenSound(SoundPath + 'rimshot022b.mp3');
+  Start   := AudioPlayback.OpenSound(SoundPath.Append('Common start.mp3'));
+  Back    := AudioPlayback.OpenSound(SoundPath.Append('Common back.mp3'));
+  Swoosh  := AudioPlayback.OpenSound(SoundPath.Append('menu swoosh.mp3'));
+  Change  := AudioPlayback.OpenSound(SoundPath.Append('select music change music 50.mp3'));
+  Option  := AudioPlayback.OpenSound(SoundPath.Append('option change col.mp3'));
+  Click   := AudioPlayback.OpenSound(SoundPath.Append('rimshot022b.mp3'));
 
-  BGMusic := AudioPlayback.OpenSound(SoundPath + 'Bebeto_-_Loop010.mp3');
+  BGMusic := AudioPlayback.OpenSound(SoundPath.Append('Bebeto_-_Loop010.mp3'));
 
   if (BGMusic <> nil) then
     BGMusic.Loop := True;
@@ -983,6 +991,8 @@ begin
   AvgSyncDiff := -1;
 end;
 
+{.$DEFINE LOG_SYNC}
+
 (*
  * Results an adjusted size of the input buffer size to keep the stream in sync
  * with the SyncSource. If no SyncSource was assigned to this stream, the
@@ -999,11 +1009,15 @@ end;
 function TAudioPlaybackStream.Synchronize(BufferSize: integer; FormatInfo: TAudioFormatInfo): integer;
 var
   TimeDiff: double;
-  TimeCorrectionFactor: double;
+  FrameDiff: double;
+  FrameSkip: integer;
+  ReqFrames: integer;
+  MasterClock: real;
+  CurPosition: real;
 const
-  AVG_HISTORY_FACTOR = 0.9;
-  SYNC_THRESHOLD = 0.045;
-  MAX_SYNC_DIFF_TIME = 0.002;
+  AVG_HISTORY_FACTOR = 0.7;
+  SYNC_REPOS_THRESHOLD = 5.000;
+  SYNC_SOFT_THRESHOLD  = 0.010;
 begin
   Result := BufferSize;
 
@@ -1013,9 +1027,12 @@ begin
   if (BufferSize <= 0) then
     Exit;
 
+  CurPosition := Position;
+  MasterClock := SyncSource.GetClock();
+
   // difference between sync-source and stream position
   // (negative if the music-stream's position is ahead of the master clock)
-  TimeDiff := SyncSource.GetClock() - (Position - GetLatency());
+  TimeDiff := MasterClock - CurPosition;
 
   // calculate average time difference (some sort of weighted mean).
   // The bigger AVG_HISTORY_FACTOR is, the smoother is the average diff.
@@ -1030,35 +1047,46 @@ begin
     AvgSyncDiff := TimeDiff * (1-AVG_HISTORY_FACTOR) +
                    AvgSyncDiff * AVG_HISTORY_FACTOR;
 
-  // check if sync needed
-  if (Abs(AvgSyncDiff) >= SYNC_THRESHOLD) then
+  {$IFDEF LOG_SYNC}
+  //Log.LogError(Format('c:%.3f | p:%.3f | d:%.3f | a:%.3f',
+  //    [MasterClock, CurPosition, TimeDiff, AvgSyncDiff]), 'Synch');
+  {$ENDIF}
+
+  // check if we are out of sync
+  if (Abs(AvgSyncDiff) >= SYNC_REPOS_THRESHOLD) then
   begin
-    // TODO: use SetPosition if diff is too large (>5s)
-    if (TimeDiff < 1) then
-      TimeCorrectionFactor := Sign(TimeDiff)*TimeDiff*TimeDiff
-    else
-      TimeCorrectionFactor := TimeDiff;
-
-    // calculate adapted buffer size
-    // reduce size of data to fetch if music is ahead, increase otherwise
-    Result := BufferSize + Round(TimeCorrectionFactor * FormatInfo.SampleRate) * FormatInfo.FrameSize;
+    {$IFDEF LOG_SYNC}
+    Log.LogError(Format('ReposSynch: %.3f > %.3f',
+        [Abs(AvgSyncDiff), SYNC_REPOS_THRESHOLD]), 'Synch');
+    {$ENDIF}
+
+    // diff far is too large -> reposition stream
+    // (resulting position might still be out of sync)
+    SetPosition(CurPosition + AvgSyncDiff);
+
+    // reset sync info
+    AvgSyncDiff := -1;
+  end
+  else if (Abs(AvgSyncDiff) >= SYNC_SOFT_THRESHOLD) then
+  begin
+    {$IFDEF LOG_SYNC}
+    Log.LogError(Format('SoftSynch: %.3f > %.3f',
+        [Abs(AvgSyncDiff), SYNC_SOFT_THRESHOLD]), 'Synch');
+    {$ENDIF}
+
+    // hard sync: directly jump to the current position
+    FrameSkip := Round(AvgSyncDiff * FormatInfo.SampleRate);
+    Result := BufferSize + FrameSkip * FormatInfo.FrameSize;
     if (Result < 0) then
       Result := 0;
 
-    // reset average
+    // reset sync info
     AvgSyncDiff := -1;
   end;
-
-  (*
-  DebugWriteln('Diff: ' + floattostrf(TimeDiff, ffFixed, 15, 3) +
-               '| SyS: ' + floattostrf(SyncSource.GetClock(), ffFixed, 15, 3) +
-               '| Pos: ' + floattostrf(Position, ffFixed, 15, 3) +
-               '| Avg: ' + floattostrf(AvgSyncDiff, ffFixed, 15, 3));
-  *)
 end;
 
 (*
- * Fills a buffer with copies of the given frame or with 0 if frame.
+ * Fills a buffer with copies of the given Frame or with 0 if Frame is nil.
  *)
 procedure TAudioPlaybackStream.FillBufferWithFrame(Buffer: PByteArray; BufferSize: integer; Frame: PByteArray; FrameSize: integer);
 var
diff --git a/cmake/src/base/UNote.pas b/cmake/src/base/UNote.pas
index 6da4cf07..6eb99df9 100644
--- a/cmake/src/base/UNote.pas
+++ b/cmake/src/base/UNote.pas
@@ -19,8 +19,8 @@
  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301, USA.
  *
- * $URL: https://ultrastardx.svn.sourceforge.net/svnroot/ultrastardx/trunk/src/base/UNote.pas $
- * $Id: UNote.pas 1626 2009-03-07 19:53:00Z k-m_schindler $
+ * $URL$
+ * $Id$
  *}
 
 unit UNote;
@@ -61,7 +61,7 @@ type
 
   PPLayer = ^TPlayer;
   TPlayer = record
-    Name:           string;
+    Name:           UTF8String;
 
     // Index in Teaminfo record
     TeamID:         byte;
@@ -123,13 +123,12 @@ uses
   UCatCovers,
   UDataBase,
   UPlaylist,
-  UDLLManager,
   UParty,
   UConfig,
   UCommon,
   UGraphic,
   UGraphicClasses,
-  UPath,
+  UPathUtils,
   UPlatform,
   UThemes;
 
diff --git a/cmake/src/base/UParty.pas b/cmake/src/base/UParty.pas
index e29b977c..2f89afd6 100644
--- a/cmake/src/base/UParty.pas
+++ b/cmake/src/base/UParty.pas
@@ -34,350 +34,979 @@ interface
 {$I switches.inc}
 
 uses
-  ModiSDK;
+  ULua;
 
 type
-  TRoundInfo = record
-    Plugin: word;
-    Winner: byte;
+  { array holds ids of modes or Party_Round_Random
+    its length defines the number of rounds
+    it is used as argument for TPartyGame.StartParty }
+  ARounds = array of integer;
+
+  { element of APartyTeamRanking returned by TPartyGame.GetTeamRanking
+    and parameter for TPartyGame.SetWinner }
+  TParty_TeamRanking = record
+    Team: Integer; //< id of team
+    Rank: Integer; //< 1 to Length(Teams) e.g. 1 is for placed first
   end;
+  AParty_TeamRanking = array of TParty_TeamRanking; //< returned by TPartyGame.GetTeamRanking
 
-  TeamOrderEntry = record
-    TeamNum: byte;
-    Score:   byte;
+  TParty_RoundList = record
+    Index: integer;
+    Name: UTF8String;
   end;
+  AParty_ModeList = array of TParty_RoundList;
+
+  { record used by TPartyGame to store round specific data }
+  TParty_Round = record
+    Mode:   Integer;
+    AlreadyPlayed: Boolean; //< true if round was already played
+    Ranking: AParty_TeamRanking;
+    RankingSet: Boolean; //< true if Self.Ranking is already set
+  end;
+
+  TParty_ModeInfo = record
+    Name: String; // name of this mode
+    Parent: Integer;   // Id of owning plugin
+
+    CanNonParty: Boolean; //< is playable when not in party mode
+    CanParty: Boolean;    //< is playable in party mode
+
+    // one bit in the following settings stands for
+    // a player or team count
+    // PlayerCount = 2 or 4 indicates that the mode is playable with 2 and 3 players per team
+    // TeamCount = 1 or 2 or 4 or 8 or 16 or 32 indicates that the mode is playable with 1 to 6 teams
+    PlayerCount: Integer;   //< playable with one, two, three etc. players per team
+    TeamCount: Integer;     //< playable with one, two, three etc. different teams
+
+
+    Functions: record // lua functions that will be called at specific events
+      BeforeSongSelect: String; // default actions are executed if functions = nil
+      AfterSongSelect: String;
+
+      BeforeSing: String;
+      OnSing: String;
+      AfterSing: String;
+    end;
+  end;
+
+  { used by TPartyGame to store player specific data }
+  TParty_PlayerInfo = record
+    Name: String;         //< Playername
+    TimesPlayed: Integer; //< How often this Player has Sung
+  end;
+
+  { used by TPartyGame to store team specific data }
+  TParty_TeamInfo = record
+    Name:  String;        //< name of the Team
+    Score: Word;          //< current score
+    JokersLeft: Integer;  //< jokers this team has left
 
-  TeamOrderArray = array[0..5] of byte;
+    NextPlayer: Integer;  //Id of the player that plays the next (the current) song
 
-  TPartyPlugin = record
-    ID:          byte;
-    TimesPlayed: byte;
+    Players: array of TParty_PlayerInfo;
   end;
 
-  TPartySession = class
+  TPartyGame = class
   private
-    function GetRandomPlayer(Team: byte): byte;
-    function GetRandomPlugin(Plugins: array of TPartyPlugin): byte;
-    function IsWinner(Player, Winner: byte): boolean;
+    bPartyGame: boolean; //< are we playing party or standard mode
+    CurRound: Integer;   //< indicates which of the elements of Rounds is played next (at the moment)
+
+    bPartyStarted: Boolean;
+
+    TimesPlayed: array of Integer; //< times every mode was played in current party game (for random mode calculation)
+
     procedure GenScores;
+    function GetRandomMode: integer;
+    function GetRandomPlayer(Team: integer): integer;
+
+    { returns true if a mode is playable with current playerconfig }
+    function ModePlayable(I: integer): boolean;
+
+    function CallLua(Parent: Integer; Func: String):Boolean;
+
+    procedure SetRankingByScore;
   public
-    Teams:    TTeamInfo;
-    Rounds:   array of TRoundInfo;
-    CurRound: byte;
+    //Teams: TTeamInfo;
+    Rounds: array of TParty_Round;    //< holds info which modes are played in this party game (if started)
+    Teams: array of TParty_TeamInfo;  //< holds info of teams playing in current round (private for easy manipulation of lua functions)
+
+    Modes: array of TParty_ModeInfo;  //< holds info of registred party modes
+
+    property CurrentRound: Integer read CurRound;
 
     constructor Create;
 
-    procedure StartNewParty(NumRounds: byte);
-    procedure StartRound;
-    procedure EndRound;
-    function  GetTeamOrder: TeamOrderArray;
-    function  GetWinnerString(Round: byte): string;
+    { set the attributes of Info to default values }
+    procedure DefaultModeInfo(var Info: TParty_ModeInfo);
+
+    { registers a new mode, returns true on success
+      (mode name does not already exist) }
+    function RegisterMode(Info: TParty_ModeInfo): Boolean;
+
+    { returns true if modes are available for
+      players and teams that are currently set
+      up. if there are no teams set up it returns
+      if there are any party modes available }
+    function ModesAvailable: Boolean;
+
+    { returns an array with the name of all available modes (that
+      are playable with current player configuration }
+    function GetAvailableModes: AParty_ModeList;
+
+    { clears all party specific data previously stored }
+    procedure Clear;
+
+    { adds a team to the team array, returning its id
+      can only be called when game is not already started }
+    function AddTeam(Name: String): Integer;
+
+    { adds a player to the player array, returning its id
+      can only be called when game is not already started }
+    function AddPlayer(Team: Integer; Name: String): Integer;
+
+    { starts a new PartyGame, returns true on success
+      before a call of this function teams and players
+      has to be added by AddTeam and AddPlayer }
+
+    function StartGame(Rounds: ARounds): Boolean;
+
+    { sets the winner(s) of current round
+      returns true on success }
+    function SetRanking(Ranking: AParty_TeamRanking): Boolean;
+
+    { increases round counter by 1 and clears all round specific information;
+      returns the number of the current round or -1 if last round has already
+      been played }
+    function NextRound: integer;
+
+    { indicates that current round has already been played }
+    procedure RoundPlayed;
+
+    { true if in a Party Game (not in standard mode) }
+    property PartyGame: Boolean read BPartyGame;
+
+
+    { returns true if last round was already played }
+    function GameFinished: Boolean;
+
+    { call plugins defined function and/or default procedure
+      only default procedure is called when no function is defined by plugin
+      if plugins function returns true then default is called after plugins
+      function was executed}
+    procedure CallBeforeSongSelect;
+    procedure CallAfterSongSelect;
+    procedure CallBeforeSing;
+    procedure CallOnSing;
+    procedure CallAfterSing;
+
+    { returns an array[1..6] of TParty_TeamRanking.
+      the index stands for the placing,
+      team is the team number (in the team array)
+      rank is correct rank if some teams have the
+      same score. 
+      }
+    function GetTeamRanking: AParty_TeamRanking;
+
+    { returns a string like "Team 1 (and Team 2) win" }
+    function GetWinnerString(Round: integer): UTF8String;
+
+    destructor  Destroy;
   end;
 
+const
+  { minimal amount of teams for party mode }
+  Party_Teams_Min = 2;
+
+  { maximal amount of teams for party mode }
+  Party_Teams_Max = 3;
+
+  { minimal amount of players for party mode }
+  Party_Players_Min = 1;
+
+  { maximal amount of players for party mode }
+  Party_Players_Max = 4;
+
+  { amount of jokers each team gets at the beginning of the game }
+  Party_Count_Jokers = 5;
+
+  { to indicate that element (mode) should set randomly in ARounds array }
+  Party_Round_Random = -1;
+
+  { values for TParty_TeamRanking.Rank }
+  PR_First = 1;
+  PR_Second = 2;
+  PR_Third = 3;
+  
+  StandardModus = 0; //Modus Id that will be played in non-party mode
+
 var
-  PartySession: TPartySession;
+  Party: TPartyGame;
 
 implementation
 
 uses
-  UDLLManager,
   UGraphic,
-  UNote,
   ULanguage,
-  ULog;
+  ULog,
+  ULuaCore,
+  UDisplay,
+  USong,
+  UNote,
+  SysUtils;
 
-constructor TPartySession.Create;
+//-------------
+// Just the constructor
+//-------------
+constructor TPartyGame.Create;
 begin
   inherited;
+
+  Clear;
 end;
 
-//----------
-// Returns a number of a random plugin
-//----------
-function TPartySession.GetRandomPlugin(Plugins: array of TPartyPlugin): byte;
+destructor TPartyGame.Destroy;
+begin
+  inherited;
+end;
+
+{ clears all party specific data previously stored }
+procedure TPartyGame.Clear;
+  var
+    I: Integer;
+begin
+  bPartyGame := false; // no party game
+  CurRound := low(integer);
+
+  bPartyStarted := false; //game not startet
+
+  SetLength(Teams, 0); //remove team info
+  SetLength(Rounds, 0); //remove round info
+
+  // clear times played
+  for I := 0 to High(TimesPlayed) do
+    TimesPlayed[I] := 0;
+end;
+
+{ private: some intelligent randomnes for plugins }
+function TPartyGame.GetRandomMode: integer;
+var
+  LowestTP: integer;
+  NumPwithLTP: integer;
+  I: integer;
+  R: integer;
+begin
+  Result := 0; //If there are no matching modes, play first modus
+  LowestTP := high(Integer);
+  NumPwithLTP := 0;
+
+  // search for the plugins less played yet
+  for I := 0 to high(Modes) do
+  begin
+    if (ModePlayable(I)) then
+    begin
+      if (TimesPlayed[I] < lowestTP) then
+      begin
+        lowestTP := TimesPlayed[I];
+        NumPwithLTP := 1;
+      end
+      else if (TimesPlayed[I] = lowestTP) then
+      begin
+        Inc(NumPwithLTP);
+      end;
+    end;
+  end;
+
+  // create random number
+  R := Random(NumPwithLTP);
+
+  // select the random mode from the modes with less timesplayed
+  for I := 0 to high(Modes) do
+  begin
+    if (TimesPlayed[I] = lowestTP) and (ModePlayable(I)) then
+    begin
+      //Plugin found
+      if (R = 0) then
+      begin
+        Result := I;
+        Inc(TimesPlayed[I]);
+        Break;
+      end;
+
+      Dec(R);
+    end;
+  end;
+end;
+
+{ private: GetRandomPlayer - returns a random player
+                             that does not play to often ;) }
+function TPartyGame.GetRandomPlayer(Team: integer): integer;
 var
-  LowestTP:    byte;
-  NumPwithLTP: word;
-  I:           integer;
-  R:           word;
+  I, R: integer;
+  lowestTP: Integer;
+  NumPwithLTP: Integer;
 begin
-  LowestTP := high(byte);
+  LowestTP := high(Integer);
   NumPwithLTP := 0;
+  Result := 0;
 
-  //Search for Plugins not often played yet
-  for I := 0 to high(Plugins) do
+  // search for players that have less played yet
+  for I := 0 to High(Teams[Team].Players) do
   begin
-    if (Plugins[I].TimesPlayed < lowestTP) then
+    if (Teams[Team].Players[I].TimesPlayed < lowestTP) then
     begin
-      lowestTP := Plugins[I].TimesPlayed;
+      lowestTP := Teams[Team].Players[I].TimesPlayed;
       NumPwithLTP := 1;
     end
-    else if (Plugins[I].TimesPlayed = lowestTP) then
+    else if (Teams[Team].Players[I].TimesPlayed = lowestTP) then
     begin
       Inc(NumPwithLTP);
     end;
   end;
 
-  //Create random no
+  // create random number
   R := Random(NumPwithLTP);
 
-  //Search for random plugin
-  for I := 0 to high(Plugins) do
+  // search for selected random player
+  for I := 0 to High(Teams[Team].Players) do
   begin
-    if Plugins[I].TimesPlayed = LowestTP then
+    if Teams[Team].Players[I].TimesPlayed = lowestTP then
     begin
-      //Plugin found
       if (R = 0) then
-      begin
-        Result := Plugins[I].ID;
-        Inc(Plugins[I].TimesPlayed);
+      begin // found selected player
+        Result := I;
         Break;
       end;
+
       Dec(R);
     end;
   end;
 end;
 
 //----------
-//StartNewParty - Reset and prepares for new party
+//GenScores - inc scores for cur. round
 //----------
-procedure TPartySession.StartNewParty(NumRounds: byte);
+procedure TPartyGame.GenScores;
 var
-  Plugins:  array of TPartyPlugin;
-  TeamMode: boolean;
-  Len:      integer;
-  I, J:     integer;
+  I: Integer;
+begin
+  if (Length(Teams) = 2) then
+  begin // score generation for 2 teams, winner gets 1 point
+    for I := 0 to High(Rounds[CurRound].Ranking) do
+      if (Rounds[CurRound].Ranking[I].Rank = PR_First) then
+        Inc(Teams[Rounds[CurRound].Ranking[I].Team].Score);
+  end
+  else if (Length(Teams) = 3) then
+  begin // score generation for 3 teams,
+    // winner gets 3 points 2nd gets 1 point
+    for I := 0 to High(Rounds[CurRound].Ranking) do
+      if (Rounds[CurRound].Ranking[I].Rank = PR_First) then
+        Inc(Teams[Rounds[CurRound].Ranking[I].Team].Score, 3)
+      else if (Rounds[CurRound].Ranking[I].Rank = PR_Second) then
+        Inc(Teams[Rounds[CurRound].Ranking[I].Team].Score);
+  end
+end;
+
+{ set the attributes of Info to default values }
+procedure TPartyGame.DefaultModeInfo(var Info: TParty_ModeInfo);
+begin
+  Info.Name := 'undefined';
+  Info.Parent := -1; //< not loaded by plugin (e.g. Duell)
+  Info.CanNonParty := false;
+  Info.CanParty := false;
+  Info.PlayerCount := High(Integer); //< no restrictions either on player count
+  Info.TeamCount := High(Integer);   //< nor on team count
+  Info.Functions.BeforeSongSelect := ''; //< use default functions
+  Info.Functions.AfterSongSelect  := '';
+  Info.Functions.BeforeSing       := '';
+  Info.Functions.OnSing           := '';
+  Info.Functions.AfterSing        := '';
+end;
+
+{ registers a new mode, returns true on success
+  (mode name does not already exist) }
+function TPartyGame.RegisterMode(Info: TParty_ModeInfo): Boolean;
+  var
+    Len: integer;
+    LowerName: String;
+    I: integer;
+begin
+  Result := false;
+
+  if (Info.Name <> 'undefined') then
+  begin
+    // search for a plugin w/ same name
+    LowerName := lowercase(Info.Name); // case sensitive search
+    for I := 0 to high(Modes) do
+      if (LowerName = lowercase(Modes[I].Name)) then
+        exit; //< no success (name already exist)
+
+    // add new mode to array and append and clear a new TimesPlayed element
+    Len := Length(Modes);
+    SetLength(Modes, Len + 1);
+    SetLength(TimesPlayed, Len + 1);
+
+    Modes[Len] := Info;
+    TimesPlayed[Len] := 0;
+
+    Result := True;
+  end;
+end;
+
+{ returns true if a mode is playable with current playerconfig }
+function TPartyGame.ModePlayable(I: integer): boolean;
+  var
+    J: integer;
 begin
-  //Set current round to 1
-  CurRound := 255;
+  if (Length(Teams) = 0) then
+    Result := true
+  else
+  begin
+    if (Modes[I].TeamCount and (1 shl (Length(Teams) - 1)) <> 0) then
+    begin
+      Result := true;
 
-  PlayersPlay := Teams.NumTeams;
+      for J := 0 to High(Teams) do
+        Result := Result and (Modes[I].PlayerCount and (1 shl (Length(Teams[J].Players) - 1)) <> 0);
+    end
+    else
+      Result := false;
+  end;
+end;
 
-  //Get team-mode and set joker, also set TimesPlayed
-  TeamMode := true;
-  for I := 0 to Teams.NumTeams - 1 do
+{ returns true if modes are available for
+  players and teams that are currently set
+  up. if there are no teams set up it returns
+  if there are any party modes available }
+function TPartyGame.ModesAvailable: Boolean;
+  var
+    I: integer;
+    CountTeams: integer;
+begin
+  CountTeams := Length(Teams);
+  if CountTeams = 0 then
+  begin
+    Result := (Length(Modes) > 0);
+  end
+  else
   begin
-    if Teams.Teaminfo[I].NumPlayers < 2 then
+    Result := false;
+    for I := 0 to High(Modes) do
     begin
-      TeamMode := false;
+      Result := ModePlayable(I);
+
+      if Result then
+        Exit;
     end;
-    //Set player attributes
-    for J := 0 to Teams.TeamInfo[I].NumPlayers-1 do
+  end;
+end;       
+
+{ returns an array with the name of all available modes (that
+  are playable with current player configuration }
+function TPartyGame.GetAvailableModes: AParty_ModeList;
+  var
+    I: integer;
+    Len: integer;
+begin
+  Len := 0;
+  SetLength(Result, Len + 1);
+  Result[Len].Index := Party_Round_Random;
+  Result[Len].Name := Language.Translate('MODE_RANDOM_NAME');
+
+  for I := 0 to High(Modes) do
+    if (ModePlayable(I)) then
     begin
-      Teams.TeamInfo[I].Playerinfo[J].TimesPlayed := 0;
+      Inc(Len);
+      SetLength(Result, Len + 1);
+      Result[Len].Index := I;
+      Result[Len].Name := Language.Translate('MODE_' + Uppercase(Modes[I].Name) + '_NAME');
     end;
-    Teams.Teaminfo[I].Joker := Round(NumRounds * 0.7);
-    Teams.Teaminfo[I].Score := 0;
+end;
+
+{ adds a team to the team array, returning its id
+  can only be called when game is not already started }
+function TPartyGame.AddTeam(Name: String): Integer;
+begin
+  Result := -1;
+  if (not bPartyStarted) and (Length(Name) > 0) and (Length(Teams) < Party_Teams_Max) then
+  begin
+    Result := Length(Teams);
+    SetLength(Teams, Result + 1);
+
+    Teams[Result].Name := Name;
+    Teams[Result].Score := 0;
+    Teams[Result].JokersLeft := Party_Count_Jokers;
+    Teams[Result].NextPlayer := -1;
   end;
+end;
 
-  //Fill plugin array
-  SetLength(Plugins, 0);
-  for I := 0 to high(DLLMan.Plugins) do
+{ adds a player to the player array, returning its id
+  can only be called when game is not already started }
+function TPartyGame.AddPlayer(Team: Integer; Name: String): Integer;
+begin
+  Result := -1;
+
+  if (not bPartyStarted) and (Team >= 0) and (Team <= High(Teams)) and (Length(Teams[Team].Players) < Party_Players_Max) and (Length(Name) > 0) then
   begin
-    if TeamMode or (not DLLMan.Plugins[I].TeamModeOnly) then
-    begin 
-      //Add only those plugins playable with current PlayerConfiguration
-      Len := Length(Plugins);
-      SetLength(Plugins, Len + 1);
-      Plugins[Len].ID := I;
-      Plugins[Len].TimesPlayed := 0;
-    end;
+    // append element to players array
+    Result := Length(Teams[Team].Players);
+    SetLength(Teams[Team].Players, Result + 1);
+
+    // fill w/ data
+    Teams[Team].Players[Result].Name := Name;
+    Teams[Team].Players[Result].TimesPlayed := 0;
   end;
+end;
+
+{ starts a new PartyGame, returns true on success
+  before a call of this function teams and players
+  has to be added by AddTeam and AddPlayer }
+function TPartyGame.StartGame(Rounds: ARounds): Boolean;
+  var
+    I: integer;
+begin
+  Result := false;
 
-  //Set rounds
-  if (Length(Plugins) >= 1) then
+  if (not bPartyStarted) and (Length(Rounds) > 0) and (Length(Teams) >= Party_Teams_Min) then
   begin
-    SetLength (Rounds, NumRounds);
-    for I := 0 to NumRounds - 1 do
+    // check teams for minimal player count
+    for I := 0 to High(Teams) do
+      if (Length(Teams[I].Players) < Party_Players_Min) then
+        exit;
+
+    // create rounds array
+    SetLength(Self.Rounds, Length(Rounds));
+
+    for I := 0 to High(Rounds) do
     begin
-      PartySession.Rounds[I].Plugin := GetRandomPlugin(Plugins);
-      PartySession.Rounds[I].Winner := 255;
+      // copy round or select a random round
+      if (Rounds[I] <> Party_Round_Random) and (Rounds[I] >= 0) and (Rounds[I] <= High(Modes)) then
+        Self.Rounds[I].Mode := Rounds[I]
+      else
+        Self.Rounds[I].Mode := GetRandomMode;
+
+      Self.Rounds[I].AlreadyPlayed := false;
+      Self.Rounds[I].RankingSet := false;
+
+      SetLength(Self.Rounds[I].Ranking, 0);
     end;
+
+    // get the party started!11
+    bPartyStarted := true;
+    bPartyGame := true;
+    CurRound := low(integer); //< set not to -1 to indicate that party game is not finished
+
+    // first round
+    NextRound;
+
+    Result := True;
+  end;
+end;
+
+{ sets the winner(s) of current round
+  returns true on success }
+function TPartyGame.SetRanking(Ranking: AParty_TeamRanking): Boolean;
+  var
+    I, J: Integer;
+    TeamExists: Integer;
+    Len: Integer;
+    Temp: TParty_TeamRanking;
+begin
+  if (bPartyStarted) and (CurRound >= 0) and (CurRound <= High(Rounds)) then
+  begin
+    Rounds[CurRound].Ranking := Ranking;
+    Result := true;
+
+    // look for teams that don't exist
+    TeamExists := 0;
+    for I := 0 to High(Rounds[CurRound].Ranking) do
+      TeamExists := TeamExists or (1 shl (Rounds[CurRound].Ranking[I].Team-1));
+
+    // create teams that don't exist
+    Len := Length(Rounds[CurRound].Ranking);
+    for I := 0 to High(Teams) do
+      if (TeamExists and (1 shl I) = 0) then
+      begin
+        Inc(Len);
+        SetLength(Rounds[CurRound].Ranking, Len);
+        Rounds[CurRound].Ranking[Len-1].Team := I + 1;
+        Rounds[CurRound].Ranking[Len-1].Rank := Length(Teams);
+      end;
+
+    // we may remove rankings from invalid teams here to
+    // but at the moment this is not necessary, because the
+    // functions this function is called from don't create
+    // invalid rankings
+
+    // bubble sort rankings by team
+    J := High(Rounds[CurRound].Ranking);
+    repeat
+      for I := 0 to J - 1 do
+        if (Rounds[CurRound].Ranking[I].Team > Rounds[CurRound].Ranking[I+1].Team) then
+        begin
+          Temp := Rounds[CurRound].Ranking[I];
+          Rounds[CurRound].Ranking[I] := Rounds[CurRound].Ranking[I+1];
+          Rounds[CurRound].Ranking[I+1] := Temp;
+        end;
+      Dec(J);
+    until J <= 0;
+
+    //set rounds RankingSet to true
+    Rounds[CurRound].RankingSet := true;
   end
   else
-    SetLength (Rounds, 0);
+    Result := false;
 end;
 
-{**
- * Returns a random player to play next round
- *}
-function TPartySession.GetRandomPlayer(Team: byte): byte;
-var
-  I, R:        integer;
-  LowestTP:    byte;
-  NumPwithLTP: byte;
+{ sets ranking of current round by score saved in players array }
+procedure TPartyGame.SetRankingByScore;
+  var
+    I, J: Integer;
+    Rank: Integer;
+    Ranking: AParty_TeamRanking;
+    Scores: array of Integer;
+    TmpRanking: TParty_TeamRanking;
+    TmpScore: Integer;
 begin
-  LowestTP    := high(byte);
-  NumPwithLTP := 0;
-  Result      := 0;
-
-  //Search for players that have not often played yet
-  for I := 0 to Teams.Teaminfo[Team].NumPlayers - 1 do
+  if (Length(Player) = Length(Teams)) then
   begin
-    if (Teams.Teaminfo[Team].Playerinfo[I].TimesPlayed < lowestTP) then
+    SetLength(Ranking, Length(Teams));
+    SetLength(Scores, Length(Teams));
+
+    // fill ranking array
+    for I := 0 to High(Ranking) do
     begin
-      lowestTP := Teams.Teaminfo[Team].Playerinfo[I].TimesPlayed;
-      NumPwithLTP := 1;
-    end
-    else if (Teams.Teaminfo[Team].Playerinfo[I].TimesPlayed = lowestTP) then
+      Ranking[I].Team := I;
+      Ranking[I].Rank := 0;
+      Scores[I] := Player[I].ScoreTotalInt;
+    end;
+
+    // bubble sort by score
+    J := High(Ranking);
+    repeat
+      for I := 0 to J - 1 do
+        if (Scores[I] < Scores[I+1]) then
+        begin
+          TmpRanking := Ranking[I];
+          Ranking[I] := Ranking[I+1];
+          Ranking[I+1] := TmpRanking;
+
+          TmpScore := Scores[I];
+          Scores[I] := Scores[I+1];
+          Scores[I+1] := TmpScore;
+        end;
+      Dec(J);
+    until J <= 0;
+
+    // set rank field
+    Rank := 1; //first rank has id 1
+    for I := 0 to High(Ranking) do
     begin
-      Inc(NumPwithLTP);
+      Ranking[I].Rank := Rank;
+
+      if (I < High(Ranking)) and (Scores[I] <> Scores[I+1]) then
+        Inc(Rank); // next rank if next team has different score
     end;
+  end
+  else
+    SetLength(Ranking, 0);
+
+  SetRanking(Ranking);
+end;
+
+{ increases round counter by 1 and clears all round specific information;
+  returns the number of the current round or -1 if last round has already
+  been played }
+function TPartyGame.NextRound: integer;
+  var I: Integer;
+begin
+  // some lines concerning the previous round
+  if (CurRound >= 0) then
+  begin
+    Rounds[CurRound].AlreadyPlayed := true;
+
+    GenScores;
   end;
 
-  //Create random number
-  R := Random(NumPwithLTP);
+  // increase round counter
+  Inc(CurRound);
+  if (CurRound < -1) then // we start first round
+    CurRound := 0;
+
+  if (CurRound > High(Rounds)) then
+    CurRound := -1; //< last round played
+
+  Result := CurRound;
 
-  //Search for random player
-  for I := 0 to Teams.Teaminfo[Team].NumPlayers - 1 do
+  // some lines concerning the next round
+  if (CurRound >= 0) then
   begin
-    if Teams.Teaminfo[Team].Playerinfo[I].TimesPlayed = lowestTP then
+    // select player
+    for I := 0 to High(Teams) do
+      Teams[I].NextPlayer := GetRandomPlayer(I);
+  end;
+end;
+
+{ indicates that current round has already been played }
+procedure TPartyGame.RoundPlayed;
+begin
+  if (bPartyStarted) and (CurRound >= 0) and (CurRound <= High(Rounds)) then
+  begin
+    // set rounds ranking by score if it was not set by plugin
+    if (not Rounds[CurRound].RankingSet) then
+      SetRankingByScore;
+
+    Rounds[CurRound].AlreadyPlayed := True;
+  end;
+end;
+
+{ returns true if last round was already played }
+function TPartyGame.GameFinished: Boolean;
+begin
+  Result := (bPartyStarted and (CurRound = -1));
+end;
+
+{ private: calls the specified function Func from lua plugin Parent
+           if both exist.
+           return true if default function should be called
+           (function or plugin does not exist, or function returns
+           true) }
+function TPartyGame.CallLua(Parent: Integer; Func: String):Boolean;
+  var
+    P: TLuaPlugin;
+begin
+  // call default function by default
+  Result := true;
+
+  // check for core plugin and empty function name
+  if (Parent >= 0) and (Length(Func) > 0) then
+  begin
+    // get plugin that registred the mode
+    P := LuaCore.GetPluginById(Parent);
+
+    if (P <> nil) then
     begin
-      //Player found
-      if (R = 0) then
-      begin
-        Result := I;
-        Break;
-      end;
-      
-      Dec(R);
+      if (P.CallFunctionByName(Func, 0, 1)) then
+        // check result
+        Result := (lua_toboolean(P.LuaState, 1));
     end;
   end;
 end;
 
-{**
- * Prepares ScreenSingModi for next round and loads plugin
- *}
-procedure TPartySession.StartRound;
-var
-  I: integer;
+{ call plugins defined function and/or default procedure
+  only default procedure is called when no function is defined by plugin
+  if plugins function returns true then default is called after plugins
+  function was executed}
+procedure TPartyGame.CallBeforeSongSelect;
+  var
+    ExecuteDefault: boolean;
 begin
-  if ((CurRound < high(Rounds)) or (CurRound = high(CurRound))) then
+  if not bPartyStarted then
+    ExecuteDefault := true
+  else if (CurRound >= 0) then
   begin
-    //Increase Current Round
-    Inc(CurRound);
-
-    Rounds[CurRound].Winner := 255;
-    DllMan.LoadPlugin(Rounds[CurRound].Plugin);
+    // we set screen song to party mode
+    // plugin should not have to do this if it
+    // don't want default procedure to be executed
+    ScreenSong.Mode := smPartyMode;
 
-    //Select Players
-    for I := 0 to Teams.NumTeams - 1 do
-      Teams.Teaminfo[I].CurPlayer := GetRandomPlayer(I);
+    with Modes[Rounds[CurRound].Mode] do
+      ExecuteDefault := (CallLua(Parent, Functions.BeforeSongSelect));
+  end
+  else
+    ExecuteDefault := true;
 
-    //Set ScreenSingModie Variables
-    ScreenSingModi.TeamInfo := Teams;
+  // execute default function:
+  if ExecuteDefault then
+  begin
+    // display song select screen
+    Display.FadeTo(@ScreenSong);
   end;
 end;
 
-//----------
-//EndRound - Get Winner from ScreenSingModi and Save Data to RoundArray
-//----------
-procedure TPartySession.EndRound;
-var
-  I: Integer;
+procedure TPartyGame.CallAfterSongSelect;
+  var
+    ExecuteDefault: boolean; 
 begin
-  //Copy Winner
-  Rounds[CurRound].Winner := ScreenSingModi.Winner;
-  //Set Scores
-  GenScores;
+  if not bPartyStarted then
+    ExecuteDefault := true
+  else if (CurRound >= 0) then
+  begin
+    with Modes[Rounds[CurRound].Mode] do
+      ExecuteDefault := (CallLua(Parent, Functions.AfterSongSelect));
+  end
+  else
+    ExecuteDefault := true;
+
+  // execute default function:
+  if ExecuteDefault then
+  begin
+    // display sing screen
+    ScreenSong.StartSong;
+  end;
+end;
 
-  //Increase TimesPlayed 4 all Players
-  For I := 0 to Teams.NumTeams-1 do
-    Inc(Teams.Teaminfo[I].Playerinfo[Teams.Teaminfo[I].CurPlayer].TimesPlayed);
+procedure TPartyGame.CallBeforeSing;
+  var
+    ExecuteDefault: boolean;
+begin
+  if not bPartyStarted then
+    ExecuteDefault := true
+  else if (CurRound >= 0) then
+  begin
+    with Modes[Rounds[CurRound].Mode] do
+      ExecuteDefault := (CallLua(Parent, Functions.BeforeSing));
+  end
+  else
+    ExecuteDefault := true;
 
+  // execute default function:
+  if ExecuteDefault then
+  begin
+    //nothing atm
+    { to-do : compartmentalize TSingScreen.OnShow into
+              functions for init of a specific part of
+              sing screen.
+              these functions should be called here before
+              sing screen is shown, or it should be called
+              by plugin if it wants to define a custom
+              singscreen start up. }
+
+    //set correct playersplay
+    if (bPartyGame) then
+      PlayersPlay := Length(Teams);
+  end;
 end;
 
-//----------
-//IsWinner - returns true if the player's bit is set in the winner byte
-//----------
-function TPartySession.IsWinner(Player, Winner: byte): boolean;
-var
-  Mask: byte;
+procedure TPartyGame.CallOnSing;
+  var
+    ExecuteDefault: boolean;
 begin
-  Mask := 1 shl Player;
-  Result := (Winner and Mask) <> 0;
+  if not bPartyStarted then
+    ExecuteDefault := true
+  else if (CurRound >= 0) then
+  begin
+    with Modes[Rounds[CurRound].Mode] do
+      ExecuteDefault := (CallLua(Parent, Functions.OnSing));;
+  end
+  else
+    ExecuteDefault := true;
+
+  // execute default function:
+  if ExecuteDefault then
+  begin
+    //nothing atm
+  end;
 end;
 
-//----------
-//GenScores - increase scores for current round
-//----------
-procedure TPartySession.GenScores;
-var
-  I: byte;
+procedure TPartyGame.CallAfterSing;
+  var
+    ExecuteDefault: boolean;
 begin
-  for I := 0 to Teams.NumTeams - 1 do
+  if not bPartyStarted then
+    ExecuteDefault := true
+  else if (CurRound >= 0) then
+  begin
+    with Modes[Rounds[CurRound].Mode] do
+      ExecuteDefault := (CallLua(Parent, Functions.AfterSing));
+  end
+  else
+    ExecuteDefault := true;
+
+  // execute default function:
+  if ExecuteDefault then
   begin
-    if isWinner(I, Rounds[CurRound].Winner) then
-      Inc(Teams.Teaminfo[I].Score);
+    if (bPartyGame) then
+      // display party score screen
+      Display.FadeTo(@ScreenPartyScore)
+    else //display standard score screen
+      Display.FadeTo(@ScreenScore);
   end;
 end;
 
-//----------
-//GetTeamOrder - returns the placement of each Team [First Position of Array is Teamnum of first placed Team, ...]
-//----------
-function TPartySession.GetTeamOrder: TeamOrderArray;
-var
-  I, J:     integer;
-  ATeams:   array [0..5] of TeamOrderEntry;
-  TempTeam: TeamOrderEntry;
+{ returns an array[1..6] of integer. the index stands for the placing,
+  value is the team number (in the team array) }
+function TPartyGame.GetTeamRanking: AParty_TeamRanking;
+  var
+    I, J: Integer;
+    Temp: TParty_TeamRanking;
+    Rank: Integer;
 begin
-  // TODO: PartyMode: Write this in another way, so that teams with the same score get the same place
-  //Fill Team array
-  for I := 0 to Teams.NumTeams - 1 do
+  SetLength(Result, Length(Teams));
+
+  // fill ranking array
+  for I := 0 to High(Result) do
   begin
-    ATeams[I].Teamnum := I;
-    ATeams[I].Score := Teams.Teaminfo[I].Score;
+    Result[I].Team := I;
+    Result[I].Rank := 0;
   end;
 
-  //Sort teams
-  for J := 0 to Teams.NumTeams - 1 do
-    for I := 1 to Teams.NumTeams - 1 do
-      if ATeams[I].Score > ATeams[I-1].Score then
+  // bubble sort by score
+  J := High(Result);
+  repeat
+    for I := 0 to J - 1 do
+      if (Teams[Result[I].Team].Score < Teams[Result[I+1].Team].Score) then
       begin
-        TempTeam    := ATeams[I-1];
-        ATeams[I-1] := ATeams[I];
-        ATeams[I]   := TempTeam;
+        Temp := Result[I];
+        Result[I] := Result[I+1];
+        Result[I+1] := Temp;
       end;
+    Dec(J);
+  until J <= 0;
+
+  // set rank field
+  Rank := 1; //first rank has id 1
+  for I := 0 to High(Result) do
+  begin
+    Result[I].Rank := Rank;
 
-  //Copy to Result
-  for I := 0 to Teams.NumTeams-1 do
-    Result[I] := ATeams[I].TeamNum;
+    if (I < High(Result)) and (Teams[Result[I].Team].Score <> Teams[Result[I+1].Team].Score) then
+      Inc(Rank); // next rank if next team has different score 
+  end; 
 end;
 
-//----------
-//GetWinnerString - Get string with WinnerTeam Name, when there is more than one Winner than Connect with and or ,
-//----------
-function  TPartySession.GetWinnerString(Round: byte): string;
+{ returns a string like "Team 1 (and Team 2) win"
+  if Round is in range from 0 to high(Rounds) then
+  result is name of winners of specified round.
+  if Round is -1 the result is name of winners of
+  the whole party game}
+function TPartyGame.GetWinnerString(Round: integer): UTF8String;
 var
-  Winners: array of string;
-  I:       integer;
+  Winners: array of UTF8String;
+  I: integer;
+  Ranking: AParty_TeamRanking;
 begin
-  Result := Language.Translate('PARTY_NOBODY');
+  Result := '';
+  Ranking := nil;
   
-  if (Round > High(Rounds)) then
-    exit;
-
-  if (Rounds[Round].Winner = 0) then
+  if (Round >= 0) and (Round <= High(Rounds)) then
   begin
-    exit;
-  end;
+    if (not Rounds[Round].AlreadyPlayed) then
+      Result := Language.Translate('PARTY_NOTPLAYEDYET')
+    else
+      Ranking := Rounds[Round].Ranking;
+  end
+  else if (Round = -1) then
+    Ranking := GetTeamRanking;
 
-  if (Rounds[Round].Winner = 255) then
-  begin
-    Result := Language.Translate('PARTY_NOTPLAYEDYET');
-    exit;
-  end;
 
-  SetLength(Winners, 0);
-  for I := 0 to Teams.NumTeams - 1 do
+  if (Ranking <> nil) then
   begin
-    if isWinner(I, Rounds[Round].Winner) then
+    SetLength(Winners, 0);
+    for I := 0 to High(Ranking) do
     begin
-      SetLength(Winners, Length(Winners) + 1);
-      Winners[high(Winners)] := Teams.TeamInfo[I].Name;
+      if (Ranking[I].Rank = PR_First) and (Ranking[I].Team >= 0) and (Ranking[I].Team <= High(Teams)) then
+      begin
+        SetLength(Winners, Length(Winners) + 1);
+        Winners[high(Winners)] := UTF8String(Teams[Ranking[I].Team].Name);
+      end;
     end;
+
+    if (Length(Winners) > 0) then
+      Result := Language.Implode(Winners);
   end;
-  Result := Language.Implode(Winners);
+
+  if (Length(Result) = 0) then
+    Result := Language.Translate('PARTY_NOBODY');
 end;
 
 end.
diff --git a/cmake/src/base/UPath.pas b/cmake/src/base/UPath.pas
index 2316ac02..7c00e7b1 100644
--- a/cmake/src/base/UPath.pas
+++ b/cmake/src/base/UPath.pas
@@ -1,188 +1,1427 @@
-{* UltraStar Deluxe - Karaoke Game
- *
- * UltraStar Deluxe is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * 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; either version 2
- * of the License, or (at your option) any later version.
- *
- * 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; see the file COPYING. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- *
- * $URL: https://ultrastardx.svn.sourceforge.net/svnroot/ultrastardx/trunk/src/base/UPath.pas $
- * $Id: UPath.pas 1624 2009-03-06 23:45:10Z k-m_schindler $
- *}
-
-unit UPath;
-
-interface
-
-{$IFDEF FPC}
-  {$MODE Delphi}
-{$ENDIF}
-
-{$I switches.inc}
-
-uses
-  SysUtils,
-  Classes;
-
-var
-  // Absolute Paths
-  GamePath:         string;
-  SoundPath:        string;
-  SongPaths:        TStringList;
-  LogPath:          string;
-  ThemePath:        string;
-  SkinsPath:        string;
-  ScreenshotsPath:  string;
-  CoverPaths:       TStringList;
-  LanguagesPath:    string;
-  PluginPath:       string;
-  VisualsPath:      string;
-  FontPath:         string;
-  ResourcesPath:    string;
-  PlayListPath:     string;
-
-function FindPath(out PathResult: string; const RequestedPath: string; NeedsWritePermission: boolean): boolean;
-procedure InitializePaths;
-procedure AddSongPath(const Path: string);
-
-implementation
-
-uses
-  StrUtils,
-  UPlatform,
-  UCommandLine,
-  ULog;
-
-procedure AddSpecialPath(var PathList: TStringList; const Path: string);
-var
-  Index: integer;
-  PathAbs, OldPathAbs: string;
-begin
-  if (PathList = nil) then
-    PathList := TStringList.Create;
-
-  if (Path = '') or not ForceDirectories(Path) then
-    Exit;
-
-  PathAbs := IncludeTrailingPathDelimiter(ExpandFileName(Path));
-
-  // check if path or a part of the path was already added
-  for Index := 0 to PathList.Count-1 do
-  begin
-    OldPathAbs := IncludeTrailingPathDelimiter(ExpandFileName(PathList[Index]));
-    // check if the new directory is a sub-directory of a previously added one.
-    // This is also true, if both paths point to the same directories.
-    if (AnsiStartsText(OldPathAbs, PathAbs)) then
-    begin
-      // ignore the new path
-      Exit;
-    end;
-
-    // check if a previously added directory is a sub-directory of the new one.
-    if (AnsiStartsText(PathAbs, OldPathAbs)) then
-    begin
-      // replace the old with the new one.
-      PathList[Index] := PathAbs;
-      Exit;
-    end;
-  end;
-
-  PathList.Add(PathAbs);
-end;
-
-procedure AddSongPath(const Path: string);
-begin
-  AddSpecialPath(SongPaths, Path);
-end;
-
-procedure AddCoverPath(const Path: string);
-begin
-  AddSpecialPath(CoverPaths, Path);
-end;
-
-(**
- * Initialize a path variable
- * After setting paths, make sure that paths exist
- *)
-function FindPath(out   PathResult:           string; 
-                  const RequestedPath:        string; 
-		        NeedsWritePermission: boolean)
-         : boolean;
-begin
-  Result := false;
-
-  if (RequestedPath = '') then
-    Exit;
-
-  // Make sure the directory exists
-  if (not ForceDirectories(RequestedPath)) then
-  begin
-    PathResult := '';
-    Exit;
-  end;
-
-  PathResult := IncludeTrailingPathDelimiter(RequestedPath);
-
-  if (NeedsWritePermission) and
-     (FileIsReadOnly(RequestedPath)) then
-  begin
-    Exit;
-  end;
-
-  Result := true;
-end;
-
-(**
- * Function sets all absolute paths e.g. song path and makes sure the directorys exist
- *)
-procedure InitializePaths;
-begin
-  // Log directory (must be writable)
-  if (not FindPath(LogPath, Platform.GetLogPath, true)) then
-  begin
-    Log.FileOutputEnabled := false;
-    Log.LogWarn('Log directory "'+ Platform.GetLogPath +'" not available', 'InitializePaths');
-  end;
-
-  FindPath(SoundPath,     Platform.GetGameSharedPath + 'sounds',    false);
-  FindPath(ThemePath,     Platform.GetGameSharedPath + 'themes',    false);
-  FindPath(SkinsPath,     Platform.GetGameSharedPath + 'themes',    false);
-  FindPath(LanguagesPath, Platform.GetGameSharedPath + 'languages', false);
-  FindPath(PluginPath,    Platform.GetGameSharedPath + 'plugins',   false);
-  FindPath(VisualsPath,   Platform.GetGameSharedPath + 'visuals',   false);
-  FindPath(FontPath,      Platform.GetGameSharedPath + 'fonts',     false);
-  FindPath(ResourcesPath, Platform.GetGameSharedPath + 'resources', false);
-
-  // Playlists are not shared as we need one directory to write too
-  FindPath(PlaylistPath, Platform.GetGameUserPath + 'playlists', true);
-
-  // Screenshot directory (must be writable)
-  if (not FindPath(ScreenshotsPath,  Platform.GetGameUserPath + 'screenshots', true)) then
-  begin
-    Log.LogWarn('Screenshot directory "'+ Platform.GetGameUserPath +'" not available', 'InitializePaths');
-  end;
-
-  // Add song paths
-  AddSongPath(Params.SongPath);
-  AddSongPath(Platform.GetGameSharedPath + 'songs');
-  AddSongPath(Platform.GetGameUserPath + 'songs');
-
-  // Add category cover paths
-  AddCoverPath(Platform.GetGameSharedPath + 'covers');
-  AddCoverPath(Platform.GetGameUserPath + 'covers');
-end;
-
-end.
+{* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *}
+
+unit UPath;
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+interface
+
+uses
+  SysUtils,
+  Classes,
+  IniFiles,
+  {$IFDEF MSWINDOWS}
+  TntClasses,
+  {$ENDIF}
+  UConfig,
+  UUnicodeUtils;
+
+type
+  IPath = interface;
+
+  {$IFDEF FPC}
+  TFileHandle = THandle;
+  {$ELSE}
+  TFileHandle = Longint;
+  {$ENDIF}
+
+  {**
+   * TUnicodeMemoryStream
+   *}
+  TUnicodeMemoryStream = class(TMemoryStream)
+  public
+    procedure LoadFromFile(const FileName: IPath);
+    procedure SaveToFile(const FileName: IPath);
+  end;
+
+  {**
+   * Unicode capable IniFile implementation.
+   * TMemIniFile and TIniFile are not able to handle INI-files with
+   * an UTF-8 BOM. This implementation checks if an UTF-8 BOM exists
+   * and removes it from the internal string-list.
+   * UTF8Encoded is set accordingly.
+   *}
+  TUnicodeMemIniFile = class(TMemIniFile)
+  private
+    FFilename: IPath;
+    FUTF8Encoded: boolean;
+  public
+    constructor Create(const FileName: IPath; UTF8Encoded: boolean = false); reintroduce;
+    procedure UpdateFile; override;
+    property UTF8Encoded: boolean READ FUTF8Encoded WRITE FUTF8Encoded;
+  end;
+  
+  {**
+   * TBinaryFileStream (inherited from THandleStream)
+   *}
+  {$IFDEF MSWINDOWS}
+  TBinaryFileStream = class(TTntFileStream)
+  {$ELSE}
+  TBinaryFileStream = class(TFileStream)
+  {$ENDIF}
+  public
+    {**
+     * @seealso TFileStream.Create for valid Mode parameters
+     *}
+    constructor Create(const FileName: IPath; Mode: word);
+  end;
+
+  {**
+   * TTextFileStream
+   *}
+  TTextFileStream = class(TStream)
+  protected
+    fLineBreak: RawByteString;
+    fFilename: IPath;
+    fMode: word;
+
+    function ReadLine(var Success: boolean): RawByteString; overload; virtual; abstract;
+  public
+    constructor Create(Filename: IPath; Mode: word);
+
+    function ReadString(): RawByteString; virtual; abstract;
+    function ReadLine(var Line: UTF8String): boolean; overload;
+    function ReadLine(var Line: AnsiString): boolean; overload;
+
+    procedure WriteString(const Str: RawByteString); virtual;
+    procedure WriteLine(const Line: RawByteString); virtual;
+
+    property LineBreak: RawByteString read fLineBreak write fLineBreak;
+    property Filename: IPath read fFilename;
+  end;
+
+  {**
+   * TMemTextStream
+   *}
+  TMemTextFileStream = class(TTextFileStream)
+  private
+    fStream: TMemoryStream;
+  protected
+    function GetSize: int64; override;
+
+    {**
+     * Copies fStream.Memory from StartPos to EndPos-1 to the result string;
+     *}
+    function CopyMemString(StartPos: int64; EndPos: int64): RawByteString;
+  public
+    constructor Create(Filename: IPath; Mode: word);
+    destructor Destroy(); override;
+
+    function Read(var Buffer; Count: longint): longint; override;
+    function Write(const Buffer; Count: longint): longint; override;
+    function Seek(Offset: longint; Origin: word): longint; override;
+    function Seek(const Offset: int64; Origin: TSeekOrigin): int64; override;
+
+    function ReadLine(var Success: boolean): RawByteString; override;
+    function ReadString(): RawByteString; override;
+  end;
+
+  {**
+  TUnicodeIniStream = class()
+  end;
+  *}
+
+  {**
+   * pdKeep:   Keep path as is, neither remove or append a delimiter
+   * pdAppend: Append a delimiter if path does not have a trailing one 
+   * pdRemove: Remove a trailing delimiter from the path 
+   *}
+  TPathDelimOption = (pdKeep, pdAppend, pdRemove);
+
+  IPathDynArray = array of IPath;
+
+  {**
+   * An IPath represents a filename, a directory or a filesystem path in general.
+   * It hides some of the operating system's specifics like path delimiters
+   * and encodings and provides an easy to use interface to handle them.
+   * Internally all paths are stored with the same path delimiter (PathDelim)
+   * and encoding (UTF-8). The transformation is already done AT THE CREATION of
+   * the IPath and hence calls to e.g. IPath.Equal() will not distinguish between
+   * Unix and Windows style paths.
+   *
+   * Create new paths with one of the Path() functions.
+   * If you need a string representation use IPath.ToNative/ToUTF8/ToWide.
+   * Note that due to the path-delimiter and encoding transformation the string
+   * might have changed. Path('one\test/path').ToUTF8() might return 'one/test/path'.
+   *
+   * It is recommended to use an IPath as long as possible without a string
+   * conversion (IPath.To...()). The whole Delphi (< 2009) and FPC RTL is ANSI
+   * only on Windows. If you would use for example FileExists(MyPath.ToNative)
+   * it would not find a file which contains characters that are not in the
+   * current locale. Same applies to AssignFile(), TFileStream.Create() and
+   * everything else in the RTL that expects a filename.
+   * As a rule of thumb: NEVER use any of the Delphi/FPC RTL filename functions
+   * if the filename parameter is not of a UTF8String or WideString type.
+   *
+   * If you need to open a file use TBinaryStream or TFileStream instead. Many
+   * of the RTL classes offer a LoadFromStream() method so ANSI Open() methods
+   * can be workaround.
+   *
+   * If there is only a ANSI and no IPath/UTF-8/WideString version and you cannot
+   * even pass a stream instead of a filename be aware that even if you know that
+   * a filename is ASCII only, subdirectories in an absolute path might contain
+   * some non-ASCII characters (for example the user's name) and hence might
+   * fail (if the characters are not in the current locale).
+   * It is rare but it happens.
+   *
+   * IMPORTANT:
+   *   This interface needs the cwstring unit on Unix (Max OS X / Linux) systems.
+   *   Cwstring functions (WideUpperCase, ...) cannot be used by external threads
+   *   as FPC uses Thread-Local-Storage for the implementation. As a result do not
+   *   call IPath stuff by external threads (e.g. in C callbacks or by SDL-threads).
+   *}
+  IPath = interface
+  ['{686BF103-CE43-4598-B85D-A2C3AF950897}']
+    {**
+     * Returns the path as an UTF8 encoded string.
+     * If UseNativeDelim is set to true, the native path delimiter ('\' on win32)
+     * is used. If it is set to false the (more) portable '/' delimiter will used.
+     *}
+    function ToUTF8(UseNativeDelim: boolean = true): UTF8String;
+
+    {**
+     * Returns the path as an UTF-16 encoded string.
+     * If UseNativeDelim is set to true, the native path delimiter ('\' on win32)
+     * is used. If it is set to false the delimiter will be '/'.
+     *}
+    function ToWide(UseNativeDelim: boolean = true): WideString;
+
+    {**
+     * Returns the path with the system's native encoding and path delimiter.
+     * Win32: ANSI (use the UTF-16 version IPath.ToWide() whenever possible)
+     * Mac:   UTF8
+     * Unix:  UTF8 or ANSI according to LC_CTYPE
+     *}
+    function ToNative(): RawByteString;
+
+    {**
+     * Note: File must be closed with FileClose(Handle) after usage
+     * @seealso SysUtils.FileOpen()
+     *}
+    function Open(Mode: longword): TFileHandle;
+
+    {** @seealso SysUtils.ExtractFileDrive() *}
+    function GetDrive(): IPath;
+
+    {** @seealso SysUtils.ExtractFilePath() *}
+    function GetPath(): IPath;
+
+    {** @seealso SysUtils.ExtractFileDir() *}
+    function GetDir(): IPath;
+
+    {** @seealso SysUtils.ExtractFileName() *}
+    function GetName(): IPath;
+
+    {** @seealso SysUtils.ExtractFileExtension() *}
+    function GetExtension(): IPath;
+
+    {**
+     * Returns a copy of the path with the extension changed to Extension.
+     * The file itself is not changed, use Rename() for this task.
+     * @seealso SysUtils.ChangeFileExt()
+     *}
+    function SetExtension(const Extension: IPath): IPath; overload;
+    function SetExtension(const Extension: RawByteString): IPath; overload;
+    function SetExtension(const Extension: WideString): IPath; overload;
+
+    {**
+     * Returns the representation of the path relative to Basename.
+     * Note that the basename must be terminated with a path delimiter
+     * otherwise the last path component will be ignored.
+     * @seealso SysUtils.ExtractRelativePath()
+     *}
+    function GetRelativePath(const BaseName: IPath): IPath;
+
+    {** @seealso SysUtils.ExpandFileName() *}
+    function GetAbsolutePath(): IPath;
+
+    {**
+     * Returns the concatenation of this path with Child. If this path does not
+     * end with a path delimiter one is inserted in front of the Child path.
+     * Example: Path('parent').Append(Path('child')) -> Path('parent/child')
+     *}
+    function Append(const Child: IPath; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
+    function Append(const Child: RawByteString; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
+    function Append(const Child: WideString; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
+
+    {**
+     * Splits the path into its components. Path delimiters are not removed from
+     * components. 
+     * Example: C:\test\my\dir -> ['C:\', 'test\', 'my\', 'dir']
+     *}
+    function SplitDirs(): IPathDynArray;
+
+    {**
+     * Returns the parent directory or PATH_NONE if none exists.
+     *}
+    function GetParent(): IPath;
+
+    {**
+     * Checks if this path is a subdir of or file inside Parent.
+     * If Direct is true this path must be a direct child.
+     * Example: C:\test\file is a direct child of C:\test and a child of C:\
+     *}
+    function IsChildOf(const Parent: IPath; Direct: boolean): boolean;
+
+    {**
+     * Adjusts the case of the path on case senstitive filesystems.
+     * If the path does not exist or the filesystem is case insensitive
+     * the original path will be returned. Otherwise a corrected copy.
+     *}
+    function AdjustCase(AdjustAllLevels: boolean): IPath;
+
+    {** @seealso SysUtils.IncludeTrailingPathDelimiter() *}
+    function AppendPathDelim(): IPath;
+
+    {** @seealso SysUtils.ExcludeTrailingPathDelimiter() *}
+    function RemovePathDelim(): IPath;
+
+    function Exists(): boolean;
+    function IsFile(): boolean;
+    function IsDirectory(): boolean;
+    function IsAbsolute(): boolean;
+    function GetFileAge(): integer; overload;
+    function GetFileAge(out FileDateTime: TDateTime): boolean; overload;
+    function GetAttr(): cardinal;
+    function SetAttr(Attr: Integer): boolean;
+    function IsReadOnly(): boolean;
+    function SetReadOnly(ReadOnly: boolean): boolean;
+
+    {**
+     * Checks if this path points to nothing, that means the path consists of
+     * the empty string '' and hence equals PATH_NONE.
+     * This is a shortcut for IPath.Equals('') or IPath.Equals(PATH_NONE).
+     * If IsUnset() returns true this path and PATH_NONE are equal but they must
+     * not be identical as the references might point to different objects.
+     *
+     * Example:
+     *   Path('').Equals(PATH_EMPTY) -> true
+     *   Path('') = PATH_EMPTY       -> false
+     *}
+    function IsUnset(): boolean;
+    function IsSet(): boolean;
+
+    {**
+     * Compares this path with Other and returns true if both paths are
+     * equal. Both paths are expanded and trailing slashes excluded before
+     * comparison. If IgnoreCase is true, the case will be ignored on
+     * case-sensitive filesystems.
+     *}
+    function Equals(const Other: IPath; IgnoreCase: boolean = false): boolean; overload;
+    function Equals(const Other: RawByteString; IgnoreCase: boolean = false): boolean; overload;
+    function Equals(const Other: WideString; IgnoreCase: boolean = false): boolean; overload;
+
+    {**
+     * Searches for a file in DirList. The Result is nil if the file was
+     * not found. Use IFileSystem.FileFind() instead if you want to use
+     * wildcards.
+     * @seealso SysUtils.FileSearch()
+     *}
+    function FileSearch(const DirList: IPath): IPath;
+
+    {**
+     * File must be closed with FileClose(Handle) after usage
+     *}
+    function CreateFile(): TFileHandle;
+    function DeleteFile(): boolean;
+    function CreateDirectory(Force: boolean = false): boolean;
+    function DeleteEmptyDir(): boolean;
+    function Rename(const NewName: IPath): boolean;
+    function CopyFile(const Target: IPath; FailIfExists: boolean): boolean;
+
+    // TODO: Dirwatch stuff
+    // AddFileChangeListener(Listener: TFileChangeListener);
+
+    {**
+     * Internal string representation. For debugging only.
+     *}
+    function GetIntern: UTF8String;
+    property Intern: UTF8String READ GetIntern;
+  end;
+
+{**
+ * Creates a new path with the given pathname. PathName can be either in UTF8
+ * or the local encoding.
+ * Notes:
+ * - On Apple only UTF8 is supported
+ * - Same applies to Unix with LC_CTYPE set to UTF8 encoding (default on newer systems)
+ *}
+function Path(const PathName: RawByteString; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
+function Path(PathName: PChar; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
+
+{**
+ * Creates a new path with the given UTF-16 pathname.
+ *}
+function Path(const PathName: WideString; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
+
+{**
+ * Returns a singleton for Path('').
+ *}
+function PATH_NONE(): IPath;
+
+implementation
+
+uses
+  RTLConsts,
+  UTextEncoding,
+  UFilesystem;
+
+{*
+ * Due to a compiler bug in FPC <= 2.2.4 reference counting does not work
+ * properly with interfaces (see http://bugs.freepascal.org/view.php?id=14019).
+ *
+ * There are two (probably more) scenarios causes a program to crash:
+ *
+ * 1. Assume we execute Path('fail').GetParent().ToUTF8(). The compiler will
+ * internally create a temporary variable to hold the result of Path('fail').
+ * This temporary var is then passed as Self to GetParent(). Unfortunately FPC
+ * does already decrement the ref-count of the temporary var at the end of the
+ * call to Path('fail') and the ref-count drops to zero and the temp object
+ * is destroyed as FPC erroneously assumes that the temp is not used anymore.
+ * As a result the Self variable in GetParent() will be invalid, the same
+ * applies to TPathImpl.fName which reference count dropped to zero when the
+ * temp was destroyed. Hence GetParent() will likely crash.
+ * If it does not, ToUTF8() will either return some random string
+ * (e.g. '' or stupid stuff like 'fhwkjehdk') or crash.
+ * Either way the result of ToUTF8() is messed up.
+ * This scenario applies whenever a function (or method) is called that returns
+ * an interfaced object (e.g. an IPath) and the result is used without storing
+ * a reference to it in a (temporary) variable first.
+ *
+ *  Tmp := Path('fail'); Tmp2 := Tmp.GetParent(); Tmp2.ToUTF8();
+ *
+ * will not crash but is very impractical and error-prone. Note that Tmp2 cannot
+ * be replaced with Tmp (see scenario 2).
+ *
+ * 2. Another situation this bug will ruin our lives is when a variable to an
+ * interfaced object is used at the left and right side of an assignment as in:
+ *   MyPath := MyPath.GetParent()
+ *
+ * Although the bug is already fixed in the FPC development version 2.3.1
+ * it will take quite some time till the next FPC release (> 2.2.4) in which
+ * this issue is fixed.
+ *
+ * To workaround this bug we use some very simple and stupid kind of garbage
+ * collection. New IPaths are stored in an IInterfaceList (call it GarbaegeList)
+ * to artificially increase the ref-count of the newly created object.
+ * This keeps the object alive when FPC's temporary variable comes to the end
+ * of its lifetime and the object's ref-count is decremented
+ * (and is now 1 instead of 0).
+ * Later on, the object is either garbage or referenced by another variable.
+ *
+ * Look at
+ *   MyPath := Path('SomeDir/SubDir').GetParent()
+ *
+ * (1) The result of Path('SomeDir/SubDir') is garbage as it is not used anymore.
+ * (2) The result of GetParent() is referenced by MyPath
+ * Object (1) has a reference count of 1 (as it is only referenced by the
+ * GarbageList). Object (2) is referenced twice (MyPath + GarbageList).
+ * When the reference to (2) is finally stored in MyPath we can safely remove
+ * (1) and (2) from the GarbageList so (1) will be freed and the ref-count of
+ * (2) will be decremented to 1.
+ *
+ * As we do not know when it is safe to remove an object from the GarbageList
+ * we assume that there are max. GarbageMaxCount IPath elements created until
+ * the execution of the expression is performed and a reference to the resulting
+ * object is assigned to a variable so all temps can be safely deleted.
+ *
+ * Worst-case scenarios are recursive calls or calls with large call stacks with
+ * functions that return an IPath. Also keep in mind that multiple threads might
+ * be executing such functions at the same time.
+ * A reasonable count might be a max. of 20.000 elements. With an average length
+ * of 40 UTF8 chars (maybe 60 byte with class info, pointer etc.) per IPath
+ * this will consume ~1.2MB.
+ *}
+{$IFDEF FPC}
+{$IF FPC_VERSION_INT <= 002002004} // <= 2.2.4
+  {$DEFINE HAVE_REFCNTBUG}
+{$IFEND}
+{$ENDIF}
+
+{$IFDEF HAVE_REFCNTBUG}
+const
+  // when GarbageList.Count reaches GarbageMaxCount the oldest references in
+  // GarbageList will be deleted until GarbageList.Count equals GarbageAfterCleanCount.
+  GarbageMaxCount = 20000;
+  GarbageAfterCleanCount = GarbageMaxCount-1000;
+
+var
+  GarbageList: IInterfaceList;
+{$ENDIF}
+
+type
+  TPathImpl = class(TInterfacedObject, IPath)
+    private
+      fName: UTF8String; //<** internal filename string, always UTF8 with PathDelim
+
+      {**
+       * Unifies the filename. Path-delimiters are replaced by '/'.
+       *}
+      procedure Unify(DelimOption: TPathDelimOption);
+
+      {**
+       * Returns a copy of fName with path delimiters changed to '/'.
+       *}
+      function GetPortableString(): UTF8String;
+
+      procedure AssertRefCount; {$IFDEF HasInline}inline;{$ENDIF}
+
+    public
+      constructor Create(const Name: UTF8String; DelimOption: TPathDelimOption);
+      destructor Destroy(); override;
+
+      function ToUTF8(UseNativeDelim: boolean): UTF8String;
+      function ToWide(UseNativeDelim: boolean): WideString;
+      function ToNative(): RawByteString;
+
+      function Open(Mode: longword): TFileHandle;
+
+      function GetDrive(): IPath;
+      function GetPath(): IPath;
+      function GetDir(): IPath;
+      function GetName(): IPath;
+      function GetExtension(): IPath;
+
+      function SetExtension(const Extension: IPath): IPath; overload;
+      function SetExtension(const Extension: RawByteString): IPath; overload;
+      function SetExtension(const Extension: WideString): IPath; overload;
+
+      function GetRelativePath(const BaseName: IPath): IPath;
+      function GetAbsolutePath(): IPath;
+      function GetParent(): IPath;
+      function SplitDirs(): IPathDynArray;
+
+      function Append(const Child: IPath; DelimOption: TPathDelimOption): IPath; overload;
+      function Append(const Child: RawByteString; DelimOption: TPathDelimOption): IPath; overload;
+      function Append(const Child: WideString; DelimOption: TPathDelimOption): IPath; overload;
+
+      function Equals(const Other: IPath; IgnoreCase: boolean): boolean; overload;
+      function Equals(const Other: RawByteString; IgnoreCase: boolean): boolean; overload;
+      function Equals(const Other: WideString; IgnoreCase: boolean): boolean; overload;
+
+      function IsChildOf(const Parent: IPath; Direct: boolean): boolean;
+
+      function AdjustCase(AdjustAllLevels: boolean): IPath;
+
+      function AppendPathDelim(): IPath;
+      function RemovePathDelim(): IPath;
+
+      function GetFileAge(): integer; overload;
+      function GetFileAge(out FileDateTime: TDateTime): boolean; overload;
+      function Exists(): boolean;
+      function IsFile(): boolean;
+      function IsDirectory(): boolean;
+      function IsAbsolute(): boolean;
+      function GetAttr(): cardinal;
+      function SetAttr(Attr: Integer): boolean;
+      function IsReadOnly(): boolean;
+      function SetReadOnly(ReadOnly: boolean): boolean;
+
+      function IsUnset(): boolean;
+      function IsSet(): boolean;
+
+      function FileSearch(const DirList: IPath): IPath;
+
+      function CreateFile(): TFileHandle;
+      function DeleteFile(): boolean;
+      function CreateDirectory(Force: boolean): boolean;
+      function DeleteEmptyDir(): boolean;
+      function Rename(const NewName: IPath): boolean;
+      function CopyFile(const Target: IPath; FailIfExists: boolean): boolean;
+
+      function GetIntern(): UTF8String;
+  end;
+
+function Path(const PathName: RawByteString; DelimOption: TPathDelimOption): IPath;
+begin
+  if (IsUTF8String(PathName)) then
+    Result := TPathImpl.Create(PathName, DelimOption)
+  else if (IsNativeUTF8()) then
+    Result := PATH_NONE
+  else
+    Result := TPathImpl.Create(AnsiToUtf8(PathName), DelimOption);
+end;
+
+function Path(PathName: PChar; DelimOption: TPathDelimOption): IPath;
+begin
+  Result := Path(RawByteString(PathName));
+end;
+
+function Path(const PathName: WideString; DelimOption: TPathDelimOption): IPath;
+begin
+  Result := TPathImpl.Create(UTF8Encode(PathName), DelimOption);
+end;
+
+
+
+procedure TPathImpl.AssertRefCount;
+begin
+  {$IFDEF HAVE_REFCNTBUG}
+  if (FRefCount <= 0) then
+    raise Exception.Create('RefCount error: ' + IntToStr(FRefCount));
+  {$ENDIF}
+end;
+
+constructor TPathImpl.Create(const Name: UTF8String; DelimOption: TPathDelimOption);
+begin
+  inherited Create();
+  fName := Name;
+  Unify(DelimOption);
+  {$IFDEF HAVE_REFCNTBUG}
+  GarbageList.Lock;
+  if (GarbageList.Count >= GarbageMaxCount) then
+  begin
+    while (GarbageList.Count > GarbageAfterCleanCount) do
+      GarbageList.Delete(0);
+  end;
+  GarbageList.Add(Self);
+  GarbageList.Unlock;
+  {$ENDIF}
+end;
+
+destructor TPathImpl.Destroy();
+begin
+  inherited;
+end;
+
+procedure TPathImpl.Unify(DelimOption: TPathDelimOption);
+var
+  I: integer;
+begin
+  // convert all path delimiters to native ones
+  for I := 1 to Length(fName) do
+  begin
+    if (fName[I] in ['\', '/']) and (fName[I] <> PathDelim) then
+      fName[I] := PathDelim;
+  end;
+
+  // Include/ExcludeTrailingPathDelimiter need PathDelim as path delimiter 
+  case DelimOption of
+    pdAppend: fName := IncludeTrailingPathDelimiter(fName);
+    pdRemove: fName := ExcludeTrailingPathDelimiter(fName);
+  end;
+end;
+
+function TPathImpl.GetPortableString(): UTF8String;
+var
+  I: integer;
+begin
+  Result := fName;
+  if (PathDelim = '/') then
+    Exit;
+
+  for I := 1 to Length(Result) do
+  begin
+    if (Result[I] = PathDelim) then
+      Result[I] := '/';
+  end;
+end;
+
+function TPathImpl.ToUTF8(UseNativeDelim: boolean): UTF8String;
+begin
+  AssertRefCount;
+
+  if (UseNativeDelim) then
+    Result := fName
+  else
+    Result := GetPortableString();
+end;
+
+function TPathImpl.ToWide(UseNativeDelim: boolean): WideString;
+begin
+  if (UseNativeDelim) then
+    Result := UTF8Decode(fName)
+  else
+    Result := UTF8Decode(GetPortableString());
+end;
+
+function TPathImpl.ToNative(): RawByteString;
+begin
+  if (IsNativeUTF8()) then
+    Result := fName
+  else
+    Result := Utf8ToAnsi(fName);
+end;
+
+function TPathImpl.GetDrive(): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.ExtractFileDrive(Self);
+end;
+
+function TPathImpl.GetPath(): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.ExtractFilePath(Self);
+end;
+
+function TPathImpl.GetDir(): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.ExtractFileDir(Self);
+end;
+
+function TPathImpl.GetName(): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.ExtractFileName(Self);
+end;
+
+function TPathImpl.GetExtension(): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.ExtractFileExt(Self);
+end;
+
+function TPathImpl.SetExtension(const Extension: IPath): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.ChangeFileExt(Self, Extension);
+end;
+
+function TPathImpl.SetExtension(const Extension: RawByteString): IPath;
+begin
+  Result := SetExtension(Path(Extension));
+end;
+
+function TPathImpl.SetExtension(const Extension: WideString): IPath;
+begin
+  Result := SetExtension(Path(Extension));
+end;
+
+function TPathImpl.GetRelativePath(const BaseName: IPath): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.ExtractRelativePath(BaseName, Self);
+end;
+
+function TPathImpl.GetAbsolutePath(): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.ExpandFileName(Self);
+end;
+
+function TPathImpl.GetParent(): IPath;
+var
+  CurPath, ParentPath: IPath;
+begin
+  AssertRefCount;
+
+  Result := PATH_NONE;
+
+  CurPath := Self.RemovePathDelim();
+  // check if current path has a parent (no further '/')
+  if (Pos(PathDelim, CurPath.ToUTF8()) = 0) then
+    Exit;
+
+  // set new path and check if it has changed to avoid endless loops
+  // e.g. with invalid paths like '/C:' (GetPath() uses ':' as delimiter too)
+  // on delphi/win32
+  ParentPath := CurPath.GetPath();
+  if (ParentPath.ToUTF8 = CurPath.ToUTF8)  then
+    Exit;
+
+  Result := ParentPath;
+end;
+
+function TPathImpl.SplitDirs(): IPathDynArray;
+var
+  CurPath: IPath;
+  Components: array of IPath;
+  CurPathStr: UTF8String;
+  DelimPos: integer;
+  I: integer;
+begin
+  SetLength(Result, 0);
+
+  if (Length(Self.ToUTF8(true)) = 0) then
+    Exit;
+
+  CurPath := Self;
+  SetLength(Components, 0);
+  repeat
+    SetLength(Components, Length(Components)+1);
+
+    CurPathStr := CurPath.ToUTF8();
+    DelimPos := LastDelimiter(PathDelim, SysUtils.ExcludeTrailingPathDelimiter(CurPathStr));
+    Components[High(Components)] := Path(Copy(CurPathStr, DelimPos+1, Length(CurPathStr)));
+
+    CurPath := CurPath.GetParent();
+  until (CurPath = PATH_NONE);
+
+  // reverse list
+  SetLength(Result, Length(Components));
+  for I := 0 to High(Components) do
+    Result[I] := Components[High(Components)-I];
+end;
+
+function TPathImpl.Append(const Child: IPath; DelimOption: TPathDelimOption): IPath;
+var
+  TmpResult: IPath;
+begin
+  AssertRefCount;
+
+  if (fName = '') then
+    TmpResult := Child
+  else
+    TmpResult := Path(Self.AppendPathDelim().ToUTF8() + Child.ToUTF8());
+
+  case DelimOption of
+    pdKeep: Result := TmpResult;
+    pdAppend: Result := TmpResult.AppendPathDelim;
+    pdRemove: Result := TmpResult.RemovePathDelim;
+  end;
+end;
+
+function TPathImpl.Append(const Child: RawByteString; DelimOption: TPathDelimOption): IPath;
+begin
+  AssertRefCount;
+  Result := Append(Path(Child), DelimOption);
+end;
+
+function TPathImpl.Append(const Child: WideString; DelimOption: TPathDelimOption): IPath;
+begin
+  AssertRefCount;
+  Result := Append(Path(Child), DelimOption);
+end;
+
+function TPathImpl.Equals(const Other: IPath; IgnoreCase: boolean): boolean;
+var
+  SelfPath, OtherPath: UTF8String;
+begin
+  SelfPath := Self.GetAbsolutePath().RemovePathDelim().ToUTF8();
+  OtherPath := Other.GetAbsolutePath().RemovePathDelim().ToUTF8();
+  if (FileSystem.IsCaseSensitive() and not IgnoreCase) then
+    Result := (CompareStr(SelfPath, OtherPath) = 0)
+  else
+    Result := (CompareText(SelfPath, OtherPath) = 0);
+end;
+
+function TPathImpl.Equals(const Other: RawByteString; IgnoreCase: boolean): boolean;
+begin
+  Result := Equals(Path(Other), IgnoreCase);
+end;
+
+function TPathImpl.Equals(const Other: WideString; IgnoreCase: boolean): boolean;
+begin
+  Result := Equals(Path(Other), IgnoreCase);
+end;
+
+function TPathImpl.IsChildOf(const Parent: IPath; Direct: boolean): boolean;
+var
+  SelfPath, ParentPath: UTF8String;
+begin
+  Result := false;
+
+  if (Direct) then
+  begin
+    SelfPath := Self.GetParent().GetAbsolutePath().AppendPathDelim().ToUTF8();
+    ParentPath := Parent.GetAbsolutePath().AppendPathDelim().ToUTF8();
+
+    // simply check if this paths parent path (SelfPath) equals ParentPath
+    Result := (SelfPath = ParentPath);
+  end
+  else
+  begin
+    SelfPath := Self.GetAbsolutePath().AppendPathDelim().ToUTF8();
+    ParentPath := Parent.GetAbsolutePath().AppendPathDelim().ToUTF8();
+
+    if (Length(SelfPath) <= Length(ParentPath)) then
+      Exit;
+
+    // check if ParentPath is a substring of SelfPath
+    if (FileSystem.IsCaseSensitive()) then
+      Result := (StrLComp(PAnsiChar(SelfPath), PAnsiChar(ParentPath), Length(ParentPath)) = 0)
+    else
+      Result := (StrLIComp(PAnsiChar(SelfPath), PAnsiChar(ParentPath), Length(ParentPath)) = 0)
+  end;
+end;
+
+function AdjustCaseRecursive(CurPath: IPath; AdjustAllLevels: boolean): IPath;
+var
+  OldParent, AdjustedParent: IPath;
+  LocalName: IPath;
+  PathFound: IPath;
+  PathWithAdjParent: IPath;
+  SearchInfo: TFileInfo;
+  FileIter: IFileIterator;
+  Pattern: IPath;
+begin
+  // if case-sensitive path exists there is no need to adjust case
+  if (CurPath.Exists()) then
+  begin
+    Result := CurPath;
+    Exit;
+  end;
+
+  LocalName := CurPath.RemovePathDelim().GetName();
+
+  // try to adjust parent
+  OldParent := CurPath.GetParent();
+  if (OldParent <> PATH_NONE) then
+  begin
+    if (not AdjustAllLevels) then
+    begin
+      AdjustedParent := OldParent;
+    end
+    else
+    begin
+      AdjustedParent := AdjustCaseRecursive(OldParent, AdjustAllLevels);
+      if (AdjustedParent = nil) then
+      begin
+        // parent path was not found case-insensitive
+        Result := nil;
+        Exit;
+      end;
+
+      // check if the path with adjusted parent can be found now
+      PathWithAdjParent := AdjustedParent.Append(LocalName);
+      if (PathWithAdjParent.Exists()) then
+      begin
+        Result := PathWithAdjParent;
+        Exit;
+      end;
+    end;
+    Pattern := AdjustedParent.Append(Path('*'));
+  end
+  else // path has no parent
+  begin
+    // the top path can either be absolute or relative
+    if (CurPath.IsAbsolute) then
+    begin
+      // the only absolute directory at Unix without a parent is root ('/')
+      // and hence does not need to be adjusted
+      Result := CurPath;
+      Exit;
+    end;
+    // this is a relative path, search in the current working dir
+    AdjustedParent := nil;
+    Pattern := Path('*');
+  end;
+
+  // compare name with all files in the current directory case-insensitive
+  FileIter := FileSystem.FileFind(Pattern, faAnyFile);
+  while (FileIter.HasNext()) do
+  begin
+    SearchInfo := FileIter.Next();
+    PathFound := SearchInfo.Name;
+    if (CompareText(LocalName.ToUTF8, PathFound.ToUTF8) = 0) then
+    begin
+      if (AdjustedParent <> nil) then
+        Result := AdjustedParent.Append(PathFound)
+      else
+        Result := PathFound;
+      Exit;
+    end;
+  end;
+
+  // no matching file found
+  Result := nil;
+end;
+
+function TPathImpl.AdjustCase(AdjustAllLevels: boolean): IPath;
+begin
+  AssertRefCount;
+
+  Result := Self;
+
+  if (FileSystem.IsCaseSensitive) then
+  begin
+    Result := AdjustCaseRecursive(Self, AdjustAllLevels);
+    if (Result = nil) then
+      Result := Self;
+  end;
+end;
+
+function TPathImpl.AppendPathDelim(): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.IncludeTrailingPathDelimiter(Self);
+end;
+
+function TPathImpl.RemovePathDelim(): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.ExcludeTrailingPathDelimiter(Self);
+end;
+
+function TPathImpl.CreateFile(): TFileHandle;
+begin
+  Result := FileSystem.FileCreate(Self);
+end;
+
+function TPathImpl.CreateDirectory(Force: boolean): boolean;
+begin
+  if (Force) then
+    Result := FileSystem.ForceDirectories(Self)
+  else
+    Result := FileSystem.DirectoryCreate(Self);
+end;
+
+function TPathImpl.Open(Mode: longword): TFileHandle;
+begin
+  Result := FileSystem.FileOpen(Self, Mode);
+end;
+
+function TPathImpl.GetFileAge(): integer;
+begin
+  Result := FileSystem.FileAge(Self);
+end;
+
+function TPathImpl.GetFileAge(out FileDateTime: TDateTime): boolean;
+begin
+  Result := FileSystem.FileAge(Self, FileDateTime);
+end;
+
+function TPathImpl.Exists(): boolean;
+begin
+  // note the different specifications of FileExists() on Win32 <> Unix
+  {$IFDEF MSWINDOWS}
+  Result := IsFile() or IsDirectory();
+  {$ELSE}
+  Result := FileSystem.FileExists(Self);
+  {$ENDIF}
+end;
+
+function TPathImpl.IsFile(): boolean;
+begin
+  // note the different specifications of FileExists() on Win32 <> Unix
+  {$IFDEF MSWINDOWS}
+  Result := FileSystem.FileExists(Self);
+  {$ELSE}
+  Result := Exists() and not IsDirectory();
+  {$ENDIF}
+end;
+
+function TPathImpl.IsDirectory(): boolean;
+begin
+  Result := FileSystem.DirectoryExists(Self);
+end;
+
+function TPathImpl.IsAbsolute(): boolean;
+begin
+  AssertRefCount;
+  Result := FileSystem.FileIsReadOnly(Self);
+end;
+
+function TPathImpl.GetAttr(): cardinal;
+begin
+  Result := FileSystem.FileGetAttr(Self);
+end;
+
+function TPathImpl.SetAttr(Attr: Integer): boolean;
+begin
+  Result := FileSystem.FileSetAttr(Self, Attr);
+end;
+
+function TPathImpl.IsReadOnly(): boolean;
+begin
+  Result := FileSystem.FileIsReadOnly(Self);
+end;
+
+function TPathImpl.SetReadOnly(ReadOnly: boolean): boolean;
+begin
+  Result := FileSystem.FileSetReadOnly(Self, ReadOnly);
+end;
+
+function TPathImpl.IsUnset(): boolean;
+begin
+  Result := (fName = '');
+end;
+
+function TPathImpl.IsSet(): boolean;
+begin
+  Result := (fName <> '');
+end;
+
+function TPathImpl.FileSearch(const DirList: IPath): IPath;
+begin
+  AssertRefCount;
+  Result := FileSystem.FileSearch(Self, DirList);
+end;
+
+function TPathImpl.Rename(const NewName: IPath): boolean;
+begin
+  Result := FileSystem.RenameFile(Self, NewName);
+end;
+
+function TPathImpl.DeleteFile(): boolean;
+begin
+  Result := FileSystem.DeleteFile(Self);
+end;
+
+function TPathImpl.DeleteEmptyDir(): boolean;
+begin
+  Result := FileSystem.RemoveDir(Self);
+end;
+
+function TPathImpl.CopyFile(const Target: IPath; FailIfExists: boolean): boolean;
+begin
+  Result := FileSystem.CopyFile(Self, Target, FailIfExists);
+end;
+
+function TPathImpl.GetIntern(): UTF8String;
+begin
+  Result := fName;
+end;
+
+
+{ TBinaryFileStream }
+
+constructor TBinaryFileStream.Create(const FileName: IPath; Mode: word);
+begin
+{$IFDEF MSWINDOWS}
+  inherited Create(FileName.ToWide(), Mode);
+{$ELSE}
+  inherited Create(FileName.ToNative(), Mode);
+{$ENDIF}
+end;
+
+{ TTextStream }
+
+constructor TTextFileStream.Create(Filename: IPath; Mode: word);
+begin
+  inherited Create();
+  fMode := Mode;
+  fFilename := Filename;
+  fLineBreak := sLineBreak;
+end;
+
+function TTextFileStream.ReadLine(var Line: UTF8String): boolean;
+begin
+  Line := ReadLine(Result);
+end;
+
+function TTextFileStream.ReadLine(var Line: AnsiString): boolean;
+begin
+  Line := ReadLine(Result);
+end;
+
+procedure TTextFileStream.WriteString(const Str: RawByteString);
+begin
+  WriteBuffer(Str[1], Length(Str));
+end;
+
+procedure TTextFileStream.WriteLine(const Line: RawByteString);
+begin
+  WriteBuffer(Line[1], Length(Line));
+  WriteBuffer(fLineBreak[1], Length(fLineBreak));
+end;
+
+{ TMemTextStream }
+
+constructor TMemTextFileStream.Create(Filename: IPath; Mode: word);
+var
+  FileStream: TBinaryFileStream;
+begin
+  inherited Create(Filename, Mode);
+
+  fStream := TMemoryStream.Create();
+
+  // load data to memory in read mode
+  if ((Mode and 3) in [fmOpenRead, fmOpenReadWrite]) then
+  begin
+    FileStream := TBinaryFileStream.Create(Filename, fmOpenRead);
+    try
+      fStream.LoadFromStream(FileStream);
+    finally
+      FileStream.Free;
+    end;
+  end
+  // check if file exists for write-mode
+  else if ((Mode and 3) = fmOpenWrite) and (not Filename.IsFile) then
+  begin
+    raise EFOpenError.CreateResFmt(@SFOpenError,
+          [FileName.GetAbsolutePath.ToNative]);
+  end;
+end;
+
+destructor TMemTextFileStream.Destroy();
+var
+  FileStream: TBinaryFileStream;
+  SaveMode: word;
+begin
+  // save changes in write mode (= not read-only mode)
+  if ((fMode and 3) <> fmOpenRead) then
+  begin
+    if (fMode = fmCreate) then
+      SaveMode := fmCreate
+    else
+      SaveMode := fmOpenWrite;
+    FileStream := TBinaryFileStream.Create(fFilename, SaveMode);
+    try
+      fStream.SaveToStream(FileStream);
+    finally
+      FileStream.Free;
+    end;
+  end;
+
+  fStream.Free;
+  inherited;
+end;
+
+function TMemTextFileStream.GetSize: int64;
+begin
+  Result := fStream.Size;
+end;
+
+function TMemTextFileStream.Read(var Buffer; Count: longint): longint;
+begin
+  Result := fStream.Read(Buffer, Count);
+end;
+
+function TMemTextFileStream.Write(const Buffer; Count: longint): longint;
+begin
+  Result := fStream.Write(Buffer, Count);
+end;
+
+function TMemTextFileStream.Seek(Offset: longint; Origin: word): longint;
+begin
+  Result := fStream.Seek(Offset, Origin);
+end;
+
+function TMemTextFileStream.Seek(const Offset: int64; Origin: TSeekOrigin): int64;
+begin
+  Result := fStream.Seek(Offset, Origin);
+end;
+
+function TMemTextFileStream.CopyMemString(StartPos: int64; EndPos: int64): RawByteString;
+var
+  LineLength: cardinal;
+  Temp: RawByteString;
+begin
+  LineLength := EndPos - StartPos;
+  if (LineLength > 0) then
+  begin
+    // set string length to line-length (+ zero-terminator)
+    SetLength(Temp, LineLength);
+    StrLCopy(PAnsiChar(Temp),
+             @PAnsiChar(fStream.Memory)[StartPos],
+             LineLength);
+    Result := Temp;
+  end
+  else
+  begin
+    Result := '';
+  end;
+end;
+
+function TMemTextFileStream.ReadString(): RawByteString;
+var
+  TextPtr: PAnsiChar;
+  CurPos, StartPos, FileSize: int64;
+begin
+  TextPtr := PAnsiChar(fStream.Memory);
+  CurPos := Position;
+  FileSize := Size;
+  StartPos := -1;
+
+  while (CurPos < FileSize) do
+  begin
+    // check for whitespace (tab, lf, cr, space)
+    if (TextPtr[CurPos] in [#9, #10, #13, ' ']) then
+    begin
+      // check if we are at the end of a string
+      if (StartPos > -1) then
+        Break;
+    end
+    else if (StartPos = -1) then // start of string found
+    begin
+      StartPos := CurPos;
+    end;
+    Inc(CurPos);
+  end;
+
+  if (StartPos = -1) then
+    Result := ''
+  else
+  begin
+    Result := CopyMemString(StartPos, CurPos);
+    fStream.Position := CurPos;
+  end;
+end;
+
+{*
+ * Implementation of ReadLine(). We need separate versions for UTF8String
+ * and AnsiString as "var" parameter types have to fit exactly.
+ * To avoid a var-parameter here, the internal version the Line parameter is
+ * used as return value.
+ *}
+function TMemTextFileStream.ReadLine(var Success: boolean): RawByteString;
+var
+  TextPtr: PAnsiChar;
+  CurPos, FileSize: int64;
+begin
+  TextPtr := PAnsiChar(fStream.Memory);
+  CurPos := fStream.Position;
+  FileSize := Size;
+
+  // check for EOF
+  if (CurPos >= FileSize) then
+  begin
+    Result := '';
+    Success := false;
+    Exit;
+  end;
+
+  Success := true;
+
+  while (CurPos < FileSize) do
+  begin
+    if (TextPtr[CurPos] in [#10, #13]) then
+    begin
+      // copy text line
+      Result := CopyMemString(fStream.Position, CurPos);
+
+      // handle windows style #13#10 (\r\n) newlines
+      if (TextPtr[CurPos] = #13) and
+         (CurPos+1 < FileSize) and
+         (TextPtr[CurPos+1] = #10) then
+      begin
+        Inc(CurPos);
+      end;
+
+      // update stream pos
+      fStream.Position := CurPos+1;
+
+      Exit;
+    end;
+    Inc(CurPos);
+  end;
+
+  Result := CopyMemString(fStream.Position, CurPos);
+  fStream.Position := FileSize;
+end;
+
+{ TUnicodeMemoryStream }
+
+procedure TUnicodeMemoryStream.LoadFromFile(const FileName: IPath);
+var
+  Stream: TStream;
+begin
+  Stream := TBinaryFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
+  try
+    LoadFromStream(Stream);
+  finally
+    Stream.Free;
+  end;
+end;
+
+procedure TUnicodeMemoryStream.SaveToFile(const FileName: IPath);
+var
+  Stream: TStream;
+begin
+  Stream := TBinaryFileStream.Create(FileName, fmCreate);
+  try
+    SaveToStream(Stream);
+  finally
+    Stream.Free;
+  end;
+end;
+
+{ TUnicodeMemIniFile }
+
+constructor TUnicodeMemIniFile.Create(const FileName: IPath; UTF8Encoded: boolean);
+var
+  List: TStringList;
+  Stream: TBinaryFileStream;
+  BOMBuf: array[0..2] of AnsiChar;
+begin
+  inherited Create('');
+  FFilename := FileName;
+  FUTF8Encoded := UTF8Encoded;
+
+  if FileName.Exists() then
+  begin
+    List := nil;
+    Stream := nil;
+    try
+      List := TStringList.Create;
+      Stream := TBinaryFileStream.Create(FileName, fmOpenRead);
+      if (Stream.Read(BOMBuf[0], SizeOf(BOMBuf)) = 3) and
+         (CompareMem(PChar(UTF8_BOM), @BomBuf, Length(UTF8_BOM))) then
+      begin
+        // truncate BOM
+        FUTF8Encoded := true;
+      end
+      else
+      begin
+        // rewind file
+        Stream.Seek(0, soBeginning);
+      end;
+      List.LoadFromStream(Stream);
+      SetStrings(List);
+    finally
+      Stream.Free;
+      List.Free;
+    end;
+  end;
+end;
+
+procedure TUnicodeMemIniFile.UpdateFile;
+var
+  List: TStringList;
+  Stream: TBinaryFileStream;
+begin
+  List := nil;
+  Stream := nil;
+  try
+    List := TStringList.Create;
+    GetStrings(List);
+    Stream := TBinaryFileStream.Create(FFileName, fmCreate);
+    if UTF8Encoded then
+      Stream.Write(UTF8_BOM, Length(UTF8_BOM));
+    List.SaveToStream(Stream);
+  finally
+    List.Free;
+    Stream.Free;
+  end;
+end;
+
+
+var
+  PATH_NONE_Singelton: IPath;
+
+function PATH_NONE(): IPath;
+begin
+  Result := PATH_NONE_Singelton;
+end;
+
+initialization
+  {$IFDEF HAVE_REFCNTBUG}
+  GarbageList := TInterfaceList.Create();
+  GarbageList.Capacity := GarbageMaxCount;
+  {$ENDIF}
+  PATH_NONE_Singelton := Path('');
+
+finalization
+  PATH_NONE_Singelton := nil;
+
+end.
diff --git a/cmake/src/base/UPathUtils.pas b/cmake/src/base/UPathUtils.pas
new file mode 100644
index 00000000..c2bcdd4b
--- /dev/null
+++ b/cmake/src/base/UPathUtils.pas
@@ -0,0 +1,196 @@
+{* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *}
+
+unit UPathUtils;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  SysUtils,
+  Classes,
+  UPath;
+
+var
+  // Absolute Paths
+  GamePath:         IPath;
+  SoundPath:        IPath;
+  SongPaths:        IInterfaceList;
+  LogPath:          IPath;
+  ThemePath:        IPath;
+  SkinsPath:        IPath;
+  ScreenshotsPath:  IPath;
+  CoverPaths:       IInterfaceList;
+  LanguagesPath:    IPath;
+  PluginPath:       IPath;
+  VisualsPath:      IPath;
+  FontPath:         IPath;
+  ResourcesPath:    IPath;
+  PlaylistPath:     IPath;
+
+function FindPath(out PathResult: IPath; const RequestedPath: IPath; NeedsWritePermission: boolean): boolean;
+procedure InitializePaths;
+procedure AddSongPath(const Path: IPath);
+
+implementation
+
+uses
+  StrUtils,
+  UPlatform,
+  UCommandLine,
+  ULog;
+
+procedure AddSpecialPath(var PathList: IInterfaceList; const Path: IPath);
+var
+  Index: integer;
+  PathAbs, PathTmp: IPath;
+  OldPath, OldPathAbs, OldPathTmp: IPath;
+begin
+  if (PathList = nil) then
+    PathList := TInterfaceList.Create;
+
+  if Path.Equals(PATH_NONE) or not Path.CreateDirectory(true) then
+    Exit;
+
+  PathTmp := Path.GetAbsolutePath();
+  PathAbs := PathTmp.AppendPathDelim();
+
+  // check if path or a part of the path was already added
+  for Index := 0 to PathList.Count-1 do
+  begin
+    OldPath := PathList[Index] as IPath;
+    OldPathTmp := OldPath.GetAbsolutePath();
+    OldPathAbs := OldPathTmp.AppendPathDelim();
+
+    // check if the new directory is a sub-directory of a previously added one.
+    // This is also true, if both paths point to the same directories.
+    if (OldPathAbs.IsChildOf(PathAbs, false) or OldPathAbs.Equals(PathAbs)) then
+    begin
+      // ignore the new path
+      Exit;
+    end;
+
+    // check if a previously added directory is a sub-directory of the new one.
+    if (PathAbs.IsChildOf(OldPathAbs, false)) then
+    begin
+      // replace the old with the new one.
+      PathList[Index] := PathAbs;
+      Exit;
+    end;
+  end;
+
+  PathList.Add(PathAbs);
+end;
+
+procedure AddSongPath(const Path: IPath);
+begin
+  AddSpecialPath(SongPaths, Path);
+end;
+
+procedure AddCoverPath(const Path: IPath);
+begin
+  AddSpecialPath(CoverPaths, Path);
+end;
+
+(**
+ * Initialize a path variable
+ * After setting paths, make sure that paths exist
+ *)
+function FindPath(
+  out PathResult: IPath;
+  const RequestedPath: IPath;
+  NeedsWritePermission: boolean): boolean;
+begin
+  Result := false;
+
+  if (RequestedPath.Equals(PATH_NONE)) then
+    Exit;
+
+  // Make sure the directory exists
+  if (not RequestedPath.CreateDirectory(true)) then
+  begin
+    PathResult := PATH_NONE;
+    Exit;
+  end;
+
+  PathResult := RequestedPath.AppendPathDelim();
+
+  if (NeedsWritePermission) and RequestedPath.IsReadOnly() then
+    Exit;
+
+  Result := true;
+end;
+
+(**
+ * Function sets all absolute paths e.g. song path and makes sure the directorys exist
+ *)
+procedure InitializePaths;
+var
+  SharedPath, UserPath: IPath;
+begin
+  // Log directory (must be writable)
+  if (not FindPath(LogPath, Platform.GetLogPath, true)) then
+  begin
+    Log.FileOutputEnabled := false;
+    Log.LogWarn('Log directory "'+ Platform.GetLogPath.ToNative +'" not available', 'InitializePaths');
+  end;
+
+  SharedPath := Platform.GetGameSharedPath;
+  UserPath := Platform.GetGameUserPath;
+
+  FindPath(SoundPath,     SharedPath.Append('sounds'),    false);
+  FindPath(ThemePath,     SharedPath.Append('themes'),    false);
+  FindPath(SkinsPath,     SharedPath.Append('themes'),    false);
+  FindPath(LanguagesPath, SharedPath.Append('languages'), false);
+  FindPath(PluginPath,    SharedPath.Append('plugins'),   false);
+  FindPath(VisualsPath,   SharedPath.Append('visuals'),   false);
+  FindPath(FontPath,      SharedPath.Append('fonts'),     false);
+  FindPath(ResourcesPath, SharedPath.Append('resources'), false);
+
+  // Playlists are not shared as we need one directory to write too
+  FindPath(PlaylistPath, UserPath.Append('playlists'), true);
+
+  // Screenshot directory (must be writable)
+  if (not FindPath(ScreenshotsPath, UserPath.Append('screenshots'), true)) then
+  begin
+    Log.LogWarn('Screenshot directory "'+ UserPath.ToNative +'" not available', 'InitializePaths');
+  end;
+
+  // Add song paths
+  AddSongPath(Params.SongPath);
+  AddSongPath(SharedPath.Append('songs'));
+  AddSongPath(UserPath.Append('songs'));
+
+  // Add category cover paths
+  AddCoverPath(SharedPath.Append('covers'));
+  AddCoverPath(UserPath.Append('covers'));
+end;
+
+end.
diff --git a/cmake/src/base/UPlatform.pas b/cmake/src/base/UPlatform.pas
index 6f13481c..11c67fa7 100644
--- a/cmake/src/base/UPlatform.pas
+++ b/cmake/src/base/UPlatform.pas
@@ -39,28 +39,20 @@ interface
 {$I switches.inc}
 
 uses
-  Classes;
+  Classes,
+  UPath;
 
 type
-  TDirectoryEntry = record
-    Name:        WideString;
-    IsDirectory: boolean;
-    IsFile:      boolean;
-  end;
-
-  TDirectoryEntryArray = array of TDirectoryEntry;
-
   TPlatform = class
-    function GetExecutionDir(): string;
+    function GetExecutionDir(): IPath;
     procedure Init; virtual;
-    function DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: boolean): TDirectoryEntryArray; virtual; abstract;
+
     function TerminateIfAlreadyRunning(var WndTitle: string): boolean; virtual;
-    function FindSongFile(Dir, Mask: WideString): WideString; virtual;
     procedure Halt; virtual;
-    function GetLogPath:        WideString; virtual; abstract;
-    function GetGameSharedPath: WideString; virtual; abstract;
-    function GetGameUserPath:   WideString; virtual; abstract;
-    function CopyFile(const Source, Target: WideString; FailIfExists: boolean): boolean; virtual;
+
+    function GetLogPath:        IPath; virtual; abstract;
+    function GetGameSharedPath: IPath; virtual; abstract;
+    function GetGameUserPath:   IPath; virtual; abstract;
   end;
 
   function Platform(): TPlatform;
@@ -76,7 +68,9 @@ uses
   {$ELSEIF Defined(UNIX)}
   UPlatformLinux,
   {$IFEND}
-  ULog;
+  ULog,
+  UUnicodeUtils,
+  UFilesystem;
 
 
 // I modified it to use the Platform_singleton in this location (in the implementation)
@@ -109,9 +103,13 @@ end;
 {**
  * Returns the directory of the executable
  *}
-function TPlatform.GetExecutionDir(): string;
+function TPlatform.GetExecutionDir(): IPath;
+var
+  ExecName, ExecDir: IPath;
 begin
-  Result := ExpandFileName(ExtractFilePath(ParamStr(0)));
+  ExecName := Path(ParamStr(0));
+  ExecDir := ExecName.GetPath;
+  Result := ExecDir.GetAbsolutePath();
 end;
 
 (**
@@ -122,65 +120,6 @@ begin
   Result := false;
 end;
 
-(**
- * Default FindSongFile() implementation
- *)
-function TPlatform.FindSongFile(Dir, Mask: WideString): WideString;
-var
-  SR: TSearchRec;   // for parsing song directory
-begin
-  Result := '';
-  if SysUtils.FindFirst(Dir + Mask, faDirectory, SR) = 0 then
-  begin
-    Result := SR.Name;
-  end;
-  SysUtils.FindClose(SR);
-end;
-
-function TPlatform.CopyFile(const Source, Target: WideString; FailIfExists: boolean): boolean;
-const
-  COPY_BUFFER_SIZE = 4096; // a good tradeoff between speed and memory consumption
-var
-  SourceFile, TargetFile: TFileStream;
-  FileCopyBuffer: array [0..COPY_BUFFER_SIZE-1] of byte; // temporary copy-buffer.
-  NumberOfBytes:  integer; // number of bytes read from SourceFile
-begin
-  Result := false;
-  SourceFile := nil;
-  TargetFile := nil;
-
-  // if overwrite is disabled return if the target file already exists
-  if (FailIfExists and FileExists(Target)) then
-    Exit;
-
-  try
-    try
-      // open source and target file (might throw an exception on error)
-      SourceFile := TFileStream.Create(Source, fmOpenRead);
-      TargetFile := TFileStream.Create(Target, fmCreate or fmOpenWrite);
-
-      while true do
-      begin
-        // read a block from the source file and check for errors or EOF
-        NumberOfBytes := SourceFile.Read(FileCopyBuffer, SizeOf(FileCopyBuffer));
-        if (NumberOfBytes <= 0) then
-          Break;
-        // write block to target file and check if everything was written
-        if (TargetFile.Write(FileCopyBuffer, NumberOfBytes) <> NumberOfBytes) then
-          Exit;
-      end;
-    except
-      Exit;
-    end;
-  finally
-    SourceFile.Free;
-    TargetFile.Free;
-  end;
-
-  Result := true;
-end;
-
-
 initialization
 {$IF Defined(MSWINDOWS)}
   Platform_singleton := TPlatformWindows.Create;
diff --git a/cmake/src/base/UPlatformLinux.pas b/cmake/src/base/UPlatformLinux.pas
index 30499a97..693facaa 100644
--- a/cmake/src/base/UPlatformLinux.pas
+++ b/cmake/src/base/UPlatformLinux.pas
@@ -36,7 +36,8 @@ interface
 uses
   Classes,
   UPlatform,
-  UConfig;
+  UConfig,
+  UPath;
 
 type
   TPlatformLinux = class(TPlatform)
@@ -44,15 +45,13 @@ type
       UseLocalDirs: boolean;
 
       procedure DetectLocalExecution();
-      function GetHomeDir(): string;
+      function GetHomeDir(): IPath;
     public
       procedure Init; override;
-
-      function DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: Boolean): TDirectoryEntryArray; override;
-
-      function GetLogPath        : WideString; override;
-      function GetGameSharedPath : WideString; override;
-      function GetGameUserPath   : WideString; override;
+      
+      function GetLogPath        : IPath; override;
+      function GetGameSharedPath : IPath; override;
+      function GetGameUserPath   : IPath; override;
   end;
 
 implementation
@@ -60,9 +59,7 @@ implementation
 uses
   UCommandLine,
   BaseUnix,
-  {$IF FPC_VERSION_INT >= 2002002}
   pwd,
-  {$IFEND}
   SysUtils,
   ULog;
 
@@ -88,114 +85,65 @@ end;
  *}
 procedure TPlatformLinux.DetectLocalExecution();
 var
-  LocalDir: string;
+  LocalDir, LanguageDir: IPath;
 begin
-  LocalDir := GetExecutionDir();
-  
   // we just check if the 'languages' folder exists in the
   // directory of the executable. If so -> local execution.
-  UseLocalDirs := (DirectoryExists(LocalDir + 'languages'));
-end;
-
-function TPlatformLinux.DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: Boolean): TDirectoryEntryArray;
-var
-  i: Integer;
-  TheDir  : pDir;
-  ADirent : pDirent;
-  Entry   : Longint;
-  lAttrib : integer;
-begin
-  i := 0;
-  Filter := LowerCase(Filter);
-
-  TheDir := FpOpenDir( Dir );
-  if Assigned(TheDir) then
-  begin
-    repeat
-      ADirent :=  FpReadDir(TheDir^);
-
-      if Assigned(ADirent) and (ADirent^.d_name <> '.') and (ADirent^.d_name <> '..') then
-      begin
-        lAttrib := FileGetAttr(Dir + ADirent^.d_name);
-        if ReturnAllSubDirs and ((lAttrib and faDirectory) <> 0) then
-        begin
-          SetLength( Result, i + 1);
-          Result[i].Name        := ADirent^.d_name;
-          Result[i].IsDirectory := true;
-          Result[i].IsFile      := false;
-          i := i + 1;
-        end
-        else if (Length(Filter) = 0) or (Pos( Filter, LowerCase(ADirent^.d_name)) > 0) then
-        begin
-          SetLength( Result, i + 1);
-          Result[i].Name        := ADirent^.d_name;
-          Result[i].IsDirectory := false;
-          Result[i].IsFile      := true;
-          i := i + 1;
-        end;
-      end;
-    until (ADirent = nil);
-
-    FpCloseDir(TheDir^);
-  end;
+  LocalDir := GetExecutionDir();
+  LanguageDir := LocalDir.Append('languages');
+  UseLocalDirs := LanguageDir.IsDirectory;
 end;
 
-function TPlatformLinux.GetLogPath: WideString;
+function TPlatformLinux.GetLogPath: IPath;
 begin
   if UseLocalDirs then
     Result := GetExecutionDir()
   else
-    Result := GetGameUserPath() + 'logs/';
+    Result := GetGameUserPath().Append('logs', pdAppend);
 
   // create non-existing directories
-  ForceDirectories(Result);
+  Result.CreateDirectory(true);
 end;
 
-function TPlatformLinux.GetGameSharedPath: WideString;
+function TPlatformLinux.GetGameSharedPath: IPath;
 begin
   if UseLocalDirs then
     Result := GetExecutionDir()
   else
-    Result := IncludeTrailingPathDelimiter(INSTALL_DATADIR);
+    Result := Path(INSTALL_DATADIR, pdAppend);
 end;
 
-function TPlatformLinux.GetGameUserPath: WideString;
+function TPlatformLinux.GetGameUserPath: IPath;
 begin
   if UseLocalDirs then
     Result := GetExecutionDir()
   else
-    Result := GetHomeDir() + '.ultrastardx/';
+    Result := GetHomeDir().Append('.ultrastardx', pdAppend);
 end;
 
 {**
  * Returns the user's home directory terminated by a path delimiter
  *}
-function TPlatformLinux.GetHomeDir(): string;
-{$IF FPC_VERSION_INT >= 2002002}
+function TPlatformLinux.GetHomeDir(): IPath;
 var
   PasswdEntry: PPasswd;
-{$IFEND}
 begin
-  Result := '';
+  Result := PATH_NONE;
 
-  {$IF FPC_VERSION_INT >= 2002002}
   // try to retrieve the info from passwd
   PasswdEntry := FpGetpwuid(FpGetuid());
   if (PasswdEntry <> nil) then
-    Result := PasswdEntry.pw_dir;
-  {$IFEND}
+    Result := Path(PasswdEntry.pw_dir);
   // fallback if passwd does not contain the path
-  if (Result = '') then
-    Result := GetEnvironmentVariable('HOME');
+  if (Result.IsUnset) then
+    Result := Path(GetEnvironmentVariable('HOME'));
   // add trailing path delimiter (normally '/')
-  if (Result <> '') then
-    Result := IncludeTrailingPathDelimiter(Result);
+  if (Result.IsSet) then
+    Result := Result.AppendPathDelim();
 
-  {$IF FPC_VERSION_INT >= 2002002}
   // GetUserDir() is another function that returns a user path.
   // It uses env-var HOME or a fallback to a temp-dir.
   //Result := GetUserDir();
-  {$IFEND}
 end;
 
 end.
diff --git a/cmake/src/base/UPlatformMacOSX.pas b/cmake/src/base/UPlatformMacOSX.pas
index 96e4bc63..d55e8bea 100644
--- a/cmake/src/base/UPlatformMacOSX.pas
+++ b/cmake/src/base/UPlatformMacOSX.pas
@@ -36,7 +36,9 @@ interface
 uses
   Classes,
   ULog,
-  UPlatform;
+  UPlatform,
+  UFilesystem,
+  UPath;
 
 type
   {**
@@ -93,19 +95,21 @@ type
        * GetBundlePath returns the path to the application bundle
        * UltraStarDeluxe.app.
        *}
-      function GetBundlePath: WideString;
+      function GetBundlePath: IPath;
 
       {**
        * GetApplicationSupportPath returns the path to
        * $HOME/Library/Application Support/UltraStarDeluxe.
        *}
-      function GetApplicationSupportPath: WideString;
+      function GetApplicationSupportPath: IPath;
 
       {**
        * see the description of @link(Init).
        *}
       procedure CreateUserFolders();
 
+      function GetHomeDir(): IPath;
+
     public
       {**
        * Init simply calls @link(CreateUserFolders), which in turn scans the
@@ -115,38 +119,31 @@ type
        *}
       procedure Init; override;
 
-      {**
-       * DirectoryFindFiles returns all entries of a folder with names and
-       * booleans about their type, i.e. file or directory.
-       *}
-      function  DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: boolean): TDirectoryEntryArray; override;
-
       {**
        * GetLogPath returns the path for log messages. Currently it is set to
-       * $HOME/Library/Application Support/UltraStarDeluxe/Log.
+       * $HOME/Library/Application Support/UltraStarDeluxe/log.
        *}
-      function  GetLogPath        : WideString; override;
+      function  GetLogPath:        IPath; override;
 
       {**
        * GetGameSharedPath returns the path for shared resources. Currently it
        * is set to /Library/Application Support/UltraStarDeluxe.
        * However it is not used.
        *}
-      function  GetGameSharedPath : WideString; override;
+      function  GetGameSharedPath: IPath; override;
 
       {**
        * GetGameUserPath returns the path for user resources. Currently it is
        * set to $HOME/Library/Application Support/UltraStarDeluxe.
        * This is where a user can add songs, themes, ....
        *}
-      function  GetGameUserPath   : WideString; override;
+      function  GetGameUserPath:   IPath; override;
   end;
 
 implementation
 
 uses
-  SysUtils,
-  BaseUnix;
+  SysUtils;
 
 procedure TPlatformMacOSX.Init;
 begin
@@ -154,178 +151,131 @@ begin
 end;
 
 procedure TPlatformMacOSX.CreateUserFolders();
-const
-  // used to construct the @link(UserPathName)
-  PathName: string = '/Library/Application Support/UltraStarDeluxe';
 var
-  RelativePath: string;
+  RelativePath: IPath;
   // BaseDir contains the path to the folder, where a search is performed.
   // It is set to the entries in @link(DirectoryList) one after the other.
-  BaseDir: string;
+  BaseDir: IPath;
   // OldBaseDir contains the path to the folder, where the search started.
   // It is used to return to it, when the search is completed in all folders.
-  OldBaseDir: string;
-  // This record contains the result of a file search with FindFirst or FindNext
-  SearchInfo: TSearchRec;
+  OldBaseDir: IPath;
+  Iter:       IFileIterator;
+  FileInfo:   TFileInfo;
+  CurPath:    IPath;
   // These two lists contain all folder and file names found
   // within the folder @link(BaseDir).
-  DirectoryList, FileList: TStringList;
+  DirectoryList, FileList: IInterfaceList;
   // DirectoryIsFinished contains the index of the folder in @link(DirectoryList),
   // which is the last one completely searched. Later folders are still to be
   // searched for additional files and folders.
   DirectoryIsFinished: longint;
-  Counter: longint;
+  I: longint;
   // These three are for creating directories, due to possible symlinks
   CreatedDirectory: boolean;
   FileAttrs:        integer;
-  DirectoryPath:    string;
-
-  UserPathName:     string;
+  DirectoryPath:    IPath;
+  UserPath:         IPath;
+  SrcFile, TgtFile: IPath;
 begin
   // Get the current folder and save it in OldBaseDir for returning to it, when
   // finished.
-  GetDir(0, OldBaseDir);
+  OldBaseDir := FileSystem.GetCurrentDir();
 
-  // UltraStarDeluxe.app/Contents contains all the default files and
-  // folders.
-  BaseDir := OldBaseDir + '/UltraStarDeluxe.app/Contents';
-  ChDir(BaseDir);
+  // UltraStarDeluxe.app/Contents contains all the default files and folders.
+  BaseDir := OldBaseDir.Append('UltraStarDeluxe.app/Contents');
+  FileSystem.SetCurrentDir(BaseDir);
 
-  // Right now, only $HOME/Library/Application Support/UltraStarDeluxe
-  // is used.
-  UserPathName := GetEnvironmentVariable('HOME') + PathName;
+  // Right now, only $HOME/Library/Application Support/UltraStarDeluxe is used.
+  UserPath := GetGameUserPath();
 
   DirectoryIsFinished := 0;
-  DirectoryList := TStringList.Create();
-  FileList := TStringList.Create();
-  DirectoryList.Add('.');
+  // replace with IInterfaceList
+  DirectoryList := TInterfaceList.Create();
+  FileList := TInterfaceList.Create();
+  DirectoryList.Add(Path('.'));
 
   // create the folder and file lists
   repeat
-
-    RelativePath := DirectoryList[DirectoryIsFinished];
-    ChDir(BaseDir + '/' + RelativePath);
-    if (FindFirst('*', faAnyFile, SearchInfo) = 0) then
+    RelativePath := (DirectoryList[DirectoryIsFinished] as IPath);
+    FileSystem.SetCurrentDir(BaseDir.Append(RelativePath));
+    Iter := FileSystem.FileFind(Path('*'), faAnyFile);
+    while (Iter.HasNext) do    
     begin
-      repeat
-        if DirectoryExists(SearchInfo.Name) then
-        begin
-          if (SearchInfo.Name <> '.') and (SearchInfo.Name <> '..') then
-            DirectoryList.Add(RelativePath + '/' + SearchInfo.Name);
-        end
-        else
-          Filelist.Add(RelativePath + '/' + SearchInfo.Name);
-      until (FindNext(SearchInfo) <> 0);
+      FileInfo := Iter.Next;
+      CurPath := FileInfo.Name;
+      if CurPath.IsDirectory() then
+      begin
+        if (not CurPath.Equals('.')) and 
+	   (not CurPath.Equals('..')) and 
+	   (not CurPath.Equals('MacOS')) then
+          DirectoryList.Add(RelativePath.Append(CurPath));
+      end
+      else
+        Filelist.Add(RelativePath.Append(CurPath));
     end;
-    FindClose(SearchInfo);
     Inc(DirectoryIsFinished);
   until (DirectoryIsFinished = DirectoryList.Count);
 
   // create missing folders
-  ForceDirectories(UserPathName);        // should not be necessary since (UserPathName+'/.') is created.
-  for Counter := 0 to DirectoryList.Count-1 do
+  UserPath.CreateDirectory(true); // should not be necessary since (UserPathName+'/.') is created.
+  for I := 0 to DirectoryList.Count-1 do
   begin
-    DirectoryPath    := UserPathName + '/' + DirectoryList[Counter];
-    CreatedDirectory := ForceDirectories(DirectoryPath);
-    FileAttrs        := FileGetAttr(DirectoryPath);
-    // Don't know how to analyse the target of the link.
+    CurPath          := DirectoryList[I] as IPath;
+    DirectoryPath    := UserPath.Append(CurPath);
+    CreatedDirectory := DirectoryPath.CreateDirectory();
+    FileAttrs        := DirectoryPath.GetAttr();
+    // Maybe analyse the target of the link with FpReadlink().
     // Let's assume the symlink is pointing to an existing directory.
     if (not CreatedDirectory) and (FileAttrs and faSymLink > 0) then
-      Log.LogError('Failed to create the folder "'+ UserPathName + '/' + DirectoryList[Counter] +'"',
+      Log.LogError('Failed to create the folder "'+ DirectoryPath.ToNative +'"',
                    'TPlatformMacOSX.CreateUserFolders');
   end;
-  DirectoryList.Free();
 
   // copy missing files
-  for Counter := 0 to Filelist.Count-1 do
+  for I := 0 to Filelist.Count-1 do
   begin
-    CopyFile(BaseDir      + '/' + Filelist[Counter],
-             UserPathName + '/' + Filelist[Counter], true);
+    CurPath := Filelist[I] as IPath;
+    SrcFile := BaseDir.Append(CurPath);
+    TgtFile := UserPath.Append(CurPath);
+    SrcFile.CopyFile(TgtFile, true);
   end;
-  FileList.Free();
 
   // go back to the initial folder
-  ChDir(OldBaseDir);
+  FileSystem.SetCurrentDir(OldBaseDir);
 end;
 
-function TPlatformMacOSX.GetBundlePath: WideString;
-var
-  i, pos : integer;
+function TPlatformMacOSX.GetBundlePath: IPath;
 begin
   // Mac applications are packaged in folders.
   // Cutting the last two folders yields the application folder.
-
-  Result := GetExecutionDir();
-  for i := 1 to 2 do
-  begin
-    pos := Length(Result);
-    repeat
-      Delete(Result, pos, 1);
-      pos := Length(Result);
-    until (pos = 0) or (Result[pos] = '/');
-  end;
+  Result := GetExecutionDir().GetParent().GetParent();
 end;
 
-function TPlatformMacOSX.GetApplicationSupportPath: WideString;
+function TPlatformMacOSX.GetApplicationSupportPath: IPath;
 const
-  PathName : string = '/Library/Application Support/UltraStarDeluxe';
+  PathName: string = 'Library/Application Support/UltraStarDeluxe';
 begin
-  Result := GetEnvironmentVariable('HOME') + PathName + '/';
+  Result := GetHomeDir().Append(PathName, pdAppend);
 end;
 
-function TPlatformMacOSX.GetLogPath: WideString;
+function TPlatformMacOSX.GetHomeDir(): IPath;
 begin
-  Result := GetApplicationSupportPath + 'Logs';
+  Result := Path(GetEnvironmentVariable('HOME'));
 end;
 
-function TPlatformMacOSX.GetGameSharedPath: WideString;
+function TPlatformMacOSX.GetLogPath: IPath;
 begin
-  Result := GetApplicationSupportPath;
+  Result := GetApplicationSupportPath.Append('logs');
 end;
 
-function TPlatformMacOSX.GetGameUserPath: WideString;
+function TPlatformMacOSX.GetGameSharedPath: IPath;
 begin
   Result := GetApplicationSupportPath;
 end;
 
-function TPlatformMacOSX.DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: boolean): TDirectoryEntryArray;
-var
-  i       : integer;
-  TheDir  : pdir;
-  ADirent : pDirent;
-  lAttrib : integer;
+function TPlatformMacOSX.GetGameUserPath: IPath;
 begin
-  i := 0;
-  Filter := LowerCase(Filter);
-
-  TheDir := FPOpenDir(Dir);
-  if Assigned(TheDir) then
-    repeat
-      ADirent := FPReadDir(TheDir);
-
-      if Assigned(ADirent) and (ADirent^.d_name <> '.') and (ADirent^.d_name <> '..') then
-      begin
-        lAttrib := FileGetAttr(Dir + ADirent^.d_name);
-        if ReturnAllSubDirs and ((lAttrib and faDirectory) <> 0) then
-        begin
-          SetLength(Result, i + 1);
-          Result[i].Name        := ADirent^.d_name;
-          Result[i].IsDirectory := true;
-          Result[i].IsFile      := false;
-          i := i + 1;
-        end
-        else if (Length(Filter) = 0) or (Pos( Filter, LowerCase(ADirent^.d_name)) > 0) then
-        begin
-          SetLength(Result, i + 1);
-          Result[i].Name        := ADirent^.d_name;
-          Result[i].IsDirectory := false;
-          Result[i].IsFile      := true;
-          i := i + 1;
-        end;
-      end;
-    until ADirent = nil;
-
-  FPCloseDir(TheDir);
+  Result := GetApplicationSupportPath;
 end;
 
 end.
diff --git a/cmake/src/base/UPlatformWindows.pas b/cmake/src/base/UPlatformWindows.pas
index e198958a..91d3cce6 100644
--- a/cmake/src/base/UPlatformWindows.pas
+++ b/cmake/src/base/UPlatformWindows.pas
@@ -38,21 +38,23 @@ interface
 
 uses
   Classes,
-  UPlatform;
+  UPlatform,
+  UPath;
 
 type
   TPlatformWindows = class(TPlatform)
     private
-      function GetSpecialPath(CSIDL: integer): WideString;
+      UseLocalDirs: boolean;
+      
+      function GetSpecialPath(CSIDL: integer): IPath;
+      procedure DetectLocalExecution();
     public
-      function DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: Boolean): TDirectoryEntryArray; override;
+      procedure Init; override;
       function TerminateIfAlreadyRunning(var WndTitle: String): Boolean; override;
 
-      function GetLogPath: WideString; override;
-      function GetGameSharedPath: WideString; override;
-      function GetGameUserPath: WideString; override;
-
-      function CopyFile(const Source, Target: WideString; FailIfExists: boolean): boolean; override;
+      function GetLogPath: IPath; override;
+      function GetGameSharedPath: IPath; override;
+      function GetGameUserPath: IPath; override;
   end;
 
 implementation
@@ -63,93 +65,10 @@ uses
   Windows,
   UConfig;
 
-type
-  TSearchRecW = record
-    Time: Integer;
-    Size: Integer;
-    Attr: Integer;
-    Name: WideString;
-    ExcludeAttr: Integer;
-    FindHandle: THandle;
-    FindData: TWin32FindDataW;
-  end;
-
-function  FindFirstW(const Path: WideString; Attr: Integer; var F: TSearchRecW): Integer; forward;
-function  FindNextW(var F: TSearchRecW): Integer; forward;
-procedure FindCloseW(var F: TSearchRecW); forward;
-function  FindMatchingFileW(var F: TSearchRecW): Integer; forward;
-function  DirectoryExistsW(const Directory: widestring): Boolean; forward;
-
-function FindFirstW(const Path: widestring; Attr: Integer; var F: TSearchRecW): Integer;
-const
-  faSpecial = faHidden or faSysFile or faVolumeID or faDirectory;
-begin
-  F.ExcludeAttr := not Attr and faSpecial;
-{$IFDEF Delphi}
-  F.FindHandle  := FindFirstFileW(PWideChar(Path), F.FindData);
-{$ELSE}
-  F.FindHandle  := FindFirstFileW(PWideChar(Path), @F.FindData);
-{$ENDIF}
-  if F.FindHandle <> INVALID_HANDLE_VALUE then
-  begin
-    Result := FindMatchingFileW(F);
-    if Result <> 0 then FindCloseW(F);
-  end else
-    Result := GetLastError;
-end;
-
-function FindNextW(var F: TSearchRecW): Integer;
-begin
-{$IFDEF Delphi}
-  if FindNextFileW(F.FindHandle, F.FindData) then
-{$ELSE}
-  if FindNextFileW(F.FindHandle, @F.FindData) then
-{$ENDIF}
-    Result := FindMatchingFileW(F)
-  else
-    Result := GetLastError;
-end;
-
-procedure FindCloseW(var F: TSearchRecW);
-begin
-  if F.FindHandle <> INVALID_HANDLE_VALUE then
-  begin
-    Windows.FindClose(F.FindHandle);
-    F.FindHandle := INVALID_HANDLE_VALUE;
-  end;
-end;
-
-function FindMatchingFileW(var F: TSearchRecW): Integer;
-var
-  LocalFileTime: TFileTime;
-begin
-  with F do
-  begin
-    while FindData.dwFileAttributes and ExcludeAttr <> 0 do
-{$IFDEF Delphi}
-      if not FindNextFileW(FindHandle, FindData) then
-{$ELSE}
-      if not FindNextFileW(FindHandle, @FindData) then
-{$ENDIF}
-      begin
-        Result := GetLastError;
-        Exit;
-      end;
-    FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
-    FileTimeToDosDateTime(LocalFileTime, LongRec(Time).Hi, LongRec(Time).Lo);
-    Size := FindData.nFileSizeLow;
-    Attr := FindData.dwFileAttributes;
-    Name := FindData.cFileName;
-  end;
-  Result := 0;
-end;
-
-function DirectoryExistsW(const Directory: widestring): Boolean;
-var
-  Code: Integer;
+procedure TPlatformWindows.Init;
 begin
-  Code := GetFileAttributesW(PWideChar(Directory));
-  Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
+  inherited Init();
+  DetectLocalExecution();
 end;
 
 //------------------------------
@@ -180,41 +99,6 @@ begin
     end;
 end;
 
-function TPlatformWindows.DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: Boolean): TDirectoryEntryArray;
-var
-    i : Integer;
-    SR : TSearchRecW;
-    Attrib : Integer;
-begin
-  i := 0;
-  Filter := LowerCase(Filter);
-
-  if FindFirstW(Dir + '*', faAnyFile or faDirectory, SR) = 0 then
-  repeat
-    if (SR.Name <> '.') and (SR.Name <> '..') then
-    begin
-      Attrib := FileGetAttr(Dir + SR.name);
-      if ReturnAllSubDirs and ((Attrib and faDirectory) <> 0) then
-      begin
-        SetLength( Result, i + 1);
-        Result[i].Name        := SR.name;
-        Result[i].IsDirectory := true;
-        Result[i].IsFile      := false;
-        i := i + 1;
-      end
-      else if (Length(Filter) = 0) or (Pos( Filter, LowerCase(SR.Name)) > 0) then
-      begin
-        SetLength( Result, i + 1);
-        Result[i].Name        := SR.Name;
-        Result[i].IsDirectory := false;
-        Result[i].IsFile      := true;
-        i := i + 1;
-      end;
-    end;
-  until FindNextW(SR) <> 0;
-  FindCloseW(SR);
-end;
-
 (**
  * Returns the path of a special folder.
  *
@@ -225,37 +109,101 @@ end;
  * CSIDL_PERSONAL      (e.g. C:\Documents and Settings\username\My Documents)
  * CSIDL_MYMUSIC       (e.g. C:\Documents and Settings\username\My Documents\My Music)
  *)
-function TPlatformWindows.GetSpecialPath(CSIDL: integer): WideString;
+function TPlatformWindows.GetSpecialPath(CSIDL: integer): IPath;
 var
   Buffer: array [0..MAX_PATH-1] of WideChar;
 begin
-{$IF Defined(Delphi) or (FPC_VERSION_INT >= 2002002)} // >= 2.2.2
   if (SHGetSpecialFolderPathW(0, @Buffer, CSIDL, false)) then
-    Result := Buffer
+    Result := Path(Buffer)
   else
-{$IFEND}
-    Result := '';
+    Result := PATH_NONE;
 end;
 
-function TPlatformWindows.GetLogPath: WideString;
+{**
+ * Detects whether the was executed locally or globally.
+ * - Local mode:
+ *   - Condition:
+ *     - config.ini is writable or creatable in the directory of the executable.
+ *   - Examples:
+ *     - The USDX zip-archive has been unpacked to a directory with write.
+ *       permissions
+ *     - XP: USDX was installed to %ProgramFiles% and the user is an admin.
+ *     - USDX is started from an external HD- or flash-drive
+ *   - Behavior:
+ *     Config files like config.ini or score db reside in the directory of the
+ *     executable. This is useful to enable windows users to have a portable
+ *     installation e.g. on an external hdd.
+ *     This is also the default behaviour of usdx prior to version 1.1
+ * - Global mode:
+ *   - Condition:
+ *     - config.ini is not writable.
+ *   - Examples:
+ *     - Vista/7: USDX was installed to %ProgramFiles%.
+ *     - XP: USDX was installed to %ProgramFiles% and the user is not an admin.
+ *     - USDX is started from CD
+ *   - Behavior:
+ *     - The config files are in a separate folder (e.g. %APPDATA%\ultrastardx)
+ *
+ * On windows, resources (themes, language-files)
+ * reside in the directory of the executable in any case
+ *
+ * Sets UseLocalDirs to true if the game is executed locally, false otherwise.
+ *}
+procedure TPlatformWindows.DetectLocalExecution();
+var
+  LocalDir, ConfigIni: IPath;
+  Handle: TFileHandle;
 begin
-  Result := GetExecutionDir();
+  LocalDir := GetExecutionDir();
+  ConfigIni := LocalDir.Append('config.ini');
+
+  // check if config.ini is writable or creatable, if so use local dirs
+  UseLocalDirs := false;
+  if (ConfigIni.Exists()) then
+  begin
+    // do not use a read-only config file
+    if (not ConfigIni.IsReadOnly()) then
+    begin
+      // Just open the file in read-write mode to be sure that we have access
+      // rights for it.
+      // Note: Do not use IsReadOnly() as it does not check file privileges, so
+      // a non-read-only file might not be writable for us.
+      Handle := ConfigIni.Open(fmOpenReadWrite);
+      if (Handle <> -1) then
+      begin
+        FileClose(Handle);
+        UseLocalDirs := true;
+      end;
+    end;
+  end
+  else // config.ini does not exist
+  begin
+    // try to create config.ini
+    Handle := ConfigIni.CreateFile();
+    if (Handle <> -1) then
+    begin
+      FileClose(Handle);
+      UseLocalDirs := true;
+    end;
+  end;
 end;
 
-function TPlatformWindows.GetGameSharedPath: WideString;
+function TPlatformWindows.GetLogPath: IPath;
 begin
-  Result := GetExecutionDir();
+  Result := GetGameUserPath;
 end;
 
-function TPlatformWindows.GetGameUserPath: WideString;
+function TPlatformWindows.GetGameSharedPath: IPath;
 begin
-  //Result := GetSpecialPath(CSIDL_APPDATA) + PathDelim + 'UltraStarDX' + PathDelim;
   Result := GetExecutionDir();
 end;
 
-function TPlatformWindows.CopyFile(const Source, Target: WideString; FailIfExists: boolean): boolean;
+function TPlatformWindows.GetGameUserPath: IPath;
 begin
-  Result := Windows.CopyFileW(PWideChar(Source), PWideChar(Target), FailIfExists);
+  if UseLocalDirs then
+    Result := GetExecutionDir()
+  else
+    Result := GetSpecialPath(CSIDL_APPDATA).Append('ultrastardx', pdAppend);
 end;
 
 end.
diff --git a/cmake/src/base/UPlaylist.pas b/cmake/src/base/UPlaylist.pas
index 419ce687..f12e06cf 100644
--- a/cmake/src/base/UPlaylist.pas
+++ b/cmake/src/base/UPlaylist.pas
@@ -34,21 +34,23 @@ interface
 {$I switches.inc}
 
 uses
+  Classes,
   USong,
-  UPath;
+  UPath,
+  UPathUtils;
 
 type
   TPlaylistItem = record
-    Artist: String;
-    Title:  String;
+    Artist: UTF8String;
+    Title:  UTF8String;
     SongID: Integer;
   end;
 
   APlaylistItem = array of TPlaylistItem;
 
   TPlaylist = record
-    Name:     String;
-    Filename: String;
+    Name:     UTF8String;
+    Filename: IPath;
     Items:    APlaylistItem;
   end;
 
@@ -68,20 +70,20 @@ type
       Playlists:    APlaylist;
 
       constructor Create;
-      Procedure   LoadPlayLists;
-      Function    LoadPlayList(Index: Cardinal; Filename: String): Boolean;
-      Procedure   SavePlayList(Index: Cardinal);
+      procedure   LoadPlayLists;
+      function    LoadPlayList(Index: Cardinal; const Filename: IPath): Boolean;
+      procedure   SavePlayList(Index: Cardinal);
 
-      Procedure   SetPlayList(Index: Cardinal);
+      procedure   SetPlayList(Index: Cardinal);
 
-      Function    AddPlaylist(Name: String): Cardinal;
-      Procedure   DelPlaylist(const Index: Cardinal);
+      function    AddPlaylist(const Name: UTF8String): Cardinal;
+      procedure   DelPlaylist(const Index: Cardinal);
 
-      Procedure   AddItem(const SongID: Cardinal; const iPlaylist: Integer = -1);
-      Procedure   DelItem(const iItem: Cardinal; const iPlaylist: Integer = -1);
+      procedure   AddItem(const SongID: Cardinal; const iPlaylist: Integer = -1);
+      procedure   DelItem(const iItem: Cardinal; const iPlaylist: Integer = -1);
 
-      Procedure   GetNames(var PLNames: array of String);
-      Function    GetIndexbySongID(const SongID: Cardinal; const iPlaylist: Integer = -1): Integer;
+      procedure   GetNames(var PLNames: array of UTF8String);
+      function    GetIndexbySongID(const SongID: Cardinal; const iPlaylist: Integer = -1): Integer;
     end;
 
     {Modes:
@@ -95,13 +97,15 @@ type
 
 implementation
 
-uses USongs,
-     ULog,
-     UMain,
-     //UFiles,
-     UGraphic,
-     UThemes,
-     SysUtils;
+uses
+  SysUtils,
+  USongs,
+  ULog,
+  UMain,
+  UFilesystem,
+  UGraphic,
+  UThemes,
+  UUnicodeUtils;
 
 //----------
 //Create - Construct Class - Dummy for now
@@ -117,90 +121,90 @@ end;
 //----------
 Procedure   TPlayListManager.LoadPlayLists;
 var
-  SR:   TSearchRec;
   Len:  Integer;
   PlayListBuffer: TPlayList;
+  Iter: IFileIterator;
+  FileInfo: TFileInfo;
 begin
   SetLength(Playlists, 0);
 
-  if FindFirst(PlayListPath + '*.upl', 0, SR) = 0 then
+  Iter := FileSystem.FileFind(PlayListPath.Append('*.upl'), 0);
+  while (Iter.HasNext) do
   begin
-    repeat
-      Len := Length(Playlists);
-      SetLength(Playlists, Len +1);
+    Len := Length(Playlists);
+    SetLength(Playlists, Len + 1);
+
+    FileInfo := Iter.Next;
 
-      if not LoadPlayList (Len, Sr.Name) then
-        SetLength(Playlists, Len)
-      else
+    if not LoadPlayList(Len, FileInfo.Name) then
+      SetLength(Playlists, Len)
+    else
+    begin
+      // Sort the Playlists - Insertion Sort
+      PlayListBuffer := Playlists[Len];
+      Dec(Len);
+      while (Len >= 0) AND (CompareText(Playlists[Len].Name, PlayListBuffer.Name) >= 0) do
       begin
-        // Sort the Playlists - Insertion Sort
-        PlayListBuffer := Playlists[Len];
-        Dec(Len);
-        while (Len >= 0) AND (CompareText(Playlists[Len].Name, PlayListBuffer.Name) >= 0) do
-        begin
-            Playlists[Len+1] := Playlists[Len];
-            Dec(Len);
-        end;
-        Playlists[Len+1] := PlayListBuffer;
+          Playlists[Len+1] := Playlists[Len];
+          Dec(Len);
       end;
-
-    until FindNext(SR) <> 0;
-    FindClose(SR);
-  end;  
+      Playlists[Len+1] := PlayListBuffer;
+    end;
+  end;
 end;
 
 //----------
 //LoadPlayList - Load a Playlist in the Array
 //----------
-Function    TPlayListManager.LoadPlayList(Index: Cardinal; Filename: String): Boolean;
-  var
-    F: TextFile;
-    Line: String;
-    PosDelimiter: Integer;
-    SongID: Integer;
-    Len: Integer;
+function TPlayListManager.LoadPlayList(Index: Cardinal; const Filename: IPath): Boolean;
 
-  Function FindSong(Artist, Title: String): Integer;
+  function FindSong(Artist, Title: UTF8String): Integer;
   var I: Integer;
   begin
     Result := -1;
 
     For I := low(CatSongs.Song) to high(CatSongs.Song) do
     begin
-      if (CatSongs.Song[I].Title = Title) AND (CatSongs.Song[I].Artist = Artist) then
+      if (CatSongs.Song[I].Title = Title) and (CatSongs.Song[I].Artist = Artist) then
       begin
         Result := I;
         Break;
       end;
     end;
   end;
+
+var
+  TextStream: TTextFileStream;
+  Line: UTF8String;
+  PosDelimiter: Integer;
+  SongID: Integer;
+  Len: Integer;
+  FilenameAbs: IPath;
 begin
-  if not FileExists(PlayListPath + Filename) then
-  begin
-    Log.LogError('Could not load Playlist: ' + Filename);
-    Result := False;
-    Exit;
+  //Load File
+  try
+    FilenameAbs := PlaylistPath.Append(Filename);
+    TextStream := TMemTextFileStream.Create(FilenameAbs, fmOpenRead);
+  except
+    begin
+      Log.LogError('Could not load Playlist: ' + FilenameAbs.ToNative);
+      Result := False;
+      Exit;
+    end;
   end;
   Result := True;
 
-  //Load File
-  AssignFile(F, PlayListPath + FileName);
-  Reset(F);
-
   //Set Filename
-  PlayLists[Index].Filename := Filename;
-  PlayLists[Index].Name := '';
+  Playlists[Index].Filename := Filename;
+  Playlists[Index].Name := '';
 
   //Read Until End of File
-  While not Eof(F) do
+  while TextStream.ReadLine(Line) do
   begin
-    //Read Curent Line
-    Readln(F, Line);
-
     if (Length(Line) > 0) then
     begin
-    PosDelimiter := Pos(':', Line);
-      if (PosDelimiter  <> 0) then
+      PosDelimiter := UTF8Pos(':', Line);
+      if (PosDelimiter <> 0) then
       begin
         //Comment or Name String
         if (Line[1] = '#') then
@@ -224,7 +228,7 @@ begin
             PlayLists[Index].Items[Len].Artist := Trim(copy(Line, 1, PosDelimiter - 1));
             PlayLists[Index].Items[Len].Title  := Trim(copy(Line, PosDelimiter + 1, Length(Line) - PosDelimiter));
           end
-          else Log.LogError('Could not find Song in Playlist: ' + PlayLists[Index].Filename + ', ' + Line);
+          else Log.LogError('Could not find Song in Playlist: ' + PlayLists[Index].Filename.ToNative + ', ' + Line);
         end;
       end;
     end;
@@ -233,71 +237,70 @@ begin
   //If no special name is given, use Filename
   if PlayLists[Index].Name = '' then
   begin
-    PlayLists[Index].Name := ChangeFileExt(FileName, '');
+    PlayLists[Index].Name := FileName.SetExtension('').ToUTF8;
   end;
 
   //Finish (Close File)
-  CloseFile(F);
+  TextStream.Free;
 end;
 
-//----------
-//SavePlayList - Saves the specified Playlist
-//----------
-Procedure   TPlayListManager.SavePlayList(Index: Cardinal);
+{**
+ * Saves the specified Playlist
+ *}
+procedure   TPlayListManager.SavePlayList(Index: Cardinal);
 var
-  F: TextFile;
+  TextStream: TTextFileStream;
+  PlaylistFile: IPath;
   I: Integer;
 begin
-  if (Not FileExists(PlaylistPath + Playlists[Index].Filename)) OR (Not FileisReadOnly(PlaylistPath + Playlists[Index].Filename)) then
-  begin
+  PlaylistFile := PlaylistPath.Append(Playlists[Index].Filename);
 
-    //open File for Rewriting
-    AssignFile(F, PlaylistPath + Playlists[Index].Filename);
-    try
-      try
-        Rewrite(F);
+  // cannot update read-only file
+  if PlaylistFile.IsFile() and PlaylistFile.IsReadOnly() then
+    Exit;
 
-        //Write Version (not nessecary but helpful)
-        WriteLn(F, '######################################');
-        WriteLn(F, '#Ultrastar Deluxe Playlist Format v1.0');
-        WriteLn(F, '#Playlist "' + Playlists[Index].Name + '" with ' + InttoStr(Length(Playlists[Index].Items)) + ' Songs.');
-        WriteLn(F, '######################################');
+  // open file for rewriting
+  TextStream := TMemTextFileStream.Create(PlaylistFile, fmCreate);
+  try
+    // Write version (not nessecary but helpful)
+    TextStream.WriteLine('######################################');
+    TextStream.WriteLine('#Ultrastar Deluxe Playlist Format v1.0');
+    TextStream.WriteLine(Format('#Playlist %s with %d Songs.',
+                         [ Playlists[Index].Name, Length(Playlists[Index].Items) ]));
+    TextStream.WriteLine('######################################');
 
-        //Write Name Information
-        WriteLn(F, '#Name: ' + Playlists[Index].Name);
+    // Write name information
+    TextStream.WriteLine('#Name: ' + Playlists[Index].Name);
 
-        //Write Song Information
-        WriteLn(F, '#Songs:');
+    // Write song information
+    TextStream.WriteLine('#Songs:');
 
-        For I := 0 to high(Playlists[Index].Items) do
-        begin
-          WriteLn(F, Playlists[Index].Items[I].Artist + ' : ' + Playlists[Index].Items[I].Title);
-        end;
-      except
-        log.LogError('Could not write Playlistfile "' + Playlists[Index].Name + '"');
-      end;
-    finally
-      CloseFile(F);
+    for I := 0 to high(Playlists[Index].Items) do
+    begin
+      TextStream.WriteLine(Playlists[Index].Items[I].Artist + ' : ' + Playlists[Index].Items[I].Title);
     end;
+  except
+    Log.LogError('Could not write Playlistfile "' + Playlists[Index].Name + '"');
   end;
+  TextStream.Free;
 end;
 
-//----------
-//SetPlayList - Display a Playlist in CatSongs
-//----------
-Procedure   TPlayListManager.SetPlayList(Index: Cardinal);
+{**
+ * Display a Playlist in CatSongs
+ *}
+procedure TPlayListManager.SetPlayList(Index: Cardinal);
 var
   I: Integer;
 begin
-  If (Int(Index) > High(PlayLists)) then
+  if (Int(Index) > High(PlayLists)) then
     exit;
 
   //Hide all Songs
-  For I := 0 to high(CatSongs.Song) do
+  for I := 0 to high(CatSongs.Song) do
      CatSongs.Song[I].Visible := False;
 
   //Show Songs in PL
-  For I := 0 to high(PlayLists[Index].Items) do
+  for I := 0 to high(PlayLists[Index].Items) do
   begin
     CatSongs.Song[PlayLists[Index].Items[I].SongID].Visible := True;
   end;
@@ -324,31 +327,33 @@ end;
 //----------
 //AddPlaylist - Adds a Playlist and Returns the Index
 //----------
-Function    TPlayListManager.AddPlaylist(Name: String): Cardinal;
+function TPlayListManager.AddPlaylist(const Name: UTF8String): cardinal;
 var
   I: Integer;
+  PlaylistFile: IPath;
 begin
   Result := Length(Playlists);
   SetLength(Playlists, Result + 1);
   
   // Sort the Playlists - Insertion Sort
-  while (Result > 0) AND (CompareText(Playlists[Result - 1].Name, Name) >= 0) do
+  while (Result > 0) and (CompareText(Playlists[Result - 1].Name, Name) >= 0) do
   begin
     Dec(Result);
     Playlists[Result+1] := Playlists[Result];
   end;
-  Playlists[Result].Name      := Name;
+  Playlists[Result].Name := Name;
+
+  // clear playlist items
+  SetLength(Playlists[Result].Items, 0);
 
   I := 1;
-  if (not FileExists(PlaylistPath + Name + '.upl')) then
-    Playlists[Result].Filename  := Name + '.upl'
-  else
+  PlaylistFile := PlaylistPath.Append(Name + '.upl');
+  while (PlaylistFile.Exists) do
   begin
-    repeat
-      Inc(I);
-    until not FileExists(PlaylistPath + Name + InttoStr(I) + '.upl');
-    Playlists[Result].Filename := Name + InttoStr(I) + '.upl';
+    Inc(I);
+    PlaylistFile := PlaylistPath.Append(Name + InttoStr(I) + '.upl');
   end;
+  Playlists[Result].Filename := PlaylistFile.GetName;
 
   //Save new Playlist
   SavePlayList(Result);
@@ -357,28 +362,28 @@ end;
 //----------
 //DelPlaylist - Deletes a Playlist
 //----------
-Procedure   TPlayListManager.DelPlaylist(const Index: Cardinal);
+procedure   TPlayListManager.DelPlaylist(const Index: Cardinal);
 var
   I: Integer;
-  Filename: String;
+  Filename: IPath;
 begin
-  If Int(Index) > High(Playlists) then
+  if Int(Index) > High(Playlists) then
     Exit;
 
-  Filename := PlaylistPath + Playlists[Index].Filename;
+  Filename := PlaylistPath.Append(Playlists[Index].Filename);
 
   //If not FileExists or File is not Writeable then exit
-  If (Not FileExists(Filename)) OR (FileisReadOnly(Filename)) then
+  if (not Filename.IsFile()) or (Filename.IsReadOnly()) then
     Exit;
 
 
   //Delete Playlist from FileSystem
-  if Not DeleteFile(Filename) then
+  if not Filename.DeleteFile() then
     Exit;
 
   //Delete Playlist from Array
   //move all PLs to the Hole
-  For I := Index to High(Playlists)-1 do
+  for I := Index to High(Playlists)-1 do
     PlayLists[I] := PlayLists[I+1];
 
   //Delete last Playlist
@@ -390,7 +395,7 @@ begin
   begin
     ScreenSong.UnLoadDetailedCover;
     ScreenSong.HideCatTL;
-    CatSongs.SetFilter('', 0);
+    CatSongs.SetFilter('', fltAll);
     ScreenSong.Interaction := 0;
     ScreenSong.FixSelected;
     ScreenSong.ChangeMusic;
@@ -471,7 +476,7 @@ end;
 //----------
 //GetNames - Writes Playlist Names in a Array
 //----------
-Procedure    TPlayListManager.GetNames(var PLNames: array of String);
+procedure TPlayListManager.GetNames(var PLNames: array of UTF8String);
 var
   I: Integer;
   Len: Integer;
diff --git a/cmake/src/base/URecord.pas b/cmake/src/base/URecord.pas
index 2c2093a0..c183875c 100644
--- a/cmake/src/base/URecord.pas
+++ b/cmake/src/base/URecord.pas
@@ -95,14 +95,14 @@ const
 
 type
   TAudioInputSource = record
-    Name: string;
+    Name: UTF8String;
   end;
 
   // soundcard input-devices information
   TAudioInputDevice = class
     public
       CfgIndex:      integer;   // index of this device in Ini.InputDeviceConfig
-      Name:          string;    // soundcard name
+      Name:          UTF8String;    // soundcard name
       Source:        array of TAudioInputSource; // soundcard input-sources
       SourceRestore: integer;  // source-index that will be selected after capturing (-1: not detected)
       MicSource:     integer;  // source-index of mic (-1: none detected)
@@ -133,6 +133,7 @@ type
       destructor Destroy; override;
 
       procedure UpdateInputDeviceConfig;
+      function ValidateSettings: boolean;
 
       // handle microphone input
       procedure HandleMicrophoneData(Buffer: PByteArray; Size: integer;
@@ -143,7 +144,7 @@ type
     private
       Started: boolean;
     protected
-      function UnifyDeviceName(const name: string; deviceIndex: integer): string;
+      function UnifyDeviceName(const name: UTF8String; deviceIndex: integer): UTF8String;
     public
       function GetName: String;           virtual; abstract;
       function InitializeRecord: boolean; virtual; abstract;
@@ -162,6 +163,8 @@ implementation
 
 uses
   ULog,
+  UGraphic,
+  ULanguage,
   UNote;
 
 var
@@ -577,6 +580,7 @@ begin
 
       deviceCfg.Name := Trim(device.Name);
       deviceCfg.Input := 0;
+      deviceCfg.Latency := LATENCY_AUTODETECT;
 
       channelCount := device.AudioFormat.Channels;
       SetLength(deviceCfg.ChannelToPlayerMap, channelCount);
@@ -593,6 +597,50 @@ begin
   end;
 end;
 
+function TAudioInputProcessor.ValidateSettings: boolean;
+const
+  MAX_PLAYER_COUNT = 6; // FIXME: there should be a global variable for this
+var
+  I, J: integer;
+  PlayerID: integer;
+  PlayerMap: array [0 .. MAX_PLAYER_COUNT] of boolean;
+  InputDevice: TAudioInputDevice;
+  InputDeviceCfg: PInputDeviceConfig;
+begin
+  // mark all players as unassigned
+  for I := 0 to High(PlayerMap) do
+    PlayerMap[I] := false;
+
+  // iterate over all active devices
+  for I := 0 to High(DeviceList) do
+  begin
+    InputDevice := DeviceList[I];
+    InputDeviceCfg := @Ini.InputDeviceConfig[InputDevice.CfgIndex];
+    // iterate over all channels of the current devices
+    for J := 0 to High(InputDeviceCfg.ChannelToPlayerMap) do
+    begin
+      // get player that was mapped to the current device channel
+      PlayerID := InputDeviceCfg.ChannelToPlayerMap[J];
+      if (PlayerID <> 0) then
+      begin
+        // check if player is already assigned to another device/channel
+        if (PlayerMap[PlayerID]) then
+        begin
+          ScreenPopupError.ShowPopup(
+              Format(Language.Translate('ERROR_PLAYER_DEVICE_ASSIGNMENT'),
+              [PlayerID]));
+          Result := false;
+          Exit;
+        end;
+
+        // mark player as assigned to a device
+        PlayerMap[PlayerID] := true;
+      end;
+    end;
+  end;
+  Result := true;
+end;
+
 {*
  * Handles captured microphone input data.
  * Params:
@@ -741,11 +789,11 @@ begin
   Started := false;
 end;
 
-function TAudioInputBase.UnifyDeviceName(const name: string; deviceIndex: integer): string;
+function TAudioInputBase.UnifyDeviceName(const name: UTF8String; deviceIndex: integer): UTF8String;
 var
   count: integer; // count of devices with this name
 
-  function IsDuplicate(const name: string): boolean;
+  function IsDuplicate(const name: UTF8String): boolean;
   var
     i: integer;
   begin
@@ -753,10 +801,13 @@ var
     // search devices with same description
     for i := 0 to deviceIndex-1 do
     begin
-      if (AudioInputProcessor.DeviceList[i].Name = name) then
+      if (AudioInputProcessor.DeviceList[i] <> nil) then
       begin
-        Result := true;
-        Break;
+        if (AudioInputProcessor.DeviceList[i].Name = name) then
+        begin
+          Result := true;
+          Break;
+        end;
       end;
     end;
   end;
diff --git a/cmake/src/base/USingScores.pas b/cmake/src/base/USingScores.pas
index 89896d2d..26c5dfe8 100644
--- a/cmake/src/base/USingScores.pas
+++ b/cmake/src/base/USingScores.pas
@@ -117,9 +117,9 @@ type
   TScorePopUp = record
     Player:     byte;        // index of the popups player
     TimeStamp:  cardinal;    // timestamp of popups spawn
-    Rating:     byte;        // 0 to 8, type of rating (cool, bad, etc.)
-    ScoreGiven: word;        // score that has already been given to the player
-    ScoreDiff:  word;        // difference between cur score at spawn and old score
+    Rating:     integer;     // 0 to 8, type of rating (cool, bad, etc.)
+    ScoreGiven: integer;     // score that has already been given to the player
+    ScoreDiff:  integer;     // difference between cur score at spawn and old score
     Next:       PScorePopUp; // next item in list
   end;
   aScorePopUp = array of TScorePopUp;
@@ -129,7 +129,7 @@ type
   //-----------
   TSingScores = class
     private
-      Positions: aScorePosition;
+      aPositions: aScorePosition;
       aPlayers:  aScorePlayer;
       oPositionCount: byte;
       oPlayerCount:   byte;
@@ -138,9 +138,18 @@ type
       FirstPopUp: PScorePopUp;
       LastPopUp:  PScorePopUp;
 
+      // only defined during draw, time passed between
+      // current and previous call of draw
+      TimePassed: Cardinal;
+
       // draws a popup by pointer
       procedure DrawPopUp(const PopUp: PScorePopUp);
 
+      // raises players score if RaiseScore was called
+      // has to be called after DrawPopUp and before
+      // DrawScore
+      procedure DoRaiseScore(const Index: integer);
+
       // draws a score by playerindex
       procedure DrawScore(const Index: integer);
 
@@ -149,6 +158,10 @@ type
 
       // removes a popup w/o destroying the list
       procedure KillPopUp(const last, cur: PScorePopUp);
+
+      // calculate the amount of points for a player that is
+      // still in popups and therfore not displayed
+      function GetPopUpPoints(const Index: integer): integer;
     public
       Settings: record // Record containing some Displaying Options
         Phase1Time: real;     // time for phase 1 to complete (in msecs)
@@ -174,6 +187,7 @@ type
       property PositionCount: byte         read oPositionCount;
       property PlayerCount:   byte         read oPlayerCount;
       property Players:       aScorePlayer read aPlayers;
+      property Positions: aScorePosition read aPositions;
 
       // constructor just sets some standard settings
       constructor Create;
@@ -201,8 +215,14 @@ type
       // it gives every player a score position
       procedure Init;
 
+      // raises the score of a specified player to the specified score
+      procedure RaiseScore(Player: byte; Score: integer);
+
+      // sets the score of a specified player to the specified score
+      procedure SetScore(Player: byte; Score: integer);
+
       // spawns a new line bonus popup for the player
-      procedure SpawnPopUp(const PlayerIndex: byte; const Rating: byte; const Score: word);
+      procedure SpawnPopUp(const PlayerIndex: byte; const Rating: integer; const Score: integer);
 
       // removes all popups from mem
       procedure KillAllPopUps;
@@ -215,6 +235,7 @@ implementation
 
 uses
   SysUtils,
+  Math,
   SDL,
   TextGL,
   ULog,
@@ -266,7 +287,7 @@ procedure TSingScores.AddPosition(const pPosition: PScorePosition);
 begin
   if (PositionCount < MaxPositions) then
   begin
-    Positions[PositionCount] := pPosition^;
+    aPositions[PositionCount] := pPosition^;
     Inc(oPositionCount);
   end;
 end;
@@ -318,6 +339,7 @@ procedure TSingScores.ClearPlayers;
 begin
   KillAllPopUps;
   oPlayerCount := 0;
+  TimePassed := 0;
 end;
 
 {**
@@ -328,6 +350,7 @@ begin
   KillAllPopUps;
   oPlayerCount    := 0;
   oPositionCount  := 0;
+  TimePassed := 0;
 end;
 
 {**
@@ -360,7 +383,7 @@ var
     nPosition.PUW := nPosition.BGW;
     nPosition.PUH := nPosition.BGH;
 
-    nPosition.PUFont     := 2;
+    nPosition.PUFont     := ftOutline1;
     nPosition.PUFontSize := 18;
 
     nPosition.PUStartX := nPosition.BGX;
@@ -399,10 +422,34 @@ begin
   AddByStatics(4, Theme.Sing.StaticP3RScoreBG, Theme.Sing.StaticP3SingBar, Theme.Sing.TextP3RScore);
 end;
 
+{**
+ * raises the score of a specified player to the specified score
+ *}
+procedure TSingScores.RaiseScore(Player: byte; Score: integer);
+begin
+  if (Player <= PlayerCount - 1) then
+    aPlayers[Player].Score := Score;
+end;
+
+{**
+ * sets the score of a specified player to the specified score
+ *}
+procedure TSingScores.SetScore(Player: byte; Score: integer);
+  var
+    Diff: Integer;
+begin
+  if (Player <= PlayerCount - 1) then
+  begin
+    Diff := Score - Players[Player].Score;
+    aPlayers[Player].Score := Score;
+    Inc(aPlayers[Player].ScoreDisplayed, Diff);
+  end;
+end;
+
 {**
  * spawns a new line bonus popup for the player
  *}
-procedure TSingScores.SpawnPopUp(const PlayerIndex: byte; const Rating: byte; const Score: word);
+procedure TSingScores.SpawnPopUp(const PlayerIndex: byte; const Rating: integer; const Score: integer);
 var
   Cur: PScorePopUp;
 begin
@@ -414,10 +461,12 @@ begin
     Cur.Player    := PlayerIndex;
     Cur.TimeStamp := SDL_GetTicks;
 
-    // limit rating value to 8
+    // limit rating value to 0..8
     // a higher value would cause a crash when selecting the bg texture
     if (Rating > 8) then
       Cur.Rating := 8
+    else if (Rating < 0) then
+      Cur.Rating := 0
     else
       Cur.Rating := Rating;
 
@@ -512,6 +561,27 @@ begin
   LastPopUp := nil;
 end;
 
+{**
+ * calculate the amount of points for a player that is
+ * still in popups and therfore not displayed
+ *}
+function TSingScores.GetPopUpPoints(const Index: integer): integer;
+  var
+    CurPopUp: PScorePopUp;
+begin
+  Result := 0;
+  
+  CurPopUp := FirstPopUp;
+  while (CurPopUp <> nil) do
+  begin
+    if (CurPopUp.Player = Index) then
+    begin // add points left "in" popup to result
+      Inc(Result, CurPopUp.ScoreDiff - CurPopUp.ScoreGiven);
+    end;
+    CurPopUp := CurPopUp.Next;
+  end;
+end;
+
 {**
  * has to be called after positions and players have been added, before first call of draw
  * it gives each player a score position
@@ -532,7 +602,7 @@ var
 
     for I := 0 to PositionCount - 1 do
     begin
-      if ((Positions[I].PlayerCount and bPlayerCount) <> 0) then
+      if ((aPositions[I].PlayerCount and bPlayerCount) <> 0) then
         Inc(Result);
     end;
   end;
@@ -546,7 +616,7 @@ var
 
     for I := 0 to PositionCount - 1 do
     begin
-      if ((Positions[I].PlayerCount and bPlayerCount) <> 0) then
+      if ((aPositions[I].PlayerCount and bPlayerCount) <> 0) then
       begin
         if (bPlayer = 0) then
         begin
@@ -614,6 +684,8 @@ var
   CurPopUp, LastPopUp: PScorePopUp;
 begin
   CurTime := SDL_GetTicks;
+  if (TimePassed <> 0) then
+    TimePassed := CurTime - TimePassed;
 
   if Visible then
   begin
@@ -644,6 +716,7 @@ begin
       // draw players w/ rating bar
       for I := 0 to PlayerCount-1 do
       begin
+        DoRaiseScore(I);
         DrawScore(I);
         DrawRatingBar(I);
       end
@@ -651,10 +724,42 @@ begin
       // draw players w/o rating bar
       for I := 0 to PlayerCount-1 do
       begin
+        DoRaiseScore(I);
         DrawScore(I);
       end;
 
   end; // eo visible
+
+  TimePassed := CurTime;
+end;
+
+{**
+ * raises players score if RaiseScore was called
+ * has to be called after DrawPopUp and before
+ * DrawScore
+ *}
+procedure TSingScores.DoRaiseScore(const Index: integer);
+  var
+    S: integer;
+    Diff: integer;
+  const
+    RaisePerSecond = 500;
+begin
+  S := (Players[Index].Score - (Players[Index].ScoreDisplayed + GetPopUpPoints(Index)));
+
+  if (S <> 0) then
+  begin
+    Diff := Round(RoundTo((RaisePerSecond * TimePassed) / 1000, 1));
+
+    { minimal raise per frame = 1 }
+    if Abs(Diff) < 1 then
+      Diff := Sign(S);
+
+    if (Abs(Diff) < Abs(S)) then
+      Inc(aPlayers[Index].ScoreDisplayed, Diff)
+    else
+      Inc(aPlayers[Index].ScoreDisplayed, S);
+  end;
 end;
 
 {**
@@ -701,13 +806,13 @@ begin
           Progress := TimeDiff / Settings.Phase1Time;
 
 
-          W := Positions[PIndex].PUW * Sin(Progress/2*Pi);
-          H := Positions[PIndex].PUH * Sin(Progress/2*Pi);
+          W := aPositions[PIndex].PUW * Sin(Progress/2*Pi);
+          H := aPositions[PIndex].PUH * Sin(Progress/2*Pi);
 
-          X := Positions[PIndex].PUStartX + (Positions[PIndex].PUW - W)/2;
-          Y := Positions[PIndex].PUStartY + (Positions[PIndex].PUH - H)/2;
+          X := aPositions[PIndex].PUStartX + (aPositions[PIndex].PUW - W)/2;
+          Y := aPositions[PIndex].PUStartY + (aPositions[PIndex].PUH - H)/2;
 
-          FontSize   := Round(Progress * Positions[PIndex].PUFontSize);
+          FontSize   := Round(Progress * aPositions[PIndex].PUFontSize);
           FontOffset := (H - FontSize) / 2;
           Alpha := 1;
         end
@@ -717,20 +822,20 @@ begin
           // phase 2 - the moving
           Progress := (TimeDiff - Settings.Phase1Time) / Settings.Phase2Time;
 
-          W := Positions[PIndex].PUW;
-          H := Positions[PIndex].PUH;
+          W := aPositions[PIndex].PUW;
+          H := aPositions[PIndex].PUH;
 
-          PosDiff := Positions[PIndex].PUTargetX - Positions[PIndex].PUStartX;
+          PosDiff := aPositions[PIndex].PUTargetX - aPositions[PIndex].PUStartX;
           if PosDiff > 0 then
             PosDiff := PosDiff + W;
-          X := Positions[PIndex].PUStartX + PosDiff * sqr(Progress);
+          X := aPositions[PIndex].PUStartX + PosDiff * sqr(Progress);
 
-          PosDiff := Positions[PIndex].PUTargetY - Positions[PIndex].PUStartY;
+          PosDiff := aPositions[PIndex].PUTargetY - aPositions[PIndex].PUStartY;
           if PosDiff < 0 then
-            PosDiff := PosDiff + Positions[PIndex].BGH;
-          Y := Positions[PIndex].PUStartY + PosDiff * sqr(Progress);
+            PosDiff := PosDiff + aPositions[PIndex].BGH;
+          Y := aPositions[PIndex].PUStartY + PosDiff * sqr(Progress);
 
-          FontSize   := Positions[PIndex].PUFontSize;
+          FontSize   := aPositions[PIndex].PUFontSize;
           FontOffset := (H - FontSize) / 2;
           Alpha := 1 - 0.3 * Progress;
         end
@@ -763,24 +868,24 @@ begin
             // set positions etc.
             Alpha := 0.7 - 0.7 * Progress;
 
-            W := Positions[PIndex].PUW;
-            H := Positions[PIndex].PUH;
+            W := aPositions[PIndex].PUW;
+            H := aPositions[PIndex].PUH;
 
-            PosDiff := Positions[PIndex].PUTargetX - Positions[PIndex].PUStartX;
+            PosDiff := aPositions[PIndex].PUTargetX - aPositions[PIndex].PUStartX;
             if (PosDiff > 0) then
               PosDiff := W
             else
               PosDiff := 0;
-            X := Positions[PIndex].PUTargetX + PosDiff * Progress;
+            X := aPositions[PIndex].PUTargetX + PosDiff * Progress;
 
-            PosDiff := Positions[PIndex].PUTargetY - Positions[PIndex].PUStartY;
+            PosDiff := aPositions[PIndex].PUTargetY - aPositions[PIndex].PUStartY;
             if (PosDiff < 0) then
-              PosDiff := -Positions[PIndex].BGH
+              PosDiff := -aPositions[PIndex].BGH
             else
               PosDiff := 0;
-            Y := Positions[PIndex].PUTargetY - PosDiff * (1 - Progress);
+            Y := aPositions[PIndex].PUTargetY - PosDiff * (1 - Progress);
 
-            FontSize   := Positions[PIndex].PUFontSize;
+            FontSize   := aPositions[PIndex].PUFontSize;
             FontOffset := (H - FontSize) / 2;
           end
           else
@@ -817,7 +922,7 @@ begin
           glDisable(GL_BLEND);
 
           // set font style and size
-          SetFontStyle(Positions[PIndex].PUFont);
+          SetFontStyle(aPositions[PIndex].PUFont);
           SetFontItalic(false);
           SetFontSize(FontSize);
           SetFontReflection(false, 0);
@@ -853,7 +958,7 @@ begin
     // only draw if player is on cur screen
     if (((Players[Index].Position and 128) = 0) = (ScreenAct = 1)) and Players[Index].Visible then
     begin
-      Position := @Positions[Players[Index].Position and 127];
+      Position := @aPositions[Players[Index].Position and 127];
 
       // draw scorebg
       glEnable(GL_TEXTURE_2D);
@@ -905,7 +1010,7 @@ begin
         Players[index].RBVisible and
         Players[index].Visible) then
     begin
-      Position := @Positions[Players[Index].Position and 127];
+      Position := @aPositions[Players[Index].Position and 127];
 
       if (Enabled and Players[Index].Enabled) then
       begin
diff --git a/cmake/src/base/USkins.pas b/cmake/src/base/USkins.pas
index a4722d95..a909b081 100644
--- a/cmake/src/base/USkins.pas
+++ b/cmake/src/base/USkins.pas
@@ -33,32 +33,42 @@ interface
 
 {$I switches.inc}
 
+uses
+  UPath,
+  UCommon;
+
 type
   TSkinTexture = record
     Name:     string;
-    FileName: string;
+    FileName: IPath;
   end;
 
   TSkinEntry = record
     Theme:    string;
     Name:     string;
-    Path:     string;
-    FileName: string;
+    Path:     IPath;
+    FileName: IPath;
+
+    DefaultColor: integer;
     Creator:  string; // not used yet
   end;
 
   TSkin = class
     Skin:        array of TSkinEntry;
     SkinTexture: array of TSkinTexture;
-    SkinPath:    string;
+    SkinPath:    IPath;
     Color:       integer;
     constructor Create;
     procedure LoadList;
-    procedure ParseDir(Dir: string);
-    procedure LoadHeader(FileName: string);
+    procedure ParseDir(Dir: IPath);
+    procedure LoadHeader(FileName: IPath);
     procedure LoadSkin(Name: string);
-    function GetTextureFileName(TextureName: string): string;
+    function GetTextureFileName(TextureName: string): IPath;
     function GetSkinNumber(Name: string): integer;
+    function GetDefaultColor(SkinNo: integer): integer;
+
+    procedure GetSkinsByTheme(Theme: string; out Skins: TUTF8StringDynArray);
+
     procedure onThemeChange;
   end;
 
@@ -71,10 +81,12 @@ uses
   IniFiles,
   Classes,
   SysUtils,
+  Math,
   UIni,
   ULog,
   UMain,
-  UPath;
+  UPathUtils,
+  UFileSystem;
 
 constructor TSkin.Create;
 begin
@@ -86,48 +98,47 @@ end;
 
 procedure TSkin.LoadList;
 var
-  SR: TSearchRec;
+  Iter: IFileIterator;
+  DirInfo: TFileInfo;
 begin
-  if FindFirst(SkinsPath+'*', faDirectory, SR) = 0 then
+  Iter := FileSystem.FileFind(SkinsPath.Append('*'), faDirectory);
+  while Iter.HasNext do
   begin
-    repeat
-      if (SR.Name <> '.') and (SR.Name <> '..') then
-        ParseDir(SkinsPath + SR.Name + PathDelim);
-    until FindNext(SR) <> 0;
-  end; // if
-  FindClose(SR);
+    DirInfo := Iter.Next();
+    if (not DirInfo.Name.Equals('.')) and (not DirInfo.Name.Equals('..')) then
+      ParseDir(SkinsPath.Append(DirInfo.Name, pdAppend));
+  end;
 end;
 
-procedure TSkin.ParseDir(Dir: string);
+procedure TSkin.ParseDir(Dir: IPath);
 var
-  SR: TSearchRec;
+  Iter: IFileIterator;
+  IniInfo: TFileInfo;
 begin
-  if FindFirst(Dir + '*.ini', faAnyFile, SR) = 0 then
+  Iter := FileSystem.FileFind(Dir.Append('*.ini'), 0);
+  while Iter.HasNext do
   begin
-    repeat
-
-      if (SR.Name <> '.') and (SR.Name <> '..') then
-        LoadHeader(Dir + SR.Name);
-        
-    until FindNext(SR) <> 0;
+    IniInfo := Iter.Next;
+    LoadHeader(Dir.Append(IniInfo.Name));
   end;
 end;
 
-procedure TSkin.LoadHeader(FileName: string);
+procedure TSkin.LoadHeader(FileName: IPath);
 var
   SkinIni: TMemIniFile;
   S:       integer;
 begin
-  SkinIni := TMemIniFile.Create(FileName);
+  SkinIni := TMemIniFile.Create(FileName.ToNative);
 
   S := Length(Skin);
   SetLength(Skin, S+1);
   
-  Skin[S].Path     := IncludeTrailingPathDelimiter(ExtractFileDir(FileName));
-  Skin[S].FileName := ExtractFileName(FileName);
+  Skin[S].Path     := FileName.GetPath;
+  Skin[S].FileName := FileName.GetName;
   Skin[S].Theme    := SkinIni.ReadString('Skin', 'Theme', '');
   Skin[S].Name     := SkinIni.ReadString('Skin', 'Name', '');
   Skin[S].Creator  := SkinIni.ReadString('Skin', 'Creator', '');
+  Skin[S].DefaultColor := Max(0, GetArrayIndex(IColor, SkinIni.ReadString('Skin', 'Color', ''), true));
 
   SkinIni.Free;
 end;
@@ -142,7 +153,7 @@ begin
   S        := GetSkinNumber(Name);
   SkinPath := Skin[S].Path;
 
-  SkinIni  := TMemIniFile.Create(SkinPath + Skin[S].FileName);
+  SkinIni  := TMemIniFile.Create(SkinPath.Append(Skin[S].FileName).ToNative);
 
   SL := TStringList.Create;
   SkinIni.ReadSection('Textures', SL);
@@ -151,42 +162,33 @@ begin
   for T := 0 to SL.Count-1 do
   begin
     SkinTexture[T].Name     := SL.Strings[T];
-    SkinTexture[T].FileName := SkinIni.ReadString('Textures', SL.Strings[T], '');
+    SkinTexture[T].FileName := Path(SkinIni.ReadString('Textures', SL.Strings[T], ''));
   end;
 
   SL.Free;
   SkinIni.Free;
 end;
 
-function TSkin.GetTextureFileName(TextureName: string): string;
+function TSkin.GetTextureFileName(TextureName: string): IPath;
 var
   T: integer;
 begin
-  Result := '';
+  Result := PATH_NONE;
   
   for T := 0 to High(SkinTexture) do
   begin
-    if ( SkinTexture[T].Name     = TextureName ) and
-       ( SkinTexture[T].FileName <> ''         ) then
+    if (SkinTexture[T].Name = TextureName) and
+       (SkinTexture[T].FileName.IsSet) then
     begin
-      Result := SkinPath + SkinTexture[T].FileName;
+      Result := SkinPath.Append(SkinTexture[T].FileName);
     end;
   end;
 
-  if ( TextureName <> '' ) and
-     ( Result      <> '' ) then
+  if (TextureName <> '') and (Result.IsSet) then
   begin
     //Log.LogError('', '-----------------------------------------');
     //Log.LogError(TextureName+' - '+ Result, 'TSkin.GetTextureFileName');
   end;
-
-{  Result := SkinPath + 'Bar.jpg';
-  if TextureName = 'Ball' then
-    Result := SkinPath + 'Ball.bmp';
-  if Copy(TextureName, 1, 4) = 'Gray' then
-    Result := SkinPath + 'Ball.bmp';
-  if Copy(TextureName, 1, 6) = 'NoteBG' then
-    Result := SkinPath + 'Ball.bmp';}
 end;
 
 function TSkin.GetSkinNumber(Name: string): integer;
@@ -195,25 +197,52 @@ var
 begin
   Result := 0; // set default to the first available skin
   for S := 0 to High(Skin) do
-    if Skin[S].Name = Name then
+    if CompareText(Skin[S].Name, Name) = 0 then
       Result := S;
 end;
 
-procedure TSkin.onThemeChange;
-var
-  S:    integer;
-  Name: String;
+procedure TSkin.GetSkinsByTheme(Theme: string; out Skins: TUTF8StringDynArray);
+  var
+    I: Integer;
+    Len: integer;
 begin
-  Ini.SkinNo:=0;
-  SetLength(ISkin, 0);
-  Name := Uppercase(ITheme[Ini.Theme]);
-  for S := 0 to High(Skin) do
-    if Name = Uppercase(Skin[S].Theme) then
+  SetLength(Skins, 0);
+  Len := 0;
+
+  for I := 0 to High(Skin) do
+    if CompareText(Theme, Skin[I].Theme) = 0 then
     begin
-      SetLength(ISkin, Length(ISkin)+1);
-      ISkin[High(ISkin)] := Skin[S].Name;
+      SetLength(Skins, Len + 1);
+      Skins[Len] := Skin[I].Name;
+      Inc(Len);
     end;
+end;
+
+{ returns number of default color for skin with
+  index SkinNo in ISkin (not in the actual skin array) }
+function TSkin.GetDefaultColor(SkinNo: integer): integer;
+  var
+    I: Integer;
+begin
+  Result := 0;
 
+  for I := 0 to High(Skin) do
+    if CompareText(ITheme[Ini.Theme], Skin[I].Theme) = 0 then
+    begin
+      if SkinNo > 0 then
+        Dec(SkinNo)
+      else
+      begin
+        Result := Skin[I].DefaultColor;
+        Break;
+      end;
+    end;
+end;
+
+procedure TSkin.onThemeChange;
+begin
+  Ini.SkinNo:=0;
+  GetSkinsByTheme(ITheme[Ini.Theme], ISkin);
 end;
 
 end.
diff --git a/cmake/src/base/USong.pas b/cmake/src/base/USong.pas
index 57f78a27..a441fe40 100644
--- a/cmake/src/base/USong.pas
+++ b/cmake/src/base/USong.pas
@@ -56,7 +56,11 @@ uses
     PseudoThread,
   {$ENDIF}
   UCatCovers,
-  UXMLSong;
+  UXMLSong,
+  UUnicodeUtils,
+  UTextEncoding,
+  UFilesystem,
+  UPath;
 
 type
 
@@ -68,42 +72,62 @@ type
   end;
 
   TScore = record
-    Name:       WideString;
+    Name:       UTF8String;
     Score:      integer;
-    Length:     string;
+    Date:       UTF8String;
+  end;
+
+  { used to hold header tags that are not supported by this version of
+    usdx (e.g. some tags from ultrastar 0.7.0) when songs are loaded in
+    songeditor. They will be written the end of the song header }
+  TCustomHeaderTag = record
+    Tag: UTF8String;
+    Content: UTF8String;
   end;
 
   TSong = class
+  private
     FileLineNo  : integer;  // line, which is read last, for error reporting
 
-    procedure ParseNote(LineNumber: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: string);
+    function DecodeFilename(Filename: RawByteString): IPath;
+    procedure ParseNote(LineNumber: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: UTF8String);
     procedure NewSentence(LineNumberP: integer; Param1, Param2: integer);
 
-    function ReadTXTHeader( const aFileName : WideString ): boolean;
-    function ReadXMLHeader( const aFileName : WideString ): boolean;
+    function ParseLyricStringParam(const Line: RawByteString; var LinePos: integer): RawByteString;
+    function ParseLyricIntParam(const Line: RawByteString; var LinePos: integer): integer;
+    function ParseLyricFloatParam(const Line: RawByteString; var LinePos: integer): extended;
+    function ParseLyricCharParam(const Line: RawByteString; var LinePos: integer): AnsiChar;
+    function ParseLyricText(const Line: RawByteString; var LinePos: integer): RawByteString;
+
+    function ReadTXTHeader(SongFile: TTextFileStream; ReadCustomTags: Boolean): boolean;
+    function ReadXMLHeader(const aFileName: IPath): boolean;
+
+    function GetFolderCategory(const aFileName: IPath): UTF8String;
+    function FindSongFile(Dir: IPath; Mask: UTF8String): IPath;
   public
-    Path:       WideString;
-    Folder:     WideString; // for sorting by folder
-    fFileName,
-    FileName:   WideString;
+    Path:         IPath; // kust path component of file (only set if file was found)
+    Folder:       UTF8String; // for sorting by folder (only set if file was found)
+    FileName:     IPath; // just name component of file (only set if file was found)
+
+    // filenames
+    Cover:      IPath;
+    Mp3:        IPath;
+    Background: IPath;
+    Video:      IPath;
 
     // sorting methods
-    //Category:   array of WideString; // TODO: do we need this?
-    Genre:      WideString;
-    Edition:    WideString;
-    Language:   WideString;
+    Genre:      UTF8String;
+    Edition:    UTF8String;
+    Language:   UTF8String;
+    Year:       Integer;
 
-    Title:      WideString;
-    Artist:     WideString;
+    Title:      UTF8String;
+    Artist:     UTF8String;
 
-    Text:       WideString;
-    Creator:    WideString;
+    Creator:    UTF8String;
 
-    Cover:      WideString;
     CoverTex:   TTexture;
-    Mp3:        WideString;
-    Background: WideString;
-    Video:      WideString;
+
     VideoGAP:   real;
     NotesGAP:   integer;
     Start:      real; // in seconds
@@ -113,6 +137,10 @@ type
     BPM:        array of TBPM;
     GAP:        real; // in miliseconds
 
+    Encoding:   TEncoding;
+
+    CustomTags: array of TCustomHeaderTag;
+
     Score:      array[0..2] of array of TScore;
 
     // these are used when sorting is enabled
@@ -122,23 +150,21 @@ type
     OrderTyp:   integer; // type of sorting for this button (0=name)
     CatNumber:  integer; // Count of Songs in Category for Cats and Number of Song in Category for Songs
 
-    SongFile: TextFile;   // all procedures in this unit operate on this file
-
     Base    : array[0..1] of integer;
     Rel     : array[0..1] of integer;
     Mult    : integer;
     MultBPM : integer;
 
-    LastError: String;
+    LastError: AnsiString;
     function  GetErrorLineNo: integer;
     property  ErrorLineNo: integer read GetErrorLineNo;
 
 
-    constructor Create  (); overload;
-    constructor Create  ( const aFileName : WideString ); overload;
+    constructor Create(); overload;
+    constructor Create(const aFileName : IPath); overload;
     function    LoadSong: boolean;
     function    LoadXMLSong: boolean;
-    function    Analyse(): boolean;
+    function    Analyse(const ReadCustomTags: Boolean = false): boolean;
     function    AnalyseXML(): boolean;
     procedure   Clear();
   end;
@@ -149,67 +175,82 @@ uses
   StrUtils,
   TextGL,
   UIni,
-  UPath,
+  UPathUtils,
   UMusic,  //needed for Lines
   UNote;   //needed for Player
 
+const
+  DEFAULT_ENCODING = encAuto;
+
 constructor TSong.Create();
 begin
   inherited;
+
+  // to-do : special create for category "songs"
+  //dirty fix to fix folders=on
+  Self.Path     := PATH_NONE();
+  Self.FileName := PATH_NONE();
+  Self.Cover    := PATH_NONE();
+  Self.Mp3      := PATH_NONE();
+  Self.Background:= PATH_NONE();
+  Self.Video    := PATH_NONE();
 end;
 
-constructor TSong.Create( const aFileName: WideString );
-  // This may be changed, when we rewrite song select code.
-  // it is some kind of dirty, but imho the best possible
-  // solution as we do atm not support nested categorys.
-  // it works like the folder sorting in 1.0.1a
-  // folder is set to the first folder under the songdir
-  // so songs ~/.ultrastardx/songs/punk is in the same
-  // category as songs in shared/ultrastardx/songs are.
-  // note: folder is just the name of a category it has
-  //       nothing to do with the path used for file loading
-  function GetFolderCategory: WideString;
-    var
-      I: Integer;
-      P: Integer; //position of next path delimiter
-  begin
-    Result := 'Unknown'; //default folder category, if we can't locate the song dir
+// This may be changed, when we rewrite song select code.
+// it is some kind of dirty, but imho the best possible
+// solution as we do atm not support nested categorys.
+// it works like the folder sorting in 1.0.1a
+// folder is set to the first folder under the songdir
+// so songs ~/.ultrastardx/songs/punk is in the same
+// category as songs in shared/ultrastardx/songs are.
+// note: folder is just the name of a category it has
+//       nothing to do with the path used for file loading
+function TSong.GetFolderCategory(const aFileName: IPath): UTF8String;
+var
+  I: Integer;
+  CurSongPath: IPath;
+  CurSongPathRel: IPath;
+begin
+  Result := 'Unknown'; //default folder category, if we can't locate the song dir
 
-    for I := 0 to SongPaths.Count-1 do
-      if (AnsiStartsText(SongPaths.Strings[I], aFilename)) then
+  for I := 0 to SongPaths.Count-1 do
+  begin
+    CurSongPath := SongPaths[I] as IPath;
+    if (aFileName.IsChildOf(CurSongPath, false)) then
+    begin
+      if (aFileName.IsChildOf(CurSongPath, true)) then
       begin
-        P := PosEx(PathDelim, aFilename, Length(SongPaths.Strings[I]) + 1);
-
-        If (P > 0) then
-        begin
-          // we have found the category name => get it
-          Result := copy(self.Path, Length(SongPaths.Strings[I]) + 1, P - Length(SongPaths.Strings[I]) - 1);
-        end
-        else
-        begin
-          // songs are in the "root" of the songdir => use songdir for the categorys name 
-          Result := SongPaths.Strings[I];
-        end;
-
-        Exit;
+        // songs are in the "root" of the songdir => use songdir for the categorys name
+        Result := CurSongPath.RemovePathDelim.ToUTF8;
+      end
+      else
+      begin
+        // use the first subdirectory below CurSongPath as the category name
+        CurSongPathRel := aFileName.GetRelativePath(CurSongPath.AppendPathDelim);
+        Result := CurSongPathRel.SplitDirs[0].RemovePathDelim.ToUTF8;
       end;
+      Exit;
+    end;
   end;
+end;
+
+constructor TSong.Create(const aFileName: IPath);
 begin
   inherited Create();
 
   Mult    := 1;
   MultBPM := 4;
-  fFileName := aFileName;
 
   LastError := '';
 
-  if fileexists( aFileName ) then
+  Self.Path     := aFileName.GetPath;
+  Self.FileName := aFileName.GetName;
+  Self.Folder   := GetFolderCategory(aFileName);
+
+  (*
+  if (aFileName.IsFile) then
   begin
-    self.Path     := ExtractFilePath( aFileName );
-    self.Folder   := GetFolderCategory;
-    self.FileName := ExtractFileName( aFileName );
-    (*
-    if ReadTXTHeader( aFileName ) then
+    if ReadTXTHeader(aFileName) then
     begin
       LoadSong();
     end
@@ -218,45 +259,189 @@ begin
       Log.LogError('Error Loading SongHeader, abort Song Loading');
       Exit;
     end;
-    *)
+  end;
+  *)
+end;
+
+function TSong.FindSongFile(Dir: IPath; Mask: UTF8String): IPath;
+var
+  Iter: IFileIterator;
+  FileInfo: TFileInfo;
+  FileName: IPath;
+begin
+  Iter := FileSystem.FileFind(Dir.Append(Mask), faDirectory);
+  if (Iter.HasNext) then
+    Result := Iter.Next.Name
+  else
+    Result := PATH_NONE;
+end;
+
+function TSong.DecodeFilename(Filename: RawByteString): IPath;
+begin
+  Result := UPath.Path(DecodeStringUTF8(Filename, Encoding));
+end;
+
+type
+  EUSDXParseException = class(Exception);
+
+{**
+ * Parses the Line string starting from LinePos for a parameter.
+ * Leading whitespace is trimmed, same applies to the first trailing whitespace.
+ * After the call LinePos will point to the position after the first trailing
+ * whitespace.
+ *
+ * Raises an EUSDXParseException if no string was found.
+ *
+ * Example:
+ *   ParseLyricParam(Line:'Param0  Param1 Param2', LinePos:8, ...)
+ *   -> Param:'Param1', LinePos:16 (= start of 'Param2')
+ *}
+function TSong.ParseLyricStringParam(const Line: RawByteString; var LinePos: integer): RawByteString;
+var
+  Start: integer;
+  OldLinePos: integer;
+const
+  Whitespace = [#9, ' '];
+begin
+  OldLinePos := LinePos;
+
+  Start := 0;
+  while (LinePos <= Length(Line)) do
+  begin
+    if (Line[LinePos] in Whitespace) then
+    begin
+      // check for end of param
+      if (Start > 0) then
+        Break;
+    end
+    // check for beginning of param
+    else if (Start = 0) then
+    begin
+      Start := LinePos;
+    end;
+    Inc(LinePos);
+  end;
+
+  // check if param was found
+  if (Start = 0) then
+  begin
+    LinePos := OldLinePos;
+    raise EUSDXParseException.Create('String expected');
+  end
+  else
+  begin
+    // copy param without trailing whitespace
+    Result := Copy(Line, Start, LinePos-Start);
+    // skip first trailing whitespace (if not at EOL)
+    if (LinePos <= Length(Line)) then
+      Inc(LinePos);
+  end;
+end;
+
+function TSong.ParseLyricIntParam(const Line: RawByteString; var LinePos: integer): integer;
+var
+  Str: RawByteString;
+  OldLinePos: integer;
+begin
+  OldLinePos := LinePos;
+  Str := ParseLyricStringParam(Line, LinePos);
+
+  if not TryStrToInt(Str, Result) then
+  begin // on convert error
+    Result := 0;
+    LinePos := OldLinePos;
+    raise EUSDXParseException.Create('Integer expected');
+  end;
+end;
+
+function TSong.ParseLyricFloatParam(const Line: RawByteString; var LinePos: integer): extended;
+var
+  Str: RawByteString;
+  OldLinePos: integer;
+begin
+  OldLinePos := LinePos;
+  Str := ParseLyricStringParam(Line, LinePos);
+
+  if not TryStrToFloat(Str, Result) then
+  begin // on convert error
+    Result := 0;
+    LinePos := OldLinePos;
+    raise EUSDXParseException.Create('Float expected');
   end;
 end;
 
-{function TSong.LoadSong(): boolean;
+function TSong.ParseLyricCharParam(const Line: RawByteString; var LinePos: integer): AnsiChar;
+var
+  Str: RawByteString;
+  OldLinePos: integer;
 begin
+  OldLinePos := LinePos;
+  Str := ParseLyricStringParam(Line, LinePos);
+  
+  if (Length(Str) < 1) then
+  begin
+    LinePos := OldLinePos;
+    raise EUSDXParseException.Create('Character expected');
+  end
+  else if (Length(Str) > 1) then
+  begin
+    Log.LogWarn(Format('"%s" in line %d: %s',
+        [FileName.ToNative, FileLineNo, 'character expected but found "' + Str + '"']),
+        'TSong.ParseLyricCharParam');
+  end;
+       
+  LinePos := OldLinePos + 1;
+  Result := Str[1];
+end;
 
-end;   }
+{**
+ * Returns the rest of the line from LinePos as lyric text.
+ * Leading and trailing whitespace is not trimmed.
+ *}
+function TSong.ParseLyricText(const Line: RawByteString; var LinePos: integer): RawByteString;
+begin
+  if (LinePos > Length(Line)) then
+    Result := ''
+  else
+  begin
+    Result := Copy(Line, LinePos, Length(Line)-LinePos+1);
+    LinePos := Length(Line)+1;
+  end;
+end;
 
 //Load TXT Song
 function TSong.LoadSong(): boolean;
-
 var
-  TempC:    char;
-  Text:     string;
-  CP:       integer; // Current Player (0 or 1)
+  CurLine: RawByteString;
+  LinePos:  integer;
   Count:    integer;
   Both:     boolean;
-  Param1:   integer;
-  Param2:   integer;
-  Param3:   integer;
-  ParamS:   string;
-  I:        integer;
 
+  Param0:    AnsiChar;
+  Param1:    integer;
+  Param2:    integer;
+  Param3:    integer;
+  ParamLyric: UTF8String;
+
+  I:        integer;
+  NotesFound: boolean;
+  SongFile: TTextFileStream;
+  FileNamePath: IPath;
 begin
   Result := false;
   LastError := '';
 
-  if not FileExists(Path + PathDelim + FileName) then
+  FileNamePath := Path.Append(FileName);
+  if not FileNamePath.IsFile() then
   begin
     LastError := 'ERROR_CORRUPT_SONG_FILE_NOT_FOUND';
-    Log.LogError('File not found: "' + Path + PathDelim + FileName + '"', 'TSong.LoadSong()');
-    exit;
+    Log.LogError('File not found: "' + FileNamePath.ToNative + '"', 'TSong.LoadSong()');
+    Exit;
   end;
 
   MultBPM           := 4; // multiply beat-count of note by 4
   Mult              := 1; // accuracy of measurement of note
   Rel[0]            := 0;
-  CP                := 0;
   Both              := false;
 
   if Length(Player) = 2 then
@@ -264,156 +449,157 @@ begin
 
   try
     // Open song file for reading.....
-    FileMode := fmOpenRead;
-    AssignFile(SongFile, fFileName);
-    Reset(SongFile);
-
-    //Clear old Song Header
-    if (self.Path = '') then
-      self.Path := ExtractFilePath(FileName);
-
-    if (self.FileName = '') then
-      self.Filename := ExtractFileName(FileName);
-
-    FileLineNo := 0;
-    //Search for Note Begining
-    repeat
-      ReadLn(SongFile, Text);
-      Inc(FileLineNo);
+    SongFile := TMemTextFileStream.Create(FileNamePath, fmOpenRead);
+    try
+      //Search for Note Beginning
+      FileLineNo := 0;
+      NotesFound := false;
+      while (SongFile.ReadLine(CurLine)) do
+      begin
+        Inc(FileLineNo);
+        if (Length(CurLine) > 0) and (CurLine[1] in [':', 'F', '*']) then
+        begin
+          NotesFound := true;
+          Break;
+        end;
+      end;
 
-      if (EoF(SongFile)) then
+      if (not NotesFound) then
       begin //Song File Corrupted - No Notes
-        CloseFile(SongFile);
-        Log.LogError('Could not load txt File, no Notes found: ' + FileName);
+        Log.LogError('Could not load txt File, no notes found: ' + FileNamePath.ToNative);
         LastError := 'ERROR_CORRUPT_SONG_NO_NOTES';
         Exit;
       end;
-      Read(SongFile, TempC);
-    until ((TempC = ':') or (TempC = 'F') or (TempC = '*'));
 
-    SetLength(Lines, 2);
-    for Count := 0 to High(Lines) do
-    begin
-      Lines[Count].High := 0;
-      Lines[Count].Number := 1;
-      Lines[Count].Current := 0;
-      Lines[Count].Resolution := self.Resolution;
-      Lines[Count].NotesGAP   := self.NotesGAP;
-      Lines[Count].ScoreValue := 0;
-
-      //Add first line and set some standard values to fields
-      //see procedure NewSentence for further explantation
-      //concerning most of these values
-      SetLength(Lines[Count].Line, 1);
-      Lines[Count].Line[0].HighNote := -1;
-      Lines[Count].Line[0].LastLine := false;
-      Lines[Count].Line[0].BaseNote := High(Integer);
-      Lines[Count].Line[0].TotalNotes := 0;
-    end;
-
-    while (TempC <> 'E') and (not EOF(SongFile)) do
-    begin
-
-      if (TempC = ':') or (TempC = '*') or (TempC = 'F') then
-      begin
-        // read notes
-        Read(SongFile, Param1);
-        Read(SongFile, Param2);
-        Read(SongFile, Param3);
-        Read(SongFile, ParamS);
-
-        //Check for ZeroNote
-        if Param2 = 0 then
-          Log.LogError('Found ZeroNote at "'+TempC+' '+IntToStr(Param1)+' '+IntToStr(Param2)+' '+IntToStr(Param3)+ParamS+'" -> Note ignored!')
-        else
-        begin
-         // add notes
-         if not Both then
-           // P1
-           ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS)
-         else
-         begin
-           // P1 + P2
-           ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS);
-           ParseNote(1, TempC, (Param1+Rel[1]) * Mult, Param2 * Mult, Param3, ParamS);
-         end;
-        end; //Zeronote check
-      end // if
-
-      else if TempC = '-' then
+      SetLength(Lines, 2);
+      for Count := 0 to High(Lines) do
       begin
-        // reads sentence
-        Read(SongFile, Param1);
-        if self.Relative then
-          Read(SongFile, Param2); // read one more data for relative system
-
-        // new sentence
-        if not Both then
-          // P1
-          NewSentence(0, (Param1 + Rel[0]) * Mult, Param2)
-        else
-        begin
-          // P1 + P2
-          NewSentence(0, (Param1 + Rel[0]) * Mult, Param2);
-          NewSentence(1, (Param1 + Rel[1]) * Mult, Param2);
-        end;
-      end // if
+        Lines[Count].High := 0;
+        Lines[Count].Number := 1;
+        Lines[Count].Current := 0;
+        Lines[Count].Resolution := self.Resolution;
+        Lines[Count].NotesGAP   := self.NotesGAP;
+        Lines[Count].ScoreValue := 0;
+
+        //Add first line and set some standard values to fields
+        //see procedure NewSentence for further explantation
+        //concerning most of these values
+        SetLength(Lines[Count].Line, 1);
+        Lines[Count].Line[0].HighNote := -1;
+        Lines[Count].Line[0].LastLine := false;
+        Lines[Count].Line[0].BaseNote := High(Integer);
+        Lines[Count].Line[0].TotalNotes := 0;
+      end;
 
-      else if TempC = 'B' then
+      while true do
       begin
-        SetLength(self.BPM, Length(self.BPM) + 1);
-        Read(SongFile, self.BPM[High(self.BPM)].StartBeat);
-        self.BPM[High(self.BPM)].StartBeat := self.BPM[High(self.BPM)].StartBeat + Rel[0];
+        LinePos := 1;
 
-        Read(SongFile, Text);
-        self.BPM[High(self.BPM)].BPM := StrToFloat(Text);
-        self.BPM[High(self.BPM)].BPM := self.BPM[High(self.BPM)].BPM * Mult * MultBPM;
-      end;
+        Param0 := ParseLyricCharParam(CurLine, LinePos);
+        if (Param0 = 'E') then
+        begin
+          Break
+        end
+        else if (Param0 in [':', '*', 'F']) then
+        begin
+          // read notes
+          Param1 := ParseLyricIntParam(CurLine, LinePos);
+          Param2 := ParseLyricIntParam(CurLine, LinePos);
+          Param3 := ParseLyricIntParam(CurLine, LinePos);
+          ParamLyric := ParseLyricText(CurLine, LinePos);
+
+          //Check for ZeroNote
+          if Param2 = 0 then
+            Log.LogWarn(Format('"%s" in line %d: %s',
+                  [FileNamePath.ToNative, FileLineNo, 'found note with length zero -> note ignored']), 'TSong.LoadSong')
+            //Log.LogError('Found zero-length note at "'+Param0+' '+IntToStr(Param1)+' '+IntToStr(Param2)+' '+IntToStr(Param3)+ParamLyric+'" -> Note ignored!')
+          else
+          begin
+           // add notes
+           if not Both then
+             // P1
+             ParseNote(0, Param0, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamLyric)
+           else
+           begin
+             // P1 + P2
+             ParseNote(0, Param0, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamLyric);
+             ParseNote(1, Param0, (Param1+Rel[1]) * Mult, Param2 * Mult, Param3, ParamLyric);
+           end;
+          end; //Zeronote check
+        end // if
+
+        else if Param0 = '-' then
+        begin
+          // reads sentence
+          Param1 := ParseLyricIntParam(CurLine, LinePos);
+          if self.Relative then
+            Param2 := ParseLyricIntParam(CurLine, LinePos); // read one more data for relative system
+
+          // new sentence
+          if not Both then
+            // P1
+            NewSentence(0, (Param1 + Rel[0]) * Mult, Param2)
+          else
+          begin
+            // P1 + P2
+            NewSentence(0, (Param1 + Rel[0]) * Mult, Param2);
+            NewSentence(1, (Param1 + Rel[1]) * Mult, Param2);
+          end;
+        end // if
 
-      ReadLn(SongFile); //Jump to next line in File, otherwise the next Read would catch the linebreak(e.g. #13 #10 on win32)
+        else if Param0 = 'B' then
+        begin
+          SetLength(self.BPM, Length(self.BPM) + 1);
+          self.BPM[High(self.BPM)].StartBeat := ParseLyricFloatParam(CurLine, LinePos);
+          self.BPM[High(self.BPM)].StartBeat := self.BPM[High(self.BPM)].StartBeat + Rel[0];
 
-      Read(SongFile, TempC);
-      Inc(FileLineNo);
-    end; // while}
+          self.BPM[High(self.BPM)].BPM := ParseLyricFloatParam(CurLine, LinePos);
+          self.BPM[High(self.BPM)].BPM := self.BPM[High(self.BPM)].BPM * Mult * MultBPM;
+        end;
 
-    CloseFile(SongFile);
+        // Read next line in File
+        if (not SongFile.ReadLine(CurLine)) then
+          Break;
 
-    for I := 0 to High(Lines) do
+        Inc(FileLineNo);
+      end; // while
+    finally
+      SongFile.Free;
+    end;
+  except
+    on E: Exception do
     begin
-      if ((Both) or (I = 0)) then
-      begin
-        if (Length(Lines[I].Line) < 2) then
-        begin
-          LastError := 'ERROR_CORRUPT_SONG_NO_BREAKS';
-          Log.LogError('Error Loading File, Can''t find any Linebreaks: "' + fFileName + '"');
-          exit;
-        end;
-
-        if (Lines[I].Line[Lines[I].High].HighNote < 0) then
-        begin
-          SetLength(Lines[I].Line, Lines[I].Number - 1);
-          Lines[I].High := Lines[I].High - 1;
-          Lines[I].Number := Lines[I].Number - 1;
-          Log.LogError('Error loading Song, sentence w/o note found in last line before E: ' + Filename);
-        end;
-      end;
+      Log.LogError(Format('Error loading file: "%s" in line %d,%d: %s',
+                  [FileNamePath.ToNative, FileLineNo, LinePos, E.Message]));
+      Exit;
     end;
+  end;
 
-    for Count := 0 to High(Lines) do
+  for I := 0 to High(Lines) do
+  begin
+    if ((Both) or (I = 0)) then
     begin
-      if (High(Lines[Count].Line) >= 0) then
-        Lines[Count].Line[High(Lines[Count].Line)].LastLine := true;
-    end;
-  except
-    try
-      CloseFile(SongFile);
-    except
+      if (Length(Lines[I].Line) < 2) then
+      begin
+        LastError := 'ERROR_CORRUPT_SONG_NO_BREAKS';
+        Log.LogError('Error loading file: Can''t find any linebreaks in "' + FileNamePath.ToNative + '"');
+        exit;
+      end;
 
+      if (Lines[I].Line[Lines[I].High].HighNote < 0) then
+      begin
+        SetLength(Lines[I].Line, Lines[I].Number - 1);
+        Lines[I].High := Lines[I].High - 1;
+        Lines[I].Number := Lines[I].Number - 1;
+        Log.LogError('Error loading Song, sentence w/o note found in last line before E: ' + FileNamePath.ToNative);
+      end;
     end;
+  end;
 
-    LastError := 'ERROR_CORRUPT_SONG_ERROR_IN_LINE';
-    Log.LogError('Error Loading File: "' + fFileName + '" in Line ' + inttostr(FileLineNo));
-    exit;
+  for Count := 0 to High(Lines) do
+  begin
+    if (High(Lines[Count].Line) >= 0) then
+      Lines[Count].Line[High(Lines[Count].Line)].LastLine := true;
   end;
 
   Result := true;
@@ -421,11 +607,7 @@ end;
 
 //Load XML Song
 function TSong.LoadXMLSong(): boolean;
-
 var
-  //TempC:     char;
-  Text:      string;
-  CP:        integer; // Current Player (0 or 1)
   Count:     integer;
   Both:      boolean;
   Param1:    integer;
@@ -438,14 +620,15 @@ var
   NoteType:  char;
   SentenceEnd, Rest, Time: integer;
   Parser: TParser;
-
+  FileNamePath: IPath;
 begin
   Result := false;
   LastError := '';
 
-  if not FileExists(Path + PathDelim + FileName) then
+  FileNamePath := Path.Append(FileName);
+  if not FileNamePath.IsFile() then
   begin
-    Log.LogError('File not found: "' + Path + PathDelim + FileName + '"', 'TSong.LoadSong()');
+    Log.LogError('File not found: "' + FileNamePath.ToNative + '"', 'TSong.LoadSong()');
     exit;
   end;
 
@@ -454,7 +637,6 @@ begin
   Lines[0].ScoreValue := 0;
   self.Relative     := false;
   Rel[0]            := 0;
-  CP                := 0;
   Both              := false;
 
   if Length(Player) = 2 then
@@ -484,7 +666,7 @@ begin
 
   //Try to Parse the Song
 
-  if Parser.ParseSong(Path + PathDelim + FileName) then
+  if Parser.ParseSong(FileNamePath) then
   begin
     //Writeln('XML Inputfile Parsed succesful');
 
@@ -551,7 +733,7 @@ begin
   end
   else
   begin
-    Log.LogError('Could not parse Inputfile: ' + Path + PathDelim + FileName);
+    Log.LogError('Could not parse inputfile: ' + FileNamePath.ToNative);
     exit;
   end;
 
@@ -563,14 +745,11 @@ begin
   Result := true;
 end;
 
-function TSong.ReadXMLHeader(const aFileName : WideString): boolean;
-
+function TSong.ReadXMLHeader(const aFileName : IPath): boolean;
 var
-  //Line, Identifier, Value: string;
-  //Temp        : word;
   Done        : byte;
   Parser      : TParser;
-
+  FileNamePath: IPath;
 begin
   Result := true;
   Done   := 0;
@@ -579,7 +758,8 @@ begin
   Parser := TParser.Create;
   Parser.Settings.DashReplacement := '~';
 
-  if Parser.ParseSong(self.Path + self.FileName) then
+  FileNamePath := Self.Path.Append(Self.FileName);
+  if Parser.ParseSong(FileNamePath) then
   begin
     //-----------
     //Required Attributes
@@ -598,9 +778,9 @@ begin
     Done := Done or 2;
 
     //MP3 File //Test if Exists
-    self.Mp3 := platform.FindSongFile(Path, '*.mp3');
+    Self.Mp3 := FindSongFile(Self.Path, '*.mp3');
     //Add Mp3 Flag to Done
-    if (FileExists(self.Path + self.Mp3)) then
+    if (Self.Path.Append(Self.Mp3).IsFile()) then
       Done := Done or 4;
 
     //Beats per Minute
@@ -621,16 +801,16 @@ begin
     self.GAP := Parser.SongInfo.Header.Gap;
 
     //Cover Picture
-    self.Cover := platform.FindSongFile(Path, '*[CO].jpg');
+    self.Cover := FindSongFile(Path, '*[CO].jpg');
 
     //Background Picture
-    self.Background := platform.FindSongFile(Path, '*[BG].jpg');
+    self.Background := FindSongFile(Path, '*[BG].jpg');
 
     // Video File
     //    self.Video := Value
 
     // Video Gap
-    //  self.VideoGAP := song_StrtoFloat( Value )
+    //  self.VideoGAP := StrtoFloatI18n( Value )
 
     //Genre Sorting
     self.Genre := Parser.SongInfo.Header.Genre;
@@ -645,7 +825,7 @@ begin
     self.Language := Parser.SongInfo.Header.Language;
   end
   else
-    Log.LogError('File Incomplete or not SingStar XML (A): ' + aFileName);
+    Log.LogError('File incomplete or not SingStar XML (A): ' + aFileName.ToNative);
 
   Parser.Free;
 
@@ -654,220 +834,297 @@ begin
   begin
     Result := false;
     if (Done and 8) = 0 then      //No BPM Flag
-      Log.LogError('BPM Tag Missing: ' + self.FileName)
+      Log.LogError('BPM tag missing: ' + self.FileName.ToNative)
     else if (Done and 4) = 0 then //No MP3 Flag
-      Log.LogError('MP3 Tag/File Missing: ' + self.FileName)
+      Log.LogError('MP3 tag/file missing: ' + self.FileName.ToNative)
     else if (Done and 2) = 0 then //No Artist Flag
-      Log.LogError('Artist Tag Missing: ' + self.FileName)
+      Log.LogError('Artist tag missing: ' + self.FileName.ToNative)
     else if (Done and 1) = 0 then //No Title Flag
-      Log.LogError('Title Tag Missing: ' + self.FileName)
+      Log.LogError('Title tag missing: ' + self.FileName.ToNative)
     else //unknown Error
-      Log.LogError('File Incomplete or not SingStar XML (B - '+ inttostr(Done) +'): ' + aFileName);
+      Log.LogError('File incomplete or not SingStar XML (B - '+ inttostr(Done) +'): ' + aFileName.ToNative);
   end;
 
 end;
 
+{**
+ * "International" StrToFloat variant. Uses either ',' or '.' as decimal
+ * separator.
+ *}
+function StrToFloatI18n(const Value: string): extended;
+var
+  TempValue : string;
+begin
+  TempValue := Value;
+  if (Pos(',', TempValue) <> 0) then
+    TempValue[Pos(',', TempValue)] := '.';
+  Result := StrToFloatDef(TempValue, 0);
+end;
 
-function TSong.ReadTXTHeader(const aFileName : WideString): boolean;
-
-  function song_StrtoFloat( aValue : string ) : extended;
-
-  var
-    lValue : string;
-
+function TSong.ReadTXTHeader(SongFile: TTextFileStream; ReadCustomTags: Boolean): boolean;
+var
+  Line, Identifier: string;
+  Value: string;
+  SepPos: integer; // separator position
+  Done: byte;      // bit-vector of mandatory fields
+  EncFile: IPath; // encoded filename
+  FullFileName: string;
+
+  { adds a custom header tag to the song
+    if there is no ':' in the read line, Tag should be empty
+    and the whole line should be in Content }
+  procedure AddCustomTag(const Tag, Content: String);
+    var Len: Integer;
   begin
-    lValue := aValue;
-
-    if (Pos(',', lValue) <> 0) then
-      lValue[Pos(',', lValue)] := '.';
-
-    Result := StrToFloatDef(lValue, 0);
+    if ReadCustomTags then
+    begin
+      Len := Length(CustomTags);
+      SetLength(CustomTags, Len + 1);
+      CustomTags[Len].Tag := DecodeStringUTF8(Tag, Encoding);
+      CustomTags[Len].Content := DecodeStringUTF8(Content, Encoding);
+    end;
   end;
-
-var
-  Line, Identifier, Value: string;
-  Temp        : word;
-  Done        : byte;
-
 begin
   Result := true;
   Done   := 0;
 
-  //Read first Line
-  ReadLn (SongFile, Line);
+  FullFileName := Path.Append(Filename).ToNative;
 
+  //Read first Line
+  SongFile.ReadLine(Line);
   if (Length(Line) <= 0) then
   begin
-    Log.LogError('File Starts with Empty Line: ' + aFileName);
+    Log.LogError('File starts with empty line: ' + FullFileName,
+                 'TSong.ReadTXTHeader');
     Result := false;
     Exit;
   end;
 
+  // check if file begins with a UTF-8 BOM, if so set encoding to UTF-8
+  if (CheckReplaceUTF8BOM(Line)) then
+    Encoding := encUTF8;
+
   //Read Lines while Line starts with # or its empty
   while (Length(Line) = 0) or (Line[1] = '#') do
   begin
     //Increase Line Number
     Inc (FileLineNo);
-    Temp := Pos(':', Line);
+    SepPos := Pos(':', Line);
 
-    //Line has a Seperator-> Headerline
-    if (Temp <> 0) then
+    //Line has no Seperator, ignore non header field
+    if (SepPos = 0) then
     begin
-      //Read Identifier and Value
-      Identifier  := Uppercase(Trim(Copy(Line, 2, Temp - 2))); //Uppercase is for Case Insensitive Checks
-      Value       := Trim(Copy(Line, Temp + 1,Length(Line) - Temp));
-
-      //Check the Identifier (If Value is given)
-      if (Length(Value) <> 0) then
+      AddCustomTag('', Copy(Line, 2, Length(Line) - 1));
+      // read next line
+      if (not SongFile.ReadLine(Line)) then
       begin
-        //-----------
-        //Required Attributes
-        //-----------
+        Result := false;
+        Log.LogError('File incomplete or not Ultrastar txt (A): ' + FullFileName);
+        Break;
+      end;
+      Continue;
+    end;
 
-        {$IFDEF UTF8_FILENAMES}
-        if ((Identifier = 'MP3') or (Identifier = 'BACKGROUND') or (Identifier = 'COVER') or (Identifier = 'VIDEO')) then
-          Value := Utf8Encode(Value);
-        {$ENDIF}
+    //Read Identifier and Value
+    Identifier  := UpperCase(Trim(Copy(Line, 2, SepPos - 2))); //Uppercase is for Case Insensitive Checks
+    Value       := Trim(Copy(Line, SepPos + 1, Length(Line) - SepPos));
 
-        //Title
-        if (Identifier = 'TITLE') then
-        begin
-          self.Title := Value;
+    //Check the Identifier (If Value is given)
+    if (Length(Value) = 0) then
+    begin
+      Log.LogInfo('Empty field "'+Identifier+'" in file ' + FullFileName,
+                   'TSong.ReadTXTHeader');
+      AddCustomTag(Identifier, '');
+    end
+    else
+    begin
 
-          //Add Title Flag to Done
-          Done := Done or 1;
-        end
+      //-----------
+      //Required Attributes
+      //-----------
 
-        //Artist
-        else if (Identifier = 'ARTIST') then
-        begin
-          self.Artist := Value;
+      if (Identifier = 'TITLE') then
+      begin
+        DecodeStringUTF8(Value, Title, Encoding);
+        //Add Title Flag to Done
+        Done := Done or 1;
+      end
 
-          //Add Artist Flag to Done
-          Done := Done or 2;
-        end
+      else if (Identifier = 'ARTIST') then
+      begin
+        DecodeStringUTF8(Value, Artist, Encoding);
+        //Add Artist Flag to Done
+        Done := Done or 2;
+      end
 
-        //MP3 File //Test if Exists
-        else if (Identifier = 'MP3') and (FileExists(self.Path + Value)) then
+      //MP3 File
+      else if (Identifier = 'MP3') then
+      begin
+        EncFile := DecodeFilename(Value);
+        if (Self.Path.Append(EncFile).IsFile) then
         begin
-          self.Mp3 := Value;
+          self.Mp3 := EncFile;
 
           //Add Mp3 Flag to Done
           Done := Done or 4;
-        end
+        end;
+      end
+
+      //Beats per Minute
+      else if (Identifier = 'BPM') then
+      begin
+        SetLength(self.BPM, 1);
+        self.BPM[0].StartBeat := 0;
 
-        //Beats per Minute
-        else if (Identifier = 'BPM') then
+        self.BPM[0].BPM := StrToFloatI18n( Value ) * Mult * MultBPM;
+
+        if self.BPM[0].BPM <> 0 then
         begin
-          SetLength(self.BPM, 1);
-          self.BPM[0].StartBeat := 0;
+          //Add BPM Flag to Done
+          Done := Done or 8;
+        end;
+      end
 
-          self.BPM[0].BPM := song_StrtoFloat( Value ) * Mult * MultBPM;
+      //---------
+      //Additional Header Information
+      //---------
 
-          if self.BPM[0].BPM <> 0 then
-          begin
-            //Add BPM Flag to Done
-            Done := Done or 8;
-          end;
-        end
+      // Gap
+      else if (Identifier = 'GAP') then
+      begin
+        self.GAP := StrToFloatI18n(Value);
+      end
 
-        //---------
-        //Additional Header Information
-        //---------
+      //Cover Picture
+      else if (Identifier = 'COVER') then
+      begin
+        self.Cover := DecodeFilename(Value);
+      end
 
-        // Gap
-        else if (Identifier = 'GAP') then
-          self.GAP := song_StrtoFloat( Value )
+      //Background Picture
+      else if (Identifier = 'BACKGROUND') then
+      begin
+        self.Background := DecodeFilename(Value);
+      end
 
-        //Cover Picture
-        else if (Identifier = 'COVER') then
-          self.Cover := Value
+      // Video File
+      else if (Identifier = 'VIDEO') then
+      begin
+        EncFile := DecodeFilename(Value);
+        if (self.Path.Append(EncFile).IsFile) then
+          self.Video := EncFile
+        else
+          Log.LogError('Can''t find video file in song: ' + FullFileName);
+      end
 
-        //Background Picture
-        else if (Identifier = 'BACKGROUND') then
-          self.Background := Value
+      // Video Gap
+      else if (Identifier = 'VIDEOGAP') then
+      begin
+        self.VideoGAP := StrToFloatI18n( Value )
+      end
 
-        // Video File
-        else if (Identifier = 'VIDEO') then
-        begin
-          if (FileExists(self.Path + Value)) then
-            self.Video := Value
-          else
-            Log.LogError('Can''t find Video File in Song: ' + aFileName);
-        end
+      //Genre Sorting
+      else if (Identifier = 'GENRE') then
+      begin
+        DecodeStringUTF8(Value, Genre, Encoding)
+      end
 
-        // Video Gap
-        else if (Identifier = 'VIDEOGAP') then
-          self.VideoGAP := song_StrtoFloat( Value )
+      //Edition Sorting
+      else if (Identifier = 'EDITION') then
+      begin
+        DecodeStringUTF8(Value, Edition, Encoding)
+      end
 
-        //Genre Sorting
-        else if (Identifier = 'GENRE') then
-          self.Genre := Value
+      //Creator Tag
+      else if (Identifier = 'CREATOR') then
+      begin
+        DecodeStringUTF8(Value, Creator, Encoding)
+      end
 
-        //Edition Sorting
-        else if (Identifier = 'EDITION') then
-          self.Edition := Value
+      //Language Sorting
+      else if (Identifier = 'LANGUAGE') then
+      begin
+        DecodeStringUTF8(Value, Language, Encoding)
+      end
 
-        //Creator Tag
-        else if (Identifier = 'CREATOR') then
-          self.Creator := Value
+      //Language Sorting
+      else if (Identifier = 'YEAR') then
+      begin
+        TryStrtoInt(Value, self.Year)
+      end
 
-        //Language Sorting
-        else if (Identifier = 'LANGUAGE') then
-          self.Language := Value
+      // Song Start
+      else if (Identifier = 'START') then
+      begin
+        self.Start := StrToFloatI18n( Value )
+      end
 
-        // Song Start
-        else if (Identifier = 'START') then
-          self.Start := song_StrtoFloat( Value )
+      // Song Ending
+      else if (Identifier = 'END') then
+      begin
+        TryStrtoInt(Value, self.Finish)
+      end
 
-        // Song Ending
-        else if (Identifier = 'END') then
-          TryStrtoInt(Value, self.Finish)
+      // Resolution
+      else if (Identifier = 'RESOLUTION') then
+      begin
+        TryStrtoInt(Value, self.Resolution)
+      end
 
-        // Resolution
-        else if (Identifier = 'RESOLUTION') then
-          TryStrtoInt(Value, self.Resolution)
+      // Notes Gap
+      else if (Identifier = 'NOTESGAP') then
+      begin
+        TryStrtoInt(Value, self.NotesGAP)
+      end
 
-        // Notes Gap
-        else if (Identifier = 'NOTESGAP') then
-          TryStrtoInt(Value, self.NotesGAP)
-        // Relative Notes
-        else if (Identifier = 'RELATIVE') and (uppercase(Value) = 'YES') then
+      // Relative Notes
+      else if (Identifier = 'RELATIVE') then
+      begin
+        if (UpperCase(Value) = 'YES') then
           self.Relative := true;
+      end
 
+      // File encoding
+      else if (Identifier = 'ENCODING') then
+      begin
+        self.Encoding := ParseEncoding(Value, DEFAULT_ENCODING);
+      end
+
+      // unsupported tag
+      else
+      begin
+        AddCustomTag(Identifier, Value);
       end;
-    end;
 
-    if not EOF(SongFile) then
-      ReadLn (SongFile, Line)
-    else
+    end; // End check for non-empty Value
+
+    // read next line
+    if (not SongFile.ReadLine(Line)) then
     begin
       Result := false;
-      Log.LogError('File Incomplete or not Ultrastar TxT (A): ' + aFileName);
-      break;
+      Log.LogError('File incomplete or not Ultrastar txt (A): ' + FullFileName);
+      Break;
     end;
+  end; // while
 
-  end;
-
-  if self.Cover = '' then
-    self.Cover := platform.FindSongFile(Path, '*[CO].jpg');
+  if self.Cover.IsUnset then
+    self.Cover := FindSongFile(Path, '*[CO].jpg');
 
   //Check if all Required Values are given
   if (Done <> 15) then
   begin
     Result := false;
     if (Done and 8) = 0 then      //No BPM Flag
-      Log.LogError('BPM Tag Missing: ' + self.FileName)
+      Log.LogError('BPM tag missing: ' + FullFileName)
     else if (Done and 4) = 0 then //No MP3 Flag
-      Log.LogError('MP3 Tag/File Missing: ' + self.FileName)
+      Log.LogError('MP3 tag/file missing: ' + FullFileName)
     else if (Done and 2) = 0 then //No Artist Flag
-      Log.LogError('Artist Tag Missing: ' + self.FileName)
+      Log.LogError('Artist tag missing: ' + FullFileName)
     else if (Done and 1) = 0 then //No Title Flag
-      Log.LogError('Title Tag Missing: ' + self.FileName)
+      Log.LogError('Title tag missing: ' + FullFileName)
     else //unknown Error
-      Log.LogError('File Incomplete or not Ultrastar TxT (B - '+ inttostr(Done) +'): ' + aFileName);
+      Log.LogError('File incomplete or not Ultrastar txt (B - '+ inttostr(Done) +'): ' + FullFileName);
   end;
-
 end;
 
 function  TSong.GetErrorLineNo: integer;
@@ -878,48 +1135,8 @@ begin
     Result := -1;
 end;
 
-procedure TSong.ParseNote(LineNumber: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: string);
-
+procedure TSong.ParseNote(LineNumber: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: UTF8String);
 begin
-  case Ini.Solmization of
-    1:  // european
-      begin
-        case (NoteP mod 12) of
-          0..1:   LyricS := ' do ';
-          2..3:   LyricS := ' re ';
-          4:      LyricS := ' mi ';
-          5..6:   LyricS := ' fa ';
-          7..8:   LyricS := ' sol ';
-          9..10:  LyricS := ' la ';
-          11:     LyricS := ' si ';
-        end;
-      end;
-    2:  // japanese
-      begin
-        case (NoteP mod 12) of
-          0..1:   LyricS := ' do ';
-          2..3:   LyricS := ' re ';
-          4:      LyricS := ' mi ';
-          5..6:   LyricS := ' fa ';
-          7..8:   LyricS := ' so ';
-          9..10:  LyricS := ' la ';
-          11:     LyricS := ' shi ';
-        end;
-      end;
-    3:  // american
-      begin
-        case (NoteP mod 12) of
-          0..1:   LyricS := ' do ';
-          2..3:   LyricS := ' re ';
-          4:      LyricS := ' mi ';
-          5..6:   LyricS := ' fa ';
-          7..8:   LyricS := ' sol ';
-          9..10:  LyricS := ' la ';
-          11:     LyricS := ' ti ';
-        end;
-      end;
-  end; // case
-
   with Lines[LineNumber].Line[Lines[LineNumber].High] do
   begin
     SetLength(Note, Length(Note) + 1);
@@ -956,14 +1173,9 @@ begin
     if Note[HighNote].Tone < BaseNote then
       BaseNote := Note[HighNote].Tone;
 
-    //delete the space that seperates the notes pitch from its lyrics
-    //it is left in the LyricS string because Read("some ordinal type") will
-    //set the files pointer to the first whitespace character after the
-    //ordinal string. Trim is no solution because it would cut the spaces
-    //that seperate the words of the lyrics, too.
-    Delete(LyricS, 1, 1);
+    Note[HighNote].Color := 1; // default color to 1 for editor
 
-    Note[HighNote].Text := LyricS;
+    DecodeStringUTF8(LyricS, Note[HighNote].Text, Encoding);
     Lyric := Lyric + Note[HighNote].Text;
 
     End_ := Note[HighNote].Start + Note[HighNote].Length;
@@ -971,10 +1183,8 @@ begin
 end;
 
 procedure TSong.NewSentence(LineNumberP: integer; Param1, Param2: integer);
-
 var
   I: integer;
-
 begin
 
   if (Lines[LineNumberP].Line[Lines[LineNumberP].High].HighNote  <> -1) then
@@ -985,7 +1195,8 @@ begin
   end
   else
   begin //use old line if it there were no notes added since last call of NewSentence
-    Log.LogError('Error loading Song, sentence w/o note found in line ' + InttoStr(FileLineNo) + ': ' + Filename);
+    Log.LogError('Error loading Song, sentence w/o note found in line ' +
+                 InttoStr(FileLineNo) + ': ' + Filename.ToNative);
   end;
 
   Lines[LineNumberP].Line[Lines[LineNumberP].High].HighNote := -1;
@@ -1012,8 +1223,7 @@ begin
   Lines[LineNumberP].Line[Lines[LineNumberP].High].LastLine := false;
 end;
 
-procedure TSong.clear();
-
+procedure TSong.Clear();
 begin
   //Main Information
   Title  := '';
@@ -1022,24 +1232,27 @@ begin
   //Sortings:
   Genre    := 'Unknown';
   Edition  := 'Unknown';
-  Language := 'Unknown'; //Language Patch
+  Language := 'Unknown';
+  Year := 0;
+
+  // set to default encoding
+  Encoding := DEFAULT_ENCODING;
+
+  // clear custom header tags
+  SetLength(CustomTags, 0);
 
   //Required Information
-  Mp3    := '';
-  {$IFDEF FPC}
-  setlength( BPM, 0 );
-  {$ELSE}
-  BPM    := nil;
-  {$ENDIF}
+  Mp3    := PATH_NONE;
+  SetLength(BPM, 0);
 
   GAP    := 0;
   Start  := 0;
   Finish := 0;
 
   //Additional Information
-  Background := '';
-  Cover      := '';
-  Video      := '';
+  Background := PATH_NONE;
+  Cover      := PATH_NONE;
+  Video      := PATH_NONE;
   VideoGAP   := 0;
   NotesGAP   := 0;
   Resolution := 4;
@@ -1048,8 +1261,9 @@ begin
   Relative := false;
 end;
 
-function TSong.Analyse(): boolean;
-
+function TSong.Analyse(const ReadCustomTags: Boolean): boolean;
+var
+  SongFile: TTextFileStream;
 begin
   Result := false;
 
@@ -1057,20 +1271,15 @@ begin
   FileLineNo := 0;
 
   //Open File and set File Pointer to the beginning
-  AssignFile(SongFile, self.Path + self.FileName);
-
+  SongFile := TMemTextFileStream.Create(Self.Path.Append(Self.FileName), fmOpenRead);
   try
-    Reset(SongFile);
-
     //Clear old Song Header
-    self.clear;
+    Self.clear;
 
     //Read Header
-    Result := self.ReadTxTHeader( FileName )
-
-    //And Close File
+    Result := Self.ReadTxTHeader(SongFile, ReadCustomTags)
   finally
-    CloseFile(SongFile);
+    SongFile.Free;
   end;
 end;
 
diff --git a/cmake/src/base/USongs.pas b/cmake/src/base/USongs.pas
index a7231cb3..cfc32a99 100644
--- a/cmake/src/base/USongs.pas
+++ b/cmake/src/base/USongs.pas
@@ -40,32 +40,36 @@ interface
 {$ENDIF}
 
 uses
+  SysUtils,
+  Classes,
   {$IFDEF MSWINDOWS}
     Windows,
     DirWatch,
   {$ELSE}
     {$IFNDEF DARWIN}
-      syscall,
+    syscall,
     {$ENDIF}
     baseunix,
     UnixType,
   {$ENDIF}
-  SysUtils,
-  Classes,
   UPlatform,
   ULog,
   UTexture,
   UCommon,
-  {$IFDEF DARWIN}
-    cthreads,
-  {$ENDIF}
   {$IFDEF USE_PSEUDO_THREAD}
-    PseudoThread,
+  PseudoThread,
   {$ENDIF}
+  UPath,
   USong,
+  UIni,
   UCatCovers;
 
 type
+  TSongFilter = (
+    fltAll,
+    fltTitle,
+    fltArtist
+  );
 
   TBPM = record
     BPM:       real;
@@ -73,11 +77,13 @@ type
   end;
 
   TScore = record
-    Name:   widestring;
+    Name:   UTF8String;
     Score:  integer;
     Length: string;
   end;
 
+  TPathDynArray = array of IPath;
+
   {$IFDEF USE_PSEUDO_THREAD}
   TSongs = class(TPseudoThread)
   {$ELSE}
@@ -102,11 +108,11 @@ type
 
 
     procedure LoadSongList;     // load all songs
-    procedure BrowseDir(Dir: widestring); // should return number of songs in the future
-    procedure BrowseTXTFiles(Dir: widestring);
-    procedure BrowseXMLFiles(Dir: widestring);
-    procedure Sort(Order: integer);
-    function  FindSongFile(Dir, Mask: widestring): widestring;
+    procedure FindFilesByExtension(const Dir: IPath; const Ext: IPath; Recursive: Boolean; var Files: TPathDynArray);
+    procedure BrowseDir(Dir: IPath); // should return number of songs in the future
+    procedure BrowseTXTFiles(Dir: IPath);
+    procedure BrowseXMLFiles(Dir: IPath);
+    procedure Sort(Order: TSortingType);
     property  Processing: boolean read fProcessing;
   end;
 
@@ -128,7 +134,7 @@ type
     function VisibleSongs: integer;                         // returns number of visible songs (for tabs)
     function VisibleIndex(Index: integer): integer;         // returns visible song index (skips invisible)
 
-    function SetFilter(FilterStr: string; const fType: byte): cardinal;
+    function SetFilter(FilterStr: UTF8String; Filter: TSongFilter): cardinal;
   end;
 
 var
@@ -156,9 +162,11 @@ uses
   UCovers,
   UFiles,
   UGraphic,
-  UIni,
-  UPath,
-  UNote;
+  UMain,
+  UPathUtils,
+  UNote,
+  UFilesystem,
+  UUnicodeUtils;
 
 constructor TSongs.Create();
 begin
@@ -232,7 +240,7 @@ begin
 
     // browse directories
     for I := 0 to SongPaths.Count-1 do
-      BrowseDir(SongPaths[I]);
+      BrowseDir(SongPaths[I] as IPath);
 
     if assigned(CatSongs) then
       CatSongs.Refresh;
@@ -264,84 +272,92 @@ begin
   Resume();
 end;
 
-procedure TSongs.BrowseDir(Dir: widestring);
+procedure TSongs.BrowseDir(Dir: IPath);
 begin
   BrowseTXTFiles(Dir);
   BrowseXMLFiles(Dir);
 end;
 
-procedure TSongs.BrowseTXTFiles(Dir: widestring);
+procedure TSongs.FindFilesByExtension(const Dir: IPath; const Ext: IPath; Recursive: Boolean; var Files: TPathDynArray);
 var
-  i:     integer;
-  Files: TDirectoryEntryArray;
-  lSong: TSong;
+  Iter: IFileIterator;
+  FileInfo: TFileInfo;
+  FileName: IPath;
 begin
-
-  try
-    Files := Platform.DirectoryFindFiles(Dir, '.txt', true)
-  except
-    Log.LogError('Couldn''t deal with directory/file: ' + Dir + ' in TSongs.BrowseTXTFiles')
-  end;
-
-  for i := 0 to Length(Files) - 1 do
+  // search for all files and directories
+  Iter := FileSystem.FileFind(Dir.Append('*'), faAnyFile);
+  while (Iter.HasNext) do
   begin
-    if Files[i].IsDirectory then
+    FileInfo := Iter.Next;
+    FileName := FileInfo.Name;
+    if ((FileInfo.Attr and faDirectory) <> 0) then
     begin
-      BrowseTXTFiles(Dir + Files[i].Name + PathDelim);  //Recursive Call
+      if Recursive and (not FileName.Equals('.')) and (not FileName.Equals('..')) then
+        FindFilesByExtension(Dir.Append(FileName), Ext, true, Files);
     end
     else
     begin
-      lSong := TSong.create(Dir + Files[i].Name);
-
-      if lSong.Analyse then
-        SongList.add(lSong)
-      else
+      if (Ext.Equals(FileName.GetExtension(), true)) then
       begin
-        Log.LogError('AnalyseFile failed for "' + Files[i].Name + '".');
-        freeandnil(lSong);
+        SetLength(Files, Length(Files)+1);
+        Files[High(Files)] := Dir.Append(FileName);
       end;
-
     end;
   end;
-  SetLength(Files, 0);
-
 end;
 
-procedure TSongs.BrowseXMLFiles(Dir: widestring);
+procedure TSongs.BrowseTXTFiles(Dir: IPath);
 var
-  i:     integer;
-  Files: TDirectoryEntryArray;
-  lSong: TSong;
+  I: integer;
+  Files: TPathDynArray;
+  Song: TSong;
+  Extension: IPath;
 begin
+  SetLength(Files, 0);
+  Extension := Path('.txt');
+  FindFilesByExtension(Dir, Extension, true, Files);
 
-  try
-    Files := Platform.DirectoryFindFiles(Dir, '.xml', true)
-  except
-    Log.LogError('Couldn''t deal with directory/file: ' + Dir + ' in TSongs.BrowseXMLFiles')
-  end;
-
-  for i := 0 to Length(Files) - 1 do
+  for I := 0 to High(Files) do
   begin
-    if Files[i].IsDirectory then
-    begin
-      BrowseXMLFiles(Dir + Files[i].Name + PathDelim); // Recursive Call
-    end
+    Song := TSong.Create(Files[I]);
+
+    if Song.Analyse then
+      SongList.Add(Song)
     else
     begin
-      lSong := TSong.create(Dir + Files[i].Name);
+      Log.LogError('AnalyseFile failed for "' + Files[I].ToNative + '".');
+      FreeAndNil(Song);
+    end;
+  end;
 
-      if lSong.AnalyseXML then
-	SongList.add(lSong)
-      else
-      begin
-	Log.LogError('AnalyseFile failed for "' + Files[i].Name + '".');
-	freeandnil(lSong);
-      end;
+  SetLength(Files, 0);
+end;
 
+procedure TSongs.BrowseXMLFiles(Dir: IPath);
+var
+  I: integer;
+  Files: TPathDynArray;
+  Song: TSong;
+  Extension: IPath;
+begin
+  SetLength(Files, 0);
+  Extension := Path('.xml');
+  FindFilesByExtension(Dir, Extension, true, Files);
+
+  for I := 0 to High(Files) do
+  begin
+    Song := TSong.Create(Files[I]);
+
+    if Song.AnalyseXML then
+      SongList.Add(Song)
+    else
+    begin
+      Log.LogError('AnalyseFile failed for "' + Files[I].ToNative + '".');
+      FreeAndNil(Song);
     end;
   end;
-  SetLength(Files, 0);
 
+  SetLength(Files, 0);
 end;
 
 (*
@@ -350,35 +366,35 @@ end;
 
 function CompareByEdition(Song1, Song2: Pointer): integer;
 begin
-  Result := CompareText(TSong(Song1).Edition, TSong(Song2).Edition);
+  Result := UTF8CompareText(TSong(Song1).Edition, TSong(Song2).Edition);
 end;
 
 function CompareByGenre(Song1, Song2: Pointer): integer;
 begin
-  Result := CompareText(TSong(Song1).Genre, TSong(Song2).Genre);
+  Result := UTF8CompareText(TSong(Song1).Genre, TSong(Song2).Genre);
 end;
 
 function CompareByTitle(Song1, Song2: Pointer): integer;
 begin
-  Result := CompareText(TSong(Song1).Title, TSong(Song2).Title);
+  Result := UTF8CompareText(TSong(Song1).Title, TSong(Song2).Title);
 end;
 
 function CompareByArtist(Song1, Song2: Pointer): integer;
 begin
-  Result := CompareText(TSong(Song1).Artist, TSong(Song2).Artist);
+  Result := UTF8CompareText(TSong(Song1).Artist, TSong(Song2).Artist);
 end;
 
 function CompareByFolder(Song1, Song2: Pointer): integer;
 begin
-  Result := CompareText(TSong(Song1).Folder, TSong(Song2).Folder);
+  Result := UTF8CompareText(TSong(Song1).Folder, TSong(Song2).Folder);
 end;
 
 function CompareByLanguage(Song1, Song2: Pointer): integer;
 begin
-  Result := CompareText(TSong(Song1).Language, TSong(Song2).Language);
+  Result := UTF8CompareText(TSong(Song1).Language, TSong(Song2).Language);
 end;
 
-procedure TSongs.Sort(Order: integer);
+procedure TSongs.Sort(Order: TSortingType);
 var
   CompareFunc: TListSortCompare;
 begin
@@ -394,8 +410,6 @@ begin
       CompareFunc := CompareByArtist;
     sFolder: // by folder
       CompareFunc := CompareByFolder;
-    sTitle2: // by title2
-      CompareFunc := CompareByTitle;
     sArtist2: // by artist2
       CompareFunc := CompareByArtist;
     sLanguage: // by Language
@@ -412,21 +426,9 @@ begin
   MergeSort(SongList, CompareFunc);
 end;
 
-function TSongs.FindSongFile(Dir, Mask: widestring): widestring;
-var
-  SR: TSearchRec;   // for parsing song directory
-begin
-  Result := '';
-  if FindFirst(Dir + Mask, faDirectory, SR) = 0 then
-  begin
-    Result := SR.Name;
-  end; // if
-  FindClose(SR);
-end;
-
 procedure TCatSongs.SortSongs();
 begin
-  case Ini.Sorting of
+  case TSortingType(Ini.Sorting) of
     sEdition: begin
         Songs.Sort(sTitle);
         Songs.Sort(sArtist);
@@ -454,12 +456,8 @@ begin
         Songs.Sort(sTitle);
         Songs.Sort(sArtist);
       end;
-    sTitle2: begin
-        Songs.Sort(sArtist2);
-        Songs.Sort(sTitle2);
-      end;
     sArtist2: begin
-        Songs.Sort(sTitle2);
+        Songs.Sort(sTitle);
         Songs.Sort(sArtist2);
       end;
   end; // case
@@ -469,14 +467,14 @@ procedure TCatSongs.Refresh;
 var
   SongIndex:   integer;
   CurSong:     TSong;
-  CatIndex:    integer; // index of current song in Song
-  Letter:      char;    // current letter for sorting using letter
-  CurCategory: string;  // current edition for sorting using edition, genre etc.
-  Order:       integer; // number used for ordernum
-  LetterTmp:   char;
-  CatNumber:   integer; // Number of Song in Category
-
-  procedure AddCategoryButton(const CategoryName: string);
+  CatIndex:    integer;    // index of current song in Song
+  Letter:      UCS4Char;   // current letter for sorting using letter
+  CurCategory: UTF8String; // current edition for sorting using edition, genre etc.
+  Order:       integer;    // number used for ordernum
+  LetterTmp:   UCS4Char;
+  CatNumber:   integer;    // Number of Song in Category
+
+  procedure AddCategoryButton(const CategoryName: UTF8String);
   var
     PrevCatBtnIndex: integer;
   begin
@@ -488,7 +486,7 @@ var
     Song[CatIndex].Main     := true;
     Song[CatIndex].OrderTyp := 0;
     Song[CatIndex].OrderNum := Order;
-    Song[CatIndex].Cover    := CatCovers.GetCover(Ini.Sorting, CategoryName);
+    Song[CatIndex].Cover    := CatCovers.GetCover(TSortingType(Ini.Sorting), CategoryName);
     Song[CatIndex].Visible  := true;
 
     // set number of songs in previous category
@@ -511,7 +509,7 @@ begin
   // Note: do NOT set Letter to ' ', otherwise no category-button will be
   // created for songs beginning with ' ' if songs of this category exist.
   // TODO: trim song-properties so ' ' will not occur as first chararcter. 
-  Letter      := #0;
+  Letter      := 0;
 
   // clear song-list
   for SongIndex := 0 to Songs.SongList.Count - 1 do
@@ -530,101 +528,102 @@ begin
     // if tabs are on, add section buttons for each new section
     if (Ini.Tabs = 1) then
     begin
-      if (Ini.Sorting = sEdition) and
-         (CompareText(CurCategory, CurSong.Edition) <> 0) then
-      begin
-        CurCategory := CurSong.Edition;
-
-        // TODO: remove this block if it is not needed anymore
-        {
-        if CurSection = 'Singstar Part 2'     then CoverName := 'Singstar';
-        if CurSection = 'Singstar German'     then CoverName := 'Singstar';
-        if CurSection = 'Singstar Spanish'    then CoverName := 'Singstar';
-        if CurSection = 'Singstar Italian'    then CoverName := 'Singstar';
-        if CurSection = 'Singstar French'     then CoverName := 'Singstar';
-        if CurSection = 'Singstar 80s Polish' then CoverName := 'Singstar 80s';
-        }
-
-        // add Category Button
-        AddCategoryButton(CurCategory);
-      end
-
-      else if (Ini.Sorting = sGenre) and
-              (CompareText(CurCategory, CurSong.Genre) <> 0) then
-      begin
-        CurCategory := CurSong.Genre;
-        // add Genre Button
-        AddCategoryButton(CurCategory);
-      end
-
-      else if (Ini.Sorting = sLanguage) and
-              (CompareText(CurCategory, CurSong.Language) <> 0) then
-      begin
-        CurCategory := CurSong.Language;
-        // add Language Button
-        AddCategoryButton(CurCategory);
-      end
-
-      else if (Ini.Sorting = sTitle) and
-              (Length(CurSong.Title) >= 1) and
-              (Letter <> UpperCase(CurSong.Title)[1]) then
-      begin
-        Letter := Uppercase(CurSong.Title)[1];
-        // add a letter Category Button
-        AddCategoryButton(Letter);
-      end
+      case (TSortingType(Ini.Sorting)) of
+        sEdition: begin
+          if (CompareText(CurCategory, CurSong.Edition) <> 0) then
+          begin
+            CurCategory := CurSong.Edition;
+
+            // add Category Button
+            AddCategoryButton(CurCategory);
+          end;
+        end;
 
-      else if (Ini.Sorting = sArtist) and
-              (Length(CurSong.Artist) >= 1) and
-              (Letter <> UpperCase(CurSong.Artist)[1]) then
-      begin
-        Letter := UpperCase(CurSong.Artist)[1];
-        // add a letter Category Button
-        AddCategoryButton(Letter);
-      end
+        sGenre: begin
+          if (CompareText(CurCategory, CurSong.Genre) <> 0) then
+          begin
+            CurCategory := CurSong.Genre;
+            // add Genre Button
+            AddCategoryButton(CurCategory);
+          end;
+        end;
 
-      else if (Ini.Sorting = sFolder) and
-              (CompareText(CurCategory, CurSong.Folder) <> 0) then
-      begin
-        CurCategory := CurSong.Folder;
-        // add folder tab
-        AddCategoryButton(CurCategory);
-      end
+        sLanguage: begin
+          if (CompareText(CurCategory, CurSong.Language) <> 0) then
+          begin
+            CurCategory := CurSong.Language;
+            // add Language Button
+            AddCategoryButton(CurCategory);
+          end
+        end;
 
-      else if (Ini.Sorting = sTitle2) and
-              (Length(CurSong.Title) >= 1) then
-      begin
-        // pack all numbers into a category named '#'
-        if (CurSong.Title[1] >= '0') and (CurSong.Title[1] <= '9') then
-          LetterTmp := '#'
-        else
-          LetterTmp := UpperCase(CurSong.Title)[1];
+        sTitle: begin
+          if (Length(CurSong.Title) >= 1) then
+          begin
+            LetterTmp := UCS4UpperCase(UTF8ToUCS4String(CurSong.Title)[0]);
+            { all numbers and some punctuation chars are put into a
+              category named '#'
+              we can't put the other punctuation chars into this category
+              because they are not in order, so there will be two different
+              categories named '#' } 
+            if (LetterTmp in [Ord('!') .. Ord('?')]) then
+              LetterTmp := Ord('#')
+            else
+              LetterTmp := UCS4UpperCase(LetterTmp);
+            if (Letter <> LetterTmp) then
+            begin
+              Letter := LetterTmp;
+              // add a letter Category Button
+              AddCategoryButton(UCS4ToUTF8String(Letter));
+            end;
+          end;
+        end;
 
-        if (Letter <> LetterTmp) then
-        begin
-          Letter := LetterTmp;
-          // add a letter Category Button
-          AddCategoryButton(Letter);
+        sArtist: begin
+          if (Length(CurSong.Artist) >= 1) then
+          begin
+            LetterTmp := UCS4UpperCase(UTF8ToUCS4String(CurSong.Artist)[0]);
+            { all numbers and some punctuation chars are put into a
+              category named '#'
+              we can't put the other punctuation chars into this category
+              because they are not in order, so there will be two different
+              categories named '#' } 
+            if (LetterTmp in [Ord('!') .. Ord('?')]) then
+              LetterTmp := Ord('#')
+            else
+              LetterTmp := UCS4UpperCase(LetterTmp);
+              
+            if (Letter <> LetterTmp) then
+            begin
+              Letter := LetterTmp;
+              // add a letter Category Button
+              AddCategoryButton(UCS4ToUTF8String(Letter));
+            end;
+          end;
         end;
-      end
 
-      else if (Ini.Sorting = sArtist2) and
-              (Length(CurSong.Artist)>=1) then
-      begin
-        // pack all numbers into a category named '#'
-        if (CurSong.Artist[1] >= '0') and (CurSong.Artist[1] <= '9') then
-          LetterTmp := '#'
-        else
-          LetterTmp := UpperCase(CurSong.Artist)[1];
+        sFolder: begin
+          if (UTF8CompareText(CurCategory, CurSong.Folder) <> 0) then
+          begin
+            CurCategory := CurSong.Folder;
+            // add folder tab
+            AddCategoryButton(CurCategory);
+          end;
+        end;
 
-        if (Letter <> LetterTmp) then
-        begin
-          Letter := LetterTmp;
-          // add a letter Category Button
-          AddCategoryButton(Letter);
+        sArtist2: begin
+          { this new sorting puts all songs by the same artist into
+            a single category }
+          if (UTF8CompareText(CurCategory, CurSong.Artist) <> 0) then
+          begin
+            CurCategory := CurSong.Artist;
+            // add folder tab
+            AddCategoryButton(CurCategory);
+          end;
         end;
-      end;
-    end;
+
+      end; // case (Ini.Sorting)
+    end; // if (Ini.Tabs = 1)
 
     CatIndex := Length(Song);
     SetLength(Song, CatIndex+1);
@@ -728,14 +727,18 @@ var
   I: integer;
 begin
   Result := -1;
-  I := SearchFrom + 1;
-  while not CatSongs.Song[I].Visible do
+  I := SearchFrom;
+  while (Result = -1) do
   begin
     Inc (I);
-    if (I>high(CatSongs.Song)) then
-      I := low(CatSongs.Song);
+
+    if (I > High(CatSongs.Song)) then
+      I := Low(CatSongs.Song);
     if (I = SearchFrom) then // Make One Round and no song found->quit
-      break;
+      Break;
+
+    if (CatSongs.Song[I].Visible) then
+      Result := I;
   end;
 end;
 // Wrong song selected when tabs on bug End
@@ -771,58 +774,58 @@ begin
   end;
 end;
 
-function TCatSongs.SetFilter(FilterStr: string; const fType: byte): cardinal;
+function TCatSongs.SetFilter(FilterStr: UTF8String; Filter: TSongFilter): cardinal;
 var
   I, J:      integer;
-  cString:   string;
-  SearchStr: array of string;
+  TmpString: UTF8String;
+  WordArray: array of UTF8String;
 begin
-{
-  fType: 0: All
-         1: Title
-         2: Artist
-}
   FilterStr := Trim(FilterStr);
-  if FilterStr<>'' then
+  if (FilterStr <> '') then
   begin
     Result := 0;
-    // Create Search Array
-    SetLength(SearchStr, 1);
+
+    // initialize word array
+    SetLength(WordArray, 1);
+
+    // Copy words to SearchStr
     I := Pos(' ', FilterStr);
     while (I <> 0) do
     begin
-      SetLength(SearchStr, Length(SearchStr) + 1);
-      cString := Copy(FilterStr, 1, I - 1);
-      if (cString <> ' ') and (cString <> '') then
-        SearchStr[High(SearchStr) - 1] := cString;
-      Delete (FilterStr, 1, I);
+      WordArray[High(WordArray)] := Copy(FilterStr, 1, I-1);
+      SetLength(WordArray, Length(WordArray) + 1);
 
-      I := Pos (' ', FilterStr);
+      FilterStr := TrimLeft(Copy(FilterStr, I+1, Length(FilterStr)-I));
+      I := Pos(' ', FilterStr);
     end;
-    // Copy last Word
-    if (FilterStr <> ' ') and (FilterStr <> '') then
-      SearchStr[High(SearchStr)] := FilterStr;
+
+    // Copy last word
+    WordArray[High(WordArray)] := FilterStr;
 
     for I := 0 to High(Song) do
     begin
       if not Song[i].Main then
       begin
-        case fType of
-          0: cString := Song[I].Artist + ' ' + Song[i].Title + ' ' + Song[i].Folder;
-          1: cString := Song[I].Title;
-          2: cString := Song[I].Artist;
+        case Filter of
+          fltAll:
+            TmpString := Song[I].Artist + ' ' + Song[i].Title + ' ' + Song[i].Folder;
+          fltTitle:
+            TmpString := Song[I].Title;
+          fltArtist:
+            TmpString := Song[I].Artist;
         end;
-        Song[i].Visible:=true;
-        // Look for every Searched Word
-        for J := 0 to High(SearchStr) do
+        Song[i].Visible := true;
+        // Look for every searched word
+        for J := 0 to High(WordArray) do
         begin
-          Song[i].Visible := Song[i].Visible and AnsiContainsText(cString, SearchStr[J])
+          Song[i].Visible := Song[i].Visible and
+                             UTF8ContainsText(TmpString, WordArray[J])
         end;
         if Song[i].Visible then
           Inc(Result);
       end
       else
-        Song[i].Visible:=false;
+        Song[i].Visible := false;
     end;
     CatNumShow := -2;
   end
@@ -830,7 +833,7 @@ begin
   begin
     for i := 0 to High(Song) do
     begin
-      Song[i].Visible := (Ini.Tabs=1) = Song[i].Main;
+      Song[i].Visible := (Ini.Tabs = 1) = Song[i].Main;
       CatNumShow := -1;
     end;
     Result := 0;
diff --git a/cmake/src/base/UTextEncoding.pas b/cmake/src/base/UTextEncoding.pas
index 6eec8eec..0c9ba4cc 100644
--- a/cmake/src/base/UTextEncoding.pas
+++ b/cmake/src/base/UTextEncoding.pas
@@ -19,8 +19,8 @@
  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301, USA.
  *
- * $URL: https://ultrastardx.svn.sourceforge.net/svnroot/ultrastardx/trunk/src/menu/UMenuText.pas $
- * $Id: UMenuText.pas 1485 2008-10-28 20:16:05Z tobigun $
+ * $URL$
+ * $Id$
  *}
 
 unit UTextEncoding;
@@ -34,114 +34,215 @@ interface
 {$I switches.inc}
 
 uses
-  SysUtils;
+  SysUtils,
+  UUnicodeUtils;
 
 type
-  TEncoding = (encCP1250, encCP1252, encUTF8, encNative);
+  TEncoding = (
+    encLocale,  // current locale (needs cwstring on linux)
+    encUTF8,    // UTF-8
+    encCP1250,  // Windows-1250 Central/Eastern Europe (used by Ultrastar)
+    encCP1252,  // Windows-1252 Western Europe (used by UltraStar Deluxe < 1.1)
+    encAuto     // try to match the w3c regex and decode as unicode on match
+                // and as fallback if not match
+  );
+
+const
+  UTF8_BOM: UTF8String = #$EF#$BB#$BF;
+
+{**
+ * Decodes Src encoded in SrcEncoding to a UTF-16 or UTF-8 encoded Dst string.
+ * Returns true if the conversion was successful.
+ *}
+function DecodeString(const Src: RawByteString; out Dst: WideString; SrcEncoding: TEncoding): boolean; overload;
+function DecodeString(const Src: RawByteString; SrcEncoding: TEncoding): WideString; overload;
+function DecodeStringUTF8(const Src: RawByteString; out Dst: UTF8String; SrcEncoding: TEncoding): boolean; overload;
+function DecodeStringUTF8(const Src: RawByteString; SrcEncoding: TEncoding): UTF8String; overload;
+
+{**
+ * Encodes the UTF-16 or UTF-8 encoded Src string to Dst using DstEncoding
+ * Returns true if the conversion was successful.
+ *}
+function EncodeString(const Src: WideString; out Dst: RawByteString; DstEncoding: TEncoding): boolean; overload;
+function EncodeString(const Src: WideString; DstEncoding: TEncoding): RawByteString; overload;
+function EncodeStringUTF8(const Src: UTF8String; out Dst: RawByteString; DstEncoding: TEncoding): boolean; overload;
+function EncodeStringUTF8(const Src: UTF8String; DstEncoding: TEncoding): RawByteString; overload;
+
+{**
+ * If Text starts with an UTF-8 BOM, the BOM is removed and true will
+ * be returned.
+ *}
+function CheckReplaceUTF8BOM(var Text: RawByteString): boolean;
 
-function RecodeString(const Src: string; SrcEncoding: TEncoding): WideString;
+{**
+ * Parses an encoding string to its TEncoding equivalent.
+ * Surrounding whitespace and dashes ('-') are removed, the upper-cased
+ * resulting value is then compared with TEncodingNames.
+ * If the encoding was not found, the result is set to the Default encoding.
+ *}
+function ParseEncoding(const EncodingStr: AnsiString; Default: TEncoding): TEncoding;
+
+{**
+ * Returns the name of an encoding.
+ *}
+function EncodingName(Encoding: TEncoding): AnsiString;
 
 implementation
 
+uses
+  StrUtils,
+  pcre,
+  UCommon,
+  ULog;
+
 type
-  TConversionTable = array[0..127] of WideChar;
+  IEncoder = interface
+    function GetName(): AnsiString;
+    function Encode(const InStr: UCS4String; out OutStr: RawByteString): boolean;
+    function Decode(const InStr: RawByteString; out OutStr: UCS4String): boolean;
+  end;
+
+  TEncoder = class(TInterfacedObject, IEncoder)
+  public
+    function GetName(): AnsiString; virtual; abstract;
+    function Encode(const InStr: UCS4String; out OutStr: RawByteString): boolean; virtual; abstract;
+    function Decode(const InStr: RawByteString; out OutStr: UCS4String): boolean; virtual; abstract;
+  end;
+
+  TSingleByteEncoder = class(TEncoder)
+  public
+    function Encode(const InStr: UCS4String; out OutStr: RawByteString): boolean; override;
+    function Decode(const InStr: RawByteString; out OutStr: UCS4String): boolean; override;
+    function DecodeChar(InChr: AnsiChar; out OutChr: UCS4Char): boolean; virtual; abstract;
+    function EncodeChar(InChr: UCS4Char; out OutChr: AnsiChar): boolean; virtual; abstract;
+  end;
 
 const
-  // Windows-1250 Central/Eastern Europe (used by Ultrastar)
-  CP1250Table: TConversionTable = (
-    { $80 }
-    #$20AC,     #0, #$201A,     #0, #$201E, #$2026, #$2020, #$2021,
-        #0, #$2030, #$0160, #$2039, #$015A, #$0164, #$017D, #$0179,
-    { $90 }
-        #0, #$2018, #$2019, #$201C, #$201D, #$2022, #$2013, #$2014,
-        #0, #$2122, #$0161, #$203A, #$015B, #$0165, #$017E, #$017A,
-    { $A0 }
-    #$00A0, #$02C7, #$02D8, #$0141, #$00A4, #$0104, #$00A6, #$00A7,
-    #$00A8, #$00A9, #$015E, #$00AB, #$00AC, #$00AD, #$00AE, #$017B,
-    { $B0 }
-    #$00B0, #$00B1, #$02DB, #$0142, #$00B4, #$00B5, #$00B6, #$00B7,
-    #$00B8, #$0105, #$015F, #$00BB, #$013D, #$02DD, #$013E, #$017C,
-    { $C0 }
-    #$0154, #$00C1, #$00C2, #$0102, #$00C4, #$0139, #$0106, #$00C7,
-    #$010C, #$00C9, #$0118, #$00CB, #$011A, #$00CD, #$00CE, #$010E,
-    { $D0 }
-    #$0110, #$0143, #$0147, #$00D3, #$00D4, #$0150, #$00D6, #$00D7,
-    #$0158, #$016E, #$00DA, #$0170, #$00DC, #$00DD, #$0162, #$00DF,
-    { $E0 }
-    #$0155, #$00E1, #$00E2, #$0103, #$00E4, #$013A, #$0107, #$00E7,
-    #$010D, #$00E9, #$0119, #$00EB, #$011B, #$00ED, #$00EE, #$010F,
-    { $F0 }
-    #$0111, #$0144, #$0148, #$00F3, #$00F4, #$0151, #$00F6, #$00F7,
-    #$0159, #$016F, #$00FA, #$0171, #$00FC, #$00FD, #$0163, #$02D9
-  );
+  ERROR_CHAR = '?';
 
-  // Windows-1252 Western Europe (used by UltraStar Deluxe < 1.1)
-  CP1252Table: TConversionTable = (
-    { $80 }
-    #$20AC,     #0, #$201A, #$0192, #$201E, #$2026, #$2020, #$2021,
-    #$02C6, #$2030, #$0160, #$2039, #$0152,     #0, #$017D,     #0,
-    { $90 }
-        #0, #$2018, #$2019, #$201C, #$201D, #$2022, #$2013, #$2014,
-    #$02DC, #$2122, #$0161, #$203A, #$0153,     #0, #$017E, #$0178,
-    { $A0 }
-    #$00A0, #$00A1, #$00A2, #$00A3, #$00A4, #$00A5, #$00A6, #$00A7,
-    #$00A8, #$00A9, #$00AA, #$00AB, #$00AC, #$00AD, #$00AE, #$00AF,
-    { $B0 }
-    #$00B0, #$00B1, #$00B2, #$00B3, #$00B4, #$00B5, #$00B6, #$00B7,
-    #$00B8, #$00B9, #$00BA, #$00BB, #$00BC, #$00BD, #$00BE, #$00BF,
-    { $C0 }
-    #$00C0, #$00C1, #$00C2, #$00C3, #$00C4, #$00C5, #$00C6, #$00C7,
-    #$00C8, #$00C9, #$00CA, #$00CB, #$00CC, #$00CD, #$00CE, #$00CF,
-    { $D0 }
-    #$00D0, #$00D1, #$00D2, #$00D3, #$00D4, #$00D5, #$00D6, #$00D7,
-    #$00D8, #$00D9, #$00DA, #$00DB, #$00DC, #$00DD, #$00DE, #$00DF,
-    { $E0 }
-    #$00E0, #$00E1, #$00E2, #$00E3, #$00E4, #$00E5, #$00E6, #$00E7,
-    #$00E8, #$00E9, #$00EA, #$00EB, #$00EC, #$00ED, #$00EE, #$00EF,
-    { $F0 }
-    #$00F0, #$00F1, #$00F2, #$00F3, #$00F4, #$00F5, #$00F6, #$00F7,
-    #$00F8, #$00F9, #$00FA, #$00FB, #$00FC, #$00FD, #$00FE, #$00FF
-  );
+var
+  Encoders: array[TEncoding] of IEncoder;
 
+function TSingleByteEncoder.Encode(const InStr: UCS4String; out OutStr: RawByteString): boolean;
+var
+  I: integer;
+begin
+  SetLength(OutStr, LengthUCS4(InStr));
+  Result := true;
+  for I := 1 to Length(OutStr) do
+  begin
+    if (not EncodeChar(InStr[I-1], OutStr[I])) then
+      Result := false;
+  end;
+end;
 
-function Convert(const Src: string; const Table: TConversionTable): WideString;
+function TSingleByteEncoder.Decode(const InStr: RawByteString; out OutStr: UCS4String): boolean;
 var
-  SrcPos, DstPos: integer;
+  I: integer;
 begin
-  SetLength(Result, Length(Src));
-  DstPos := 1;
-  for SrcPos := 1 to Length(Src) do
+  SetLength(OutStr, Length(InStr)+1);
+  Result := true;
+  for I := 1 to Length(InStr) do
   begin
-    if (Src[SrcPos] < #128) then
-    begin
-      // copy ASCII char
-      Result[DstPos] := Src[SrcPos];
-      Inc(DstPos);
-    end
-    else
+    if (not DecodeChar(InStr[I], OutStr[I-1])) then
+      Result := false;
+  end;
+  OutStr[High(OutStr)] := 0;
+end;
+
+function DecodeString(const Src: RawByteString; out Dst: WideString; SrcEncoding: TEncoding): boolean;
+var
+  DstUCS4: UCS4String;
+begin
+  Result := Encoders[SrcEncoding].Decode(Src, DstUCS4);
+  Dst := UCS4StringToWideString(DstUCS4);
+end;
+
+function DecodeString(const Src: RawByteString; SrcEncoding: TEncoding): WideString;
+begin
+  DecodeString(Src, Result, SrcEncoding);
+end;
+
+function DecodeStringUTF8(const Src: RawByteString; out Dst: UTF8String; SrcEncoding: TEncoding): boolean;
+var
+  DstUCS4: UCS4String;
+begin
+  Result := Encoders[SrcEncoding].Decode(Src, DstUCS4);
+  Dst := UCS4ToUTF8String(DstUCS4);
+end;
+
+function DecodeStringUTF8(const Src: RawByteString; SrcEncoding: TEncoding): UTF8String;
+begin
+  DecodeStringUTF8(Src, Result, SrcEncoding);
+end;
+
+function EncodeString(const Src: WideString; out Dst: RawByteString; DstEncoding: TEncoding): boolean;
+begin
+  Result := Encoders[DstEncoding].Encode(WideStringToUCS4String(Src), Dst);
+end;
+
+function EncodeString(const Src: WideString; DstEncoding: TEncoding): RawByteString;
+begin
+  EncodeString(Src, Result, DstEncoding);
+end;
+
+function EncodeStringUTF8(const Src: UTF8String; out Dst: RawByteString; DstEncoding: TEncoding): boolean;
+begin
+  Result := Encoders[DstEncoding].Encode(UTF8ToUCS4String(Src), Dst);
+end;
+
+function EncodeStringUTF8(const Src: UTF8String; DstEncoding: TEncoding): RawByteString;
+begin
+  EncodeStringUTF8(Src, Result, DstEncoding);
+end;
+
+function CheckReplaceUTF8BOM(var Text: RawByteString): boolean;
+begin
+  if AnsiStartsStr(UTF8_BOM, Text) then
+  begin
+    Text := Copy(Text, Length(UTF8_BOM)+1, Length(Text)-Length(UTF8_BOM));
+    Result := true;
+    Exit;
+  end;
+  Result := false;
+end;
+
+function ParseEncoding(const EncodingStr: AnsiString; Default: TEncoding): TEncoding;
+var
+  PrepStr: AnsiString; // prepared encoding string
+  Encoding: TEncoding;
+begin
+  // remove surrounding whitespace, replace dashes, to upper case
+  PrepStr := UpperCase(AnsiReplaceStr(Trim(EncodingStr), '-', ''));
+  for Encoding := Low(TEncoding) to High(TEncoding) do
+  begin
+    if (Encoders[Encoding].GetName() = PrepStr) then
     begin
-      // look-up char
-      Result[DstPos] := Table[Ord(Src[SrcPos]) - 128];
-      // ignore invalid characters
-      if (Result[DstPos] <> #0) then
-        Inc(DstPos);
+      Result := Encoding;
+      Exit;
     end;
   end;
-  SetLength(Result, DstPos-1);
+  Result := Default;
 end;
 
-function RecodeString(const Src: string; SrcEncoding: TEncoding): WideString;
+function EncodingName(Encoding: TEncoding): AnsiString;
 begin
-  case SrcEncoding of
-    encCP1250:
-      Result := Convert(Src, CP1250Table);
-    encCP1252:
-      Result := Convert(Src, CP1252Table);
-    encUTF8:
-      Result := UTF8Decode(Src);
-    encNative:
-      Result := UTF8Decode(AnsiToUtf8(Src));
-  end;
+  Result := Encoders[Encoding].GetName();
 end;
 
+{$I ..\\encoding\\Locale.inc}
+{$I ..\\encoding\\UTF8.inc}
+{$I ..\\encoding\\CP1250.inc}
+{$I ..\\encoding\\CP1252.inc}
+{$I ..\\encoding\\Auto.inc}
+
+initialization
+  Encoders[encLocale] := TEncoderLocale.Create;
+  Encoders[encUTF8]   := TEncoderUTF8.Create;
+  Encoders[encCP1250] := TEncoderCP1250.Create;
+  Encoders[encCP1252] := TEncoderCP1252.Create;
+
+  // use USDX < 1.1 encoding for backward compatibility (encCP1252)
+  Encoders[encAuto] := TEncoderAuto.Create(Encoders[encUTF8], Encoders[encCP1252]);
+
 end.
diff --git a/cmake/src/base/UTexture.pas b/cmake/src/base/UTexture.pas
index 97f244fe..c1334dd7 100644
--- a/cmake/src/base/UTexture.pas
+++ b/cmake/src/base/UTexture.pas
@@ -40,6 +40,7 @@ uses
   Classes,
   SysUtils,
   UCommon,
+  UPath,
   SDL,
   SDL_Image;
 
@@ -66,7 +67,7 @@ type
     TexX2:    real;
     TexY2:    real;
     Alpha:    real;
-    Name:     string; // experimental for handling cache images. maybe it's useful for dynamic skins
+    Name:     IPath; // experimental for handling cache images. maybe it's useful for dynamic skins
   end;
 
 type
@@ -91,7 +92,7 @@ procedure AdjustPixelFormat(var TexSurface: PSDL_Surface; Typ: TTextureType);
 type
   PTextureEntry = ^TTextureEntry;
   TTextureEntry = record
-    Name:         string;
+    Name:         IPath;
     Typ:          TTextureType;
     Color:        cardinal;
 
@@ -105,7 +106,7 @@ type
       Texture: array of TTextureEntry;
     public
       procedure AddTexture(var Tex: TTexture; Typ: TTextureType; Color: cardinal; Cache: boolean);
-      function FindTexture(const Name: string; Typ: TTextureType; Color: cardinal): integer;
+      function FindTexture(const Name: IPath; Typ: TTextureType; Color: cardinal): integer;
   end;
 
   TTextureUnit = class
@@ -116,14 +117,14 @@ type
 
       procedure AddTexture(var Tex: TTexture; Typ: TTextureType; Cache: boolean = false); overload;
       procedure AddTexture(var Tex: TTexture; Typ: TTextureType; Color: cardinal; Cache: boolean = false); overload;
-      function GetTexture(const Name: string; Typ: TTextureType; FromCache: boolean = false): TTexture; overload;
-      function GetTexture(const Name: string; Typ: TTextureType; Col: LongWord; FromCache: boolean = false): TTexture; overload;
-      function LoadTexture(FromRegistry: boolean; const Identifier: string; Typ: TTextureType; Col: LongWord): TTexture; overload;
-      function LoadTexture(const Identifier: string; Typ: TTextureType; Col: LongWord): TTexture; overload;
-      function LoadTexture(const Identifier: string): TTexture; overload;
-      function CreateTexture(Data: PChar; const Name: string; Width, Height: word; BitsPerPixel: byte): TTexture;
-      procedure UnloadTexture(const Name: string; Typ: TTextureType; FromCache: boolean); overload;
-      procedure UnloadTexture(const Name: string; Typ: TTextureType; Col: cardinal; FromCache: boolean); overload;
+      function GetTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean = false): TTexture; overload;
+      function GetTexture(const Name: IPath; Typ: TTextureType; Col: LongWord; FromCache: boolean = false): TTexture; overload;
+      function LoadTexture(FromRegistry: boolean; const Identifier: IPath; Typ: TTextureType; Col: LongWord): TTexture; overload;
+      function LoadTexture(const Identifier: IPath; Typ: TTextureType; Col: LongWord): TTexture; overload;
+      function LoadTexture(const Identifier: IPath): TTexture; overload;
+      function CreateTexture(Data: PChar; const Name: IPath; Width, Height: word; BitsPerPixel: byte): TTexture;
+      procedure UnloadTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean); overload;
+      procedure UnloadTexture(const Name: IPath; Typ: TTextureType; Col: cardinal; FromCache: boolean); overload;
       //procedure FlushTextureDatabase();
 
       constructor Create;
@@ -188,7 +189,7 @@ begin
     Texture[TextureIndex].Texture      := Tex;
 end;
 
-function TTextureDatabase.FindTexture(const Name: string; Typ: TTextureType; Color: cardinal): integer;
+function TTextureDatabase.FindTexture(const Name: IPath; Typ: TTextureType; Color: cardinal): integer;
 var
   TextureIndex: integer;
   CurrentTexture: PTextureEntry;
@@ -197,7 +198,7 @@ begin
   for TextureIndex := 0 to High(Texture) do
   begin
     CurrentTexture := @Texture[TextureIndex];
-    if (CurrentTexture.Name = Name) and
+    if (CurrentTexture.Name.Equals(Name)) and
        (CurrentTexture.Typ = Typ) then
     begin
       // colorized textures must match in their color too
@@ -235,18 +236,18 @@ begin
   TextureDatabase.AddTexture(Tex, Typ, Color, Cache);
 end;
 
-function TTextureUnit.LoadTexture(FromRegistry: boolean; const Identifier: string; Typ: TTextureType; Col: LongWord): TTexture;
+function TTextureUnit.LoadTexture(FromRegistry: boolean; const Identifier: IPath; Typ: TTextureType; Col: LongWord): TTexture;
 begin
   // FIXME: what is the FromRegistry parameter supposed to do?
   Result := LoadTexture(Identifier, Typ, Col);
 end;
 
-function TTextureUnit.LoadTexture(const Identifier: string): TTexture;
+function TTextureUnit.LoadTexture(const Identifier: IPath): TTexture;
 begin
   Result := LoadTexture(Identifier, TEXTURE_TYPE_PLAIN, 0);
 end;
 
-function TTextureUnit.LoadTexture(const Identifier: string; Typ: TTextureType; Col: LongWord): TTexture;
+function TTextureUnit.LoadTexture(const Identifier: IPath; Typ: TTextureType; Col: LongWord): TTexture;
 var
   TexSurface: PSDL_Surface;
   newWidth, newHeight: integer;
@@ -260,7 +261,7 @@ begin
   TexSurface := LoadImage(Identifier);
   if not assigned(TexSurface) then
   begin
-    Log.LogError('Could not load texture: "' + Identifier +'" with type "'+ TextureTypeToStr(Typ) +'"',
+    Log.LogError('Could not load texture: "' + Identifier.ToNative +'" with type "'+ TextureTypeToStr(Typ) +'"',
                  'TTextureUnit.LoadTexture');
     Exit;
   end;
@@ -363,16 +364,16 @@ begin
   SDL_FreeSurface(TexSurface);
 end;
 
-function TTextureUnit.GetTexture(const Name: string; Typ: TTextureType; FromCache: boolean): TTexture;
+function TTextureUnit.GetTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean): TTexture;
 begin
   Result := GetTexture(Name, Typ, 0, FromCache);
 end;
 
-function TTextureUnit.GetTexture(const Name: string; Typ: TTextureType; Col: LongWord; FromCache: boolean): TTexture;
+function TTextureUnit.GetTexture(const Name: IPath; Typ: TTextureType; Col: LongWord; FromCache: boolean): TTexture;
 var
   TextureIndex: integer;
 begin
-  if (Name = '') then
+  if (Name.IsUnset) then
   begin
     // zero texture data
     FillChar(Result, SizeOf(Result), 0);
@@ -413,7 +414,7 @@ begin
   Result := TextureDatabase.Texture[TextureIndex].Texture;
 end;
 
-function TTextureUnit.CreateTexture(Data: PChar; const Name: string; Width, Height: word; BitsPerPixel: byte): TTexture;
+function TTextureUnit.CreateTexture(Data: PChar; const Name: IPath; Width, Height: word; BitsPerPixel: byte): TTexture;
 var
   //Error:     integer;
   ActTex:    GLuint;
@@ -467,12 +468,12 @@ begin
   Result.Name := Name;
 end;
 
-procedure TTextureUnit.UnloadTexture(const Name: string; Typ: TTextureType; FromCache: boolean);
+procedure TTextureUnit.UnloadTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean);
 begin
   UnloadTexture(Name, Typ, 0, FromCache);
 end;
 
-procedure TTextureUnit.UnloadTexture(const Name: string; Typ: TTextureType; Col: cardinal; FromCache: boolean);
+procedure TTextureUnit.UnloadTexture(const Name: IPath; Typ: TTextureType; Col: cardinal; FromCache: boolean);
 var
   T:      integer;
   TexNum: GLuint;
@@ -539,7 +540,8 @@ begin
       Exit;
     end;
   end;
-  Log.LogWarn('Unknown texture type: "' + TypeStr + '". Using default texture type "' + TextureTypeToStr(Default) + '"', 'ParseTextureType');
+  Log.LogInfo('Unknown texture type: "' + TypeStr + '". Using default texture type "'
+      + TextureTypeToStr(Default) + '"', 'UTexture.ParseTextureType');
   Result := Default;
 end;
 
diff --git a/cmake/src/base/UThemes.pas b/cmake/src/base/UThemes.pas
index 3fd77853..b385406f 100644
--- a/cmake/src/base/UThemes.pas
+++ b/cmake/src/base/UThemes.pas
@@ -34,11 +34,12 @@ interface
 {$I switches.inc}
 
 uses
-  ULog,
   IniFiles,
   SysUtils,
   Classes,
-  UTexture;
+  ULog,
+  UTexture,
+  UPath;
 
 type
   TRGB = record
@@ -112,7 +113,7 @@ type
     Font:   integer;
     Size:   integer;
     Align:  integer;
-    Text:   string;
+    Text:   UTF8String;
     //Reflection
     Reflection:           boolean;
     ReflectionSpacing:    real;
@@ -169,7 +170,9 @@ type
 
   TThemeSelectSlide = record
     Tex:    string;
+    Typ:    TTextureType;
     TexSBG: string;
+    TypSBG: TTextureType;
     X:      integer;
     Y:      integer;
     W:      integer;
@@ -182,7 +185,7 @@ type
     showArrows:boolean;
     oneItemOnly:boolean;
 
-    Text:   string;
+    Text:   UTF8String;
     ColR,  ColG,  ColB,  Int:     real;
     DColR, DColG, DColB, DInt:    real;
     TColR,  TColG,  TColB,  TInt:     real;
@@ -215,7 +218,7 @@ type
   TThemeBasic = class
     Background:       TThemeBackground;
     Text:             AThemeText;
-    Static:           AThemeStatic;
+    Statics:           AThemeStatic;
 
     //Button Collection Mod
     ButtonCollection: AThemeButtonCollection;
@@ -236,8 +239,8 @@ type
 
     TextDescription:      TThemeText;
     TextDescriptionLong:  TThemeText;
-    Description:          array[0..5] of string;
-    DescriptionLong:      array[0..5] of string;
+    Description:          array[0..5] of UTF8String;
+    DescriptionLong:      array[0..5] of UTF8String;
   end;
 
   TThemeName = class(TThemeBasic)
@@ -354,7 +357,7 @@ type
     TextP3RScore:     TThemeText;
 
     //Linebonus Translations
-    LineBonusText:    array [0..8] of string;
+    LineBonusText:    array [0..8] of UTF8String;
 
     //Pause Popup
      PausePopUp:      TThemeStatic;
@@ -396,6 +399,7 @@ type
     StaticBackLevelRound:   array[1..6] of TThemeStatic;
     StaticLevel:            array[1..6] of TThemeStatic;
     StaticLevelRound:       array[1..6] of TThemeStatic;
+    StaticPlayerIdBox:      array[1..6] of TThemeStatic;
 
 //    Description:          array[0..5] of string;}
   end;
@@ -408,6 +412,7 @@ type
     TextNumber:       AThemeText;
     TextName:         AThemeText;
     TextScore:        AThemeText;
+    TextDate:         AThemeText;
   end;
 
   TThemeOptions = class(TThemeBasic)
@@ -421,7 +426,7 @@ type
     ButtonExit:       TThemeButton;
 
     TextDescription:      TThemeText;
-    Description:          array[0..7] of string;
+    Description:          array[0..7] of UTF8String;
   end;
 
   TThemeOptionsGame = class(TThemeBasic)
@@ -496,8 +501,8 @@ type
 
     TextDescription:      TThemeText;
     TextDescriptionLong:  TThemeText;
-    Description:          array[0..5] of string;
-    DescriptionLong:      array[0..5] of string;
+    Description:          array[0..5] of UTF8string;
+    DescriptionLong:      array[0..5] of UTF8string;
   end;
 
   //Error- and Check-Popup
@@ -531,10 +536,10 @@ type
     TextFound:        TThemeText;
 
     //Translated Texts
-    Songsfound:       string;
-    NoSongsfound:     string;
-    CatText:          string;
-    IType:            array [0..2] of string;
+    Songsfound:       UTF8String;
+    NoSongsfound:     UTF8String;
+    CatText:          UTF8String;
+    IType:            array [0..2] of UTF8String;
   end;
 
   //Party Screens
@@ -646,17 +651,17 @@ type
     SelectLevel: TThemeSelectSlide;
     SelectPlayList: TThemeSelectSlide;
     SelectPlayList2: TThemeSelectSlide;
-    SelectRounds: TThemeSelectSlide;
-    SelectTeams: TThemeSelectSlide;
-    SelectPlayers1: TThemeSelectSlide;
-    SelectPlayers2: TThemeSelectSlide;
-    SelectPlayers3: TThemeSelectSlide;
 
     {ButtonNext: TThemeButton;
     ButtonPrev: TThemeButton;}
   end;
 
   TThemePartyPlayer = class(TThemeBasic)
+    SelectTeams: TThemeSelectSlide;
+    SelectPlayers1: TThemeSelectSlide;
+    SelectPlayers2: TThemeSelectSlide;
+    SelectPlayers3: TThemeSelectSlide;
+
     Team1Name: TThemeButton;
     Player1Name: TThemeButton;
     Player2Name: TThemeButton;
@@ -679,6 +684,11 @@ type
     ButtonPrev: TThemeButton;}
   end;
 
+  TThemePartyRounds = class(TThemeBasic)
+    SelectRoundCount: TThemeSelectSlide;
+    SelectRound: array [0..6] of TThemeSelectSlide;
+  end;
+
   //Stats Screens
   TThemeStatMain = class(TThemeBasic)
     ButtonScores:     TThemeButton;
@@ -700,15 +710,22 @@ type
     TextPage:         TThemeText;
     TextList:         AThemeText;
 
-    Description:      array[0..3] of string;
-    DescriptionR:     array[0..3] of string;
-    FormatStr:        array[0..3] of string;
-    PageStr:          string;
+    Description:      array[0..3] of UTF8String;
+    DescriptionR:     array[0..3] of UTF8String;
+    FormatStr:        array[0..3] of UTF8String;
+    PageStr:          UTF8String;
   end;
 
   //Playlist Translations
   TThemePlaylist = record
-    CatText:    string;
+    CatText:    UTF8String;
+  end;
+
+  TThemeEntry = record
+    Name: string;
+    Filename: IPath;
+    DefaultSkin: integer;
+    Creator: string;
   end;
 
   TTheme = class
@@ -721,8 +738,9 @@ type
 
     LastThemeBasic:   TThemeBasic;
     procedure CreateThemeObjects();
-    
+    procedure LoadHeader(FileName: IPath);
   public
+    Themes:           array of TThemeEntry;
     Loading:          TThemeLoading;
     Main:             TThemeMain;
     Name:             TThemeName;
@@ -754,6 +772,7 @@ type
     PartyWin:         TThemePartyWin;
     PartyOptions:     TThemePartyOptions;
     PartyPlayer:      TThemePartyPlayer;
+    PartyRounds:      TThemePartyRounds;
 
     //Stats Screens:
     StatMain:         TThemeStatMain;
@@ -761,11 +780,13 @@ type
 
     Playlist:         TThemePlaylist;
 
-    ILevel: array[0..2] of string;
+    ILevel: array[0..2] of UTF8String;
+
+    constructor Create;
 
-    constructor Create(const FileName: string); overload; // Initialize theme system
-    constructor Create(const FileName: string; Color: integer); overload; // Initialize theme system with color
-    function LoadTheme(FileName: string; sColor: integer): boolean; // Load some theme settings from file
+    procedure LoadList;
+
+    function LoadTheme(ThemeNum: integer; sColor: integer): boolean; // Load some theme settings from file
 
     procedure LoadColors;
 
@@ -818,9 +839,13 @@ uses
   ULanguage,
   USkins,
   UIni,
+  UPathUtils,
+  UFileSystem,
+  TextGL,
   gl,
   glext,
-  math;
+  math,
+  StrUtils;
 
 //-----------
 //Helper procs to use TRGB in Opengl ...maybe this should be somewhere else
@@ -845,12 +870,7 @@ begin
   glColor4f(Color.R, Color.G, Color.B, Min(Color.A, Alpha));
 end;
 
-constructor TTheme.Create(const FileName: string);
-begin
-  Create(FileName, 0);
-end;
-
-constructor TTheme.Create(const FileName: string; Color: integer);
+constructor TTheme.Create;
 begin
   inherited Create();
 
@@ -884,16 +904,89 @@ begin
   PartyScore := TThemePartyScore.Create;
   PartyOptions := TThemePartyOptions.Create;
   PartyPlayer := TThemePartyPlayer.Create;
+  PartyRounds := TThemePartyRounds.Create;
 
   //Stats Screens:
   StatMain :=   TThemeStatMain.Create;
   StatDetail := TThemeStatDetail.Create;
 
-  LoadTheme(FileName, Color);
+  //LoadTheme(FileName, Color);
+  LoadList;
+end;
+
+procedure TTheme.LoadHeader(FileName: IPath);
+  var
+    Entry: TThemeEntry;
+    Ini: TMemIniFile;
+    SkinName: string;
+    SkinsFound: boolean;
+    ThemeVersion: string;
+    I: integer;
+    Len: integer;
+    Skins: TUTF8StringDynArray;
+begin
+  Entry.Filename := ThemePath.Append(FileName);
+  //read info from theme header
+  Ini := TMemIniFile.Create(Entry.Filename.ToNative);
+
+  Entry.Name := Ini.ReadString('Theme', 'Name', FileName.SetExtension('').ToNative);
+  ThemeVersion := Trim(UpperCase(Ini.ReadString('Theme', 'US_Version', 'no version tag')));
+  Entry.Creator := Ini.ReadString('Theme', 'Creator', 'Unknown');
+  SkinName := Ini.ReadString('Theme', 'DefaultSkin', FileName.SetExtension('').ToNative);
+
+  Ini.Free;
+
+  // don't load theme with wrong version tag
+  if ThemeVersion <> 'USD 110' then
+  begin
+    Log.LogWarn('Wrong Version (' + ThemeVersion + ') in Theme : ' + Entry.Name, 'Theme.LoadHeader');
+  end
+  else
+  begin
+    //Search for Skins for this Theme
+    SkinsFound := false;
+    for I := Low(Skin.Skin) to High(Skin.Skin) do
+    begin
+      if (CompareText(Skin.Skin[I].Theme, Entry.Name) = 0) then
+      begin
+        SkinsFound := true;
+        break;
+      end;
+    end;
+
+    if SkinsFound then
+    begin
+      { found a valid Theme }
+      // set correct default skin
+      Skin.GetSkinsByTheme(Entry.Name, Skins);
+      Entry.DefaultSkin := max(0, GetArrayIndex(Skins, SkinName, true));
+  
+      Len := Length(Themes);
+      SetLength(Themes, Len + 1);
+      SetLength(ITheme, Len + 1);
+      Themes[Len] := Entry;
+      ITheme[Len] := Entry.Name;
+    end;
+  end;
+end;
 
+procedure TTheme.LoadList;
+  var
+    Iter: IFileIterator;
+    FileInfo: TFileInfo;
+begin
+  Log.LogStatus('Searching for Theme : ' + ThemePath.ToNative + '*.ini', 'Theme.LoadList');
+
+  Iter := FileSystem.FileFind(ThemePath.Append('*.ini'), 0);
+  while (Iter.HasNext) do
+  begin
+    FileInfo := Iter.Next;
+    Log.LogStatus('Found Theme: ' + FileInfo.Name.ToNative, 'Theme.LoadList');
+    LoadHeader(Fileinfo.Name);
+  end;
 end;
 
-function TTheme.LoadTheme(FileName: string; sColor: integer): boolean;
+function TTheme.LoadTheme(ThemeNum: integer; sColor: integer): boolean;
 var
   I:    integer;
 begin
@@ -901,23 +994,21 @@ begin
 
   CreateThemeObjects();
 
-  Log.LogStatus('Loading: '+ FileName, 'TTheme.LoadTheme');
+  Log.LogStatus('Loading: '+ Themes[ThemeNum].FileName.ToNative, 'TTheme.LoadTheme');
 
-  FileName := AdaptFilePaths(FileName);
-
-  if not FileExists(FileName) then
+  if not Themes[ThemeNum].FileName.IsFile() then
   begin
-    Log.LogError('Theme does not exist ('+ FileName +')', 'TTheme.LoadTheme');
+    Log.LogError('Theme does not exist ('+ Themes[ThemeNum].FileName.ToNative +')', 'TTheme.LoadTheme');
   end;
 
-  if FileExists(FileName) then
+  if Themes[ThemeNum].FileName.IsFile() then
   begin
     Result := true;
 
     {$IFDEF THEMESAVE}
-    ThemeIni := TIniFile.Create(FileName);
+    ThemeIni := TIniFile.Create(Themes[ThemeNum].FileName.ToNative);
     {$ELSE}
-    ThemeIni := TMemIniFile.Create(FileName);
+    ThemeIni := TMemIniFile.Create(Themes[ThemeNum].FileName.ToNative);
     {$ENDIF}
 
     if ThemeIni.ReadString('Theme', 'Name', '') <> '' then
@@ -1164,6 +1255,7 @@ begin
         ThemeLoadStatic(Score.StaticBackLevelRound[I], 'ScoreStaticBackLevelRound' + IntToStr(I));
         ThemeLoadStatic(Score.StaticLevel[I],          'ScoreStaticLevel'          + IntToStr(I));
         ThemeLoadStatic(Score.StaticLevelRound[I],     'ScoreStaticLevelRound'     + IntToStr(I));
+        ThemeLoadStatic(Score.StaticPlayerIdBox[I],    'ScoreStaticPlayerIdBox'    + IntToStr(I));
 
         ThemeLoadStatic(Score.StaticRatings[I],        'ScoreStaticRatingPicture'  + IntToStr(I));
       end;
@@ -1177,6 +1269,7 @@ begin
       ThemeLoadTexts(Top5.TextNumber,     'Top5TextNumber');
       ThemeLoadTexts(Top5.TextName,       'Top5TextName');
       ThemeLoadTexts(Top5.TextScore,      'Top5TextScore');
+      ThemeLoadTexts(Top5.TextDate,       'Top5TextDate');
 
       // Options
       ThemeLoadBasic(Options, 'Options');
@@ -1434,17 +1527,17 @@ begin
       ThemeLoadSelectSlide(PartyOptions.SelectLevel, 'PartyOptionsSelectLevel');
       ThemeLoadSelectSlide(PartyOptions.SelectPlayList, 'PartyOptionsSelectPlayList');
       ThemeLoadSelectSlide(PartyOptions.SelectPlayList2, 'PartyOptionsSelectPlayList2');
-      ThemeLoadSelectSlide(PartyOptions.SelectRounds, 'PartyOptionsSelectRounds');
-      ThemeLoadSelectSlide(PartyOptions.SelectTeams, 'PartyOptionsSelectTeams');
-      ThemeLoadSelectSlide(PartyOptions.SelectPlayers1, 'PartyOptionsSelectPlayers1');
-      ThemeLoadSelectSlide(PartyOptions.SelectPlayers2, 'PartyOptionsSelectPlayers2');
-      ThemeLoadSelectSlide(PartyOptions.SelectPlayers3, 'PartyOptionsSelectPlayers3');
-
       {ThemeLoadButton (ButtonNext, 'ButtonNext');
       ThemeLoadButton (ButtonPrev, 'ButtonPrev');}
 
       //Party Player
       ThemeLoadBasic(PartyPlayer, 'PartyPlayer');
+
+      ThemeLoadSelectSlide(PartyPlayer.SelectTeams, 'PartyPlayerSelectTeams');
+      ThemeLoadSelectSlide(PartyPlayer.SelectPlayers1, 'PartyPlayerSelectPlayers1');
+      ThemeLoadSelectSlide(PartyPlayer.SelectPlayers2, 'PartyPlayerSelectPlayers2');
+      ThemeLoadSelectSlide(PartyPlayer.SelectPlayers3, 'PartyPlayerSelectPlayers3');
+
       ThemeLoadButton(PartyPlayer.Team1Name, 'PartyPlayerTeam1Name');
       ThemeLoadButton(PartyPlayer.Player1Name, 'PartyPlayerPlayer1Name');
       ThemeLoadButton(PartyPlayer.Player2Name, 'PartyPlayerPlayer2Name');
@@ -1463,6 +1556,13 @@ begin
       ThemeLoadButton(PartyPlayer.Player11Name, 'PartyPlayerPlayer11Name');
       ThemeLoadButton(PartyPlayer.Player12Name, 'PartyPlayerPlayer12Name');
 
+      // Party Rounds
+      ThemeLoadBasic(PartyRounds, 'PartyRounds');
+
+      ThemeLoadSelectSlide(PartyRounds.SelectRoundCount, 'PartyRoundsSelectRoundCount');
+      for I := 0 to High(PartyRounds.SelectRound) do
+        ThemeLoadSelectSlide(PartyRounds.SelectRound[I], 'PartyRoundsSelectRound' + IntToStr(I + 1));
+
       {ThemeLoadButton(ButtonNext, 'PartyPlayerButtonNext');
       ThemeLoadButton(ButtonPrev, 'PartyPlayerButtonPrev');}
 
@@ -1524,7 +1624,7 @@ procedure TTheme.ThemeLoadBasic(Theme: TThemeBasic; const Name: string);
 begin
   ThemeLoadBackground(Theme.Background, Name);
   ThemeLoadTexts(Theme.Text, Name + 'Text');
-  ThemeLoadStatics(Theme.Static, Name + 'Static');
+  ThemeLoadStatics(Theme.Statics, Name + 'Static');
   ThemeLoadButtonCollections(Theme.ButtonCollection, Name + 'ButtonCollection');
 
   LastThemeBasic := Theme;
@@ -1568,7 +1668,7 @@ begin
   ThemeText.ColG  := ThemeIni.ReadFloat(Name, 'ColG', 0);
   ThemeText.ColB  := ThemeIni.ReadFloat(Name, 'ColB', 0);
 
-  ThemeText.Font  := ThemeIni.ReadInteger(Name, 'Font', 0);
+  ThemeText.Font  := ThemeIni.ReadInteger(Name, 'Font', ftNormal);
   ThemeText.Size  := ThemeIni.ReadInteger(Name, 'Size', 0);
   ThemeText.Align := ThemeIni.ReadInteger(Name, 'Align', 0);
 
@@ -1773,7 +1873,9 @@ begin
   ThemeSelectS.Text := Language.Translate(ThemeIni.ReadString(Name, 'Text', ''));
 
   ThemeSelectS.Tex := {Skin.SkinPath + }ThemeIni.ReadString(Name, 'Tex', '');
+  ThemeSelectS.Typ := ParseTextureType(ThemeIni.ReadString(Name, 'Type', ''), TEXTURE_TYPE_PLAIN);
   ThemeSelectS.TexSBG := {Skin.SkinPath + }ThemeIni.ReadString(Name, 'TexSBG', '');
+  ThemeSelectS.TypSBG := ParseTextureType(ThemeIni.ReadString(Name, 'TypeSBG', ''), TEXTURE_TYPE_PLAIN);
 
   ThemeSelectS.X := ThemeIni.ReadInteger(Name, 'X', 0);
   ThemeSelectS.Y := ThemeIni.ReadInteger(Name, 'Y', 0);
@@ -1807,6 +1909,9 @@ begin
   ThemeSelectS.STInt :=  ThemeIni.ReadFloat(Name, 'STInt', 1);
   LoadColor(ThemeSelectS.STDColR, ThemeSelectS.STDColG,  ThemeSelectS.STDColB, ThemeIni.ReadString(Name, 'STDColor', ''));
   ThemeSelectS.STDInt :=  ThemeIni.ReadFloat(Name, 'STDInt', 1);
+
+  ThemeSelectS.showArrows := (ThemeIni.ReadInteger(Name, 'ShowArrows', 0) = 1);
+  ThemeSelectS.oneItemOnly := (ThemeIni.ReadInteger(Name, 'OneItemOnly', 0) = 1);
 end;
 
 procedure TTheme.ThemeLoadEqualizer(var ThemeEqualizer: TThemeEqualizer; const Name: string);
@@ -2023,15 +2128,15 @@ begin
         //New Theme-Color Patch
     4:  begin
           // violet
-          Result.R := 230/255;
-          Result.G := 63/255;
-          Result.B := 230/255;
+          Result.R := 212/255;
+          Result.G := 71/255;
+          Result.B := 247/255;
         end;
     5:  begin
           // orange
-          Result.R := 255/255;
+          Result.R := 247/255;
           Result.G := 144/255;
-          Result.B := 0;
+          Result.B := 71/255;
         end;
     6:  begin
           // yellow
@@ -2193,7 +2298,7 @@ begin
   ThemeIni.WriteInteger(Name, 'Texts', Length(Theme.Text));
 
   ThemeSaveBackground(Theme.Background, Name + 'Background');
-  ThemeSaveStatics(Theme.Static, Name + 'Static');
+  ThemeSaveStatics(Theme.Statics, Name + 'Static');
   ThemeSaveTexts(Theme.Text, Name + 'Text');
 end;
 
diff --git a/cmake/src/base/UTime.pas b/cmake/src/base/UTime.pas
index 83844cb5..0610ef59 100644
--- a/cmake/src/base/UTime.pas
+++ b/cmake/src/base/UTime.pas
@@ -40,20 +40,26 @@ type
       function GetTime(): real;
   end;
 
+  TRelativeTimerState = (rtsStopped, rtsWait, rtsPaused, rtsRunning);
+
   TRelativeTimer = class
     private
       AbsoluteTime: int64;      // system-clock reference time for calculation of CurrentTime
-      RelativeTimeOffset: real;
-      Paused: boolean;
+      RelativeTime: real;
       TriggerMode: boolean;
+      State: TRelativeTimerState;
     public
-      constructor Create(TriggerMode: boolean = false);
+      constructor Create();
+      procedure Start(WaitForTrigger: boolean = false);
       procedure Pause();
-      procedure Resume();
+      procedure Stop();
       function GetTime(): real;
-      function GetAndResetTime(): real;
-      procedure SetTime(Time: real; Trigger: boolean = true);
-      procedure Reset();
+      procedure SetTime(Time: real);
+      function GetState(): TRelativeTimerState;
+  end;
+
+  TSyncSource = class
+    function GetClock(): real; virtual; abstract;
   end;
 
 procedure CountSkipTimeSet;
@@ -126,85 +132,115 @@ end;
  * TRelativeTimer
  **}
 
-(*
- * creates a new timer.
- * if triggermode is false (default), the timer
- * will immediately begin with counting.
- * if triggermode is true, it will wait until get/settime() or pause() is called
- * for the first time.
+(**
+ * Creates a new relative timer.
+ * A relative timer works like a stop-watch. It can be paused and
+ * resumed afterwards, continuing with the counter it had when it was paused.
  *)
-constructor TRelativeTimer.Create(TriggerMode: boolean);
+constructor TRelativeTimer.Create();
 begin
-  inherited Create();
-  Self.TriggerMode := TriggerMode;
-  Reset();
-  Paused := false;
+  State := rtsStopped;
+  AbsoluteTime := 0;
+  RelativeTime := 0;
 end;
 
-procedure TRelativeTimer.Pause();
+(**
+ * Starts the timer.
+ * If WaitForTrigger is false the timer will be started immediately.
+ * If WaitForTrigger is true the timer will be started when a trigger event
+ * occurs. A trigger event is a call of one of the Get-/SetTime() methods.
+ * In addition the timer can be started by calling this method again with
+ * WaitForTrigger set to false.
+ *)
+procedure TRelativeTimer.Start(WaitForTrigger: boolean = false);
 begin
-  RelativeTimeOffset := GetTime();
-  Paused := true;
+  case (State) of
+    rtsStopped, rtsPaused: begin
+      if (WaitForTrigger) then
+      begin
+        State := rtsWait;
+      end
+      else
+      begin
+        State := rtsRunning;
+        AbsoluteTime := SDL_GetTicks();
+      end;
+    end;
+
+    rtsWait: begin
+      if (not WaitForTrigger) then
+      begin
+        State := rtsRunning;
+        AbsoluteTime := SDL_GetTicks();
+        RelativeTime := 0;
+      end;
+    end;
+  end;
 end;
 
-procedure TRelativeTimer.Resume();
+(**
+ * Pauses the timer and leaves the counter untouched.
+ *)
+procedure TRelativeTimer.Pause();
 begin
-  AbsoluteTime := SDL_GetTicks();
-  Paused := false;
+  if (State = rtsRunning) then
+  begin
+    // Important: GetTime() must be called in running state
+    RelativeTime := GetTime();
+    State := rtsPaused;
+  end;
 end;
 
-(*
- * Returns the counter of the timer.
- * If in TriggerMode it will return 0 and start the counter on the first call.
+(**
+ * Stops the timer and sets its counter to 0.
  *)
-function TRelativeTimer.GetTime: real;
+procedure TRelativeTimer.Stop();
 begin
-  // initialize absolute time on first call in triggered mode
-  if (TriggerMode and (AbsoluteTime = 0)) then
+  if (State <> rtsStopped) then
   begin
-    AbsoluteTime := SDL_GetTicks();
-    Result := RelativeTimeOffset;
-    Exit;
+    State := rtsStopped;
+    RelativeTime := 0;
   end;
-
-  if Paused then
-    Result := RelativeTimeOffset
-  else
-    Result := RelativeTimeOffset + (SDL_GetTicks() - AbsoluteTime) / cSDLCorrectionRatio;
 end;
 
-(*
- * Returns the counter of the timer and resets the counter to 0 afterwards.
- * Note: In TriggerMode the counter will not be stopped as with Reset().
+(**
+ * Returns the current counter of the timer.
+ * If WaitForTrigger was true in Start() the timer will be started
+ * if it was not already running.
  *)
-function TRelativeTimer.GetAndResetTime(): real;
+function TRelativeTimer.GetTime(): real;
 begin
-  Result := GetTime();
-  SetTime(0);
+  case (State) of
+    rtsStopped, rtsPaused:
+      Result := RelativeTime;
+    rtsRunning:
+      Result := RelativeTime + (SDL_GetTicks() - AbsoluteTime) / cSDLCorrectionRatio;
+    rtsWait: begin
+      // start triggered
+      State := rtsRunning;
+      AbsoluteTime := SDL_GetTicks();
+      Result := RelativeTime;
+    end;
+  end;
 end;
 
-(*
- * Sets the timer to the given time. This will trigger in TriggerMode if
- * Trigger is set to true. Otherwise the counter's state will not change.
+(**
+ * Sets the counter of the timer.
+ * If WaitForTrigger was true in Start() the timer will be started
+ * if it was not already running.
  *)
-procedure TRelativeTimer.SetTime(Time: real; Trigger: boolean);
+procedure TRelativeTimer.SetTime(Time: real);
 begin
-  RelativeTimeOffset := Time;
-  if ((not TriggerMode) or Trigger) then
-    AbsoluteTime := SDL_GetTicks();
+  RelativeTime := Time;
+  AbsoluteTime := SDL_GetTicks();
+  // start triggered
+  if (State = rtsWait) then
+    State := rtsRunning;
 end;
 
-(*
- * Resets the counter of the timer to 0.
- * If in TriggerMode the timer will not start counting until it is triggered again.
- *)
-procedure TRelativeTimer.Reset();
+function TRelativeTimer.GetState(): TRelativeTimerState;
 begin
-  RelativeTimeOffset := 0;
-  if (TriggerMode) then
-    AbsoluteTime := 0
-  else
-    AbsoluteTime := SDL_GetTicks();
+  Result := State;
 end;
 
 end.
diff --git a/cmake/src/base/UUnicodeUtils.pas b/cmake/src/base/UUnicodeUtils.pas
new file mode 100644
index 00000000..37b53a67
--- /dev/null
+++ b/cmake/src/base/UUnicodeUtils.pas
@@ -0,0 +1,670 @@
+{* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *}
+
+unit UUnicodeUtils;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+uses
+{$IFDEF MSWINDOWS}
+  Windows,
+{$ENDIF}
+  StrUtils,
+  SysUtils;
+
+type
+  // String with unknown encoding. Introduced with Delphi 2009 and maybe soon
+  // with FPC.
+  RawByteString = AnsiString;
+
+{**
+ * Returns true if the system uses UTF-8 as default string type
+ * (filesystem or API calls).
+ * This is always true on Mac OS X and always false on Win32. On Unix it depends
+ * on the LC_CTYPE setting.
+ * Do not use AnsiToUTF8() or UTF8ToAnsi() if this function returns true.
+ *}
+function IsNativeUTF8(): boolean;
+
+(*
+ * Character classes
+ *)
+
+function IsAlphaChar(ch: WideChar): boolean; overload;
+function IsAlphaChar(ch: UCS4Char): boolean; overload;
+
+function IsNumericChar(ch: WideChar): boolean; overload;
+function IsNumericChar(ch: UCS4Char): boolean; overload;
+
+function IsAlphaNumericChar(ch: WideChar): boolean; overload;
+function IsAlphaNumericChar(ch: UCS4Char): boolean; overload;
+
+function IsPunctuationChar(ch: WideChar): boolean; overload;
+function IsPunctuationChar(ch: UCS4Char): boolean; overload;
+
+function IsControlChar(ch: WideChar): boolean; overload;
+function IsControlChar(ch: UCS4Char): boolean; overload;
+
+function IsPrintableChar(ch: WideChar): boolean; overload;
+function IsPrintableChar(ch: UCS4Char): boolean; overload;
+
+{**
+ * Checks if the given string is a valid UTF-8 string.
+ * If an ANSI encoded string (with char codes >= 128) is passed, the
+ * function will most probably return false, as most ANSI strings sequences
+ * are illegal in UTF-8.
+ *}
+function IsUTF8String(const str: RawByteString): boolean;
+
+{**
+ * Iterates over an UTF-8 encoded string.
+ * StrPtr will be  increased to the beginning of the next character on each
+ * call.
+ * Results true if the given string starts with an UTF-8 encoded char.
+ *}
+function NextCharUTF8(var StrPtr: PAnsiChar; out Ch: UCS4Char): boolean;
+
+{**
+ * Deletes Count chars (not bytes) beginning at char- (not byte-) position Index.
+ * Index values start with 1.
+ *}
+procedure UTF8Delete(var Str: UTF8String; Index: Integer; Count: Integer);
+procedure UCS4Delete(var Str: UCS4String; Index: Integer; Count: Integer);
+
+{**
+ * Checks if the string is composed of ASCII characters.
+ *}
+function IsASCIIString(const str: RawByteString): boolean;
+
+{*
+ * String format conversion
+ *}
+
+function UTF8ToUCS4String(const str: UTF8String): UCS4String;
+function UCS4ToUTF8String(const str: UCS4String): UTF8String; overload;
+function UCS4ToUTF8String(ch: UCS4Char): UTF8String; overload;
+
+{**
+ * Returns the number of characters (not bytes) in string str.
+ *}
+function LengthUTF8(const str: UTF8String): integer;
+
+{**
+ * Returns the length of an UCS4String. Note that Length(UCS4String) returns
+ * the length+1 as UCS4Strings are zero-terminated.
+ *}
+function LengthUCS4(const str: UCS4String): integer;
+
+{** @seealso WideCompareStr *}
+function UTF8CompareStr(const S1, S2: UTF8String): integer;
+{** @seealso WideCompareText *}
+function UTF8CompareText(const S1, S2: UTF8String): integer;
+
+function UTF8StartsText(const SubText, Text: UTF8String): boolean;
+
+function UTF8ContainsStr(const Text, SubText: UTF8String): boolean;
+function UTF8ContainsText(const Text, SubText: UTF8String): boolean;
+
+{** @seealso WideUpperCase *}
+function UTF8UpperCase(const str: UTF8String): UTF8String;
+{** @seealso WideCompareText *}
+function UTF8LowerCase(const str: UTF8String): UTF8String;
+
+{**
+ * Converts a UCS-4 char ch to its upper-case representation.
+ *}
+function UCS4UpperCase(ch: UCS4Char): UCS4Char; overload;
+
+{**
+ * Converts a UCS-4 string str to its upper-case representation.
+ *}
+function UCS4UpperCase(const str: UCS4String): UCS4String; overload;
+
+{**
+ * Converts a UCS4Char to an UCS4String.
+ * Note that UCS4Strings are zero-terminated dynamic arrays.
+ *}
+function UCS4CharToString(ch: UCS4Char): UCS4String;
+
+{**
+ * @seealso System.Pos()
+ *}
+function UTF8Pos(const substr: UTF8String; const str: UTF8String): Integer;
+
+{**
+ * Copies a segment of str starting with Index (1-based) with Count characters (not bytes).
+ *}
+function UTF8Copy(const str: UTF8String; Index: Integer = 1; Count: Integer = -1): UTF8String;
+
+{**
+ * Copies a segment of str starting with Index (0-based) with Count characters.
+ * Note: Do not use Copy() to copy UCS4Strings as the result will not contain
+ * a trailing #0 character and hence is invalid.
+ *}
+function UCS4Copy(const str: UCS4String; Index: Integer = 0; Count: Integer = -1): UCS4String;
+
+(*
+ * Converts a WideString to its upper- or lower-case representation.
+ * Wrapper for WideUpper/LowerCase. Needed because some plattforms have
+ * problems with unicode support.
+ *
+ * Note that characters in UTF-16 might consist of one or two WideChar valus
+ * (see surrogates). So instead of using WideStringUpperCase(ch)[1] for single
+ * character access, convert to UCS-4 where each character is represented by
+ * one UCS4Char. 
+ *)
+function WideStringUpperCase(const str: WideString) : WideString; overload;
+function WideStringUpperCase(ch: WideChar): WideString; overload;
+function WideStringLowerCase(const str: WideString): WideString; overload;
+function WideStringLowerCase(ch: WideChar): WideString; overload;
+
+function WideStringReplaceChar(const text: WideString; search, rep: WideChar): WideString;
+
+implementation
+
+{$IFDEF UNIX}
+{$IFNDEF DARWIN}
+const
+  LC_CTYPE = 0;
+
+function setlocale(category: integer; locale: PChar): PChar; cdecl; external 'c';
+{$ENDIF}
+{$ENDIF}
+
+var
+  NativeUTF8: boolean;
+
+procedure InitUnicodeUtils();
+{$IFDEF UNIX}
+{$IFNDEF DARWIN}
+var
+  localeName: PChar;
+{$ENDIF}
+{$ENDIF}
+begin
+  {$IF Defined(DARWIN)}
+    NativeUTF8 := true;
+  {$ELSEIF Defined(MSWindows)}
+    NativeUTF8 := false;
+  {$ELSEIF Defined(UNIX)}
+    // check if locale name contains UTF8 or UTF-8
+    localeName := setlocale(LC_CTYPE, nil);
+    NativeUTF8 := Pos('UTF8', UpperCase(AnsiReplaceStr(localeName, '-', ''))) > 0;
+  {$ELSE}
+    raise Exception.Create('Unknown system');
+  {$IFEND}
+end;
+
+function IsNativeUTF8(): boolean;
+begin
+  Result := NativeUTF8;
+end;
+
+function IsAlphaChar(ch: WideChar): boolean;
+begin
+  {$IFDEF MSWINDOWS}
+    Result := IsCharAlphaW(ch);
+  {$ELSE}
+    // TODO: add chars > 255 (or replace with libxml2 functions?)
+    case ch of
+      'A'..'Z',  // A-Z
+      'a'..'z',  // a-z
+      #170,#181,#186,
+      #192..#214,
+      #216..#246,
+      #248..#255:
+        Result := true;
+      else
+        Result := false;
+    end;
+  {$ENDIF}
+end;
+
+function IsAlphaChar(ch: UCS4Char): boolean;
+begin
+  Result := IsAlphaChar(WideChar(Ord(ch)));
+end;
+
+function IsNumericChar(ch: WideChar): boolean;
+begin
+  // TODO: replace with libxml2 functions?
+  // ignore non-arabic numerals as we do not want to handle them
+  case ch of
+    '0'..'9':
+      Result := true;
+    else
+      Result := false;
+  end;
+end;
+
+function IsNumericChar(ch: UCS4Char): boolean;
+begin
+  Result := IsNumericChar(WideChar(Ord(ch)));
+end;
+
+function IsAlphaNumericChar(ch: WideChar): boolean;
+begin
+  Result := (IsAlphaChar(ch) or IsNumericChar(ch));
+end;
+
+function IsAlphaNumericChar(ch: UCS4Char): boolean;
+begin
+  Result := (IsAlphaChar(ch) or IsNumericChar(ch));
+end;
+
+function IsPunctuationChar(ch: WideChar): boolean;
+begin
+  // TODO: add chars > 255 (or replace with libxml2 functions?)
+  case ch of
+    ' '..'/',':'..'@','['..'`','{'..'~',
+    #160..#191,#215,#247:
+      Result := true;
+    else
+      Result := false;
+  end;
+end;
+
+function IsPunctuationChar(ch: UCS4Char): boolean;
+begin
+  Result := IsPunctuationChar(WideChar(Ord(ch)));
+end;
+
+function IsControlChar(ch: WideChar): boolean;
+begin
+  case ch of
+    #0..#31,
+    #127..#159:
+      Result := true;
+    else
+      Result := false;
+  end;
+end;
+
+function IsControlChar(ch: UCS4Char): boolean;
+begin
+  Result := IsControlChar(WideChar(Ord(ch)));
+end;
+
+function IsPrintableChar(ch: WideChar): boolean;
+begin
+  Result := not IsControlChar(ch);
+end;
+
+function IsPrintableChar(ch: UCS4Char): boolean;
+begin
+  Result := IsPrintableChar(WideChar(Ord(ch)));
+end;
+
+
+function NextCharUTF8(var StrPtr: PAnsiChar; out Ch: UCS4Char): boolean;
+
+  // find the most significant zero bit (Result: [7..-1])
+  function FindZeroMSB(b: byte): integer;
+  var
+    Mask: byte;
+  begin
+    Mask := $80;
+    Result := 7;
+    while (b and Mask <> 0) do
+    begin
+      Mask := Mask shr 1;
+      Dec(Result);
+    end;
+  end;
+
+var
+  ZeroBit: integer;
+  SeqCount: integer; // number of trailing bytes to follow
+const
+  Mask: array[1..3] of byte = ($1F, $0F, $07);
+begin
+  Result := false;
+  SeqCount := 0;
+  Ch := 0;
+
+  while (StrPtr^ <> #0) do
+  begin
+    if (StrPtr^ < #128) then
+    begin
+      // check that no more trailing bytes are expected
+      if (SeqCount = 0) then
+      begin
+        Ch := Ord(StrPtr^);
+        Inc(StrPtr);
+        Result := true;
+      end;
+      Break;
+    end
+    else
+    begin
+      ZeroBit := FindZeroMSB(Ord(StrPtr^));
+      // trailing byte expected
+      if (SeqCount > 0) then
+      begin
+        // check if trailing byte has pattern 10xxxxxx
+        if (ZeroBit <> 6) then
+        begin
+          Inc(StrPtr);
+          Break;
+        end;
+
+        Dec(SeqCount);
+        Ch := (Ch shl 6) or (Ord(StrPtr^) and $3F);
+
+        // check if char is finished
+        if (SeqCount = 0) then
+        begin
+          Inc(StrPtr);
+          Result := true;
+          Break;
+        end;
+      end
+      else // leading byte expected
+      begin
+        // check if pattern is one of 110xxxxx/1110xxxx/11110xxx
+        if (ZeroBit > 5) or (ZeroBit < 3) then
+        begin
+          Inc(StrPtr);
+          Break;
+        end;
+        // calculate number of trailing bytes (1, 2 or 3)
+        SeqCount := 6 - ZeroBit;
+        // extract first part of char
+        Ch := Ord(StrPtr^) and Mask[SeqCount];
+      end;
+    end;
+
+    Inc(StrPtr);
+  end;
+
+  if (not Result) then
+    Ch := Ord('?');
+end;
+
+function IsUTF8String(const str: RawByteString): boolean;
+var
+  Ch: UCS4Char;
+  StrPtr: PAnsiChar;
+begin
+  Result := true;
+  StrPtr := PChar(str);
+  while (StrPtr^ <> #0) do
+  begin
+    if (not NextCharUTF8(StrPtr, Ch)) then
+    begin
+      Result := false;
+      Exit;
+    end;
+  end;
+end;
+
+function IsASCIIString(const str: RawByteString): boolean;
+var
+  I: integer;
+begin
+  for I := 1 to Length(str) do
+  begin
+    if (str[I] >= #128) then
+    begin
+      Result := false;
+      Exit;
+    end;
+  end;    
+  Result := true;
+end;
+
+
+function UTF8ToUCS4String(const str: UTF8String): UCS4String;
+begin
+  Result := WideStringToUCS4String(UTF8Decode(str));
+end;
+
+function UCS4ToUTF8String(const str: UCS4String): UTF8String;
+begin
+  Result := UTF8Encode(UCS4StringToWideString(str));
+end;
+
+function UCS4ToUTF8String(ch: UCS4Char): UTF8String;
+begin
+  Result := UCS4ToUTF8String(UCS4CharToString(ch));
+end;
+
+function LengthUTF8(const str: UTF8String): integer;
+begin
+  Result := LengthUCS4(UTF8ToUCS4String(str));
+end;
+
+function LengthUCS4(const str: UCS4String): integer;
+begin
+  Result := High(str);
+  if (Result = -1) then
+    Result := 0;
+end;
+
+function UTF8CompareStr(const S1, S2: UTF8String): integer;
+begin
+  Result := WideCompareStr(UTF8Decode(S1), UTF8Decode(S2));
+end;
+
+function UTF8CompareText(const S1, S2: UTF8String): integer;
+begin
+  Result := WideCompareText(UTF8Decode(S1), UTF8Decode(S2));
+end;
+
+function UTF8StartsStr(const SubText, Text: UTF8String): boolean;
+begin
+  // TODO: use WideSameStr (slower but handles different representations of the same char)?
+  Result := (Pos(SubText, Text) = 1);
+end;
+
+function UTF8StartsText(const SubText, Text: UTF8String): boolean;
+begin
+  // TODO: use WideSameText (slower but handles different representations of the same char)?
+  Result := (Pos(UTF8UpperCase(SubText), UTF8UpperCase(Text)) = 1);
+end;
+
+function UTF8ContainsStr(const Text, SubText: UTF8String): boolean;
+begin
+  Result := Pos(SubText, Text) > 0;
+end;
+
+function UTF8ContainsText(const Text, SubText: UTF8String): boolean;
+begin
+  Result := Pos(UTF8UpperCase(SubText), UTF8UpperCase(Text)) > 0;
+end;
+
+function UTF8UpperCase(const str: UTF8String): UTF8String;
+begin
+  Result := UTF8Encode(WideStringUpperCase(UTF8Decode(str)));
+end;
+
+function UTF8LowerCase(const str: UTF8String): UTF8String;
+begin
+  Result := UTF8Encode(WideStringLowerCase(UTF8Decode(str)));
+end;
+
+function UCS4UpperCase(ch: UCS4Char): UCS4Char;
+begin
+  Result := UCS4UpperCase(UCS4CharToString(ch))[0];
+end;
+
+function UCS4UpperCase(const str: UCS4String): UCS4String;
+begin
+  // convert to upper-case as WideString and convert result back to UCS-4
+  Result := WideStringToUCS4String(
+            WideStringUpperCase(
+            UCS4StringToWideString(str)));
+end;
+
+function UCS4CharToString(ch: UCS4Char): UCS4String;
+begin
+  SetLength(Result, 2);
+  Result[0] := ch;
+  Result[1] := 0;
+end;
+
+function UTF8Pos(const substr: UTF8String; const str: UTF8String): Integer;
+begin
+  Result := Pos(substr, str);
+end;
+
+function UTF8Copy(const str: UTF8String; Index: Integer; Count: Integer): UTF8String;
+begin
+  Result := UCS4ToUTF8String(UCS4Copy(UTF8ToUCS4String(str), Index-1, Count));
+end;
+
+function UCS4Copy(const str: UCS4String; Index: Integer; Count: Integer): UCS4String;
+var
+  I: integer;
+  MaxCount: integer;
+begin
+  // calculate max. copy count
+  MaxCount := LengthUCS4(str)-Index;
+  if (MaxCount < 0) then
+    MaxCount := 0;
+  // adjust copy count
+  if (Count > MaxCount) or (Count < 0) then
+    Count := MaxCount;
+
+  // copy (and add zero terminator)
+  SetLength(Result, Count + 1);
+  for I := 0 to Count-1 do
+    Result[I] := str[Index+I];
+  Result[Count] := 0;
+end;
+
+procedure UTF8Delete(var Str: UTF8String; Index: Integer; Count: Integer);
+var
+  StrUCS4: UCS4String;
+begin
+  StrUCS4 := UTF8ToUCS4String(str);
+  UCS4Delete(StrUCS4, Index-1, Count);
+  Str := UCS4ToUTF8String(StrUCS4);
+end;
+
+procedure UCS4Delete(var Str: UCS4String; Index: Integer; Count: Integer);
+var
+  Len: integer;
+  OldStr: UCS4String;
+  I: integer;
+begin
+  Len := LengthUCS4(Str);
+  if (Count <= 0) or (Index < 0) or (Index >= Len) then
+    Exit;
+  if (Index + Count > Len) then
+    Count := Len-Index;
+  
+  OldStr := Str;
+  SetLength(Str, Len-Count+1);
+  for I := 0 to Index-1 do
+    Str[I] := OldStr[I];
+  for I := Index+Count to Len-1 do
+    Str[I-Count] := OldStr[I];
+  Str[High(Str)] := 0;
+end;
+
+function WideStringUpperCase(ch: WideChar): WideString;
+begin
+  // If WideChar #0 is converted to a WideString in Delphi, a string with
+  // length 1 and a single char #0 is returned. In FPC an empty (length=0)
+  // string will be returned. This will crash, if a non printable key was
+  // pressed, its char code (#0) is translated to upper-case and the the first
+  // character is accessed with Result[1].
+  // We cannot catch this error in the WideString parameter variant as the string
+  // has length 0 already.
+  
+  // Force min. string length of 1
+  if (ch = #0) then
+    Result := #0
+  else
+    Result := WideStringUpperCase(WideString(ch));
+end;
+
+function WideStringUpperCase(const str: WideString): WideString;
+begin
+  // On Linux and MacOSX the cwstring unit is necessary for Unicode function-calls.
+  // Otherwise you will get an EIntOverflow exception (thrown by unimplementedwidestring()).
+  // The Unicode manager cwstring does not work with MacOSX at the moment because
+  // of missing references to iconv.
+  // Note: Should be fixed now
+
+  {.$IFNDEF DARWIN}
+  {.$IFDEF NOIGNORE}
+    Result := WideUpperCase(str)
+  {.$ELSE}
+    //Result := UTF8Decode(UpperCase(UTF8Encode(str)));
+  {.$ENDIF}
+end;
+
+function WideStringLowerCase(ch: WideChar): WideString;
+begin
+  // see WideStringUpperCase
+  if (ch = #0) then
+    Result := #0
+  else
+    Result := WideStringLowerCase(WideString(ch));
+end;
+
+function WideStringLowerCase(const str: WideString): WideString;
+begin
+  // see WideStringUpperCase
+  Result := WideLowerCase(str)
+end;
+
+function WideStringReplaceChar(const text: WideString; search, rep: WideChar): WideString;
+var
+  iPos  : integer;
+//  sTemp : WideString;
+begin
+(*
+  result := text;
+  iPos   := Pos(search, result);
+  while (iPos > 0) do
+  begin
+    sTemp  := copy(result, iPos + length(search), length(result));
+    result := copy(result, 1, iPos - 1) + rep + sTEmp;
+    iPos   := Pos(search, result);
+  end;
+*)
+  result := text;
+
+  if search = rep then
+    exit;
+
+  for iPos := 1 to length(result) do
+  begin
+    if result[iPos] = search then
+      result[iPos] := rep;
+  end;
+end;
+
+initialization
+  InitUnicodeUtils;
+
+end.
diff --git a/cmake/src/base/UXMLSong.pas b/cmake/src/base/UXMLSong.pas
index 58b48789..e9751eba 100644
--- a/cmake/src/base/UXMLSong.pas
+++ b/cmake/src/base/UXMLSong.pas
@@ -34,7 +34,9 @@ interface
 {$I switches.inc}
 
 uses
-  Classes;
+  Classes,
+  UPath,
+  UUnicodeUtils;
 
 type
   TNote = record
@@ -42,30 +44,30 @@ type
     Duration: Cardinal;
     Tone:     Integer;
     NoteTyp:  Byte;
-    Lyric:    String;
+    Lyric:    UTF8String;
   end;
-  ANote = Array of TNote;
+  ANote = array of TNote;
 
   TSentence = record
     Singer:   Byte;
     Duration: Cardinal;
     Notes:    ANote;
   end;
-  ASentence = Array of TSentence;
+  ASentence = array of TSentence;
 
-  TSongInfo = Record
+  TSongInfo = record
     ID: Cardinal;
     DualChannel: Boolean;
-    Header: Record
-      Artist:     String;
-      Title:      String;
+    Header: record
+      Artist:     UTF8String;
+      Title:      UTF8String;
       Gap:        Cardinal;
       BPM:        Real;
       Resolution: Byte;
-      Edition:    String;
-      Genre:      String;
-      Year:       String;
-      Language:   String;
+      Edition:    UTF8String;
+      Genre:      UTF8String;
+      Year:       UTF8String;
+      Language:   UTF8String;
     end;
     CountSentences: Cardinal;
     Sentences: ASentence;
@@ -81,23 +83,23 @@ type
       BindLyrics: Boolean;    //Should the Lyrics be bind to the last Word (no Space)
       FirstNote: Boolean;     //Is this the First Note found? For Gap calculating
 
-      Function  ParseLine(Line: String): Boolean;
+      function  ParseLine(Line: RawByteString): Boolean;
     public
       SongInfo: TSongInfo;
-      ErrorMessage: String;
-      Edition: String;
-      SingstarVersion: String;
+      ErrorMessage: string;
+      Edition: UTF8String;
+      SingstarVersion: string;
 
-      Settings: Record
+      Settings: record
         DashReplacement: Char;
       end;
 
-      Constructor Create;
+      constructor Create;
 
-      Function  ParseConfigforEdition(const Filename: String): String;
+      function  ParseConfigForEdition(const Filename: IPath): String;
 
-      Function  ParseSongHeader(const Filename: String): Boolean; //Parse Song Header only
-      Function  ParseSong (const Filename: String): Boolean;      //Parse whole Song
+      function  ParseSongHeader(const Filename: IPath): Boolean; //Parse Song Header only
+      function  ParseSong (const Filename: IPath): Boolean;      //Parse whole Song
   end;
 
 const
@@ -114,9 +116,12 @@ const
   DS_Both    = 3;
 
 implementation
-uses SysUtils, StrUtils;
 
-Constructor TParser.Create;
+uses
+  SysUtils,
+  StrUtils;
+
+constructor TParser.Create;
 begin
   inherited Create;
   ErrorMessage := '';
@@ -124,19 +129,24 @@ begin
   DecimalSeparator := '.';
 end;
 
-Function  TParser.ParseSong (const Filename: String): Boolean;
-var I: Integer;
+function TParser.ParseSong(const Filename: IPath): Boolean;
+var
+  I: Integer;
+  FileStream: TBinaryFileStream;
 begin
   Result := False;
-  if FileExists(Filename) then
+  if Filename.IsFile() then
   begin
-    SSFile := TStringList.Create;
+    ErrorMessage := 'Can''t open melody.xml file';
 
+    SSFile := TStringList.Create;
+    FileStream := TBinaryFileStream.Create(Filename, fmOpenRead);
     try
-      ErrorMessage := 'Can''t open melody.xml file';
-      SSFile.LoadFromFile(Filename);
+      SSFile.LoadFromStream(FileStream);
+
       ErrorMessage := '';
       Result := True;
+
       I := 0;
 
       SongInfo.CountSentences := 0;
@@ -153,7 +163,7 @@ begin
 
       SetLength(SongInfo.Sentences, 0);
 
-      While Result And (I < SSFile.Count) do
+      while Result and (I < SSFile.Count) do
       begin
         Result := ParseLine(SSFile.Strings[I]);
 
@@ -162,21 +172,24 @@ begin
 
     finally
       SSFile.Free;
+      FileStream.Free;
     end;
   end;
 end;
 
-Function  TParser.ParseSongHeader (const Filename: String): Boolean;
-var I: Integer;
+function  TParser.ParseSongHeader (const Filename: IPath): Boolean;
+var
+  I: Integer;
+  Stream: TBinaryFileStream;
 begin
   Result := False;
-  if FileExists(Filename) then
+
+  if Filename.IsFile() then
   begin
     SSFile := TStringList.Create;
-    SSFile.Clear;
-    
+    Stream := TBinaryFileStream.Create(Filename, fmOpenRead);
     try
-      SSFile.LoadFromFile(Filename);
+      SSFile.LoadFromStream(Stream);
 
       If (SSFile.Count > 0) then
       begin
@@ -207,6 +220,7 @@ begin
 
     finally
       SSFile.Free;
+      Stream.Free;
     end;
   end
   else
@@ -569,18 +583,20 @@ begin
     Result := true;
 end;
 
-Function  TParser.ParseConfigforEdition(const Filename: String): String;
+Function  TParser.ParseConfigForEdition(const Filename: IPath): String;
 var
   txt: TStringlist;
+  Stream: TBinaryFileStream;
   I: Integer;
   J, K: Integer;
   S: String;
 begin
   Result := '';
-  txt := TStringlist.Create;
-  try
-    txt.LoadFromFile(Filename);
 
+  Stream := TBinaryFileStream.Create(Filename, fmOpenRead);
+  try
+    txt := TStringlist.Create;
+    txt.LoadFromStream(Stream);
     For I := 0 to txt.Count-1 do
     begin
       S := Trim(txt.Strings[I]);
@@ -600,6 +616,7 @@ begin
     Edition := Result;
   finally
     txt.Free;
+    Stream.Free;
   end;
 end;
 
-- 
cgit v1.2.3