diff --git a/installer/Attach.cfg b/installer/Attach.cfg index 662b86f..6c57635 100644 --- a/installer/Attach.cfg +++ b/installer/Attach.cfg @@ -31,8 +31,8 @@ -M -$M16384,1048576 -K$00400000 --LE"c:\programme\borland\delphi7\Projects\Bpl" --LN"c:\programme\borland\delphi7\Projects\Bpl" +-LE"c:\program files\borland\delphi7\Projects\Bpl" +-LN"c:\program files\borland\delphi7\Projects\Bpl" -w-UNSAFE_TYPE -w-UNSAFE_CODE -w-UNSAFE_CAST diff --git a/installer/Attach.dpr b/installer/Attach.dpr index 1056bb3..214317d 100644 --- a/installer/Attach.dpr +++ b/installer/Attach.dpr @@ -14,24 +14,31 @@ begin WriteLn(''); WriteLn('// Looking up files...'); { Check files } - if FileExists(ExtractFilePath(ParamStr(0)) + 'files\hl2launch.exe') then - WriteLn('// Found files\hl2launch.exe') + if FileExists(ExtractFilePath(ParamStr(0)) + 'files\server.dll.source') then + WriteLn('// Found files\server.dll.source') else begin - WriteLn('// Error: Couldn''t find files\hl2launch.exe!'); + WriteLn('// Error: Couldn''t find files\server.dll.source!'); ReadLn; exit; end; - if FileExists(ExtractFilePath(ParamStr(0)) + 'files\server.dll') then - WriteLn('// Found files\server.dll') + if FileExists(ExtractFilePath(ParamStr(0)) + 'files\server_i486.so.source') then + WriteLn('// Found files\server_i486.so.source') else begin - WriteLn('// Error: Couldn''t find files\server.dll!'); + WriteLn('// Error: Couldn''t find files\server_i486.so.source!'); ReadLn; exit; end; - if FileExists(ExtractFilePath(ParamStr(0)) + 'files\server_i486.so') then - WriteLn('// Found files\server_i486.so') + if FileExists(ExtractFilePath(ParamStr(0)) + 'files\server.dll.orangebox') then + WriteLn('// Found files\server.dll.orangebox') else begin - WriteLn('// Error: Couldn''t find files\server_i486.so!'); + WriteLn('// Error: Couldn''t find files\server.dll.orangebox!'); + ReadLn; + exit; + end; + if FileExists(ExtractFilePath(ParamStr(0)) + 'files\server_i486.so.orangebox') then + WriteLn('// Found files\server_i486.so.orangebox') + else begin + WriteLn('// Error: Couldn''t find files\server_i486.so.orangebox!'); ReadLn; exit; end; @@ -53,9 +60,10 @@ begin { Compress files } WriteLn('// Compressing files...'); eFiles := TStringList.Create; - eFiles.Add(ExtractFilePath(ParamStr(0)) + 'files\hl2launch.exe'); - eFiles.Add(ExtractFilePath(ParamStr(0)) + 'files\server.dll'); - eFiles.Add(ExtractFilePath(ParamStr(0)) + 'files\server_i486.so'); + eFiles.Add(ExtractFilePath(ParamStr(0)) + 'files\server.dll.source'); + eFiles.Add(ExtractFilePath(ParamStr(0)) + 'files\server_i486.so.source'); + eFiles.Add(ExtractFilePath(ParamStr(0)) + 'files\server.dll.orangebox'); + eFiles.Add(ExtractFilePath(ParamStr(0)) + 'files\server_i486.so.orangebox'); eStream := TMemoryStream.Create; CompressFiles(eFiles, ExtractFilePath(ParamStr(0)) + 'temp.zip'); eStream.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'temp.zip'); @@ -63,6 +71,7 @@ begin AttachToFile(ExtractFilePath(ParamStr(0)) + 'MMS_Installer.exe', eStream, Version); DeleteFile(ExtractFilePath(ParamStr(0)) + 'temp.zip'); eStream.Free; + eFiles.Free; WriteLn('// Done.'); ReadLn; end. diff --git a/installer/Attach.exe b/installer/Attach.exe index d46d25b..d83a203 100644 Binary files a/installer/Attach.exe and b/installer/Attach.exe differ diff --git a/installer/HL2Launch.cfg b/installer/HL2Launch.cfg deleted file mode 100644 index 662b86f..0000000 --- a/installer/HL2Launch.cfg +++ /dev/null @@ -1,38 +0,0 @@ --$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:\programme\borland\delphi7\Projects\Bpl" --LN"c:\programme\borland\delphi7\Projects\Bpl" --w-UNSAFE_TYPE --w-UNSAFE_CODE --w-UNSAFE_CAST diff --git a/installer/HL2Launch.dpr b/installer/HL2Launch.dpr deleted file mode 100644 index 7eacc19..0000000 --- a/installer/HL2Launch.dpr +++ /dev/null @@ -1,189 +0,0 @@ -program HL2Launch; - -{$APPTYPE CONSOLE} - -uses - SysUtils, - ShellApi, - Windows, - Classes; - -procedure LaunchFile(eFile, eStartDir, eParams: String); -var eStartInfo: TStartupInfo; - eProcInfo: TProcessInformation; -begin - FillChar(eStartInfo, SizeOf(TStartupInfo), 0); - with eStartInfo do begin - cb := SizeOf(eStartInfo); - dwFlags := STARTF_USESHOWWINDOW; - end; - - if (CreateProcess(nil, PChar(eFile + #32 + eParams), nil, nil, False, NORMAL_PRIORITY_CLASS, nil, PChar(eStartDir), eStartInfo, eProcInfo)) then begin - try - WaitForSingleObject(eProcInfo.hProcess, INFINITE); - finally - CloseHandle(eProcInfo.hProcess); - CloseHandle(eProcInfo.hThread); - end; - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 2); - Write(' Done.' + #13#10); - end - else begin - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 12); - Write(' Couldn''t start hl2.exe!' + #13#10); - end; -end; - -function GetFileSize(eFile: String): Int64; -var eFindHandle: THandle; - eFindData: TWIN32FINDDATA; -begin - Result := 0; - if not FileExists(eFile) then exit; - eFindHandle := FindFirstFile(PChar(eFile), eFindData); - if eFindHandle = INVALID_HANDLE_VALUE then exit; - Result := (eFindData.nFileSizeHigh * (Int64(MAXDWORD) + 1)) + eFindData.nFileSizeLow; - FindClose(eFindHandle); -end; - -var eStream: TFileStream; - ePath, eParams: String; - eModDir: String; - eSearchRec: TSearchRec; - eStr: TStringList; - i: integer; - CheckSuccessful: Boolean; - StartTime: Cardinal; -begin - ePath := ExtractFilePath(ParamStr(0)); - for i := 1 to ParamCount do - eParams := eParams + #32 + ParamStr(i); - Delete(eParams, 1, 1); - if Pos('console', LowerCase(eParams)) = 0 then - eParams := eParams + ' -console'; - eStream := nil; - eModDir := ''; - - SetConsoleTitle('HL2 Launcher'); - Sleep(200); // wait a few ms until the launch program is closed - - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 2); - WriteLn(' _ _ _ ____ _ _ '); - WriteLn('| | | | | |___ \ | | __ _ _ _ _ __ ___| |__ ___ _ __ '); - WriteLn('| |_| | | __) | | | / _` | | | | ''_ \ / __| ''_ \ / _ \ ''__|'); - WriteLn('| _ | |___ / __/ | |__| (_| | |_| | | | | (__| | | | __/ | '); - WriteLn('|_| |_|_____|_____| |_____\__,_|\__,_|_| |_|\___|_| |_|\___|_| '); - WriteLn(' for listen servers using Metamod:Source'); - WriteLn(''); - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7); - { Check files } - WriteLn('Checking files...'); - if not FileExists(ePath + 'hl2.exe') then begin - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 12); - WriteLn('Error: hl2.exe is missing! Maybe wrong directory? If not, start your HL2 Mod again via Steam and try again.'); - ReadLn; - exit; - end; - if not FileExists(Copy(ePath, 1, Pos('\steamapps\', LowerCase(ePath))) + 'steam.exe') then begin - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 12); - WriteLn('Error: Cannot find steam.exe! Make sure this application is located in your listen server''s directory.'); - ReadLn; - exit; - end; - { Verify GameInfo.txt ... } - Write('Verifying GameInfo.txt...'); - if (FindFirst(ePath + '*.*', faDirectory, eSearchRec) = 0) then begin - repeat - if (FileExists(ePath + eSearchRec.Name + '\GameInfo.txt')) then begin - eModDir := eSearchRec.Name; - break; - end; - until (FindNext(eSearchRec) <> 0); - end; - FindClose(eSearchRec.FindHandle); - if eModDir = '' then begin - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 12); - WriteLn(''); - WriteLn('Error: Couldn''t find GameInfo.txt!'); - ReadLn; - exit; - end - else begin - if Pos('game', LowerCase(eParams)) = 0 then // a small test which isn't worth a notice - eParams := '-game ' + eSearchRec.Name + #32 + eParams; - - eStr := TStringList.Create; - eStr.LoadFromFile(ePath + eModDir + '\GameInfo.txt'); - if Pos('|gameinfo_path|addons/metamod/bin', LowerCase(eStr.Text)) = 0 then begin - CheckSuccessful := False; - for i := 0 to eStr.Count -1 do begin - if Pos('searchpaths', LowerCase(Trim(eStr[i]))) = 1 then begin - if i+3 >= eStr.Count then - break; - eStr.Insert(i+2, ' GameBin |gameinfo_path|addons/metamod/bin'); - CheckSuccessful := True; - break; - end; - end; - - if CheckSuccessful then begin - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 2); - SetFileAttributes(PChar(ePath + eModDir + '\GameInfo.txt'), 0); - eStr.SaveToFile(ePath + eModDir + '\GameInfo.txt'); - SetFileAttributes(PChar(ePath + eModDir + '\GameInfo.txt'), faReadOnly); - Write(' Registered MM:S sucessfully' + #13#10); - end - else begin - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4); - Write(' Unexpected EOF, your GameInfo.txt seems to be corrupt' + #13#10); - end; - end - else begin - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 2); - Write(' Done' + #13#10); - end; - eStr.Free; - end; - { ... and set it to write-protected } - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7); - Write('Setting GameInfo.txt to write-protected...'); - try - eStream := TFileStream.Create(ePath + eModDir + '\GameInfo.txt', fmShareDenyWrite); - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 2); - Write(' Done.' + #13#10); - except - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 14); - WriteLn(''); - WriteLn('Warning: Couldn''t set GameInfo.txt to write-protected!'); - eStream := nil; - end; - { Launch Steam if not opened } - ShellExecute(0, 'open', PChar(Copy(ePath, 1, Pos('\steamapps\', LowerCase(ePath))) + 'steam.exe'), nil, PChar(Copy(ePath, 1, Pos('\steamapps\', LowerCase(ePath)))), SW_SHOW); - { Launch game } - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7); - Write('Starting HL2...'); - StartTime := GetTickCount; - LaunchFile(ePath + 'hl2.exe', Copy(ePath, 1, Pos('Steam', ePath)+5), eParams); - if (GetTickCount - StartTime < 10000) then begin - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 14); - WriteLn('Important: If you experience any problems starting HL2 using this program, please start it once via Steam and try again.'); - ReadLn; - end; - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7); - { Free GameInfo.txt } - Write('Removing read-only again from GameInfo.txt...'); - if Assigned(eStream) then begin - eStream.Free; - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 2); - Write(' Done' + #13#10); - end - else begin - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4); - Write(' Skipped' + #13#10); - end; - { End message } - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7); - WriteLn(''); - WriteLn('Thanks for using Metamod:Source! Visit http://www.sourcemm.net/'); - Sleep(2500); -end. diff --git a/installer/HL2Launch.exe b/installer/HL2Launch.exe deleted file mode 100644 index 4a85d2d..0000000 Binary files a/installer/HL2Launch.exe and /dev/null differ diff --git a/installer/MMS_Installer.cfg b/installer/MMS_Installer.cfg index e9c1cc1..6c57635 100644 --- a/installer/MMS_Installer.cfg +++ b/installer/MMS_Installer.cfg @@ -31,8 +31,8 @@ -M -$M16384,1048576 -K$00400000 --LE"c:\program files (x86)\borland\delphi7\Projects\Bpl" --LN"c:\program files (x86)\borland\delphi7\Projects\Bpl" +-LE"c:\program files\borland\delphi7\Projects\Bpl" +-LN"c:\program files\borland\delphi7\Projects\Bpl" -w-UNSAFE_TYPE -w-UNSAFE_CODE -w-UNSAFE_CAST diff --git a/installer/MMS_Installer.exe b/installer/MMS_Installer.exe index 35e42b1..0b64b15 100644 Binary files a/installer/MMS_Installer.exe and b/installer/MMS_Installer.exe differ diff --git a/installer/UnitFunctions.pas b/installer/UnitFunctions.pas index a6f024d..d14c3c8 100644 --- a/installer/UnitFunctions.pas +++ b/installer/UnitFunctions.pas @@ -10,6 +10,8 @@ function GetAllFiles(Mask: String; Attr: Integer; Recursive: Boolean; ShowDirs: // ftp function GetAllDirs: TStringList; +function GetModName(const Path: String): String; + implementation uses UnitfrmMain; @@ -121,6 +123,20 @@ begin Result := eList; end; } +function GetModName(const Path: String): String; +var i: integer; +begin + Result := ''; + for i := Length(Path) -1 downto 1 do begin + if (Path[i] = '\') or (Path[i] = '/') then begin + Result := Copy(Path, i +1, Length(Path) -i); + break; + end; + end; + if (Copy(Result, Length(Result), 1) = '\') or (Copy(Result, Length(Result), 1) = '/') then + Result := Copy(Result, 1, Length(Result) -1); +end; + end. diff --git a/installer/UnitInstall.pas b/installer/UnitInstall.pas index 66e7b1e..c43835a 100644 --- a/installer/UnitInstall.pas +++ b/installer/UnitInstall.pas @@ -13,11 +13,11 @@ procedure AddSkipped; procedure AddNotFound; procedure DownloadFile(eFile: String; eDestination: String); -procedure BasicInstallation(ePath: String; SteamInstall, ListenInstall: Boolean; OS: TOS); -procedure InstallDedicated(eModPath: String; UseSteam: Boolean); -procedure InstallListen(ePath: String); -procedure InstallCustom(ePath: String; eOS: TOS); -procedure InstallFTP(OS: TOS); +procedure BasicInstallation(ePath: String; SteamInstall, ListenInstall: Boolean; OS: TOS; const Source: Boolean); +procedure InstallDedicated(eModPath: String; const UseSteam, Source: Boolean); +procedure InstallListen(ePath: String; const Source: Boolean); +procedure InstallCustom(ePath: String; eOS: TOS; const Source: Boolean); +procedure InstallFTP(OS: TOS; const Source: Boolean; const ModDir: String); var StartTime: TDateTime; SteamPath: String; @@ -32,7 +32,7 @@ uses UnitfrmMain, UnitfrmProxy, UnitFunctions, UnitPackSystem; function InstallTime: String; begin - Result := FormatDateTime('HH:MM:SS', Now - StartTime); + Result := Copy(FormatDateTime('HH:MM:SS', Now - StartTime), 4, 5); end; procedure AddStatus(Text: String; Color: TColor; ShowTime: Boolean = True); @@ -218,18 +218,16 @@ end; { Basic Installation } -procedure BasicInstallation(ePath: String; SteamInstall, ListenInstall: Boolean; OS: TOS); +procedure BasicInstallation(ePath: String; SteamInstall, ListenInstall: Boolean; OS: TOS; const Source: Boolean); var eStr: TStringList; - i: integer; CopyConfig: Boolean; - eFound: Boolean; begin frmMain.ggeAll.MaxValue := 8; frmMain.ggeAll.Progress := 0; frmMain.ggeItem.MaxValue := 1; frmMain.ggeItem.Progress := 0; - if (GetProcessID('Steam.exe') <> -1) and (SteamInstall) then begin + {if (GetProcessID('Steam.exe') <> -1) and (SteamInstall) then begin if MessageBox(frmMain.Handle, 'Steam is still running. It is necersarry to shut it down before you install Metamod:Source. Shut it down now?', PChar(frmMain.Caption), MB_ICONQUESTION + MB_YESNO) = mrYes then begin AddStatus('Shutting down Steam...', clBlack, False); if GetProcessID('Steam.exe') = -1 then @@ -247,13 +245,13 @@ begin Application.Terminate; exit; end; - end; + end;} frmMain.ggeAll.Progress := 1; frmMain.ggeItem.Progress := 1; { Unpack } frmMain.ggeItem.Progress := 0; AddStatus('Unpacking files...', clBlack); - if not Unpack() then begin + if not Unpack(Source) then begin AddStatus('No files attached!', clRed); Screen.Cursor := crDefault; exit; @@ -265,8 +263,8 @@ begin CopyConfig := True; AddStatus('Creating directories...', clBlack); if DirectoryExists(ePath + 'addons\metamod\bin') then begin - case MessageBox(frmMain.Handle, 'A Metamod:Source installation was already detected. If you choose to reinstall, your configuration files will be erased. Click Yes to continue, No to Upgrade, or Cancel to abort the install.', PChar(frmMain.Caption), MB_ICONQUESTION + MB_YESNOCANCEL) of - mrNo: CopyConfig := False; + case MessageBox(frmMain.Handle, 'A Metamod:Source installation was already detected. If you choose to reinstall, your configuration files will be erased. Click Yes to upgrade, No to reinstall, or Cancel to abort the installation.', PChar(frmMain.Caption), MB_ICONQUESTION + MB_YESNOCANCEL) of + mrYes: CopyConfig := False; mrCancel: begin Application.Terminate; exit; @@ -282,7 +280,7 @@ begin frmMain.ggeItem.Progress := 1; frmMain.ggeAll.Progress := 3; - { gameinfo.txt } + { gameinfo.txt for check / create VDF file } if not FileExists(ePath + 'gameinfo.txt') then begin if MessageBox(frmMain.Handle, 'The file "gameinfo.txt" couldn''t be found. Continue installation?', PChar(frmMain.Caption), MB_ICONQUESTION + MB_YESNO) = mrNo then begin AddStatus('Installation canceled by user!', clRed, False); @@ -304,33 +302,32 @@ begin AddSkipped; frmMain.ggeItem.Progress := 1; frmMain.ggeAll.Progress := 4; - { Gameinfo.txt } + { CDF Plugin } frmMain.ggeItem.Progress := 0; - eFound := False; - AddStatus('Editing gameinfo.txt...', clBlack); - eStr.LoadFromFile(ePath + 'gameinfo.txt'); - for i := 0 to eStr.Count -1 do begin - if Trim(LowerCase(eStr[i])) = 'gamebin |gameinfo_path|addons/metamod/bin' then begin - eFound := True; - break; + AddStatus('Creating VDF Plugin...', clBlack); + if (FileExists(ePath + 'addons\metamod.vdf')) then begin + eStr.LoadFromFile(ePath + 'addons\metamod.vdf'); + if (Pos('server.dll', eStr.Text) <> 0) then + AddSkipped + else begin + eStr.Add(''); + eStr.Add('"Plugin"'); + eStr.Add('{'); + eStr.Add(' "file" "..\' + GetModName(ePath) + '\addons\metamod\bin\server.dll"'); + eStr.Add('}'); + eStr.SaveToFile(ePath + 'addons\metamod.vdf'); + AddDone; end; - end; - - if not eFound then begin - for i := 0 to eStr.Count -1 do begin - if Trim(eStr[i]) = 'SearchPaths' then begin - eStr.Insert(i +2, ' GameBin |gameinfo_path|addons/metamod/bin'); - AddDone; - break; - end; - end; - SetFileAttributes(PChar(ePath + 'gameinfo.txt'), 0); - eStr.SaveToFile(ePath + 'gameinfo.txt'); - SetFileAttributes(PChar(ePath + 'gameinfo.txt'), faReadOnly); // important for listen servers - AddDone; end - else - AddSkipped; + else begin + eStr.Add(''); + eStr.Add('"Plugin"'); + eStr.Add('{'); + eStr.Add(' "file" "..\' + GetModName(ePath) + '\addons\metamod\bin\server.dll"'); + eStr.Add('}'); + eStr.SaveToFile(ePath + 'addons\metamod.vdf'); + AddDone; + end; eStr.Free; frmMain.ggeItem.Progress := 1; frmMain.ggeAll.Progress := 5; @@ -342,16 +339,9 @@ begin AddDone; frmMain.ggeItem.Progress := 1; frmMain.ggeAll.Progress := 6; - if ListenInstall then begin - ePath := ExtractFilePath(Copy(ePath, 1, Length(ePath)-1)); - AddStatus('Copying hl2launch.exe...', clBlack); - CopyFile(PChar(ExtractFilePath(ParamStr(0)) + 'hl2launch.exe'), PChar(ePath + 'hl2launch.exe'), False); - AddDone; - end; { Remove files } frmMain.ggeItem.Progress := 0; AddStatus('Removing temporary files...', clBlack); - DeleteFile(PChar(ExtractFilePath(ParamStr(0)) + 'hl2launch.exe')); DeleteFile(PChar(ExtractFilePath(ParamStr(0)) + 'server.dll')); DeleteFile(PChar(ExtractFilePath(ParamStr(0)) + 'server_i486.so')); AddDone; @@ -363,44 +353,41 @@ begin frmMain.cmdNext.Enabled := True; frmMain.cmdCancel.Hide; Screen.Cursor := crDefault; - - if ListenInstall then - MessageBox(frmMain.Handle, PChar('hl2launch.exe has been copied to ' + ePath + '. You can use it if you want to start your Source game with Metamod:Source enabled.'), PChar(Application.Title), MB_ICONINFORMATION); end; { Dedicated Server } -procedure InstallDedicated(eModPath: String; UseSteam: Boolean); +procedure InstallDedicated(eModPath: String; const UseSteam, Source: Boolean); begin StartTime := Now; Screen.Cursor := crHourGlass; AddStatus('Starting Metamod:Source installation on dedicated server...', clBlack, False); - BasicInstallation(eModPath, UseSteam, False, osWindows); + BasicInstallation(eModPath, UseSteam, False, osWindows, Source); end; { Listen Server } -procedure InstallListen(ePath: String); +procedure InstallListen(ePath: String; const Source: Boolean); begin StartTime := Now; Screen.Cursor := crHourGlass; AddStatus('Starting Metamod:Source installation on the listen server...', clBlack); - BasicInstallation(ePath, True, True, osWindows); + BasicInstallation(ePath, True, True, osWindows, Source); end; { Custom mod } -procedure InstallCustom(ePath: String; eOS: TOS); +procedure InstallCustom(ePath: String; eOS: TOS; const Source: Boolean); begin StartTime := Now; Screen.Cursor := crHourGlass; AddStatus('Starting Metamod:Source installation...', clBlack); - BasicInstallation(ePath, False, False, eOS); + BasicInstallation(ePath, False, False, eOS, Source); end; { FTP } -procedure InstallFTP(OS: TOS); +procedure InstallFTP(OS: TOS; const Source: Boolean; const ModDir: String); function DoReconnect: Boolean; begin Result := False; @@ -414,11 +401,13 @@ begin end; end; + label CreateAgain; label UploadAgain; + var eStr: TStringList; - i: integer; - CopyConfig, eFound: Boolean; + CopyConfig, CommentFound: Boolean; + i: Integer; begin frmMain.cmdCancel.Show; frmMain.cmdNext.Hide; @@ -426,72 +415,27 @@ begin frmMain.ggeAll.MaxValue := 6; frmMain.ggeAll.Progress := 0; - frmMain.ggeItem.MaxValue := 1; + frmMain.ggeItem.MaxValue := 3; frmMain.ggeItem.Progress := 0; + StartTime := Now; { Unpack } - frmMain.ggeItem.Progress := 0; AddStatus('Unpacking files...', clBlack); - if not Unpack() then begin + if not Unpack(Source) then begin AddStatus('No files attached!', clRed); Screen.Cursor := crDefault; exit; end; AddDone; - frmMain.ggeAll.Progress := 2; + frmMain.ggeAll.Progress := 1; frmMain.ggeItem.Progress := 1; - { Check for installation } - AddStatus('Editing gameinfo.txt...', clBlack); - eStr := TStringList.Create; - DownloadFile('gameinfo.txt', ExtractFilePath(ParamStr(0)) + 'gameinfo.txt'); - eStr.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'gameinfo.txt'); - - CopyConfig := True; - eFound := False; - for i := 0 to eStr.Count -1 do begin - if Trim(LowerCase(eStr[i])) = 'gamebin |gameinfo_path|addons/metamod/bin' then begin - eFound := True; - case MessageBox(frmMain.Handle, 'A Metamod:Source installation was already detected. If you choose to reinstall, your configuration files will be erased. Click Yes to continue, No to Upgrade, or Cancel to abort the install.', PChar(frmMain.Caption), MB_ICONQUESTION + MB_YESNOCANCEL) of - mrNo: CopyConfig := False; - mrCancel: begin - Application.Terminate; - eStr.Free; - exit; - end; - end; - break; - end; - end; - - if not eFound then begin - for i := 0 to eStr.Count -1 do begin - if Trim(eStr[i]) = 'SearchPaths' then begin - eStr.Insert(i +2, ' GameBin |gameinfo_path|addons/metamod/bin'); - AddDone; - break; - end; - end; - eStr.SaveToFile(ExtractFilePath(ParamStr(0)) + 'gameinfo.txt'); - UploadFile(ExtractFilePath(ParamStr(0)) + 'gameinfo.txt', 'gameinfo.txt'); - try - AddStatus('Trying to set gameinfo.txt to read-only...', clBlack); - frmMain.IdFTP.Site('CHMOD 744 gameinfo.txt'); - AddDone; - except - AddStatus('Warning: CHMOD not supported.', clMaroon); - end; - DeleteFile(PChar(ExtractFilePath(ParamStr(0)) + 'gameinfo.txt')); - end - else - AddSkipped; - frmMain.ggeAll.Progress := 3; - frmMain.ggeItem.Progress := 1; - + Sleep(250); { Create directories } + frmMain.ggeAll.Progress := 2; frmMain.ggeItem.Progress := 0; - frmMain.ggeItem.MaxValue := 3; + AddStatus('Creating directories...', clBlack); - if not eFound then begin + try FTPMakeDir('addons'); frmMain.IdFTP.ChangeDir('addons'); frmMain.ggeItem.Progress := 1; @@ -501,24 +445,108 @@ begin FTPMakeDir('bin'); frmMain.ggeItem.Progress := 3; AddDone; - end - else + except AddSkipped; - frmMain.ggeAll.Progress := 4; + end; + { Check VDF Plugin } + CopyConfig := True; + + frmMain.ggeAll.Progress := 3; + frmMain.ggeItem.Progress := 0; + + AddStatus('Creating VDF Plugin...', clBlack); + eStr := TStringList.Create; + try + frmMain.IdFTP.ChangeDirUp; + + frmMain.ggeItem.Progress := 1; + DownloadFile('metamod.vdf', ExtractFilePath(ParamStr(0)) + 'metamod.vdf'); + eStr.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'metamod.vdf'); + frmMain.ggeItem.Progress := 2; + if (((Pos('server.dll', eStr.Text) <> 0) and (OS = osWindows)) or ((Pos('server_i486.so', eStr.Text) <> 0) and (OS = osLinux))) then begin + case MessageBox(frmMain.Handle, 'A Metamod:Source installation was already detected. If you choose to reinstall, your configuration files will be erased. Click Yes to upgrade, No to reinstall, or Cancel to abort the installation.', PChar(frmMain.Caption), MB_ICONQUESTION + MB_YESNOCANCEL) of + mrYes: CopyConfig := False; + mrCancel: begin + Application.Terminate; + eStr.Free; + exit; + end; + end; + end; + except + // bacon + end; + { Create and Upload plugin here } + frmMain.ggeItem.Progress := 2; + eStr.Clear; + eStr.Add('"Plugin"'); + eStr.Add('{'); + if (OS = osWindows) then + eStr.Add(' "file" "..\' + ModDir + '\addons\metamod\bin\server.dll"') + else + eStr.Add(' "file" "../' + ModDir + '/addons/metamod/bin/server_i486.so"'); + eStr.Add('}'); + eStr.SaveToFile(ExtractFilePath(ParamStr(0)) + 'metamod.vdf'); + UploadFile(ExtractFilePath(ParamStr(0)) + 'metamod.vdf', 'metamod.vdf'); frmMain.ggeItem.Progress := 3; { Upload metaplugins.ini } frmMain.ggeAll.Progress := 4; frmMain.ggeItem.MaxValue := 1; frmMain.ggeItem.Progress := 0; AddStatus('Uploading metaplugins.ini...', clBlack); - if CopyConfig then begin + frmMain.IdFTP.ChangeDir('metamod'); + if (CopyConfig) then begin eStr.Clear; + // see http://svn.alliedmods.net/viewvc.cgi/metamodsource/orangebox/addons/metamod/metaplugins.ini?revision=1099&root=Packages + eStr.Add(';List one plugin per line. Each line should contain the path to the plugin''s binary.'); + eStr.Add(';Any line starting with a '';'' character is a comment line, and is ignored.'); + eStr.Add(';'); + eStr.Add(';You do not need to include the _i486.so or .dll part of the file name. Example:'); + eStr.Add('; addons/sourcemod/bin/sourcemod_mm'); + eStr.Add(';You may also put an alias in front of the file, for example:'); + eStr.Add('; sm addons/sourcemod/bin/sourcemod_mm'); + eStr.Add(';Will allow you to use "meta load sm" from the console.'); + eStr.Add(';'); + eStr.Add(';********* LIST PLUGINS BELOW ***********'); + // end eStr.SaveToFile(ExtractFilePath(ParamStr(0)) + 'metaplugins.ini'); UploadFile(ExtractFilePath(ParamStr(0)) + 'metaplugins.ini', 'metaplugins.ini'); - DeleteFile(PChar(ExtractFilePath(ParamStr(0)) + 'metaplugins.ini')); end - else - AddSkipped; + else if (frmMain.IdFTP.Size('metaplugins.ini') <> -1) then begin + DownloadFile('metaplugins.ini', ExtractFilePath(ParamStr(0)) + 'metaplugins.ini'); + eStr.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'metaplugins.ini'); + CommentFound := False; + for i := 0 to eStr.Count -1 do begin + if (Pos(';', eStr[i]) = 1) then begin + CommentFound := True; + break; + end; + end; + + if (CommentFound) then + AddSkipped + else begin + // delete empty lines first + for i := eStr.Count -1 downto 0 do begin + if (Trim(eStr[i]) = '') then + eStr.Delete(i); + end; + // see http://svn.alliedmods.net/viewvc.cgi/metamodsource/orangebox/addons/metamod/metaplugins.ini?revision=1099&root=Packages + eStr.Insert(0, ';List one plugin per line. Each line should contain the path to the plugin''s binary.'); + eStr.Insert(1, ';Any line starting with a '';'' character is a comment line, and is ignored.'); + eStr.Insert(2, ';'); + eStr.Insert(3, ';You do not need to include the _i486.so or .dll part of the file name. Example:'); + eStr.Insert(4, '; addons/sourcemod/bin/sourcemod_mm'); + eStr.Insert(5, ';You may also put an alias in front of the file, for example:'); + eStr.Insert(6, '; sm addons/sourcemod/bin/sourcemod_mm'); + eStr.Insert(7, ';Will allow you to use "meta load sm" from the console.'); + eStr.Insert(8, ';'); + eStr.Insert(9, ';********* LIST PLUGINS BELOW ***********'); + // end + eStr.SaveToFile(ExtractFilePath(ParamStr(0)) + 'metaplugins.ini'); + UploadFile(ExtractFilePath(ParamStr(0)) + 'metaplugins.ini', 'metaplugins.ini'); + end; + end; frmMain.ggeAll.Progress := 5; frmMain.ggeItem.Progress := 1; { Upload server.dll / server_i486.so } @@ -537,9 +565,10 @@ begin end; { Remove created files } AddStatus('Removing temporary files...', clBlack); - DeleteFile(PChar(ExtractFilePath(ParamStr(0)) + 'hl2launch.exe')); DeleteFile(PChar(ExtractFilePath(ParamStr(0)) + 'server.dll')); DeleteFile(PChar(ExtractFilePath(ParamStr(0)) + 'server_i486.so')); + DeleteFile(PChar(ExtractFilePath(ParamStr(0)) + 'metamod.vdf')); + DeleteFile(PChar(ExtractFilePath(ParamStr(0)) + 'metaplugins.ini')); AddDone; { End } frmMain.IdFTP.Disconnect; diff --git a/installer/UnitPackSystem.pas b/installer/UnitPackSystem.pas index 077808a..21e0b2c 100644 --- a/installer/UnitPackSystem.pas +++ b/installer/UnitPackSystem.pas @@ -4,11 +4,11 @@ interface uses SysUtils, Classes, Zlib; -procedure CompressFiles(Files : TStrings; const Filename : String); -function DecompressStream(Stream : TMemoryStream; DestDirectory : String): Boolean; +procedure CompressFiles(Files: TStrings; const Filename: String); +function DecompressStream(Stream: TMemoryStream; DestDirectory: String; const Source: Boolean): Boolean; function AttachToFile(const AFileName: string; MemoryStream: TMemoryStream; Version: String): Boolean; function LoadFromFile(const AFileName: string; MemoryStream: TMemoryStream): Boolean; -function Unpack: Boolean; +function Unpack(const Source: Boolean): Boolean; function GetVersion: String; implementation @@ -54,6 +54,8 @@ begin { append the compressed file to the destination file } tmpFile := TFileStream.Create('tmp',fmOpenRead); try + l := tmpFile.Size; + outfile.WriteBuffer(l, SizeOf(l)); outfile.CopyFrom(tmpFile,0); finally tmpFile.Free; @@ -68,12 +70,13 @@ begin DeleteFile('tmp'); end; end; -function DecompressStream(Stream : TMemoryStream; DestDirectory : String): Boolean; + +function DecompressStream(Stream : TMemoryStream; DestDirectory : String; const Source: Boolean): Boolean; var dest,s : String; decompr : TDecompressionStream; outfile : TFilestream; - i,l,c : Integer; + i,l,lr,c : Integer; begin // IncludeTrailingPathDelimiter (D6/D7 only) dest := IncludeTrailingPathDelimiter(DestDirectory); @@ -82,24 +85,30 @@ begin try { number of files } Stream.Read(c,SizeOf(c)); - for i := 1 to c do - begin + for i := 1 to c do begin { read filename } Stream.Read(l,SizeOf(l)); SetLength(s,l); Stream.Read(s[1],l); - { read filesize } Stream.Read(l,SizeOf(l)); - { decompress the files and store it } - s := dest+s; //include the path - outfile := TFileStream.Create(s,fmCreate); - decompr := TDecompressionStream.Create(Stream); - try - outfile.CopyFrom(decompr,l); - finally - outfile.Free; - decompr.Free; - end; + Stream.Read(lr,SizeOf(lr)); + { check if this is the right file } + if ((Pos('.source', s) <> 0) and (Source)) or ((Pos('.orangebox', s) <> 0) and (not Source)) then begin + { remove extension and read filesize } + s := ChangeFileExt(s, ''); + { decompress the files and store it } + s := dest+s; //include the path + outfile := TFileStream.Create(s,fmCreate); + decompr := TDecompressionStream.Create(Stream); + try + outfile.CopyFrom(decompr,l); + finally + outfile.Free; + decompr.Free; + end; + end + else + Stream.Position := Stream.Position + lr; end; finally Result := True; @@ -114,6 +123,7 @@ begin Result := False; if not FileExists(AFileName) then Exit; + try aStream := TFileStream.Create(AFileName, fmOpenWrite or fmShareDenyWrite); MemoryStream.Seek(0, soFromBeginning); @@ -177,14 +187,14 @@ end; { Unpack function } -function Unpack: Boolean; +function Unpack(const Source: Boolean): Boolean; var eStream: TMemoryStream; begin eStream := TMemoryStream.Create; try // Get ZIP LoadFromFile(ParamStr(0), eStream); - DecompressStream(eStream, ExtractFilePath(ParamStr(0))); // Unpack files + DecompressStream(eStream, ExtractFilePath(ParamStr(0)), Source); // Unpack files Result := True; except diff --git a/installer/UnitSelectModPath.dfm b/installer/UnitSelectModPath.dfm index 436262c..ef9ae6a 100644 Binary files a/installer/UnitSelectModPath.dfm and b/installer/UnitSelectModPath.dfm differ diff --git a/installer/UnitSelectModPath.pas b/installer/UnitSelectModPath.pas index 368d478..99251cd 100644 --- a/installer/UnitSelectModPath.pas +++ b/installer/UnitSelectModPath.pas @@ -5,7 +5,7 @@ interface uses SysUtils, Windows, Messages, Classes, Graphics, Controls, StdCtrls, ExtCtrls, Forms, FileCtrl, ComCtrls, ShellCtrls, - TFlatComboBoxUnit, TFlatButtonUnit; + TFlatComboBoxUnit, TFlatButtonUnit, TFlatCheckBoxUnit; type TfrmSelectModPath = class(TForm) @@ -14,6 +14,8 @@ type trvDirectory: TShellTreeView; cmdOK: TFlatButton; cmdCancel: TFlatButton; + chkUsesOrangebox: TFlatCheckBox; + procedure trvDirectoryClick(Sender: TObject); end; var @@ -23,4 +25,11 @@ implementation {$R *.DFM} +procedure TfrmSelectModPath.trvDirectoryClick(Sender: TObject); +begin + // !! OrangeBox Check !! + if (trvDirectory.Selected <> nil) then + chkUsesOrangebox.Checked := (chkUsesOrangebox.Checked) or (trvDirectory.Selected.Text = 'tf'); +end; + end. diff --git a/installer/UnitfrmMain.dfm b/installer/UnitfrmMain.dfm index 14b183c..4e2a9f9 100644 --- a/installer/UnitfrmMain.dfm +++ b/installer/UnitfrmMain.dfm @@ -10407,7 +10407,7 @@ object frmMain: TfrmMain Lines.Strings = ( 'The zlib/libpng License' '' - 'Copyright (c) 2006, AlliedModders' + 'Copyright (c) 2006-2008, AlliedModders' '' 'This software is provided '#39'as-is'#39', without any express or implie' + @@ -10838,12 +10838,12 @@ object frmMain: TfrmMain Height = 13 Caption = '2. Connect to server and select the mod directory:' end - object lblStep4: TLabel + object lblStep5: TLabel Left = 44 - Top = 296 + Top = 338 Width = 64 Height = 13 - Caption = '4. Click Next.' + Caption = '5. Click Next.' end object lblStep3: TLabel Left = 44 @@ -10852,6 +10852,13 @@ object frmMain: TfrmMain Height = 13 Caption = '3. Select the operating system of your server:' end + object lblStep4: TLabel + Left = 44 + Top = 296 + Width = 125 + Height = 13 + Caption = '4. Select the engine type:' + end object pnlHeader3: TPanel Left = 0 Top = 0 @@ -11130,7 +11137,7 @@ object frmMain: TfrmMain TabStop = True end object optLinux: TFlatRadioButton - Left = 173 + Left = 183 Top = 3 Width = 82 Height = 14 @@ -11138,7 +11145,7 @@ object frmMain: TfrmMain TabOrder = 1 end object FlatRadioButton1: TFlatRadioButton - Left = 353 + Left = 319 Top = 3 Width = 82 Height = 14 @@ -11147,6 +11154,40 @@ object frmMain: TfrmMain TabOrder = 2 end end + object pnlEngineType: TPanel + Left = 44 + Top = 312 + Width = 441 + Height = 21 + BevelOuter = bvLowered + TabOrder = 5 + object optAutoDetect: TFlatRadioButton + Left = 1 + Top = 3 + Width = 154 + Height = 14 + Caption = 'Auto-Detect (recommended)' + Checked = True + TabOrder = 0 + TabStop = True + end + object optSource: TFlatRadioButton + Left = 183 + Top = 3 + Width = 96 + Height = 15 + Caption = 'Source'#8482' Engine' + TabOrder = 1 + end + object optOrangeBox: TFlatRadioButton + Left = 319 + Top = 3 + Width = 118 + Height = 15 + Caption = 'OrangeBox'#8482' Engine' + TabOrder = 2 + end + end end object jspInstallProgress: TJvStandardPage Left = 0 @@ -11404,8 +11445,8 @@ object frmMain: TfrmMain end end object ilImages: TImageList - Left = 154 - Top = 324 + Left = 334 + Top = 8 Bitmap = { 494C010102000400040010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 0000000000003600000028000000400000001000000001002000000000000010 @@ -11555,23 +11596,23 @@ object frmMain: TfrmMain OnWork = IdFTPWork ProxySettings.ProxyType = fpcmNone ProxySettings.Port = 0 - Left = 274 - Top = 324 + Left = 454 + Top = 8 end object IdAntiFreeze: TIdAntiFreeze IdleTimeOut = 150 - Left = 244 - Top = 324 + Left = 424 + Top = 8 end object tmrSpeed: TTimer Enabled = False OnTimer = tmrSpeedTimer - Left = 214 - Top = 324 + Left = 394 + Top = 8 end object IdLogFile: TIdLogFile LogTime = False - Left = 184 - Top = 324 + Left = 364 + Top = 8 end end diff --git a/installer/UnitfrmMain.pas b/installer/UnitfrmMain.pas index aee1c93..2f810dc 100644 --- a/installer/UnitfrmMain.pas +++ b/installer/UnitfrmMain.pas @@ -63,7 +63,7 @@ type cmdConnect: TFlatButton; pnlDirectory: TPanel; trvDirectories: TTreeView; - lblStep4: TLabel; + lblStep5: TLabel; jspInstallProgress: TJvStandardPage; pnlHeader5: TPanel; imgIcon4: TImage; @@ -101,6 +101,11 @@ type shpMods: TShape; trvMods: TTreeView; FlatRadioButton1: TFlatRadioButton; + pnlEngineType: TPanel; + optAutoDetect: TFlatRadioButton; + optSource: TFlatRadioButton; + optOrangeBox: TFlatRadioButton; + lblStep4: TLabel; procedure jvwStepsCancelButtonClick(Sender: TObject); procedure cmdCancelClick(Sender: TObject); procedure cmdNextClick(Sender: TObject); @@ -136,6 +141,9 @@ var var VERSION: String = ''; +const NormalHeight = 382; + FTPHeight = 422; + implementation uses UnitFunctions, UnitfrmProxy, UnitInstall, UnitSelectModPath; @@ -159,10 +167,11 @@ var ePath: String; CurNode: TTreeNode; eOS: TOS; i: integer; + Source: Boolean; begin { FTP } - if jplWizard.ActivePage = jspFTP then begin - if not IdFTP.Connected then + if (jplWizard.ActivePage = jspFTP) then begin + if (not IdFTP.Connected) then IdFTP.Connect; eStr := TStringList.Create; ePath := '/'; @@ -175,14 +184,22 @@ begin end; IdFTP.ChangeDir(ePath); IdFTP.List(eStr, '', False); - if eStr.IndexOf('gameinfo.txt') = -1 then begin + eStr.CaseSensitive := False; + // check if gameinfo.txt is in the directory -> valid installation + if (eStr.IndexOf('gameinfo.txt') = -1) then begin MessageBox(Handle, 'Invalid directory. Please select your mod directory and try again.', PChar(Application.Title), MB_ICONWARNING); eStr.Free; exit; end else eStr.Free; - + // check for orangebox directory (!! OrangeBox Check !!) + if (optAutoDetect.Checked) then + Source := not ((AnsiSameText(trvDirectories.Selected.Text, 'tf')) or (Pos('orangebox', LowerCase(ePath)) <> 0)) + else if (optSource.Checked) then + Source := True + else + Source := False; // design stuff trvDirectories.Enabled := False; cmdConnect.Enabled := False; @@ -190,81 +207,131 @@ begin optLinux.Enabled := False; Screen.Cursor := crHourGlass; - if optWindows.Checked then - eOS := osWindows - else + if (optWindows.Checked) then begin + eOS := osWindows; + if (Source) then + rtfDetails.Lines.Text := '* Installing Source binaries for Windows' + #13#10 + #13#10 + else + rtfDetails.Lines.Text := '* Installing OrangeBox binaries for Windows' + #13#10 + #13#10 + end + else begin eOS := osLinux; - + if (Source) then + rtfDetails.Lines.Text := '* Installing Source binaries for Linux' + #13#10 + #13#10 + else + rtfDetails.Lines.Text := '* Installing OrangeBox binaries for Linux' + #13#10 + #13#10 + end; jspInstallProgress.Show; // installation Screen.Cursor := crAppStart; - InstallFTP(eOS); + InstallFTP(eOS, Source, trvDirectories.Selected.Text); end - else if jplWizard.ActivePage = jspInstallProgress then + else if (jplWizard.ActivePage = jspInstallProgress) then Close - else if jplWizard.ActivePage = jspSelectMod then begin + else if (jplWizard.ActivePage = jspSelectMod) then begin { Dedicated Server } - if (frbDedicatedServer.Checked) or (frbStandaloneServer.Checked) then begin + if frbDedicatedServer.Checked then begin + Source := True; + ePath := trvMods.Selected.Text; + if (ePath = 'Counter-Strike:Source') then + ePath := trvMods.Selected.Parent.Text + '\source dedicated server\cstrike' + else if (ePath = 'Day of Defeat:Source') then + ePath := trvMods.Selected.Parent.Text + '\source dedicated server\dod' + else if (ePath = 'Half-Life 2 Deathmatch') then + ePath := trvMods.Selected.Parent.Text + '\source dedicated server\hl2mp' + else begin + { get games } + if (ePath = 'Team Fortress 2') then + ePath := trvMods.Selected.Parent.Text + '\source 2007 dedicated server\tf'; + { ask user, just in case } + case MessageBox(Handle, 'It looks like your server is using the OrangeBox engine. Would you like to install the appropriate binaries for it?', PChar(Application.Title), MB_ICONQUESTION + MB_YESNOCANCEL) of + mrYes: Source := False; + mrNo: Source := True; + mrCancel: exit; + end; + end; + SteamPath := IncludeTrailingPathDelimiter(SteamPath) + 'steamapps\'; + // install it + if (DirectoryExists(SteamPath + ePath)) then begin + jspInstallProgress.Show; + InstallDedicated(IncludeTrailingPathDelimiter(SteamPath + ePath), True, Source); + end + else begin + MessageBox(Handle, 'Error: The directory of the mod you selected doesn''t exist any more. Run Dedicated Server with the chosen mod and try again.', PChar(Application.Title), MB_ICONERROR); + jspSelectMod.Show; + exit; + end; + end; + { Standalone Server } + if (frbStandaloneServer.Checked) then begin + Source := True; ePath := trvMods.Selected.Text; if ePath = 'Counter-Strike:Source' then ePath := 'cstrike' else if ePath = 'Day of Defeat:Source' then ePath := 'dod' - else - ePath := 'hl2mp'; - ePath := 'SteamApps\' + trvMods.Selected.Parent.Text + '\source dedicated server\' + ePath; - // install it - if frbDedicatedServer.Checked then begin - if DirectoryExists(SteamPath + ePath) then begin - jspInstallProgress.Show; - InstallDedicated(IncludeTrailingPathDelimiter(SteamPath + ePath), True); - end - else begin - MessageBox(Handle, 'Error: The directory of the mod you selected doesn''t exist any more. Run Dedicated Server with the chosen mod and try again.', PChar(Application.Title), MB_ICONERROR); - jspSelectMod.Show; - exit; + else if ePath = 'Half-Life 2 Deathmatch' then + ePath := 'hl2mp' + else begin + { get games } + if (ePath = 'Team Fortress 2') then + ePath := 'orangebox\tf'; + { ask user, just in case } + case MessageBox(Handle, 'It looks like your server is using the OrangeBox engine. Would you like to install the appropriate binaries for it?', PChar(Application.Title), MB_ICONQUESTION + MB_YESNOCANCEL) of + mrYes: Source := False; + mrNo: Source := True; + mrCancel: exit; end; + end; + // install it + if (DirectoryExists(StandaloneServer + ePath)) then begin + jspInstallProgress.Show; + InstallDedicated(IncludeTrailingPathDelimiter(StandaloneServer + ePath), False, Source) end else begin - if DirectoryExists(StandaloneServer + ePath) then begin - jspInstallProgress.Show; - InstallDedicated(IncludeTrailingPathDelimiter(StandaloneServer + ePath), False) - end - else begin - MessageBox(Handle, 'Error: The directory of the mod you selected doesn''t exist (any more). Run Half-Life Dedicated Server with the chosen mod again and restart.', PChar(Application.Title), MB_ICONERROR); - jspSelectMod.Show; - exit; - end; + MessageBox(Handle, 'Error: The directory of the mod you selected doesn''t exist (any more). Run Half-Life Dedicated Server with the chosen mod again and restart.', PChar(Application.Title), MB_ICONERROR); + jspSelectMod.Show; + exit; end; end; { Listen Server } - if frbListenServer.Checked then begin + if (frbListenServer.Checked) then begin + Source := True; ePath := trvMods.Selected.Text; - if ePath = 'Counter-Strike:Source' then + if (ePath = 'Counter-Strike:Source') then ePath := SteamPath + 'SteamApps\' + trvMods.Selected.Parent.Text + '\counter-strike source\cstrike' - else if ePath = 'Half-Life 2 Deathmatch' then + else if (ePath = 'Half-Life 2 Deathmatch') then ePath := SteamPath + 'SteamApps\' + trvMods.Selected.Parent.Text + '\half-life 2 deathmatch\hl2mp' - else - ePath := SteamPath + 'SteamApps\' + trvMods.Selected.Parent.Text + '\day of defeat source\dod'; + else if ePath = 'Day of Defeat:Source' then + ePath := SteamPath + 'SteamApps\' + trvMods.Selected.Parent.Text + '\day of defeat source\dod' + else begin + { get games } + if (ePath = 'Team Fortress 2') then + ePath := SteamPath + 'SteamApps\' + trvMods.Selected.Parent.Text + '\team fortress 2\tf'; + { ask user, just in case } + case MessageBox(Handle, 'It looks like your server is using the OrangeBox engine. Would you like to install the appropriate binaries for it?', PChar(Application.Title), MB_ICONQUESTION + MB_YESNOCANCEL) of + mrYes: Source := False; + mrNo: Source := True; + mrCancel: exit; + end; + end; - if Pos(SteamPath, ePath) = 0 then + if (Pos(SteamPath, ePath) = 0) then MessageBox(Handle, 'An error occured. Please report this bug to the Metamod:Source team and post a new thread on the forums of www.amxmodx.org.', PChar(Application.Title), MB_ICONSTOP) else begin - if not FileExists(ePath + '\gameinfo.txt') then begin + if (not FileExists(ePath + '\gameinfo.txt')) then begin MessageBox(Handle, 'You have to play this game once before installing Metamod:Source. Do that and try again.', PChar(Application.Title), MB_ICONWARNING); exit; end; jspInstallProgress.Show; - InstallListen(IncludeTrailingPathDelimiter(ePath)); + InstallListen(IncludeTrailingPathDelimiter(ePath), Source); end; end; { Custom mod below } end - else if jplWizard.ActivePage <> jspInstallMethod then - jplWizard.NextPage - else begin - if frbDedicatedServer.Checked then begin // Dedicated Server + else if (jplWizard.ActivePage = jspInstallMethod) then begin + if (frbDedicatedServer.Checked) then begin // Dedicated Server eRegistry := TRegistry.Create(KEY_READ); try eRegistry.RootKey := HKEY_CURRENT_USER; @@ -274,19 +341,21 @@ begin SteamPath := ePath; ePath := ePath + 'SteamApps\'; - if DirectoryExists(ePath) then begin + if (DirectoryExists(ePath)) then begin trvMods.Items.Clear; // Check Mods eStr := GetAllFiles(ePath + '*.*', faDirectory, False, True, False); for i := 0 to eStr.Count -1 do begin CurNode := trvMods.Items.Add(nil, eStr[i]); - if DirectoryExists(ePath + eStr[i] + '\source dedicated server\cstrike') then + if (DirectoryExists(ePath + eStr[i] + '\source dedicated server\cstrike')) then trvMods.Items.AddChild(CurNode, 'Counter-Strike:Source'); - if DirectoryExists(ePath + eStr[i] + '\source dedicated server\dod') then + if (DirectoryExists(ePath + eStr[i] + '\source dedicated server\dod')) then trvMods.Items.AddChild(CurNode, 'Day of Defeat:Source'); - if DirectoryExists(ePath + eStr[i] + '\source dedicated server\hl2mp') then + if (DirectoryExists(ePath + eStr[i] + '\source dedicated server\hl2mp')) then trvMods.Items.AddChild(CurNode, 'Half-Life 2 Deatmatch'); + if (DirectoryExists(ePath + eStr[i] + '\source 2007 dedicated server\tf')) then + trvMods.Items.AddChild(CurNode, 'Team Fortress 2'); if CurNode.Count = 0 then CurNode.Free @@ -307,11 +376,11 @@ begin eRegistry.Free; end; end - else if frbListenServer.Checked then begin // Listen Server + else if (frbListenServer.Checked) then begin // Listen Server eRegistry := TRegistry.Create(KEY_READ); try eRegistry.RootKey := HKEY_CURRENT_USER; - if eRegistry.OpenKey('Software\Valve\Steam', False) then begin + if (eRegistry.OpenKey('Software\Valve\Steam', False)) then begin ePath := eRegistry.ReadString('SteamPath'); ePath := IncludeTrailingPathDelimiter(StringReplace(ePath, '/', '\', [rfReplaceAll])); SteamPath := ePath; @@ -330,6 +399,8 @@ begin trvMods.Items.AddChild(CurNode, 'Day of Defeat:Source'); if DirectoryExists(ePath + eStr[i] + '\half-life 2 deathmatch') then trvMods.Items.AddChild(CurNode, 'Half-Life 2 Deatmatch'); + if DirectoryExists(ePath + eStr[i] + '\team fortress 2') then + trvMods.Items.AddChild(CurNode, 'Team Fortress 2'); if CurNode.Count = 0 then CurNode.Free @@ -350,7 +421,7 @@ begin eRegistry.Free; end; end - else if frbStandaloneServer.Checked then begin // Standalone Server + else if (frbStandaloneServer.Checked) then begin // Standalone Server eRegistry := TRegistry.Create; try eRegistry.RootKey := HKEY_CURRENT_USER; @@ -362,6 +433,8 @@ begin trvMods.Items.Add(nil, 'Day of Defeat:Source'); if DirectoryExists(StandaloneServer + 'hl2mp') then trvMods.Items.Add(nil, 'Half-Life 2 Deatmatch'); + if DirectoryExists(StandaloneServer + 'orangebox\tf') then + trvMods.Items.Add(nil, 'Team Fortress 2'); jspSelectMod.Show; cmdNext.Enabled := False; end @@ -371,16 +444,22 @@ begin eRegistry.Free; end; end - else if frbSelectMod.Checked then begin + else if (frbSelectMod.Checked) then begin { Custom mod } if frmSelectModPath.ShowModal = mrOk then begin + ePath := frmSelectModPath.trvDirectory.SelectedFolder.PathName; + { install now } jspInstallProgress.Show; - InstallCustom(IncludeTrailingPathDelimiter(frmSelectModPath.trvDirectory.SelectedFolder.PathName), osWindows); + InstallCustom(IncludeTrailingPathDelimiter(ePath), osWindows, not frmSelectModPath.chkUsesOrangebox.Checked); end; end - else if frbFTP.Checked then // FTP + else if (frbFTP.Checked) then begin // FTP + Height := FTPHeight; jspFTP.Show; - end; + end; + end + else + jplWizard.NextPage end; procedure TfrmMain.CheckNext(Sender: TObject); @@ -390,8 +469,10 @@ end; procedure TfrmMain.cmdBackClick(Sender: TObject); begin - if jplWizard.ActivePage = jspFTP then - jspInstallMethod.Show + if jplWizard.ActivePage = jspFTP then begin + Height := NormalHeight; + jspInstallMethod.Show; + end else begin jplWizard.PrevPage; cmdBack.Visible := jplWizard.ActivePageIndex <> 0; @@ -399,9 +480,9 @@ begin end; procedure TfrmMain.cmdConnectClick(Sender: TObject); -var i: integer; +var i, k: integer; eStr: TStringList; - CurNode: TTreeNode; + CurNode, CurNode2: TTreeNode; Path: String; begin if (Trim(txtHost.Text) = '') or (Trim(txtUsername.Text) = '') then @@ -432,7 +513,7 @@ begin eStr := TStringList.Create; eStr.Text := StringReplace(Path, '/', #13, [rfReplaceAll]); for i := eStr.Count -1 downto 0 do begin - if eStr[i] = '' then + if (eStr[i] = '') then eStr.Delete(i); end; if (Copy(Path, Length(Path) -1, 1) <> '/') then @@ -441,47 +522,39 @@ begin trvDirectories.Enabled := True; cmdConnect.Enabled := True; cmdConnect.Caption := 'Disconnect'; - // ... change to / and create all the directories ... - CurNode := nil; - if (Path <> '/') then begin - try - IdFTP.ChangeDir('/'); - with GetAllDirs do begin - for i := 0 to Count -1 do begin - if (Assigned(CurNode)) then - trvDirectories.Items.AddChild(trvDirectories.Items.Add(nil, Strings[i]), 'Scanning...') - else begin - CurNode := trvDirectories.Items.Add(nil, Strings[i]); - trvDirectories.Items.AddChild(CurNode, 'Scanning...'); - if (Pos('/' + CurNode.Text + '/', Path) = 0) then - CurNode := nil; - end - end; - Free; - end; - IdFTP.ChangeDir(Path); - except - if (IdFTP.Connected) then - IdFTP.ChangeDir(Path) - else - IdFTP.Connect; - end; - end; // ... find directories in start path ... - if eStr.Count <> 0 then begin - for i := 0 to eStr.Count -1 do begin - if (not ((i = 0) and (Assigned(CurNode)))) then - CurNode := trvDirectories.Items.AddChild(CurNode, eStr[i]); + CurNode := nil; + CurNode2 := nil; + if (eStr.Count <> 0) then begin + IdFTP.ChangeDir('/'); + for i := 0 to eStr.Count do begin + try + with GetAllDirs do begin + for k := 0 to Count -1 do begin + if (i = eStr.Count) or (Strings[k] <> eStr[i]) then + trvDirectories.Items.AddChild(trvDirectories.Items.AddChild(CurNode, Strings[k]), 'Scanning...') + else + CurNode2 := trvDirectories.Items.AddChild(CurNode, Strings[k]); + end; + Free; + + CurNode := CurNode2; + trvDirectories.Selected := CurNode; + Repaint; + Application.ProcessMessages; + end; + + if (i <> eStr.Count) then + IdFTP.ChangeDir(eStr[i]); + except + IdFTP.CheckForDisconnect(False); + if (not IdFTP.Connected) then + IdFTP.Disconnect; + CurNode := trvDirectories.Items.AddChild(CurNode, eStr.Strings[i]) + end; end; end; - trvDirectories.Selected := CurNode; eStr.Free; - // ... scan for directories ... - with GetAllDirs do begin - for i := 0 to Count -1 do - trvDirectories.Items.AddChild(trvDirectories.Items.AddChild(CurNode, Strings[i]), 'Scanning...'); - Free; - end; if Assigned(CurNode) then CurNode.Expand(False); diff --git a/installer/files/Readme.txt b/installer/files/Readme.txt index 9c39d58..4f858b5 100644 --- a/installer/files/Readme.txt +++ b/installer/files/Readme.txt @@ -1,7 +1,8 @@ -In this folder should be server.dll and server_i486.so. +Every MM:S library should be located in this directory. -How you prepare a release: -1) Copy the latest MM:S dlls and hl2launch.exe into this folder -2) Run Attach.exe -3) Test MMS_Installer.exe once (should work but nobody wants bug releases, especially not in the installer) -4) If everything worked fine, release it, otherwise pm me (Basic-Master) \ No newline at end of file +How to prepare a release: +1) Copy the latest MM:S dlls into this folder +2) Append .source to the Source binaries and .orangebox to the OrangeBox binaries +3) Run Attach.exe +4) Test MMS_Installer.exe once (recommended) +5) Release it or e-Mail me if you find a bug \ No newline at end of file diff --git a/modules.versions b/modules.versions index 47f61d1..29e45bc 100644 --- a/modules.versions +++ b/modules.versions @@ -1,7 +1,7 @@ [PRODUCT] major = 1 minor = 4 -revision = 0 +revision = 4 [sourcemm] folder = sourcemm diff --git a/sourcehook/generate/sh_memfuncinfo.h b/sourcehook/generate/sh_memfuncinfo.h index fbfddab..a2f2d8c 100644 --- a/sourcehook/generate/sh_memfuncinfo.h +++ b/sourcehook/generate/sh_memfuncinfo.h @@ -1,5 +1,5 @@ /* ======== SourceHook ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcehook/generate/sh_memfuncinfo.hxx b/sourcehook/generate/sh_memfuncinfo.hxx index 5ed37ab..19e6e1e 100644 --- a/sourcehook/generate/sh_memfuncinfo.hxx +++ b/sourcehook/generate/sh_memfuncinfo.hxx @@ -1,5 +1,5 @@ /* ======== SourceHook ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcehook/generate/sourcehook.h b/sourcehook/generate/sourcehook.h index 49e6813..92ff0c0 100644 --- a/sourcehook/generate/sourcehook.h +++ b/sourcehook/generate/sourcehook.h @@ -1,5 +1,5 @@ /* ======== SourceHook ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcehook/generate/sourcehook.hxx b/sourcehook/generate/sourcehook.hxx index 1431c12..0c767a0 100755 --- a/sourcehook/generate/sourcehook.hxx +++ b/sourcehook/generate/sourcehook.hxx @@ -1,5 +1,5 @@ /* ======== SourceHook ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcehook/sh_list.h b/sourcehook/sh_list.h index 12a56f2..b672361 100644 --- a/sourcehook/sh_list.h +++ b/sourcehook/sh_list.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcehook/sh_memfuncinfo.h b/sourcehook/sh_memfuncinfo.h index fbfddab..a2f2d8c 100644 --- a/sourcehook/sh_memfuncinfo.h +++ b/sourcehook/sh_memfuncinfo.h @@ -1,5 +1,5 @@ /* ======== SourceHook ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcehook/sh_memory.h b/sourcehook/sh_memory.h index d626bb9..d2d3531 100644 --- a/sourcehook/sh_memory.h +++ b/sourcehook/sh_memory.h @@ -1,5 +1,5 @@ /* ======== SourceHook ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcehook/sh_stack.h b/sourcehook/sh_stack.h index ffbeecf..8ab9168 100644 --- a/sourcehook/sh_stack.h +++ b/sourcehook/sh_stack.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcehook/sh_string.h b/sourcehook/sh_string.h index 28f2f4b..79f96b8 100755 --- a/sourcehook/sh_string.h +++ b/sourcehook/sh_string.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -153,6 +153,30 @@ public: return npos; } + int find_last_of(const char c, int index = npos) const + { + int len = static_cast(size()); + if (len < 1) + return npos; + if (index >= len || index < npos) + return npos; + int i; + if (index == npos) + i = len - 1; + else + i = index; + + for (; i>=0; i--) + { + if (v[i] == c) + { + return i; + } + } + + return npos; + } + bool is_space(int c) const { if (c == '\f' || c == '\n' || diff --git a/sourcehook/sh_tinyhash.h b/sourcehook/sh_tinyhash.h index 634b253..70e2ee8 100644 --- a/sourcehook/sh_tinyhash.h +++ b/sourcehook/sh_tinyhash.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcehook/sh_vector.h b/sourcehook/sh_vector.h index 45f4ed6..03a3896 100755 --- a/sourcehook/sh_vector.h +++ b/sourcehook/sh_vector.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -19,39 +19,58 @@ namespace SourceHook // Vector template class CVector { - bool Grow() + bool Grow(size_t amount) { // automatic grow size_t newSize = m_Size * 2; + if (newSize == 0) - newSize = 8; // a good init value + { + newSize = 8; + } + + while (m_CurrentUsedSize + amount > newSize) + { + newSize *= 2; + } + T *newData = new T[newSize]; - if (!newData) - return false; + if (m_Data) { for (size_t i=0; i= m_Size) - return Grow(); + if (m_CurrentUsedSize + amount >= m_Size) + { + return Grow(amount); + } else + { return true; + } } bool ChangeSize(size_t size) { // change size if (size == m_Size) + { return true; + } if (!size) { @@ -65,19 +84,24 @@ template class CVector } T *newData = new T[size]; - if (!newData) - return false; + if (m_Data) { size_t end = (m_CurrentUsedSize < size) ? (m_CurrentUsedSize) : size; for (size_t i=0; i m_Size) + { m_CurrentUsedSize = m_Size; + } return true; } @@ -85,7 +109,9 @@ template class CVector void FreeMemIfPossible() { if (!m_Data) + { return; + } if (!m_CurrentUsedSize) { @@ -95,10 +121,14 @@ template class CVector size_t newSize = m_Size; while (m_CurrentUsedSize <= newSize / 2) + { newSize /= 2; + } if (newSize != m_Size) + { ChangeSize(newSize); + } } protected: T *m_Data; @@ -321,14 +351,13 @@ public: bool push_back(const T & elem) { - ++m_CurrentUsedSize; - if (!GrowIfNeeded()) + if (!GrowIfNeeded(1)) { - --m_CurrentUsedSize; return false; } - m_Data[m_CurrentUsedSize - 1] = elem; + m_Data[m_CurrentUsedSize++] = elem; + return true; } @@ -433,13 +462,13 @@ public: size_t ofs = where - begin(); - ++m_CurrentUsedSize; - if (!GrowIfNeeded()) + if (!GrowIfNeeded(1)) { - --m_CurrentUsedSize; return false; } + ++m_CurrentUsedSize; + where = begin() + ofs; // Move subsequent entries diff --git a/sourcehook/sourcehook.cpp b/sourcehook/sourcehook.cpp index 9f34447..f945296 100644 --- a/sourcehook/sourcehook.cpp +++ b/sourcehook/sourcehook.cpp @@ -1,5 +1,5 @@ /* ======== SourceHook ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -752,11 +752,6 @@ namespace SourceHook bool CSourceHookImpl::ShouldContinue() { - // If recall is true, we shall not continue either. - // This is because, if it's true and ShouldContinue is called, it suggests that the - // actual recall is done and that we are back in the original handler which shall return - // immediately. - // Post-recalls: // The second element on the stack has recall set to Recall_Post1. // This means that we want to skip this part and the original function calling thing, so @@ -778,7 +773,21 @@ namespace SourceHook return m_HLIStack.front().shouldContinue; } } - return m_HLIStack.front().shouldContinue && !m_HLIStack.front().recall; + + // 16.01.2008: We used to return false for recalls here. + // This caused the hook funcs to think that the whole iface has been destroyed + // (the original purpose of shouldcontinue was to notify the hookfuncs that everything has been + // invalidated by RemoveHook) so they did not release their iterator. -> Leaks + // Now, GetIter sets the iterator to END so it still gets released but the hooks don't continue. + + // But: we have to return false in the post phase of pre recalls (ie. a pre hook made a recall). + if (m_HLIStack.front().recall == HookLoopInfo::Recall_Pre && + static_cast(m_HLIStack.front().pCurIface)->m_PreHooks.RelFlagGet()) + { + return false; + } + + return m_HLIStack.front().shouldContinue; } void CSourceHookImpl::DoRecall() @@ -994,6 +1003,9 @@ namespace SourceHook ret->Set(m_UsedIters); // m_UsedIters is the last returned and not released iterator ret->Next(); // Use next instead of directly incrementing its m_Iter: // skips paused plugins + + // Set the last iterator to END! + m_UsedIters->GoToEnd(); } ret->m_pNext = m_UsedIters; @@ -1008,6 +1020,8 @@ namespace SourceHook } void CSourceHookImpl::CHookList::ReleaseIter(IIter *pIter) { + m_RelFlag = true; + CIter *pIter2 = static_cast(pIter); // Unlink from m_UsedIters @@ -1017,7 +1031,7 @@ namespace SourceHook if (pIter2->m_pPrev) pIter2->m_pPrev->m_pNext = pIter2->m_pNext; if (pIter2 == m_UsedIters) - m_UsedIters = NULL; + m_UsedIters = m_UsedIters->m_pNext; // Link to m_FreeIters @@ -1048,6 +1062,11 @@ namespace SourceHook SkipPaused(); } + void CSourceHookImpl::CHookList::CIter::GoToEnd() + { + m_Iter = m_pList->m_List.end(); + } + bool CSourceHookImpl::CHookList::CIter::End() { if (!m_pList) @@ -1056,7 +1075,7 @@ namespace SourceHook } void CSourceHookImpl::CHookList::CIter::Next() { - if (!m_pList) + if (!m_pList || m_Iter == m_pList->m_List.end()) return; ++m_Iter; SkipPaused(); diff --git a/sourcehook/sourcehook.h b/sourcehook/sourcehook.h index 49e6813..92ff0c0 100644 --- a/sourcehook/sourcehook.h +++ b/sourcehook/sourcehook.h @@ -1,5 +1,5 @@ /* ======== SourceHook ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcehook/sourcehook_impl.h b/sourcehook/sourcehook_impl.h index eee3ad6..92d6a5f 100644 --- a/sourcehook/sourcehook_impl.h +++ b/sourcehook/sourcehook_impl.h @@ -1,5 +1,5 @@ /* ======== SourceHook ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -257,6 +257,7 @@ namespace SourceHook virtual ~CIter(); void GoToBegin(); + void GoToEnd(); void Set(CIter *pOther); bool End(); @@ -276,6 +277,7 @@ namespace SourceHook // For recalls bool m_Recall; bool m_RQFlag; + bool m_RelFlag; void SetRecallState(); // Sets the list into a state where the next returned // iterator (from GetIter) will be a copy of the last @@ -283,8 +285,9 @@ namespace SourceHook // The hook resets this state automatically on: // GetIter, ReleaseIter - void RQFlagReset() { m_RQFlag = false; } + void RQFlagReset() { m_RQFlag = false; m_RelFlag = false; } bool RQFlagGet() { return m_RQFlag; } + bool RelFlagGet() { return m_RelFlag; } CHookList(); CHookList(const CHookList &other); virtual ~CHookList(); diff --git a/sourcehook/test/Makefile b/sourcehook/test/Makefile index 6c859b0..323625a 100644 --- a/sourcehook/test/Makefile +++ b/sourcehook/test/Makefile @@ -1,4 +1,4 @@ -#(C)2004-2007 SourceMM Development Team +#(C)2004-2008 SourceMM Development Team # Makefile written by David "BAILOPAN" Anderson and Pavol Marko OPT_FLAGS = -O3 -funroll-loops -s -pipe diff --git a/sourcehook/test/msvc7/test.vcproj b/sourcehook/test/msvc7/test.vcproj index 1f9496a..364a956 100644 --- a/sourcehook/test/msvc7/test.vcproj +++ b/sourcehook/test/msvc7/test.vcproj @@ -289,6 +289,9 @@ + + ::iterator event; \ + SourceHook::List::iterator event; \ IMetamodListener *api; \ for (PluginIter iter = g_PluginMngr._begin(); iter != g_PluginMngr._end(); iter++) { \ _Xpl = (*iter); \ if (_Xpl->m_Id == plid) \ continue; \ for (event=_Xpl->m_Events.begin(); event!=_Xpl->m_Events.end(); event++) { \ - api = (*event); \ + api = (*event).event; \ api->evn(plid); \ } \ } @@ -182,6 +183,38 @@ void CPluginManager::SetAllLoaded() } } +void CPluginManager::SetVSPAsLoaded() +{ + PluginIter i; + CPlugin *pPlugin; + SourceHook::List::iterator event; + + for (i = m_Plugins.begin(); i != m_Plugins.end(); i++) + { + pPlugin = (*i); + if (pPlugin->m_Status < Pl_Paused) + { + continue; + } + /* Only valid for plugins >= 10 (v1:5, SourceMM 1.4) */ + if (pPlugin->m_API->GetApiVersion() < 10) + { + continue; + } + for (event = pPlugin->m_Events.begin(); + event != pPlugin->m_Events.end(); + event++) + { + if ((*event).got_vsp) + { + continue; + } + (*event).got_vsp = true; + (*event).event->OnVSPListening(&g_VspListener); + } + } +} + bool CPluginManager::Pause(PluginId id, char *error, size_t maxlen) { CPlugin *pl = FindById(id); @@ -335,7 +368,7 @@ CPluginManager::CPlugin *CPluginManager::_Load(const char *file, PluginId source if (!pl->m_Lib) { if (error) - UTIL_Format(error, maxlen, "%s", dlerror()); + UTIL_Format(error, maxlen, "[%d]", GetLastError()); pl->m_Status = Pl_Error; } else { CreateInterfaceFn pfn = (CreateInterfaceFn)(dlsym(pl->m_Lib, PL_EXPOSURE_C)); @@ -373,6 +406,22 @@ CPluginManager::CPlugin *CPluginManager::_Load(const char *file, PluginId source //if (pl->m_API->GetApiVersion() >= 4) pl->m_API->AllPluginsLoaded(); } + if (g_VspListener.IsRootLoadMethod() + || (g_VspListener.IsLoaded() && g_SmmAPI.VSPEnabled())) + { + SourceHook::List::iterator event; + for (event = pl->m_Events.begin(); + event != pl->m_Events.end(); + event++) + { + if (pl->m_API->GetApiVersion() < 10 || (*event).got_vsp) + { + continue; + } + (*event).got_vsp = true; + (*event).event->OnVSPListening(&g_VspListener); + } + } } else { pl->m_Status = Pl_Refused; } @@ -496,23 +545,17 @@ bool CPluginManager::UnloadAll() { PluginIter i; - SourceHook::List remqueue; - - for (i=m_Plugins.begin(); i!=m_Plugins.end(); i++) - remqueue.push_back( (*i) ); - char error[128]; bool status = true; - for (i=remqueue.begin(); i!=remqueue.end(); i++) + while ((i = m_Plugins.begin()) != m_Plugins.end()) { if ( !_Unload( (*i), true, error, sizeof(error)) ) + { status = false; + } } - m_Plugins.clear(); - remqueue.clear(); - return status; } @@ -622,3 +665,34 @@ void CPluginManager::UnregAllConCmds(CPlugin *pl) pl->m_Cmds.clear(); } + +const char *CPluginManager::GetStatusText(CPlugin *pl) +{ + switch (pl->m_Status) + { + case Pl_NotFound: + return "NOFILE"; + case Pl_Error: + return "ERROR"; + case Pl_Refused: + return "FAILED"; + case Pl_Paused: + return "PAUSED"; + case Pl_Running: + { + if (pl->m_API && pl->m_API->QueryRunning(NULL, 0)) + { + return "STOPPED"; + } else { + return "RUNNING"; + } + } + default: + return "-"; + } +} + +unsigned int CPluginManager::GetPluginCount() +{ + return (unsigned int)m_Plugins.size(); +} diff --git a/sourcemm/CPlugin.h b/sourcemm/CPlugin.h index 005041f..12f810b 100644 --- a/sourcemm/CPlugin.h +++ b/sourcemm/CPlugin.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -50,6 +50,13 @@ namespace SourceMM SourceHook::String alias; SourceHook::String value; }; + + struct CPluginEventHandler + { + bool got_vsp; + IMetamodListener *event; + }; + /** * @brief Implements Plugin Manager API */ @@ -72,7 +79,7 @@ namespace SourceMM HINSTANCE m_Lib; SourceHook::List m_Cvars; SourceHook::List m_Cmds; - SourceHook::List m_Events; + SourceHook::List m_Events; }; public: CPluginManager(); @@ -123,6 +130,10 @@ namespace SourceMM //Internal iterators SourceHook::List::iterator _begin(); SourceHook::List::iterator _end(); + + void SetVSPAsLoaded(); + unsigned int GetPluginCount(); + const char *GetStatusText(CPlugin *pl); private: //These are identical internal functions for the wrappers above. CPlugin *_Load(const char *file, PluginId source, char *error, size_t maxlen); diff --git a/sourcemm/CSmmAPI.cpp b/sourcemm/CSmmAPI.cpp index 0f336d2..16b5894 100644 --- a/sourcemm/CSmmAPI.cpp +++ b/sourcemm/CSmmAPI.cpp @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -141,8 +141,12 @@ void CSmmAPI::ConPrintf(const char *fmt, ...) void CSmmAPI::AddListener(ISmmPlugin *plugin, IMetamodListener *pListener) { CPluginManager::CPlugin *pl = g_PluginMngr.FindByAPI(plugin); + CPluginEventHandler cpeh; - pl->m_Events.push_back(pListener); + cpeh.event = pListener; + cpeh.got_vsp = false; + + pl->m_Events.push_back(cpeh); } void *CSmmAPI::MetaFactory(const char *iface, int *_ret, PluginId *id) @@ -174,7 +178,7 @@ void *CSmmAPI::MetaFactory(const char *iface, int *_ret, PluginId *id) } CPluginManager::CPlugin *pl; - SourceHook::List::iterator event; + SourceHook::List::iterator event; IMetamodListener *api; int ret = 0; void *val = NULL; @@ -184,7 +188,7 @@ void *CSmmAPI::MetaFactory(const char *iface, int *_ret, PluginId *id) pl = (*iter); for (event=pl->m_Events.begin(); event!=pl->m_Events.end(); event++) { - api = (*event); + api = (*event).event; ret = IFACE_FAILED; if ( (val=api->OnMetamodQuery(iface, &ret)) != NULL ) { @@ -221,22 +225,6 @@ void *CSmmAPI::MetaFactory(const char *iface, int *_ret, PluginId *id) #define ENGINEW32_OFFS 38 #define IA32_CALL 0xE8 -bool vcmp(const void *_addr1, const void *_addr2, size_t len) -{ - unsigned char *addr1 = (unsigned char *)_addr1; - unsigned char *addr2 = (unsigned char *)_addr2; - - for (size_t i=0; iGetCallback(); ptr = (unsigned char *)callback; #ifdef OS_LINUX - if (vcmp(ptr, ENGINE486_SIG, SIGLEN)) + if (UTIL_VerifySignature(ptr, ENGINE486_SIG, SIGLEN)) { offs = ENGINE486_OFFS; } - else if (vcmp(ptr, ENGINE686_SIG, SIGLEN)) + else if (UTIL_VerifySignature(ptr, ENGINE686_SIG, SIGLEN)) { offs = ENGINE686_OFFS; } - else if (vcmp(ptr, ENGINEAMD_SIG, SIGLEN)) + else if (UTIL_VerifySignature(ptr, ENGINEAMD_SIG, SIGLEN)) { offs = ENGINEAMD_OFFS; } #elif defined OS_WIN32 // Only one Windows engine binary so far... - if (vcmp(ptr, ENGINEW32_SIG, SIGLEN)) + if (UTIL_VerifySignature(ptr, ENGINEW32_SIG, SIGLEN)) { offs = ENGINEW32_OFFS; } @@ -454,16 +442,40 @@ void CSmmAPI::ClientConPrintf(edict_t *client, const char *fmt, ...) void CSmmAPI::LoadAsVSP() { - char command[350]; + size_t len; + char engine_file[PATH_SIZE]; + char rel_path[PATH_SIZE * 2]; + + GetFileOfAddress(g_Engine.engine, engine_file, sizeof(engine_file)); + + /* Chop off the "engine" file part */ + len = strlen(engine_file); + for (size_t i = len - 1; i >= 0 && i < len; i--) + { + if (engine_file[i] == '/' + || engine_file[i] == '\\') + { + engine_file[i] = '\0'; + break; + } + } + + const char *usepath = g_SmmPath.c_str(); + if (UTIL_Relatize(rel_path, sizeof(rel_path), engine_file, g_SmmPath.c_str())) + { + usepath = rel_path; + } + + char command[PATH_SIZE * 2]; g_VspListener.SetLoadable(true); - UTIL_Format(command, sizeof(command), "plugin_load \"%s\"\n", g_SmmPath.c_str()); + UTIL_Format(command, sizeof(command), "plugin_load \"%s\"\n", usepath); g_Engine.engine->ServerCommand(command); } void CSmmAPI::EnableVSPListener() { /* If GameInit already passed and we're not already enabled or loaded, go ahead and LoadAsVSP load */ - if (bGameInit && !m_VSP && !g_VspListener.IsLoaded()) + if (bGameInit && !m_VSP && !g_VspListener.IsLoaded() && !g_VspListener.IsRootLoadMethod()) { LoadAsVSP(); } @@ -492,6 +504,11 @@ int CSmmAPI::GetGameDLLVersion() #define MSGCLASS2_SIGLEN 16 #define MSGCLASS2_SIG "\x56\x8B\x74\x24\x2A\x85\xF6\x7C\x2A\x3B\x35\x2A\x2A\x2A\x2A\x7D" #define MSGCLASS2_OFFS 11 + + /* Windows frame pointer sig */ + #define MSGCLASS3_SIGLEN 18 + #define MSGCLASS3_SIG "\x55\x8B\xEC\x51\x89\x2A\x2A\x8B\x2A\x2A\x50\x8B\x0D\x2A\x2A\x2A\x2A\xE8" + #define MSGCLASS3_OFFS 13 #elif defined OS_LINUX /* No frame pointer sig */ #define MSGCLASS_SIGLEN 14 @@ -508,45 +525,22 @@ int CSmmAPI::GetGameDLLVersion() /* :TODO: Make this prettier */ bool CSmmAPI::CacheUserMessages() { - SourceHook::MemFuncInfo info = {true, -1, 0, 0}; - SourceHook::GetFuncInfo(&IServerGameDLL::GetUserMessageInfo, info); - - /* Get address of original GetUserMessageInfo() */ - char *vfunc = reinterpret_cast(g_GameDllPatch->GetOrigFunc(info.vtbloffs, info.vtblindex)); - - /* If we can't get original function, that means there's no hook */ - if (vfunc == NULL) - { - /* Get virtual function address 'manually' then */ - char *adjustedptr = reinterpret_cast(g_GameDll.pGameDLL) + info.thisptroffs + info.vtbloffs; - char **vtable = *reinterpret_cast(adjustedptr); - - vfunc = vtable[info.vtblindex]; - } - - /* Oh dear, we have a relative jump on our hands - * PVK II on Windows made me do this, but I suppose it doesn't hurt to check this on Linux too... - */ - if (*vfunc == '\xE9') - { - /* Get address from displacement... - * - * Add 5 because it's relative to next instruction: - * Opcode <1 byte> + 32-bit displacement <4 bytes> - */ - vfunc = vfunc + *reinterpret_cast(vfunc + 1) + 5; - } - UserMsgDict *dict = NULL; + char *vfunc = UTIL_GetOrigFunction(&IServerGameDLL::GetUserMessageInfo, g_GameDll.pGameDLL, g_GameDllPatch); - if (vcmp(vfunc, MSGCLASS_SIG, MSGCLASS_SIGLEN)) + if (!vfunc) + { + return false; + } + + if (UTIL_VerifySignature(vfunc, MSGCLASS_SIG, MSGCLASS_SIGLEN)) { /* Get address of CUserMessages instance */ char **userMsgClass = *reinterpret_cast(vfunc + MSGCLASS_OFFS); /* Get address of CUserMessages::m_UserMessages */ dict = reinterpret_cast(*userMsgClass); - } else if (vcmp(vfunc, MSGCLASS2_SIG, MSGCLASS2_SIGLEN)) { + } else if (UTIL_VerifySignature(vfunc, MSGCLASS2_SIG, MSGCLASS2_SIGLEN)) { #ifdef OS_WIN32 /* If we get here, the code is possibly inlined like in Dystopia */ @@ -562,6 +556,14 @@ bool CSmmAPI::CacheUserMessages() /* Get address of CUserMessages::m_UserMessages */ dict = reinterpret_cast(*userMsgClass); #endif + #ifdef OS_WIN32 + } else if (UTIL_VerifySignature(vfunc, MSGCLASS3_SIG, MSGCLASS3_SIGLEN)) { + /* Get address of CUserMessages instance */ + char **userMsgClass = *reinterpret_cast(vfunc + MSGCLASS3_OFFS); + + /* Get address of CUserMessages::m_UserMessages */ + dict = reinterpret_cast(*userMsgClass); + #endif } if (dict) diff --git a/sourcemm/CSmmAPI.h b/sourcemm/CSmmAPI.h index 914fb7f..1a2d29e 100644 --- a/sourcemm/CSmmAPI.h +++ b/sourcemm/CSmmAPI.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcemm/IPluginManager.h b/sourcemm/IPluginManager.h index fd0662f..226ad61 100644 --- a/sourcemm/IPluginManager.h +++ b/sourcemm/IPluginManager.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcemm/ISmmAPI.h b/sourcemm/ISmmAPI.h index e6e90a7..fa616ef 100644 --- a/sourcemm/ISmmAPI.h +++ b/sourcemm/ISmmAPI.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcemm/ISmmPlugin.h b/sourcemm/ISmmPlugin.h index dea7700..7665f9a 100644 --- a/sourcemm/ISmmPlugin.h +++ b/sourcemm/ISmmPlugin.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== -* Copyright (C) 2004-2007 Metamod:Source Development Team +* Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcemm/LICENSE.txt b/sourcemm/LICENSE.txt index bf9f1fd..2618fa4 100644 --- a/sourcemm/LICENSE.txt +++ b/sourcemm/LICENSE.txt @@ -1,4 +1,4 @@ -The software is Copyright (C) 2004-2007, Metamod:Source Development Team. +The software is Copyright (C) 2004-2008, Metamod:Source Development Team. Metamod:Source is distributed under the "zLib/libpng" license, which is reproduced below: diff --git a/sourcemm/Makefile b/sourcemm/Makefile index 7cea117..d97b384 100644 --- a/sourcemm/Makefile +++ b/sourcemm/Makefile @@ -1,4 +1,4 @@ -#(C)2004-2007 SourceMM Development Team +#(C)2004-2008 SourceMM Development Team # Makefile written by David "BAILOPAN" Anderson HL2SDK = ../../hl2sdk @@ -34,7 +34,10 @@ endif GCC_VERSION := $(shell $(CPP) -dumpversion >&1 | cut -b1) -CFLAGS += -D_LINUX -DNDEBUG -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_snprintf=snprintf -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp -Wall -Wno-non-virtual-dtor -Werror -fPIC -fno-exceptions -fno-rtti -msse +CFLAGS += -D_LINUX -DNDEBUG -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp \ + -Dstrnicmp=strncasecmp -D_snprintf=snprintf -D_vsnprintf=vsnprintf -D_alloca=alloca \ + -Dstrcmpi=strcasecmp -Wall -Wno-non-virtual-dtor -Wno-uninitialized -Werror -fPIC \ + -fno-exceptions -fno-rtti -msse -m32 ifeq "$(GCC_VERSION)" "4" CFLAGS += $(GCC4_FLAGS) @@ -55,7 +58,7 @@ all: ln -sf $(BIN_DIR)/$(BINARY) $(BINARY) sourcemm: $(OBJ_LINUX) - $(CPP) $(INCLUDE) $(CFLAGS) $(OBJ_LINUX) $(LINK) -shared -ldl -lm -o$(BIN_DIR)/$(BINARY) + $(CPP) $(INCLUDE) -m32 $(CFLAGS) $(OBJ_LINUX) $(LINK) -shared -ldl -lm -o$(BIN_DIR)/$(BINARY) debug: $(MAKE) all DEBUG=true diff --git a/sourcemm/changelog.txt b/sourcemm/changelog.txt index fffa8f6..a630813 100644 --- a/sourcemm/changelog.txt +++ b/sourcemm/changelog.txt @@ -1,3 +1,35 @@ +2008/??/?? 1.4.5: + - Fixed amb1952: Crash when first plugin listed in metaplugins.ini had an alias + and was not on the first line of the file. + +2008/07/26 1.4.4: + - Fixed a bug where loading plugins built for MM:S 1.3 or older (API ver <= 9) + would cause a crash. + - Fixed a bug where loading plugins using VDF files caused Metamod:Source + to crash on The Ship. (bug 1523) + - Fixed a class of crashes caused by improper cvar removal handling. (bug 1416) + - Fixed a bug where VDF files were opened by MM:S even if the .vdf extension + was not at the very end of the filename. For example, "plugin.vdf.disabled" + would have been opened in previous versions. (bug 1534) + - Removed FCVAR_REPLICATED from MM:S convars. (bug 1479) + +2008/01/23 1.4.3: + - Metamod:Source can now be loaded via a .vdf instead of gameinfo.txt. + - Added new plugin loading mechanism via .vdf files in the metamod folder. + - Changed "meta list" output to look similar to Metamod:Source 1.6. + - Plugins which need a VSP pointer can now receive it on late load. + - Fixed a small memory leak when using recalls (RETURN_META_NEWPARAMS). + - Fixed a rare memory corruption bug in the CVector class. + +2007/06/26 1.4.2: + - Fixed a bug where unloading all plugins could crash if one plugin had child plugins. + +2007/05/16 1.4.1: + - The client version of the "meta" command should now work with games using the latest + Source beta (srcds0407). + - Fixed amb233 (VSP listener didn't work with Steam dedicated version). + - Fixed amb277 (failed to get user message list in Kreedz Climbing). + 2007/04/05 1.4.0: - Added API functions for retrieving User Message info without potentially crashing. - Added API functions for letting SourceMM plugins use Valve Server Plugin callbacks. diff --git a/sourcemm/concommands.cpp b/sourcemm/concommands.cpp index 4a3344f..72aed0b 100644 --- a/sourcemm/concommands.cpp +++ b/sourcemm/concommands.cpp @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -9,31 +9,38 @@ */ #include +#include "convar_smm.h" #include "CSmmAPI.h" #include "concommands.h" #include "CPlugin.h" #include "sh_string.h" #include "sh_list.h" +#include "vsp_listener.h" /** * @brief Console Command Implementations * @file concommands.cpp */ -CAlwaysRegisterableCommand g_EternalCommand; SMConVarAccessor g_SMConVarAccessor; +SMConVarAccessor::SMConVarAccessor() +{ + m_TopConCommandBase = NULL; +} + bool SMConVarAccessor::RegisterConCommandBase(ConCommandBase *pCommand) { - // Add the FCVAR_GAMEDLL flag - // => No crash on exit! - // UPDATE: Do _not_ add the FCVAR_GAMEDLL flag here, as it - // causes the command to be unusable on listenservers until you load a map - // We will set the FCVAR_GAMEDLL flag on all commands we have registered once we are being unloaded - //pCommand->AddFlags(FCVAR_GAMEDLL); + /* Add the FCVAR_GAMEDLL flag + * => No crash on exit! + * UPDATE: Do _not_ add the FCVAR_GAMEDLL flag here, as it + * causes the command to be unusable on listen servers until you load a map + * We will set the FCVAR_GAMEDLL flag on all commands we have registered once we are being unloaded + */ + // pCommand->AddFlags(FCVAR_GAMEDLL); m_RegisteredCommands.push_back(pCommand); - pCommand->SetNext( NULL ); + pCommand->SetNext(NULL); g_Engine.icvar->RegisterConCommandBase(pCommand); return true; @@ -41,8 +48,8 @@ bool SMConVarAccessor::RegisterConCommandBase(ConCommandBase *pCommand) bool SMConVarAccessor::Register(ConCommandBase *pCommand) { - //simple, don't mark as part of sourcemm! - pCommand->SetNext( NULL ); + /* Simple, don't mark as part of sourcemm! */ + pCommand->SetNext(NULL); g_Engine.icvar->RegisterConCommandBase(pCommand); return true; @@ -59,67 +66,52 @@ void SMConVarAccessor::MarkCommandsAsGameDLL() void SMConVarAccessor::Unregister(ConCommandBase *pCommand) { - ICvar *cv = g_Engine.icvar; - ConCommandBase *ptr = cv->GetCommands(); + ConCommandBase *pCur = NULL; + ConCommandBase *pPrev = NULL; - if (ptr == pCommand) + if (!pCommand || !pCommand->IsRegistered()) { - //first in list - g_EternalCommand.BringToFront(); - g_EternalCommand.SetNext(const_cast(pCommand->GetNext())); - } else { - //find us and unregister us - ConCommandBase *pPrev = NULL; - while (ptr) - { - if (ptr == pCommand) - break; - pPrev = ptr; - ptr = const_cast(ptr->GetNext()); - } - if (pPrev && ptr == pCommand) + return; + } + + pCur = g_Engine.icvar->GetCommands(); + pCommand->SetRegistered(false); + + if (!m_TopConCommandBase || !pCur) + { + return; + } + + if (pCur == pCommand) + { + *m_TopConCommandBase = const_cast(pCommand->GetNext()); + pCommand->SetNext(NULL); + return; + } + + pPrev = pCur; + pCur = const_cast(pCur->GetNext()); + + while (pCur) + { + if (pCur == pCommand) { pPrev->SetNext(const_cast(pCommand->GetNext())); + pCommand->SetNext(NULL); } + + pPrev = pCur; + pCur = const_cast(pCur->GetNext()); } } -void SMConVarAccessor::UnregisterGameDLLCommands() -{ - ConCommandBase *begin = g_Engine.icvar->GetCommands(); - ConCommandBase *iter = begin; - ConCommandBase *prev = NULL; - while (iter) - { - // watch out for the ETERNAL COMMAND! - if (iter != &g_EternalCommand && iter->IsBitSet(FCVAR_GAMEDLL)) - { - // Remove it! - if (iter == begin) - { - g_EternalCommand.BringToFront(); - iter = const_cast(iter->GetNext()); - g_EternalCommand.SetNext(iter); - prev = &g_EternalCommand; - continue; - } - else - { - iter = const_cast(iter->GetNext()); - prev->SetNext(iter); - continue; - } - } - prev = iter; - iter = const_cast(iter->GetNext()); - } -} - -ConVar metamod_version("metamod_version", SOURCEMM_VERSION, FCVAR_REPLICATED | FCVAR_SPONLY | FCVAR_NOTIFY, "Metamod:Source Version"); -#if defined WIN32 || defined _WIN32 +ConVar metamod_version("metamod_version", SOURCEMM_VERSION, FCVAR_SPONLY | FCVAR_NOTIFY, "Metamod:Source Version"); +#ifdef OS_WIN32 ConVar mm_pluginsfile("mm_pluginsfile", "addons\\metamod\\metaplugins.ini", FCVAR_SPONLY, "Metamod:Source Plugins File"); +ConVar mm_basedir("mm_basedir", "addons\\metamod", FCVAR_SPONLY, "Metamod:Source base folder"); #else ConVar mm_pluginsfile("mm_pluginsfile", "addons/metamod/metaplugins.ini", FCVAR_SPONLY, "Metamod:Source Plugins File"); +ConVar mm_basedir("mm_basedir", "addons/metamod", FCVAR_SPONLY, "Metamod:Source base folder"); #endif CON_COMMAND(meta, "Metamod:Source Menu") @@ -128,6 +120,12 @@ CON_COMMAND(meta, "Metamod:Source Menu") int args = e->Cmd_Argc(); + if (g_VspListener.IsRootLoadMethod() && !g_bLevelChanged) + { + CONMSG("WARNING: You must change the map to activate Metamod:Source.\n"); + return; + } + if (args >= 2) { const char *command = e->Cmd_Argv(1); @@ -138,15 +136,23 @@ CON_COMMAND(meta, "Metamod:Source Menu") CONMSG(" GameDLL/Plugins: David \"BAILOPAN\" Anderson\n"); CONMSG(" GameDLL: Scott \"Damaged Soul\" Ehlert\n"); CONMSG("For more information, see the official website\n"); - CONMSG("http://www.sourcemm.net/\n"); + CONMSG("http://www.metamodsource.net/\n"); return; } else if (strcmp(command, "version") == 0) { CONMSG("Metamod:Source version %s\n", SOURCEMM_VERSION); + if (g_VspListener.IsRootLoadMethod()) + { + CONMSG("Loaded As: Valve Server Plugin\n"); + } + else + { + CONMSG("Loaded As: GameDLL (gameinfo.txt)\n"); + } CONMSG("Compiled on: %s\n", SOURCEMM_DATE); CONMSG("Plugin interface version: %d:%d\n", PLAPI_VERSION, PLAPI_MIN_VERSION); CONMSG("SourceHook version: %d:%d\n", g_SourceHook.GetIfaceVersion(), g_SourceHook.GetImplVersion()); - CONMSG("http://www.sourcemm.net/\n"); + CONMSG("http://www.metamodsource.net/\n"); return; } else if (strcmp(command, "game") == 0) { @@ -191,59 +197,63 @@ CON_COMMAND(meta, "Metamod:Source Menu") return; } else if (strcmp(command, "list") == 0) { - SourceMM::CPluginManager::CPlugin *pl; + size_t len; PluginIter i; - const char *status=""; - const char *version=NULL; - const char *name=NULL; - const char *author=NULL; + char buffer[255]; + ISmmPlugin *plapi; + const char *plname; + SourceMM::CPluginManager::CPlugin *pl; + unsigned int plnum = g_PluginMngr.GetPluginCount(); + +#define IS_STR_FILLED(var) (var != NULL && var[0] != '\0') + + if (!plnum) + { + CONMSG("No plugins loaded.\n"); + return; + } + else + { + CONMSG("Listing %d plugin%s:\n", plnum, (plnum > 1) ? "s" : ""); + } - CONMSG("-Id- %-20.19s %-10.9s %-20.19s %-8.7s\n", "Name", "Version", "Author", "Status"); for (i=g_PluginMngr._begin(); i!=g_PluginMngr._end(); i++) { pl = (*i); if (!pl) + { break; - if (pl->m_Status == Pl_Paused) - { - status = "PAUSE"; - } else if (pl->m_Status == Pl_Running) { - if (pl->m_API && pl->m_API->QueryRunning(NULL, 0)) - status = "RUN"; - else - status = "STOPPED"; - } else if (pl->m_Status == Pl_Refused) { - status = "FAIL"; - } else if (pl->m_Status == Pl_Error) { - status = "ERROR"; - } else if (pl->m_Status == Pl_NotFound) { - status = "NOFILE"; } - if (pl->m_API) + len = 0; + + if (pl->m_Status != Pl_Running) { - version = pl->m_API->GetVersion(); - author = pl->m_API->GetAuthor(); - name = pl->m_API->GetName(); - } else { - version = "-"; - author = "-"; - name = "-"; + len += UTIL_Format(buffer, sizeof(buffer), " [%02d] <%s>", pl->m_Id, g_PluginMngr.GetStatusText(pl)); + } + else + { + len += UTIL_Format(buffer, sizeof(buffer), " [%02d]", pl->m_Id); } - if (!version) - version = "-"; - if (!author) - author = "-"; - if (!name) - name = pl->m_File.c_str(); + if ((plapi = pl->m_API)) + { + plname = IS_STR_FILLED(plapi->GetName()) ? plapi->GetName() : pl->m_File.c_str(); + len += UTIL_Format(&buffer[len], sizeof(buffer)-len, " %s", plname); + if (IS_STR_FILLED(plapi->GetVersion())) + { + len += UTIL_Format(&buffer[len], sizeof(buffer)-len, " (%s)", plapi->GetVersion()); + } + if (IS_STR_FILLED(plapi->GetAuthor())) + { + UTIL_Format(&buffer[len], sizeof(buffer)-len, " by %s", plapi->GetAuthor()); + } + } - CONMSG("[%02d] %-20.19s %-10.9s %-20.19s %-8.7s\n", pl->m_Id, name, version, author, status); + CONMSG("%s\n", buffer); } - //CONMSG("\n"); - return; } else if (strcmp(command, "cmds") == 0) { if (args >= 3) @@ -625,53 +635,6 @@ CON_COMMAND(meta, "Metamod:Source Menu") CONMSG(" version - Version information\n"); } -CAlwaysRegisterableCommand::CAlwaysRegisterableCommand() -{ - Create("", NULL, FCVAR_UNREGISTERED|FCVAR_GAMEDLL); - m_pICvar = NULL; -} - -bool CAlwaysRegisterableCommand::IsRegistered( void ) const -{ - return false; -} - -void CAlwaysRegisterableCommand::BringToFront() -{ - if (!m_pICvar) - m_pICvar = g_Engine.icvar; - - // First, let's try to find us! - ConCommandBase *pPtr = m_pICvar->GetCommands(); - - if (pPtr == this) - { - // We are already at the beginning; Nothing to do - return; - } - - while (pPtr) - { - if (pPtr == this && pPtr->IsCommand() && stricmp(GetName(), pPtr->GetName()) == 0) - break; - ConCommandBase *pPrev = NULL; - while (pPtr) - { - if (pPtr == this) - break; - pPrev = pPtr; - pPtr = const_cast(pPtr->GetNext()); - } - if (pPrev && pPtr == this) - { - pPrev->SetNext(m_pNext); // Remove us from the list - } - // Now, register us - SetNext(NULL); - m_pICvar->RegisterConCommandBase(this); - } -} - void ClientCommand_handler(edict_t *client) { IVEngineServer *e = g_Engine.engine; @@ -688,10 +651,10 @@ void ClientCommand_handler(edict_t *client) { CLIENT_CONMSG(client, "Metamod:Source was developed by:\n"); CLIENT_CONMSG(client, " SourceHook: Pavol \"PM OnoTo\" Marko\n"); - CLIENT_CONMSG(client, " GameDLL/Plugins: David \"BAILOPAN\" Anderson\n"); - CLIENT_CONMSG(client, " GameDLL: Scott \"Damaged Soul\" Ehlert\n"); + CLIENT_CONMSG(client, " Core: David \"BAILOPAN\" Anderson\n"); + CLIENT_CONMSG(client, " Core: Scott \"Damaged Soul\" Ehlert\n"); CLIENT_CONMSG(client, "For more information, see the official website\n"); - CLIENT_CONMSG(client, "http://www.sourcemm.net/\n"); + CLIENT_CONMSG(client, "http://www.metamodsource.net/\n"); RETURN_META(MRES_SUPERCEDE); } else if(strcmp(subcmd, "version") == 0) { @@ -699,49 +662,53 @@ void ClientCommand_handler(edict_t *client) CLIENT_CONMSG(client, "Compiled on: %s\n", SOURCEMM_DATE); CLIENT_CONMSG(client, "Plugin interface version: %d:%d\n", PLAPI_VERSION, PLAPI_MIN_VERSION); CLIENT_CONMSG(client, "SourceHook version: %d:%d\n", g_SourceHook.GetIfaceVersion(), g_SourceHook.GetImplVersion()); - CLIENT_CONMSG(client, "http://www.sourcemm.net/\n"); + CLIENT_CONMSG(client, "http://www.metamodsource.net/\n"); RETURN_META(MRES_SUPERCEDE); } else if(strcmp(subcmd, "list") == 0) { SourceMM::CPluginManager::CPlugin *pl; - Pl_Status st; + ISmmPlugin *plapi; + const char *plname; PluginIter i; - const char *version = NULL; - const char *name = NULL; - const char *author = NULL; - const char *status = NULL; + char buffer[256]; + int len = 0; + int plnum = 0; - CLIENT_CONMSG(client, "-Id- %-20.19s %-10.9s %-20.19s %6s\n", "Name", "Version", "Author", "Status"); - - for (i=g_PluginMngr._begin(); i!=g_PluginMngr._end(); i++) + for (i = g_PluginMngr._begin(); i != g_PluginMngr._end(); i++, len=0) { pl = (*i); - if (!pl) - break; - - st = pl->m_Status; - - /* Only show plugins that are running or paused */ - if (pl->m_API && (st == Pl_Running || st == Pl_Paused)) + if (pl && pl->m_Status == Pl_Running) { - version = pl->m_API->GetVersion(); - author = pl->m_API->GetAuthor(); - name = pl->m_API->GetName(); - - if (st == Pl_Running && pl->m_API->QueryRunning(NULL, 0)) + plapi = pl->m_API; + if (!plapi || !plapi->QueryRunning(NULL, 0)) { - status = "RUN"; - } else { - status = "PAUSE"; + continue; + } + plnum++; + + len += UTIL_Format(buffer, sizeof(buffer), " [%02d]", plnum); + + plname = IS_STR_FILLED(plapi->GetName()) ? plapi->GetName() : pl->m_File.c_str(); + len += UTIL_Format(&buffer[len], sizeof(buffer)-len, " %s", plname); + + if (IS_STR_FILLED(plapi->GetVersion())) + { + len += UTIL_Format(&buffer[len], sizeof(buffer)-len, " (%s)", plapi->GetVersion()); + } + if (IS_STR_FILLED(plapi->GetAuthor())) + { + UTIL_Format(&buffer[len], sizeof(buffer)-len, " by %s", plapi->GetAuthor()); } - if (!version || !author || !name) - break; - - CLIENT_CONMSG(client, "[%02d] %-20.19s %-10.9s %-20.19s %6s\n", pl->m_Id, name, version, author, status); + CLIENT_CONMSG(client, "%s\n", buffer); } } + if (!plnum) + { + CLIENT_CONMSG(client, "No active plugins loaded.\n"); + } + RETURN_META(MRES_SUPERCEDE); } } @@ -758,7 +725,79 @@ void ClientCommand_handler(edict_t *client) RETURN_META(MRES_IGNORED); } +void SMConVarAccessor::UnloadMetamodCommands() +{ + Unregister(&metamod_version); + Unregister(&mm_pluginsfile); + Unregister(&mm_basedir); + Unregister(&meta_command); +} + const char *GetPluginsFile() { return mm_pluginsfile.GetString(); } + +const char *GetMetamodBaseDir() +{ + return mm_basedir.GetString(); +} + +/* Signature for ICvar::GetCommands() in vstdlib for Win32 and Linux. + * + * 20226EE0 A1 50 5C 5A 20 mov eax,dword ptr ds:[205A5C50h] <-- What we want + * 20226EE5 C3 ret + */ +#define CMDLIST_SIG "\xA1\x2A\x2A\x2A\x2A\xC3" +#define CMDLIST_SIGLEN 6 + +/* Linux symbol name of ConCommandBase list in vstdlib */ +#define CMDLIST_SYMBOL "_ZN14ConCommandBase18s_pConCommandBasesE" + +/* This function retrieves the address of the var that holds the top of the ConCommandBase list. + * Having this allows us to change the beginning of this list with ease. + * + * This craziness eliminates the need for the eternal command/cvar used previously which + * could have caused a crash as a result of registering commands/cvars more than once. + */ +bool SMConVarAccessor::InitConCommandBaseList() +{ + char *vfunc = UTIL_GetOrigFunction(&ICvar::GetCommands, g_Engine.icvar, g_CvarPatch); + + if (!vfunc) + { + return false; + } + +#ifdef OS_WIN32 + if (UTIL_VerifySignature(vfunc, CMDLIST_SIG, CMDLIST_SIGLEN)) + { + /* Skip past 0xA1 and get addr of ConCommandBase list var */ + m_TopConCommandBase = *reinterpret_cast(vfunc + 1); + return true; + } +#elif defined OS_LINUX + /* Try dlsym first */ + char path[PATH_SIZE]; + if (GetFileOfAddress((void *)g_Engine.icvar, path, sizeof(path))) + { + void *handle = dlopen(path, RTLD_NOW); + if (handle) + { + m_TopConCommandBase = reinterpret_cast(dlsym(handle, CMDLIST_SYMBOL)); + dlclose(handle); + return true; + } + } + + /* If dlsym failed, then verify signature of function */ + if (!m_TopConCommandBase && UTIL_VerifySignature(vfunc, CMDLIST_SIG, CMDLIST_SIGLEN)) + { + /* Skip past 0xA1 and get addr of ConCommandBase list var */ + m_TopConCommandBase = *reinterpret_cast(vfunc + 1); + return true; + } +#endif + + return false; +} diff --git a/sourcemm/concommands.h b/sourcemm/concommands.h index 56ca953..0ecf594 100644 --- a/sourcemm/concommands.h +++ b/sourcemm/concommands.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -16,37 +16,28 @@ * @file concommands.h */ -#include -#include -#include "sourcemm.h" #include "convar_smm.h" +#include "sourcemm.h" #include "sh_list.h" class SMConVarAccessor : public IConCommandBaseAccessor { SourceHook::List m_RegisteredCommands; + ConCommandBase **m_TopConCommandBase; public: + SMConVarAccessor(); virtual bool RegisterConCommandBase(ConCommandBase *pCommand); bool Register(ConCommandBase *pCommand); void MarkCommandsAsGameDLL(); + bool InitConCommandBaseList(); void Unregister(ConCommandBase *pCommand); - void UnregisterGameDLLCommands(); -}; - -class CAlwaysRegisterableCommand : public ConCommandBase -{ - ICvar *m_pICvar; -public: - CAlwaysRegisterableCommand(); - bool IsRegistered( void ) const; - // If already registered, removes us - // Then it registers us again - void BringToFront(); + void UnloadMetamodCommands(); }; void ClientCommand_handler(edict_t *client); const char *GetPluginsFile(); +const char *GetMetamodBaseDir(); extern SMConVarAccessor g_SMConVarAccessor; diff --git a/sourcemm/convar_smm.h b/sourcemm/convar_smm.h index 26abce9..dd4d9e3 100644 --- a/sourcemm/convar_smm.h +++ b/sourcemm/convar_smm.h @@ -198,6 +198,11 @@ protected: // ConVars in this executable use this 'global' to access values. static IConCommandBaseAccessor *s_pAccessor; +public: + inline void SetRegistered(bool registered) + { + m_bRegistered = registered; + } }; //----------------------------------------------------------------------------- @@ -242,7 +247,7 @@ private: FnCommandCompletionCallback m_fnCompletionCallback; bool m_bHasCompletionCallback; public: - FnCommandCallback GetCallback() { return m_fnCommandCallback; } + inline FnCommandCallback GetCallback() { return m_fnCommandCallback; } }; //----------------------------------------------------------------------------- diff --git a/sourcemm/msvc8/sourcemm.vcproj b/sourcemm/msvc8/sourcemm.vcproj index 615c8d1..6430d55 100644 --- a/sourcemm/msvc8/sourcemm.vcproj +++ b/sourcemm/msvc8/sourcemm.vcproj @@ -41,6 +41,7 @@ + + diff --git a/sourcemm/oslink.cpp b/sourcemm/oslink.cpp index cee64a3..049b743 100644 --- a/sourcemm/oslink.cpp +++ b/sourcemm/oslink.cpp @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -16,17 +16,29 @@ #include "oslink.h" #ifdef __linux #include -#include #endif +#include #if defined __WIN32__ || defined _WIN32 || defined WIN32 const char *dlerror() { static char buf[1024]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &buf, 0, NULL); + DWORD num; + + num = GetLastError(); + + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + num, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buf, + sizeof(buf), + NULL) + == 0) + { + _snprintf(buf, sizeof(buf), "unknown error %x", num); + } + return buf; } #endif diff --git a/sourcemm/oslink.h b/sourcemm/oslink.h index 77e4b75..3529456 100644 --- a/sourcemm/oslink.h +++ b/sourcemm/oslink.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -56,7 +56,7 @@ #endif #if defined __linux__ - extern int errno; + #include int GetLastError(); #endif diff --git a/sourcemm/sample_mm/LICENSE.txt b/sourcemm/sample_mm/LICENSE.txt index bf9f1fd..2618fa4 100644 --- a/sourcemm/sample_mm/LICENSE.txt +++ b/sourcemm/sample_mm/LICENSE.txt @@ -1,4 +1,4 @@ -The software is Copyright (C) 2004-2007, Metamod:Source Development Team. +The software is Copyright (C) 2004-2008, Metamod:Source Development Team. Metamod:Source is distributed under the "zLib/libpng" license, which is reproduced below: diff --git a/sourcemm/sample_mm/Makefile b/sourcemm/sample_mm/Makefile index 8d6f411..fb828ac 100644 --- a/sourcemm/sample_mm/Makefile +++ b/sourcemm/sample_mm/Makefile @@ -1,4 +1,4 @@ -#(C)2004-2007 SourceMM Development Team +#(C)2004-2008 SourceMM Development Team # Makefile written by David "BAILOPAN" Anderson HL2SDK = ../../../hl2sdk diff --git a/sourcemm/sample_mm/SamplePlugin.cpp b/sourcemm/sample_mm/SamplePlugin.cpp index 62c874e..3535159 100644 --- a/sourcemm/sample_mm/SamplePlugin.cpp +++ b/sourcemm/sample_mm/SamplePlugin.cpp @@ -1,5 +1,5 @@ /* ======== sample_mm ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcemm/sample_mm/SamplePlugin.h b/sourcemm/sample_mm/SamplePlugin.h index 74f5af1..96c480d 100644 --- a/sourcemm/sample_mm/SamplePlugin.h +++ b/sourcemm/sample_mm/SamplePlugin.h @@ -1,5 +1,5 @@ /* ======== sample_mm ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcemm/sample_mm/cvars.cpp b/sourcemm/sample_mm/cvars.cpp index 4a6c16a..f887bdf 100644 --- a/sourcemm/sample_mm/cvars.cpp +++ b/sourcemm/sample_mm/cvars.cpp @@ -1,5 +1,5 @@ /* ======== sample_mm ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcemm/sample_mm/cvars.h b/sourcemm/sample_mm/cvars.h index 9bc6286..dc036ab 100644 --- a/sourcemm/sample_mm/cvars.h +++ b/sourcemm/sample_mm/cvars.h @@ -1,5 +1,5 @@ /* ======== sample_mm ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng diff --git a/sourcemm/sample_mm/msvc8/sample_mm.vcproj b/sourcemm/sample_mm/msvc8/sample_mm.vcproj index 9886076..0e10bef 100644 --- a/sourcemm/sample_mm/msvc8/sample_mm.vcproj +++ b/sourcemm/sample_mm/msvc8/sample_mm.vcproj @@ -41,6 +41,7 @@ using namespace SourceMM; @@ -43,6 +45,8 @@ void DLLShutdown_handler(); void LevelShutdown_handler(); bool LevelInit_handler(char const *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background); bool GameInit_handler(); +void LookForVDFs(const char *dir); +bool KVLoadFromFile(KeyValues *kv, IBaseFileSystem *filesystem, const char *resourceName, const char *pathID = NULL); GameDllInfo g_GameDll = {false, NULL, NULL, NULL, NULL}; EngineInfo g_Engine; @@ -57,24 +61,28 @@ bool gParsedGameInfo = false; bool bGameInit = false; SourceHook::List gamedll_list; SourceHook::CallClass *g_GameDllPatch; +SourceHook::CallClass *g_CvarPatch; int g_GameDllVersion = 0; const char VSPIFACE_001[] = "ISERVERPLUGINCALLBACKS001"; const char VSPIFACE_002[] = "ISERVERPLUGINCALLBACKS002"; const char GAMEINFO_PATH[] = "|gameinfo_path|"; +IFileSystem *baseFs = NULL; +bool g_bLevelChanged = false; + void ClearGamedllList(); /* Helper Macro */ #define IFACE_MACRO(orig,nam) \ CPluginManager::CPlugin *pl; \ - SourceHook::List::iterator event; \ + SourceHook::List::iterator event; \ IMetamodListener *api; \ int mret = 0; \ void *val = NULL; \ for (PluginIter iter = g_PluginMngr._begin(); iter != g_PluginMngr._end(); iter++) { \ pl = (*iter); \ for (event=pl->m_Events.begin(); event!=pl->m_Events.end(); event++) { \ - api = (*event); \ + api = (*event).event; \ mret = IFACE_FAILED; \ if ( (val=api->On##nam##Query(iface, &mret)) != NULL ) { \ if (ret) *ret = mret; \ @@ -86,12 +94,12 @@ void ClearGamedllList(); #define ITER_EVENT(evn, args) \ CPluginManager::CPlugin *pl; \ - SourceHook::List::iterator event; \ + SourceHook::List::iterator event; \ IMetamodListener *api; \ for (PluginIter iter = g_PluginMngr._begin(); iter != g_PluginMngr._end(); iter++) { \ pl = (*iter); \ for (event=pl->m_Events.begin(); event!=pl->m_Events.end(); event++) { \ - api = (*event); \ + api = (*event).event; \ api->evn args; \ } \ } @@ -119,25 +127,34 @@ void InitMainStates() SH_ADD_HOOK_STATICFUNC(IServerGameDLL, LevelShutdown, g_GameDll.pGameDLL, LevelShutdown_handler, true); SH_ADD_HOOK_STATICFUNC(IServerGameDLL, LevelInit, g_GameDll.pGameDLL, LevelInit_handler, true); SH_ADD_HOOK_STATICFUNC(IServerGameDLL, GameInit, g_GameDll.pGameDLL, GameInit_handler, false); - - if (g_GameDll.pGameClients) - { - SH_ADD_HOOK_STATICFUNC(IServerGameClients, ClientCommand, g_GameDll.pGameClients, ClientCommand_handler, false); - } else { - /* If IServerGameClients isn't found, this really isn't a fatal error so... */ - LogMessage("[META] Warning: Could not find IServerGameClients!"); - LogMessage("[META] Warning: The 'meta' command will not be available to clients."); - } } -bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals) +void DoInitialPluginLoads() { - g_Engine.engineFactory = engineFactory; - g_Engine.fileSystemFactory = filesystemFactory; - g_Engine.physicsFactory = physicsFactory; - g_Engine.pGlobals = pGlobals; + const char *pluginFile = g_Engine.icvar->GetCommandLineValue("mm_pluginsfile"); + const char *mmBaseDir = g_Engine.icvar->GetCommandLineValue("mm_basedir"); + if (!pluginFile) + { + pluginFile = GetPluginsFile(); + } + if (!mmBaseDir) + { + mmBaseDir = GetMetamodBaseDir(); + } + + char full_path[260]; + + g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), pluginFile); + LoadPluginsFromFile(full_path); + g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), mmBaseDir); + LookForVDFs(full_path); +} + +bool StartupMetamod(CreateInterfaceFn engineFactory, bool bWaitForGameInit) +{ g_Engine.engine = (IVEngineServer *)((engineFactory)(INTERFACEVERSION_VENGINESERVER, NULL)); + if (!g_Engine.engine) { Error("Could not find IVEngineServer! Metamod cannot load."); @@ -152,10 +169,24 @@ bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, g_Engine.loaded = true; - /* Initialize our console hooks */ + /* The Ship is the only game known at this time that uses the pre-Episode One engine */ + g_Engine.original = strcmp(CommandLine()->ParmValue("-game", "hl2"), "ship") == 0; + ConCommandBaseMgr::OneTimeInit(static_cast(&g_SMConVarAccessor)); g_GameDllPatch = SH_GET_CALLCLASS(g_GameDll.pGameDLL); + g_CvarPatch = SH_GET_CALLCLASS(g_Engine.icvar); + + if (g_GameDll.pGameClients) + { + SH_ADD_HOOK_STATICFUNC(IServerGameClients, ClientCommand, g_GameDll.pGameClients, ClientCommand_handler, false); + } + else + { + /* If IServerGameClients isn't found, this really isn't a fatal error so... */ + LogMessage("[META] Warning: Could not find IServerGameClients!"); + LogMessage("[META] Warning: The 'meta' command will not be available to clients."); + } if (!g_SmmAPI.CacheCmds()) { @@ -172,34 +203,127 @@ bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, LogMessage("[META] Warning: The 'meta game' command will not display user messages."); } - const char *pluginFile = g_Engine.icvar->GetCommandLineValue("mm_pluginsfile"); - if (!pluginFile) + baseFs = (IFileSystem *)((engineFactory)(FILESYSTEM_INTERFACE_VERSION, NULL)); + if (baseFs == NULL) { - pluginFile = GetPluginsFile(); + LogMessage("[META] Failed to find filesystem interface, .vdf files will not be parsed."); } - char full_path[260]; - g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), pluginFile); + if (!g_SMConVarAccessor.InitConCommandBaseList()) + { + /* This is very unlikely considering it's old engine */ + LogMessage("[META] Warning: Failed to find ConCommandBase list!"); + LogMessage("[META] Warning: ConVars and ConCommands cannot be unregistered properly! Please file a bug report."); + } - LoadPluginsFromFile(full_path); + if (!bWaitForGameInit) + { + DoInitialPluginLoads(); + bInFirstLevel = true; + } - bInFirstLevel = true; + return true; +} + +bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals) +{ + g_Engine.engineFactory = engineFactory; + g_Engine.fileSystemFactory = filesystemFactory; + g_Engine.physicsFactory = physicsFactory; + g_Engine.pGlobals = pGlobals; + + StartupMetamod(engineFactory, false); RETURN_META_VALUE(MRES_IGNORED, true); } +bool AlternatelyLoadMetamod(CreateInterfaceFn ifaceFactory, CreateInterfaceFn serverFactory) +{ + g_Engine.engineFactory = ifaceFactory; + g_Engine.fileSystemFactory = ifaceFactory; + g_Engine.physicsFactory = ifaceFactory; + + IPlayerInfoManager *playerInfoManager = (IPlayerInfoManager *)serverFactory("PlayerInfoManager002", NULL); + if (playerInfoManager == NULL) + { + Error("Metamod:Source requires gameinfo.txt modification to load on this game."); + return false; + } + + g_Engine.pGlobals = playerInfoManager->GetGlobalVars(); + + /* Now find the server */ + g_GameDll.factory = serverFactory; + g_GameDll.lib = NULL; + + char gamedll_iface[] = "ServerGameDLL000"; + for (unsigned int i = 3; i <= 50; i++) + { + gamedll_iface[15] = '0' + i; + g_GameDll.pGameDLL = (IServerGameDLL *)serverFactory(gamedll_iface, NULL); + if (g_GameDll.pGameDLL != NULL) + { + g_GameDllVersion = i; + break; + } + } + + if (g_GameDll.pGameDLL == NULL) + { + Error("Metamod:Source requires gameinfo.txt modification to load on this game."); + return false; + } + + char gameclients_iface[] = "ServerGameClients000"; + for (unsigned int i = 3; i <= 4; i++) + { + gameclients_iface[19] = '0' + i; + g_GameDll.pGameClients = (IServerGameClients *)serverFactory(gameclients_iface, NULL); + if (g_GameDll.pGameClients != NULL) + { + break; + } + } + + char smm_path[PATH_SIZE]; + const char *game_dir; + GetFileOfAddress((void *)AlternatelyLoadMetamod, smm_path, sizeof(smm_path)); + g_SmmPath.assign(smm_path); + + game_dir = CommandLine()->ParmValue("-game", "hl2"); + abspath(smm_path, game_dir); + g_ModPath.assign(smm_path); + + InitMainStates(); + + if (!StartupMetamod(ifaceFactory, true)) + { + return false; + } + + g_PluginMngr.SetAllLoaded(); + + return true; +} + bool GameInit_handler() { if (bGameInit) { - return true; + RETURN_META_VALUE(MRES_IGNORED, true); } - if (g_SmmAPI.VSPEnabled()) + if (g_SmmAPI.VSPEnabled() && !g_VspListener.IsRootLoadMethod()) { g_SmmAPI.LoadAsVSP(); } + if (g_VspListener.IsRootLoadMethod()) + { + DoInitialPluginLoads(); + //gaben + } + bGameInit = true; RETURN_META_VALUE(MRES_IGNORED, true); @@ -217,7 +341,7 @@ SMM_API void *CreateInterface(const char *iface, int *ret) /* Prevent loading of self as a SourceMM plugin or Valve server plugin :x */ if (strcmp(iface, PLAPI_NAME) == 0) { - Warning("Do not try loading Metamod:Source as a SourceMM or Valve server plugin.\n"); + Warning("Do not try loading Metamod:Source as a Metamod:Source plugin"); if (ret) { @@ -240,6 +364,12 @@ SMM_API void *CreateInterface(const char *iface, int *ret) return &g_VspListener; } + /* If we're a VSP, bypass this by default */ + if (g_VspListener.IsRootLoadMethod()) + { + IFACE_MACRO(g_GameDll.factory, GameDLL); + } + if (!gParsedGameInfo) { gParsedGameInfo = true; @@ -373,7 +503,6 @@ SMM_API void *CreateInterface(const char *iface, int *ret) pInfo->lib = gamedll; pInfo->loaded = true; pInfo->pGameDLL = NULL; - pInfo->pGameClients = (IServerGameClients *)((fn)(INTERFACEVERSION_SERVERGAMECLIENTS, NULL)); gamedll_list.push_back(pInfo); break; } @@ -446,6 +575,15 @@ SMM_API void *CreateInterface(const char *iface, int *ret) } } + /* We use this interface for responding to the meta client command */ + if (strncmp(iface, "ServerGameClients", 17) == 0) + { + void *ptr = (g_GameDll.factory)(iface, ret); + g_GameDll.pGameClients = static_cast(ptr); + + return ptr; + } + /* If we got here, there's definitely a GameDLL */ IFACE_MACRO(g_GameDll.factory, GameDLL); } @@ -465,29 +603,212 @@ void ClearGamedllList() gamedll_list.clear(); } -void DLLShutdown_handler() +void UnloadMetamod(bool shutting_down) { /* Unload plugins */ g_PluginMngr.UnloadAll(); - /* Add the FCVAR_GAMEDLL flag to our cvars so the engine removes them properly */ - g_SMConVarAccessor.MarkCommandsAsGameDLL(); - g_SMConVarAccessor.UnregisterGameDLLCommands(); + if (shutting_down) + { + /* Add the FCVAR_GAMEDLL flag to our cvars so the engine removes them properly */ + g_SMConVarAccessor.MarkCommandsAsGameDLL(); + g_Engine.icvar->UnlinkVariables(FCVAR_GAMEDLL); - SH_CALL(g_GameDllPatch, &IServerGameDLL::DLLShutdown)(); + SH_CALL(g_GameDllPatch, &IServerGameDLL::DLLShutdown)(); + } SH_RELEASE_CALLCLASS(g_GameDllPatch); + SH_RELEASE_CALLCLASS(g_CvarPatch); g_GameDllPatch = NULL; + g_CvarPatch = NULL; g_SourceHook.CompleteShutdown(); if (g_GameDll.lib && g_GameDll.loaded) + { dlclose(g_GameDll.lib); + } memset(&g_GameDll, 0, sizeof(GameDllInfo)); +} +void DLLShutdown_handler() +{ + UnloadMetamod(true); RETURN_META(MRES_SUPERCEDE); } +void LoadFromVDF(const char *file) +{ + PluginId id; + bool already, kvfileLoaded; + KeyValues *pValues; + const char *plugin_file, *alias; + char full_path[256], error[256]; + + pValues = new KeyValues("Metamod Plugin"); + + if (g_Engine.original) + { + /* The Ship must use a special version of this function */ + kvfileLoaded = KVLoadFromFile(pValues, baseFs, file); + } + else + { + kvfileLoaded = pValues->LoadFromFile(baseFs, file); + } + + if (!kvfileLoaded) + { + pValues->deleteThis(); + return; + } + + if ((plugin_file = pValues->GetString("file", NULL)) == NULL) + { + pValues->deleteThis(); + return; + } + + if ((alias = pValues->GetString("alias", NULL)) != NULL) + { + g_PluginMngr.SetAlias(alias, plugin_file); + } + + /* Attempt to find a file extension */ + if (UTIL_GetExtension(plugin_file) == NULL) + { + g_SmmAPI.PathFormat(full_path, + sizeof(full_path), + "%s/%s%s", + g_ModPath.c_str(), + plugin_file, +#if defined WIN32 || defined _WIN32 + ".dll" +#else + "_i486.so" +#endif + ); + } + else + { + g_SmmAPI.PathFormat(full_path, + sizeof(full_path), + "%s/%s", + g_ModPath.c_str(), + plugin_file); + } + + id = g_PluginMngr.Load(full_path, Pl_File, already, error, sizeof(error)); + if (id < Pl_MinId || g_PluginMngr.FindById(id)->m_Status < Pl_Paused) + { + LogMessage("[META] Failed to load plugin %s: %s", plugin_file, error); + } + + pValues->deleteThis(); +} + +void LookForVDFs(const char *dir) +{ + char path[MAX_PATH]; + int extidx; + +#if defined _MSC_VER + HANDLE hFind; + WIN32_FIND_DATA fd; + char error[255]; + + g_SmmAPI.PathFormat(path, sizeof(path), "%s\\*.*", dir); + if ((hFind = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE) + { + DWORD dw = GetLastError(); + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + error, + sizeof(error), + NULL); + LogMessage("[META] Could not open folder \"%s\" (%s)", dir, error); + return; + } + + do + { + if (strcmp(fd.cFileName, ".") == 0 + || strcmp(fd.cFileName, "..") == 0) + { + continue; + } + extidx = strlen(fd.cFileName) - 4; + if (extidx < 0 || stricmp(&fd.cFileName[extidx], ".vdf")) + { + continue; + } + g_SmmAPI.PathFormat(path, sizeof(path), "%s\\%s", dir, fd.cFileName); + LoadFromVDF(path); + } while (FindNextFile(hFind, &fd)); + + FindClose(hFind); +#else + DIR *pDir; + struct dirent *pEnt; + + if ((pDir = opendir(dir)) == NULL) + { + LogMessage("[META] Could not open folder \"%s\" (%s)", dir, strerror(errno)); + return; + } + + while ((pEnt = readdir(pDir)) != NULL) + { + if (strcmp(pEnt->d_name, ".") == 0 + || strcmp(pEnt->d_name, "..") == 0) + { + continue; + } + extidx = strlen(pEnt->d_name) - 4; + if (extidx < 0 || stricmp(&pEnt->d_name[extidx], ".vdf")) + { + continue; + } + g_SmmAPI.PathFormat(path, sizeof(path), "%s/%s", dir, pEnt->d_name); + LoadFromVDF(path); + } + + closedir(pDir); +#endif +} + +bool KVLoadFromFile(KeyValues *kv, IBaseFileSystem *filesystem, const char *resourceName, const char *pathID) +{ + Assert(filesystem); +#ifdef _MSC_VER + Assert(_heapchk() == _HEAPOK); +#endif + + FileHandle_t f = filesystem->Open(resourceName, "rb", pathID); + if (!f) + return false; + + // load file into a null-terminated buffer + int fileSize = filesystem->Size(f); + char *buffer = (char *)MemAllocScratch(fileSize + 1); + + Assert(buffer); + + filesystem->Read(buffer, fileSize, f); // read into local buffer + + buffer[fileSize] = 0; // null terminate file as EOF + + filesystem->Close( f ); // close file after reading + + bool retOK = kv->LoadFromBuffer( resourceName, buffer, filesystem ); + + MemFreeScratch(); + + return retOK; +} + int LoadPluginsFromFile(const char *_file) { FILE *fp; @@ -505,21 +826,22 @@ int LoadPluginsFromFile(const char *_file) char buffer[255], error[255], full_path[255]; const char *ptr, *ext, *file; size_t length; - while (!feof(fp)) + while (!feof(fp) && fgets(buffer, sizeof(buffer), fp) != NULL) { - buffer[0] = '\0'; - fgets(buffer, sizeof(buffer), fp); - length = strlen(buffer); - if (!length) - continue; - if (buffer[length-1] == '\n') - buffer[--length] = '\0'; - UTIL_TrimLeft(buffer); UTIL_TrimRight(buffer); - if (buffer[0] == '\0' || buffer[0] == ';' || strncmp(buffer, "//", 2) == 0) + length = strlen(buffer); + if (!length) + { continue; + } + + if (buffer[0] == '\0' || buffer[0] == ';' || strncmp(buffer, "//", 2) == 0) + { + continue; + } + file = buffer; if (buffer[0] == '"') { @@ -535,7 +857,9 @@ int LoadPluginsFromFile(const char *_file) } cptr++; } - } else { + } + else + { char *cptr = buffer; while (*cptr) { @@ -543,7 +867,9 @@ int LoadPluginsFromFile(const char *_file) { char *optr = cptr; while (*cptr && isspace(*cptr)) + { cptr++; + } *optr = '\0'; UTIL_TrimRight(cptr); if (*cptr && isalpha(*cptr)) @@ -568,13 +894,21 @@ int LoadPluginsFromFile(const char *_file) if (id < Pl_MinId || g_PluginMngr.FindById(id)->m_Status < Pl_Paused) { LogMessage("[META] Failed to load plugin %s. %s", buffer, error); - } else { - if (already) - skipped++; - else - total++; } - } else { + else + { + if (already) + { + skipped++; + } + else + { + total++; + } + } + } + else + { /* Attempt to find a file extension */ ptr = UTIL_GetExtension(file); /* Add an extension if there's none there */ @@ -585,7 +919,9 @@ int LoadPluginsFromFile(const char *_file) #else ext = "_i486.so"; #endif - } else { + } + else + { ext = ""; } /* Format the new path */ @@ -594,11 +930,17 @@ int LoadPluginsFromFile(const char *_file) if (id < Pl_MinId || g_PluginMngr.FindById(id)->m_Status < Pl_Paused) { LogMessage("[META] Failed to load plugin %s. %s", buffer, error); - } else { + } + else + { if (already) + { skipped++; + } else + { total++; + } } } } @@ -607,13 +949,16 @@ int LoadPluginsFromFile(const char *_file) if (skipped) { LogMessage("[META] Loaded %d plugins from file (%d already loaded)", total, skipped); - } else { + } + else + { LogMessage("[META] Loaded %d plugins from file.", total); } - + return total; } + /* Wrapper function. This is called when the GameDLL thinks it's using * the engine's real engineFactory. */ @@ -663,13 +1008,20 @@ void LevelShutdown_handler(void) if (!bInFirstLevel) { char full_path[255]; - g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), GetPluginsFile()); + g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), GetPluginsFile()); LoadPluginsFromFile(full_path); - } else { + + g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), GetMetamodBaseDir()); + LookForVDFs(full_path); + } + else + { bInFirstLevel = false; } + g_bLevelChanged = true; + ITER_EVENT(OnLevelShutdown, ()); RETURN_META(MRES_IGNORED); diff --git a/sourcemm/sourcemm.h b/sourcemm/sourcemm.h index b61f9eb..6b8ab2a 100644 --- a/sourcemm/sourcemm.h +++ b/sourcemm/sourcemm.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -74,11 +74,12 @@ struct GameDllInfo /** @brief Stores information about the HL2 Engine pointers */ struct EngineInfo { - EngineInfo() : loaded(false), + EngineInfo() : loaded(false), original(false), engineFactory(NULL), physicsFactory(NULL), fileSystemFactory(NULL), pGlobals(NULL), icvar(NULL), engine(NULL) { }; bool loaded; + bool original; CreateInterfaceFn engineFactory; CreateInterfaceFn physicsFactory; CreateInterfaceFn fileSystemFactory; @@ -87,6 +88,8 @@ struct EngineInfo IVEngineServer *engine; }; +bool AlternatelyLoadMetamod(CreateInterfaceFn ifaceFactory, CreateInterfaceFn serverFactory); + /** @brief Global variable for GameDLL info */ extern GameDllInfo g_GameDll; @@ -115,8 +118,14 @@ extern PluginId g_PLID; extern int g_GameDllVersion; extern bool bGameInit; +extern bool g_bLevelChanged; + +void UnloadMetamod(bool shutting_down); /** @brief Global CallClass for IServerGameDLL */ extern SourceHook::CallClass *g_GameDllPatch; +/** @brief Global CallClass for ICvar */ +extern SourceHook::CallClass *g_CvarPatch; + #endif //_INCLUDE_SOURCEMM_H diff --git a/sourcemm/stub_mm/LICENSE.txt b/sourcemm/stub_mm/LICENSE.txt index bf9f1fd..2618fa4 100644 --- a/sourcemm/stub_mm/LICENSE.txt +++ b/sourcemm/stub_mm/LICENSE.txt @@ -1,4 +1,4 @@ -The software is Copyright (C) 2004-2007, Metamod:Source Development Team. +The software is Copyright (C) 2004-2008, Metamod:Source Development Team. Metamod:Source is distributed under the "zLib/libpng" license, which is reproduced below: diff --git a/sourcemm/stub_mm/Makefile b/sourcemm/stub_mm/Makefile index eec1eb9..3b9c67e 100644 --- a/sourcemm/stub_mm/Makefile +++ b/sourcemm/stub_mm/Makefile @@ -1,4 +1,4 @@ -#(C)2004-2007 SourceMM Development Team +#(C)2004-2008 SourceMM Development Team # Makefile written by David "BAILOPAN" Anderson HL2SDK = ../../../hl2sdk diff --git a/sourcemm/stub_mm/msvc8/stub_mm.vcproj b/sourcemm/stub_mm/msvc8/stub_mm.vcproj index 6490884..ed30615 100644 --- a/sourcemm/stub_mm/msvc8/stub_mm.vcproj +++ b/sourcemm/stub_mm/msvc8/stub_mm.vcproj @@ -41,6 +41,7 @@ = maxlength - total) + { + /* Not enough space in the buffer */ + return false; + } + total += len; + } + + /* Add the absolute path. */ + len = _snprintf(&buffer[total], maxlength - total, "%s", &rootFrom[1]); + if (len >= maxlength - total) + { + return false; + } + + return true; +} + +size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list params) +{ + size_t len = vsnprintf(buffer, maxlength, fmt, params); + + if (len >= maxlength) + { + len = maxlength - 1; + buffer[len] = '\0'; + } + + return len; +} + +bool UTIL_VerifySignature(const void *addr, const char *sig, size_t len) +{ + unsigned char *addr1 = (unsigned char *) addr; + unsigned char *addr2 = (unsigned char *) sig; + + for (size_t i = 0; i < len; i++) + { + if (addr2[i] == '*') + continue; + if (addr1[i] != addr2[i]) + return false; + } + + return true; +} diff --git a/sourcemm/util.h b/sourcemm/util.h index 2dbfda6..d8c8260 100644 --- a/sourcemm/util.h +++ b/sourcemm/util.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -12,12 +12,15 @@ #define _INCLUDE_UTIL_H #include +#include /** * @brief Utility functions * @file util.h */ +#define IA32_JMP_IMM32 '\xE9' + /** * @brief Returns a pointer to the extension in a file name. */ @@ -56,17 +59,72 @@ size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...); /** * @brief Same as vsnprintf except that it ensures the string buffer is null terminated. */ -inline size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list params) -{ - size_t len = vsnprintf(buffer, maxlength, fmt, params); +size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list params); - if (len >= maxlength) +/** + * @brief Forms a relative path given two absolute paths. + * + * @param buffer Buffer to store relative path in. + * @param maxlength Maximum length of the output buffer. + * @param relTo Destination folder to use as a working directory. + * Final folder name should not be pathchar-terminated. + * @param relFrom Source file or folder to use as a target. + * @return True on success, false on failure. + */ +bool UTIL_Relatize(char buffer[], + size_t maxlength, + const char *relTo, + const char *relFrom); + +/** + * @brief Compares memory address against a signature. + * + * @param addr Memory address to check. + * @param sig Signature used to check against memory address. Accept 0x2A as wildcard. + * @param len Length of signature. + * @return True if signature was verified, false otherwise. + */ +bool UTIL_VerifySignature(const void *addr, const char *sig, size_t len); + +/** + * @brief Returns the original function address of a given virtual function. + * + * @param mfp Member function pointer to virtual function. + * @param ptr Pointer to interface in which the virtual function belongs. + * @param cls A CallClass for the interface in which the virtual function belongs. + * @return Address of function originally pointed to by the virtual function. + */ +template +char *UTIL_GetOrigFunction(MFP vfunc, Iface *ptr, SourceHook::CallClass *cls) +{ + SourceHook::MemFuncInfo info = {true, -1, 0, 0}; + SourceHook::GetFuncInfo(vfunc, info); + + /* Get address of original GetUserMessageInfo() */ + char *func = reinterpret_cast(cls->GetOrigFunc(info.vtbloffs, info.vtblindex)); + + /* If we can't get original function, that means there's no hook */ + if (func == NULL) { - len = maxlength - 1; - buffer[len] = '\0'; + /* Get virtual function address 'manually' then */ + char *adjustedptr = reinterpret_cast(ptr) + info.vtbloffs + info.vtbloffs; + char **vtable = *reinterpret_cast(adjustedptr); + + func = vtable[info.vtblindex]; } - return len; + /* Check for relative jumps */ + if (func[0] == IA32_JMP_IMM32) + { + /* Get address from displacement... + * + * Add 5 because it's relative to next instruction: + * Opcode <1 byte> + 32-bit displacement <4 bytes> + */ + func += *reinterpret_cast(func + 1) + 5; + } + + return func; } #endif //_INCLUDE_UTIL_H diff --git a/sourcemm/version.rc b/sourcemm/version.rc index 67529d4..4ce03de 100755 --- a/sourcemm/version.rc +++ b/sourcemm/version.rc @@ -47,7 +47,7 @@ BEGIN VALUE "FileDescription", "Metamod: Source" VALUE "FileVersion", SVN_FILE_VERSION_STRING VALUE "InternalName", "sourcemm" - VALUE "LegalCopyright", "Copyright (c) 2004-2007, Metamod: Source Development Team" + VALUE "LegalCopyright", "Copyright (c) 2004-2008, Metamod: Source Development Team" VALUE "OriginalFilename", "server.dll" VALUE "ProductName", "Metamod: Source" VALUE "ProductVersion", SVN_PRODUCT_VERSION @@ -99,3 +99,4 @@ END ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED + diff --git a/sourcemm/vsp_listener.cpp b/sourcemm/vsp_listener.cpp index c02a230..947fb49 100644 --- a/sourcemm/vsp_listener.cpp +++ b/sourcemm/vsp_listener.cpp @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -10,15 +10,31 @@ #include "vsp_listener.h" #include "CPlugin.h" +#include "concommands.h" + +SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false); using namespace SourceMM; VSPListener g_VspListener; +ConCommand *g_plugin_unload = NULL; +bool g_bIsTryingToUnload; + +void InterceptPluginUnloads() +{ + g_bIsTryingToUnload = true; +} + +void InterceptPluginUnloads_Post() +{ + g_bIsTryingToUnload = false; +} VSPListener::VSPListener() { m_Loaded = false; m_Loadable = false; + m_bIsRootLoadMethod = false; } void VSPListener::ClientActive(edict_t *pEntity) @@ -92,6 +108,25 @@ void VSPListener::ServerActivate(edict_t *pEdictList, int edictCount, int client void VSPListener::Unload() { + if (g_bIsTryingToUnload) + { + Error("Metamod:Source cannot be unloaded from VSP mode. Use \"meta unload\" to unload specific plugins.\n"); + return; + } + if (IsRootLoadMethod()) + { + if (g_plugin_unload != NULL) + { + SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, g_plugin_unload, InterceptPluginUnloads, false); + SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, g_plugin_unload, InterceptPluginUnloads_Post, true); + g_plugin_unload = NULL; + } + g_SMConVarAccessor.UnloadMetamodCommands(); + UnloadMetamod(false); + } + m_Loadable = true; + m_Loaded = false; + m_bIsRootLoadMethod = false; } void VSPListener::SetLoadable(bool set) @@ -99,53 +134,60 @@ void VSPListener::SetLoadable(bool set) m_Loadable = set; } +bool VSPListener::IsRootLoadMethod() +{ + return m_bIsRootLoadMethod; +} + bool VSPListener::Load(CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory) { - if (!g_GameDll.loaded) - { - Error("Metamod:Source is not a Valve Server Plugin\n"); - - return false; - } - - if (!m_Loadable) - { - Warning("Do not manually load Metamod:Source as a Valve Server Plugin\n"); - - return false; - } - if (m_Loaded) { return false; } + if (!m_Loadable && !g_GameDll.loaded) + { + /* New loading mechanism, do a bunch o' stuff! */ + m_bIsRootLoadMethod = true; + m_Loaded = true; + SetLoadable(false); + if (!AlternatelyLoadMetamod(interfaceFactory, gameServerFactory)) + { + return false; + } + + ConCommandBase *pBase = g_Engine.icvar->GetCommands(); + while (pBase != NULL) + { + if (pBase->IsCommand() && strcmp(pBase->GetName(), "plugin_unload") == 0) + { + g_plugin_unload = (ConCommand *)pBase; + break; + } + pBase = const_cast(pBase->GetNext()); + } + + if (g_plugin_unload != NULL) + { + SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, g_plugin_unload, InterceptPluginUnloads, false); + SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, g_plugin_unload, InterceptPluginUnloads_Post, true); + } + + /* Ho ho ho... if we get here, set a new cvar version. */ + extern ConVar metamod_version; + char buffer[255]; + + UTIL_Format(buffer, sizeof(buffer), "%sV", metamod_version.GetString()); + metamod_version.SetValue(buffer); + } + m_Loaded = true; SetLoadable(false); - PluginIter iter; - CPluginManager::CPlugin *pPlugin; - SourceHook::List::iterator event; - IMetamodListener *pML; - for (iter=g_PluginMngr._begin(); iter!=g_PluginMngr._end(); iter++) + if (!m_bIsRootLoadMethod) { - pPlugin = (*iter); - if (pPlugin->m_Status < Pl_Paused) - { - continue; - } - /* Only valid for plugins >= 10 (v1:5, SourceMM 1.4) */ - if (pPlugin->m_API->GetApiVersion() < 10) - { - continue; - } - for (event=pPlugin->m_Events.begin(); - event!=pPlugin->m_Events.end(); - event++) - { - pML = (*event); - pML->OnVSPListening(this); - } + g_PluginMngr.SetVSPAsLoaded(); } return true; diff --git a/sourcemm/vsp_listener.h b/sourcemm/vsp_listener.h index 44b7b60..8c9a0b8 100644 --- a/sourcemm/vsp_listener.h +++ b/sourcemm/vsp_listener.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2007 Metamod:Source Development Team + * Copyright (C) 2004-2008 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -39,9 +39,11 @@ public: public: bool IsLoaded(); void SetLoadable(bool loadable); + bool IsRootLoadMethod(); private: bool m_Loaded; bool m_Loadable; + bool m_bIsRootLoadMethod; }; extern VSPListener g_VspListener; diff --git a/update_tool/Makefile b/update_tool/Makefile deleted file mode 100644 index 02273b2..0000000 --- a/update_tool/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -#(C)2004-2007 SourceMM Development Team -# Makefile written by David "BAILOPAN" Anderson - -HL2SDK = ../../hl2sdk -SMM_ROOT = .. -SRCDS = ~/srcds - -### EDIT BELOW FOR OTHER PROJECTS ### - -OPT_FLAGS = -O2 -funroll-loops -s -pipe -GCC4_FLAGS = -fvisibility=hidden -fvisibility-inlines-hidden -DEBUG_FLAGS = -g -ggdb3 -CPP = gcc-4.1 -BINARY = sourcemm_update_tool_i486.so - -HL2PUB = $(HL2SDK)/public -HL2LIB = $(HL2SDK)/linux_sdk - -OBJECTS = update_tool.cpp - -LINK = vstdlib_i486.so tier0_i486.so -static-libgcc - -INCLUDE = -I. -I$(HL2PUB) -I$(HL2PUB)/dlls -I$(HL2PUB)/engine -I$(HL2PUB)/tier0 -I$(HL2PUB)/tier1 \ - -I$(HL2PUB)/vstdlib -I$(HL2SDK)/tier1 -I$(SMM_ROOT) -I$(SMM_ROOT)/sourcehook - -ifeq "$(DEBUG)" "true" - BIN_DIR = Debug - CFLAGS = $(DEBUG_FLAGS) -else - BIN_DIR = Release - CFLAGS = $(OPT_FLAGS) -endif - -GCC_VERSION := $(shell $(CPP) -dumpversion >&1 | cut -b1) - -CFLAGS += -D_LINUX -DNDEBUG -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_snprintf=snprintf -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp -Wall -Wno-non-virtual-dtor -Werror -fPIC -fno-exceptions -fno-rtti -msse - -ifeq "$(GCC_VERSION)" "4" - CFLAGS += $(GCC4_FLAGS) -endif - -OBJ_LINUX := $(OBJECTS:%.cpp=$(BIN_DIR)/%.o) - -$(BIN_DIR)/%.o: %.cpp - $(CPP) $(INCLUDE) $(CFLAGS) -o $@ -c $< - -all: - mkdir -p $(BIN_DIR) - ln -sf $(SRCDS)/bin/tier0_i486.so tier0_i486.so - ln -sf $(SRCDS)/bin/vstdlib_i486.so vstdlib_i486.so - nasm api_link.asm -f elf -o $(BIN_DIR)/api_link.o -DLINUX - $(MAKE) sourcemm - rm -rf $(BINARY) - ln -sf $(BIN_DIR)/$(BINARY) $(BINARY) - -sourcemm: $(OBJ_LINUX) - $(CPP) $(INCLUDE) $(CFLAGS) $(OBJ_LINUX) $(BIN_DIR)/api_link.o $(LINK) -shared -ldl -lm -o$(BIN_DIR)/$(BINARY) - -debug: - $(MAKE) all DEBUG=true - -default: all - -clean: - rm -rf Release/*.o - rm -rf Release/$(BINARY) - rm -rf Debug/*.o - rm -rf Debug/$(BINARY) diff --git a/update_tool/README.txt b/update_tool/README.txt deleted file mode 100644 index 9c974a4..0000000 --- a/update_tool/README.txt +++ /dev/null @@ -1,90 +0,0 @@ -This is the README file for sourcemm_update_tool. - -This tool will automatically correct gameinfo.txt when your server gets updated, and Valve's updater overwrites Metamod:Source's changes. This tool is experimental, and is thus a separate download for now. - -1. INSTALLATION - - a. Extract the entire package to your mod folder. The structure should look like: - - /addons/metamod/bin/sourcemm_update_tool.dll - /addons/metamod/bin/sourcemm_update_tool_i486.so - /addons/metamod/README.txt - /addons/sourcemm_update_tool.vdf - /sourcemm_updater.conf - - b. Open /sourcemm_updater.conf with your favorite text editor. Change the - "cstrike" folder to match your mod folder. - -2. CONFIGURATION - - The sourcemm_updater.conf file has two configuration options. - - mmpath - Set this to the path Metamod:Source is located in. - This defaults to addons/metamod/bin - restart - Set this to how the server should be restarted when - gameinfo.txt is patched. There are three options: - - quit - Execute "quit" in the console. Currently does not work. - never - Do not restart. - error - Generate an error message. Because of a bug in SourceDS, - this will generate a crash dump on Windows, but the server - will successfully quit. - -3. USAGE - - You do not need to do anything to use the updater tool. When your server starts, - it silently checks to see if Metamod:Source is loaded. If not, it will make sure - gameinfo.txt is correctly set. Then, depending on how it's configured, it will - kill the server. Most game server provides have auto-restart functionality on their - servers; if not, you will need to manually restart the server. - - The update tool unloads itself immediately after the server starts, so it will not - use any resources, and will not display when you type 'plugin_print'. - -4. TROUBLESHOOTING - - This tool is currently experimental. There are two possible problems. For - any issue you encounter, you should post a report here: - - http://bugs.alliedmods.net/index.php?project=4 - - a. The updater tool does not patch gameinfo.txt - - Verify that the tool is loading. You can do this by opening up the - sourcemm_update_tool.vdf file and copying its file path. Then, enter - the following command in your server console: - - plugin_load - - Example: - - plugin_load cstrike\addons\metamod\bin\sourcemm_update_tool - - If you get the following reply: - - Failed to load plugin "cstrike\addons\sourcemm_update_tool" - Unable to load plugin "cstrike\addons\sourcemm_update_tool" - - Then the tool is working, and you should post a bug report. If instead, - you get: - - Unable to load plugin "cstrike\addons\sourcemm_update_tool" - Unable to load plugin "cstrike\addons\sourcemm_update_tool" - - Then the tool is not loading properly, and the path you are trying to use - is not correct. - - b. The server always dies, and you can't start it at all - - The updater tool is either crashing or not repairing Metamod:Source - correctly. First, try changing the 'restart' line in sourcemm_updater.conf - to the following line: - - restart = never - - If that does not fix the problem, remove the .vdf file so the updater tool - will not be loaded. - - In either case, you should post a bug report containing your mod name and - your gameinfo.txt as an attachment. - \ No newline at end of file diff --git a/update_tool/api_link.asm b/update_tool/api_link.asm deleted file mode 100644 index 6f94053..0000000 --- a/update_tool/api_link.asm +++ /dev/null @@ -1,138 +0,0 @@ -;;;; -;; (C)2005-2007 AlliedModders LLC -;; By the Metamod:Source Development Team -;; This software is licensed under the zlib/libpng free software license. -;; -;; This assembly file is a short thunk wrapper to let us load as a VSP and exit quietly, -;; without any overhead of the rest of the interface, and also to prevent linking against -;; tierX or vstdlib -;;;; - -;;Exports: -;; void GetBaseDir(char *buffer, maxlength); -;; void *GetThisPointer(); -;;Imports: -;; void LoadFunction(); - -section .text - -global GetThisPointer, GetGameDir, ServerCommand -global _GetThisPointer, _GetGameDir, _ServerCommand -global _GetICvar, GetICvar -extern _LoadFunction - -GetICvar: -_GetICvar: - mov eax, [icvar] - ret - -GetThisPointer: -_GetThisPointer: - mov eax, GLOBAL_POINTER - ret - -GetGameDir: -_GetGameDir: - push ebp - mov ebp, esp - - mov ecx, [engine] ;get this pointer - mov edx, [ecx] ;get the vtable - push dword [ebp+12] ;push maxlenth - push dword [ebp+8] ;push buffer - %ifdef LINUX - push ecx ;push this pointer - %endif - call dword [edx+216] ;call IVEngineServer::GetGameDir - %ifdef LINUX - add esp, 12 ;correct stack - %endif - - pop ebp - ret - -ServerCommand -_ServerCommand: - push ebp - mov ebp, esp - - mov ecx, [engine] ;get this pointer - mov edx, [ecx] ;get the vtable - push dword [ebp+8] ;push string - %ifdef LINUX - push ecx ;push this pointer - %endif - call dword [edx+144] ;call IVEngineServer::ServerCommand - %ifdef LINUX - add esp, 8 ;correct stack - %endif - - pop ebp - ret - -thisLoadFunction: - push ebp - mov ebp, esp - - push edi - - ;get factory - %ifdef LINUX - mov edi, [ebp+12] - %else - mov edi, [ebp+8] - %endif - - push dword 0 ;NULL - push dword VENGINESERVER ;iface name - call edi ;call factory - add esp, 8 ;correct stack - - test eax, eax ;do we have a valid pointer? - jz .end ;no, bail out - - mov [engine], eax ;store the engine pointer - - push dword 0 ;NULL - push dword VENGINECVAR ;iface name - call edi ;call factory - add esp, 8 ;correct stack - - test eax, eax ;do we have a valid pointer? - jz .end ;no, bail out - - mov [icvar], eax ;store the icvar pointer - - call _LoadFunction - -.end: - ;We never load, never ever ever! - xor eax, eax - - pop edi - - pop ebp - %ifdef LINUX - ret - %else - retn 8 - %endif - -thisUnloadFunction: - ret - -section .data - INTERFACE_NAME DB "ISERVERPLUGINCALLBACKS001", 0 - VENGINESERVER DB "VEngineServer021", 0 - VENGINECVAR DB "VEngineCvar003", 0 - - VIRTUAL_TABLE DD thisLoadFunction - DD thisUnloadFunction - ;We don't need any more of the vtable here - - GLOBAL_POINTER DD VIRTUAL_TABLE - - temp_ret DD 0 - temp_ptr DD temp_ret - engine DD 0 - icvar DD 0 diff --git a/update_tool/msvc8/update_tool.sln b/update_tool/msvc8/update_tool.sln deleted file mode 100644 index 5250f8a..0000000 --- a/update_tool/msvc8/update_tool.sln +++ /dev/null @@ -1,20 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 9.00 -# Visual Studio 2005 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "update_tool", "update_tool.vcproj", "{DDD1563F-7EE2-4E76-BE57-ED84A2664A51}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DDD1563F-7EE2-4E76-BE57-ED84A2664A51}.Debug|Win32.ActiveCfg = Debug|Win32 - {DDD1563F-7EE2-4E76-BE57-ED84A2664A51}.Debug|Win32.Build.0 = Debug|Win32 - {DDD1563F-7EE2-4E76-BE57-ED84A2664A51}.Release|Win32.ActiveCfg = Release|Win32 - {DDD1563F-7EE2-4E76-BE57-ED84A2664A51}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/update_tool/msvc8/update_tool.vcproj b/update_tool/msvc8/update_tool.vcproj deleted file mode 100644 index 825e521..0000000 --- a/update_tool/msvc8/update_tool.vcproj +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/update_tool/sourcemm_update_tool.vdf b/update_tool/sourcemm_update_tool.vdf deleted file mode 100644 index 79a505c..0000000 --- a/update_tool/sourcemm_update_tool.vdf +++ /dev/null @@ -1,6 +0,0 @@ -//This is a sample VDF file. You need to edit "cstrike" to point to the mod you use. - -"Plugin" -{ - "file" "cstrike/addons/metamod/bin/sourcemm_update_tool" -} diff --git a/update_tool/sourcemm_updater.conf b/update_tool/sourcemm_updater.conf deleted file mode 100644 index bdf1eea..0000000 --- a/update_tool/sourcemm_updater.conf +++ /dev/null @@ -1,9 +0,0 @@ -;Use this to configure where Metamod resides, if you have -;changed its location -;mmpath = addons/metamod/bin - -;Use this to specify how the updater tool restarts SourceDS -; never - don't restart -; error - restart by generating a fatal error message -; quit - restart by issuing a "quit" server command -restart = error diff --git a/update_tool/update_tool.cpp b/update_tool/update_tool.cpp deleted file mode 100644 index ef6dbfe..0000000 --- a/update_tool/update_tool.cpp +++ /dev/null @@ -1,366 +0,0 @@ -#include -#include -#include -#include - -#if defined _MSC_VER -#define SEPCHAR "\\" -#define MMPATH "addons\\metamod\\bin" -#define WINDOWS_LEAN_AND_MEAN -#include -#elif defined __linux__ -#define SEPCHAR "/" -#define MMPATH "addons/metamod/bin" -#include -#endif - -#include - -extern "C" void GetGameDir(char *buffer, int maxlength); -extern "C" void *GetThisPointer(); -extern "C" void ServerCommand(const char *command); -extern "C" ICvar *GetICvar(); - -size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...); -bool s_isspace(char c); -bool RenameFile(const char *old, const char *newf); -bool RemoveFile(const char *file); - -/* This will be called by the thunk */ -#if defined _MSC_VER -extern "C" void LoadFunction() -#elif defined __linux__ -extern "C" void _LoadFunction() -#endif -{ - ICvar *pCvar = GetICvar(); - if (pCvar->FindVar("metamod_version") != NULL) - { - /* Already exists, bail out */ - return; - } - - char gamedir[260]; - char mmpath[260]; - - enum RestartMode - { - Restart_Never, - Restart_Error, - Restart_Quit, - }; - - RestartMode mode = Restart_Error; - - GetGameDir(gamedir, sizeof(gamedir)); - - /* Defaults */ - UTIL_Format(mmpath, sizeof(mmpath), "|gameinfo_path|%s", MMPATH); - - /* Read config */ - char config[260]; - UTIL_Format(config, sizeof(config), "%s" SEPCHAR "sourcemm_updater.conf", gamedir); - FILE *fpCfg = fopen(config, "rt"); - if (fpCfg) - { - char cfgLine[512]; - while (!feof(fpCfg) && fgets(cfgLine, sizeof(cfgLine), fpCfg) != NULL) - { - char key[255]; - - size_t keyLen = 0; - - /* Strip whitespace */ - char *input = cfgLine; - while (*input != '\0' && s_isspace(*input)) - { - input++; - } - - /* Strip ending whitespace */ - size_t len = strlen(input); - for (size_t i = len - 1; - i >= 0 && i < len; - i--) - { - if (s_isspace(input[i])) - { - input[i] = '\0'; - len--; - } else { - break; - } - } - - /* Eat stuff until we find a key */ - while (*input != '\0' && !s_isspace(*input)) - { - if (keyLen < sizeof(key)) - { - key[keyLen++] = *input; - } - input++; - } - key[keyLen] = '\0'; - - /* Eat spaces until we hit an = sign */ - while (*input != '\0' && *input != '=') - { - input++; - } - - if (*input == '=') - { - input++; - } - - /* Eat spaces again */ - while (*input != '\0' && s_isspace(*input)) - { - input++; - } - - /* Ignore comments */ - if (key[0] == ';') - { - continue; - } - - /* The rest is our key */ - if (strcmp(key, "mmpath") == 0) - { - UTIL_Format(mmpath, sizeof(mmpath), "%s", input); - } else if (strcmp(key, "restart") == 0) { - if (strcmp(input, "never") == 0) - { - mode = Restart_Never; - } else if (strcmp(input, "error") == 0) { - mode = Restart_Error; - } else if (strcmp(input, "quit") == 0) { - mode = Restart_Quit; - } - } - } - fclose(fpCfg); - } - - char old_path[260]; - char new_path[260]; - - UTIL_Format(old_path, sizeof(old_path), "%s" SEPCHAR "gameinfo.txt", gamedir); - UTIL_Format(new_path, sizeof(new_path), "%s" SEPCHAR "gameinfo.new.txt", gamedir); - - FILE *fp = fopen(old_path, "rt"); - - if (!fp) - { - return; - } - - FILE *op = fopen(new_path, "wt"); - if (!op) - { - fclose(fp); - return; - } - - enum ParseState - { - Parse_None, - Parse_Root, - Parse_GameInfo, - Parse_FileSystem, - Parse_SearchPaths, - }; - - ParseState ps = Parse_Root; - - char input[1024]; - char backup[1024]; - - bool bWroteOutput = false; - - while (!feof(fp) && fgets(input, sizeof(input), fp) != NULL) - { - UTIL_Format(backup, sizeof(backup), "%s", input); - - if (ps != Parse_None) - { - char *inbuf = input; - - /* Strip beginning whitespace */ - while (*inbuf != '\0' && s_isspace(*inbuf)) - { - inbuf++; - } - - /* Strip ending whitespace */ - size_t len = strlen(inbuf); - for (size_t i = len - 1; - i >= 0 && i < len; - i--) - { - if (s_isspace(inbuf[i])) - { - inbuf[i] = '\0'; - len--; - } else { - break; - } - } - - /* Strip quotation marks */ - if (inbuf[0] == '"' - && inbuf[len-1] == '"') - { - inbuf[len - 1] = '\0'; - inbuf = &inbuf[1]; - len -= 2; - } - - /* Do tests */ - if (ps == Parse_Root && strcmp(inbuf, "GameInfo") == 0) - { - ps = Parse_GameInfo; - } else if (ps == Parse_GameInfo && strcmp(inbuf, "FileSystem") == 0) { - ps = Parse_FileSystem; - } else if (ps == Parse_FileSystem && strcmp(inbuf, "SearchPaths") == 0) { - ps = Parse_SearchPaths; - } else if (ps == Parse_SearchPaths) { - const char *game = strstr(inbuf, "Game"); - if (game) - { - if (strstr(game, "GameBin") != NULL - && strstr(game, mmpath) != NULL) - { - fclose(op); - op = NULL; - break; /* Nothing more to do! */ - } else { - fputs("\t\t\tGameBin\t\t\t", op); - fputs(mmpath, op); - fputs("\n", op); - ps = Parse_None; - bWroteOutput = true; - } - } - } - } - - fputs(backup, op); - } - - if (!op) - { - /* Well, we can't really do anything else. Give up. */ - fclose(fp); - return; - } - - /* Close all streams */ - fclose(op); - fclose(fp); - - /* If we didn't change anything, abort here */ - if (!bWroteOutput) - { - RemoveFile(new_path); - return; - } - - /* Move the old file to a backup name */ - char backup_name[260]; - UTIL_Format(backup_name, sizeof(backup_name), "%s" SEPCHAR "gameinfo.backup.txt", gamedir); - - if (!RenameFile(old_path, backup_name)) - { - /* If we can't rename, just bail out. - * We don't want to overwrite the client's default - * without backing it up first! - */ - return; - } - if (!RenameFile(new_path, old_path)) - { - /* Since this failed, we really have no choice. - * Try and rename the old back. - */ - RenameFile(backup_name, old_path); - return; - } - RemoveFile(new_path); - - if (mode == Restart_Error) - { - Error("Server is restarting to load Metamod:Source"); - } else if (mode == Restart_Quit) { - ServerCommand("quit\n"); - } -} - -bool RemoveFile(const char *file) -{ -#if defined _MSC_VER - return (_unlink(file) == 0); -#else - return (unlink(file) == 0); -#endif -} - -bool s_isspace(char c) -{ - if ((unsigned)c & 0x80) - { - return false; - } else { - return isspace(c) ? true : false; - } -} - -size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - size_t len = vsnprintf(buffer, maxlength, fmt, ap); - va_end(ap); - - if (len >= maxlength) - { - len = maxlength - 1; - buffer[len] = '\0'; - } - - return len; -} - -bool RenameFile(const char *old, const char *newf) -{ -#if defined __linux__ - return (rename(old, newf) == 0); -#elif defined WIN32 - return (MoveFileA(old, newf) != 0); -#endif -} - -#if defined _MSC_VER -extern "C" __declspec(dllexport) void *CreateInterface(const char *iface, int *ret) -#elif defined __linux__ -extern "C" __attribute__((visibility("default"))) void *CreateInterface(const char *iface, int *ret) -#endif -{ - if (strcmp(iface, "ISERVERPLUGINCALLBACKS001") == 0) - { - if (ret) - { - *ret = 0; - } - return GetThisPointer(); - } - - if (ret) - { - *ret = 1; - } - return NULL; -}