![]() | ||
How To Script (very long)
| ||
| Notices |
![]() |
| | LinkBack | Thread Tools | Display Modes |
| | #1 (permalink) | ||||||
| Member
| How To Script (very long) A technical guide to Halo 2 Scripts A tutorial by Soldier of Light With help by Shalted, The Tycko Man, Doom, and Killing for Fun. Table of Contents: a) section breakdown b) syntax explanation c) global dissection – real example d) decompiling e) compiling f) credits A) Section Breakdown Halo 2 scripts are a very complex system. They exist solely in the scnr tag, and are able to run in single and multiplayer maps. They are the single most powerful tool in the game, able to edit almost any property on the fly. So without further ado, let’s get on into how they are structured. There are 4 main reflexives involved with the scripts (in order of appearance): String table Script names Globals Syntax data Brief descriptions of each of them: The string table lists every single string used in the scripts. Useless for the most part, unless you’re doing quick editing or decompiling. The engine uses numbers instead of strings to determine what command it’s running. The script names lists all of the names, types, and return values of the scripts in the map. It contains the data for the script header: “(script type name”. It also tells the game where (in the syntax data) to find the commands for the script. The globals reflexive lists all of the global variables for the map, along with their type. It tells the game where to find the initialization expression, which gives it a default value. Together, these create a global expression: “(global type name value)”. The syntax data is the most complex part of the scripting process. For now, just know that this is where most of the work occurs. B) Syntax Explanation The first three reflexives have very simple structures, and I will explain them as I go. But first I must explain the syntax data. The syntax data is a reflexive with 20 byte chunks. It can have padding anywhere inside of it, including the beginning, middle, and end, all seen in halo maps. Padding is denoted by 0000 followed by 18 bytes of BA. The structure of this reflexive is as follows: struct syntax { short expression index short identity short value type short expression type int sibling pointer int script table offset int value } A quick explanation of each value: Expression index – Each chunk of syntax data has an expression index. This value tells the game its location in the syntax data. The first chunk of syntax data will always have expression index 73E3. Usually there are 4 or 5 padding chunks before any actual data, so the first expression index you will actually see will usually be 77E3 or 78E3. Padding chunks have 0000 as their expression index to indicate that they serve no purpose. The next three values occur in reverse generality. The most general is last, and the most specific is first. I will list them in the order they appear. Identity – A value used by the engine to determine a specific command or object type. Every function in the game has a unique identity assigned to it, and it is the same between maps. For instance, “sleep” is command number 19. There is a list included with all commands, identities, and return types. Value Type – This value determines what type of expression we’re looking at. For example, if this value is a 2, then we are looking at a function. If it is a 7, we’re looking at a short. If we’re looking at a 44, then it’s a difficulty.There is also a list of these values included. Expression Type – This value tells the game how to use the expression. There are very few values, the main ones being 8 and 9. 8 means that it is a syntax chunk “(“, and 9 means it is a statement chunk. Sibling Pointer – This value points to the next expression inside a set of parenthesis. It consists of 2 shorts which work much the same way as dependency pointers. The second value is the expression index of the chunk it is pointing to, and the first value is the difference between that and 73E3 (the chunk number). Script table offset – Very simple. This int tells the game how many bytes into the string table the specific string for this expression is. This value is not necessary, and will not affect the way the script runs. Value – This value is pretty simple. When the expression type of a chunk is 8, this value will point to the first child inside the parenthesis. Otherwise, this value changes based on the value type of the chunk. For example, if the chunk is a real number, it will be a 4 byte float. If it’s a reference to an object, it will be a chunk number or dependency. C) Global Dissection – real example Alright, now that we’re through the structure of the syntax data, how do we use it? Let’s start with something simpler: a global. It has the same basic structure as a script command, but it’s simpler. Let’s start in the globals reflexive. We’ll use elongation for its simplicity. This reflexive contains a 32 byte name, a 4 byte type, and a 4 byte expression pointer. Let’s dissect this particular one: Name: k_crate_spacing. Type: 0700 0000 = short. Exp. Pointer = 0400 77E3. Great, so now what? Now we follow the expression index into the syntax data. We look at chunk number 4 (counting from 0), whose expression index is 77E3. Fortunately for us, bungie separates different scripts and globals using some padding, so it’s not too hard to find by hand. This is how this particular expression looks in hex: 77E3 0700 0700 0900 FFFF FFFF 2100 0000 9001 FFFF Now let’s dissect this. The 77E3 is the expression index, which was used to find the expression. It has type 9, which means it’s an expression. It has value type 7, which means it’s a short, and its identity is also 7, which means it’s declared here, not by another function. We can skip the pointer, since it has no siblings. Next we come to the script table pointer, but since this is a global with a short value, we ignore it (not sure why there’s anything there). Then we come to its value, 9001 (hex) = 400 (dec). This is the time between crate drops in elongation. Yay! Now you see how I did my first script mod. The simple 2 byte change was changing the 9001 to 3C00 (100). Not so simple anymore, huh. D) Decompiling Alright, you made it through the easy stuff. Now comes the part that’s sooo tedious it has to be automated. So now let’s start decompiling a script. For this example I will be using the crate_eraser script from elongation. We start in the script names reflexive. The structure of this is remarkably similar to that of the globals reflexive, with one difference. It still has the same 32 byte name, followed by a 2 byte type, a 2 byte return type, and a 4 byte expression pointer. In this reflexive, the return type is the same as the type from globals. The type here is slightly different. All of the types for a script are as follows: 0 = startup 1 = dormant (called using wake) 2 = continuous 3 = static (called normally, using (command), and can have a return type) 4 = stub (unsure of its use) 5 = command_script (used to command ai players) For this script, here is the information: Type: 0200 = continuous Return type: 0400 = void Exp. Pointer = 8500 F8E3 Which becomes: “(script continuous crate_eraser” *note* the return type is not present in the header. Now we follow the root expression index of the script names reflexive into the syntax data. Here’s where the differences begin. We no longer have such simple expressions. When dealing with commands in the syntax data, it’s important to note that before every function name, there is an expression of type 8, which serves to specify the bounds of the expression. Basically, the expression will continue until it encounters its next sibling. So naturally, when we follow the root expression index, we find a type 8 expression. However, this is different from most in that it does not have any function following those contained within it. This is the begin expression, and contains all of the expressions in a script. So without further ado, let’s delve in. The first expression in this script looks like: F8E3 0000 0400 0800 FFFF FFFF 2006 0000 8600 F9E3 Now we dissect. Notice it’s type 8, its value type is void, and its identity is 0. These numbers tell us that it’s the syntax statement for the begin function. You’ll notice that this chunk’s value type is void, but there are still numbers in its value position. Since this is a type 8 function, these are a pointer to its child commands. It has no siblings, so its sibling pointer is FFFF FFFF, and this expression will be closed when the main script is closed. F9E3 0000 0200 0900 7500 E8E3 0000 0000 0000 0000 Notice that the previous statement had a child pointer of F9E3 and this begins with F9E3. This is how you navigate through the syntax data: by following child and sibling pointers. Now let’s dissect this piece of data. Here we have a type of 9, meaning it’s a statement. A value type of 2 means it’s a function, and an identity of 0 means it’s begin. Note that you could also follow the string offset to find out that it’s begin, but the engine actually uses the identity value. So our script stands at: (script continuous crate_eraser (begin Since this expression is a statement, it can’t have any children, so we follow its sibling pointer to our next command. E8E3 3400 0400 0800 7D00 F0E3 5606 0000 7600 E9E3 Let’s have a look at this. Right off the bat we see that it’s type 8, which means syntax expression. Value type 4 means its value is void. And identity 3400 means that its child command has identity 3400. Now we get into some more complicated things. You see that it has both a child and sibling pointer. We’re going to follow the child pointer first, and come back to the sibling later. I’m gonna start going faster now. E9E3 3400 0200 0900 7700 EAE3 0201 0000 0000 0000 We follow its child pointer to this expression. It’s a function statement, with function identity 3400. Since I don’t know what is offhand, I follow its string offset to find that it’s “object_destroy”. So that means that this line begins with: (object_destroy But what comes after? To find out, we follow its sibling pointer. Note that its sibling is inside the same set of parenthesis. EAE3 2500 3200 0800 FFFF FFFF 6606 0000 7800 EBE3 Here we have another type 8, which is a syntax expression. That means we have another function coming up. But what’s this… the value type isn’t 4? It doesn’t give a void result? No, this time the function is the argument of another, and it actually returns something. 3200 = scenery_name. And 2500 is the identity of the function we will be using. But since there will be no sibling for this function, its sibling pointer is nulled. That’s alright, I’ll explain it when we reach the end. Let’s look at what we have so far: (object_destroy ( It’s coming along. So let’s go and follow the child pointer. EBE3 2500 0200 0900 7900 ECE3 1101 0000 0000 0000 Another type 9 statement, another command, this time identity 2500 = “list_get”. Add that string in, and we have: (object_destroy (list_get Follow the sibling pointer to the next one. ECE3 2300 1F00 0800 7C00 EFE3 7006 0000 7A00 EDE3 Another type 8 expression, another open parenthesis. This time the value type is 1F00 = object_list. Add this in, and here’s our line so far: (object_destroy (list_get ( Getting pretty complex. Let’s keep going, starting with children. EDE3 2300 0200 0900 7B00 EEE3 1A01 0000 0000 0000 Type 9 statement, it’s a function, identity 2300 = volume_return. Line is now: (object_destroy (list_get (volume_return Keep going on to its sibling. EEE3 0D00 0D00 0900 FFFF FFFF 3001 0000 0100 0000 Type 9 statement. 0D00 = trigger_volume. If you follow the string back to the table, you’ll see the name of the volume, but the game doesn’t use that. The game uses the value here, which is an index. We’re using chunk 1 of the trigger volumes reflexive. Its name is tv_crate_eraser_left. Notice that there’s no more pointers here! What does that mean? That means that we put the close parenthesis here. So put it together and we get: (object_destroy (list_get (volue_return tv_crate_eraser_left) *phew* almost done with this line. Let’s keep going. To continue, we go back to the last open parenthesis, which opened the list_get command, and follow its sibling pointer. EFE3 0700 0700 0900 FFFF FFFF 9D06 0000 0000 FFFF Alright, type 9 statement, value type 0700 = short. Again, notice no more pointers. If you go back to the opening for object_destroy, there’s your next sibling pointer. However, I’m going to stop here. Here’s the whole line: (object_destroy (list_get (volume_return tv_crate_eraser_left) 0)) Add it into what we got before, and the first 3 lines of our script are: (script continuous crate_eraser (begin (object_destroy (list_get (volume_return tv_crate_eraser_left) 0)) Wow!! That’s a lot of work for one line. Now you see why we need a program for this. If you want, you can continue this pattern to decompile the rest of the script, but I’m going to move on to compiling now. E) Compiling Phew, that’s a lot of work. Now are you ready for the final task: Compiling? I hope so, because this is probably what you read the tutorial for. Please, if you skipped straight to compiling, go back and read the rest, you’re going to need it to understand this. If you’ve read the rest, then let’s jump right in. For this tutorial, I’m not going to do the public example that you all know, the zero gravity terminal, mainly because there was too much guesswork involved on my part, and too many mistakes. Therefore, I’m going to walk you through a very similar script on coagulation. The script we will be using looks like: (script startup gravity_hack (begin (physics_set_gravity 0.5) ) ) Now, before we start writing out the syntax data, we need to set up the rest of our map. Let’s start by adding the chunk we need. If you open up entity, and go to the chunk adder, we need to inject a single chunk named “Scripts” from a map with scripts into our coagulation. It’s easiest from elongation, because it only has 3 chunks, so it’s faster to delete the others. We will edit this chunk at the end of the tutorial. Quick walkthrough: 1) Open elongation 2) Open the scnr tag 3) Right-click the references section, and click clone chunk 4) Find the “Scripts” reflexive, click it, and click copy to clipboard. 5) Close elongation, open coagulation. 6) Open the scnr tag 7) Right-click the references section, and click clone chunk. 8) Click the parent chunk (it should say header, then one child chunk, click that) and click “add to selected reflex/chunk.” 9) Navigate to the new “Scripts” reflexive, open it, click the first chunk, and click delete. 10) Repeat step 9. 11) Click add meta to map. Alright, now we have our reflexive and chunk in coag. The next step is not actually necessary, but is a good practice to get into. We’re going to add our strings to the string table. You can use entity to get the offset for it. Once there in the hex editor, you’re going to overwrite the (decorator_rebuild_all with “begin” followed by a null byte (00) followed by “physics_set_gravity” followed by a null byte. If you choose not to do this step, the script will still run, but will not be decompiled properly by our tools. Don’t think that this is a way to add security to your scripts however, because they can still be decompiled using the command identity list. Now we start writing the syntax data. Following the example set by Bungie, we’re going to do 2 strange practices: Leave padding chunks before and after scripts, and place the begin statement at the end. Since we’re putting “begin” at the end, that means our first line is actually going to be “(physics_set_gravity 0.5)”. So the first thing we have to do is navigate to the syntax data, and find where we’re going to inject this script. Once you’ve located the beginning of the syntax data (note that it begins with 0000, not with BABA) we’re going to skip ahead 4 chunks to do our injecting. That means you should be 80 bytes past the start of the reflexive before you insert any data. An important thing to mention here is that we’re not actually adding any bytes to the map, only overwriting. The syntax data is padded to a certain amount (516 chunks). You only add more if you run out of space, and you only add 516 more padding chunks to be overwritten. Alright, now let’s start writing our expression. We begin with the “(“ of the physics_set_gravity command. We have all of the info we need about it, so let’s start writing it. We start with the expression index. Since we left 4 chunks of padding, our first expression index will be 77E3. Next comes the 3 types. I’m going to go in reverse order for logic’s sake. Starting with the expression type, it’s type 8 because it’s a syntax expression. Going backwards to value type, it’s a void value. Then we need the identity for physics_set_gravity which is 7F00. So our first 4 bytes should be like this: 77E3 7F00 0400 0800 Good, now let’s move on. If you look at the script, the () for physics_set_gravity does not have any siblings after it, so we add a null pointer here (FFFF FFFF). Then we have the table pointer, and since this is a syntax expression, it doesn’t reference the table. Bungie uses random values, but it’s just easier to use 0000 0000. And now comes the child reference. Since the next chunk will have expression index 78E3, our child reference looks like this: 0500 78E3. So here’s the first full chunk: 77E3 7F00 0400 0800 FFFF FFFF 0000 0000 0500 78E3. Yay, we’re making progress. Continuing on, we write out the next expression. Its expression index will be 78E3. Its 3 types (in reverse order) are 0900, 0200 (function name) and 7F00 again. Now, since we’re inside the parenthesis, this string does have a sibling: its value. So the next chunk that we’re going to write will end up having index 79E3, so our pointer looks like 0600 79E3. Now for the script table reference (not necessary, but a good practice). If you added your strings the way I told you, “physics_set_gravity” should be 6 bytes in, so type 0600 0000. And now since it’s a command it doesn’t have a value. Here’s this chunk: 78E3 7F00 0200 0900 0600 79E3 0600 0000 0000 0000 Alright, now for the value. Expression index for this chunk is 79E3. The physics_set_gravity command takes floats as its value, so that dictates our 3 types (still in reverse): 0900, 0600, and 0600. Since this is at the end of its parenthesis, it has no sibling, so add a null pointer in. It’s a value, and doesn’t reference the string table (of course Bungie would still have values here… go figure), so add some 0’s. And now we add in the value. If you don’t want 0.5 then go figure out your own value, but the hex for 0.5 (endian swapped) is 0000 003F. And that’s another chunk: 79E3 0600 0600 0900 FFFF FFFF 0000 0000 0000 003F Now we’ve done the bulk of our actual scripts, and are ready to do the begin statement, which we saved for last. So let’s look at how that would be. We’ll start with the syntax statement. Expression index will be 7AE3. The 3 type values are: 0800, 0400, and 0000 (begin’s identity). The begin expression doesn’t have any siblings, so put a null sibling pointer here. Syntax expressions don’t reference the table, so null pointer here. Then we need our child pointer: since the next chunk will have index 7BE3, our pointer looks like 0800 7BE3. Here’s our next chunk: 7AE3 0000 0400 0800 FFFF FFFF 0000 0000 0800 7BE3 And our last chunk! This will be the actual “begin”. So here we go. Expression index 7BE3. Types are: 0900, 0200, 0000. Now this chunk here has a sibling: the first chunk we wrote. So we’re going to point it there using 0400 77E3. Now the table reference. If you made the table like I said, this will end up being 0000 0000. Since it’s a command it doesn’t have a value. So here’s our final chunk: 7BE3 0000 0200 0900 0400 77E3 0000 0000 0000 0000. So here’s the whole thing: 77E3 7F00 0400 0800 FFFF FFFF 0000 0000 0500 78E3 78E3 7F00 0200 0900 0600 79E3 0600 0000 0000 0000 79E3 0600 0600 0900 FFFF FFFF 0000 0000 0000 003F 7AE3 0000 0400 0800 FFFF FFFF 0000 0000 0800 7BE3 7BE3 0000 0200 0900 0400 77E3 0000 0000 0000 0000 That totals 100 bytes. After the last 0000 there should be another chunk of padding (0000, followed by 18 bytes of BA). That creates the script (begin (physics_set_gravity 0.5) ) Now there’s just one thing left to do. We need to link the “Scripts” reflexive to it, in order to put in place the “(script startup gravity_hack” and the closing “)”. So head on up to where you added that chunk before. You should see “crate_eraser” (if you followed my walkthrough for adding). You can go ahead and change that to whatever you want less than 32 bytes (if you’re following this to the letter, that would mean gravity_hack). Now after the 32 bytes allotted for the name comes the other information. The first 2 bytes are the script type, which we’re going to make startup, 0000. Then comes the return type, which will still be void (0400). Then comes the pointer into the syntax data. We want to point it to the beginning of the script, which is the “(begin” line. Its expression index is 7AE3, so our pointer looks like 0700 7AE3. Congratulations, if you managed to follow that, you should now have a low grav coagulation!! Sign the map, transfer it and enjoy. *Note* When adding scripts into a map that already has scripts, make sure you adjust the expression indices accordingly. F) Credits This tutorial was written by Soldier of Light, with help by other members of Brok3n Halo, mainly Shalted, The Tycko Man, and KillingForFun. Special thanks to Doom for being the little annoying voice making me finish it >_>. If I find this tutorial posted somewhere without these credits I will personally find the person who did it and wring their neck. Thank you for reading this, and I hope you learned something from it.
__________________ Halo Demo Derby "The Ultimate Wheelman Challenge": se7ensins.com/forums/halo-3-discussion/105913-halo-demo-derby-tourny.html Last edited by Sasquatch 45; 08-07-2007 at 02:45 PM. | ||||||
| | |
| | #6 (permalink) | ||||||
| I ask for help! ![]()
Join Date: Jun 2007 Location: The South
Posts: 3,724
Tournaments Joined: 0 Tournament Wins: 0 Gave Thanks: 237
Received Thanks: 70
Nominated 0 Times in 0 Posts TOTM Award(s): 0 ![]() ![]() ![]() ![]() ![]() | THANK YOU!!!!!!!!! i havent read it cause i dont have time but ive been looking for this!!! | ||||||
| | |
| | #7 (permalink) | ||||||
| S7 Legend ![]()
Join Date: Oct 2006 Location: Canada! Eh?
Posts: 4,455
Tournaments Joined: 0 Tournament Wins: 0 Gave Thanks: 71
Received Thanks: 189
Nominated 0 Times in 0 Posts TOTM Award(s): 0 ![]() ![]() ![]() ![]() ![]() ![]() | aggggg my eyes its late and white text on black background is very painful. This is a nice tutorial. thnx =] | ||||||
| | |
| | #10 (permalink) | ||||||
| 7S Addict
| You realise you spent half a day writing that and no one is going to read it all.......... People on this site only want thanks and +rep. When someone posts a tutorial, the first thing they say is "Thanks and +rep please" No one will read all the way through this because they would rather just put in a post like "Oh, nice, maybe Ill read it later" so they can have that one extra number added to their post count.. | ||||||
| | |
| | #11 (permalink) | ||||||
| I ask for help! ![]()
Join Date: Jun 2007 Location: The South
Posts: 3,724
Tournaments Joined: 0 Tournament Wins: 0 Gave Thanks: 237
Received Thanks: 70
Nominated 0 Times in 0 Posts TOTM Award(s): 0 ![]() ![]() ![]() ![]() ![]() | yeah, i hate them stupid people that just post, "ill read it later" just to get a post!! lol. but uh, i dont do it for the post count, i just hate it when you write somthing and nobody responds, so its kinda like a reasurence. | ||||||
| | |