From 0974fa1e329aa383111b337f2956569bb2a9ce90 Mon Sep 17 00:00:00 2001 From: "Sergey \"GrayFace\" Rozhenko" Date: Sat, 4 Sep 2021 23:45:05 +0700 Subject: [PATCH] Lod Tool v1.0: Script-based command-line tool for working with Might and Magic and Heroes archives. --- LodTool/LodTool.bdsproj | 175 ++++++++ LodTool/LodTool.cfg | 38 ++ LodTool/LodTool.dpr | 870 ++++++++++++++++++++++++++++++++++++++++ LodTool/LodTool.res | Bin 0 -> 6712 bytes LodTool/LodTool1.res | Bin 0 -> 800 bytes 5 files changed, 1083 insertions(+) create mode 100644 LodTool/LodTool.bdsproj create mode 100644 LodTool/LodTool.cfg create mode 100644 LodTool/LodTool.dpr create mode 100644 LodTool/LodTool.res create mode 100644 LodTool/LodTool1.res diff --git a/LodTool/LodTool.bdsproj b/LodTool/LodTool.bdsproj new file mode 100644 index 0000000..a38b9e8 --- /dev/null +++ b/LodTool/LodTool.bdsproj @@ -0,0 +1,175 @@ + + + + + + + + + + + + LodTool.dpr + + + 7.0 + + + 8 + 0 + 1 + 1 + 0 + 0 + 1 + 1 + 1 + 0 + 0 + 1 + 0 + 1 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 1 + 1 + 1 + True + True + WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; + + False + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + False + False + False + True + True + True + True + True + True + + + + 0 + 0 + False + 1 + False + False + False + 16384 + 1048576 + 4194304 + + + + + + + + + + + + False + + + + + + False + c:\Games\mm65\DATA\_gen\ + + True + False + + + + $00000000 + + + + True + False + 1 + 0 + 0 + 0 + False + False + False + False + False + 1033 + 1252 + + + + LodTool + 1.0.0.0 + + Sergey Rozhenko (http://grayface.github.io) + + + LodTool + 1.0.0.0 + + + + diff --git a/LodTool/LodTool.cfg b/LodTool/LodTool.cfg new file mode 100644 index 0000000..67ff7e5 --- /dev/null +++ b/LodTool/LodTool.cfg @@ -0,0 +1,38 @@ +-$A8 +-$B- +-$C+ +-$D+ +-$E- +-$F- +-$G+ +-$H+ +-$I+ +-$J- +-$K- +-$L+ +-$M- +-$N+ +-$O+ +-$P+ +-$Q- +-$R- +-$S- +-$T- +-$U- +-$V+ +-$W- +-$X+ +-$YD +-$Z1 +-cg +-AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +-H+ +-W+ +-M +-$M16384,1048576 +-K$00400000 +-LE"C:\Users\Serg\Documents\Borland Studio Projects\Bpl" +-LN"C:\Users\Serg\Documents\Borland Studio Projects\Bpl" +-w-UNSAFE_TYPE +-w-UNSAFE_CODE +-w-UNSAFE_CAST diff --git a/LodTool/LodTool.dpr b/LodTool/LodTool.dpr new file mode 100644 index 0000000..3e04681 --- /dev/null +++ b/LodTool/LodTool.dpr @@ -0,0 +1,870 @@ +program LodTool; + +uses + Windows, Messages, SysUtils, RSLod, Graphics, Classes, RSSysUtils, RSQ, + RSStrUtils, dzlib, RSGraphics, RSSimpleExpr, SysConst; + +// note: not using try..finally, because any error leads to program closing +//{$R *.res} +{$R LodTool1.res} // don't include icon + +var + Lod: TRSMMArchive; + List: TStringList; + Data: TRSByteArray; + StoredLods, StoredLists: TStringList; + BaseDir: string; + ErrorFile: string; + ErrorLine: int; + +procedure DoExecCommands(const str, fname: string); forward; + +function SortedList(CaseSens: Boolean): TStringList; +begin + Result:= TStringList.Create; + Result.CaseSensitive:= CaseSens; + Result.Duplicates:= dupIgnore; + Result.Sorted:= true; +end; + +procedure MyParseExpr(const s: string; var a: TRSOperatorArray; Custom: TRSCustomOpEvent = nil; AcceptEmpty: Boolean = false); +begin + if RSParseExpr(s, a, Custom, AcceptEmpty) <> 0 then + raise Exception.Create('Expression syntax error'); +end; + +function ExprToInt(const expr: string): int; +var + a: TRSOperatorArray; +begin + MyParseExpr(expr, a); + Result:= Round(RSCalcExpr(a)); +end; + +//-- TCallbacks + +var + ResX, ResY: ext; + +type + TCallbacks = class + class function ChangePalIndex(const Name: string; data: ptr): ext; + class procedure FindDimentions(Sender: TRSLwd; Name: PChar; + var Width, Height: int2; RealWidth, RealHeight: int); + end; + +class function TCallbacks.ChangePalIndex(const name: string; data: ptr): ext; +begin + if Name = 'pal' then + Result:= int(data) + else + Result:= 0; +end; + +class procedure TCallbacks.FindDimentions(Sender: TRSLwd; Name: PChar; + var Width, Height: int2; RealWidth, RealHeight: int); +begin + if ResX <> 0 then + Width:= Round(RealWidth/ResX); + if ResY <> 0 then + Height:= Round(RealHeight/ResY); +end; + +//-- Params + +var + ParamsShift: int; + ParamsList: TStringList; + IncludeFmt: TRSKeyValueStringList; + CmdList: TStringList; + +function Param(i: int): string; +begin + inc(i, ParamsShift); + if i < ParamsList.Count then + Result:= ParamsList[i] + else + Result:= ParamStr(i + 1); +end; + +procedure LoadParams(const name: string); +begin + ParamsList.LoadFromFile(name); +end; + +procedure ShiftParams(i: int); +begin + inc(ParamsShift, i); +end; + +procedure IncludeParam(const name, val: string); +begin + IncludeFmt.Add(name, val); +end; + +procedure ReplaceParam(const name, val: string); +begin + CmdList.Text:= RSStringReplace(CmdList.Text, '<' + name + '>', val, [rfReplaceAll, rfIgnoreCase]); +end; + +procedure ReplaceCopy(const name, val: string; i, j: int); +begin + if i < 0 then + i:= length(val) + i + 1; + if j < 0 then + j:= length(val) + j + 1; + ReplaceParam(name, Copy(val, i, j - i + 1)); +end; + +procedure Calc(const name: string; expr: int; fmt: string); +begin + if fmt = '' then + fmt:= '%d'; + ReplaceParam(name, Format(fmt, [expr])); +end; + +//-- List + +procedure AddPattern(const pat: string); +var + i: int; +begin + for i := 0 to Lod.Count - 1 do + if RSWildcardMatch(pat, Lod.Names[i]) then + List.Add(Lod.Names[i]); +end; + +procedure RemovePattern(const pat: string); +var + i: int; +begin + for i := List.Count - 1 downto 0 do + if RSWildcardMatch(pat, List[i]) then + List.Delete(i); +end; + +procedure SetPattern(const pat: string); +begin + List.Clear; + AddPattern(pat); +end; + +procedure SaveList(const name: string); +begin + List.SaveToFile(name); +end; + +procedure LoadList(const name: string); +begin + List.LoadFromFile(name); +end; + +procedure StoreList(const s: string); +var + i: int; +begin + i:= StoredLists.Add(s); + StoredLists.Objects[i].Free; + StoredLists.Objects[i]:= List; + List:= SortedList(false); +end; + +procedure RestoreList(const s: string); +var + i: int; +begin + if StoredLists.Find(s, i) then + begin + FreeAndNil(List); + List:= ptr(StoredLists.Objects[i]); + StoredLists.Delete(i); + end else + List.Clear; +end; + +procedure ListAdd(const s: string); +var + i: int; +begin + if not StoredLists.Find(s, i) then + exit; + with TStringList(StoredLists.Objects[i]) do + for i:= 0 to Count - 1 do + List.Add(Strings[i]); +end; + +procedure DoListRemove(const s: string; b: Boolean); +var + i, j: int; +begin + if not StoredLists.Find(s, i) then + exit; + with TStringList(StoredLists.Objects[i]) do + for i:= List.Count - 1 downto 0 do + if Find(List.Strings[i], j) = b then + List.Delete(i); +end; + +procedure ListRemove(const s: string); +begin + DoListRemove(s, true); +end; + +procedure ListAnd(const s: string); +begin + DoListRemove(s, false); +end; + +procedure ListInvert(const s: string); +begin + StoreList(s); + SetPattern('*'); + ListRemove(s); +end; + +//-- Lod + +procedure Load(const name: string); +begin + FreeAndNil(Lod); + Lod:= RSLoadMMArchive(name); + Lod.RawFiles.WriteOnDemand:= true; + if Lod is TRSLwd then + TRSLwd(Lod).OnFindDimentions:= TCallbacks.FindDimentions; +end; + +procedure Save(const name: string); +begin + if SameText(name, Lod.RawFiles.FileName) then + Lod.RawFiles.Rebuild + else + Lod.SaveAs(name); +end; + +procedure StoreLod(const s: string); +var + i: int; +begin + i:= StoredLods.Add(s); + StoredLods.Objects[i].Free; + StoredLods.Objects[i]:= Lod; + Lod:= nil; +end; + +procedure RestoreLod(const s: string); +var + i: int; +begin + FreeAndNil(Lod); + if StoredLods.Find(s, i) then + begin + Lod:= ptr(StoredLods.Objects[i]); + StoredLods.Delete(i); + end; +end; + +procedure SetWriteOnDemand(i: int); +begin + Lod.RawFiles.WriteOnDemand:= i <> 0; +end; + +//-- Operations + +procedure DeleteList; +var + i: int; +begin + for i:= 0 to List.Count - 1 do + Lod.RawFiles.Delete(List[i]); + //List.Clear; +end; + +procedure RenameLodFile(const old, new: string); +var + i: int; +begin + if Lod.RawFiles.FindFile(old, i) then + Lod.RawFiles.Rename(i, new); +end; + +procedure MergeLod(const s: string); +var + i: int; +begin + if not StoredLods.Find(s, i) then + exit; + TRSMMArchive(StoredLods.Objects[i]).RawFiles.MergeTo(Lod.RawFiles); +end; + +procedure CompareLod(const name: string); +var + lod2: TRSMMArchive; + b: Boolean; + i, j: int; +begin + if not StoredLods.Find(name, i) then + exit; + List.Clear; + lod2:= TRSMMArchive(StoredLods.Objects[i]); + with Lod do + for i := 0 to Count - 1 do + begin + j:= i; + b:= (j < lod2.Count) and SameText(Names[i], lod2.Names[j]) or + lod2.RawFiles.FindFile(Names[i], j); + if not b or not CompareFiles(lod2, i, j) then + List.Add(Lod.Names[i]); + end; +end; + +procedure ExportFiles(const s: string); +var + i, j: int; +begin + for i:= 0 to List.Count - 1 do + if Lod.RawFiles.FindFile(List[i], j) then + Lod.Extract(j, s); +end; + +procedure ImportFiles(const s: string); +begin + with TRSFindFile.Create(s) do + try + while FindEachFile do + Lod.Add(FileName); + finally + Free; + end; +end; + +procedure ImportResolution(x: int; const sy: string); +begin + if sy = '' then + ResY:= x + else + ResY:= ExprToInt(sy); + ResX:= x; +end; + +type + TMMLodFile = packed record + Name: array[1..16] of char; + BmpSize: int; + DataSize: int; + BmpWidth: int2; + BmpHeight: int2; + BmpWidthLn2: int2; // textures: log2(BmpWidth) + BmpHeightLn2: int2; // textures: log2(BmpHeight) + BmpWidthMinus1: int2; // textures: BmpWidth - 1 + BmpHeightMinus1: int2; // textures: BmpHeight - 1 + Palette: int2; + _unk: int2; // runtime palette index + UnpSize: int; + Bits: int; // Bits: 2 - mipmaps, $10 - something important too, + // $400 - don't free buffers, $200 - transparent icon + // Data... + // Palette... + end; + + TMMSprite = packed record + Name: array[1..12] of char; + Size: int; + w: int2; + h: int2; + Palette: int2; + unk_1: int2; + yskip: int2; // number of clear lines at the bottom + unk_2: int2; // used in runtime only, for bits + UnpSize: int; + end; + +{procedure AddPalIndex(dn: int); +var + m: TMemoryStream; + r: TStream; + i, j: int; +begin + m:= TMemoryStream.Create; + with Lod.RawFiles do + for j := 0 to List.Count - 1 do + if FindFile(List[j], i) and not IsPacked[i] then + begin + m.Size:= Size[i]; + m.Seek(0, 0); + r:= GetAsIsFileStream(i); + r.ReadBuffer(m.Memory^, Size[i]); + FreeAsIsFileStream(i, r); + if (Lod as TRSLodBase).Version = RSLodSprites then + with TMMSprite(m.Memory^) do + inc(Palette, dn) + else if TRSLodBase(Lod).Version = RSLodBitmaps then + with TMMLodFile(m.Memory^) do + inc(Palette, dn); + Add(Name[i], m, Size[i], clNone); + end; + + m.Free; +end;} + +procedure ChangePalIndex(const expr: string); +var + a: TRSOperatorArray; + m: TMemoryStream; + r: TStream; + i, j: int; +begin + MyParseExpr(expr, a); + RSCalcExpr(a); + m:= TMemoryStream.Create; + with Lod.RawFiles do + for j := 0 to List.Count - 1 do + if FindFile(List[j], i) and not IsPacked[i] then + begin + m.Size:= Size[i]; + m.Seek(0, 0); + r:= GetAsIsFileStream(i); + r.ReadBuffer(m.Memory^, Size[i]); + FreeAsIsFileStream(i, r); + if (Lod as TRSLodBase).Version = RSLodSprites then + with TMMSprite(m.Memory^) do + Palette:= Round(RSCalcExpr(a, TCallbacks.ChangePalIndex, ptr(Palette))) + else if TRSLodBase(Lod).Version = RSLodBitmaps then + with TMMLodFile(m.Memory^) do + Palette:= Round(RSCalcExpr(a, TCallbacks.ChangePalIndex, ptr(Palette))); + Add(Name[i], m, Size[i], clNone); + end; + + m.Free; +end; + +procedure CopyBits(const s: string); +var + hdr: TMMLodFile; + r: TStream; + m: TMemoryStream; + i, idx, k1, k2: int; +begin + if not StoredLods.Find(s, idx) then + exit; + Assert((Lod as TRSLodBase).Version in [RSLodBitmaps, RSLodIcons]); + m:= TMemoryStream.Create; + with TRSMMArchive(StoredLods.Objects[idx]) do + for i := 0 to List.Count - 1 do + if Lod.RawFiles.FindFile(List[i], k1) and RawFiles.FindFile(List[i], k2) then + begin + r:= RawFiles.GetAsIsFileStream(k2); + r.ReadBuffer(hdr, SizeOf(hdr)); + RawFiles.FreeAsIsFileStream(k2, r); + m.Size:= 0; + Lod.RawFiles.RawExtract(k1, m); + TMMLodFile(m.Memory^).Bits:= hdr.Bits; + m.Position:= 0; + Lod.RawFiles.Add(Lod.Names[k1], m, m.Size, clNone); + end; + m.Free; +end; + +procedure CheckBit(v: int); +var + hdr: TMMLodFile; + r: TStream; + i: int; +begin + with Lod, RawFiles do + for i := 0 to Count - 1 do + begin + r:= GetAsIsFileStream(i); + r.ReadBuffer(hdr, SizeOf(hdr)); + FreeAsIsFileStream(i, r); + if (hdr.Bits and v) <> 0 then + List.Add(Name[i]); + end; +end; + +procedure ListBits; +var + hdr: TMMLodFile; + r: TStream; + i, j: int; k: uint; +begin + with Lod, RawFiles do + for i := 0 to Count - 1 do + begin + r:= GetAsIsFileStream(i); + r.ReadBuffer(hdr, SizeOf(hdr)); + FreeAsIsFileStream(i, r); + k:= 1; + for j := 0 to 31 do + begin + if hdr.Bits and k <> 0 then + List.Add(IntToStr(k)); + k:= k*2; + end; + end; +end; + +procedure JoinPixelData; +var + a: TRSByteArray; + b: TBitmap; + i, j, k: int; +begin + with Lod.RawFiles do + for j := 0 to List.Count - 1 do + if FindFile(List[j], i) then + begin + b:= Lod.ExtractArrayOrBmp(i, a); + Assert((b <> nil) and (b.PixelFormat = pf8bit)); + k:= length(Data); + SetLength(Data, k + b.Width*b.Height); + RSBitmapToBuffer(@Data[k], b); + b.Free; + end; +end; + +procedure SaveData(const name: string); +begin + RSSaveFile(name, Data); +end; + +procedure ClearData; +begin + Data:= nil; +end; + +//-- File operations + +procedure DeleteF(const name: string); +begin + DeleteFile(name); +end; + +procedure RenameF(const old, new: string); +begin + RenameFile(old, new); +end; + +procedure RenameOverF(const old, new: string); +begin + DeleteFile(new); + RenameFile(old, new); +end; + +procedure CopyF(const old, new: string); +begin + CopyFile(PChar(old), PChar(new), true); +end; + +procedure CopyOverF(const old, new: string); +begin + CopyFile(PChar(old), PChar(new), false); +end; + +procedure ChangeDir(const s: string); +begin + if s <> '' then + SetCurrentDir(s) + else + SetCurrentDir(BaseDir); +end; + +procedure mkdir(const s: string); +begin + RSCreateDir(s); +end; + +//-- Command Files + +procedure ReplaceAndExec(s: string; sl: TRSKeyValueStringList; const fname: string); +var + i: int; +begin + for i:= 0 to sl.Count - 1 do + s:= RSStringReplace(s, '<' + sl[i] + '>', sl.Values[i], [rfReplaceAll, rfIgnoreCase]); + IncludeFmt.Clear; + DoExecCommands(s, fname); +end; + +procedure ExecCommands(const name: string); +begin + ReplaceAndExec(RSLoadTextFile(name), IncludeFmt, name); +end; + +procedure EnumList(const name: string); +var + lst: TStringList; + sl: TRSKeyValueStringList; + s: string; + i: int; +begin + s:= RSLoadTextFile(name); + lst:= TStringList.Create; + lst.Assign(List); + sl:= TRSKeyValueStringList.Create(true); + sl.Assign(IncludeFmt); + for i:= 0 to lst.Count - 1 do + begin + sl.KeyValue['item']:= lst[i]; + ReplaceAndExec(s, sl, name); + end; + sl.Free; +end; + +//-- Commands + +type + TMyCmd1 = procedure(const p1: string); + TMyCmd2 = procedure(const p1, p2: string); + TMyCmdNum = procedure(p1: int); + TMyCmdNum1s1 = procedure(i: int; const p2: string); + TMyCmd2n2 = procedure(const p1, p2: string; i, j: int); + TMyCmd1n1s1 = procedure(const p1: string; p2: int; p3: string); + +procedure ExecCmd(const ps: TRSParsedString); +var + cmd, p1, p2, p3, p4: string; + + procedure c(const name: string; f: TMyCmd1); overload; + begin + if cmd = name then + f(p1) + else if cmd = name + '%' then + f(Param(ExprToInt(p1))); + end; + + procedure c(const name: string; f: TMyCmd2); overload; + begin + if cmd = name then + f(p1, p2) + else if cmd = name + '%' then + f(Param(ExprToInt(p1)), Param(ExprToInt(p2))); + end; + + procedure c2(const name: string; f: TMyCmd2); overload; + begin + if cmd = name then + f(p1, p2) + else if cmd = name + '%' then + f(p1, Param(ExprToInt(p2))); + end; + + procedure c2(const name: string; f: TMyCmd2n2); overload; + begin + if cmd = name then + f(p1, p2, ExprToInt(p3), ExprToInt(p4)) + else if cmd = name + '%' then + f(p1, Param(ExprToInt(p2)), ExprToInt(p3), ExprToInt(p4)); + end; + + procedure c(const name: string; f: TMyCmdNum); overload; + begin + if cmd = name then + f(ExprToInt(p1)); + end; + + procedure c(const name: string; f: TProcedure); overload; + begin + if cmd = name then + f; + end; + + procedure c(const name: string; f: TMyCmdNum1s1); overload; + begin + if cmd = name then + f(ExprToInt(p1), p2); + end; + + procedure c(const name: string; f: TMyCmd1n1s1); overload; + begin + if cmd = name then + f(p1, ExprToInt(p2), p3); + end; + +begin + cmd:= LowerCase(Trim(RSGetToken(ps, 0))); + p1:= RSGetToken(ps, 1); + p2:= RSGetToken(ps, 2); + p3:= RSGetToken(ps, 3); + p4:= RSGetToken(ps, 4); + c('+', &AddPattern); + c('-', &RemovePattern); + c('=', &SetPattern); + c('save list', &SaveList); + c('load list', &LoadList); + c('list->', &StoreList); + c('list<-', &RestoreList); + c('list+', &ListAdd); + c('list-', &ListRemove); + c('list and', &ListAnd); + c('list invert', &ListInvert); + + c('load', &Load); + c('save', &Save); + c('lod->', &StoreLod); + c('lod<-', &RestoreLod); + c('write on demand', &SetWriteOnDemand); + + c('del', &DeleteList); + c('rename', &RenameLodFile); +// c('pal add', &AddPalIndex); + c('pal=', &ChangePalIndex); + c('lod+', &MergeLod); + c('compare', &CompareLod); + c('export', &ExportFiles); + c('import', &ImportFiles); + c('import resolution', &ImportResolution); + + c('join pixel data', &JoinPixelData); + c('save data', &SaveData); + c('clear data', &ClearData); + + c('copy bits', &CopyBits); + c('check bit', &CheckBit); + c('list bits', &ListBits); + + c('include', &ExecCommands); + c2('include replace', &IncludeParam); + c2('replace', &ReplaceParam); + c('enum', &EnumList); + c2('substr', &ReplaceCopy); + c('calc', &Calc); + + c('delete file', &DeleteF); + c('rename file', &RenameF); + c('copy file', &CopyF); + c('force rename file', &RenameOverF); + c('force copy file', &CopyOverF); + c('cd', &ChangeDir); + c('mkdir', &mkdir); + + c('load params', &LoadParams); + c('shift params', &ShiftParams); +end; + +procedure InfoBox; +const + info = 'Usage: LodTool.exe Commands.txt Param1 Param2 ...' + + #10'(Press Ctrl+C to copy this text to clipboard)' + + #10'All commands that take strings as arguments have a version with "%"' + + ' at the end of their name that takes parameter indexes instead.' + + #10'For example, "load%|1" would load Param1 file, "replace%|dir|1" would replace "" with Param1.' + + #10'Omitted parameters are treated as empty strings (e.g. "list->" is perfectly fine).' + + #10'In place of any integer parameter you can use an expression (with +,-,*,^,/,div,mod,and,or,<,<=,>,>=,<> operators).' + + #10#10'Commands (case-insensitive):'; + cmd: array[1..38] of string = ( + '+|mask`Add all files matching the mask (e.g. "pal*") to the list', + '-|mask`Remove all items matching the mask from the list', + '=|mask`Clear the list and add all files matching the mask', + 'save list|fname`Save the list as text to "fname"', + 'load list|fname`Load the list from "fname"', + 'list->|name`Store list in "name" variable, switch to empty list', + 'list<-|name`Restore list from "name"', + 'list+|name`Add items from list "name" to current list', + 'list-|name`Remove items of list "name" from current list', + 'list and|name`Only keep items that are also present in list "name" in current list', + 'list invert|name`Combination of commands: list->|name, =|*, list-|name', + + '~load|fname`Load archive from "fname"', + 'save|fname`Save archive to "fname"', + 'lod->|name`Move current archive to variable "name" (independant of "name" list variable)', + 'lod<-|name`Restore archive from variable "name"', + + 'del`Delete all files found in the list from current archive', + 'rename|old|new`Rename "old" file into "new" in the archive, overwrite if needed', +// 'pal add|N`Add N to palette indexes of all files from the list', + 'pal=|expr`Set palette index to the result of expression "expr" in which "pal" stands for current palette index', + 'lod+|name`Add(merge) all contents of archive in the "name" variable to current archive', + 'compare|name`Set list to all files that have changed in current archive compared to the one in "name" variable', + 'export|fname`Export files in the list to folder "fname"', + 'import|fmask`Import files matching mask (and path) "fmask"', + 'import resolution|x_res|y_res`set resolution for subsequent LWD imports, e.g. "import resolution|2" for 2x original resolution', + 'write on demand|on`If set to 1 (default), keeps all changes in memory until Save is called. If set to 0, performs all operations immediately, like MMArchive', + +{ 'join pixel data', + 'save data', + 'clear data', + + 'copy bits', + 'check bit', + 'list bits',} + + '~replace|name|value`Replace "" with "value" in current file', + 'include|fname`Include commands file', + 'include replace|name|value`Replace "" with "value" in the next included file', + 'enum|fname`Run commands file for every item in the list, with "" replaced by item name', + 'substr|name|str|i|j`Replace "" with substring of "str" from "i" to "j", where 1 is the first character, -1 is the last, -2 is the one before it etc', + 'calc|name|expr|fmt`Replace "" with the result of expression "expr" rounded to an integer, optionally formatted according to "fmt"', + + '~delete file|fname`Delete file from file system', + 'rename file|old|new`Rename file in file system if destination doesn''t exits', + 'copy file|old|new`Copy file if destination doesn''t exits', + 'force rename file|old|new`Rename file in file system, overwrite if present', + 'force copy file|old|new`Copy file in file system, overwrite if present', + 'cd|fname`Change current folder to "fname" or to initial folder if there''s no parameter', + 'mkdir|fname`Create folder "fname" (and all missing parent folders)', + +// '~load params|fname`Load text file as the list of Param1, Param2, ...', +// 'shift params|N`Add N to param indexes used (calling it multiple times would accumulate shifts)', + + '' + ); +var + s: string; + i: int; +begin + s:= info; + for i := 1 to high(cmd) - 1 do + s:= s + RSStringReplace(RSStringReplace('~' + cmd[i], '`', ' '#9'- '), '~', #10' ') + '.'; +// s:= s + #13#10 + RSStringReplace(cmd[i], '`', #13#10' ') + '.'; + RSMessageBox(0, s, 'LodTool Help', MB_ICONINFORMATION); +end; + +procedure DoExecCommands(const str, fname: string); +var + sl: TStringList; + i: int; +begin + sl:= TStringList.Create; + sl.Text:= str; + for i := 0 to sl.Count - 1 do + begin + CmdList:= sl; + ErrorFile:= fname; + ErrorLine:= i; + ExecCmd(RSParseString(sl[i], ['|'])); + end; + sl.Free; +end; + +procedure ShowError; +var + Title: array[0..63] of Char; + msg: string; +begin + if ExceptObject is Exception then + msg:= Exception(ExceptObject).Message; + LoadString(FindResourceHInstance(HInstance), PResStringRec(@SExceptTitle).Identifier, Title, SizeOf(Title)); + RSMessageBox(0, Format('%s:%d: %s.', [ErrorFile, ErrorLine + 1, msg]), Title, MB_OK or MB_ICONSTOP or MB_TASKMODAL); +end; + +begin + if ParamStr(1) = '' then + begin + InfoBox; + exit; + end; + ParamsList:= TStringList.Create; + List:= SortedList(false); + IncludeFmt:= TRSKeyValueStringList.Create; + StoredLods:= SortedList(true); + StoredLists:= SortedList(true); + BaseDir:= GetCurrentDir; + ErrorFile:= Param(0); + ErrorLine:= -2; + try + ExecCommands(Param(0)); + except + ShowError; + Halt(1); + end; +end. diff --git a/LodTool/LodTool.res b/LodTool/LodTool.res new file mode 100644 index 0000000000000000000000000000000000000000..5108e276ae73214fe5756d19b0db725881421e98 GIT binary patch literal 6712 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 literal 0 HcmV?d00001 diff --git a/LodTool/LodTool1.res b/LodTool/LodTool1.res new file mode 100644 index 0000000000000000000000000000000000000000..65b9b81f2e813ee1743b1b0850fb7b3af67c1e90 GIT binary patch literal 800 zcmZ9KJ5NGE5QPtfLONQh~W%CzIvQSfxWg2PWmYTrQglpjEoevZ)un67TiQs;ask*;k1V ztZjT{etspZPdLK=t!E`844MBU-OyX`%{A3Pu_D+88mbF(te(bX^NuO0tdg#nWi*(g zL)cQf12fV@E$~a!wAgix)W@X`zy!_;nVH(&YeLTfwXx%BtU78C$8;B6w(FYIO{v`A zFH{5*X3%1q=Z=lhG2B!0sWY_H$VZ+vB9TYx}amn z&8fTAIll;W3v7?r23O={08?KJy|FXJC-9!SSDW4quj^1TqH+rR%zdzsVC$TkE_)Tb zOYqQk8;290p1_)a3pC7kZoTho*?rx`@4LJ2ZQivf?`)mTK8iL$@_iW}n^9QD)aHjwO literal 0 HcmV?d00001