library LanguageLoader;

uses
  SysUtils,
  Classes,
  IniFiles,
  windows;

{$E asi}

{$R *.res}

procedure NOP(Adress : integer; NumberOfBytes : byte);
var
EndAdress:integer;
OldProtect : cardinal;
begin
EndAdress := Adress + NumberOfBytes;
VirtualProtect(ptr(Adress),NumberOfBytes,PAGE_READWRITE,OldProtect);

  while Adress < EndAdress do
  begin
  PByte(Adress)^:= $90;
  Inc(Adress);
  end;
end;

TYPE
Menu_line_type = Record
// Takes 18 bytes
  StringType : Byte;
  GXT_key : String[7];  // 8th byte is zero
  Unknown1 : Byte;
  DestinationMenu : Word;
  X_position : Word;
  Y_position : Word;
  Unknown2 : Byte;
  Unknown3: Byte;
end;

type
Menu_table_type = Array[0..12] of Menu_line_type;
// Menu_tables_type = Array[0..43] of Menu_table_type;

const
Project_title : string = 'Language loader 1.0';
Footer_comunique : string =
'                                     http://fastman92.tk'+#13+
'                                     (C) fastman92 2011';
GTA_San_Andreas_v1_0_US_HOODLUM_No_CD_Fixed_EXE : byte = 1;
GTA_San_Andreas_v1_01_EURO_No_CD_Fixed_EXE = 4;

var
Number_of_languages : byte;
ParamsFile : TIniFIle;
CodePointer : Pointer;
i : integer;
OldProtect : Cardinal;
EXE_version : Byte;

GXT_filenames : Array[0..50] of String;
Is_system_metric_values : Array [0..50] of Byte;

GXT_language_key : String;
Menu_language_table : ^Menu_table_type;

begin
  case PByte($400088)^ of
    $CA: EXE_version := GTA_San_Andreas_v1_0_US_HOODLUM_No_CD_Fixed_EXE;
    $D0: EXE_version := GTA_San_Andreas_v1_01_EURO_No_CD_Fixed_EXE;
    else
        MessageBoxA(0,PChar('Sorry, but this version of gta_sa.exe is not supported'+#13+
                     'Supported EXE:'+#13+#13+
                     '    1.  GTA San Andreas v1.0 [US] HOODLUM No-CD  Fixed EXE:'+#13+
                     '         EXE size:  14 383 616 bytes'+#13+#13+#13+
                      Footer_comunique),
                     PChar(Project_title),MB_OK);
        ExitProcess(0);
    end;

  ParamsFile := TIniFile.Create(ExpandFileName('text\languages.ini'));

  Number_of_languages := StrToInt(ParamsFile.ReadString('MAIN','Number_of_languages',''));

      if Number_of_languages > 11 then begin
      MessageBoxA(0, PChar(
'Number of languages currently can`t exceed above 11 languages. Your text\languages.ini file is configured to use '+IntToStr(Number_of_languages)+' languages.'+#13+#13+#13+
Footer_comunique
), PChar(Project_title),0);
      ExitProcess(0);
      end;

  Number_of_languages := Number_of_languages - 1;

  CodePointer := VirtualAlloc(NIL, 500 , MEM_COMMIT , PAGE_READWRITE);

  // CText_load
  VirtualProtect(ptr($6A01D2),12,PAGE_EXECUTE_READWRITE,OldProtect);
  PByte($6A01D4)^:= Number_of_languages;                    // cmp     eax, Number_of_languages

  PByte($6A01D6)^:= $07;                                    // ja 006A01DE  american_language  ; default jump

  PByte($6A01D7)^:= $E9;                                    // jmp CodePointer
  PInteger($6A01D8)^:= Integer(CodePointer)-$6A01DC;
  NOP($006A01DC, 2);

  // CText_load on code pointer
  PByte(Integer(CodePointer))^:= $8B;               // mov eax
  PWord(Integer(CodePointer)+1)^:= $8504;              //        , [4*eax
  PInteger(Integer(CodePointer)+3)^:= Integer(@GXT_filenames); //          +GXT_filenames_offset]
  PByte(Integer(CodePointer)+7)^:= $50;   // push eax
  PByte(Integer(CodePointer)+8)^:= $E9;                                         // jmp
  PInteger(Integer(CodePointer)+9)^:= $6A01E3 - (Integer(CodePointer)+13);  //     6A01E3

  // CText_loadMission
  CodePointer :=  ptr(Integer(CodePointer) + 20);
  VirtualProtect(ptr($69FCF5),$C,PAGE_EXECUTE_READWRITE,OldProtect);

  PByte($69FCF7)^:= Number_of_languages;    // cmp     eax, Number_of_languages
  PByte($69FCF9)^:= $07;                                    // ja 0069FD01  american_language  ; default jump

  PByte($69FCFA)^:= $E9;                                    // jmp CodePointer
  PInteger($69FCFB)^:= Integer(CodePointer)-$69FCFF;
  NOP($69FCFF, 2);

  // CText_loadMission on code pointer
  PByte(Integer(CodePointer))^:= $8B;               // mov eax
  PWord(Integer(CodePointer)+1)^:= $8504;              //        , [4*eax
  PInteger(Integer(CodePointer)+3)^:= Integer(@GXT_filenames); //          +GXT_filenames_offset]
  PByte(Integer(CodePointer)+7)^:= $50;   // push eax
  
  PByte(Integer(CodePointer)+8)^:= $E9;                                         // jmp
  PInteger(Integer(CodePointer)+9)^:= $69FD06 - (Integer(CodePointer)+13);  //     6A01E3

  // _is_system_metric
  // $BA67CC - current language
  CodePointer :=  ptr(Integer(CodePointer) + 20);
  VirtualProtect(ptr($56D220),5,PAGE_EXECUTE_READWRITE,OldProtect);

  PByte($56D220)^:= $E9;                                    // jmp CodePointer
  PInteger($56D221)^:= Integer(CodePointer)-$56D225;
  NOP($56D225, 6);

  // _is_system_metric on code pointer
  PByte(Integer(CodePointer))^:= $0F;
  PWord(Integer(CodePointer)+1)^:= $05B6;      // movzx eax,byte ptr [Current_language]
  PInteger(Integer(CodePointer)+3)^:= $BA67CC;

  PWord(Integer(CodePointer)+7)^:= $B60F;
  PByte(Integer(CodePointer)+9)^:= $80;
  PInteger(Integer(CodePointer)+10)^:= Integer(@Is_system_metric_values[0]);  // movzx eax,byte ptr [eax+Is_system_metric_values_offset]

  PWord(Integer(CodePointer)+14)^:= $C084;  // test al, al
  PWord(Integer(CodePointer)+16)^:= $950F;
  PByte(Integer(CodePointer)+18)^:= $C0;

  PByte(Integer(CodePointer)+19)^:= $C3;

  // CMenuManager__ExecuteAction
  CodePointer :=  ptr(Integer(CodePointer) + 20);
  VirtualProtect(ptr($57CD7D),4,PAGE_EXECUTE_READWRITE,OldProtect);
  PInteger($57CD7D)^:= Integer(CodePointer)-$57CD81;

  // CMenuManager__ExecuteAction on code pointer
  PWord(Integer(CodePointer))^:= $FA83; // cmp edx,
  PByte(Integer(CodePointer)+2)^:= 60 + 1 + Number_of_languages;

  PWord(Integer(CodePointer)+3)^:= $870F;   // ja BadStringType_do_nothing
  PInteger(Integer(CodePointer)+5)^:= $57D447 - (Integer(CodePointer)+9);

  PWord(Integer(CodePointer)+9)^:= $EA83;   // sub edx, 60
  PByte(Integer(CodePointer)+11)^:= 60;

  PByte(Integer(CodePointer)+12)^:= $0F;
  PWord(Integer(CodePointer)+13)^:= $05B6;      // movzx eax,byte ptr [Current_language]
  PInteger(Integer(CodePointer)+15)^:= $BA67CC;

  PWord(Integer(CodePointer)+19)^:= $D038;

  PWord(Integer(CodePointer)+21)^:= $840F;                                         // je This language is already is loaded, don`t reload
  PInteger(Integer(CodePointer)+23)^:= $57D449 - (Integer(CodePointer)+27);

  PWord(Integer(CodePointer)+27)^:= $1588;
  PInteger(Integer(CodePointer)+29)^:= $00BA67CC;   // mov [00BA67CC],dl

  PByte(Integer(CodePointer)+33)^:= $E9;
  PInteger(Integer(CodePointer)+34)^:= $57D429 - (Integer(CodePointer)+38);

  // Create language menu
  Menu_language_table := ptr($008CF8CA);
  // StrPCopy(@Menu_language_table[0].GXT_key, 'FEL_POL');
  GXT_filenames[0] := ParamsFile.ReadString('1','GXT_file','american.gxt');
  GXT_language_key := ParamsFile.ReadString('1','GXT_language_key','');
  Is_system_metric_values[0] := ParamsFile.ReadInteger('1', 'Is_system_metric', 0);
  
  Menu_language_table[0].StringType := 66;
  StrPCopy(@Menu_language_table[0].GXT_key, GXT_language_key);
  Menu_language_table[0].Unknown1 := 11;
  Menu_language_table[0].DestinationMenu := 28;
  Menu_language_table[0].X_position := 320;
      if Number_of_languages < 7 then
      begin
      Menu_language_table[0].Y_position := 132   end
      else begin
      Menu_language_table[0].Y_position := 132 - (Number_of_languages-7)*16;
      end;

  Menu_language_table[0].Unknown2 := 3;
  Menu_language_table[0].Unknown3 := 0;

      for i := 1 to Number_of_languages do
      begin
      GXT_filenames[i] := ParamsFile.ReadString(IntToStr(i+1),'GXT_file','american.gxt');
      GXT_language_key := ParamsFile.ReadString(IntToStr(i+1),'GXT_language_key','');
      Is_system_metric_values[i] := ParamsFile.ReadInteger(IntToStr(i+1), 'Is_system_metric', 0);

      Menu_language_table[i].StringType := 66+i;
      StrPCopy(@Menu_language_table[i].GXT_key, GXT_language_key);
      Menu_language_table[i].Unknown1 := 11;
      Menu_language_table[i].DestinationMenu := 28;
      Menu_language_table[i].X_position := 0;
      Menu_language_table[i].Y_position := 0;
      Menu_language_table[i].Unknown2 := 3;
      Menu_language_table[i].Unknown3 := 0;
      end;

      Number_of_languages := Number_of_languages+1;

      Menu_language_table[Number_of_languages].StringType := 2;
      StrPCopy(@Menu_language_table[Number_of_languages].GXT_key, 'FEDS_TB');
      Menu_language_table[Number_of_languages].Unknown1 := 11;
      Menu_language_table[Number_of_languages].DestinationMenu := 4;
      Menu_language_table[Number_of_languages].X_position := 490;
      Menu_language_table[Number_of_languages].Y_position := 380;
      Menu_language_table[Number_of_languages].Unknown2 := 1;
      Menu_language_table[Number_of_languages].Unknown3 := 0;

      Inc(Number_of_languages);

      WHILE Number_of_languages < 11 DO
      BEGIN

      Menu_language_table[Number_of_languages].StringType := 0;
      StrPCopy(@Menu_language_table[Number_of_languages].GXT_key, '');
      Menu_language_table[Number_of_languages].Unknown1 := 0;
      Menu_language_table[Number_of_languages].DestinationMenu := 0;
      Menu_language_table[Number_of_languages].X_position := 0;
      Menu_language_table[Number_of_languages].Y_position := 0;
      Menu_language_table[Number_of_languages].Unknown2 := 0;
      Menu_language_table[Number_of_languages].Unknown3 := 0;
      Inc(Number_of_languages);
      END;
   // ExitProcess(0);

  ParamsFile.Destroy;

end.
