DEADSOFTWARE

rewritten dyntree visitor; seems to fix segfaults (but i don't know why)
[d2df-sdl.git] / src / game / g_saveload.pas
1 (* Copyright (C) DooM 2D:Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 unit g_saveload;
19 interface
21 uses
22 e_graphics, g_phys, g_textures, BinEditor;
24 function g_GetSaveName(n: Integer): String;
25 function g_SaveGame(n: Integer; Name: String): Boolean;
26 function g_LoadGame(n: Integer): Boolean;
27 procedure Obj_SaveState(o: PObj; var Mem: TBinMemoryWriter);
28 procedure Obj_LoadState(o: PObj; var Mem: TBinMemoryReader);
30 implementation
32 uses
33 g_game, g_items, g_map, g_monsters, g_triggers,
34 g_basic, g_main, SysUtils, Math, wadreader,
35 MAPSTRUCT, MAPDEF, g_weapons, g_player, g_console,
36 e_log, g_language;
38 const
39 SAVE_SIGNATURE = $56534644; // 'DFSV'
40 SAVE_VERSION = $03;
41 END_MARKER_STRING = 'END';
42 PLAYER_VIEW_SIGNATURE = $57564C50; // 'PLVW'
43 OBJ_SIGNATURE = $4A424F5F; // '_OBJ'
45 procedure Obj_SaveState(o: PObj; var Mem: TBinMemoryWriter);
46 var
47 sig: DWORD;
48 begin
49 if Mem = nil then
50 Exit;
52 // Ñèãíàòóðà îáúåêòà:
53 sig := OBJ_SIGNATURE; // '_OBJ'
54 Mem.WriteDWORD(sig);
55 // Ïîëîæåíèå ïî-ãîðèçîíòàëè:
56 Mem.WriteInt(o^.X);
57 // Ïîëîæåíèå ïî-âåðòèêàëè:
58 Mem.WriteInt(o^.Y);
59 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê:
60 Mem.WriteInt(o^.Rect.X);
61 Mem.WriteInt(o^.Rect.Y);
62 Mem.WriteWord(o^.Rect.Width);
63 Mem.WriteWord(o^.Rect.Height);
64 // Ñêîðîñòü:
65 Mem.WriteInt(o^.Vel.X);
66 Mem.WriteInt(o^.Vel.Y);
67 // Ïðèáàâêà ê ñêîðîñòè:
68 Mem.WriteInt(o^.Accel.X);
69 Mem.WriteInt(o^.Accel.Y);
70 end;
72 procedure Obj_LoadState(o: PObj; var Mem: TBinMemoryReader);
73 var
74 sig: DWORD;
75 begin
76 if Mem = nil then
77 Exit;
79 // Ñèãíàòóðà îáúåêòà:
80 Mem.ReadDWORD(sig);
81 if sig <> OBJ_SIGNATURE then // '_OBJ'
82 begin
83 raise EBinSizeError.Create('Obj_LoadState: Wrong Object Signature');
84 end;
85 // Ïîëîæåíèå ïî-ãîðèçîíòàëè:
86 Mem.ReadInt(o^.X);
87 // Ïîëîæåíèå ïî-âåðòèêàëè:
88 Mem.ReadInt(o^.Y);
89 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê:
90 Mem.ReadInt(o^.Rect.X);
91 Mem.ReadInt(o^.Rect.Y);
92 Mem.ReadWord(o^.Rect.Width);
93 Mem.ReadWord(o^.Rect.Height);
94 // Ñêîðîñòü:
95 Mem.ReadInt(o^.Vel.X);
96 Mem.ReadInt(o^.Vel.Y);
97 // Ïðèáàâêà ê ñêîðîñòè:
98 Mem.ReadInt(o^.Accel.X);
99 Mem.ReadInt(o^.Accel.Y);
100 end;
102 function g_GetSaveName(n: Integer): String;
103 var
104 bFile: TBinFileReader;
105 bMem: TBinMemoryReader;
106 str: String;
107 begin
108 Result := '';
109 str := '';
110 bMem := nil;
111 bFile := nil;
113 try
114 // Îòêðûâàåì ôàéë ñîõðàíåíèé:
115 bFile := TBinFileReader.Create();
116 if bFile.OpenFile(DataDir + 'SAVGAME' + IntToStr(n) + '.DAT',
117 SAVE_SIGNATURE, SAVE_VERSION) then
118 begin
119 // ×èòàåì ïåðâûé áëîê - ñîñòîÿíèå èãðû:
120 bMem := TBinMemoryReader.Create();
121 bFile.ReadMemory(bMem);
122 // Èìÿ èãðû:
123 bMem.ReadString(str);
125 // Çàêðûâàåì ôàéë:
126 bFile.Close();
127 end;
129 except
130 on E1: EInOutError do
131 e_WriteLog('GetSaveName I/O Error: '+E1.Message, MSG_WARNING);
132 on E2: EBinSizeError do
133 e_WriteLog('GetSaveName Size Error: '+E2.Message, MSG_WARNING);
134 end;
136 bMem.Free();
137 bFile.Free();
139 Result := str;
140 end;
142 function g_SaveGame(n: Integer; Name: String): Boolean;
143 var
144 bFile: TBinFileWriter;
145 bMem: TBinMemoryWriter;
146 sig: DWORD;
147 str: String;
148 nPlayers: Byte;
149 i, k: Integer;
150 PID1, PID2: Word;
151 begin
152 Result := False;
153 bMem := nil;
154 bFile := nil;
156 try
157 // Ñîçäàåì ôàéë ñîõðàíåíèÿ:
158 bFile := TBinFileWriter.Create();
159 bFile.OpenFile(DataDir + 'SAVGAME' + IntToStr(n) + '.DAT',
160 SAVE_SIGNATURE, SAVE_VERSION);
162 ///// Ïîëó÷àåì ñîñòîÿíèå èãðû: /////
163 bMem := TBinMemoryWriter.Create(256);
164 // Èìÿ èãðû:
165 bMem.WriteString(Name, 32);
166 // Ïóòü ê êàðòå:
167 str := gGameSettings.WAD;
168 bMem.WriteString(str, 128);
169 // Èìÿ êàðòû:
170 str := g_ExtractFileName(gMapInfo.Map);
171 bMem.WriteString(str, 32);
172 // Êîëè÷åñòâî èãðîêîâ:
173 nPlayers := g_Player_GetCount();
174 bMem.WriteByte(nPlayers);
175 // Èãðîâîå âðåìÿ:
176 bMem.WriteDWORD(gTime);
177 // Òèï èãðû:
178 bMem.WriteByte(gGameSettings.GameType);
179 // Ðåæèì èãðû:
180 bMem.WriteByte(gGameSettings.GameMode);
181 // Ëèìèò âðåìåíè:
182 bMem.WriteWord(gGameSettings.TimeLimit);
183 // Ëèìèò î÷êîâ:
184 bMem.WriteWord(gGameSettings.GoalLimit);
185 // Ëèìèò æèçíåé:
186 bMem.WriteByte(gGameSettings.MaxLives);
187 // Èãðîâûå îïöèè:
188 bMem.WriteDWORD(gGameSettings.Options);
189 // Äëÿ êîîïà:
190 bMem.WriteWord(gCoopMonstersKilled);
191 bMem.WriteWord(gCoopSecretsFound);
192 bMem.WriteWord(gCoopTotalMonstersKilled);
193 bMem.WriteWord(gCoopTotalSecretsFound);
194 bMem.WriteWord(gCoopTotalMonsters);
195 bMem.WriteWord(gCoopTotalSecrets);
196 // Ñîõðàíÿåì ñîñòîÿíèå èãðû:
197 bFile.WriteMemory(bMem);
198 bMem.Free();
199 bMem := nil;
200 ///// /////
202 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà: /////
203 bMem := TBinMemoryWriter.Create(8);
204 sig := PLAYER_VIEW_SIGNATURE;
205 bMem.WriteDWORD(sig); // 'PLVW'
206 PID1 := 0;
207 PID2 := 0;
208 if gPlayer1 <> nil then
209 PID1 := gPlayer1.UID;
210 if gPlayer2 <> nil then
211 PID2 := gPlayer2.UID;
212 bMem.WriteWord(PID1);
213 bMem.WriteWord(PID2);
214 bFile.WriteMemory(bMem);
215 bMem.Free();
216 bMem := nil;
217 ///// /////
219 ///// Ïîëó÷àåì ñîñòîÿíèå êàðòû: /////
220 g_Map_SaveState(bMem);
221 // Ñîõðàíÿåì ñîñòîÿíèå êàðòû:
222 bFile.WriteMemory(bMem);
223 bMem.Free();
224 bMem := nil;
225 ///// /////
227 ///// Ïîëó÷àåì ñîñòîÿíèå ïðåäìåòîâ: /////
228 g_Items_SaveState(bMem);
229 // Ñîõðàíÿåì ñîñòîÿíèå ïðåäìåòîâ:
230 bFile.WriteMemory(bMem);
231 bMem.Free();
232 bMem := nil;
233 ///// /////
235 ///// Ïîëó÷àåì ñîñòîÿíèå òðèããåðîâ: /////
236 g_Triggers_SaveState(bMem);
237 // Ñîõðàíÿåì ñîñòîÿíèå òðèããåðîâ:
238 bFile.WriteMemory(bMem);
239 bMem.Free();
240 bMem := nil;
241 ///// /////
243 ///// Ïîëó÷àåì ñîñòîÿíèå îðóæèÿ: /////
244 g_Weapon_SaveState(bMem);
245 // Ñîõðàíÿåì ñîñòîÿíèå îðóæèÿ:
246 bFile.WriteMemory(bMem);
247 bMem.Free();
248 bMem := nil;
249 ///// /////
251 ///// Ïîëó÷àåì ñîñòîÿíèå ìîíñòðîâ: /////
252 g_Monsters_SaveState(bMem);
253 // Ñîõðàíÿåì ñîñòîÿíèå ìîíñòðîâ:
254 bFile.WriteMemory(bMem);
255 bMem.Free();
256 bMem := nil;
257 ///// /////
259 ///// Ïîëó÷àåì ñîñòîÿíèå òðóïîâ: /////
260 g_Player_Corpses_SaveState(bMem);
261 // Ñîõðàíÿåì ñîñòîÿíèå òðóïîâ:
262 bFile.WriteMemory(bMem);
263 bMem.Free();
264 bMem := nil;
265 ///// /////
267 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ): /////
268 if nPlayers > 0 then
269 begin
270 k := 0;
271 for i := 0 to High(gPlayers) do
272 if gPlayers[i] <> nil then
273 begin
274 // Ïîëó÷àåì ñîñòîÿíèå èãðîêà:
275 gPlayers[i].SaveState(bMem);
276 // Ñîõðàíÿåì ñîñòîÿíèå èãðîêà:
277 bFile.WriteMemory(bMem);
278 bMem.Free();
279 bMem := nil;
280 Inc(k);
281 end;
283 // Âñå ëè èãðîêè íà ìåñòå:
284 if k <> nPlayers then
285 begin
286 raise EInOutError.Create('g_SaveGame: Wrong Players Count');
287 end;
288 end;
289 ///// /////
291 ///// Ìàðêåð îêîí÷àíèÿ: /////
292 bMem := TBinMemoryWriter.Create(4);
293 // Ñòðîêà - îáîçíà÷åíèå êîíöà:
294 str := END_MARKER_STRING; // 'END'
295 bMem.WriteString(str, 3);
296 // Ñîõðàíÿåì ìàðêåð îêîí÷àíèÿ:
297 bFile.WriteMemory(bMem);
298 bMem.Free();
299 bMem := nil;
300 ///// /////
302 // Çàêðûâàåì ôàéë ñîõðàíåíèÿ:
303 bFile.Close();
304 Result := True;
306 except
307 on E1: EInOutError do
308 begin
309 g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
310 e_WriteLog('SaveState I/O Error: '+E1.Message, MSG_WARNING);
311 end;
312 on E2: EBinSizeError do
313 begin
314 g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
315 e_WriteLog('SaveState Size Error: '+E2.Message, MSG_WARNING);
316 end;
317 end;
319 bMem.Free();
320 bFile.Free();
321 end;
323 function g_LoadGame(n: Integer): Boolean;
324 var
325 bFile: TBinFileReader;
326 bMem: TBinMemoryReader;
327 sig: DWORD;
328 str, WAD_Path, Map_Name: String;
329 nPlayers, Game_Type, Game_Mode, Game_MaxLives: Byte;
330 Game_TimeLimit, Game_GoalLimit: Word;
331 Game_Time, Game_Options: Cardinal;
332 Game_CoopMonstersKilled,
333 Game_CoopSecretsFound,
334 Game_CoopTotalMonstersKilled,
335 Game_CoopTotalSecretsFound,
336 Game_CoopTotalMonsters,
337 Game_CoopTotalSecrets,
338 PID1, PID2: Word;
339 i: Integer;
340 begin
341 Result := False;
342 bMem := nil;
343 bFile := nil;
345 try
346 // Îòêðûâàåì ôàéë ñ ñîõðàíåíèåì:
347 bFile := TBinFileReader.Create();
348 if not bFile.OpenFile(DataDir + 'SAVGAME' + IntToStr(n) + '.DAT',
349 SAVE_SIGNATURE, SAVE_VERSION) then
350 begin
351 bFile.Free();
352 Exit;
353 end;
355 e_WriteLog('Loading saved game...', MSG_NOTIFY);
356 g_Game_Free();
358 g_Game_ClearLoading();
359 g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False);
360 gLoadGameMode := True;
362 ///// Çàãðóæàåì ñîñòîÿíèå èãðû: /////
363 bMem := TBinMemoryReader.Create();
364 bFile.ReadMemory(bMem);
365 // Èìÿ èãðû:
366 bMem.ReadString(str);
367 // Ïóòü ê êàðòå:
368 bMem.ReadString(WAD_Path);
369 // Èìÿ êàðòû:
370 bMem.ReadString(Map_Name);
371 // Êîëè÷åñòâî èãðîêîâ:
372 bMem.ReadByte(nPlayers);
373 // Èãðîâîå âðåìÿ:
374 bMem.ReadDWORD(Game_Time);
375 // Òèï èãðû:
376 bMem.ReadByte(Game_Type);
377 // Ðåæèì èãðû:
378 bMem.ReadByte(Game_Mode);
379 // Ëèìèò âðåìåíè:
380 bMem.ReadWord(Game_TimeLimit);
381 // Ëèìèò î÷êîâ:
382 bMem.ReadWord(Game_GoalLimit);
383 // Ëèìèò æèçíåé:
384 bMem.ReadByte(Game_MaxLives);
385 // Èãðîâûå îïöèè:
386 bMem.ReadDWORD(Game_Options);
387 // Äëÿ êîîïà:
388 bMem.ReadWord(Game_CoopMonstersKilled);
389 bMem.ReadWord(Game_CoopSecretsFound);
390 bMem.ReadWord(Game_CoopTotalMonstersKilled);
391 bMem.ReadWord(Game_CoopTotalSecretsFound);
392 bMem.ReadWord(Game_CoopTotalMonsters);
393 bMem.ReadWord(Game_CoopTotalSecrets);
394 // Cîñòîÿíèå èãðû çàãðóæåíî:
395 bMem.Free();
396 bMem := nil;
397 ///// /////
399 ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà: /////
400 bMem := TBinMemoryReader.Create();
401 bFile.ReadMemory(bMem);
402 bMem.ReadDWORD(sig);
403 if sig <> PLAYER_VIEW_SIGNATURE then // 'PLVW'
404 begin
405 raise EInOutError.Create('g_LoadGame: Wrong Player View Signature');
406 end;
407 bMem.ReadWord(PID1);
408 bMem.ReadWord(PID2);
409 bMem.Free();
410 bMem := nil;
411 ///// /////
413 // Çàãðóæàåì êàðòó:
414 ZeroMemory(@gGameSettings, SizeOf(TGameSettings));
415 gAimLine := False;
416 gShowMap := False;
417 if (Game_Type = GT_NONE) or (Game_Type = GT_SINGLE) then
418 begin
419 // Íàñòðîéêè èãðû:
420 gGameSettings.GameType := GT_SINGLE;
421 gGameSettings.MaxLives := 0;
422 gGameSettings.Options := gGameSettings.Options + GAME_OPTION_ALLOWEXIT;
423 gGameSettings.Options := gGameSettings.Options + GAME_OPTION_MONSTERS;
424 gGameSettings.Options := gGameSettings.Options + GAME_OPTION_BOTVSMONSTER;
425 gSwitchGameMode := GM_SINGLE;
426 end
427 else
428 begin
429 // Íàñòðîéêè èãðû:
430 gGameSettings.GameType := GT_CUSTOM;
431 gGameSettings.GameMode := Game_Mode;
432 gSwitchGameMode := Game_Mode;
433 gGameSettings.TimeLimit := Game_TimeLimit;
434 gGameSettings.GoalLimit := Game_GoalLimit;
435 gGameSettings.MaxLives := IfThen(Game_Mode = GM_CTF, 0, Game_MaxLives);
436 gGameSettings.Options := Game_Options;
437 end;
438 g_Game_ExecuteEvent('ongamestart');
440 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
441 g_Game_SetupScreenSize();
443 // Çàãðóçêà è çàïóñê êàðòû:
444 if not g_Game_StartMap(WAD_Path + ':\' + Map_Name, True) then
445 begin
446 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
447 Exit;
448 end;
450 // Íàñòðîéêè èãðîêîâ è áîòîâ:
451 g_Player_Init();
453 // Óñòàíàâëèâàåì âðåìÿ:
454 gTime := Game_Time;
455 // Âîçâðàùàåì ñòàòû:
456 gCoopMonstersKilled := Game_CoopMonstersKilled;
457 gCoopSecretsFound := Game_CoopSecretsFound;
458 gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
459 gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
460 gCoopTotalMonsters := Game_CoopTotalMonsters;
461 gCoopTotalSecrets := Game_CoopTotalSecrets;
463 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû: /////
464 bMem := TBinMemoryReader.Create();
465 bFile.ReadMemory(bMem);
466 // Ñîñòîÿíèå êàðòû:
467 g_Map_LoadState(bMem);
468 bMem.Free();
469 bMem := nil;
470 ///// /////
472 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ: /////
473 bMem := TBinMemoryReader.Create();
474 bFile.ReadMemory(bMem);
475 // Ñîñòîÿíèå ïðåäìåòîâ:
476 g_Items_LoadState(bMem);
477 bMem.Free();
478 bMem := nil;
479 ///// /////
481 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ: /////
482 bMem := TBinMemoryReader.Create();
483 bFile.ReadMemory(bMem);
484 // Ñîñòîÿíèå òðèããåðîâ:
485 g_Triggers_LoadState(bMem);
486 bMem.Free();
487 bMem := nil;
488 ///// /////
490 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ: /////
491 bMem := TBinMemoryReader.Create();
492 bFile.ReadMemory(bMem);
493 // Ñîñòîÿíèå îðóæèÿ:
494 g_Weapon_LoadState(bMem);
495 bMem.Free();
496 bMem := nil;
497 ///// /////
499 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ: /////
500 bMem := TBinMemoryReader.Create();
501 bFile.ReadMemory(bMem);
502 // Ñîñòîÿíèå ìîíñòðîâ:
503 g_Monsters_LoadState(bMem);
504 bMem.Free();
505 bMem := nil;
506 ///// /////
508 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ: /////
509 bMem := TBinMemoryReader.Create();
510 bFile.ReadMemory(bMem);
511 // Ñîñòîÿíèå òðóïîâ:
512 g_Player_Corpses_LoadState(bMem);
513 bMem.Free();
514 bMem := nil;
515 ///// /////
517 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ): /////
518 if nPlayers > 0 then
519 begin
520 // Çàãðóæàåì:
521 for i := 0 to nPlayers-1 do
522 begin
523 // Çàãðóæàåì ñîñòîÿíèå èãðîêà:
524 bMem := TBinMemoryReader.Create();
525 bFile.ReadMemory(bMem);
526 // Ñîñòîÿíèå èãðîêà/áîòà:
527 g_Player_CreateFromState(bMem);
528 bMem.Free();
529 bMem := nil;
530 end;
531 end;
532 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà:
533 gPlayer1 := g_Player_Get(PID1);
534 gPlayer2 := g_Player_Get(PID2);
535 if gPlayer1 <> nil then
536 begin
537 gPlayer1.Name := gPlayer1Settings.Name;
538 gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
539 gPlayer1.FActualModelName := gPlayer1Settings.Model;
540 gPlayer1.SetModel(gPlayer1.FActualModelName);
541 gPlayer1.SetColor(gPlayer1Settings.Color);
542 end;
543 if gPlayer2 <> nil then
544 begin
545 gPlayer2.Name := gPlayer2Settings.Name;
546 gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
547 gPlayer2.FActualModelName := gPlayer2Settings.Model;
548 gPlayer2.SetModel(gPlayer2.FActualModelName);
549 gPlayer2.SetColor(gPlayer2Settings.Color);
550 end;
551 ///// /////
553 ///// Ìàðêåð îêîí÷àíèÿ: /////
554 bMem := TBinMemoryReader.Create();
555 bFile.ReadMemory(bMem);
556 // Ñòðîêà - îáîçíà÷åíèå êîíöà:
557 bMem.ReadString(str);
558 if str <> END_MARKER_STRING then // 'END'
559 begin
560 raise EInOutError.Create('g_LoadGame: No END Marker');
561 end;
562 // Ìàðêåð îêîí÷àíèÿ çàãðóæåí:
563 bMem.Free();
564 bMem := nil;
565 ///// /////
567 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ:
568 if {(gMonsters <> nil) and} (gTriggers <> nil) then
569 g_Map_ReAdd_DieTriggers();
571 // Çàêðûâàåì ôàéë çàãðóçêè:
572 bFile.Close();
573 gLoadGameMode := False;
574 Result := True;
576 except
577 on E1: EInOutError do
578 begin
579 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
580 e_WriteLog('LoadState I/O Error: '+E1.Message, MSG_WARNING);
581 end;
582 on E2: EBinSizeError do
583 begin
584 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
585 e_WriteLog('LoadState Size Error: '+E2.Message, MSG_WARNING);
586 end;
587 end;
589 bMem.Free();
590 bFile.Free();
591 end;
593 end.