[RM2K3] DOES DYNRPG SUPPORT MAP MANIPULATION?
Posts
Pages:
1
Is it possible to change a specific tile on the map with DynRPG? To calrify, I do not want to change multiple tile types, but a single one. Ideally I would like to be able to create a plugin that allows the user to dynamically change the maps via switches and variables, so that makers don't have to make multiple maps with slight differences when they want an area changed on their map.
Here is an interface for the map tiles:
Untested, but should work, let me know if it doesn't...
Note: Since this is data which is normally not changable ingame, it means any change here persists for the game session (until game is closed) and is not affected by saving/loading/resetting the game - you have to take care of this yourself.
class TileMap { public: void **vTable; int width; int height; bool loopX; bool loopY; short *tiles; inline short *tile(int x, int y) { if (x < 0 || x >= width || y < 0 || y >= height) return NULL; return &tiles[y * width + x]; }; }; class DbMap { public: void **vTable; int _unknown[12]; TileMap *lowerLayer; TileMap *upperLayer; }; DbMap *&dbMap = (**reinterpret_cast<DbMap ***>(0x4CDD14)); // *** EXAMPLE: *** // Change lower layer tile at (3, 5) to tile ID 5000 short *tile = dbMap->lowerLayer->tile(3, 5); if (tile) { std::cout << "Current tile ID: " << *tile << std::endl; *tile = 5000; }
Untested, but should work, let me know if it doesn't...
Note: Since this is data which is normally not changable ingame, it means any change here persists for the game session (until game is closed) and is not affected by saving/loading/resetting the game - you have to take care of this yourself.
author=CherryWow, I didn't expect to get a reply so soon, thanks! This is exactly what I was looking for. It works fine, but the tile id is confusing. It seems to include every tile the auto tile can create. Do you happen to know the order of the tile ids? If not, that is okay I will just have to test each one. Also why is there std::cout in the code, does that work in DynRPG? Also I have a few questions about this:
Here is an interface for the map tiles:class TileMap { public: void **vTable; int width; int height; bool loopX; bool loopY; short *tiles; inline short *tile(int x, int y) { if (x < 0 || x >= width || y < 0 || y >= height) return NULL; return &tiles[y * width + x]; }; } class DbMap { public: void **vTable; int _unknown[12]; TileMap *lowerLayer; TileMap *upperLayer; } DbMap *&dbMap = (**reinterpret_cast<DbMap ***>(0x4CDD14)) // *** EXAMPLE: *** // Change lower layer tile at (3, 5) to tile ID 5000 short *tile = dbMap->lowerLayer->tile(3, 5); if (tile) { std::cout << "Current tile ID: " << *tile << std::endl; *tile = 5000; }
Untested, but should work, let me know if it doesn't...
Note: Since this is data which is normally not changable ingame, it means any change here persists for the game session (until game is closed) and is not affected by saving/loading/resetting the game - you have to take care of this yourself.
class TileMap { public: void **vTable; int width; //How is this being utilized? int height; //? bool loopX; //? bool loopY; //? short *tiles; inline short *tile(int x, int y) { if (x < 0 || x >= width || y < 0 || y >= height) return NULL; return &tiles[y * width + x]; }; }; class DbMap { public: void **vTable; int _unknown[12]; //This is? TileMap *lowerLayer; TileMap *upperLayer; }; DbMap *&dbMap = (**reinterpret_cast<DbMap ***>(0x4CDD14));
author=DNKppThe change chipset commad just swaps chipsets. It does not change specific tiles, but instead swaps all the tiles to the ones listed in the new chipset.
Isn't there a change chipset command for exactly this purpose? Otherwise handle it via event.
Do you happen to know the order of the tile ids?No, sorry, I don't know that. You will have to use trial and error to figure it out. EasyRPG should probably have some docs about that but I couldn't find it.
Also why is there std::cout in the code, does that work in DynRPG?I wrote that just for demonstration purposes - but it will actually work if you allocate a console:
#include <stdio.h> // in onStartup: AllocConsole(); // No include needed because DynRPG already includes the WinAPI headers freopen("CON", "w", stdout);
Are these variables getting their values somewhere else in DynRPG?No, they are used by the RPG Maker itself (the pointer to the DbMap instance of the currently map is located at the address that you can get by dereferencing address 0x4CDD14 twice, as is written in the last line of my code).
Also if I change a tile, can I still retrieve its original value some how? It seems to be a volatile memory change, so the original value should still exist somewhere?Well, in the map file itself on disk, but you won't have an easy way of accessing that ^^
//How is this being utilized?Width and height are used in the function that I wrote, they are needed to know the array bounds and the size of the first dimension. About the looping flags (indicating whether the map horizontally and/or vertically loops), you probably won't need them, I just included them for completeness, since I already know what fields they are. I could have used "int _unknown" too.
//This is?I don't know what fields exist at those 48 bytes, that's why the placeholder is called _unknown. There is probably other stuff about the map in there, such as its music or encounters or such. I just know that the tile map pointers start at offset 0x34 so this is padding to ensure our own definition also places them at that offset.
Also, I realized that while technically changes in those normally-not-saved-and-loaded structures do persist for the game session, in this case loading a new map will of course void any changes as well. But you still need to take care of saving and loading changes yourself so that you can reapply them when loading from a save file. (Loading a save file or starting a new game (re)loads the map as far as I know, so you don't have to worry about restoring the original tiles at least.)
@Cherry
I am unfamiliar with void **vTable(virtual table function?), but if I am understanding correctly the variables width, height, loopX, and loopY are being dereferenced by dbMap? I am guessing these variables point to an offset in 0x4CDD14(Map Data)? Also is the width and height variables just the size of the chipset in pixels or is divided into tiles? Just to be clear, I understand how the classes work for the most part, but I am confused about those variables because I don't completely understand where they are getting their values, so that they function in the if statement properly.
I understand that, but how does that work? Aren't variables locked in their scope? I see dbMap is dereferenced, but how does that apply to width, height, loopX, loopY, and unknown? I am guessing void **vTable is doing something to them?
This is interesting. I never thought of doing something like this in DynRPG.
I have rarely used virtual tables before, so sorry if my questions sound stupid. I tried looking up void **vTable, but nothing really comes up beside virtual tables, so I am unsure what this is exactly. I am guessing it is part of RPGMaker or DynRPG in some way.
I am unfamiliar with void **vTable(virtual table function?), but if I am understanding correctly the variables width, height, loopX, and loopY are being dereferenced by dbMap? I am guessing these variables point to an offset in 0x4CDD14(Map Data)? Also is the width and height variables just the size of the chipset in pixels or is divided into tiles? Just to be clear, I understand how the classes work for the most part, but I am confused about those variables because I don't completely understand where they are getting their values, so that they function in the if statement properly.
author=Cherry
No, they are used by the RPG Maker itself (the pointer to the DbMap instance of the currently map is located at the address that you can get by dereferencing address 0x4CDD14 twice, as is written in the last line of my code).
I understand that, but how does that work? Aren't variables locked in their scope? I see dbMap is dereferenced, but how does that apply to width, height, loopX, loopY, and unknown? I am guessing void **vTable is doing something to them?
author=Cherry#include <stdio.h> // in onStartup: AllocConsole(); // No include needed because DynRPG already includes the WinAPI headers freopen("CON", "w", stdout);
This is interesting. I never thought of doing something like this in DynRPG.
I have rarely used virtual tables before, so sorry if my questions sound stupid. I tried looking up void **vTable, but nothing really comes up beside virtual tables, so I am unsure what this is exactly. I am guessing it is part of RPGMaker or DynRPG in some way.
(Width and height are in tiles, it's just the size of the two-dimensional tiles array.)
The vTable has nothing to do with it, it's just a pointer to Delphi's virtual function table for each object, which is again included for completeness but it's not needed here, we could also put an "int _unknown" instead.
You are looking at this from a wrong point of view. Things being locked in their scope and what a variable logically "is" are just concepts put up by the compiler. At the end of the day, it's data in memory which the computer will read from and write to based on machine code instructions created by the compiler based on your code.
Most of what DynRPG's header files do (and what my definitions here do as well) is to create C++ structures which will look the same in memory (as in, have the same things at the same places relative to each other) as the equivalent Delphi structures do which the RPG Maker already uses. And then I use the "SomeType &someValue = *reinterpret_cast<SomeType *>(memoryAddressHere)" construct to force C++ to "put" someValue at memoryAddressHere, at which point a variable from the RPG Maker already exists, hence we then have the same underlying value now accessible by both of us (both readable and writable). (Plus, I also sometimes call an RPG Maker function directly, which requires inline assembly because Delphi uses a different calling convention which C++ doesn't support.)
So, this whole construct - the DbMap instance, the two TileMap instances whose pointers are saved inside the DbMap structure, and the tiles arrays whose pointers are saved in the TileMap structures - already exists, the RPG Maker created it internally. All we do (ignoring the double-derefencing for a moment, which is a technical implementation detail in Delphi not relevant to the overall technique) is telling the compiler that our variable "dbMap" is to be put at a specific memory address (it's actually the address that's already written at 0x4CDD14, which is 0x4D1F70 in RM, but that doesn't matter for this explanation) instead of allocating it on the stack or heap. This works because we are not actually assigning dbMap in this line, we are assigning &dbMap, it's a reference assignment, and the reference is thereby linked to the address, similarly how "int a = 1; int &b = a; b = 2; /* a is now also 2 */" works. This is the address at which RPG Maker has already put its own dbMap variable, so we now just got access to this piece of information in RM's memory by a name we can use (dbMap). Since the whole structure of the DbMap and its two TileMaps and their tiles arrays already exists in memory (RM has created it for itself), we don't have to worry about that, we just tapped into the existing structure. If the RPG Maker changes something, we'll see it, and the other way round as well, since we are just operating on the exact same data now.
Simplified example of how it works - we have an instance xyzThing of a structure XYZ defined by the RPG Maker, and it has some fields x, y, a, b, z in it, and our own code (which doesn't have access to RPG Maker's source code which can even be in another programming language) would like to get access to the fields a and b of this xyzThing instance - this is how it can be done:
You can see it working here: https://onlinegdb.com/Bkj2hqqeu - you can see it prints 333, 444, 999 as expected.
The vTable has nothing to do with it, it's just a pointer to Delphi's virtual function table for each object, which is again included for completeness but it's not needed here, we could also put an "int _unknown" instead.
You are looking at this from a wrong point of view. Things being locked in their scope and what a variable logically "is" are just concepts put up by the compiler. At the end of the day, it's data in memory which the computer will read from and write to based on machine code instructions created by the compiler based on your code.
Most of what DynRPG's header files do (and what my definitions here do as well) is to create C++ structures which will look the same in memory (as in, have the same things at the same places relative to each other) as the equivalent Delphi structures do which the RPG Maker already uses. And then I use the "SomeType &someValue = *reinterpret_cast<SomeType *>(memoryAddressHere)" construct to force C++ to "put" someValue at memoryAddressHere, at which point a variable from the RPG Maker already exists, hence we then have the same underlying value now accessible by both of us (both readable and writable). (Plus, I also sometimes call an RPG Maker function directly, which requires inline assembly because Delphi uses a different calling convention which C++ doesn't support.)
So, this whole construct - the DbMap instance, the two TileMap instances whose pointers are saved inside the DbMap structure, and the tiles arrays whose pointers are saved in the TileMap structures - already exists, the RPG Maker created it internally. All we do (ignoring the double-derefencing for a moment, which is a technical implementation detail in Delphi not relevant to the overall technique) is telling the compiler that our variable "dbMap" is to be put at a specific memory address (it's actually the address that's already written at 0x4CDD14, which is 0x4D1F70 in RM, but that doesn't matter for this explanation) instead of allocating it on the stack or heap. This works because we are not actually assigning dbMap in this line, we are assigning &dbMap, it's a reference assignment, and the reference is thereby linked to the address, similarly how "int a = 1; int &b = a; b = 2; /* a is now also 2 */" works. This is the address at which RPG Maker has already put its own dbMap variable, so we now just got access to this piece of information in RM's memory by a name we can use (dbMap). Since the whole structure of the DbMap and its two TileMaps and their tiles arrays already exists in memory (RM has created it for itself), we don't have to worry about that, we just tapped into the existing structure. If the RPG Maker changes something, we'll see it, and the other way round as well, since we are just operating on the exact same data now.
Simplified example of how it works - we have an instance xyzThing of a structure XYZ defined by the RPG Maker, and it has some fields x, y, a, b, z in it, and our own code (which doesn't have access to RPG Maker's source code which can even be in another programming language) would like to get access to the fields a and b of this xyzThing instance - this is how it can be done:
// *************************************** // This part of the code is in reality written in Delphi and not C++ and existing in the RPG Maker: class XYZ { public: // Note: Would also work with "private:"! But it simplifies the example. int x; int y; int a; int b; int z; }; XYZ xyzThing; xyzThing.x = 111; xyzThing.y = 222; xyzThing.a = 333; xyzThing.b = 444; xyzThing.z = 555; // *************************************** // This part of the code is actually C++, in DynRPG or your own code, which is loaded into the // same process as the RPG Maker's code so it has access to all its memory: // We are now defining a structure whose memory layout is identical to XYZ, at least up to // the last field we are interested in class Something { public: // Let's say we don't know what the fields at the first 8 bytes in the structure are // (they were originally x and y) and we don't care about their value much int _unknown[2]; // We know this field is interesting for us (it's originally called a, but we can't know that) int foo; // And this field is interesting for us too (it's originally called b, but we can't know that) int bar; // The original structure also had a field z at the end but we don't care about that one, // we may not even be aware of its existence, and there is nothing after it in // the structure which we are interested in, so it doesn't matter (unlike x and y for // which had to define *some* fields as padding because the a and b came afterwards // and had to be aligned at the right location). }; // Let's say we know for sure that xyzThing is always located at address 0x12345678, then we can write // this reference assignment to get access to the same "thing" that is known as xyzThing to the // third-party code that we don't normally have access to: // Something &something = *reinterpret_cast<Something *>(0x12345678) // However, to make this example actually compile, let's take advantage of the fact that this // *is* an example where we *do* actually have xyzThing in scope. The following line does much // the same as "Something &something = xyzThing;", but the latter wouldn't compile because according // to the compiler, Something and XYZ are different types. // Again - in reality we won't have access to "xyzThing" as variable, that's why // we have to use its hardcoded memory address! This is just to make the example // work. In reality we don't have access to anything of the code written above, // the only "touching point" is that fact that both the RPG Maker's code and our // own code live in the same process, i.e. memory space! Something &something = *reinterpret_cast<Something *>(&xyzThing); std::cout << something.foo << std::endl; // This will print 333, which is actually xyzThing.a! std::cout << something.bar << std::endl; // This will print 444, which is actually xyzThing.b! something.foo = 999; // Let's change something! // *************************************** // Now, the following part of the code is again RPG Maker's own code which wouldn't // actually be C++ in reality. Let's say at some point later on, the RPG Maker // now accesses xyzThing.a - which is known to us as something.foo above, i.e. // we just changed this value! std::cout << xyzThing.a << std::endl; // Prints 999! // So, we achieved full read and write access to the data known as xyzThing to one // part of the code from another part of the code, without having its source code or having // access to its class definition or even variable definition (except for the line // where we use &xyzThing but again that's just to make this example work smoothly.)
You can see it working here: https://onlinegdb.com/Bkj2hqqeu - you can see it prints 333, 444, 999 as expected.
@Cherry
Ah, that makes a lot of sense now. It seems I have been misunderstanding how DynRPG works until now. I thought it was replacing rm2k3's source code, but in fact it is just occupying the same memory which allows you to manipulate rm2k3. This explains why most, if not all, changes made in DynRPG are volatile too. Your example gives me a much better idea on how to approach DynRPG too now. Thanks for the explanation!
If I am understanding correctly, I should be able to expand DynRPG, by creating my own headers that points to new memory?
Ah, that makes a lot of sense now. It seems I have been misunderstanding how DynRPG works until now. I thought it was replacing rm2k3's source code, but in fact it is just occupying the same memory which allows you to manipulate rm2k3. This explains why most, if not all, changes made in DynRPG are volatile too. Your example gives me a much better idea on how to approach DynRPG too now. Thanks for the explanation!
If I am understanding correctly, I should be able to expand DynRPG, by creating my own headers that points to new memory?
@Cherry
Would it be possible to change a tile to one that is from a different chipset, without changing the chipset?
Would it be possible to change a tile to one that is from a different chipset, without changing the chipset?
I guess the only useful way would be to modify the actual graphics of the chipset that is loaded (in memory).
After a bit more digging, I think I know how to get into the actual graphic - here are updated class definitions:
(RPG::Image and RPG::DStringPtr are documented in DynRPG.)
Note that there isn't only "image", there is also "imageDarkened", which seems to be another copy of the graphics data but with a plalette whose colors are darkened a bit. I'm not sure what it's used for, it's possible that it's not used in the game at all and maybe only used in the editor when you select one map layer and the other layer is drawn in a darkened fashion - in that case you can ignore it.
Also you will have to play with it and see how it behaves when changing maps, restarting the game and such, because I don't know if the chipset graphics are loaded from disk every time or maybe they are cached and reused if the same chipset file would be loaded again (in which case your changes would be applied to a different map as well and you'd have to undo them when you see the map changes even if the chipset filename were the same).
After a bit more digging, I think I know how to get into the actual graphic - here are updated class definitions:
class TileMap { public: void **vTable; int width; int height; bool loopX; bool loopY; short *tiles; inline short *tile(int x, int y) { if (x < 0 || x >= width || y < 0 || y >= height) return NULL; return &tiles[y * width + x]; }; }; class Chipset { public: void **vTable; int _unknown; RPG::DStringPtr filename; RPG::Image *image; RPG::Image *imageDarkened; }; class DbMap { public: void **vTable; int _unknown[12]; TileMap *lowerLayer; TileMap *upperLayer; int _unknown2; Chipset *chipset; }; DbMap *&dbMap = (**reinterpret_cast<DbMap ***>(0x4CDD14)); // You can access the filename of the chipset, if you are interested, like this: std::cout << (std::string) dbMap->chipset->filename << std::endl; // You can get a pointer to the actual graphic RPG::Image *chipsetGraphic = dbMap->chipset->image; // You can change something there - let's change the pixel at (40, 50) in the graphic // to palette color 10 chipsetGraphic->pixel(40, 50) = 10;
(RPG::Image and RPG::DStringPtr are documented in DynRPG.)
Note that there isn't only "image", there is also "imageDarkened", which seems to be another copy of the graphics data but with a plalette whose colors are darkened a bit. I'm not sure what it's used for, it's possible that it's not used in the game at all and maybe only used in the editor when you select one map layer and the other layer is drawn in a darkened fashion - in that case you can ignore it.
Also you will have to play with it and see how it behaves when changing maps, restarting the game and such, because I don't know if the chipset graphics are loaded from disk every time or maybe they are cached and reused if the same chipset file would be loaded again (in which case your changes would be applied to a different map as well and you'd have to undo them when you see the map changes even if the chipset filename were the same).
author=Cherry
I guess the only useful way would be to modify the actual graphics of the chipset that is loaded (in memory).
Doing that would retain the pathing/terrain properties, wouldn't it? Also if I changed the graphic of a tile, then wouldn't that change all tiles on the map also? I guess I will start by experimenting with the Chipset class first and see how rm2k3 handles the changes to the tile graphics..
It won't change pathing/terrain, this is stored somewhere else (haven't reverse-engingeered that yet). But yes, changing the graphic of a tile inside the chipset graphic of course will change its representation of the map in all places where the tile is used (as long as your change is still present in memory).
Pages:
1














