Author Topic: [FF7 PC] Worldmap script disassembler and recompiler - Terraform (0.9.2)  (Read 6402 times)

mav

  • *
  • Posts: 239
  • The Sauce team
    • View Profile
Terraform - Worldmap script editor for Final Fantasy VII
Version: 0.9.2 / 05.04.2020
 

What it is:

This is as far as I know the first tool that can not only read and extract worldmap scripts from world_*.lgp archive into a human-readable language, but also recompile them back to the LGP after you edited the scripts. So it's basically Makou Reactor (a very early, simple version of it) but for worldmap scripts. What can you do with it? Basically you can edit most of the game behavior that happens on the worldmap, including the behavior of Weapons, interactions with vehicles, all the messages that happen on the worldmap, etc.

For example you can make Ruby not really want to fight you, you can make Diamond Weapon approach Midgar faster (part of my Definitive Edition mod), or you can change the colour of your submarine to gold, or you can even set the lightning to a different time of day. The limit is your imagination (and the FF7 engine of course).

How to get it:

This is currently an ALPHA release, which means it's pretty rough around the edges. It's geared towards more technical folk that aren't afraid of command line and have some programming knowledge.

You can currently download this tool from my GitHub repository: https://github.com/maciej-trebacz/ff7-terraform/ (direct download link)

How to use it:

Please make sure to read the README file as it contains some instructions on how to run and use it.

Also make sure to become familiar with how FF7 World scripts work at Qhimm's wiki page: http://wiki.ffrtt.ru/index.php?title=FF7/WorldMap_Module/Script . I did update it with my recent finding and I'll keep updating it when I discover what the remaining unknown opcodes do.

Change Log:
Spoiler: show

0.9.2 [2020-03-24]
- better error handling and messages
- document few unknown opcodes

0.9.1 [2020-03-24]
- add Field ID constants for opcodes that use them

0.9.0 [2020-03-23]
- initial release




Let me know what you think and have fun tinkering with the worldmap :).

PS. Please don't judge the Python code quality, the this tool was meant to be a proof of concept from the beginning ;D. Now that it actually does what it was supposed to do I'll start making the source code more readable and making the tool itself easier to use.

PS 2. If you don't want to download the tool itself and set everything up, but you just want to look at the WorldScript code for the game you can download the disassembled scripts HERE. The archive contains three directories with scripts for three world maps - overworld, underworld and snow fields in Great Glacier. The scripts are best viewed using Sublime Text editor with its syntax highlighting turned on.
« Last Edit: 2020-04-05 13:49:52 by mav »

obesebear

  • *
  • Posts: 1389
    • View Profile
This is wonderful.  Thank you for tackling this!

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
This is great and a LONG needed addition to the set of tools. If you want any help interpreting any undocumented script codes or improving source code readability I'd be happy to offer my services.

mav

  • *
  • Posts: 239
  • The Sauce team
    • View Profile
This is great and a LONG needed addition to the set of tools. If you want any help interpreting any undocumented script codes or improving source code readability I'd be happy to offer my services.

Thanks! I've added a link with the disassembled scripts at the end of the first post if you want to look at them without downloading the tool. I have a flag which adds the raw HEX data above each disassembled line so you can see how the opcodes are created from raw data. There are quite a few that are still unknown, so any input on what is that they do is very welcome. I already found what some of them do and updated the wiki accordingly.

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Awesome! I can't tell you anything definite yet, but here's what little I've found so far:

Code 01B is similar to code 018 (distance to Point) and Code 321 (also currently unknown). It might be a "find midpoint between two points" function, but it doesn't care about the vertical values of either point. This is susceptible to a divide by 0 error. Probably not an issue, but don't call this if the points are directly on the x or z axis.
Code 01A is a valid code, but there's no documentation on it. It looks like it's probably the distance between two points or two models. I'm not sure which.

mav

  • *
  • Posts: 239
  • The Sauce team
    • View Profile
Good analysis. As far as I can tell 01B is used only twice, in the code that determines Emerald Weapon's movement:

Code: [Select]
    WriteTo(TempByte(0), SpecialByte($Random8BitNumber) * 9 >> 8)
    WriteTo(TempByte(3), TempByte(0))
    WriteTo(TempByte(4), TempByte(3) + 1)

    ...

    WriteTo(TempWord(22), Unknown1b(TempByte(3)))
    WriteTo(TempWord(24), Unknown1b(TempByte(4)))
    If TempWord(22) - TempWord(24) >= 128 Then
      WriteTo(TempWord(22), TempWord(22) - 256)
      GoTo @LABEL_3
    EndIf
    If TempWord(24) - TempWord(22) >= 128 Then
      WriteTo(TempWord(24), TempWord(24) - 256)
    EndIf
    @LABEL_3
    WriteTo(TempWord(14), TempWord(8) - 256)
    WriteTo(TempWord(16), TempWord(14) * TempWord(22))
    WriteTo(TempWord(14), 256 - TempWord(14))
    WriteTo(TempWord(18), TempWord(24) * TempWord(14))
    WriteTo(TempWord(20), TempWord(16) + TempWord(18) >> 8)
    SetEntityDirection(TempWord(20))

That's a lot of code just to determine what his direction should be :D. TempByte(0) is a random number between 0 and 9 which determines Emerald's starting location. They are setup in 022_model_30_00 function in wm2.ev.

01A is not used anywhere as far as I can tell.

By the way, I pushed an update to the repo - the parser and decompiler now properly handle Field level ID constants, so it's easier to read code that references them, for example now we get:

Code: [Select]
  If SavemapBit($OwnChocoboStable, 0) Then
    EnterFieldLevel($ChocoboFarm, 1)
    GoTo @LABEL_2
  EndIf

and

Code: [Select]
If SpecialByte($LastFieldID) == $IcicleVillageNorth Then
Previously we had magic numbers in place of $ChocoboFarm and $IcicleVillageNorth constants.

Thanks to codemann8 for figuring a lot of this stuff out and listing all the possible fields that worldmap can reference.
« Last Edit: 2020-03-24 18:09:44 by mav »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
No, 01A is not used which is why I mentioned it. Codes that I find that are valid and unused are more interesting to me than codes that are. :)

I'm still trying to figure out what the format of output these distance codes generate. They make little sense to me. Both 018 and 01B take a single argument and compare it to some preloaded value (between 0 and Fh inclusive) and do some weird comparisons on it.

Code: [Select]
  if ( CurrentActor.X - Destination.X <= 0 )
    v5 = -(CurrentActor.X - Destination.X);
  else
    v5 = CurrentActor.X - Destination.X;
  deltaX = v5;
  if ( v5 >= 24000 )
    deltaX = 48000 - v5;

  if ( CurrentActor.Y - Destination.Y <= 0 )
    deltaY = -(CurrentActor.Y - Destination.Y);
  else
    deltaY = CurrentActor.Y - Destination.Y;

  if ( CurrentActor.Z - Destination.Z <= 0 )
    v3 = -(CurrentActor.Z - Destination.Z);
  else
    v3 = CurrentActor.Z - Destination.Z;
  deltaZ = v3;
  if ( v3 >= 1C000 )
    deltaZ = 38000 - v3;

  return deltaZ + deltaY + deltaX;
That's my best guess, but I'm not sure what that output would tell you. In the context of Emerald Weapon they seem to be some form of circular path and direction finding.

mav

  • *
  • Posts: 239
  • The Sauce team
    • View Profile
It's a longshot, but maybe this opcode is for getting distance from a model to a wall (or to a triangle in the walkmesh)? Because it probably needs to know how far Emerald is from a wall so it doesn't run into walls.
« Last Edit: 2020-03-24 21:13:55 by mav »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Given the small number of acceptable values they’re more likely to be waypoints that emerald travels from one to another. The code isn’t complex enough to be finding a wall. Coding collision detection is harder than you’d think.

NxK

  • *
  • Posts: 130
  • In AI I Trust
    • View Profile
    • YT
Hey, I have been playing around a bit with the tool. I have been able to extract files from the world_us.lgp file. First of all, I have never used Python before, I did follow the instructions you put in your readme though. Now, when trying to re-compile my output, I get this:

Spoiler: show
Terraform v0.9.1 - FF7 Worldmap script editor
---------------------------------------------

  • Compiling world scripts...
  • Reading messages...
  • Reading scripts...

Traceback (most recent call last):
  File "C:\Users\MSI\Downloads\ff7-terraform-master\ff7-terraform-master\terraform.py", line 94, in <module>
    compile_world(argv[2], argv[3])
  File "C:\Users\MSI\Downloads\ff7-terraform-master\ff7-terraform-master\terraform.py", line 47, in compile_world
    parser.compile()
  File "C:\Users\MSI\Downloads\ff7-terraform-master\ff7-terraform-master\parse.py", line 94, in compile
    self.load_scripts()
  File "C:\Users\MSI\Downloads\ff7-terraform-master\ff7-terraform-master\parse.py", line 86, in load_scripts
    code = compiler.compile()
  File "C:\Users\MSI\Downloads\ff7-terraform-master\ff7-terraform-master\compiler.py", line 145, in compile
    with open('world_script.lark') as f:
FileNotFoundError: [Errno 2] No such file or directory: 'world_script.lark'


It seems the 'world_script.lark' file is missing in the "C:\Users\MSI\Downloads\ff7-terraform-master\ff7-terraform-master" directory. It is, however, indeed there, which is why I am not sure why this is not working.

EDIT: I was able to fix the issue by modifying the compiler.py file to refer to world_script.lark's absolute path.
« Last Edit: 2020-04-04 21:10:06 by NxK »

mav

  • *
  • Posts: 239
  • The Sauce team
    • View Profile
I just pushed an update to the repo, which makes the error messages when a script contains an error a bit more human friendly. I also found meaning for some previously unknown opcodes so I've added support for them to the decompiler. I've also updated the wiki with some details.

There are two opcodes that puzzle me: 0x30a and 0x33a. They both seem to be used for setting entity vertical speed, I can't find any difference between them.

One interesting opcode that I documented is 0x319, it lets you toggle some map viewport options, like minimap or camera height (either high or low). One value - 3 - makes the camera appear really close to the player, but also disables directional buttons (but you still can enter Highwind).

At this point I'd like to focus on making this tool more usable by packaging it into an executable (so you don't need to install Python), that will be my next release, hopefully coming soon :).
« Last Edit: 2020-04-05 13:56:36 by mav »

NxK

  • *
  • Posts: 130
  • In AI I Trust
    • View Profile
    • YT
I noticed one more thing: When I replace the world_us.lgp file (I am using the Steam version for this) with one I recompile with Terraform, even if I don't make any changes to the files (so, I just extract the files, then re-compile them immediately afterwards, which should in theory give me the exact same world_us.lgp file), my game crashes every time I try to enter the third world map (the snow map in Great Glacier). Do you get this issue as well? Any idea why this is?

mav

  • *
  • Posts: 239
  • The Sauce team
    • View Profile
To be honest I didn't test that third map yet, I'll have to check that out. Recompiling doesn't recreate world lgp file perfectly like how it was originally because of some weird rules Square used when they've originally created it, so the game might not like how this particular world map was recreated.

Thanks for the report!
« Last Edit: 2020-04-06 14:20:16 by mav »

NxK

  • *
  • Posts: 130
  • In AI I Trust
    • View Profile
    • YT
Thanks for the heads-up.
I actually encountered two more oddities: When entering Fort Condor from the world map, you don't enter Fort Condor but the Mideel Materia Cave (Quadra Magic Cave). When leaving for the Cargo Ship for the first time on Disc1, after the Rufus send-off minigame, as soon as the field tries to transition to the world map where it would show the Cargo Ship in the ocean, you instead end up below the ancient forest, able to move all over the world map but technically softlocked as you seem to be invisible, below the world map and unable to enter any town.