unit Chaotica;

interface

uses Global, RegularExpressionsCore, RegexHelper, Classes, SysUtils,
     Windows, ShellAPI, ControlPoint, Translation;

const
  export_flame = 'chaotica_export.flame';

function C_GetPathOf(filename: string; usex64: boolean): string;
function C_SupportsDllPlugins(usex64: boolean): boolean;
function C_IsDllPluginBlacklisted(filename: string; usex64: boolean): boolean;
function C_IsVariationNative(name: string; usex64: boolean): boolean;
function C_IsDllPluginInstalled(filename: string): boolean;

procedure C_SyncDllPlugins;
procedure C_ExecuteChaotica(flamexml: string; plugins: TStringList; usex64: boolean);
//procedure C_InstallVariation(name: string);

implementation

(*   // AV: rewrote and moved to Global unit
function CheckX64: Boolean;
var
  SEInfo: TShellExecuteInfo;
  ExitCode: DWORD;
  ExecuteFile, ParamString, StartInString: string;
begin
  {$ifdef Apo7X64}
  Result := true;
  exit;
  {$endif}

  ExecuteFile := ExtractFilePath(Application.ExeName) + 'chk64.exe';
  FillChar(SEInfo, SizeOf(SEInfo), 0);
  SEInfo.cbSize := SizeOf(TShellExecuteInfo);

  with SEInfo do begin
    fMask := SEE_MASK_NOCLOSEPROCESS;
    Wnd := Application.Handle;
    lpFile := PChar(ExecuteFile) ;
    nShow := SW_SHOWNORMAL;
  end;
  
  if ShellExecuteEx(@SEInfo) then
  begin
    repeat
      Application.ProcessMessages;
      GetExitCodeProcess(SEInfo.hProcess, ExitCode);
    until (ExitCode <> STILL_ACTIVE) or Application.Terminated;
    Result := (ExitCode = 0);
  end else begin
    Result := false;
  end;
end;
*)

function C_GetPathOf(filename: string; usex64: boolean): string;
begin
  Result := ChaoticaPath + '\' + filename;
end;

function C_SupportsDllPlugins(usex64: boolean): boolean;
const
  re_root : string = '<variation_compatibility\s+(.*?)>.*?</variation_compatibility>';
  re_attrib : string = 'supports_dll_plugins="(.*?)"';
var
  xml_file : TStringList;
  xml_text, attrib, value : string;
begin
  if usex64 then begin
    Result := false;
    Exit;
  end;

  xml_file := TStringList.Create;
// AV: for compatibility with new Chaotica versions
  if not FileExists(ChaoticaPath + '\variation_compatibility.xml') then
  begin
    xml_file.Add('<variation_compatibility >' + #13#10 + '</variation_compatibility>');
    xml_file.SaveToFile(ChaoticaPath + '\variation_compatibility.xml');
    xml_file.Clear;
  end;
  xml_file.LoadFromFile(C_GetPathOf('variation_compatibility.xml', false));
  xml_text := xml_file.Text;
  xml_file.Free;

  attrib := GetStringPart(xml_text, re_root, 1, 'supports_dll_plugins="false"');
  value := GetStringPart(attrib, re_attrib, 1, 'false');

  Result := (value = 'true');
end;

function C_IsDllPluginBlacklisted(filename: string; usex64: boolean): boolean;
var
  i: integer;
  blacklist: TStringList;
begin
  blacklist := TStringList.Create;
  if not FileExists(ChaoticaPath + '\plugin_dll_blacklist.txt') then
  begin
    blacklist.Add('avMobius.dll');
    blacklist.Add('Cross.dll');
    blacklist.Add('Epispiral.dll');
    blacklist.Add('EpispiralVariationPlugin.dll');
    blacklist.Add('FlowerVariationPlugin.dll');
    blacklist.Add('Lissajous.dll');
    blacklist.Add('Mandelbrot.dll');
    blacklist.Add('ShapeVariationPlugin.dll');
    blacklist.Add('slinky.dll');
    blacklist.Add('Spirograph.dll');
    blacklist.Add('Square.dll');
    blacklist.Add('Stretchy Pants.dll');
    blacklist.Add('Waffle.dll');
    blacklist.SaveToFile(ChaoticaPath + '\plugin_dll_blacklist.txt');
  end;

  blacklist.LoadFromFile(C_GetPathOf('plugin_dll_blacklist.txt', usex64));

  for i := 0 to blacklist.Count - 1 do begin
    if LowerCase(filename) = LowerCase(blacklist.Strings[i]) then begin
      Result := true;
      blacklist.Free;
      Exit;
    end;
  end;

  blacklist.Free;
  Result := false;
end;

function C_IsVariationNative(name: string; usex64: boolean): boolean;
const
  re_root : string = '<variation_compatibility.*?>(.*?)</variation_compatibility>';
  re_var : string = '<variation name="(.*?)".*?/>';
var
  xml, var_name : string;
  xml_file : TStringList;
  find_var : TPerlRegEx;
  found_var : boolean;
begin

  xml_file := TStringList.Create;
  // AV: for compatibility with new Chaotica versions
  if not FileExists(ChaoticaPath + '\variation_compatibility.xml') then
  begin
    xml_file.Add('<variation_compatibility >' + #13#10 + '</variation_compatibility>');
    xml_file.SaveToFile(ChaoticaPath + '\variation_compatibility.xml');
    xml_file.Clear;
  end;
  xml_file.LoadFromFile(C_GetPathOf('variation_compatibility.xml', false));
  xml := xml_file.Text;
  xml_file.Free;

  find_var := TPerlRegEx.Create;
  find_var.RegEx := re_var;
  find_var.Options := [preSingleLine, preCaseless];
  find_var.Subject := GetStringPart(xml, re_root, 1, '');
  found_var := find_var.Match;

  while found_var do begin
    var_name := String(find_var.Groups[1]);
    found_var := find_var.MatchAgain;

    if LowerCase(name) = var_name then begin
      find_var.Destroy;
      Result := true;
      Exit;
    end;
  end;

  find_var.Destroy;
  Result := false;
end;

function C_IsDllPluginInstalled(filename: string): boolean;
var
  path : string;
begin
  path := C_GetPathOf('plugins\' + filename, false);
  Result := FileExists(path);
end;

////////////////////////////////////////////////////////////////////
(*
procedure C_InstallVariation(name: string);
var
  filename: string;
begin
  filename := GetFileNameOfVariation(name);

  if (filename = '') then Exit;
  if C_IsDllPluginInstalled(filename) then Exit;

  CopyFile(PCHAR(filename), PCHAR(C_GetPathOf('plugins\' +
    ExtractFileName(filename), false)), false);
end;
*)

procedure C_SyncDllPlugins;
var
  src_dir: string;
  tgt_dir: string;

  searchResult: TSearchRec;
begin
  src_dir := PluginPath;
  tgt_dir := C_GetPathOf('Plugins', false);

  if (not DirectoryExists(src_dir)) then Exit;
  if (not DirectoryExists(tgt_dir)) then Exit;
  //CreateDir(ChaoticaPath + '\Plugins');

  // First clear all plugins on Chaotica side
  if FindFirst(tgt_dir + '\*.dll', faAnyFile, searchResult) = 0 then
  begin
    repeat
      DeleteFile(PCHAR(tgt_dir + '\' + searchResult.Name)) ;
    until (FindNext(searchResult) <> 0);
    SysUtils.FindClose(searchResult);
  end;

  // Then copy all plugins from Apophysis to Chaotica
  if FindFirst(src_dir + '*.dll', faAnyFile, searchResult) = 0 then
  begin
    repeat
      if not C_IsDllPluginBlacklisted(searchResult.Name, false)
      then CopyFile(
        PCHAR(src_dir + '\' + searchResult.Name),
        PCHAR(tgt_dir + '\' + searchResult.Name),
        false);
    until (FindNext(searchResult) <> 0);
    SysUtils.FindClose(searchResult);
  end;
end;

procedure C_ExecuteChaotica(flamexml: string; plugins: TStringList; usex64: boolean);
var
  i: integer;
  name, fname: string;
  fails: TStringList;
  txt: TStringList;
  fin_usex64: boolean;
begin
  fails := TStringList.Create;

  {$ifdef CPUX64}
  fin_usex64 := true;
  {$else}
  fin_usex64 := usex64; // currently useless...
  for i := 0 to plugins.Count - 1 do begin
    name := plugins.Strings[i];
    fin_usex64 := fin_usex64 and C_IsVariationNative(name, usex64);
  end;

  for i := 0 to plugins.Count - 1 do begin
    name := plugins.Strings[i];           // assume built-in

    if not C_IsVariationNative(name, fin_usex64) then begin   // not native -> try install
      if C_SupportsDllPlugins(fin_usex64) then                // dll unsupported -> fail
        fails.Add(plugins.Strings[i])
      else if C_IsDllPluginBlacklisted(name, fin_usex64) then // dll supported and blacklisted -> fail
        fails.Add(plugins.Strings[i]);
      //else C_InstallVariation(plugins.Strings[i]);         // dll supported and not blacklisted -> install
       // ^^^ this is done on Apophysis startup now!
    end;
  end;
  {$endif}

  name := C_GetPathOf('chaotica.exe', fin_usex64);
  if (not FileExists(name)) then begin
    messagebox(0, PCHAR(TextByKey('main-status-nochaotica')),
      'Apophysis AV', MB_ICONHAND);
    fails.Free;  // AV: fixed possible memory leak
    Exit;
  end;

  if (fails.Count > 0) then begin
    messagebox(0, PCHAR(TextByKey('main-status-oldchaotica')),
      'Apophysis AV', MB_ICONHAND or MB_OK);
  end;

  // TODO: add directory cleaning // <-- AV: done
  fname := APPDATA + export_flame;  // AV: moved into app folder
  txt := TStringList.Create;

  txt.Text := flamexml;
  txt.SaveToFile(fname);

  txt.Free;
  fails.Free;

  ShellExecute(0, PChar('open'), pchar(name), PChar('"' + fname + '"'),
                  PChar(ExtractFilePath(name)), SW_SHOWNORMAL);
end;

end.
