{* UltraStar Deluxe - Karaoke Game * * UltraStar Deluxe is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * $URL$ * $Id$ *} unit UHooks; {********************* THookManager Class for saving, managing and calling of hooks. Saves all hookable events and their subscribers *********************} interface {$IFDEF FPC} {$MODE Delphi} {$ENDIF} {$I switches.inc} uses uPluginDefs, SysUtils; type //Record that saves info from Subscriber PSubscriberInfo = ^TSubscriberInfo; TSubscriberInfo = record Self: THandle; //ID of this Subscription (First word: ID of Subscription; 2nd word: ID of Hook) Next: PSubscriberInfo; //Pointer to next Item in HookChain Owner: integer; //For Error Handling and Plugin Unloading. //Here is s/t tricky //To avoid writing of Wrapping Functions to Hook an Event with a Class //We save a Normal Proc or a Method of a Class case isClass: boolean of False: (Proc: TUS_Hook); //Proc that will be called on Event True: (ProcOfClass: TUS_Hook_of_Object); end; TEventInfo = record Name: string[60]; //Name of Event FirstSubscriber: PSubscriberInfo; //First subscriber in chain LastSubscriber: PSubscriberInfo; //Last " (for easier subscriber adding end; THookManager = class private Events: array of TEventInfo; SpaceinEvents: word; //Number of empty Items in Events Array. (e.g. Deleted Items) procedure FreeSubscriber(const EventIndex: word; const Last, Cur: PSubscriberInfo); public constructor Create(const SpacetoAllocate: word); function AddEvent (const EventName: Pchar): THandle; function DelEvent (hEvent: THandle): integer; function AddSubscriber (const EventName: Pchar; const Proc: TUS_Hook = nil; const ProcOfClass: TUS_Hook_of_Object = nil): THandle; function DelSubscriber (const hSubscriber: THandle): integer; function CallEventChain (const hEvent: THandle; const wParam: TwParam; lParam: TlParam): integer; function EventExists (const EventName: Pchar): integer; procedure DelbyOwner(const Owner: integer); end; function HookTest(wParam: TwParam; lParam: TlParam): integer; stdcall; var HookManager: THookManager; implementation uses ULog, UCore; //------------ // Create - Creates Class and Set Standard Values //------------ constructor THookManager.Create(const SpacetoAllocate: word); var I: integer; begin inherited Create(); //Get the Space and "Zero" it SetLength (Events, SpacetoAllocate); for I := 0 to SpacetoAllocate-1 do Events[I].Name[1] := chr(0); SpaceinEvents := SpacetoAllocate; {$IFDEF DEBUG} debugWriteLn('HookManager: Succesful Created.'); {$ENDIF} end; //------------ // AddEvent - Adds an Event and return the Events Handle or 0 on Failure //------------ function THookManager.AddEvent (const EventName: Pchar): THandle; var I: integer; begin Result := 0; if (EventExists(EventName) = 0) then begin if (SpaceinEvents > 0) then begin //There is already Space available //Go Search it! for I := 0 to High(Events) do if (Events[I].Name[1] = chr(0)) then begin //Found Space Result := I; Dec(SpaceinEvents); Break; end; {$IFDEF DEBUG} debugWriteLn('HookManager: Found Space for Event at Handle: ''' + InttoStr(Result+1) + ''); {$ENDIF} end else begin //There is no Space => Go make some! Result := Length(Events); SetLength(Events, Result + 1); end; //Set Events Data Events[Result].Name := EventName; Events[Result].FirstSubscriber := nil; Events[Result].LastSubscriber := nil; //Handle is Index + 1 Inc(Result); {$IFDEF DEBUG} debugWriteLn('HookManager: Add Event succesful: ''' + EventName + ''); {$ENDIF} end {$IFDEF DEBUG} else debugWriteLn('HookManager: Trying to ReAdd Event: ''' + EventName + ''); {$ENDIF} end; //------------ // DelEvent - Deletes an Event by Handle Returns False on Failure //------------ function THookManager.DelEvent (hEvent: THandle): integer; var Cur, Last: PSubscriberInfo; begin hEvent := hEvent - 1; //Arrayindex is Handle - 1 Result := -1; if (Length(Events) > hEvent) and (Events[hEvent].Name[1] <> chr(0)) then begin //Event exists //Free the Space for all Subscribers Cur := Events[hEvent].FirstSubscriber; while (Cur <> nil) do begin Last := Cur; Cur := Cur.Next; FreeMem(Last, SizeOf(TSubscriberInfo)); end; {$IFDEF DEBUG} debugWriteLn('HookManager: Removed Event succesful: ''' + Events[hEvent].Name + ''); {$ENDIF} //Free the Event Events[hEvent].Name[1] := chr(0); Inc(SpaceinEvents); //There is one more space for new events end {$IFDEF DEBUG} else debugWriteLn('HookManager: Try to Remove not Existing Event. Handle: ''' + InttoStr(hEvent) + ''); {$ENDIF} end; //------------ // AddSubscriber - Adds an Subscriber to the Event by Name // Returns Handle of the Subscribtion or 0 on Failure //------------ function THookManager.AddSubscriber (const EventName: Pchar; const Proc: TUS_Hook; const ProcOfClass: TUS_Hook_of_Object): THandle; var EventHandle: THandle; EventIndex: Cardinal; Cur: PSubscriberInfo; begin Result := 0; if (@Proc <> nil) or (@ProcOfClass <> nil) then begin EventHandle := EventExists(EventName); if (EventHandle <> 0) then begin EventIndex := EventHandle - 1; //Get Memory GetMem(Cur, SizeOf(TSubscriberInfo)); //Fill it with Data Cur.Next := nil; //Add Owner Cur.Owner := Core.CurExecuted; if (@Proc = nil) then begin //Use the ProcofClass Method Cur.isClass := True; Cur.ProcOfClass := ProcofClass; end else //Use the normal Proc begin Cur.isClass := False; Cur.Proc := Proc; end; //Create Handle (1st word: Handle of Event; 2nd word: unique ID if (Events[EventIndex].LastSubscriber = nil) then begin if (Events[EventIndex].FirstSubscriber = nil) then begin Result := (EventHandle SHL 16); Events[EventIndex].FirstSubscriber := Cur; end else begin Result := Events[EventIndex].FirstSubscriber.Self + 1; end; end else begin Result := Events[EventIndex].LastSubscriber.Self + 1; Events[EventIndex].LastSubscriber.Next := Cur; end; Cur.Self := Result; //Add to Chain Events[EventIndex].LastSubscriber := Cur; {$IFDEF DEBUG} debugWriteLn('HookManager: Add Subscriber to Event ''' + Events[EventIndex].Name + ''' succesful. Handle: ''' + InttoStr(Result) + ''' Owner: ' + InttoStr(Cur.Owner)); {$ENDIF} end; end; end; //------------ // FreeSubscriber - Helper for DelSubscriber. Prevents Loss of Chain Items. Frees Memory. //------------ procedure THookManager.FreeSubscriber(const EventIndex: word; const Last, Cur: PSubscriberInfo); begin //Delete from Chain if (Last <> nil) then begin Last.Next := Cur.Next; end else //Was first Popup begin Events[EventIndex].FirstSubscriber := Cur.Next; end; //Was this Last subscription ? if (Cur = Events[EventIndex].LastSubscriber) then begin //Change Last Subscriber Events[EventIndex].LastSubscriber := Last; end; //Free Space: FreeMem(Cur, SizeOf(TSubscriberInfo)); end; //------------ // DelSubscriber - Deletes a Subscribtion by Handle, return non Zero on Failure //------------ function THookManager.DelSubscriber (const hSubscriber: THandle): integer; var EventIndex: Cardinal; Cur, Last: PSubscriberInfo; begin Result := -1; EventIndex := ((hSubscriber and (High(THandle) xor High(word))) SHR 16) - 1; //Existing Event ? if (EventIndex < Length(Events)) and (Events[EventIndex].Name[1] <> chr(0)) then begin Result := -2; //Return -1 on not existing Event, -2 on not existing Subscription //Search for Subscription Cur := Events[EventIndex].FirstSubscriber; Last := nil; //go through the chain ... while (Cur <> nil) do begin if (Cur.Self = hSubscriber) then begin //Found Subscription we searched for FreeSubscriber(EventIndex, Last, Cur); {$IFDEF DEBUG} debugWriteLn('HookManager: Del Subscriber from Event ''' + Events[EventIndex].Name + ''' succesful. Handle: ''' + InttoStr(hSubscriber) + ''); {$ENDIF} //Set Result and Break the Loop Result := 0; Break; end; Last := Cur; Cur := Cur.Next; end; end; end; //------------ // CallEventChain - Calls the Chain of a specified EventHandle // Returns: -1: Handle doesn't Exist, 0 Chain is called until the End //------------ function THookManager.CallEventChain (const hEvent: THandle; const wParam: TwParam; lParam: TlParam): integer; var EventIndex: Cardinal; Cur: PSubscriberInfo; CurExecutedBackup: integer; //backup of Core.CurExecuted Attribute begin Result := -1; EventIndex := hEvent - 1; if ((EventIndex <= High(Events)) and (Events[EventIndex].Name[1] <> chr(0))) then begin //Existing Event //Backup CurExecuted CurExecutedBackup := Core.CurExecuted; //Start calling the Chain !!!11 Cur := Events[EventIndex].FirstSubscriber; Result := 0; //Call Hooks until the Chain is at the End or breaked while ((Cur <> nil) and (Result = 0)) do begin //Set CurExecuted Core.CurExecuted := Cur.Owner; if (Cur.isClass) then Result := Cur.ProcOfClass(wParam, lParam) else Result := Cur.Proc(wParam, lParam); Cur := Cur.Next; end; //Restore CurExecuted Core.CurExecuted := CurExecutedBackup; end; {$IFDEF DEBUG} debugWriteLn('HookManager: Called Chain from Event ''' + Events[EventIndex].Name + ''' succesful. Result: ''' + InttoStr(Result) + ''); {$ENDIF} end; //------------ // EventExists - Returns non Zero if an Event with the given Name exists //------------ function THookManager.EventExists (const EventName: Pchar): integer; var I: integer; Name: string[60]; begin Result := 0; //if (Length(EventName) < Name := string(EventName); //Sure not to search for empty space if (Name[1] <> chr(0)) then begin //Search for Event for I := 0 to High(Events) do if (Events[I].Name = Name) then begin //Event found Result := I + 1; Break; end; end; end; //------------ // DelbyOwner - Dels all Subscriptions by a specific Owner. (For Clean Plugin/Module unloading) //------------ procedure THookManager.DelbyOwner(const Owner: integer); var I: integer; Cur, Last: PSubscriberInfo; begin //Search for Owner in all Hooks Chains for I := 0 to High(Events) do begin if (Events[I].Name[1] <> chr(0)) then begin Last := nil; Cur := Events[I].FirstSubscriber; //Went Through Chain while (Cur <> nil) do begin if (Cur.Owner = Owner) then begin //Found Subscription by Owner -> Delete FreeSubscriber(I, Last, Cur); if (Last <> nil) then Cur := Last.Next else Cur := Events[I].FirstSubscriber; end else begin //Next Item: Last := Cur; Cur := Cur.Next; end; end; end; end; end; function HookTest(wParam: TwParam; lParam: TlParam): integer; stdcall; begin Result := 0; //Don't break the chain Core.ShowMessage(CORE_SM_INFO, Pchar(string(Pchar(Pointer(lParam))) + ': ' + string(Pchar(Pointer(wParam))))); end; end.