AmanoJyaku

Members
  • Content Count

    172
  • Joined

  • Last visited

  • Days Won

    7

AmanoJyaku last won the day on February 4

AmanoJyaku had the most liked content!

Community Reputation

126 Jedi Grand Master

3 Followers

About AmanoJyaku

  • Rank
    Jedi Knight

Recent Profile Visitors

1,901 profile views
  1. Just got my second vaccine. DarthParametric, you can take your mask off. I'm not contagious anymore!!!

    1. Stormie97

      Stormie97

      So, feeling any urge to buy Microsoft products?

    2. DarthParametric

      DarthParametric

      What about the other 8 or 9 billion or so?

  2. The 4th will be with you. Always.

    1. DarthParametric

      DarthParametric

      Except on the 5th.

    2. Sith Holocron

      Sith Holocron

      For those not celebrating today, we'll see you tomorrow.

      ZhbzKbA.jpg

      We have margaritas and nachos!

  3. Sorry for the delay. I wanted to put out an update, but I've been addressing bugs. More important, I'm dealing with real life and needed to take some time for myself. This project has consumed most of my free time, I haven't played a game since February of last year. I'll pick this up next week, but for now I'm playing some games!
  4. Just a quick update. Here's output from TSL's k_contain_unlock.ncs: Overall, the results look good! Though, there are things that need to be dealt with: Subroutines only list the number of return values and arguments; type information needs to be substituted, e.g. void Sub1122(object, int, int) Arguments in expressions are listed as CPTOPSP; this needs to be replaced with a variable name and argument position, e.g. if (!GetLocalBoolean(Arg0, 57)) Variable declarators with initializers are written as two lines; while correct, these should be merged, e.g. object Object0 = 0; Arguments that are assigned to need to be printed, e.g. if (Arg2 < 900) Arg2 = 0; For loops are presented as while loops; while correct, for (Int0 = 1 ; Int0 <= Arg1; ++Int0) is more compact than Int0 = 0; while (Int0 <= Arg1) ++Int0 Increment and decrement are not yet printed (this is trivial, I just didn't do it) Printing expressions needs to take place at a different point in the process; the current method prints some expressions twice, and omits others Switch statements need to be printed Vectors and structs need to be detected, which will require multiple methods Return statements need to be printed Break and continue statements in loops need to be detected Arguments of type "action" (AssignCommand, DelayCommand, ActionDoCommand) need to be printed These are largely minor issues, but they're taking a backseat to a bug I introduced along the way: The else-if statements have the wrong test condition. They should all be pointing at the same variable as the preceding if statement, Arg1. This error is propagated further as reflected in the last line. Removing 5 arguments? There are only 2!!! Printing code was added yesterday, so I expect this to be a quick fix.
  5. Here you go: d3_saber_force.ncs
  6. I wanted to give an update a few weeks ago, but I had to tackle a problem I ignored for months. The reverse compiler is now more than 5,000 lines in length, and that makes it difficult to maintain. So, almost 4,000 lines of source code have been moved into a library. Additionally, many functions did too much and have since been split into smaller functions or replaced entirely. It's not sexy, but coding never is. Or is it? Cue: Salt-N-Peppa's "Let's talk about sex" Let's talk about Hex, baby! Let's talk about Binary! Let's talk about Octal numbers, Signed and unsigned, number theory! OK, OK, I'm back to "normal". Anyway, after three weeks of maintenance I got back to the real work and decided to rewrite the remaining 1,000+ lines entirely. Everything from reading an NCS file from disk to producing NWScript is new. And so much better than before, since I eliminated some bugs in the code. I'll provide a more detailed post later, but here's some sample output from TSL's k_inc_npckill.ncs: I purposely left out nested scopes because I wanted to focus local variables. This script is particularly interesting because it contains a vector. See the really long line that starts with float = GetPositionFromLocation? That's one of the vector's members. The source code looks like this: The really long line in the sample output essentially merges the two lines above, but if you look closely you'll see it's the combination of creating vPos.z, adding 1.0f to vPos.z, then storing the result back to vPos.z. Here's a cut-and-paste from the NSS file to compare to the sample output: Now that I've confirmed the stack is being created correctly, the next tasks are to: Add variable declarations, e.g. int nParam, object oParam Properly display modification of variable values, e.g. vPos.z = vPoz.z + 1.0f Insert nested scopes (if-else, while, etc...) Probably other stuff I can't think of right now
  7. Stripped out some of the diagnostic output and added some source code output. Here's the original Handmaiden conditional script again: //c_003handfight int StartingConditional() { string tString = GetScriptStringParameter(); int tInt = GetScriptParameter( 1 ); int LevelInt = GetScriptParameter( 2 ); if ( ( GetGlobalNumber(tString) == tInt ) && ( GetGlobalNumber ("G_PC_LEVEL") < LevelInt ) ) { return TRUE; } return FALSE; } Here's the output from the reverse compiler: //Kotor2\c_003handfight.ncs This is definitely a K2 file Sub13, 23 } Sub23, 250 38 string = GetScriptStringParameter(); 65 int = GetScriptParameter(1, ); 92 int = GetScriptParameter(2, ); 166 if (GetGlobalNumber(CPTOPSP, ) == CPTOPSP && GetGlobalNumber("G_PC_LEVEL", ) < CPTOPSP) { } 224 ; } Recognizable! Here's the output of k_inc_npckill.ncs: And k_contain_unlock: Rough, but getting there!
  8. I fixed the switch bug and decided to try the first step of reverse compiling: finding statements. For example, the following has six statements: //c_003handfight int StartingConditional() { string tString = GetScriptStringParameter(); int tInt = GetScriptParameter( 1 ); int LevelInt = GetScriptParameter( 2 ); if ( ( GetGlobalNumber(tString) == tInt ) && ( GetGlobalNumber ("G_PC_LEVEL") < LevelInt ) ) { return TRUE; } return FALSE; } The first three are declaration statements, they introduce entities into the StartingConditional subroutine scope. In C, variables and subroutines (functions) are entities. Entities have names and types. In an NCS file, subroutines are found by the JSR opcode and variables are created by the RSADDx opcode. The return statement is one of the jump statements, which includes the break and continue statements. All three are implemented by the JMP opcode. The if statement is one of the selection statements, which includes the switch statement. The if statement is also a compound statement or block, meaning it can contain multiple statements. The previous updates detailed my efforts to correctly identify the various types of statements. Here's what the reverse compiler produces: This is definitely a K2 file Sub13, 23 13 adds 1 15 Sub23 removes 0 returns 1 21 Sub23, 250 23 adds 1 25 GetScriptStringParameter adds 1 30 38 removes -1 44 adds 1 46 adds 1 52 GetScriptParameter removes -1 adds 1 57 65 removes -1 71 adds 1 73 adds 1 79 GetScriptParameter removes -1 adds 1 84 92 removes -1 98 adds 1 106 GetGlobalNumber removes -1 adds 1 111 adds 1 119 removes -1 121 adds 1 129 removes -1 135 adds 1 149 GetGlobalNumber removes -1 adds 1 154 adds 1 162 removes -1 164 removes -1 166 if() removes -1 210 adds 1 216 224 removes -4 230 248 Sub13 is the hidden _start() function. This file is a conditional script used in dialogs, so it returns a value. This is the file you use to practice fighting with Handmaiden, and she won't fight you if you aren't good enough. The value returned is obtained from Sub23 and stored in the space reserved by the RSADDx opcode at offset 13 (written above as "add 1"). Sub23 is int StartingConditional(), which is what is required for conditional scripts. The alternative is void main() for non-dialog scripts. If this was a non-dialog script offset 13 would not be a reservation. It would be Sub21, because the JSR opcode is six bytes and the next opcode is the two-byte RETN (13 + 6 + 2 = 21). Offsets 23-38 are the a declaration statement, string tString = GetScriptStringParameter(). You see three elements added to the stack, and two removed. Offset 30 is the CPDOWNSP opcode. Blank lines mean elements have not been added to or removed from the stack. Instead, CPDOWNSP copies the value returned by GetScriptStringParameter() to the element reserved by offset 23, string tString. CPDOWNSP is the assignment operator, "=". Offset 38 then destroys the value returned by GetScriptStringParameter() since it is no longer needed. In C this is known as discarding. The key to writing a reverse compiler is finding these discarded values. The MOVSP opcode discards values, so it's a safe bet that a MOVSP opcode is the end of a statement. In the above example, offsets 38, 65, and 92 are discarding values. You can think of MOVSP as the semicolon at the end of the statement. Offset 164 is the logical AND, 98-162 are the operands. Since we have already found the ranges created by JZ and JNZ, we already know the boundaries of those block statements. Offset 166 is the if () statement, which ends at offset 210. And blocks are surrounded by curly braces. Offset 210 places the return value onto the stack. This is after the if () statement, so it's value is FALSE. It's then returned at offset 216 using CPDOWNSP to the reservation created at offset 13. Offset 224 then destroys the three local variables and the return value using MOVSP. Finally, the RETN opcode should be at offset 230, but for some reason it's a JMP to 248. (I swear, I hate the NCS compilers.) Offset 248 has the RETN. Neither the JMP or RETN would be printed. TL;DR I just have to remove diagnostic info, group expressions into statements, and ensure the branching statements and nested statements are handled correctly. Look forward to another update soon.
  9. All he said was he doesn't want to report on anything unless he's 100% sure (previous tweets deleted), and in response to "who should EA pick to make the game" he replied no one would guess right. He isn't confirming, denying, or hinting at anything.
  10. The Phantom Bug Attack of the Cases Revenge of the Switch
  11. I promised an update in two weeks... How about two days? The approach of using ranges has borne fruit. Consider the following code: int subroutine(int I, float F, vector V) { int i = 1; while(true) { if(i > 10) { //do stuff } else { return -1; } } return 0; } The reverse compiler needs to be able to find the return type and parameter types. To do this, it: Counts the number of local variables in Part 1 Counts the number of variables returned in Part 2 Destroys the local and returned variables in Part 3 Verifies the stack is empty, and assumes the count being destroyed is the number of input arguments in Part 4 Using the above code as an example, the method outlined in the post from two days ago effectively ignores everything inside the while loop. So: 1 local variable is counted, created by the RSADDI opcode 1 return variable is counted, created by the CPTOPSP opcode and copied by the CPDOWNSP opcode 1 local variable and 1 returned variable are destroyed by the MOVSP opcode 5 input arguments are destroyed by the MOVSP opcode (vectors are three floats) Here's the output of the KoTOR2 file k_contain_unlock.ncs: This is definitely a K2 file Subroutine 13, 21 JSR Subroutine 21, 995 JSR Subroutine 995, 1122 Return variables: 0 Local variables: 0 Parameters: 0 Subroutine 1122, 1766 Return variables: 0 Local variables: 0 Parameters: 3 Subroutine 1766, 2948 Return variables: 1 Local variables: 0 Parameters: 2 Subroutine 2948, 6915 JSR Subroutine 6915, 7116 Return variables: 1 Local variables: 0 Parameters: 1 A subroutine with the status "JSR" means it depends upon another subroutine to be reverse compiled first: Sub13 calls Sub21 Sub21 calls Sub995 Sub2948 calls Sub6915 Here's what the other status codes mean: Return variables: 0 - returns void; 1 - returns a single value, e.g. int, float, string or object; a value of 2 or more returns a struct or vector Local variables: 0 - should always be 0, because it's calculated after the stack is cleared; non-zero value means the analysis is flawed Parameters: 0 - there are no input parameters; 1 or more means there are input parameters So, what are the subroutines? Sub13 - void _start() - the hidden routine Sub21 - void _global() - the routine that creates global variables Sub995 - int main() Sub1122 - void PlaceTreasureDisposable(object oContainer = OBJECT_SELF, int numberOfItems = 1, int nItemType = 900) Sub1766 - string GetTreasureBundle (int nItemLevel, int nItemType = 0) Sub2948 - string GetBundlePrefix (int nItemLevel, int nItemType) Sub6915 - string SWTR_GetQuantity(int iCount) Still targeting an update in two weeks. Or earlier.
  12. Life has been crazy, and I've seen things I never thought would happen in my country. But, coding never stops. Time for an overdue update! An earlier update outlined the structure of a subroutine: return_type subA(Argument_1, Argument_2...) { //Part 1 - The code that does stuff //Part 2 - Values returned //Part 3 - Destruction of local variables //Part 4 - Destruction of arguments } Part 2 returns values from a subroutine using the CPDOWNSP opcode. Parts 3 and 4 destroy local variables and arguments using the MOVSP opcode. These opcodes can appear in any part of a subroutine. So, how do we know if we're returning a value, and destroying variables and/or arguments? We analyze part 1 to determine what, if anything, is on the stack. Computers execute operations in sequence unless directed otherwise. In high-level languages like NWScript, control statements alter the sequence. An if statement conditionally executes code. If the test condition fails, this code is jumped over. So, what does an if statement look like in the compiled NCS? 102 CPTOPSP -3 1 110 CONSTI 0 116 EQUALxx 118 JZ 170 124 CPTOPSP -1 1 132 CPTOPSP -3 1 140 CONSTO object 146 JSR 298 152 MOVSP -3 158 JMP 296 164 JMP 170 In the example, the opcode at offset 102 of the NCS file copies the value of a variable to the top of the stack. Offset 110 places the value 0 onto the stack. Offset 116 performs a test of equality of the two values, removes them from the stack, then places the result of the test onto the stack. Offset 118 performs a conditional jump based on the result, removing the result in the process. Offsets 124-164 are the body of the if statement, which consists of a call to a subroutine followed by a return statement: if ( nKillMode == 0 ) { DamagingExplosion(OBJECT_SELF, nDelay, nDamage); return; } So many ops, so few statements. That's why we write in high-level languages instead of assembly, because the compiler allows us to focus on what the program should do rather than creating the proper sequence of opcodes to keep the program from breaking. Notice in the body of the if statement there is a MOVSP. That is the destruction of local variables, part 3. There is no part 2, because the subroutine returns void (nothing). But, how do we know the MOVSP is part 3 and not part 4? And why are there two unconditional jumps??? The challenge of writing a reverse compiler is knowing the rules used to compile. I've spent the past six months observing, analyzing, and documenting those rules. For example, all control statements other than the switch statement are created using the jump-if-zero (JZ) opcode. (Switch statements are created using the jump-if-not-zero [JNZ] opcode.) However, the existence of a JZ opcode does not mean you've encountered a control statement! JZ is used to create the following: Logical AND Logical OR if statements else if statements while statements for statements do-while statements (I finally found one!!!) This means we need to some way of determining what the range of addresses created by the JZ opcode is. In the example above, the range created by the if statement is [124, 170). In this range notation a bracket "[" means the offset is included in the range, and a parenthesis ")" means the offset is outside of the range. The 170 is obvious because it's specified by the JZ opcode; if nKillMode == 0 returns false we jump to 170 and resume execution from there. Where does the 124 come from? Remember what was stated above: "computers execute operations in sequence unless directed otherwise." A JZ opcode is 6 bytes in length and the JZ is at offset 118, so 118 + 6 means the next op in the sequence is at offset 124. If nKillMode == 0 returns true we enter the body of the if statement at offset 124. Offset 124 is the first address in the range, offset 170 is the first address outside of the range. All control statements are ranges, but not all ranges are control statements. The NCS file is itself a range: [0, file size). Subroutines are ranges. However, our focus is on the ranges created by JZ. How do we determine what they are? The unconditional jump, JMP. Notice the example ends with two JMPs. This caused me lots of stress because I didn't know why they were there. I'm now convinced this is one of the rules of compiling NCS files: control statements end with a JMP. The if statement is easily identified as such because the second JMP goes to the same offset as the JZ. The true block of an if-else statement ends in a JMP to an offset greater than that of the JZ. The if and else-if statements all end in JMPs to the same offset. Loops all end in JMPs to an offset less than the offset of the JZ. Logical AND and Logical Or are not control statements, so they do not end in a JMP. Our example has two JMPs. Why? Most likely the original compiler was dumb. The NWScript return statement is translated into a JMP to the end of the subroutine, offset 296. The opcode at this offset is RETN, the last opcode in a subroutine. Anytime you see a return in NWScript that isn't the last statement in the subroutine, you know it will be implemented as a JMP: if ( nKillMode == 0 ) { return; } if ( nKillMode == 1 ) { return; } if ( nKillMode == 2 ) { return; } is 118 JZ 170 158 JMP 296 164 JMP 170 186 JZ 230 218 JMP 296 224 JMP 230 246 JZ 290 278 JMP 296 284 JMP 290 296 RETN The original compiler just stuck the body of the if statement in between the JZ and JMP. A modern compiler would have removed the second JMP since it's unreachable code. OK, this is great and all. But what about the reverse compiler? Well, now that I have the rules down I'm close to the finish line. The reverse compiler accurately finds ranges and determines what types they are. The output looks like this: This is definitely a K2 file Subroutine 13, 21 13, 21 //Subroutine Subroutine 21, 298 21, 298 //Subroutine 124, 170 //if 192, 230 //if 252, 290 //if Subroutine 298, 1345 298, 1345 //Subroutine 320, 396 //if 704, 722 //logical AND or OR 728, 1216 //loop 787, 805 //logical AND or OR 811, 1135 //if 914, 970 //if-else 1017, 1043 //if-else Subroutine 1345, 1601 1345, 1601 //Subroutine 1459, 1553 //if 1511, 1547 //if Subroutine 1601, 1637 1601, 1637 //Subroutine Subroutine 1637, 1813 1637, 1813 //Subroutine 1659, 1727 //if There's more to be done, particularly for loops because they support the break and continue statements (also implemented with JMP). But, hard part seems to be behind me. Should have an update in two weeks.
  13. I haven't read all of the posts yet, so I don't know if TSL's development history was addressed. It wasn't Obsidian's fault, it was LucasArts'. LucasArts requested BioWare make a sequel before KoTOR was finished, transferred development to Obsidian after BioWare refused, kept Obsidian from speaking with BioWare, and gave Obsidian a year and a half to make the game when KoTOR took three years. The first Obsidian learned about KoTOR came from playing the game after it was released, and the company had to scrap damn near everything it had and start over. Out of 18 months, 3 were lost to learning about KoTOR, and then story and game development proceeded. This is the reason why you barely see anything from KoTOR in TSL other than the game engine and a few character and object models. Even worse, TSL was supposed to be a bigger game and the Exile was supposed to get to level 50. That was no longer possible with only 15 months of development and testing. Much of what has been restored in TSLRCM is undeveloped content that was scrapped due to time constraints. The game we're playing is less than a year's worth of work. It's amazing it's as good as it is.
  14. As far as I know, anything you can do in a DOS prompt you can do in a PowerShell prompt. And PowerShell is way better in other ways. So, no need to make changes to the Registry. Also, I try to avoid downloading things from websites as I can't trust the downloads are free of malware, or that the REG files won't do something I don't expect. No, I'm not saying I have proof of anything wrong with the link or the files posted there. I'm just saying be paranoid, because 30ish years of using the Internet says that's the best approach. And, yeah, I recognize how that sounds on a mod site. 😁
  15. I just discovered Windows 10 virtual desktops...

    How have I lived without them all this time???