Decoding the NosTale Minimap: Can We Go Bigger?

11/25/2023 14:35 pocow#1
NosTale offers a minimap that is relatively small in size. I’ve been exploring the game’s binaries in Cheat Engine, with the aim of resizing the NosTale Minimap.

So far I’ve managed to locate addresses related to the position, minimap rendering borders, and the collapse and position reset booleans. These booleans allow the minimap to be collapsed or moved to the top-right corner (original position) of the screen. More importantly, I’ve found several addresses linked to the actual size of the NosTale Minimap.

NOTE: The address' types of the following values are 2 Bytes. The size values represent the window containing the minimap image.

Uncollapsed Minimap:
Height = 185
Width = 163

Collapsed Minimap:
Height = 25
Width = 163

Despite these promising discoveries, my attempts to enlarge NosTale’s minimap by modifying these addresses have not been successful.

Based on my observations, it seems that when you collapse the minimap in NosTale, it doesn’t just shrink - it gets removed entirely. This led me to believe that the in-game size of the minimap’s image is the same as the size of the .png file. However, the window that houses the image might still be resizable, which might be crucial in enlarging the minimap's image as well.

I’m reaching out to those with more experience in reverse engineering: Have you tried to enlarge the NosTale Minimap? Do you think it’s possible? Any tips or feedback would be appreciated.
11/25/2023 14:58 Limoo#2
Quote:
Originally Posted by pocow View Post
NosTale offers a minimap that is relatively small in size. I’ve been exploring the game’s binaries in Cheat Engine, with the aim of resizing the NosTale Minimap.

So far I’ve managed to locate addresses related to the position, minimap rendering borders, and the collapse and position reset booleans. These booleans allow the minimap to be collapsed or moved to the top-right corner (original position) of the screen. More importantly, I’ve found several addresses linked to the actual size of the NosTale Minimap.

NOTE: The address' types of the following values are 2 Bytes. The size values represent the window containing the minimap image.

Uncollapsed Minimap:
Height = 185
Width = 163

Collapsed Minimap:
Height = 25
Width = 163

Despite these promising discoveries, my attempts to enlarge NosTale’s minimap by modifying these addresses have not been successful.

Based on my observations, it seems that when you collapse the minimap in NosTale, it doesn’t just shrink - it gets removed entirely. This led me to believe that the in-game size of the minimap’s image is the same as the size of the .png file. However, the window that houses the image might still be resizable, which might be crucial in enlarging minimap's image as well.

I’m reaching out to those with more experience in reverse engineering: Have you tried to enlarge the NosTale Minimap? Do you think it’s possible? Any tips or feedback would be appreciated.
Surely it's possible, I saw it in a video of Maza (the guy who spams his "creations")
11/26/2023 12:09 Bejine#3
[Only registered and activated users can see links. Click Here To Register...]

Yes you can definitely resize it.
You have to find the base TNTMiniMapWidget of the whole minimap widget, which will have a pointer to another TNTMiniMapWidget at 0xC0.
You will have to resize both of them (because the second one is a child of the first one), and then also in the second widget change X and Y scale for objects on the minimap - these are values at 0x78 and 0x7C, both floats (I think their default values were 0.8937500119 and 0.7944444418 respectively).
Keep in mind that resizing the first widget will not change the background size, as its a rectangle texture directly placed on the widget. You would also have to change the texture data which can be accessed by a pointer at 0x59.
But I'm too lazy to explain that part too.
In my example picture i changed the sizes a bit randomly so it got placed on top of the caption bar, but you can of course calculate them properly and place them better.

One important notice - after you minimalize the minimap with the button, the base widget will change its size to the original one. It's probably hardcoded somewhere in a function, so if you can find it it should be trivial to just change the integers in memory to your custom ones.
[Only registered and activated users can see links. Click Here To Register...]

EDIT:
Keep in mind NosTale widgets use left, top, right and bottom coordinates instead of x, y, width, height. The values you wrote in your post are, from what I roughly checked, parts of texture data of the base widget background, but I might be wrong.
11/26/2023 15:44 Apourtartt#4
Probably worth mentioning that you can also allow the user to resize it (with the cursor like on item's sheet) by playing with 0xAC and 0xAD from TEWCustomFormWidget and its children.
0xAD is a bitflag defined as: [Only registered and activated users can see links. Click Here To Register...]
11/28/2023 00:04 pocow#5
I've found the right pointer to the Widgets. Thanks for the tips.

An advice I recently stumbled upon was to find an instruction pattern which accesses a static address that holds a pointer to a data structure.

I followed the advice and identified A1 DC BE 72 00 => mov eax,[NostaleClientX.exe+0032BEDC] as an instruction pattern accessing the static address that holds the pointer to the TNTMiniMapBaseWidget.

If I understood the advice correctly, does this mean that in case NosTale receives a game update that cause a shift of pointer locations, I still can find the TNTMiniMapWidget's pointer just by scanning for this instruction pattern?
11/28/2023 10:49 Apourtartt#6
That's it
If you are using NostaleWidget ([Only registered and activated users can see links. Click Here To Register...]), you can also just do a
Code:
TGameRootWidget* grw = TLBSWidgetHandler::getNtInstance()->getGameRootWidget();
auto widgets = grw->findClassesWithExpectedSize("TNTMiniMapWidget", 1);
TNTMiniMapWidget* minimap = reinterpret_cast<TNTMiniMapWidget*>(widgets[0]);
// Then you can play with this TNTMiniMapWidget as you wish
12/09/2023 20:44 pocow#7
While reverse-engineering the TNTMiniMapBaseWidget data structure I noticed gaps between values.

[Only registered and activated users can see links. Click Here To Register...]

For the sake of convenience let's take the TNTMiniMapBaseWidget as example data structure.

Given the following pseudocode:

Code:
class TNTMiniMapBaseWidget : Widget {
...
word MinimapRightBorder = 880; // Offset: 0014, Address: EC25124
word MinimapBottomBorder = 580; // Offset: 0016, Address: EC25126
dword pointer0 = 01000101; // Offset: 0018, Address: EC25128 (no pointer)
dword pointer1 = 00010001; // Offset: 001C, Address: EC2512C (no pointer)
dword pointer2 = 0F3B54E0; // Offset: 0020, Address: EC25130

// Gap of 7 addresses with each having a value of 0

byte Byte = 255; // Offset: 0040, Address: EC25150
byte MinimapBackgroundBlueColor = 255; // Offset: 0041, Address: EC25151
byte MinimapBackgroundGreenColor = 255; // Offset: 0041, Address: EC25152
...
}
How exactly are these gaps represented in the source code version of a data structure?

Why are these gaps there at all?
12/11/2023 00:10 Apourtartt#8
You can use IDR (Interactive Delphi Reconstructor) to give you an idea (it is not always accurate) of the underlying type. Might also be only padding (see: https://en.wikipedia.org/wiki/Data_structure_alignment)

In the case of the TNTMiniMapWidget, I don't have much info:
Code:
class TMiniMapCore : public TEWControlWidget
{
public:
	static constexpr std::string_view NAME = "TMiniMapCore";

protected:
	char unknown12[16];							//0x68
};
static_assert(sizeof(TMiniMapCore) == 0x78, "TMiniMapCore does not have a size of 0x78.");

class TNTMiniMapWidget : public TMiniMapCore
{
public:
	static constexpr std::string_view NAME = "TNTMiniMapWidget";

protected:
	float xIconOffset;							//0x78
	float yIconOffset;							//0x7C
	char unknown13[4];							//0x80 Change on map change A0 00 B4 00 for nosville
	int32_t mapId;								//0x84
	char unknown14[4];							//0x88 Change on map change 02 00 00 00
	float xPlayerPosition;						//0x8C
	float zPlayerPosition;						//0x90
	float yPlayerPosition;						//0x94
	char unknown15[4];							//0x98
	TMapPlayerObj* player;						//0x9C
	TMapNpcObj* npc;							//0xA0
	void* unknownList;							//0xA4
	void* unknownList2;							//0xA8
	void* unknownList3;							//0xAC
	TList<TMapNpcObj>* npcsList;				//0xB0
	void* unknownList5;							//0xB4
	TList<TMapMonsterObj*>* bossList;			//0xB8
	void* unknownList7;							//0xBC
	void* unknownList8;							//0xC0
	bool displayPlayer;							//0xC4
	bool unknown16;								//0xC5
	bool unknown17;								//0xC6
	bool unknown18;								//0xC7
	bool displayNpcs;							//0xC8
	bool unknown19;								//0xC9
	bool unknown20;								//0xCA
	bool unknown21;								//0xCB
	char unknown22[8];							//0xCC
	float xCursorPosition;						//0xD4
	float yCursorPosition;						//0xD8
	uint8_t iconTypeHovered;					//0xDC 1 for portal, 3 for npc, 255 for nothing
	char unknown23[7];							//0xDD
	void* npcHint; //TMapNpcObj					//0xE4
	char unknown23[8];							//0xE8
};
static_assert(sizeof(TNTMiniMapWidget) == 0xF0, "TNTMiniMapWidget does not have a size of 0xF0.");