DEADSOFTWARE

game: do not use absolute path in wad selection widgets (it looks ugly, and [almost...
[d2df-sdl.git] / src / game / g_saveload.pas
index e6a1ca293344ca1b603ff026a4fbc514c0ee7f54..09f5076c9b919fc95d9f96a09f672f20193bc24b 100644 (file)
@@ -1,9 +1,8 @@
-(* Copyright (C)  DooM 2D:Forever Developers
+(* Copyright (C)  Doom 2D: Forever Developers
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, version 3 of the License ONLY.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -19,362 +18,279 @@ unit g_saveload;
 interface
 
 uses
-  e_graphics, g_phys, g_textures, BinEditor;
+  SysUtils, Classes,
+  e_graphics, g_phys, g_textures;
 
-function g_GetSaveName(n: Integer): String;
-function g_SaveGame(n: Integer; Name: String): Boolean;
-function g_LoadGame(n: Integer): Boolean;
-procedure Obj_SaveState(o: PObj; var Mem: TBinMemoryWriter);
-procedure Obj_LoadState(o: PObj; var Mem: TBinMemoryReader);
 
-type
-  TLoadSaveHook = procedure ();
+function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
 
-procedure g_SetPreLoadHook (ahook: TLoadSaveHook);
-procedure g_SetPostLoadHook (ahook: TLoadSaveHook);
+function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
+function g_LoadGameFrom (const filename: AnsiString): Boolean;
 
-procedure g_CallPreLoadHooks ();
-procedure g_CallPostLoadHooks ();
+function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
+function g_LoadGame (n: Integer): Boolean;
+
+procedure Obj_SaveState (st: TStream; o: PObj);
+procedure Obj_LoadState (o: PObj; st: TStream);
 
 
 implementation
 
 uses
+  MAPDEF, utils, xstreams,
   g_game, g_items, g_map, g_monsters, g_triggers,
-  g_basic, g_main, SysUtils, Math, wadreader,
-  MAPSTRUCT, MAPDEF, g_weapons, g_player, g_console,
-  e_log, g_language;
+  g_basic, g_main, Math, wadreader,
+  g_weapons, g_player, g_console,
+  e_log, e_res, g_language;
 
 const
   SAVE_SIGNATURE = $56534644; // 'DFSV'
-  SAVE_VERSION = $03;
+  SAVE_VERSION = $07;
   END_MARKER_STRING = 'END';
   PLAYER_VIEW_SIGNATURE = $57564C50; // 'PLVW'
   OBJ_SIGNATURE = $4A424F5F; // '_OBJ'
 
 
-var
-  preloadHooks: array of TLoadSaveHook = nil;
-  postloadHooks: array of TLoadSaveHook = nil;
-
-
-procedure g_SetPreLoadHook (ahook: TLoadSaveHook);
-begin
-  if not assigned(ahook) then exit;
-  SetLength(preloadHooks, Length(preloadHooks)+1);
-  preloadHooks[High(preloadHooks)] := ahook;
-end;
-
-
-procedure g_SetPostLoadHook (ahook: TLoadSaveHook);
-begin
-  if not assigned(ahook) then exit;
-  SetLength(postloadHooks, Length(postloadHooks)+1);
-  postloadHooks[High(postloadHooks)] := ahook;
-end;
-
-
-procedure g_CallPreLoadHooks ();
-var
-  f: Integer;
+procedure Obj_SaveState (st: TStream; o: PObj);
 begin
-  for f := 0 to High(preloadHooks) do preloadHooks[f]();
+  if (st = nil) then exit;
+  // Ñèãíàòóðà îáúåêòà
+  utils.writeSign(st, '_OBJ');
+  utils.writeInt(st, Byte(0)); // version
+  // Ïîëîæåíèå ïî-ãîðèçîíòàëè
+  utils.writeInt(st, LongInt(o^.X));
+  // Ïîëîæåíèå ïî-âåðòèêàëè
+  utils.writeInt(st, LongInt(o^.Y));
+  // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
+  utils.writeInt(st, LongInt(o^.Rect.X));
+  utils.writeInt(st, LongInt(o^.Rect.Y));
+  utils.writeInt(st, Word(o^.Rect.Width));
+  utils.writeInt(st, Word(o^.Rect.Height));
+  // Ñêîðîñòü
+  utils.writeInt(st, LongInt(o^.Vel.X));
+  utils.writeInt(st, LongInt(o^.Vel.Y));
+  // Óñêîðåíèå
+  utils.writeInt(st, LongInt(o^.Accel.X));
+  utils.writeInt(st, LongInt(o^.Accel.Y));
 end;
 
 
-procedure g_CallPostLoadHooks ();
-var
-  f: Integer;
+procedure Obj_LoadState (o: PObj; st: TStream);
 begin
-  for f := 0 to High(postloadHooks) do postloadHooks[f]();
+  if (st = nil) then exit;
+  // Ñèãíàòóðà îáúåêòà:
+  if not utils.checkSign(st, '_OBJ') then raise XStreamError.Create('invalid object signature');
+  if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid object version');
+  // Ïîëîæåíèå ïî-ãîðèçîíòàëè
+  o^.X := utils.readLongInt(st);
+  // Ïîëîæåíèå ïî-âåðòèêàëè
+  o^.Y := utils.readLongInt(st);
+  // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
+  o^.Rect.X := utils.readLongInt(st);
+  o^.Rect.Y := utils.readLongInt(st);
+  o^.Rect.Width := utils.readWord(st);
+  o^.Rect.Height := utils.readWord(st);
+  // Ñêîðîñòü
+  o^.Vel.X := utils.readLongInt(st);
+  o^.Vel.Y := utils.readLongInt(st);
+  // Óñêîðåíèå
+  o^.Accel.X := utils.readLongInt(st);
+  o^.Accel.Y := utils.readLongInt(st);
 end;
 
 
-procedure Obj_SaveState(o: PObj; var Mem: TBinMemoryWriter);
-var
-  sig: DWORD;
+function buildSaveName (n: Integer): AnsiString;
 begin
-  if Mem = nil then
-    Exit;
-
-// Ñèãíàòóðà îáúåêòà:
-  sig := OBJ_SIGNATURE; // '_OBJ'
-  Mem.WriteDWORD(sig);
-// Ïîëîæåíèå ïî-ãîðèçîíòàëè:
-  Mem.WriteInt(o^.X);
-// Ïîëîæåíèå ïî-âåðòèêàëè:
-  Mem.WriteInt(o^.Y);
-// Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê:
-  Mem.WriteInt(o^.Rect.X);
-  Mem.WriteInt(o^.Rect.Y);
-  Mem.WriteWord(o^.Rect.Width);
-  Mem.WriteWord(o^.Rect.Height);
-// Ñêîðîñòü:
-  Mem.WriteInt(o^.Vel.X);
-  Mem.WriteInt(o^.Vel.Y);
-// Ïðèáàâêà ê ñêîðîñòè:
-  Mem.WriteInt(o^.Accel.X);
-  Mem.WriteInt(o^.Accel.Y);
+  result := 'SAVGAME' + IntToStr(n) + '.DAT'
 end;
 
-procedure Obj_LoadState(o: PObj; var Mem: TBinMemoryReader);
-var
-  sig: DWORD;
-begin
-  if Mem = nil then
-    Exit;
-
-// Ñèãíàòóðà îáúåêòà:
-  Mem.ReadDWORD(sig);
-  if sig <> OBJ_SIGNATURE then // '_OBJ'
-  begin
-    raise EBinSizeError.Create('Obj_LoadState: Wrong Object Signature');
-  end;
-// Ïîëîæåíèå ïî-ãîðèçîíòàëè:
-  Mem.ReadInt(o^.X);
-// Ïîëîæåíèå ïî-âåðòèêàëè:
-  Mem.ReadInt(o^.Y);
-// Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê:
-  Mem.ReadInt(o^.Rect.X);
-  Mem.ReadInt(o^.Rect.Y);
-  Mem.ReadWord(o^.Rect.Width);
-  Mem.ReadWord(o^.Rect.Height);
-// Ñêîðîñòü:
-  Mem.ReadInt(o^.Vel.X);
-  Mem.ReadInt(o^.Vel.Y);
-// Ïðèáàâêà ê ñêîðîñòè:
-  Mem.ReadInt(o^.Accel.X);
-  Mem.ReadInt(o^.Accel.Y);
-end;
 
-function g_GetSaveName(n: Integer): String;
+function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
 var
-  bFile: TBinFileReader;
-  bMem: TBinMemoryReader;
-  str: String;
+  st: TStream = nil;
+  ver: Byte;
+  stlen: Word;
+  filename: AnsiString;
 begin
-  Result := '';
-  str := '';
-  bMem := nil;
-  bFile := nil;
-
+  valid := false;
+  result := '';
+  if (n < 0) or (n > 65535) then exit;
   try
-  // Îòêðûâàåì ôàéë ñîõðàíåíèé:
-    bFile := TBinFileReader.Create();
-    if bFile.OpenFile(DataDir + 'SAVGAME' + IntToStr(n) + '.DAT',
-                      SAVE_SIGNATURE, SAVE_VERSION) then
-    begin
-    // ×èòàåì ïåðâûé áëîê - ñîñòîÿíèå èãðû:
-      bMem := TBinMemoryReader.Create();
-      bFile.ReadMemory(bMem);
-    // Èìÿ èãðû:
-      bMem.ReadString(str);
-
-    // Çàêðûâàåì ôàéë:
-      bFile.Close();
+    // Îòêðûâàåì ôàéë ñîõðàíåíèé
+    filename := buildSaveName(n);
+    st := e_OpenResourceRO(SaveDirs, filename);
+    try
+      if not utils.checkSign(st, 'DFSV') then
+      begin
+        e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
+        //raise XStreamError.Create('invalid save game signature');
+        exit;
+      end;
+      ver := utils.readByte(st);
+      if (ver < 7) then
+      begin
+        utils.readLongWord(st); // section size
+        stlen := utils.readWord(st);
+        if (stlen < 1) or (stlen > 64) then
+        begin
+          e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
+          //raise XStreamError.Create('invalid save game version');
+          exit;
+        end;
+        // Èìÿ ñýéâà
+        SetLength(result, stlen);
+        st.ReadBuffer(result[1], stlen);
+      end
+      else
+      begin
+        // 7+
+        // Èìÿ ñýéâà
+        result := utils.readStr(st, 64);
+      end;
+      valid := (ver = SAVE_VERSION);
+      //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
+    finally
+      st.Free();
     end;
-
   except
-    on E1: EInOutError do
-      e_WriteLog('GetSaveName I/O Error: '+E1.Message, MSG_WARNING);
-    on E2: EBinSizeError do
-      e_WriteLog('GetSaveName Size Error: '+E2.Message, MSG_WARNING);
+    begin
+      //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
+      //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
+      result := '';
+    end;
   end;
-
-  bMem.Free();
-  bFile.Free();
-
-  Result := str;
 end;
 
-function g_SaveGame(n: Integer; Name: String): Boolean;
+
+function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
 var
-  bFile: TBinFileWriter;
-  bMem: TBinMemoryWriter;
-  sig: DWORD;
-  str: String;
-  nPlayers: Byte;
+  st: TStream = nil;
   i, k: Integer;
   PID1, PID2: Word;
 begin
-  Result := False;
-  bMem := nil;
-  bFile := nil;
-
+  result := false;
   try
-  // Ñîçäàåì ôàéë ñîõðàíåíèÿ:
-    bFile := TBinFileWriter.Create();
-    bFile.OpenFile(DataDir + 'SAVGAME' + IntToStr(n) + '.DAT',
-                   SAVE_SIGNATURE, SAVE_VERSION);
-
-  ///// Ïîëó÷àåì ñîñòîÿíèå èãðû: /////
-    bMem := TBinMemoryWriter.Create(256);
-  // Èìÿ èãðû:
-    bMem.WriteString(Name, 32);
-  // Ïóòü ê êàðòå:
-    str := gGameSettings.WAD;
-    bMem.WriteString(str, 128);
-  // Èìÿ êàðòû:
-    str := g_ExtractFileName(gMapInfo.Map);
-    bMem.WriteString(str, 32);
-  // Êîëè÷åñòâî èãðîêîâ:
-    nPlayers := g_Player_GetCount();
-    bMem.WriteByte(nPlayers);
-  // Èãðîâîå âðåìÿ:
-    bMem.WriteDWORD(gTime);
-  // Òèï èãðû:
-    bMem.WriteByte(gGameSettings.GameType);
-  // Ðåæèì èãðû:
-    bMem.WriteByte(gGameSettings.GameMode);
-  // Ëèìèò âðåìåíè:
-    bMem.WriteWord(gGameSettings.TimeLimit);
-  // Ëèìèò î÷êîâ:
-    bMem.WriteWord(gGameSettings.GoalLimit);
-  // Ëèìèò æèçíåé:
-    bMem.WriteByte(gGameSettings.MaxLives);
-  // Èãðîâûå îïöèè:
-    bMem.WriteDWORD(gGameSettings.Options);
-  // Äëÿ êîîïà:
-    bMem.WriteWord(gCoopMonstersKilled);
-    bMem.WriteWord(gCoopSecretsFound);
-    bMem.WriteWord(gCoopTotalMonstersKilled);
-    bMem.WriteWord(gCoopTotalSecretsFound);
-    bMem.WriteWord(gCoopTotalMonsters);
-    bMem.WriteWord(gCoopTotalSecrets);
-  // Ñîõðàíÿåì ñîñòîÿíèå èãðû:
-    bFile.WriteMemory(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà: /////
-    bMem := TBinMemoryWriter.Create(8);
-    sig := PLAYER_VIEW_SIGNATURE;
-    bMem.WriteDWORD(sig); // 'PLVW'
-    PID1 := 0;
-    PID2 := 0;
-    if gPlayer1 <> nil then
-      PID1 := gPlayer1.UID;
-    if gPlayer2 <> nil then
-      PID2 := gPlayer2.UID;
-    bMem.WriteWord(PID1);
-    bMem.WriteWord(PID2);
-    bFile.WriteMemory(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Ïîëó÷àåì ñîñòîÿíèå êàðòû: /////
-    g_Map_SaveState(bMem);
-  // Ñîõðàíÿåì ñîñòîÿíèå êàðòû:
-    bFile.WriteMemory(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Ïîëó÷àåì ñîñòîÿíèå ïðåäìåòîâ: /////
-    g_Items_SaveState(bMem);
-  // Ñîõðàíÿåì ñîñòîÿíèå ïðåäìåòîâ:
-    bFile.WriteMemory(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Ïîëó÷àåì ñîñòîÿíèå òðèããåðîâ: /////
-    g_Triggers_SaveState(bMem);
-  // Ñîõðàíÿåì ñîñòîÿíèå òðèããåðîâ:
-    bFile.WriteMemory(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Ïîëó÷àåì ñîñòîÿíèå îðóæèÿ: /////
-    g_Weapon_SaveState(bMem);
-  // Ñîõðàíÿåì ñîñòîÿíèå îðóæèÿ:
-    bFile.WriteMemory(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Ïîëó÷àåì ñîñòîÿíèå ìîíñòðîâ: /////
-    g_Monsters_SaveState(bMem);
-  // Ñîõðàíÿåì ñîñòîÿíèå ìîíñòðîâ:
-    bFile.WriteMemory(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Ïîëó÷àåì ñîñòîÿíèå òðóïîâ: /////
-    g_Player_Corpses_SaveState(bMem);
-  // Ñîõðàíÿåì ñîñòîÿíèå òðóïîâ:
-    bFile.WriteMemory(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ): /////
-    if nPlayers > 0 then
-    begin
-      k := 0;
-      for i := 0 to High(gPlayers) do
-        if gPlayers[i] <> nil then
+    st := e_CreateResource(SaveDirs, filename);
+    try
+      utils.writeSign(st, 'DFSV');
+      utils.writeInt(st, Byte(SAVE_VERSION));
+      // Èìÿ ñýéâà
+      utils.writeStr(st, aname, 64);
+      // Ïîëíûé ïóòü ê âàäó è êàðòà
+      //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
+      utils.writeStr(st, gCurrentMapFileName);
+      // Ïóòü ê êàðòå
+      utils.writeStr(st, gGameSettings.WAD);
+      // Èìÿ êàðòû
+      utils.writeStr(st, g_ExtractFileName(gMapInfo.Map));
+      // Êîëè÷åñòâî èãðîêîâ
+      utils.writeInt(st, Word(g_Player_GetCount));
+      // Èãðîâîå âðåìÿ
+      utils.writeInt(st, LongWord(gTime));
+      // Òèï èãðû
+      utils.writeInt(st, Byte(gGameSettings.GameType));
+      // Ðåæèì èãðû
+      utils.writeInt(st, Byte(gGameSettings.GameMode));
+      // Ëèìèò âðåìåíè
+      utils.writeInt(st, Word(gGameSettings.TimeLimit));
+      // Ëèìèò î÷êîâ
+      utils.writeInt(st, Word(gGameSettings.GoalLimit));
+      // Ëèìèò æèçíåé
+      utils.writeInt(st, Byte(gGameSettings.MaxLives));
+      // Èãðîâûå îïöèè
+      utils.writeInt(st, LongWord(gGameSettings.Options));
+      // Äëÿ êîîïà
+      utils.writeInt(st, Word(gCoopMonstersKilled));
+      utils.writeInt(st, Word(gCoopSecretsFound));
+      utils.writeInt(st, Word(gCoopTotalMonstersKilled));
+      utils.writeInt(st, Word(gCoopTotalSecretsFound));
+      utils.writeInt(st, Word(gCoopTotalMonsters));
+      utils.writeInt(st, Word(gCoopTotalSecrets));
+
+      ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
+      utils.writeSign(st, 'PLVW');
+      utils.writeInt(st, Byte(0)); // version
+      PID1 := 0;
+      PID2 := 0;
+      if (gPlayer1 <> nil) then PID1 := gPlayer1.UID;
+      if (gPlayer2 <> nil) then PID2 := gPlayer2.UID;
+      utils.writeInt(st, Word(PID1));
+      utils.writeInt(st, Word(PID2));
+      ///// /////
+
+      ///// Ñîñòîÿíèå êàðòû /////
+      g_Map_SaveState(st);
+      ///// /////
+
+      ///// Ñîñòîÿíèå ïðåäìåòîâ /////
+      g_Items_SaveState(st);
+      ///// /////
+
+      ///// Ñîñòîÿíèå òðèããåðîâ /////
+      g_Triggers_SaveState(st);
+      ///// /////
+
+      ///// Ñîñòîÿíèå îðóæèÿ /////
+      g_Weapon_SaveState(st);
+      ///// /////
+
+      ///// Ñîñòîÿíèå ìîíñòðîâ /////
+      g_Monsters_SaveState(st);
+      ///// /////
+
+      ///// Ñîñòîÿíèå òðóïîâ /////
+      g_Player_Corpses_SaveState(st);
+      ///// /////
+
+      ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
+      if (g_Player_GetCount > 0) then
+      begin
+        k := 0;
+        for i := 0 to High(gPlayers) do
         begin
-        // Ïîëó÷àåì ñîñòîÿíèå èãðîêà:
-          gPlayers[i].SaveState(bMem);
-        // Ñîõðàíÿåì ñîñòîÿíèå èãðîêà:
-          bFile.WriteMemory(bMem);
-          bMem.Free();
-          bMem := nil;
-          Inc(k);
+          if (gPlayers[i] <> nil) then
+          begin
+            // Ñîñòîÿíèå èãðîêà
+            gPlayers[i].SaveState(st);
+            Inc(k);
+          end;
         end;
 
-    // Âñå ëè èãðîêè íà ìåñòå:
-      if k <> nPlayers then
-      begin
-        raise EInOutError.Create('g_SaveGame: Wrong Players Count');
+        // Âñå ëè èãðîêè íà ìåñòå
+        if (k <> g_Player_GetCount) then raise XStreamError.Create('g_SaveGame: wrong players count');
       end;
+      ///// /////
+
+      ///// Ìàðêåð îêîí÷àíèÿ /////
+      utils.writeSign(st, 'END');
+      utils.writeInt(st, Byte(0));
+      ///// /////
+      result := true;
+    finally
+      st.Free();
     end;
-  ///// /////
-
-  ///// Ìàðêåð îêîí÷àíèÿ: /////
-    bMem := TBinMemoryWriter.Create(4);
-  // Ñòðîêà - îáîçíà÷åíèå êîíöà:
-    str := END_MARKER_STRING; // 'END'
-    bMem.WriteString(str, 3);
-  // Ñîõðàíÿåì ìàðêåð îêîí÷àíèÿ:
-    bFile.WriteMemory(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  // Çàêðûâàåì ôàéë ñîõðàíåíèÿ:
-    bFile.Close();
-    Result := True;
 
   except
-    on E1: EInOutError do
+    on e: Exception do
       begin
+        st.Free();
         g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
-        e_WriteLog('SaveState I/O Error: '+E1.Message, MSG_WARNING);
-      end;
-    on E2: EBinSizeError do
-      begin
-        g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
-        e_WriteLog('SaveState Size Error: '+E2.Message, MSG_WARNING);
+        e_WriteLog('SaveState Error: '+e.message, TMsgType.Warning);
+        if deleteOnError then DeleteFile(filename);
+        {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
+e_WriteStackTrace(e.message);
+        result := false;
       end;
   end;
-
-  bMem.Free();
-  bFile.Free();
 end;
 
-function g_LoadGame(n: Integer): Boolean;
+
+function g_LoadGameFrom (const filename: AnsiString): Boolean;
 var
-  bFile: TBinFileReader;
-  bMem: TBinMemoryReader;
-  sig: DWORD;
-  str, WAD_Path, Map_Name: String;
-  nPlayers, Game_Type, Game_Mode, Game_MaxLives: Byte;
+  st: TStream = nil;
+  WAD_Path, Map_Name: AnsiString;
+  nPlayers: Integer;
+  Game_Type, Game_Mode, Game_MaxLives: Byte;
   Game_TimeLimit, Game_GoalLimit: Word;
   Game_Time, Game_Options: Cardinal;
   Game_CoopMonstersKilled,
@@ -385,260 +301,241 @@ var
   Game_CoopTotalSecrets,
   PID1, PID2: Word;
   i: Integer;
+  gameCleared: Boolean = false;
+  curmapfile: AnsiString = '';
+  {$IF DEFINED(D2F_DEBUG)}
+  errpos: LongWord = 0;
+  {$ENDIF}
 begin
-  Result := False;
-  bMem := nil;
-  bFile := nil;
+  result := false;
 
   try
-  // Îòêðûâàåì ôàéë ñ ñîõðàíåíèåì:
-    bFile := TBinFileReader.Create();
-    if not bFile.OpenFile(DataDir + 'SAVGAME' + IntToStr(n) + '.DAT',
-                          SAVE_SIGNATURE, SAVE_VERSION) then
-    begin
-      bFile.Free();
-      Exit;
-    end;
+    st := e_OpenResourceRO(SaveDirs, filename);
+    try
+      if not utils.checkSign(st, 'DFSV') then raise XStreamError.Create('invalid save game signature');
+      if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
+
+      e_WriteLog('Loading saved game...', TMsgType.Notify);
+
+      {$IF DEFINED(D2F_DEBUG)}try{$ENDIF}
+        //g_Game_Free(false); // don't free textures for the same map
+        g_Game_ClearLoading();
+        g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False);
+        gLoadGameMode := True;
+
+        ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
+        // Èìÿ ñýéâà
+        {str :=} utils.readStr(st, 64);
+
+        // Ïîëíûé ïóòü ê âàäó è êàðòà
+        curmapfile := utils.readStr(st);
+
+        if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName]);
+        if (Length(curmapfile) <> 0) then e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile]);
+        // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû
+        g_Game_Free(curmapfile <> gCurrentMapFileName); // don't free textures for the same map
+        gameCleared := true;
+
+        // Ïóòü ê êàðòå
+        WAD_Path := utils.readStr(st);
+        // Èìÿ êàðòû
+        Map_Name := utils.readStr(st);
+        // Êîëè÷åñòâî èãðîêîâ
+        nPlayers := utils.readWord(st);
+        // Èãðîâîå âðåìÿ
+        Game_Time := utils.readLongWord(st);
+        // Òèï èãðû
+        Game_Type := utils.readByte(st);
+        // Ðåæèì èãðû
+        Game_Mode := utils.readByte(st);
+        // Ëèìèò âðåìåíè
+        Game_TimeLimit := utils.readWord(st);
+        // Ëèìèò î÷êîâ
+        Game_GoalLimit := utils.readWord(st);
+        // Ëèìèò æèçíåé
+        Game_MaxLives := utils.readByte(st);
+        // Èãðîâûå îïöèè
+        Game_Options := utils.readLongWord(st);
+        // Äëÿ êîîïà
+        Game_CoopMonstersKilled := utils.readWord(st);
+        Game_CoopSecretsFound := utils.readWord(st);
+        Game_CoopTotalMonstersKilled := utils.readWord(st);
+        Game_CoopTotalSecretsFound := utils.readWord(st);
+        Game_CoopTotalMonsters := utils.readWord(st);
+        Game_CoopTotalSecrets := utils.readWord(st);
+        ///// /////
+
+        ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
+        if not utils.checkSign(st, 'PLVW') then raise XStreamError.Create('invalid viewport signature');
+        if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid viewport version');
+        PID1 := utils.readWord(st);
+        PID2 := utils.readWord(st);
+        ///// /////
+
+        // Çàãðóæàåì êàðòó:
+        ZeroMemory(@gGameSettings, sizeof(TGameSettings));
+        gAimLine := false;
+        gShowMap := false;
+        if (Game_Type = GT_NONE) or (Game_Type = GT_SINGLE) then
+        begin
+          // Íàñòðîéêè èãðû
+          gGameSettings.GameType := GT_SINGLE;
+          gGameSettings.MaxLives := 0;
+          gGameSettings.Options := gGameSettings.Options+GAME_OPTION_ALLOWEXIT;
+          gGameSettings.Options := gGameSettings.Options+GAME_OPTION_MONSTERS;
+          gGameSettings.Options := gGameSettings.Options+GAME_OPTION_BOTVSMONSTER;
+          gSwitchGameMode := GM_SINGLE;
+        end
+        else
+        begin
+          // Íàñòðîéêè èãðû
+          gGameSettings.GameType := GT_CUSTOM;
+          gGameSettings.GameMode := Game_Mode;
+          gSwitchGameMode := Game_Mode;
+          gGameSettings.TimeLimit := Game_TimeLimit;
+          gGameSettings.GoalLimit := Game_GoalLimit;
+          gGameSettings.MaxLives := IfThen(Game_Mode = GM_CTF, 0, Game_MaxLives);
+          gGameSettings.Options := Game_Options;
+        end;
+        g_Game_ExecuteEvent('ongamestart');
 
-    e_WriteLog('Loading saved game...', MSG_NOTIFY);
-    g_Game_Free();
-
-    g_Game_ClearLoading();
-    g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False);
-    gLoadGameMode := True;
-
-    g_CallPreLoadHooks();
-
-  ///// Çàãðóæàåì ñîñòîÿíèå èãðû: /////
-    bMem := TBinMemoryReader.Create();
-    bFile.ReadMemory(bMem);
-  // Èìÿ èãðû:
-    bMem.ReadString(str);
-  // Ïóòü ê êàðòå:
-    bMem.ReadString(WAD_Path);
-  // Èìÿ êàðòû:
-    bMem.ReadString(Map_Name);
-  // Êîëè÷åñòâî èãðîêîâ:
-    bMem.ReadByte(nPlayers);
-  // Èãðîâîå âðåìÿ:
-    bMem.ReadDWORD(Game_Time);
-  // Òèï èãðû:
-    bMem.ReadByte(Game_Type);
-  // Ðåæèì èãðû:
-    bMem.ReadByte(Game_Mode);
-  // Ëèìèò âðåìåíè:
-    bMem.ReadWord(Game_TimeLimit);
-  // Ëèìèò î÷êîâ:
-    bMem.ReadWord(Game_GoalLimit);
-  // Ëèìèò æèçíåé:
-    bMem.ReadByte(Game_MaxLives);
-  // Èãðîâûå îïöèè:
-    bMem.ReadDWORD(Game_Options);
-  // Äëÿ êîîïà:
-    bMem.ReadWord(Game_CoopMonstersKilled);
-    bMem.ReadWord(Game_CoopSecretsFound);
-    bMem.ReadWord(Game_CoopTotalMonstersKilled);
-    bMem.ReadWord(Game_CoopTotalSecretsFound);
-    bMem.ReadWord(Game_CoopTotalMonsters);
-    bMem.ReadWord(Game_CoopTotalSecrets);
-  // Cîñòîÿíèå èãðû çàãðóæåíî:
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà: /////
-    bMem := TBinMemoryReader.Create();
-    bFile.ReadMemory(bMem);
-    bMem.ReadDWORD(sig);
-    if sig <> PLAYER_VIEW_SIGNATURE then // 'PLVW'
-    begin
-      raise EInOutError.Create('g_LoadGame: Wrong Player View Signature');
-    end;
-    bMem.ReadWord(PID1);
-    bMem.ReadWord(PID2);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  // Çàãðóæàåì êàðòó:
-    ZeroMemory(@gGameSettings, SizeOf(TGameSettings));
-    gAimLine := False;
-    gShowMap := False;
-    if (Game_Type = GT_NONE) or (Game_Type = GT_SINGLE) then
-    begin
-    // Íàñòðîéêè èãðû:
-      gGameSettings.GameType := GT_SINGLE;
-      gGameSettings.MaxLives := 0;
-      gGameSettings.Options := gGameSettings.Options + GAME_OPTION_ALLOWEXIT;
-      gGameSettings.Options := gGameSettings.Options + GAME_OPTION_MONSTERS;
-      gGameSettings.Options := gGameSettings.Options + GAME_OPTION_BOTVSMONSTER;
-      gSwitchGameMode := GM_SINGLE;
-    end
-    else
-    begin
-    // Íàñòðîéêè èãðû:
-      gGameSettings.GameType := GT_CUSTOM;
-      gGameSettings.GameMode := Game_Mode;
-      gSwitchGameMode := Game_Mode;
-      gGameSettings.TimeLimit := Game_TimeLimit;
-      gGameSettings.GoalLimit := Game_GoalLimit;
-      gGameSettings.MaxLives := IfThen(Game_Mode = GM_CTF, 0, Game_MaxLives);
-      gGameSettings.Options := Game_Options;
-    end;
-    g_Game_ExecuteEvent('ongamestart');
+        // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ
+        g_Game_SetupScreenSize();
 
-  // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
-    g_Game_SetupScreenSize();
+        // Çàãðóçêà è çàïóñê êàðòû
+        //FIXME: save/load `asMegawad`
+        if not g_Game_StartMap(false{asMegawad}, WAD_Path+':\'+Map_Name, True, curmapfile) then
+        begin
+          g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
+          exit;
+        end;
 
-  // Çàãðóçêà è çàïóñê êàðòû:
-    if not g_Game_StartMap(WAD_Path + ':\' + Map_Name, True) then
-    begin
-      g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
-      Exit;
-    end;
+        // Íàñòðîéêè èãðîêîâ è áîòîâ
+        g_Player_Init();
+
+        // Óñòàíàâëèâàåì âðåìÿ
+        gTime := Game_Time;
+        // Âîçâðàùàåì ñòàòû
+        gCoopMonstersKilled := Game_CoopMonstersKilled;
+        gCoopSecretsFound := Game_CoopSecretsFound;
+        gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
+        gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
+        gCoopTotalMonsters := Game_CoopTotalMonsters;
+        gCoopTotalSecrets := Game_CoopTotalSecrets;
+
+        ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
+        g_Map_LoadState(st);
+        ///// /////
+
+        ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
+        g_Items_LoadState(st);
+        ///// /////
+
+        ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
+        g_Triggers_LoadState(st);
+        ///// /////
+
+        ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
+        g_Weapon_LoadState(st);
+        ///// /////
+
+        ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
+        g_Monsters_LoadState(st);
+        ///// /////
+
+        ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
+        g_Player_Corpses_LoadState(st);
+        ///// /////
+
+        ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
+        if nPlayers > 0 then
+        begin
+          // Çàãðóæàåì
+          for i := 0 to nPlayers-1 do g_Player_CreateFromState(st);
+        end;
 
-  // Íàñòðîéêè èãðîêîâ è áîòîâ:
-    g_Player_Init();
-
-  // Óñòàíàâëèâàåì âðåìÿ:
-    gTime := Game_Time;
-  // Âîçâðàùàåì ñòàòû:
-    gCoopMonstersKilled := Game_CoopMonstersKilled;
-    gCoopSecretsFound := Game_CoopSecretsFound;
-    gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
-    gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
-    gCoopTotalMonsters := Game_CoopTotalMonsters;
-    gCoopTotalSecrets := Game_CoopTotalSecrets;
-
-  ///// Çàãðóæàåì ñîñòîÿíèå êàðòû: /////
-    bMem := TBinMemoryReader.Create();
-    bFile.ReadMemory(bMem);
-  // Ñîñòîÿíèå êàðòû:
-    g_Map_LoadState(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ: /////
-    bMem := TBinMemoryReader.Create();
-    bFile.ReadMemory(bMem);
-  // Ñîñòîÿíèå ïðåäìåòîâ:
-    g_Items_LoadState(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ: /////
-    bMem := TBinMemoryReader.Create();
-    bFile.ReadMemory(bMem);
-  // Ñîñòîÿíèå òðèããåðîâ:
-    g_Triggers_LoadState(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ: /////
-    bMem := TBinMemoryReader.Create();
-    bFile.ReadMemory(bMem);
-  // Ñîñòîÿíèå îðóæèÿ:
-    g_Weapon_LoadState(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ: /////
-    bMem := TBinMemoryReader.Create();
-    bFile.ReadMemory(bMem);
-  // Ñîñòîÿíèå ìîíñòðîâ:
-    g_Monsters_LoadState(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ: /////
-    bMem := TBinMemoryReader.Create();
-    bFile.ReadMemory(bMem);
-  // Ñîñòîÿíèå òðóïîâ:
-    g_Player_Corpses_LoadState(bMem);
-    bMem.Free();
-    bMem := nil;
-  ///// /////
-
-  ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ): /////
-    if nPlayers > 0 then
-    begin
-    // Çàãðóæàåì:
-      for i := 0 to nPlayers-1 do
-      begin
-      // Çàãðóæàåì ñîñòîÿíèå èãðîêà:
-        bMem := TBinMemoryReader.Create();
-        bFile.ReadMemory(bMem);
-      // Ñîñòîÿíèå èãðîêà/áîòà:
-        g_Player_CreateFromState(bMem);
-        bMem.Free();
-        bMem := nil;
-      end;
-    end;
-  // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà:
-    gPlayer1 := g_Player_Get(PID1);
-    gPlayer2 := g_Player_Get(PID2);
-    if gPlayer1 <> nil then
-    begin
-      gPlayer1.Name := gPlayer1Settings.Name;
-      gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
-      gPlayer1.FActualModelName := gPlayer1Settings.Model;
-      gPlayer1.SetModel(gPlayer1.FActualModelName);
-      gPlayer1.SetColor(gPlayer1Settings.Color);
-    end;
-    if gPlayer2 <> nil then
-    begin
-      gPlayer2.Name := gPlayer2Settings.Name;
-      gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
-      gPlayer2.FActualModelName := gPlayer2Settings.Model;
-      gPlayer2.SetModel(gPlayer2.FActualModelName);
-      gPlayer2.SetColor(gPlayer2Settings.Color);
-    end;
-  ///// /////
-
-  ///// Ìàðêåð îêîí÷àíèÿ: /////
-    bMem := TBinMemoryReader.Create();
-    bFile.ReadMemory(bMem);
-  // Ñòðîêà - îáîçíà÷åíèå êîíöà:
-    bMem.ReadString(str);
-    if str <> END_MARKER_STRING then // 'END'
-    begin
-      raise EInOutError.Create('g_LoadGame: No END Marker');
-    end;
-  // Ìàðêåð îêîí÷àíèÿ çàãðóæåí:
-    bMem.Free();
-    bMem := nil;
-  ///// /////
+        // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
+        gPlayer1 := g_Player_Get(PID1);
+        gPlayer2 := g_Player_Get(PID2);
 
-  // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ:
-    if {(gMonsters <> nil) and} (gTriggers <> nil) then
-      g_Map_ReAdd_DieTriggers();
+        if (gPlayer1 <> nil) then
+        begin
+          gPlayer1.Name := gPlayer1Settings.Name;
+          gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
+          gPlayer1.FActualModelName := gPlayer1Settings.Model;
+          gPlayer1.SetModel(gPlayer1.FActualModelName);
+          gPlayer1.SetColor(gPlayer1Settings.Color);
+        end;
 
-  // Çàêðûâàåì ôàéë çàãðóçêè:
-    bFile.Close();
-    gLoadGameMode := False;
-    g_CallPostLoadHooks();
-    Result := True;
+        if (gPlayer2 <> nil) then
+        begin
+          gPlayer2.Name := gPlayer2Settings.Name;
+          gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
+          gPlayer2.FActualModelName := gPlayer2Settings.Model;
+          gPlayer2.SetModel(gPlayer2.FActualModelName);
+          gPlayer2.SetColor(gPlayer2Settings.Color);
+        end;
+        ///// /////
+
+        ///// Ìàðêåð îêîí÷àíèÿ /////
+        if not utils.checkSign(st, 'END') then raise XStreamError.Create('no end marker');
+        if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid end marker');
+        ///// /////
+
+        // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
+        if (gTriggers <> nil) then g_Map_ReAdd_DieTriggers();
 
+        // done
+        gLoadGameMode := false;
+        result := true;
+      {$IF DEFINED(D2F_DEBUG)}
+      except
+        begin
+          errpos := LongWord(st.position);
+          raise;
+        end;
+      end;
+      {$ENDIF}
+    finally
+      st.Free();
+    end;
   except
-    on E1: EInOutError do
+    on e: EFileNotFoundException do
       begin
         g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
-        e_WriteLog('LoadState I/O Error: '+E1.Message, MSG_WARNING);
+        g_Console_Add('LoadState Error: '+e.message);
+        e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
+        gLoadGameMode := false;
+        result := false;
       end;
-    on E2: EBinSizeError do
+    on e: Exception do
       begin
         g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
-        e_WriteLog('LoadState Size Error: '+E2.Message, MSG_WARNING);
+        g_Console_Add('LoadState Error: '+e.message);
+        e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
+        {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos], TMsgType.Warning);{$ENDIF}
+        gLoadGameMode := false;
+        result := false;
+        if gState <> STATE_MENU then
+          g_FatalError(_lc[I_GAME_ERROR_LOAD])
+        else if not gameCleared then
+          g_Game_Free();
+        {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
       end;
   end;
+end;
+
+
+function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
+begin
+  result := g_SaveGameTo(buildSaveName(n), aname, true);
+end;
+
 
-  bMem.Free();
-  bFile.Free();
+function g_LoadGame (n: Integer): Boolean;
+begin
+  result := g_LoadGameFrom(buildSaveName(n));
 end;
 
+
 end.