Welcome to Open Carnage

A resource for Halo Custom Edition and MCC modding, with unique means of rewarding content creation and support. Have a wander to see why we're worth the time! - EST. 2012


  • Content count

  • Joined

  • Last visited

  • Raffle Tickets


1 Follower

About PiRate

Extra Information

  • Gender
  1. I had done a silly thing many years back on Custom Edition when I was familiarizing myself with scripting. I decided to try my hand at it again. Also vaguely related:
  2. Found out the reason, see bottom of post. Does Chimera cache the controls folder? I made some updates to a plugin I placed in there and whenever I run Halo with the Chimera strings.dll, it loads the stale version of that plugin that first loaded with the new version of Chimera. Even when I delete my plugin from the controls folder, the plugin still loads :spooky:. Using vanilla Strings.dll fixes this behaviour; the new version of the plugin is used or, if deleted, it is never loaded at all. I understand my plugin is not likely to be compatible with Chimera, but I have been making a number of edits to my plugin to attempt compatibility. EDIT Nevermind, found out I still had the mods folder present with a stale copy.
  3. Sound warning. For a brighter but less custom experience:
  4. Try changing the resolution down to the lowest and then back to your original resolution. I notice that when I use windowed mode the game will cap to my refresh rate despite the setting reading otherwise and it takes some messing with the video setting to make it work right again. I almost exclusively use fullscreen for this reason.
  5. For the last few weeks I've been messing around with Halo's scripting engine to the end of integrating new functions into it. Below is a working snippet of code (C++). long chat_say(std::optional<std::string_view> text) { sentutil::chat::send_chat(text.value_or("Hello, World!").data()); return 314l; } bool Load() { // Returns types and arguments are deduced by install_script_function. // The parsing and evaluation coroutine interfaces are instantiated as necessary. // Furthermore, install_script_function is not limited to function pointers, // as one can pass it a std::function (and a std::function-wrapped lambda). return sentutil::script::install_script_function<"chat_say">( chat_say, "sends chat over the global channel", // help text (usage) "[<string: message>]" // help text (parameters) ); } The installed function can be accessed, queried through help, and auto-completed like any other in Halo, and this function can even be used as follows (with devmode): begin (sleep (chat_say "ping")) (chat_say "pong") The first call to chat_say returns 314 to sleep, which causes the thread of evaluation to immediately commit ping to all chat and roughly 10 seconds later (314 ticks to be more precise) commits pong to all chat. This is not exactly a motivating example and I have no need of a framework this robust, but I suppose I learned quite a bit along the way.
  6. I am unsure of the progress on this script but I'll share some short research into it in case someone with the time and familiarity with lua can make use of it. cheat_jetpack cannot be used as it applies globally to all player-controlled units. Furthermore, marking a player as invulnerable would have undesired effects. A hack to Halo's code must be used, so that players can be selectively marked as invulnerably to falling damage. The relevant section when Halo's loads the global cheat_jetpack can be found below: CPU Disasm Address Hex dump Command Comments 0057332F |> \A0 ???????? MOV AL,BYTE PTR DS:[globals::cheat_jetpack] 00573334 |. 83CB FF OR EBX,FFFFFFFF 00573337 |. 84C0 TEST AL,AL 00573339 |. 74 0C JZ SHORT 00573347 0057333B |. 399E 1802000 CMP DWORD PTR DS:[ESI+218],EBX ; cmp unit->controlling_player, -1 00573341 |. 0F85 2D01000 JNE 00573474 ; take jump to skip falling damage check The reason the controlling player is tested against is that cheat_jetpack applies only to player-controlled units. EAX is clobbered without being read from regardless of the path through this snippet, which gives us a bit of freedom. I opt to repurpose the object flags (a DWORD) at offset 0x10 from the player's object address (specifically the most-significant bit, but I believe the 4 most significant bits go unused). CPU Disasm Address Hex dump Command Comments 0057332F \83CB FF OR EBX,FFFFFFFF 00573332 F746 10 00000080 TEST DWORD PTR DS:[ESI+10],80000000 ; unit->object_flags & 0x80000000 (most-significant bit) 00573339 |. 74 0C JZ SHORT 00573347 0057333B |. 399E 18020000 CMP DWORD PTR DS:[ESI+218],EBX ; cmp unit->controlling_player, -1 00573341 |. 0F85 2D010000 JNE 00573474 ; take jump to skip falling damage check This effectively disables cheat_jetpack, but replaces it with the ability to re-enable it on specific units by setting the most-significant bit in the object flags. The signature A0????????83CBFF84C0740C399E18020000 is sufficient to search for this snippet. This script should be a start if someone with the time wants to complete this request. I have not done ANY testing for it outside of manually writing the patch and setting the repurposed flag through OllyDBG, and I am not familiar with lua nor its integration into SAPP, so quite a bit of additional work and verification may be needed. Also note that if the player dies, they will no longer be marked as invulnerable to fall damage. This might be a blessing for this request but it is inconsistent with the original behavior of cheat_jetpack. If someone wishes to complete the script to the satisfaction of the requestor, I do not mind if they wish to take full credit for it. My apologies if my naming convention clashes with the standard convention for lua/SAPP scripting. Edit: Addresses above are from the 1.10 haloded.exe included with SAPP, but the signature works on the Custom Edition server (in addition to the 1.10 PC/CE clients). Edit2: Script linked isnt functional, will try to fix now that I have time to and SAPP set up Edit3: Script loads and unloads, properly patches, and the set_player_jetpack function works as expected now The script link above has been changed to a functional version (loads, unloads, properly patches). That being said, everything from this post still applies; the script does not complete the request, but if someone has the time and ability, they can use the set_player_jetpack function within to finish the request. The set_player_jetpack function was tested via the lua_call command. Link to the update script in this post for good measure: pastebin.
  7. I am unsure how to explain your experience with sleep_until's second parameter. Preliminary testing indicates that omitting the second parameter and setting it to 30 yield the same result. Additionally, evaluating (sleep_until false 1 90) produces 90 evaluations of the condition in 3 seconds, as expected. Investigation into the code also corroborates this. CPU Disasm Address Command Comments 004898F3 |. MOV EAX,DWORD PTR SS:[LOCAL.2] 004898F7 |. MOV BYTE PTR DS:[EAX],0 ; condition value, default 0 (false) 004898FA |. MOV EAX,DWORD PTR DS:[Globals::lpGameTimeGlobals] 004898FF |. MOV EAX,DWORD PTR DS:[EAX+0C] 00489902 |. MOV DWORD PTR DS:[EDX],EAX ; sets call_time = lpGameTimeGlobals->game_ticks 00489904 |. MOV EAX,DWORD PTR SS:[LOCAL.3] 00489908 |. MOV WORD PTR DS:[EDI],0 ; bool that is set when 2nd and 3rd args are evaluated/defaulted 0048990D |. MOV WORD PTR DS:[EBX],1E ; period, default 0x1E (30 decimal) ticks 00489912 |. MOV DWORD PTR DS:[EAX],-1 ; timeout, default -1 This snippet sets up the period to the value to 30, later taking on the value of the second argument when and if it is evaluated, and remains unchanged if the second argument is omitted. As to how that argument is used: CPU Disasm Address Command Comments 004899BF |. MOV EAX,DWORD PTR SS:[LOCAL.1] ; EAX <- &period 004899C3 |. MOV AX,WORD PTR DS:[EAX] ; AX <- period 004899C6 |. CMP AX,1 ; AX <- max(period, (short)1) 004899CA |. JGE SHORT 004899D3 ; ^ ^ 004899CC |. MOV EAX,1 ; ^ ^ 004899D1 |. JMP SHORT 004899D6 ; ^ ^ 004899D3 |> MOVSX EAX,AX ; (long)max(period, (short)1) 004899D6 |> MOV ECX,DWORD PTR DS:[Globals::lpGameTimeGlobals] 004899DC |. MOV EDX,DWORD PTR DS:[ECX+0C] ; lpGameTimeGlobals->game_ticks 004899DF |. MOV ECX,DWORD PTR SS:[LOCAL.3] 004899E3 |. ADD EAX,EDX ; EAX <- game_ticks + max(period, (short)1) 004899E5 |. MOV DWORD PTR SS:[EBP+8],EAX ; lpThread->wake_tick = game_ticks + max(period, (short)1) sleep_until's mechanism is implemented by setting a value that indicates to the script engine on what tick the script thread can be updated again, as shown here in the thread update function: Address Command Comments 0048A410 |> /MOV ECX,DWORD PTR DS:[ESI+8] ; ECX <- lpScriptThread->wake_tick 0048A413 |. |TEST ECX,ECX 0048A415 |. |JL 0048A4FC ; stops thread update if lpThread->wake_tick < 0 0048A41B |. |MOV EAX,DWORD PTR DS:[Globals::lpGameTimeGlobals] 0048A420 |. |CMP BYTE PTR DS:[EAX],0 0048A423 |. |JE SHORT 0048A43C ; skips time check if game is not ready 0048A425 |. |MOV DL,BYTE PTR DS:[EAX+1] 0048A428 |. |TEST DL,DL 0048A42A |. |JNZ SHORT 0048A433 ; skips time check if game is not running 0048A42C |. |MOV DL,BYTE PTR DS:[EAX+2] 0048A42F |. |TEST DL,DL 0048A431 |. |JZ SHORT 0048A43C ; skips time check if game is paused 0048A433 |> |CMP ECX,DWORD PTR DS:[EAX+0C] ; cmp lpThread->wake_tick, lpGameTimeGlobals->game_ticks 0048A436 |. |JG 0048A4FC ; stops thread update if lpThread->wake_tick > lpGameTimeGlobals->game_ticks Then, upon waking, the thread will check the condition again. If the condition evaluates to false, the thread is set to sleep another period ticks as indicated in the second snippet. Otherwise, if the condition evaluates to true, the script thread returns from the sleep_until call.
  8. While doing some reversing of Halo to fully integrate client functions into the scripting engine, I came across a curiosity regarding sleep_until. According to the references I can locate, sleep_until can accept two arguments - one required and one optional. Even the internal reference generated by script_doc affirms this: However, sleep_until has 3 parameters, and technically accepts even more arguments. Note: Any addresses below are for the 1.10 PC client, because most of my notes are on the PC client and have yet to be ported to the CE client. However, what I mention below holds true for the CE client. I am posting this under the CE section because I assume more people would be using Halo script in CE rather than PC. Below is the parsing function for sleep_until, in Cish (i.e. no guarantees it is legal C), translated from the assembly: // Note: I use BOOL below to convey meaning and it does not necessarily // reflect the width of Halo bools, depending on the compiler. // Returns TRUE if the script node referred to by node_id can evaluate // to the value type specified, and FALSE otherwise. // Has side-effects. BOOL parse_script_node_expected(identity_type node_id, enum_short type); BOOL parse_sleep_until(short function_index, identity_type script_node_id) { script_node *primary_node = get_script_node(script_node_id); script_node *function_node = get_script_node(primary_node->value.id); // ------------------------------ // condition argument, required // type: boolean (enum value 5) identity_type condition_node_id = function_node->next_script_node_id; if (condition_node_id == k_invalid_identity || !parse_script_node_expected(condition_node_id, value_type_boolean)) return FALSE; // condition argument is required // ------------------------------ // period argument, optional // type: short (enum value 7) script_node *condition_node = get_script_node(condition_node_id); identity_type period_node_id = condition_node->next_script_node_id; if (period_node_id == k_invalid_identity) return TRUE; // period argument is optional if (!parse_script_node_expected(period_node_id, value_type_short)) return FALSE; // period argument supplied but could not parse to short // ------------------------------ // timeout argument, optional // type: long (enum value 8) script_node *period_node = get_script_node(period_node_id); identity_type timeout_node_id = period_node->next_script_node_id; if (timeout_node_id != k_invalid_identity && !parse_script_node_expected(timeout_node_id, value_type_long)) return FALSE; // timeout argument supplied but could not parse to long return TRUE; } You can probably guess what this third argument and its type is. To confirm its meaning, here is a snippet from sleep_until's evaluation function: CPU Disasm Address Hex dump Command Comments 00489980 |. 8B00 MOV EAX,DWORD PTR DS:[EAX] ; EAX <- timeout (3rd argument) 00489982 |. 83F8 FF CMP EAX,-1 00489985 |. 74 0E JE SHORT 00489995 ; never timeout! 00489987 |. 8B12 MOV EDX,DWORD PTR DS:[EDX] ; EDX <- time of function call in ticks 00489989 |. 03D0 ADD EDX,EAX ; EDX <- timeout + function_call_time 0048998B |. A1 6C1D6F00 MOV EAX,DWORD PTR DS:[Globals::lpGameTimeGlobals] 00489990 |. 3950 0C CMP DWORD PTR DS:[EAX+0C],EDX ; lpGameTimeGlobals->game_ticks 00489993 |. 7D 77 JGE SHORT 00489A0C ; jumps to exit sleep_until context if game_ticks >= (timeout + function_call_time) The third argument is a timeout, in ticks relative to the time when sleep_until would be called! An updated description for the function should resemble: However, because Halo does not verify the number of arguments does not exceed 3, nor their type, Halo will gladly parse, compile, and evaluate the following: (sleep_until false 15 3600 "foo" 3.14159265359) Fake edit for those that read this post: you can paste text into the client console by pressing F6. I have no idea if this holds for Mac users, but I found this information out while completing documentation of the console line buffer structure. I am also unsure if this works while using HAC2. Real edit: Turns out the timeout is not in absolute ticks, will slowly edit my post to reflect this (done).