010 Editor Scripting Basics


Description

By reading this tutorial you can learn the basics of 010 editor scripting.

010EdLogoFull.JPG

Introduction

Difficulty:  Hard

This tutorial is dedicated to explaining the basics of scripting in SweetScape 010 Editor. First of all, I want to say that I am not a programmer and the way I write the code may look weird to a person who knows more about programming. But following my way you can create useful script to automate many tasks you need to perform with World of Warcraft files. Important: You don't need any programming knowledge to start learning scripting in 010. I did not have any at first at all, so we are going to start from the real basics.

010 Editor uses two different filetypes to perform operations on the file.

The first one is template (.bt) - it parses the file and gives us the structure with which we can work manually or within a script. Templates for most WoW files are already written and I am not going to teach you making them as I don't have that much of that knowledge myself. 

The next one is script (.1s) - scripts can be executed inside 010 editor, but unlike to templates, they can be excuted both on an open file or in a plain way (without a file opened). 010 Editor scripting uses a C-similar language. For experienced users: The language (1SC) used in 010 Editor scripting is indeed similar to C and shares most of the functions and syntax with it. Though, some parts of it are really weird and different from the the original C. Some functionality of C is not implemented. So, be careful and read the function reference.

Learning basics

This page contains a lot of useful information about this hex editor, though we are mostly interested in Function Reference part. Let's have a look at it. It contains a description of how you can use each available function. This is vitally important to read those definitions if you want to learn scripting.

IO Functions are functions that take and return a value. They are used for most of our data operations including reading, writing and so on. Interface functions allow you to emulate the actions of a user such inside the editor, such as closing or saving the file, opening files, moving cursor and so on. Math functions are obviously doing some mathematical calculations. I have not used functions from other pages but I guess they can be useful too.

So, the basic idea is to execute a template on a file, get the structure and read or write some values to it.

Writing your first script

So, when I was learning 010 editor scripting what I started from was pure practice. Just trial and error method, no programming knowledge at all (I did not even have programming at school, as most people do). And I suggest you doing exactly the same thing because it is easier to learn by making something useful rather than reading boring theory written by some noob who obviously codes wrong in a non-professional way, but yet working.

The first thing that we are going to do is a collision cloner script. The purpose of it is to copy collision from one .m2 model to another. Some of you may ask: "What's the use of it? Let's make something really useful!". Well, basically when you create custom models, sometimes you need to have some parts of model collidable and some not. The convertion software always sets collision to the entire model, which is not what we want. So we can create two version of a tree - with leaves and without leaves. Then we convert both to .m2 model and copy collision from the one without leaves to the one with leaves. So, we get a collideable trunk and leaves that you can pass through. Anyway, let's get started.

Step 1. Investigating the data

First of all, when we know what we want to do we need to find out what data is reponsible for handling collision. The best source of information for that kind of things is our Wiki (the link is in the upper menu of the website). But in case of collision, everything is rather simple. So, first thing that I want you to do is to create an empty script file and open some .m2 model in 010 Editor. Apply the M2Template1.bt script on it. 

We get a dropdown with the structure:

Clip2net_151213122710.thumb.png.570f137bThis is a shortened version because the list is too long to display. M2 file structure is based on offsets (starts with ofs). Those are values that are always located by the same adress no matter what model it is. They identify the adress of an actual block of data in the file. So, you can just read them and know where the actual data is written. The lines starting with (n) identify the amount of elements used in the data array. They are also static and are always located on the same adress. And everything else is structures.

The client finds out how to read the file by reading ofs's and n's and finding out where the block of data is located in the file. There is no matter what the order of the data structure is when the static part is over. 

So, what we are going to do is:

  1. Read offsets for collision.
  2. Read the amount of data elements.
  3. Find and read the collision data depending on the given values.
  4. Copy it to the end of the target file.
  5. Change offsets and n-values to the proper values corresponding to the new data.

The old data block will remain the file, but the client won't read that part as offsets won't point to that adress anymore.

The last thing you need to do in this step is: Right click on the structure - Coloumn Display Format - Name. It will save you a lot of time in the future steps, just believe me.

Step 2. Writing the script

I will  paste here the entire script and write comments to it explaining how things work.

char sFile[], tFile[];
/* Declare two char (type of value) arrays ([], are able to store multiple elements) that will represent two opened files, a source one and a target one. */
int sIndex, tIndex; 
// Declare two integer values for a little check if the file is 
// opened succesfully.
           
//Let's get the file names of a source file and a target file.
           
sFile = InputOpenFileName("Select source file...", ".m2"); 
// This function opens a dialogue window where you can select a file. 
// The first argument is a window name and the second one is 
// the required file type.
           
        sIndex = FileOpen(sFile); // This way we open this file inside an editor.
        if(sIndex < 0) // If a file failed to open, the function will return -1, so let's check it.
        return; 
// If the function returned -1, something went wrong and return; will be exucted
// so the script will stop.

tFile = InputOpenFileName("Select target file...", ".m2"); 
// Do the same for target file, but don't open.
          
          
RunTemplate("M2Template1.bt"); 
// This function executes a template on an opened file.
           
int64 bt = M2_file.nBoundingTriangles; 
int64 bv = M2_file.nBoundingVertices;
int64 bn = M2_file.nBoundingNormals;

// Read the n-values for collision elements into an int64 value. 
// You can find out the adress of the element in a template by simply looking at 
// the dropdown names and separating them by a dot.
           
int64 qbt = 1 * sizeof(M2_file._BoundingTriangles);
int64 qbv = 1 * sizeof(M2_file._BoundingVertices);
int64 qbn = 1 * sizeof(M2_file._BoundingNormals);

// Find out the size of the collision structures by using sizeof keyword. 
// The reason why I am multiplying with 1 here is because 010 editor scripting 
// language is weird and it does not work the other way. 
           
uchar bt_buf[qbt];
uchar bv_buf[qbv];
uchar bn_buf[qbn];

// Declare some buffer arrays for storing our data. Write in the size values 
// into the [], in order to define how many elements the buffer is able to store.
           
int64 bts_ofs = M2_file.ofsBoundingTriangles;
int64 bvs_ofs = M2_file.ofsBoundingVertices;
int64 bns_ofs = M2_file.ofsBoundingNormals;

// Read offsets.
          
         
ReadBytes( bt_buf, bts_ofs, qbt );
ReadBytes( bv_buf, bvs_ofs, qbv );
ReadBytes( bn_buf, bns_ofs, qbn );

// Execute ReadBytes function for each of our three data blocks. 
// What this function does is reading bytes into bt_buf, starting at 
// adress bts_ofs for the size of qbt bytes. Now we have our collision in memory.
           
FileClose(); // Close the source file.
           
tIndex = FileOpen(tFile); // Open a target file.
if(tIndex < 0)
        return;

           
RunTemplate("M2Template1.bt"); // Excecute a template.
           
M2_file.nBoundingTriangles = bt; 
M2_file.nBoundingVertices = bv;
M2_file.nBoundingNormals = bn;

           
// This part here is dedicated to finding the end of the file. 
// The end of the file is going to serve as our new offset. 
// As we want to write data in the end of the file in order 
// not to break the structure.           

int64 size = FileSize(); // Find the size of the whole file.
           
FSkip( size ); 
// Move the reading position to the end of the file by skipping 
// all the data in the file.
           
int64 file_end; // Declare a value for storing the adress of the end of the file.
 FSeek(size); 
/*  Make sure the reading position is on the last byte, sometimes FSkip does not move the position for some reason. */
 file_end = FTell(); // Write the current reading position to the value.

           
M2_file.ofsBoundingTriangles = file_end; // Set the offset of the first data block to the end of the file.
InsertBytes( file_end, qbt); // This function insets empty bytes starting from adress file_end with the size of qbt.
WriteBytes( bt_buf, file_end, qbt ); // Write our read bytes from bt_buf, starting at file_end with the size of qbt.
InsertBytes(file_end + qbt, 60 ); 
/* Insert a a bit of empty bytes after the block of data. file_end + qbt is the new end of file now, 60 is the amount of empty bytes. This is needed for separating the blocks of data to avoid bugs. */
           
int64 sizeTemp1 = FileSize(); // Returns the size of the entire file.
int64 size2 = sizeTemp1 - size; // Find out the amount of the newely entered data.
           
FSkip( size2 );  // Moves the reading point by the amount of entered data.                 
file_end = FTell(); // New end of file.        
           
M2_file.ofsBoundingVertices = file_end;
InsertBytes( file_end, qbv);
WriteBytes( bv_buf, file_end, qbv );
InsertBytes(file_end + qbv, 60 );
           

int64 sizeTemp2 = FileSize();
int64 size3 = sizeTemp2 - (size + size2);
           
FSkip( size3 );                  
file_end = FTell();
           
           
M2_file.ofsBoundingNormals = file_end;
InsertBytes( file_end, qbn);
WriteBytes( bn_buf, file_end, qbn  );
InsertBytes(file_end + qbn, 60 );
           

          
           
FileSave(); // Saves the file.
FileClose(); // Close the file.
MessageBox( idOk, "Collision cloner", "Your collision data has been succesfully transfered." ); /* A message indicating the success of the operation. If the script was excecuted till the end, it will appear. */

So, by reading attentively this script and trying to understand what it does you can learn the basics of scripting. Basically, it covers most operations that we need to perform on WoW files. Let's also look into some other useful things now.

Useful patterns

Here I will provide a set of code snippets that can be used for constructing your script in no time, not depending on the purpose of it. I will post plain code here and explain what each of them does and how it is better to use it.

Snippet 1. Batch file processing.

One of the most useful things in 010 editor scripting. It allows you to execute your script on multiple files at once. There are quite a few ways to do it such as CMD execution, but we will talk about that a bit later.

char sFile[]; // Declare a char for storing file names.
int sIndex;  // Index to check if the file was opened correctly.
int k; // Loop variable for doing iteration.

TOpenFileNames z = InputOpenFileNames(  "Select .adt files", "(*.adt)" ); /* This function opens a dialogue window where you can select multiple files. If you specify any file extension as a second argument, other files won't be available for opening. Very useful for filtering unnecessary files. */
 
     for( k = 0; k < z.count; k++ ) // This loop applies the following code to every filename in the array.
     {
        sFile = z.file[k].filename; // Get the file name. [k] indicates which filename is used now, goes +1 every time loop goes on.
        sIndex = FileOpen(sFile); // Opens the file.
          if(sIndex < 0) // Checks if the file is opened correctly.
          return; // Breaks the process if it did not.
        Processing(); // Custom function for processing.
        FileSave(); // Saves the file.
        FileClose(); // Closes the file.
     }

 /* This is the way you can declare a custom function. Paste your script inside the following brackets and then you can execute it from any part of the code. */

void Processing()
    { 

        // Your script goes here.   
    }

Example script. Sets the MFBO chunk of an .adt to a given values:

//--------------------------------------
//--- 010 Editor v6.0.3 Script File
//
// File:MFBO editor
// Author:Skarn
// Revision:1.0
// Purpose:Edits the MFBO chunk values in ADT.
//--------------------------------------

char sFile[];
int sIndex;
int k;

TOpenFileNames z = InputOpenFileNames(  "Select .adt files", "(*.adt)" );
 
     for( k = 0; k < z.count; k++ )
     {
        sFile = z.file[k].filename;
        sIndex = FileOpen(sFile);
          if(sIndex < 0)
          return;
        SchweinFleish();
        FileSave();
        FileClose();
     }

void SchweinFleish() 
    {
      int z, y, a, p;
      RunTemplate( "WoWADT.bt" );
        
        for( z = 0; z < 3; z++ )
             {
                for( y = 0; y < 3; y++ )
                    {
                       ADT_file.mfbo.maximum[z].height[y] = 600; 
                    {
             }
        for( a = 0; a < 3; a++ )
                   {
                      for( p = 0; p < 3; p++ )
                          {
                             ADT_file.mfbo.minimum[a].height[p] = -150;
                   }       
      
        
        
    }

CMD Processing

You can also do perfectly without this snippet by using CMD way of execution. It has a lot of advantages comparing to what I just wrote. Though, ,y snippet allows you to select multiple files no matter what they are. You manually select what you want to do processing on from the folder. CMD way is very automatic and only works on a particular file extension.

Create a script file that contains only processing, no InputOpenFileName and so on. Just plain processing.

Create a .cmd text file containing this:

@ECHO OFF
for /r %%x in (*WMO) do 010editor "%%x" -script:"C:\Users\Skarn\Desktop\wmo-converter.1sc" -nowarnings -noui

Now you can save it an launch from the folder. You can also change the arguments.

@ECHO OFF disables the CMD black window.

/r enables processing in all the subfolders.

for %%x in (*WMO) do 010editor "%%x" -script:"C:\Users\Skarn\Desktop\wmo-converter.1sc" executes the script on all the .WMO files.

-nowarnings disables the warning window.

-noui allows you to execute the script in a noui silent way, so you can process thousands of files and blinking windows won't bother you.

Snippet 2. Coming soon!

 

 

 

 

 

 

 

 

 

 



Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now