| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265 |
- /**--------------------------------------------------------------------------**\
- ==============================
- y_hooks - Hook any callback!
- ==============================
- Description:
- Automatically hooks any callbacks with a very simple syntax.
- Legal:
- Version: MPL 1.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is the SA:MP callback hooks include.
-
- The Initial Developer of the Original Code is Alex "Y_Less" Cole.
- Portions created by the Initial Developer are Copyright (C) 2008
- the Initial Developer. All Rights Reserved.
-
- Contributors:
- ZeeX, koolk, JoeBullet/Google63, g_aSlice/Slice
-
- Thanks:
- JoeBullet/Google63 - Handy arbitrary ASM jump code using SCTRL.
- Peter, Cam - Support.
- ZeeX, g_aSlice/Slice, Popz, others - Very productive conversations.
- koolk - IsPlayerinAreaEx code.
- TheAlpha - Danish translation.
- breadfish - German translation.
- Fireburn - Dutch translation.
- yom - French translation.
- 50p - Polish translation.
- Zamaroht - Spanish translation.
- Dracoblue, sintax, mabako, Xtreme, other coders - Producing other modes
- for me to strive to better.
- Pixels^ - Running XScripters where the idea was born.
- Matite - Pestering me to release it and using it.
-
- Very special thanks to:
- Thiadmer - PAWN, whose limits continue to amaze me!
- Kye/Kalcor - SA:MP.
- SA:MP Team past, present and future - SA:MP.
-
- Version:
- 3.0
- Changelog:
- 23/03/14:
- Rewrote for better ordering.
- 26/12/13:
- Added sections.
- 15/04/13:
- Changed the assembly to be shorter by actually using the stack.
- Now slightly handles return values.
- 14/04/13:
- Entirely new version - simpler, faster, and more generic.
- 02/01/13:
- Rewrote the system to do away with ALS.
- Removed overflow checks in every callback.
- Streamlined code.
- 14/04/12:
- Added crash fix from Slice, now returning correct values.
- Fixed ALS detection of mode callbacks.
- 25/02/12:
- Extracted most of the code to a separate file.
- 17/03/11:
- Second complete re-write using another new technique. Now VERY fast!
- Updated OnPlayerUpdate code using Google63's SCTRL jump code.
- 06/08/10:
- First version
- \**--------------------------------------------------------------------------**/
- /*
- ad88888ba
- d8" "8b ,d
- Y8, 88
- `Y8aaaaa, ,adPPYba, MM88MMM 88 88 8b,dPPYba,
- `"""""8b, a8P_____88 88 88 88 88P' "8a
- `8b 8PP""""""" 88 88 88 88 d8
- Y8a a8P "8b, ,aa 88, "8a, ,a88 88b, ,a8"
- "Y88888P" `"Ybbd8"' "Y888 `"YbbdP'Y8 88`YbbdP"'
- 88
- 88
- */
- #define hook%1(%2) UNIQUE_FUNCTION<@yH_%1@...>(%2);UNIQUE_FUNCTION<@yH_%1@...>(%2)
- #define rehook%1(%2) UNIQUE_FUNCTION<@yH_%1@...>(%2)
- #define Hook:%0_%1(%2) @yH_%1@%0(%2);@yH_%1@%0(%2)
- // Strip out extra spaces (nicely recursive)
- #define @yH_%0\32;%1(%2) @yH_%1(%2)
- #define Y_HOOKS_CONTINUE_RETURN_1 (1) // Continue the hook chain, return 1
- #define Y_HOOKS_CONTINUE_RETURN_0 (0) // Continue the hook chain, return 0
- #define Y_HOOKS_BREAK_RETURN_0 (-1) // Break the hook chain, return 0
- #define Y_HOOKS_BREAK_RETURN_1 (-2) // Break the hook chain, return 1
- enum E_HOOK_NAME_REPLACEMENT_DATA
- {
- E_HOOK_NAME_REPLACEMENT_SHORT[16],
- E_HOOK_NAME_REPLACEMENT_LONG[16],
- E_HOOK_NAME_REPLACEMENT_MIN,
- E_HOOK_NAME_REPLACEMENT_MAX
- }
- #if !defined CHAIN_ORDER
- #define CHAIN_ORDER() 0
- #endif
- #define PRE_HOOK(%0) forward @CO_%0();public @CO_%0(){return CHAIN_ORDER()+1;}
- enum E_PRE_HOOK
- {
- E_PRE_HOOK_NAME[16],
- E_PRE_HOOK_VALUE
- }
- PRE_HOOK(HookChain)
- #undef CHAIN_ORDER
- #define CHAIN_ORDER @CO_HookChain
- static stock _HookChain_IncludeStates() <_ALS : _ALS_x0, _ALS : _ALS_x1, _ALS : _ALS_x2, _ALS : _ALS_x3>
- {
- }
- static stock _HookChain_IncludeStates() <_ALS : _ALS_go>
- {
- }
- #define HOOK_FORWARD:%0_%2(%1); \
- forward %0_%2(%1); \
- public %0_%2(%1) <_ALS : _ALS_x0, _ALS : _ALS_x1> { return 1; } \
- public %0_%2(%1) <> { return 1; }
- #define HOOK_RET:%0(%1) forward @RET%0(); public @RET%0()
- HOOK_RET:OnPlayerCommandText()
- {
- return 0;
- }
- HOOK_RET:OnRconCommand()
- {
- return 0;
- }
- #if !defined MAX_HOOK_REPLACEMENTS
- #define MAX_HOOK_REPLACEMENTS (16)
- #endif
- // Generate a function name using only ONE of the parts, so two replacements for
- // the same long name will collide at compile-time
- #define DEFINE_HOOK_REPLACEMENT(%0,%1); forward @_yH%0(); public @_yH%0() { _Hooks_AddReplacement(#%0, #%1); }
- // Strip spaces from the generated function name.
- #define @_yH%0\32;%1(%2) @_yH%0%1(%2)
- // Create the default replacements.
- DEFINE_HOOK_REPLACEMENT(Checkpoint, CP );
- DEFINE_HOOK_REPLACEMENT(Container , Cnt);
- DEFINE_HOOK_REPLACEMENT(Inventory , Inv);
- DEFINE_HOOK_REPLACEMENT(Dynamic , Dyn);
- DEFINE_HOOK_REPLACEMENT(TextDraw , TD );
- DEFINE_HOOK_REPLACEMENT(Update , Upd);
- DEFINE_HOOK_REPLACEMENT(Object , Obj);
- DEFINE_HOOK_REPLACEMENT(Command , Cmd);
- DEFINE_HOOK_REPLACEMENT(DynamicCP , DynamicCP);
- static stock
- YSI_g_sReplacements[MAX_HOOK_REPLACEMENTS][E_HOOK_NAME_REPLACEMENT_DATA],
- YSI_g_sReplacementsLongOrder[MAX_HOOK_REPLACEMENTS],
- YSI_g_sReplacementsShortOrder[MAX_HOOK_REPLACEMENTS],
- YSI_g_sReplacePtr;
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_MakeLongName</summary>
- <param name="name">Function name to modify.</param>
- <returns>
- -
- </returns>
- <remarks>
- Expands all name parts like "CP" and "Obj" to their full versions (in this
- example "Checkpoint" and "Object").
- </remarks>
- \**--------------------------------------------------------------------------**/
- stock Hooks_MakeLongName(name[64])
- {
- new
- end = 0,
- i = 0,
- pos = -1,
- idx = YSI_g_sReplacementsShortOrder[0];
- while (i != YSI_g_sReplacePtr)
- {
- // Allow for multiple replacements of the same string.
- if ((pos = strfind(name, YSI_g_sReplacements[idx][E_HOOK_NAME_REPLACEMENT_SHORT], false, pos + 1)) == -1)
- {
- ++i,
- idx = YSI_g_sReplacementsShortOrder[i];
- }
- // This assumes CamelCase. If the letter immediately following the end
- // of the string is lower-case, then the short word found is not a
- // complete word and should not be replaced.
- else if ('a' <= name[(end = pos + YSI_g_sReplacements[idx][E_HOOK_NAME_REPLACEMENT_MIN])] <= 'z')
- continue;
- else
- {
- P:5("Found hook name replacement: %d, %s", pos, YSI_g_sReplacements[idx][E_HOOK_NAME_REPLACEMENT_SHORT]);
- // Found a complete word according to CamelCase rules.
- strdel(name, pos + 1, end),
- name[pos] = 0x80000000 | idx;
- }
- }
- // All the replacements have been found and marked. Now actually insert.
- // If "end" is "0", it means that it was never assigned to within the loop
- // above, which means that no replacements were found.
- if (end)
- {
- // "pos" must be "-1" at the start of this loop.
- while (name[++pos])
- {
- if (name[pos] < '\0')
- {
- // Negative number instead of a character. Used to indicate
- // where a replacement should be inserted. They are not done
- // inline in the loop above because some replacements may be
- // contained within others - we don't want those to be both done
- // within each other.
- P:5("Inserting hook name replacement: %d, %s", pos, YSI_g_sReplacements[name[pos] & ~0x80000000][E_HOOK_NAME_REPLACEMENT_LONG]);
- P:7("Current character: 0x%04x%04x", name[pos] >>> 16, name[pos] & 0xFFFF);
- // It took me ages to find a bug in this code. For some reason,
- // "strins" was packing the result. This was because the value
- // in "name[pos]" was not a real character, so was seen as an
- // indication that the string was packed. I fixed it by moving
- // the assignment to "name[pos]" above "strins". So while the
- // two lines look independent, they aren't!
- i = name[pos] & ~0x80000000,
- name[pos] = YSI_g_sReplacements[i][E_HOOK_NAME_REPLACEMENT_LONG],
- strins(name, YSI_g_sReplacements[i][E_HOOK_NAME_REPLACEMENT_LONG + E_HOOK_NAME_REPLACEMENT_DATA:1], pos + 1),
- pos += YSI_g_sReplacements[i][E_HOOK_NAME_REPLACEMENT_MAX] - 1;
- P:7("New string: %s", name);
- }
- }
- }
- // It is possible for the expansions to become TOO big.
- return Hooks_MakeShortName(name);
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_MakeShortName</summary>
- <param name="name">Function name to modify.</param>
- <returns>
- -
- </returns>
- <remarks>
- Compresses function names when required to fit within 32 characters
- according to well defined rules (see "YSI_g_sReplacements").
- </remarks>
- \**--------------------------------------------------------------------------**/
- stock Hooks_MakeShortName(name[64])
- {
- // Easy one.
- new
- len,
- pos = -1,
- idx = YSI_g_sReplacementsLongOrder[0];
- for (new i = 0; (len = strlen(name)) >= 32 && i != YSI_g_sReplacePtr; )
- {
- if ((pos = strfind(name, YSI_g_sReplacements[idx][E_HOOK_NAME_REPLACEMENT_LONG], false, pos + 1)) == -1)
- {
- ++i,
- idx = YSI_g_sReplacementsLongOrder[i];
- }
- else
- {
- strdel(name, pos, pos + YSI_g_sReplacements[idx][E_HOOK_NAME_REPLACEMENT_MAX]),
- strins(name, YSI_g_sReplacements[idx][E_HOOK_NAME_REPLACEMENT_SHORT], pos);
- }
- }
- return len;
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_IsolateName</summary>
- <param name="name">The string to get the hooked function name from.</param>
- <returns>
- The input string without y_hooks name decorations.
- </returns>
- <remarks>
- -
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC Hooks_IsolateName(string:name[])
- {
- new
- pos = strfind(name, "@", false, 4);
- // Make "pos" a legal value inside the error message.
- if (pos == -1) P:E("Invalid hook name: %s", unpack(name), ++pos);
- name[pos] = '\0',
- strdel(name, 0, 4);
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_GetPreloadLibraries</summary>
- <param name="preloads">Desination in which to store all the preloads.</param>
- <param name="precount">Number of found preload libraries.</param>
- <param name="size">Maximum number of libraries to store.</param>
- <returns>
- -
- </returns>
- <remarks>
- Some includes, like "fixes.inc" and anti-cheats MUST come before all other
- includes in order for everything to function correctly (at least fixes.inc
- must). This function looks for these definitions:
-
- PRE_HOOK(FIXES)
-
- Which tell y_hooks that any "FIXES_" prefixed callbacks are part of one of
- these chains.
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC stock Hooks_GetPreloadLibraries(preloads[][E_PRE_HOOK], &precount, size = sizeof (preloads))
- {
- --size,
- precount = 0;
- new
- entry,
- idx;
- {
- new
- name[32 char],
- addr;
- while ((idx = AMX_GetPublicEntryPrefix(idx, entry, _A<@CO_>)))
- {
- if (precount == size)
- {
- P:E("y_hooks prehook array filled");
- break;
- }
- addr = AMX_Read(entry);
- AMX_ReadString(AMX_Read(entry + 4) + AMX_BASE_ADDRESS + 4, name),
- strunpack(preloads[precount][E_PRE_HOOK_NAME], name, 16),
- preloads[precount][E_PRE_HOOK_VALUE] = CallFunction(addr),
- ++precount;
- // Remove this public from the publics table. Means that future public
- // function calls will be faster by having a smaller search space.
- Hooks_InvalidateName(entry);
- }
- }
- // Sort the preload libraries.
- {
- new
- tmp[E_PRE_HOOK];
- for (entry = precount - 1; entry > 0; --entry)
- {
- for (idx = 0; idx != entry; ++idx)
- {
- if (preloads[idx][E_PRE_HOOK_VALUE] > preloads[idx + 1][E_PRE_HOOK_VALUE])
- {
- tmp = preloads[idx],
- preloads[idx] = preloads[idx + 1],
- preloads[idx + 1] = tmp;
- }
- }
- }
- }
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_GetPreHooks</summary>
- <param name="preloads">Names of libraries that come before y_hooks.</param>
- <param name="precount">Number of pre libraries.</param>
- <param name="name">Name of the callback.</param>
- <param name="hooks">Destination in which to store the headers.</param>
- <param name="count">Number of headers found.</param>
- <returns>
- -
- </returns>
- <remarks>
- Finds all the AMX file headers for functions with a similar name to the
- given callback that should be called before (or near) the given callback.
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC stock Hooks_GetPreHooks(preloads[][E_PRE_HOOK], precount, name[64], hooks[], &count)
- {
- new
- idx,
- lfunc[64];
- // Collect all the functions with something like this name.
- do
- {
- strcat(lfunc, name),
- Hooks_MakeShortName(lfunc);
- if (AMX_GetPublicEntry(0, hooks[count], lfunc, true)) ++count;
- strcpy(lfunc, preloads[idx][E_PRE_HOOK_NAME]),
- strcat(lfunc, "_");
- }
- while (++idx <= precount);
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_GetPointerRewrite</summary>
- <param name="hooks">All the prehooks for this callback.</param>
- <param name="num">The number of prehooks.</param>
- <param name="ptr">A pointer to write the new stub address to.</param>
- <param name="next">The pointer for the function called after y_hooks.</param>
- <param name="name">The name of the callback being processed.</param>
- <param name="nlen">Space available in the header to write text in.</param>
- <returns>
- -
- </returns>
- <remarks>
- -
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC stock Hooks_GetPointerRewrite(hooks[], num, &ptr, &next, name[], nlen)
- {
- switch (num)
- {
- case 0:
- {
- next = 0;
- new
- len = strlen(name);
- if (nlen >= len)
- {
- // We don't have an existing callback with this name, only hooks.
- // We need to add the name of the callback to the AMX header,
- // and we have enough space in which to do so.
- new
- str[32];
- strpack(str, name),
- AMX_WriteString(AMX_BASE_ADDRESS + AMX_Read(ptr + 4), str, len);
- }
- else
- {
- P:F("Could not write function name in \"Hooks_MakePublicPointer\".");
- // TODO: Fix this. Use an alternate memory location (the actual
- // code segment in which we are writing seems like a good
- // choice).
- }
- }
- case 1:
- {
- // No "fixes.inc", but this callback already exists. In that case,
- // just replace the pointer address.
- next = ptr = hooks[0];
- }
- default:
- {
- // Special hooks. Optimise them.
- for (new cur = 1; cur != num; ++cur)
- {
- ptr = hooks[cur];
- new
- tmp = AMX_Read(ptr),
- nt = Hooks_GetStubEntry(tmp);
- tmp += AMX_HEADER_COD,
- AMX_Write(tmp, _:RelocateOpcode(OP_JUMP));
- switch (nt)
- {
- case -1: ptr = tmp + 4, next = 0;
- case 0: next = 0;
- default:
- {
- ptr = tmp + 4,
- next = tmp + nt,
- nt = AMX_Read(next),
- // Chain those not hooked.
- AMX_Write(ptr, nt),
- // Store the possible next address.
- AMX_Write(next, nt - (AMX_REAL_DATA + AMX_HEADER_COD));
- }
- }
- }
- }
- }
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_GetStubEntry</summary>
- <param name="stub">Starting address of the function.</param>
- <returns>
- The address at which the actual code in this function starts.
- </returns>
- <remarks>
- This handles three cases. Regular functions end instantly as found.
- Functions that start with a switch (even before "PROC") are assumed to be
- state-based functions, and we find the most likely state to be used (i.e. we
- remove all future state changes).
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC stock Hooks_GetStubEntry(stub)
- {
- // Get the address of the next function from the ALS state stub.
- new
- ctx[DisasmContext];
- DisasmInit(ctx, stub, stub + 64);
- switch (DisasmNextInsn(ctx))
- {
- case OP_LOAD_PRI:
- {
- if (DisasmNextInsn(ctx) == OP_SWITCH && DisasmNextInsn(ctx) == OP_CASETBL)
- {
- // Get the number of items in the casetable.
- if (DisasmGetNumOperands(ctx) == 3) // 2 means no used hook.
- {
- // Got a hook to return. Find it.
- new
- h0 = DisasmGetOperand(ctx, 3),
- h1 = DisasmGetOperand(ctx, 5),
- h2 = DisasmGetOperand(ctx, 7);
- if (h1 == h2) return 8 * 4; // Most likely.
- else if (h0 == h2) return 10 * 4;
- else if (h0 == h1) return 12 * 4;
- else P:E("y_hooks could not extract state stub jump");
- }
- else return -1;
- }
- }
- case OP_JUMP:
- {
- // Already replaced once (shouldn't happen, but may if two different
- // hooks use two different short versions of a callback).
- return 4; // DisasmGetOperand(ctx, 0);
- }
- case OP_PROC:
- {
- //return stub;
- P:E("y_hooks attempting to redirect a PROC hook");
- }
- }
- return 0;
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_GetAllHooks</summary>
- <param name="name">The name of the callback (with y_hooks prefix).</param>
- <param name="hooks">Array in which to store the function headers.</param>
- <param name="idx">Current position in the AMX header.</param>
- <param name="namelen">Min bound of space used by all these names.</param>
- <returns>
- The number of hooks found.
- </returns>
- <remarks>
- The name of the function currently being processed is derived from the first
- found hook. This means we already know of one hook, but to simplify the
- code we get that one again here. Above we only know the name not the
- address. Hence the "- 1" in "i = idx - 1" (to go back one function name).
-
- Our "namelen" variable already contains the full length of the first found
- hook - this is the length of "name", plus N extra characters. The following
- are all valid, and may occur when orders are played with:
-
- @yH_OnX@
- @yH_OnX@1
- @yH_OnX@01
- @yH_OnX@024
- @yH_OnX@ZZZ
- @yH_OnX@999@024
-
- If we want to get the EXACT space taken up by all these hook names we would
- need to get the string of the name in this function then measure it. There
- is really no point in doing this - if we have a second we will always have
- enough space for our new names. Instead, we assume that they are all just
-
- @yH_OnX@
-
- And add on that minimum length accordingly (plus 1 for the NULL character).
-
- This length is used if the original callback doesn't exist but hooks do. In
- that case we need to add the callback to the AMX header, and there is a tiny
- chance that the original name will be longer than one hook's name. In that
- case, having two or more hooks will (AFAIK) always ensure that we have
- enough space to write the longer name.
-
- If there is only one hook, no original function, and the name of the hook is
- shorter than the name of the original function then we have an issue and
- will have to do something else instead.
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC stock Hooks_GetAllHooks(const name[], hooks[128], &idx, &namelen)
- {
- // Start from the very start - repeats the first item.
- new
- len = strlen(name) + 1,
- count,
- i = idx - 1;
- while ((i = AMX_GetPublicEntry(i, hooks[count], name)))
- {
- Hooks_InvalidateName(hooks[count]),
- idx = i;
- // Record how much consecutive space we have to play with in the AMX.
- if (count) namelen += len; // The first hook was already counted.
- // Increment and count how many hooks of this type we have.
- if (++count == sizeof (hooks))
- {
- P:W("Hooks_GetAllHooks: Potential overflow.");
- break;
- }
- }
- return count;
- }
- /**--------------------------------------------------------------------------**\
- <summary></summary>
- <param name=""></param>
- <returns>
- -
- </returns>
- <remarks>
- -
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC stock Hooks_DoAllHooks()
- {
- // Get the preloaders.
- new
- precount = 0,
- preloads[8][E_PRE_HOOK];
- Hooks_GetPreloadLibraries(preloads, precount);
- // Main loop
- new
- name[32],
- idx;
- // Get the next hook type.
- while ((idx = AMX_GetPublicNamePrefix(idx, name, _A<@yH_>)))
- {
- // Collect all the hooks of this function, and rewrite the call code.
- Hooks_Collate(preloads, precount, name, idx);
- }
- Hooks_SortPublics();
- }
- /**--------------------------------------------------------------------------**\
- <summary></summary>
- <param name=""></param>
- <returns>
- -
- </returns>
- <remarks>
- -
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC stock Hooks_Collate(preloads[][E_PRE_HOOK], precount, name[32], &idx)
- {
- // This records the amount of space available in the nametable, currently
- // taken up by the names of hooks that we are about to destroy.
- new
- namelen = strlen(name);
- // For this function, note that:
- //
- // hook OnPlayerConnect(playerid)
- //
- // Compiles as:
- //
- // public @yH_OnPlayerConnect@XXX(playerid)
- //
- // Where "XXX" is some unique number (the exact value is irrelevant, it just
- // means that multiple hooks of the same function have different names).
- static
- sName[64],
- sHooks[128];
- // Isolate the common prefix part:
- //
- // @yH_OnPlayerConnect@042
- //
- // Becomes:
- //
- // @yH_OnPlayerConnect@
- //
- // The numbers at the end are irrelevant, now we can just search for hooks
- // of this exact callback.
- name{strfind(name, "@", false, 4) + 1} = '\0',
- // The above now becomes:
- //
- // OnPlayerConnect
- //
- // This also handles cases such as:
- //
- // @yH_OnPlayerEnterRaceCheckpoint@162
- //
- // Being invalid (too long), so instead converts the shortened:
- //
- // @yH_OnPlayerEnterRaceCP@162
- //
- // To:
- //
- // OnPlayerEnterRaceCheckpoint
- //
- // Thus expanding common name length reductions.
- strunpack(sName, name),
- Hooks_IsolateName(sName),
- Hooks_MakeLongName(sName);
- new
- // Get all the hooks of this type. They are stored alphabetically.
- hookCount = Hooks_GetAllHooks(name, sHooks, idx, namelen),
- writePtr = sHooks[0], // Header for the first found hook.
- nextPtr,
- pc, ph[4];
- // Get the preloads.
- Hooks_GetPreHooks(preloads, precount, sName, ph, pc),
- // Get where in the chain we are being inserted.
- Hooks_GetPointerRewrite(ph, pc, writePtr, nextPtr, sName, namelen);
- // Add ALS hooks to the end of the list.
- if ((sHooks[hookCount] = nextPtr)) ++hookCount;
- // Write the code.
- Hooks_GenerateCode(sName, sHooks, hookCount, writePtr, pc > 1);
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_GenerateCode</summary>
- <param name="name">Name of the function to generate.</param>
- <param name="hooks">All the functions to call.</param>
- <param name="count">Number of functions to call.</param>
- <param name="write">Where to write the new function's pointer.</param>
- <param name="hasprehooks">Needs to call other stuff first.</param>
- <returns>
- -
- </returns>
- <remarks>
- -
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC Hooks_GenerateCode(name[64], hooks[], count, write, bool:hasprehooks)
- {
- // We now have:
- //
- // 1) All the hooks of this function.
- // 2) The original function if it exists.
- // 3) Special ALS chained functions if they exists.
- //
- // This took huge chunks of complex code in the old version. Now not so
- // much! I don't know if this code is faster (I suspect it is), but it is
- // absolutely simpler!
- new
- size = Hooks_WriteFunction(hooks, count, Hooks_GetDefaultReturn(name));
- P:4("Hooks_GenerateCode %32s called: %6d %6d %08x %d", name[4], hasprehooks, size, hasprehooks ? (write - AMX_HEADER_COD) : (write - AMX_BASE_ADDRESS), CGen_GetCodeSpace());
- if (size)
- {
- //AMX_Write(write, 40);
- if (hasprehooks) AMX_Write(write, CGen_GetCodeSpace() + AMX_REAL_DATA);
- else AMX_Write(write, CGen_GetCodeSpace() - AMX_HEADER_COD);
- CGen_AddCodeSpace(size);
- }
- else
- {
- if (hasprehooks) AMX_Write(write, AMX_Read(hooks[0]) + (AMX_REAL_DATA + AMX_HEADER_COD));
- else AMX_Write(write, AMX_Read(hooks[0]));
- }
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_InvalidateName</summary>
- <param name="entry">The public function slot to destroy.</param>
- <returns>
- -
- </returns>
- <remarks>
- Basically, once we know a function has been included, wipe it from the AMX
- header.
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC Hooks_InvalidateName(entry)
- {
- AMX_Write(AMX_BASE_ADDRESS + AMX_Read(entry + 4), 0);
- }
- /*
- 88888888888 88 88 ad88
- 88 ,d "" 88 d8"
- 88 88 88 88
- 88aaaaa 88 88 8b,dPPYba, ,adPPYba, MM88MMM 88 ,adPPYba, 8b,dPPYba, 88 8b,dPPYba, MM88MMM ,adPPYba,
- 88""""" 88 88 88P' `"8a a8" "" 88 88 a8" "8a 88P' `"8a 88 88P' `"8a 88 a8" "8a
- 88 88 88 88 88 8b 88 88 8b d8 88 88 88 88 88 88 8b d8
- 88 "8a, ,a88 88 88 "8a, ,aa 88, 88 "8a, ,a8" 88 88 88 88 88 88 "8a, ,a8"
- 88 `"YbbdP'Y8 88 88 `"Ybbd8"' "Y888 88 `"YbbdP"' 88 88 88 88 88 88 `"YbbdP"'
- */
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_GetFunctionWritePoint</summary>
- <param name="name">The function to get the address pointer of.</param>
- <param name="write">Destination variable.</param>
- <returns>
- The address at which this function's pointer is stored in the AMX header, if
- the function exists of course.
- </returns>
- <remarks>
- -
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC stock Hooks_GetFunctionWritePoint(name[], &write)
- {
- AMX_GetPublicEntry(0, write, name, true);
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_GetDefaultReturn</summary>
- <param name="name">The function to get the default return of.</param>
- <returns>
- The default return for a callback, normally 1.
- </returns>
- <remarks>
- -
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC stock Hooks_GetDefaultReturn(name[64])
- {
- strins(name, "@RET", 0);
- Hooks_MakeShortName(name);
- new
- ptr;
- if (AMX_GetPublicEntry(0, ptr, name, true))
- {
- // A "RET_OnWhatever" function exists - rationalise the return.
- return CallFunction(AMX_Read(ptr)) ? 1 : 0;
- }
- return 1;
- }
- /*
- ,ad8888ba, 88
- d8"' `"8b 88
- d8' 88
- 88 ,adPPYba, ,adPPYb,88 ,adPPYba, ,adPPYb,d8 ,adPPYba, 8b,dPPYba,
- 88 a8" "8a a8" `Y88 a8P_____88 a8" `Y88 a8P_____88 88P' `"8a
- Y8, 8b d8 8b 88 8PP""""""" 8b 88 8PP""""""" 88 88
- Y8a. .a8P "8a, ,a8" "8a, ,d88 "8b, ,aa "8a, ,d88 "8b, ,aa 88 88
- `"Y8888Y"' `"YbbdP"' `"8bbdP"Y8 `"Ybbd8"' `"YbbdP"Y8 `"Ybbd8"' 88 88
- aa, ,88
- "Y8bbdP"
- */
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_WriteFunction</summary>
- <param name="pointers">The hooks to link together.</param>
- <param name="size">The number of functions in the array.</param>
- <param name="ret">The default return.</param>
- <param name="skipable">Can future hooks be ignored on -1?</param>
- <returns>
- The number of bytes written to memory.
- </returns>
- <remarks>
- Generate some new code, very nicely :D.
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC Hooks_WriteFunction(const pointers[], const size, const ret = 1, const skipable = true)
- {
- new
- bool:multiple = size != 1,
- base = (AMX_HEADER_COD - AMX_BASE_ADDRESS) + AMX_REAL_ADDRESS,
- ctx[AsmContext];
- // Make sure the underlying system doesn't change without us. Now supported
- // natively.
- CGen_UseCodeSpace(ctx);
-
- // Start of the function.
- @emit PROC // 1
-
- // Allocate space for our "ret" variable at "frm - 4".
- if (multiple) @emit PUSH.C ret // 3
-
- // Copy the stack to itself (MOVS).
- // Allocate space.
- @emit LOAD.S.alt 8 // 5
- @emit LCTRL 4 // 7
- @emit SUB // 8
- @emit SCTRL 4 // 10
- @emit XCHG // 11
-
- // The "MOVS" OpCode only takes a constant, not a variable, so we need to
- // generate self-modifying code (just to be UBER meta)! This code is
- // generated AFTER the file is loaded so we bypass the data segment checks
- // and can freely write wherever we want.
- @emit STOR.pri (CGen_GetCodeSpace() + (18 * 4) - (multiple ? 0 : 8)) // 13
-
- // Do the copying. "alt" is already "STK", load the "FRM" offset.
- @emit LCTRL 5 // 15
- @emit ADD.C 12 // 17
- // This is the instruction we want to modify...
- @emit MOVS 0 // 19 (- 1)
-
- // Push the (fake) number of parameters.
- @emit PUSH.C -4
- // Now loop over all our functions and insert "CALL" opcodes for them.
- if (multiple)
- {
- for (new i = 0; ; )
- {
- // Get the absolute offset from here.
- @emit CALL (AMX_Read(pointers[i]) + base) // 2
- if (skipable)
- {
- // =====================================
- // THIS SECTION IS CURRENTLY 10 CELLS.
- // =====================================
- // Note: Including the original call...
- //
- // if (func() < 0) break;
- // else ret = ret & func();
- //
- @emit ZERO.alt // 3
- @emit JSLESS.rel ((size - i) * (10 * 4) - (5 * 4)) // 5
- // =========================
- // JUMP OVER THIS SECTION.
- // =========================
- }
- @emit LOAD.S.alt -4 // 7
- if (ret) @emit AND // 8
- else @emit OR // 8
- // Loop and do the very first items last.
- if (++i == size) break;
- else @emit STOR.S.pri -4 // 10
- }
- if (skipable)
- {
- @emit JUMP.rel 4 // 10
- // This is the point the large "JSLESS" above goes to.
- // -1 = 0, -2 = 1
- @emit INVERT
- }
- }
- else if (skipable)
- {
- // Still need this code as they may hook a function that doesn't exist,
- // but we still need to correctly process -1 or -2.
- @emit CALL (AMX_Read(pointers[0]) + base)
- @emit ZERO.alt
- @emit JSGEQ.rel 4
- @emit INVERT
- }
- else
- {
- // Just replace the original (turns out, this takes no code). Basically
- // just discard everything we've written so far (reclaims the memory).
- return 0;
- }
-
- // This is the point the small "JUMP" above goes to.
- @emit MOVE.alt
- // Remove the whole stack then get the return value.
- @emit LCTRL 5
- @emit SCTRL 4
- //@emit LOAD.S.pri -4
- @emit MOVE.pri
-
- // Return.
- @emit RETN
-
- // Return the number of bytes written.
- return ctx[AsmContext_buffer_offset];
- }
- /*
- ad88888ba 88
- d8" "8b ,d ""
- Y8, 88
- `Y8aaaaa, ,adPPYba, 8b,dPPYba, MM88MMM 88 8b,dPPYba, ,adPPYb,d8
- `"""""8b, a8" "8a 88P' "Y8 88 88 88P' `"8a a8" `Y88
- `8b 8b d8 88 88 88 88 88 8b 88
- Y8a a8P "8a, ,a8" 88 88, 88 88 88 "8a, ,d88
- "Y88888P" `"YbbdP"' 88 "Y888 88 88 88 `"YbbdP"Y8
- aa, ,88
- "Y8bbdP"
- */
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_CompareNextCell</summary>
- <param name="addr0">The 1st address to read.</param>
- <param name="addr1">The 2nd address to read.</param>
- <returns>
- -1 - The first address is bigger.
- 0 - The addresses are the same
- 1 - The second address is bigger.
- </returns>
- <remarks>
- Reads two addresses, converts them to big endian, and compares them as four
- characters of a string at once.
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC Hooks_CompareNextCell(addr0, addr1)
- {
- new
- s0 = Cell_ReverseBytes(AMX_Read(addr0)),
- s1 = Cell_ReverseBytes(AMX_Read(addr1));
- // Propogate NULLs.
- if (!(s0 & 0xFF000000)) s0 = 0;
- else if (!(s0 & 0x00FF0000)) s0 &= 0xFF000000;
- else if (!(s0 & 0x0000FF00)) s0 &= 0xFFFF0000;
- else if (!(s0 & 0x000000FF)) s0 &= 0xFFFFFF00;
- if (!(s1 & 0xFF000000)) s1 = 0;
- else if (!(s1 & 0x00FF0000)) s1 &= 0xFF000000;
- else if (!(s1 & 0x0000FF00)) s1 &= 0xFFFF0000;
- else if (!(s1 & 0x000000FF)) s1 &= 0xFFFFFF00;
- // We need the numbers to be compared as big-endian. Now any trailing NULLs
- // don't matter at all.
- if (s1 > s0) return 1;
- else if (s1 < s0) return -1;
- else return 0;
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_ComparePublics</summary>
- <param name="idx0">The index of the 1st public.</param>
- <param name="idx1">The index of the 2nd public.</param>
- <returns>
- -
- </returns>
- <remarks>
- Compares two public function entries, and if need-be, swaps them over.
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC Hooks_ComparePublics(idx0, idx1)
- {
- idx0 = idx0 * 8 + AMX_HEADER_PUBLICS;
- idx1 = idx1 * 8 + AMX_HEADER_PUBLICS;
- new
- addr0 = AMX_BASE_ADDRESS + AMX_Read(idx0 + 4),
- addr1 = AMX_BASE_ADDRESS + AMX_Read(idx1 + 4);
- for ( ; ; )
- {
- switch (Hooks_CompareNextCell(addr0, addr1))
- {
- case -1:
- {
- // Swap them over.
- new
- tmpFunc = AMX_Read(idx0),
- tmpName = AMX_Read(idx0 + 4);
- AMX_Write(idx0, AMX_Read(idx1));
- AMX_Write(idx0 + 4, AMX_Read(idx1 + 4));
- AMX_Write(idx1, tmpFunc);
- AMX_Write(idx1 + 4, tmpName);
- return;
- }
- case 1:
- {
- // Already in order - good.
- return;
- }
- // case 0: // Loop.
- }
- addr0 += 4;
- addr1 += 4;
- }
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_SortPublics</summary>
- <returns>
- -
- </returns>
- <remarks>
- Goes through the whole of the public functions table and sorts them all in
- to alphabetical order. This is done as we move and rename some so we need
- to fix the virtual machine's binary search.
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC Hooks_SortPublics()
- {
- // Count the number of still active functions.
- new
- diff = Hooks_CountInvalidPublics() * 8,
- oldCount = (AMX_HEADER_NATIVES - AMX_HEADER_PUBLICS) / 8;
- // Now I need to SORT the functions, and I have honestly no idea how to do
- // that. Fortunately I don't actually need to move the strings themselves
- // around as they just sit nicely in the nametable; I only need to sort the
- // pointers.
- for (new i = oldCount - 1; i > 0; --i)
- {
- for (new j = 0; j != i; ++j)
- {
- // This neatly moves all the functions with blanked names to the
- // start of the public functions table (which will soon be moved).
- Hooks_ComparePublics(j, j + 1);
- }
- }
- // Move the start address UP to reduce the VM's search space.
- if (diff)
- {
- // Update stored values in y_amx so they reflect the new structure.
- AMX_Write(AMX_BASE_ADDRESS + 32, AMX_Read(AMX_BASE_ADDRESS + 32) + diff);
- AMX_HEADER_PUBLICS += diff;
- // I'd love to be able to update ZeeX's code as well, but I can't.
- // Issue pull request for this.
- ResetStaticAmxHeader();
- }
- // TODO: Inform the fixes2 plugin of the change. That stores indexes, not
- // addresses so it needs to update itself (somehow - I don't actually know
- // HOW it will do this...) Probably inform it first, store the addresses,
- // then inform it again to track down and replace those addresses.
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_CountInvalidPublics</summary>
- <returns>
- -
- </returns>
- <remarks>
- Counts the number of public functions that have had their names erased.
- </remarks>
- \**--------------------------------------------------------------------------**/
- _Y_HOOKS_STATIC Hooks_CountInvalidPublics()
- {
- new
- idx,
- buf,
- count;
- // Search for functions whose names start with nothing.
- while ((idx = AMX_GetPublicEntryPrefix(idx, buf, 0)))
- ++count;
- P:4("Hooks_CountInvalidPublics: Invalid = %d", count);
- return count;
- }
- /**--------------------------------------------------------------------------**\
- <summary>OnScriptInit</summary>
- <returns>
- -
- </returns>
- <remarks>
- Call the main hook run code, then advance the ALS chain.
- </remarks>
- \**--------------------------------------------------------------------------**/
- // New stuff.
- stock _Hooks_AddReplacement(const longName[], const shortName[])
- {
- // MAY need to strip spaces off the input strings, but I don't think so.
- if (YSI_g_sReplacePtr == MAX_HOOK_REPLACEMENTS)
- {
- P:E("Insufficient space in the replacements table.");
- return;
- }
- strcpy(YSI_g_sReplacements[YSI_g_sReplacePtr][E_HOOK_NAME_REPLACEMENT_SHORT], shortName, 16),
- strcpy(YSI_g_sReplacements[YSI_g_sReplacePtr][E_HOOK_NAME_REPLACEMENT_LONG] , longName , 16),
- YSI_g_sReplacements[YSI_g_sReplacePtr][E_HOOK_NAME_REPLACEMENT_MIN] = strlen(shortName),
- YSI_g_sReplacements[YSI_g_sReplacePtr][E_HOOK_NAME_REPLACEMENT_MAX] = strlen(longName),
- YSI_g_sReplacementsLongOrder[YSI_g_sReplacePtr] = YSI_g_sReplacePtr,
- YSI_g_sReplacementsShortOrder[YSI_g_sReplacePtr] = YSI_g_sReplacePtr,
- ++YSI_g_sReplacePtr;
- }
- /**--------------------------------------------------------------------------**\
- <summary>Hooks_SortReplacements</summary>
- <returns>
- -
- </returns>
- <remarks>
- Once all the replacement strings have been found, sort them by the length of
- the short versions of the strings. This is so that the longest (and special
- case, e.g. "DynamicCP"-> "DynamicCP") replacements are always done first.
- </remarks>
- \**--------------------------------------------------------------------------**/
- static stock Hooks_SortReplacements()
- {
- new
- idx0,
- idx1,
- temp;
- for (new i = YSI_g_sReplacePtr - 1; i > 0; --i)
- {
- for (new j = 0; j != i; ++j)
- {
- // Sort the strings in order of their short replacement.
- idx0 = YSI_g_sReplacementsShortOrder[j],
- idx1 = YSI_g_sReplacementsShortOrder[j + 1];
- if (YSI_g_sReplacements[idx0][E_HOOK_NAME_REPLACEMENT_MIN] < YSI_g_sReplacements[idx1][E_HOOK_NAME_REPLACEMENT_MIN])
- {
- temp = YSI_g_sReplacementsShortOrder[j],
- YSI_g_sReplacementsShortOrder[j] = YSI_g_sReplacementsShortOrder[j + 1],
- YSI_g_sReplacementsShortOrder[j + 1] = temp;
- }
- // Sort the strings in order of their long replacement.
- idx0 = YSI_g_sReplacementsLongOrder[j],
- idx1 = YSI_g_sReplacementsLongOrder[j + 1];
- if (YSI_g_sReplacements[idx0][E_HOOK_NAME_REPLACEMENT_MAX] < YSI_g_sReplacements[idx1][E_HOOK_NAME_REPLACEMENT_MAX])
- {
- temp = YSI_g_sReplacementsLongOrder[j],
- YSI_g_sReplacementsLongOrder[j] = YSI_g_sReplacementsLongOrder[j + 1],
- YSI_g_sReplacementsLongOrder[j + 1] = temp;
- }
- }
- }
- P:C(for (new i = 0 ; i != YSI_g_sReplacePtr; ++i) P:0("Hook Replacement: %d, %d, %s, %s", YSI_g_sReplacements[i][E_HOOK_NAME_REPLACEMENT_MIN], YSI_g_sReplacements[i][E_HOOK_NAME_REPLACEMENT_MAX], YSI_g_sReplacements[i][E_HOOK_NAME_REPLACEMENT_SHORT], YSI_g_sReplacements[i][E_HOOK_NAME_REPLACEMENT_LONG]););
- }
- /**--------------------------------------------------------------------------**\
- <summary>OnScriptInit</summary>
- <returns>
- -
- </returns>
- <remarks>
- Call the main hook run code, then advance the ALS chain.
- </remarks>
- \**--------------------------------------------------------------------------**/
- public OnScriptInit()
- {
- P:1("Hooks_OnScriptInit called");
- state _ALS : _ALS_go;
- // Get the replacements.
- new
- idx,
- entry;
- // Loop over the redefinition functions and call them to have them call the
- // "_Hooks_AddReplacement" function above. If we were being REALLY clever,
- // these functions could be removed from the public functions table
- // afterwards (there is already code in y_hooks for this) to reduce is size.
- while ((idx = AMX_GetPublicEntryPrefix(idx, entry, _A<@_yH>)))
- {
- // From "amx\dynamic_call.inc" - check it is included in "y_hooks.inc".
- CallFunction(AMX_Read(entry));
- Hooks_InvalidateName(entry);
- }
- Hooks_SortReplacements();
- Hooks_DoAllHooks();
- P:1("Hooks_OnScriptInit chain");
- // Dump the generated callbacks for debugging.
- //DisasmDump("YSI_TEST.asm");
- HookChain_OnScriptInit();
- P:1("Hooks_OnScriptInit end");
- return 1;
- }
- HOOK_FORWARD:HookChain_OnScriptInit();
- #if defined _ALS_OnScriptInit
- #undef OnScriptInit
- #else
- #define _ALS_OnScriptInit
- #endif
- #define OnScriptInit(%0) HookChain_OnScriptInit(%0) <_ALS : _ALS_go>
|