@@ -212,3 +212,212 @@ native deleteproperty(id = 0, const name[] = "", value = cellmin);
212212 */
213213native bool: existproperty (id = 0 , const name[] = " " , value = cellmin);
214214
215+ /* */ native __packpubvar (dest[], const source[], maxlength = sizeof (dest)) = strpack;
216+
217+ static stock __findpubvar (const __name[])
218+ {
219+ // Scans the AMX header to find a public variable and returns the address of the entry, or `0`
220+ // if there isn't one. Can't use `static` here because we rely on the string being all `\0`s.
221+ new __packed[64 char ], __offset = 0 ;
222+ const __cellbytes = cellbits / charbits;
223+ // In 32-bit scripts this is `64`, but in 64-bit scripts it is only `96`. Part of the header
224+ // entry is always 32-bit, regardless of the cell bit width. We need it in bytes.
225+ const __defsize = __cellbytes + 4 ;
226+ // Convert the normal input string to a C string, via a packed string. Pack it, then reverse
227+ // every individual cell to get the correct final memory layout.
228+ __packpubvar (__packed, __name);
229+ // All cells have trailing nulls, so this can only be true when all bytes are non-zero because
230+ // the top byte is non-zero.
231+ while ((__packed[__offset] = swapchars (__packed[__offset])) > (cellmax >>> (charbits - 1 )))
232+ {
233+ ++ __offset;
234+ }
235+ new __start, __end;
236+ // Now we start doing assembly magic to dig through the AMX header itself.
237+ // Get the offset from the start of the file to the start of the data segment (DAT).
238+ # emit LCTRL 1
239+ // Invert it to get the start of the file relative to DAT.
240+ # emit NEG
241+ // Store this offset. We'll need it a lot.
242+ # emit MOVE .alt
243+ # emit STOR .S .alt __offset
244+ // Get the offset to the pubvars pointer.
245+ # emit ADD .C 44 // Doesn't change with `cellbits`.
246+ # emit STOR .S .pri __start
247+ # emit LREF .S .pri __start
248+ // Make this pointer relative to `DAT`.
249+ # emit ADD
250+ // Point to the name pointer in the entry to begin with.
251+ # emit ADD .C __cellbytes
252+ # emit STOR .S .pri __start
253+ // Get the offset to the tags pointer.
254+ # emit MOVE .pri
255+ # emit ADD .C 48 // Doesn't change with `cellbits`.
256+ # emit STOR .S .pri __end
257+ # emit LREF .S .pri __end
258+ # emit ADD
259+ # emit ADD .C __cellbytes
260+ # emit STOR .S .pri __end
261+ // Loop through the
262+ new __s0, __s1, __s2;
263+ // This could be made a binary search, but it would complicate the code a lot, and isn't worth
264+ // it given how rare public variables are. In fact, amx_assembly doesn't even bother with
265+ // comparing four bytes at once and just loads and compares single characters instead, which is
266+ // an option... It would simplify the code even more.
267+ while (__start != __end)
268+ {
269+ // Load the pointer.
270+ # emit LREF .S .pri __start
271+ // Add the `DAT` offset.
272+ # emit LOAD .S .alt __offset
273+ # emit ADD
274+ // Save the pointer.
275+ # emit STOR .S .pri __s0
276+ __s1 = 0 ;
277+ for ( ; ; )
278+ {
279+ # emit LREF .S .pri __s0
280+ # emit STOR .S .pri __s2
281+ if (__packed[__s1] == __s2)
282+ {
283+ __s0 += __cellbytes;
284+ ++ __s1;
285+ continue ;
286+ }
287+ // They are different, but this could be because of junk data loaded from after the end
288+ // of the pubvar name in the AMX. We need to determine whether the `NULL` comes first.
289+ __s1 = __packed[__s1];
290+ while (__s1 && (__s1 & 0x FF ) == (__s2 & 0x FF ))
291+ {
292+ __s1 > >>= 8 ;
293+ __s2 > >>= 8 ;
294+ }
295+ if ((__s1 & 0x FF ) || (__s2 & 0x FF ))
296+ {
297+ break ;
298+ }
299+ // Enough matches.
300+ return __start - __cellbytes;
301+ }
302+ // This wasn't a match. Try the next one.
303+ __start += __defsize;
304+ }
305+ return 0 ;
306+ }
307+
308+ /* *
309+ * <library>core</library>
310+ * <summary>Gets a specific public variable from the current script.</summary>
311+ * <param name="name">The public variable's name.</param>
312+ * <seealso name="setpubvar"/>
313+ * <seealso name="existpubvar"/>
314+ * <seealso name="numpubvars"/>
315+ * <returns>Naught if the variable doesn't exist (use <c>existpubvar</c> first).</returns>
316+ */
317+ /*
318+ native getpubvar(const name[]);
319+ */
320+ stock getpubvar (const __name[])
321+ {
322+ new __entry = __findpubvar (__name);
323+ if (__entry)
324+ {
325+ // Get the pointer to the data.
326+ # emit LREF .S .pri __entry
327+ # emit STOR .S .pri __entry
328+ // Get the old data (fortunately the pointer is relative to `DAT`).
329+ # emit LREF .S .pri __entry
330+ // Return the value in `pri`.
331+ # emit STACK 4
332+ # emit RETN
333+ }
334+ return 0 ;
335+ }
336+
337+ /* *
338+ * <library>core</library>
339+ * <summary>Sets a specific public variable in the current script.</summary>
340+ * <param name="name">The public variable's name.</param>
341+ * <param name="value">The value to store in the public variable.</param>
342+ * <seealso name="getpubvar"/>
343+ * <seealso name="existpubvar"/>
344+ * <seealso name="numpubvars"/>
345+ * <returns>The previous value of the variable.</returns>
346+ */
347+ /*
348+ native setpubvar(const name[], value);
349+ */
350+ stock setpubvar (const __name[], __value)
351+ {
352+ new __entry = __findpubvar (__name);
353+ if (__entry)
354+ {
355+ // Get the pointer to the data.
356+ # emit LREF .S .pri __entry
357+ # emit STOR .S .pri __entry
358+ // Get the old data (fortunately the pointer is relative to `DAT`).
359+ # emit LREF .S .pri __entry
360+ // Store the new data.
361+ # emit LOAD .S .alt __value
362+ # emit SREF .S .alt __entry
363+ // Return the value in `pri`.
364+ # emit STACK 4
365+ # emit RETN
366+ }
367+ return 0 ;
368+ }
369+
370+ /* *
371+ * <library>core</library>
372+ * <summary>Checks if a specific public variable exists in the current script.</summary>
373+ * <param name="name">The public variable's name.</param>
374+ * <seealso name="getpubvar"/>
375+ * <seealso name="setpubvar"/>
376+ * <seealso name="numpubvars"/>
377+ * <returns>Does the variable exist?</returns>
378+ */
379+ /*
380+ native bool:existpubvar(const name[]);
381+ */
382+ stock bool: existpubvar (const __name[])
383+ {
384+ return __findpubvar (__name) != 0 ;
385+ }
386+
387+ /* *
388+ * <library>core</library>
389+ * <summary>Counts how many public variables there are in the script.</summary>
390+ * <seealso name="getpubvar"/>
391+ * <seealso name="setpubvar"/>
392+ * <seealso name="existpubvar"/>
393+ * <returns>The number of public variables declared.</returns>
394+ */
395+ /*
396+ native numpubvars();
397+ */
398+ stock numpubvars ()
399+ {
400+ // In 32-bit scripts this is `64`, but in 64-bit scripts it is only `96`. Part of the header
401+ // entry is always 32-bit, regardless of the cell bit width. We need it in bytes.
402+ const __defsize = cellbits / charbits + 4 ;
403+ new __start, __end;
404+ // Now we start doing assembly magic to dig through the AMX header itself.
405+ // Get the offset from the start of the file to the start of the data segment (DAT).
406+ # emit LCTRL 1
407+ // Invert it to get the start of the file relative to DAT.
408+ # emit NEG
409+ // Get the offset to the pubvars pointer.
410+ # emit ADD .C 44 // Doesn't change with `cellbits`.
411+ # emit STOR .S .pri __start
412+ # emit LREF .S .pri __start
413+ # emit STOR .S .pri __start
414+ // Get the offset to the tags pointer.
415+ # emit LCTRL 1
416+ # emit NEG
417+ # emit ADD .C 48 // Doesn't change with `cellbits`.
418+ # emit STOR .S .pri __end
419+ # emit LREF .S .pri __end
420+ # emit STOR .S .pri __end
421+ return (__end - __start) / __defsize;
422+ }
423+
0 commit comments