Вопрос
Как на Паскале/Дельфи собрать несколько файлов из папки в один файл моего формата, формат такой:
type
TDataFile = packed record
DirID: string[3];
DirName: string[12];
FileID : string[4];
FileName: string[12];
FileContent: ????;
// предполагаемое содержимое файла, не знаю какой тип использовать
end;
И как потом сделать чтение, показ например битмапа который допустим в середине этого файла, как это делается в Quake 1,2,3 и Half-Life — *.
Ответ
Что же… Вопрос неплохой, но наверное четкого ответа на него написать не получится, но напишу как это пришлось реализовывать мне.
Требуется: Записать в один файл несколько других из папки.
1. Использовать ZIP :). Понимаю что примитивно, но в некоторых случаях очень действенно. А самое удобное в данном варианте, что все это очень просто реализуется.
2. Использовать потоки. То есть несколько файлов последовательно пишутся в один поток. А потом дописывается заголовок.
type TRecInfo = record sID: String[8];
sName: String[255];
iStart, iSize: Cardinal;
end;
const UC_FILEHEADER = 'FILEVER 1.0';
{Число -> Строка} function Number2String(Number: Extended;
iLength: integer = 0;
FillChar: Char = ' '): string;
var i: integer;
Temp: String;
begin Result := '';
Temp := FloatToStr(Number);
;
i := Pos(',', Temp);
if i >
0 then Temp[i] := '.';
Result := Temp;
if iLength > 0 then for i := Length(Temp) to iLength do Result := Result + FillChar;
end;
function DoSaveFile(FileName: String): Boolean;
var uRecord: TRecInfo;
TempStr: String[64];
Header, Data, Temp: TMemoryStream;
begin Result := False;
{Создаем потоки} Data := TMemoryStream.Create;
Temp := TMemoryStream.Create;
Header := TMemoryStream.Create;
try // SKIPPED // {Организовать цикл по требуемым на запись файлами} Temp.LoadFromFile({FILE_2_SAVE});
{Сохраняем текущую позицию} uRecord.iStart := Data.Position;
{Записываем тип и имя объекта} uRecord.sID := {OBJECT_TYPE};
// Укажи тип файла (например по расширению) uRecord.sName := {FILE_2_SAVE};
// Например имя файла {Пишем данные и определяем длину} Temp.SaveToStream(Data);
uRecord.iSize := Data.Size — uRecord.iStart;
Header.Write(uRecord, SizeOf(uRecord));
// SKIPPED // {Формирование блоков данных завершено — объединяем в один файл}
{Записываем начало и длину} Header.Write(uRecord, SizeOf(uRecord));
{Пишем файловый заголовок!} TempStr := UC_FILEHEADER + Number2String(Header.Size, 64 — (SizeOf(UC_FILEHEADER)+1), ' ');
{Очистка} Temp.Clear;
Temp.Position := 0; Temp.Write(TempStr, 64);
{пишем заголовок данных, потом данные} Header.SaveToStream(Temp);
Data.SaveToStream(Temp);
{Записываем файл} Temp.SaveToFile(FileName);
Result := True; finally {закрываем потоки} Temp.Free();
Data.Free();
Header.Free();
end;
end;
Но как показывает практика данные надо еще и как-то читать. Немного печально, но необходимо. Поэтому пишем нечто похожее на это
function IsValidHeader(Header: String): Cardinal;
var i, j, k: Integer;
StrTemp: String;
begin Result := 0;
{Проверка заголовка на идентификатор} if Pos(UC_FILEHEADER, Header) = 0 then Exit;
{Читаем длину строки} for i := SizeOf(UC_FILEHEADER)+1 to 64 do begin Val(Header[i], k, j);
if j <> 0 then Break;
StrTemp := StrTemp + Header[i];
end;
Result := StrToInt(StrTemp);
end;
function DoOpenFile(FileName: String): Boolean; var i, j, RecCount: Cardinal;
uRecord: TRecInfo;
TempList: TStrings;
Buffer: PChar;
TempStr: String[64];
Knowledge, Temp: TFileStream;
TempFile, TempDir: array[0..255] of char;
begin Result := False;
{Установка для временной записи} GetTempPath(255, TempDir);
GetTempFileName(TempDir, PChar('~wt'), 0, TempFile);
{Начинаем с чтения в память} Knowledge := TFileStream.Create(FileName, fmOpenRead);
try {читаем файл} Knowledge.Position := 0;
{читаем и проверяем заголовок} Knowledge.Read(TempStr, 64);
i := IsValidHeader(TempStr);
{если файл не наш, то его открывать не нужно!!!}
if i = 0 then Exit;
{читаем заголовок данных}
Knowledge.Position := 64;
RecCount := i div SizeOf(uRecord);
for j := 1 to RecCount do begin {Устанавливаем позицию}
Knowledge.Position := 64 + (j — 1) * SizeOf(uRecord);
Knowledge.Read(uRecord, SizeOf(uRecord));
{Читаем гипотезу}
if Pos('jpg', uRecord.sID) > 0 then begin GetMem(Buffer, uRecord.iSize);
{Читаем из потока}
i := Knowledge.Position;
Knowledge.Position := 32 + RecCount * SizeOf(uRecord) + uRecord.iStart;
Knowledge.Read(Buffer^, uRecord.iSize);
Knowledge.Position := i;
{Пишем во временный файл, если нужно, если нет то можно взять напрямую из потока}
Temp := TFileStream.Create(TempFile, fmCreate);
Temp.Write(Buffer^, uRecord.iSize);
Temp.Free;
// SKIPPED // FreeMem(Buffer);
end;
end;
Result := True;
finally {удаляем файл} DeleteFile(TempFile);
{закрываем потоки}
Knowledge.Free();
end;
end;
Вот примерно такие пироги с котятами. Конечно где-то возможны ошибки, поскольку часть кода просто выкинута, но в общих чертах должно работать.
Из конференции Expert_FAQ
Copyright 2000-2004 Сообщество Чайников
Контактная информация