Jump to content

Open Blade of Exile Win32 Custom Scenarii Support


Chokboyz

Recommended Posts

Update : why the bug happens has been found, see posts below.

 

Hello,

 

for what i have been able to understand (and test) custom scenario selection in the Win32 build of BoE is broken. Browsing the source of the build Ormus has kindly released

under GNU GLP2, i managed to (hopefully) found a fix ...

 

First of all, i'm not a programmer, so my code is not of the highest standard. Feel free to improve it wink

 

- What is wrong ?

 

In the FILEIO.cpp, the problem lies in the void build_scen_headers() function :

 

The parameters "scenarios/*.exs" in the line SendMessage(listbox,LB_DIR,0x0,(LPARAM) (LPCTSTR) "scenarios/*.exs");

and a few lines under, the parameter "scenarios/%s" in sprintf((char *) filename,"scenarios/%s",filename2);

are leaving the program clueless.

 

In fact, loading a savefile change the current directory to the one the savefile is. You can test it using the GetCurrentDirectory function or simply by copying the scenarios folder in the same directory as your savefile (usually My Documents) and scenarii should work.

 

What is happening is that the program try to find the "scenarios" directory in the folder your savefile is instead of the BoE folder.

 

- How to fix it ?

 

A quick "fix" would be to put your "scenarios" folder in the same folder as your savefile. Not properly a fix, it saves from having to recompile the code.

 

To fix the program itself, so it would look for the "scenarios" folder in the BoE folder, requires to modify the FILEIO.CPP file.

 

First we need a way to get the path to the executable "blades of exile.exe". I didn't find a function for it in the source code (not certain about that) so here is a proposition of such a fonction :

 

Click to reveal..
void Get_Path(char* path){

char szDirectory[MAX_PATH] = ""; //initialization

GetModuleFileName(NULL,szDirectory,sizeof(szDirectory)-1); //get the executable full path

int i=MAX_PATH; // initialize the first while loop

while(szDirectory != '\\')

{

i--; // find the last '\' in the path to the executable

} // in order to get rid of 'blades of exile.exe'

 

szDirectory[i+1]='\0'; // close the string after the last '\'

 

int j=0; // initialize the second loop

for(j=0;j<i+1;j++)

{

if (szDirectory[j] == '\\')

{

szDirectory[j]='/'; // replace the '\' by '/' in the path

} // since BoE needs '/'

}

for(j=0;j<i+2;j++)

{

path[j]=szDirectory[j]; // transfert the path to argument string

}

 

}

 

Then we need to modify the paramaters in the void build_scen_headers() function :

 

  • Replace SendMessage(listbox,LB_DIR,0x0,(LPARAM) (LPCTSTR) "scenarios/*.exs");

    by

    Click to reveal..
    char path[MAX_PATH]; //initialization

    Get_Path(path); // get the path to the executable

    strcat(path,"scenarios/*.EXS"); // concatenate the rest of the string

     

    SendMessage(listbox,LB_DIR,0x0, (LONG) path ); // send the message with correct path

  • and replace, a few line under, sprintf((char *) filename,"scenarios/%s",filename2);

    by

    Click to reveal..
    Get_Path(path); // get the path to the executable

    strcat(path,"scenarios/"); // concatenate the

    strcat(path,filename2); // rest of the string

    sprintf((char *) filename,path); // sprintf with correct path

 

and it should work : the program would now look for scenarios in the "[path_to_BoE]/scenarios" folder.

This fix has been tested with the latest Ormus' source code and worked. (thanks to nerubianlord for pointing out that the file extension was zip and not rar, allowing me to get the files)

 

For modularity, the path to the scenario folder could also be stored and read in the .ini file, thus being editable without having to recompile the sources.

 

Hope it helps,

Chokboyz

Link to comment
Share on other sites

Addentum : seems like i forgot to say something important ... rolleyes

 

While the program looks for "scenarios" folder in the same folder as the loaded savefile, it tries to load the actual scenario from the scenario folder located in the Blade of Exile directory ...

 

So the quick "fix" mentionned above means : copy the "scenarios" folder to these two locations (savefile folder and blade of exile folder).

 

It seems i'm not the first to notice that (although it is not necessary My Documents/Scenarios but the folder your savefile is in),

Originally Posted By: UA
Looks like scenarios need to be in both the Documents\Scenarios directory and the Blades of Exile\Scenarios directory. Ormus's build is building the list of scenarios from Documents\Scenarios and trying to load scenarios from Blades of Exile\Scenarios.

but i should add it anyway ...

Link to comment
Share on other sites

Good work, Chokboyz. I have been trying to get around this problem, and did it in a similar way. For some reason, the LB_DIR message sent to the listbox cannot handle a relative path, but instead only accepts an absolute, so I have to use GetModuleFileName, to obtain the path. Also, you do not need to add a '/' to the path, you can use a regular '\'.

 

EDIT: I should also point out that, for some strange reason, the SendMessage function returns LB_ERR even if it finds the files and they're in the right place. A bug perhaps?

Link to comment
Share on other sites

Hello,

 

i've finally understood the custom scenarios loading mechanics and found what was wrong.

 

- "Technical" considerations :

 

First of all, to load a scenario, BoE only needs the variable party.scen_name (a string which contains the "name_of_the_scen.exs") to be set. This variable is stored and read in savefile (cf in FILEIO.cpp, in the function save_file() : WriteFile(file_id, party.scen_name, 256, &dwByteRead, NULL); and in the load_file() function : ReadFile(file_id, party.scen_name, 256, &dwByteRead, NULL);) and that explains why while in a scenario, loading works smoothly.

 

Second, concerning the prefabricated scenarios, the party.scen_name is hardcoded (in BLADES.cpp, handle_startup_press() switch case 3) and that's why, while custom scenario loading is broken, prefabricated ones works.

 

Clicking the custom scenario button, call the pick_a_scen() function (in BLADES.cpp, handle_startup_press() switch case 4) which try to return the number of the scenario in the list of all founded scenario.

Right after initializing variables, the pick_a_scen() function calls the build_scen_headers() function which creates a listbox and tries to populates it with all "/scenarios/*.exs" founded in the current directory (relative path). And here is where the bug occurs.

 

- The bug :

 

Trying to populate the listbox, build_scen_headers() looks in current folder which is ... savefile folder. Indeed, in load_file() (resp save_file()) the function GetOpenFileName() (resp GetSaveFileName()) is used and change the current directory to the one of the selected file permanently. Having to load a party to pick a custom scenario, the build_scen_headers() is then looking in the wrong savefile folder to populate the listbox.

 

- A note of interest :

 

The function build_scen_file_name() would suffers from the same bug, except the first 8 lines of the code are a fix to avoid using current directory. Oddly enough, this fix has not been used for build_scen_headers, preventing it to work as intended.

 

Browsing the original sources, it seems this bug is present since the original release of the sources. Could it actually be that the "custom scenarios" button never worked ? confused

 

It's also worth to note that the fix in build_scen_file_name() use the global variable file_path_name[256] defined in WinMain() which is actually the full path to the executable. Since the scenario loading functions are seldom called, it could be better (memory wise) to actually replace this global variable by a call to a function (it's what i do in my proposed solution).

 

- Proposition of solution :

 

Here is a possible fix :

 

Legend : new_code, old_code, deleted_code

 

First we need to (re)define a function to get the current path to the executable.

 

The previous Get_Path() is here updated, hopefully cleaner (thanks again to nerubianlord for pointing out that '\' characters were also working. Note that file_path size is now 256 instead of MAX_PATH to comply with original code.)

 

Click to reveal..
In FILEIO.h :

void Get_Path(char* path);

 

In FILEIO.cpp :

void Get_Path(char* path){

 

char file_path[256]; // initialization

GetModuleFileName(NULL,file_path,256); // get path to the executable

 

int i=255; // initialize the first while loop

while(file_path != '\\')

{

i--; // find the last '\' in the path to the executable

} // in order to get rid of 'blades of exile.exe'

 

int j=0; // initialize the second loop

 

for(j=0;j<i+1;j++)

{

path[j]=file_path[j]; // transfert the path to argument string

}

path[i+1]='\0'; // close the argument string after the last '\'

}

 

then we correct the current directory in build_scen_headers() (in FILEIO.cpp) :

 

 

Click to reveal..
[...]

listbox = CreateWindow("listbox", NULL, WS_CHILDWINDOW, // 3

0,0,0,0, // 7

mainPtr, // 8

(HMENU) 1, // 9

(HINSTANCE) GetWindowLong(mainPtr, GWL_HINSTANCE), // 10

NULL); // 11

 

char file_path[256]="";

Get_Path(file_path); // get path to the directory the executable is in

SetCurrentDirectory(file_path); // change the current directory to the right one

 

SendMessage(listbox,LB_DIR,0x0,(LPARAM) (LPCTSTR) "scenarios/*.exs");

[...]

 

and the "custom scenarios" button and listing should now works.

 

The next changes are not part of the fix, but an attempt to make a cleaner code.

 

We replace the fix in build_scen_file_name() (in FILEIO.cpp) by a simple call to Get_Path() :

 

Click to reveal..
void build_scen_file_name (char *file_n)

{

short i,last_slash = -1;

 

for (i = 0; i < 256; i++)

if ((file_path_name == 92) || (file_path_name == '/'))

last_slash = i;

 

if (last_slash < 0)

{

sprintf((char *) file_n,"scenarios/%s",party.scen_name);

return;

}

 

char file_path[256];

Get_Path(file_path); //get executable directory

 

strcpy(file_n,file_path); // copy the executable directory.

file_n += last_slash + 1;

sprintf((char *) file_n,"scenarios/%s",party.scen_name);

}

 

Finally, we get rid of the now useless global variable file_path_name[256] (yes, it was only used by build_scen_file_name()) :

 

Click to reveal..
In BLADES.cpp :

int WINAPI WinMain (...)

[...]

GetModuleFileName(hInstance,file_path_name,256);

[...]

 

In GLOBVAR.cpp :

[...]

char file_path_name[256];

[...]

 

In GLOBVAR.h :

[...]

extern char file_path_name[256];

[...]

 

Hope it helps,

Chokboyz

Link to comment
Share on other sites

The changes you suggested made it impossible to compile the Windows source code on Bloodshed Dev-Cpp. I got it to compile with the changes below, then it crashed when I tried to load a custom scenario. I was still able to load the prefab scenarios.

 

void build_scen_headers()

{

short i;

short cur_entry = 0;

HWND listbox;

WORD count;

char filename[256],filename2[256];

void Get_Path(char* path);

char* path;

for (i = 0; i < 100; i++)

scen_headers.flag1 = 0;

Link to comment
Share on other sites

Strange, it compiles fine when i do ...

What is the error message ? In which file/function does it occurs ?

 

About the changes you made :

void build_scen_headers()

{

short i;

short cur_entry = 0;

HWND listbox;

WORD count;

char filename[256],filename2[256];

void Get_Path(char* path); <= definition of the function Get_Path (should already be defined so should not compiled, checks if you declared the Get_Path function in your FILEIO.h)

char* path; <= this is a pointer to an unspecified array, not initialized ?

for (i = 0; i < 100; i++)

scen_headers.flag1 = 0;

 

Chokboyz

Link to comment
Share on other sites

This error occurring after I deleted the void Get_path and char* path lines. It is currently objecting to:

strcat(path,"scenarios/"); // concatenate the

 

It may also object to:

strcat(path,filename2); // rest of the string

sprintf((char *) filename,path); // sprintf with correct path

 

void build_scen_headers()

{

short i;

short cur_entry = 0;

HWND listbox;

WORD count;

char filename[256],filename2[256];

for (i = 0; i < 100; i++)

scen_headers.flag1 = 0;

listbox = CreateWindow("listbox", NULL, WS_CHILDWINDOW, // 3

0,0,0,0, // 7

mainPtr, // 8

(HMENU) 1, // 9

(HINSTANCE) GetWindowLong(mainPtr, GWL_HINSTANCE), // 10

NULL); // 11

 

char file_path[256]="";

Get_Path(file_path); // get path to the directory the executable is in

SetCurrentDirectory(file_path); // change the current directory to the right one

SendMessage(listbox,LB_DIR,0x0,(LPARAM) (LPCTSTR) "scenarios/*.exs");

count = (WORD) SendMessage(listbox,LB_GETCOUNT,0,0L);

count = min(count,20);

 

for (i = 0; i < count; i++)

{

SendMessage(listbox,LB_GETTEXT,i,(LONG) (LPSTR) filename2);

Get_Path(path); // get the path to the executable

strcat(path,"scenarios/"); // concatenate the

strcat(path,filename2); // rest of the string

sprintf((char *) filename,path); // sprintf with correct path

 

if (load_scenario_header(filename,cur_entry) == TRUE)

{

// now we need to store the file name, first stripping any path that occurs

// before it

strcpy((char *) data_store2->scen_names[cur_entry],(char *) filename2);

cur_entry++;

}

}

 

DestroyWindow(listbox);

}

 

Link to comment
Share on other sites

Ok, i see; there are leftovers from the previous "fix" ... My bad, i should have added that previous changes needed to be removed.

 

You must remove the three lines (those with comments) right after SendMessage(listbox,LB_GETTEXT,i,(LONG) (LPSTR) filename2);

and revert the last one by sprintf((char *) filename,"scenarios/%s",filename2);

 

Here is my load_scen_headers() function if you just want to copy/paste

 

Click to reveal..
void build_scen_headers()

{

short i;

short cur_entry = 0;

HWND listbox;

WORD count;

char filename[256],filename2[256];

 

for (i = 0; i < 100; i++)

scen_headers.flag1 = 0;

 

listbox = CreateWindow("listbox", NULL, WS_CHILDWINDOW, // 3

0,0,0,0, // 7

mainPtr, // 8

(HMENU) 1, // 9

(HINSTANCE) GetWindowLong(mainPtr, GWL_HINSTANCE), // 10

NULL); // 11

 

char file_path[256]=""; // these are the only three lines added to the original function

Get_Path(file_path);

SetCurrentDirectory(file_path);

 

SendMessage(listbox,LB_DIR,0x0,(LPARAM) (LPCTSTR) "scenarios/*.exs");

count = (WORD) SendMessage(listbox,LB_GETCOUNT,0,0L);

 

count = min(count,100);

 

for (i = 0; i < count; i++)

{

SendMessage(listbox,LB_GETTEXT,i,(LONG) (LPSTR) filename2);

sprintf((char *) filename,"scenarios/%s",filename2);

 

if (load_scenario_header(filename,cur_entry) == TRUE)

{

// now we need to store the file name, first stripping any path that occurs

// before it

strcpy((char *) data_store2->scen_names[cur_entry],(char *) filename2);

cur_entry++;

}

}

 

DestroyWindow(listbox);

}

 

and it should works ...

 

You may also want to check if you have updated the definition of Get_Path() as it doesn't do useless characters shifting in its the last definition.

 

Hope it helps,

Chokboyz

 

P.S : Technically, the compiler was objecting that the path variable was nowhere defined.

When you defined char* path; at the beginning of the function, you tell the compiler to create and reserve memory for a pointer named path. Unfortunately, pointers are not initialised (to NULL or whatever) in C, so it could point anywhere in the memory; hence the crash when Get_Path tries to write in the array it points to (incoming segmentation_fault).

Link to comment
Share on other sites

I reloaded the original source from the latest Ormus/Spiderweb code. I copied in the changes you listed and it then compiled and ran properly, ditto it loaded the scenarios properly. Now we need to fix those scenario icons, Scenpics.bmp is 851 here.

 

Generally, the formatting you used above will be totally lost when the relevant text is pasted into the IDE. One way around this is to just show the entire function.

 

Edit: the scenario icons problem was cured by restoring the original text of dlgutils.cpp, may have only been a problem for me.

Link to comment
Share on other sites

Quote:
One way around this is to just show the entire function.

Will do that from now on.

 

Quote:
the scenario icons problem was cured by restoring the original text of dlgutils.cpp, may have only been a problem for me.

Didn't have any icons problem here, so it seems, indeed, to be an altered DLGUTILS.cpp file ... (in fact, i didn't changed a single line of code in this file)

 

Nice to see it compiled and worked; this should finally fix the custom scenario selection bug.

 

I also fixed the Conceal Ability flag (and made some cosmetic changes), only to found you had already fixed it (amongt others things, thanks to your hardwork). Is there an updated list of bug to fix ? (Compiled Suggestion List is apparently not)

 

Chokboyz

Link to comment
Share on other sites

I am striking major problems with the BoE game. It seems that data from one scenario is not being cleaned out when another is loaded. Graphics from one scenario appear in another&& The three official scenarios can't be loaded from the main screen.

 

I am currently trying to understand what is happening. Whether it is a change that I have made myself or what.

 

Edit: the problem seems to occur with loading pre-existing save game files.

 

 

Link to comment
Share on other sites

Originally Posted By: Ishad Nha
I am striking major problems with the BoE game. It seems that data from one scenario is not being cleaned out when another is loaded. Graphics from one scenario appear in another&& The three official scenarios can't be loaded from the main screen.

I am currently trying to understand what is happening. Whether it is a change that I have made myself or what.

Edit: the problem seems to occur with loading pre-existing save game files.



It's almost certainly not a bug caused by you: the original version of BoE had a few weird glitches related to this as well.
Link to comment
Share on other sites

You are absolutely right, there are two major flaws in the previous code : first, assuming that only scenario loading needs to be in the executable directory (why i forget loading of game currently in a scenario, i don't know) and second, not initialising an array (in Get_Path) that gets check later ...

 

Here is the solution i propose : as soon as the pointer to the savefile is set (while loading or saving), return to the executable directory (so that the program behaves as intended); and initialise the file_path[256] array to "" to prevent random memory to be read in it.

 

Below, the functions that need to be changed in the FILEIO.ccp(use a clean copy of FILEIO.cpp to avoid leftovers; as usual changes from the original file are in cyan) :

 

Click to reveal..
in FILEIO.cpp

 

void Get_Path(char* path){

 

char file_path[256]=""; // initialization

GetModuleFileName(NULL,file_path,256); // get path to the executable

 

int i=255; // initialize the first while loop

while(file_path != '\\')

{

i--; // find the last '\' in the path to the executable

} // in order to get rid of 'blades of exile.exe'

 

 

int j=0; // initialize the second loop

 

for(j=0;j<i+1;j++)

{

path[j]=file_path[j]; // transfert the path to argument string

}

path[i+1]='\0'; // close the argument string after the last '\'

}

 

Click to reveal..
void load_file()

{

HANDLE file_id;

short i,j,k;

Boolean town_restore = FALSE;

Boolean maps_there = FALSE;

DWORD dwByteRead;

UINT count;

char *party_ptr;

char *pc_ptr;

short flag;

Boolean in_scen = FALSE;

 

short flags[3][2] = {{5790,1342}, // slot 0 ... 5790 - out 1342 - town

{100,200}, // slot 1 100 in scenario, 200 not in

{3422,5567}}; // slot 2 ... 3422 - no maps 5567 - maps

 

ofn.hwndOwner = mainPtr;

ofn.lpstrFile = szFileName;

ofn.lpstrFileTitle = szTitleName;

ofn.Flags = 0;

 

if (GetOpenFileName(&ofn) == 0) return;

 

file_id = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if (file_id == INVALID_HANDLE_VALUE) return;

 

char file_path[256]=""; // returns to executable directory

Get_Path(file_path);

SetCurrentDirectory(file_path);

 

for (i = 0; i < 3; i++)

{

if (ReadFile(file_id, &flag, sizeof(short), &dwByteRead, NULL) == FALSE)

{

CloseHandle(file_id);

return;

}

 

if ((flag != flags[0]) && (flag != flags[1]))

{

CloseHandle(file_id);

FCD(1063,0);

return;

}

 

if ((i == 0) && (flag == flags[1])) town_restore = TRUE;

if ((i == 1) && (flag == flags[0])) in_scen = TRUE;

if ((i == 2) && (flag == flags[1])) maps_there = TRUE;

}

 

// LOAD PARTY

[...]

 

Click to reveal..
void save_file(short mode) //mode 0 - normal 1 - save as

{

HANDLE file_id;

Boolean town_save = FALSE;

 

short i, j;

 

DWORD count, bytes, dwByteRead;

short flag;

short *store;

party_record_type *party_ptr;

setup_save_type *setup_ptr;

pc_record_type *pc_ptr;

current_town_type *town_ptr;

 

char *party_encryptor;

 

if ((in_startup_mode == FALSE) && (is_town()))

town_save = TRUE;

 

ofn.hwndOwner = mainPtr;

ofn.lpstrFile = szFileName;

ofn.lpstrFileTitle = szTitleName;

ofn.Flags = OFN_OVERWRITEPROMPT;

 

if ((mode == 1) || (in_startup_mode == TRUE)) {

if (GetSaveFileName(&ofn) == 0)

return;

}

 

if (strcmpi(&ofn.lpstrFile[ofn.nFileExtension], "savx") == 0)

{

/* for experimental formats of save-game files */

}

 

file_id = CreateFile(szFileName, GENERIC_WRITE, 0, NULL,

CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

if (file_id == INVALID_HANDLE_VALUE) return;

 

char file_path[256]=""; // returns to executable directory

Get_Path(file_path);

SetCurrentDirectory(file_path);

 

store = &flag;

 

flag = (town_save == TRUE) ? 1342 : 5790;

[...]

 

and it should works ...

 

Thanks for pointing that out,

Chokboyz

Link to comment
Share on other sites

Originally Posted By: Chokboyz
Is there an updated list of bug to fix ? (Compiled Suggestion List is apparently not)
It may not be completely up to date, but it's the most up to date one that's here, I think. A few bugs are mentioned in the thread that I haven't added to the first post, so you could go through the last page or three of the thread. And most of the bugs listed have not been fixed, I think. I'll remove them when they are fixed in both Mac and Windows versions and an executable with the fixes is released.

Stareye was working on the Mac version awhile ago and making changes that would alter the scenario format. I'm not sure whether those changes made it into Khoth's repository...
Link to comment
Share on other sites

Ok, i will browse the last pages of the topic and see what has already been done. Will also checks this scenario format changes you mentioned ... Thanks for the compiled list anyway ...

 

Quote:
I'll remove them when they are fixed in both Mac and Windows versions and an executable with the fixes is released.

Fair enough ... wink

 

Chokboyz

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...