Welcome to Spyro the Dragon Forums!

You are not logged in.

#1 Jul 31, 2015 3:31 PM

Sheep
Member
Award: Skateboard Contest Winner
From: Norway
Registered: Jan 24, 2008
Posts: 983
Gems: 0
Birthday: 20 January
Age: 25 years old
Gender: Male
Website

Collision data hacking

For about a week now, I've been trying to both understand the level collision system, and extract the data from the game. I've decided to make a topic about it to have somewhere to write my discoveries and struggles down, and in case some might find it interesting or perhaps would want to contribute.

The idea is based on the texture hacking made possible with LXShadow's SpyroEdit. I played with it a bit myself(and it's source code), to find that it also was very possible to move the terrain around, though only the visible model, since the level terrain model that Spyro collides with is slightly different to the one that is drawn and is visible, and is found elsewhere in the game memory. It would be so very fun to be able to both retexture and remodel the levels and create practically completely new levels, so I decided to see if I could find this other mesh and how it worked. I've made some progress at least.

I'm playing with the NTSC/US version of Spyro 2, and so far I've almost solely been focusing on Skelos Badlands, since it's there I've made the most progress, and the data is placed slightly differently in different levels.

Using the Playstation Emulation Cheater, [PEC], I was with relative ease able to isolate the game memory values that change when, and only when, the bridge in Skelos Badlands is unfolded. When I had done this, I tried randomly changing different values to see if anything happened. Through a good deal of trial and error, I was able to move around the individual invisible collision model triangles, and have Spyro seemingly stand in thin air as you can see here:

Hidden text

(I state some wrong things in the video).

By adding a few lines of code to SpyroEdit, to manipulate the right chunk of memory, I found that all the triangles in the level could be moved around like this, though with some limitations, due to the system they're part of.

Through some further tinkering in [PEC], I also found two bits that decide a triangle's terrain type. And again using SpyroEdit, I changed their value in most of a level's triangles:

Hidden text

---------------------------------------------------------------

What I know(or believe I know) at the moment is this:
- A level's collision mesh/model consists of a single, huge and compact list of triangles.
- While you're in Skelos Badlands, the collision model spans the memory address range 0x000E97A8 to 0x0010e48c, and (I think) is made up of 12 441 triangles.
- A triangle is made up of 12 bytes(which each is 8 bits):

Hidden text

A triangle is defined by an absolute X, Y and Z coordinate, which makes the first corner or vertex of the triangle.
The two other vertices are found by adding a pair of relative coordinates, (or vectors), to the first corner's position.
The relative coordinates are 8-bit.
The absolute coordinates are 14-bit.
Two bits each from the absolute coordinates, which would otherwise be 16-bit, are used for slightly different things.
For x and y, they seem to be used for some sort of precision correction to the relative coordinates.
For z, the two bits decide the type of the terrain.

  1: xxxx xxxx]    | 8 bits for the least significant part of the absolute x coordinate
  2: [ww][xx xxxx  | 2 bits, and then 6 bits for the most significant part of absolute x coordinate
  3: [xxxx xxxx]   | 8 bits for the first relative x coordinate
  4: [xxxx xxxx]   | 8 bits for the second relative x coordinate

  5: yyyy yyyy]    | 8 bits for the least significant part of the absolute y coordinate
  6: [ww][yy yyyy  | 2 bits, and then 6 bits for the most significant part of absolute y coordinate
  7: [yyyy yyyy]   | 8 bits for the first relative y coordinate
  8: [yyyy yyyy]   | 8 bits for the second relative y coordinate

  9: zzzz zzzz]    | 8 bits for the least significant part of the absolute z coordinate
 10: [tt][zz zzzz  | 2 bits for terrain type, and then 6 bits for the most significant part of absolute z coordinate
 11: [zzzz zzzz]   | 8 bits for the first relative z coordinate
 12: [zzzz zzzz]   | 8 bits for the second relative z coordinate

I have successfully imported the whole collision model into GameMaker, where I've rendered it like this, with the face color decided by the triangle's position in the list(from black to white):
skeloscolmodel.png
As you see, though there are cracks between the triangles, because I haven't yet figured out exactly how to use the mysterious pair of 2-bit values.
It might be that I've just somehow found a way to use the triangle data that works almost perfectly, but that I'm doing something very, very wrong. I have been struggling with it for more than two whole days with no progress, and am getting rather frustrated. This is my current code for importing the model in GameMaker, in GML(GameMaker Language):

Hidden text
var buffer = buffer_load("CollisionData.cld");
numTris = buffer_read(buffer, buffer_u32);

for(var n = 0; n < numTris; n++){
    var absX1 = buffer_read(buffer, buffer_u8),
    var absX2 = buffer_read(buffer, buffer_u8),
    var rX1 = buffer_read(buffer, buffer_u8),
    var rX2 = buffer_read(buffer, buffer_u8),
    var absY1 = buffer_read(buffer, buffer_u8),
    var absY2 = buffer_read(buffer, buffer_u8),
    var rY1 = buffer_read(buffer, buffer_u8),
    var rY2 = buffer_read(buffer, buffer_u8),
    var absZ1 = buffer_read(buffer, buffer_u8),
    var absZ2 = buffer_read(buffer, buffer_u8),
    var rZ1 = buffer_read(buffer, buffer_u8),
    var rZ2 = buffer_read(buffer, buffer_u8);
    
    absoluteX[n] = absX1 | ((absX2 & $3F ) << 8);
    absoluteY[n] = absY1 | ((absY2 & $3F ) << 8);
    absoluteZ[n] = absZ1 | ((absZ2 & $3F ) << 8);
    
    // The bits
    xBits[n] = (absX2 >> 6);
    yBits[n] = (absY2 >> 6);
    zBits[n] = (absZ2 >> 6); // Controls terrain type

    // How do I use these? Tried all I can think of! D:
    xBit1 = (xBits[n] & 1);
    xBit2 = ((xBits[n] >> 1) & 1);
    yBit1 = (yBits[n] & 1);
    yBit2 = ((yBits[n] >> 1) & 1);
  
    
    // For first relative x and y coordinates, move the most significant bit to the place of the least significant bit, multiply the thing by two, and subtract
    // 512 if the 2nd most significant bit is a 1, to make the value signed.
    // This transforms the value's range from
    // [0 --------------------------------------------255]
    //     to (roughly)
    // [0 ------- 255|-256 ----- 0 ----- 255|-256 ------0]
    // which oddly enoough is what seems to work.
    relativeX1[n] = ((rX1 & 128) >> 7 | (rX1 & 127) << 1)*2 - 512*((rX1 & 64)>>6);
    relativeY1[n] = ((rY1 & 128) >> 7 | (rY1 & 127) << 1)*2 - 512*((rY1 & 64)>>6);  
    relativeZ1[n] = rZ1; // Use Z as is
    
    // For the second relative x and y coordinates, just multiply the value by two, and make it signed.
    relativeX2[n] = rX2*2 -512*((rX2 & 128)>>7);
    relativeY2[n] = rY2*2 -512*((rY2 & 128)>>7);
    relativeZ2[n] = rZ2;

    // The triangle vertices are now:
    x1[n] = absoluteX[n];
    y1[n] = absoluteY[n];
    z1[n] = absoluteZ[n];
    x2[n] = absoluteX[n] + relativeX1[n];
    y2[n] = absoluteY[n] + relativeY1[n];
    z2[n] = absoluteZ[n] + relativeZ1[n];
    x3[n] = absoluteX[n] + relativeX2[n];
    y3[n] = absoluteY[n] + relativeY2[n];
    z3[n] = absoluteZ[n] + relativeZ2[n];
}
buffer_delete(buffer);

While this is my function(based on the SaveColors() function) in SpyroEdit that writes the terrain collision model to a file(only works in Skelos Badlands, NTSC version):

Hidden text
void SaveCollisionData()
{
	char filename[MAX_PATH];
	char levelFolder[MAX_PATH];
	DWORD nil;

	GetLevelFolder(levelFolder);

	sprintf(filename, ".\\%s", levelFolder);
	CreateDirectory(filename, NULL);
	sprintf(filename, ".\\%s\\CollisionData.cld", levelFolder);

	HANDLE clrOut = CreateFile(filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if (clrOut == INVALID_HANDLE_VALUE)
		return;
	// Write number of tris
	int numTriangles = (0x000E9D60 + 12*12441 - 0x000E97A8)/12;
	WriteFile(clrOut, &numTriangles, 4, &nil, NULL);

	UINT8* bytemem = (UINT8*) memory;
	for (int i = 0x000E97A8; i < 0x000E9D60+12*12441; i += 12)
	{
		WriteFile(clrOut, &bytemem[i], 12, &nil, NULL);
	}

	CloseHandle(clrOut);
}

Mysteries:
- The two bytes (or single 16-bit int) at 0x000DA3B4 and 0x000DA3B5, when decreased, makes the level uncollideable at heights higher than '(some value, perhaps 256) multiplied by the integer value'.
- Spanning 0x000DA3B6 to 0x000DC85C, there might be some sort of 'spatial partitioning' grid or tree structure, where what I believe are lots of empty cells are marked with FFFF 16-bit ints. I suspect that the not-empty cells in some way reference lists of triangle indices found in (below point). Messing with some of the values seemed to turn off smaller groups of triangles.
- Almost directly following this grid(or whatever it is), there seem to be lists of triangle indices(separated by two bytes that can't be triangle indices), because the range of the values matches perfectly with the number of triangles in the level. Also, when I set a portion of this memory to just zeroes, and gradually increased it's range in memory, the collideable part of the level seemed to be eaten up as if by an approaching, huge wall.


Other things:
This part of memory somehow controls the animation of some of the lava pool vertices(which move around in circles).
The zeroes made the pattern quite clear. The value above the zeroes control which vertex in the visible model segment(educated guess) to move, or how far away to move it.
http://postimg.org/image/4emc4i75p/

Offline

#2 Jul 31, 2015 9:00 PM

Gekoncze
Baby Dragon
Award: Speedway Contest Winner Final
From: Czech Republic
Registered: May 16, 2009
Posts: 5,352
Gems: 156
Age: 25 years old
Gender: Male

Re: Collision data hacking

Wow o.o this is incredible. I don't think I would have the patience to research it so deeply.

Offline

#3 Aug 02, 2015 7:42 AM

CrystalFissure
Member
From: Adelaide
Registered: May 16, 2015
Posts: 77
Gems: 0
Gender: Male
Website

Re: Collision data hacking

This is insane. Thanks a heap for showing this. I'll send it to Kly_Men_COmpany and see what he thinks.

Offline

#4 Aug 02, 2015 9:28 AM

Sheep
Member
Award: Skateboard Contest Winner
From: Norway
Registered: Jan 24, 2008
Posts: 983
Gems: 0
Birthday: 20 January
Age: 25 years old
Gender: Male
Website

Re: Collision data hacking

Some new bits...

Directly following the collision model, there is a rather long list of byte values that also decide the type or material of triangles. Here it does not seem to be about 'not water'/'water', but different kinds of solid surfaces. First there are a lot of 191-s, which applies to the bridge triangles and makes it make that creaky wooden sound when you walk on it. Then there are lots of 193-s, which is what makes Spyro recognize the lava as lava.

I could change the bridge triangles into lava triangles and the other way around, but most of the triangles in the level don't seem to have values in this list of bytes, so I couldn't change their material. I initially thought there was a value for each triangle, but changing some of the later values made the sky disappear. How it is decided which triangles have values and which don't, I don't know.

Also, most special materials, like lava, seem to only be solid when they're within some degree of being horizontal. As walls, they pretty much don't exist.

Last edited by Sheep (Aug 02, 2015 9:39 AM)

Offline

#5 Aug 03, 2015 3:23 PM

aleksusklim
Member
From: Uzbekistan
Registered: Aug 03, 2015
Posts: 12
Gems: 0
Birthday: 30 May
Age: 26 years old
Gender: Male
Website

Re: Collision data hacking

Hello!
(I am Kly_Men_COmpany)

Thank you for your investigation.
Yes, I think it’s time to hack all that collisions!

But before I need to share something that I know.

I use Greatest Hits Spyro3 image (since it’s not buggy, it’s only one and the same everywhere, and since we are translating it to Russian during 4 years – I’m more familiar with GH than any other Spyro3 release).

Also I use WinHex ( http://klimaleksus.narod.ru/Files/WinHex.rar ) and Epsxe ( http://klimaleksus.narod.ru/Files/Epsxe_v_1.7.2.rar ).

So, let’s take WAD.WAD file.
Snapshot:
http://klimaleksus.narod.ru/Files/H/coll_01.png

Header contains many 8-byte structures which represents subfiles. It’s like an achieve.
First 32-bit word is the offset and second 32-bit word is the size.

So, first value = 2048 means that first subfile is located at 2048-th byte of WAD. Also that means that maximal subfile number is 2048/8=256, but there are only 195 of them.

A few of them are empty, some are represent loading screens and logos, cutscenes, levels, atlas, menus, credits – all of game resources.

Here they are:
Subfile 001 is maybe general Spyro animation (not tested by me).
Subfile 002 is main menu logic binary code (with strings and with chipmod-protection).
Subfile 003 is "Sony computer Entertainment …" compressed graphical image.
Subfile 004 is the "Universal" logo (compressed).
Subfile 005 is a white copyright text at the bottom (compressed).
Subfile 006 is main "Insomniac" logo in compressed format that appear from dark BEFORE main menu game starts. This is not the texture from a level (Fireworks Factory part, 10-th subfile). So, there are two copies of Insomniac logo (this for loading, and second as a model texture).
(logos: http://klimaleksus.narod.ru/Files/2/strip.png )

There is my tool to convert between compressed graphics and RAW 16-byte VRAM default format: http://klimaleksus.narod.ru/Files/2/strip2.rar
To view RAW 16-bit images you can use PVV or something better… Well, I have PGG (http://klimaleksus.narod.ru/Files/2/PGG2_fix.rar , including PVV too), for minor replacing it’s still OK.

Subfiles from 007 till 069 are cutscenes. Every cutscene consists of 3 files: big (500-1000 Kb), small (15-25 Kb) and very big (1-3 Mb).
Second (small) file contains object binary code and some other stuff (may be encrypted, but we know how to particularly decrypt it), also – every playable level have similar small code file.
First file is something like a playable level – with VRAM textures, ground, sky and sounds.
Third file is cutscene’s main timeline with all animation and music (with dialogues). Its internal structure isn’t totally hacked, but we can get, change and replace music track. Also I have some non-finished hacks with in-game (in-cutscene) camera, that allow me to move the camera at wish during cutscene. The problem is that this big file is loaded incrementally into the memory, and the whole mechanism isn’t learned yet.

To know what exactly cutscenes they are, you can refer to our list (for Platinum version subfiles are the same) :
http://klimaleksus.narod.ru/Files/SOUNDS/source/

(7-8-9 subfiles triple is not a true cutscene, but main menu screen at Fireworks Factory with Insomniac logo)

Subfile 070 is the binary logic code used between cutscenes and levels. Here is autosave and other automatic memory-card related stuff and strings (red errors and other text that appears at the bottom of screen).

Subfiles from 071 till 096 are loading images for all cutscenes and worlds (their order is almost natural: http://klimaleksus.narod.ru/Files/3/screens.rar ). Format is RAW 16-bit, but with 28-bytes header.

Subfile 097 is somehow used when Spyro flies in portals between levels or worlds (alone and with balloon, whirligig or rocket). This thing contains those skies and 3D models (unrecognized format for now, but we tried to hack it a couple of times) of balloon/whirligig/rocket.
Seems like all skies here are exact copies of such skies from main levels.

Subfiles from 098 till 171 are levels. Every level consists of two subfiles: big and small.
First is big and it is the level itself. Second is small like that second small cutscene’s subfile, and also contains binary game logic code. Particularly encrypted-XORed (for that annoying libcrypt protection in Platinum version), has in header some critical (and absolute) pointers to game lists and functions.
There are two known lists: list of all dialogues on a level (just SPEECH.STR’s offset/channel/length; we know the format and how to change it – we will replace all dialogues with our translated versions); and a list of all object types. This list handles all pointers to object-specified code.
Interesting thing that this list is global for entire game (similar lists can be found at Spyro1 and Spyro2), but have non-zero pointers only to those objects that existed in current level. That’s the first thing why we can’t have an object from one level to just appear in another – there is no binary logic code for it, the pointer is empty (the second reason is that there is no 3D animated model for it too).
Pointers to functions differ from one level to another. They’re somehow related with water (not only to swim underwater, but also to swim at the water surface) and maybe something else.
If you want to know the order, here it is:
http://klimaleksus.narod.ru/Files/PLATINUM/

Subfiles 172-177 (also 088 and 008) are empty.
Subfile 178 – just another RAW image, this one for global game-over screen.

Subfile 179 is game Atlas. Look like all main textures (with its own font), logic binary code and all text. Without level previews from atlas pages.

Subfile 180 is the binary logic code for in-game memory card options menu. Loaded and used only after choosing "options". Contains all memory card related strings.

Subfiles 181 and 182 are world preview pictures for atlas. There are all of such pictures in a list. Graphic format is rather uncommon, we use Tile Molester to change something. There also palettes for every image; and a header is similar to those from 071-096 screens group.

Subfile 183 consist of some binary code (with only one string "Too many moby tags"), I don’t know or don’t remember what it is.

Subfiles from 184 till 195 are scenes for final credits – camera paths, skies and ground. Format is similar to playable levels’ big subfile; and in some credits subfiles there are also most of level objects (in disabled non-animated invisible state), but generally there is no objects.
Subfile 184 contains final credits music.

Let’s do some data identification training!

We already know how looks like WAD header. 8 bytes, offset and size. For example here (in 100-subfile) is the same:
http://klimaleksus.narod.ru/Files/H/coll_02.png
There are three groups of headers. In the first there are 8 sub-subfiles ("sub-sub" since we are already in "subfile"), it the second (from offset 96) there are 6, and from offset 160 there are not offset-size pairs, but just offsets (31 addresses).

This is main structure of the header of ANY level. Also, cutscenes (first file) and credits.
There is always at least 4 sub-subfiles. And at most 10 (or 12 possible). First 4 represents level. Next pair – first sublevel (challenge portal), so – all following pair used for sublevels.
In all home worlds there’s only 4 sub-subfiles (no sublevels), at Sunny Villa there are 8 sub-subfiles (2 sublevels).

Look at first sub-subfile of any level:
http://klimaleksus.narod.ru/Files/H/coll_03.png
http://klimaleksus.narod.ru/Files/H/coll_04.png
http://klimaleksus.narod.ru/Files/H/coll_05.png

This is RAW image 16-bit data. Almost always looks like long lines with similar bytes or repeated pairs. Sometimes It’s very easy to determine such image just by looking on it hexadecimal view.
How that format is stored? We have many definitions how to deal with it. 16-bit data can be used as one of modes: 15bpp, 8bpp or 4bpp. Most common is 15bpp, where every RGB channel has 5 bits, and one bit used as transparency flag. How it’s used is explained in PlayStation SDK and it is related to semi-transparent textures. 8bpp and 4bpp mode uses a palette – 256/16 pixel strip line that encoded in 15bpp. All that stuff pretty understandable in PVV.

So, entire half of screen is: 1024(width)*512(height)/2(right half)*2(bytes per pixel)=524288.
Let’s jump to this offset in first sub-subfile:
http://klimaleksus.narod.ru/Files/H/coll_06.png
Image data is ended, but SOUND data is following!
This is VAG standard format. Sound data can be recognized even much easer than image data, because there is always one small number (0, 2, 6 or something) after every 16 bytes.

That’s how a separator of different sounds looks like:
http://klimaleksus.narod.ru/Files/H/coll_07.png (zeroes at the middle)
We have an algorithm how to extract, change and replacer such sounds.

So, in every first sub-subfile there is 524288 bytes of VRAM image data and then some VAG sound data related to the level. I don’t know how exactly sounds are stored, but if you change first sub-subfiles between two levels, then sounds will be messed up (and of course all of textures!)

The second sub-subfile and also all sublevels 'first' sub-subfiles (5,7,9) are interesting for now, because they contain ground visible and collision model. We will look at them below.

Third sub-subfile is 3D animation container. There’s somehow stored all typed of objects – in a level (but seems that no for sublevels). Again there is strong link between typeID of an object and its 3D animation (like a link between typeID and logic code from one of tables in 'next' small subfile).
Looks like that third portion of WAD-alike pointers in the header of level subfile are point somewhere in third sub-subfile. At beginnings of object types… There is not only visible part, but also some collision-related flags (destructible, solid, hurting, etc.)

This data can be indentified by repeated 0x7C (char '|') and 16-bytes long 0xFF:
http://klimaleksus.narod.ru/Files/H/coll_08.png
Some parts are not such similar, and looks pretty random:
http://klimaleksus.narod.ru/Files/H/coll_09.png
Would be very great to hack this data, but all of my attempts gave no significant result.

Also it’ll be useful to maybe not to understand format entirely, but deep enough to allow different object type manipulations – to replace one object with another, to switch animation, to make an object from one level appear in another (maybe even without its logic code).

Fourth sub-subfile and also every 'second' sub-subfile from sublevels (6,8,10) are objects and variables data. Main thing is level (sublevel) object list and their variables.
There are many sections with special data. Every section is like a structure where first 4 bytes is a size of current section. They are always one after another and always at the same indexes.

But not every level have all of them. They control some local stuff (may be related to water surface or something else, moving…). And if any structure is empty in current level, then its size will be equal to 4 (just size of 4 bytes holding number '4')  or 8 (4-byte number '8' plus 4 zero bytes).
I call it a 'jump'. Standing at beginning of a section I can read 4 bytes integer and then jump locally forward to this number (or 'number-4' if reckon in an offset after actual reading this number). So '4' means do nothing after reading, '8' means skip 4 bytes etc.

Let’s look at 4-th sub-subfile:
http://klimaleksus.narod.ru/Files/H/coll_10.png

There is a header, which length is different in Spyro1, Spyro2 and Spyro3. Also, indexes of sections different too.
Well, I know that header for Spyro3 is 48-bytes long. Next number is '4' – then first section is empty. Next is '8' – section two is again empty. Then:
http://klimaleksus.narod.ru/Files/H/coll_11.png
There is number 532, so jump to +532 relative to current point (before the number) will give:
http://klimaleksus.narod.ru/Files/H/coll_12.png
This is fourth section, and again I don’t know what it’s doing… In future I plan to extract all of sections from all of levels to think about what they can do.

Next jump to +656:
http://klimaleksus.narod.ru/Files/H/coll_13.png
Next section ('8') is empty, and again jump to +736:
http://klimaleksus.narod.ru/Files/H/coll_14.png
After another two empty sections, we can notice text like "jump", "headbash", "rotate camera", etc. This is for "Help" option menu. This section exists in every level!

Jump to +684:
http://klimaleksus.narod.ru/Files/H/coll_15.png

Again empty ('4') section and another jump to +372:
http://klimaleksus.narod.ru/Files/H/coll_16.png
Last jump to +104 (0x68) and finally we’ve found objects!
28640 – length of section, next number (inside a section) – 209 (0xD1) – initial number of objects at the level. Yeah, this is where SpyroEdit is messing around))
Next there are 209 structures by 88 byte each, that represents every object. There are XYZ coordinates, angle, scale, typeID, secondary ID (for animation actions or for type subdivision), animation phase; pointer to variables (12-byte structures followed next to section), pointer to somewhere in third sub-subfile to make object collideble; maybe a pointer to parent/friend object (f.e. bottle and butterfly) – many interesting stuff.

This section contains some zero-filled object structures for future objects that would be created at runtime.

Next section:
http://klimaleksus.narod.ru/Files/H/coll_17.png
Is objects’ variables. 12-byte structures. Almost every object points here.
Actually, they aren’t always 12-bytes long. Some object can have additional pointers (here) to store some other data, and there can be other pointers, and so on. This is primary defined by a type of the object, and this is big problem that cause crashing when we trying to change any object typeID – because new object type could treat some variable-data as a pointer and make a memory access violation error it that wasn’t a real pointer. Only a few types have similar pointers tree and can be replaceable.

Also there is all character text for level dialogues. Text format is hard enough to explain, and there are some things that we need to hack if we want to replace a text by some longer text (many Russian words and phrases are longer than English).

Final jump after this section:
http://klimaleksus.narod.ru/Files/H/coll_18.png
I don’t exactly know how that is stored, but at the end of this 'fourth' (4,6,8,10-th) sub-subfile is a big list of all pointers that appeared here. Every 'local' sub-subfile pointer must be placed here to be converted to absolute (0x80…) pointer at runtime. This is done since game knows load-address of entire sub-subfile. We can use this list to easily indentifying pointers, for example.
Also, if we change any pointer or add new, we can just make it absolute (i we know exact target address) without adding to a list.

Now, let’s look to second sub-subfile! It’s structure also consists of sections, and here they are:
http://klimaleksus.narod.ru/Files/H/coll_19.png
This is texture definition table. First (after the size) number is counter. Then a lot of data for textures – XY of corner, which bpp, XY of palette, etc. The format is almost all hacked.
For Spyro1 there is much extra data, that represents subdivision of hi-quality textures (seems like in Spyro2/3 this data is calculated at runtime).

One jump and we got:
http://klimaleksus.narod.ru/Files/H/coll_20.png
Ground visible model! It can be determined because at the beginning there’s a big list of parts (chunks), and then:
http://klimaleksus.narod.ru/Files/H/coll_21.png
A header which always ends with four 0xFF. Below is some random vertex data, but next are colors – many 4-bytes in RGB0 format, where last byte is always zero. Also you can easily mess it up to change color at runtime (well, seems SpyroEdit does exactly that).

Jump to next structure:
http://klimaleksus.narod.ru/Files/H/coll_22.png
(at the top you can see the rest of polygonal 16-bytes data from the ground model)

Here are two other structures. I can be wrong, but I have a feeling that this part is responsible for 'living' world – for example water swaying. Or may be not. May be for some render optimizations, when some parts of the ground model became truly invisible when they are not visible by the camera (f.e. from other side of a wall). I remember that I messed something here to finally make my CastleSkatepark visible world not to flicker and dance around.

Next jump and…
http://klimaleksus.narod.ru/Files/H/coll_23.png
This is it!
Ground collision model.

We will continue with collision, but let’s make another jump and see what’s next:
http://klimaleksus.narod.ru/Files/H/coll_24.png
This is the sky.

At beginning again is a list of parts (but before parts number there is RGB0 background color), but then no recognizable header. Instead, there are good colors:
http://klimaleksus.narod.ru/Files/H/coll_25.png
Sky color data is always in RGBM format, where magical M (last byte) equal to 48 (0x30). So, if you ever see anywhere some (mod 4 aligned) data, where every last byte = 48 then this is a part of a sky.

For example, a local sky is stored in a 'fourth'-sub-subfile in one of sublevels in Super Bonus Round (because that yeti skatepark sky is not the sky from the main level) and located in one of sections that is empty in every other level.

We can do a few more jumps and at the end there are:
http://klimaleksus.narod.ru/Files/H/coll_26.png
names of hatched from eggs dragons and something else.

Oh, about the eggs. Remember those second WAD-alike data in the header of subfile? Those were a pointer-size pairs of hatched dragons related stuff – their model, textures and animation track.
We don’t know the format, but it’s very easy to replace one with another, even from different level.

Actually, name of eggs isn’t the end. Let’s go to third sub-subfile and scroll up enough:
http://klimaleksus.narod.ru/Files/H/coll_27.png
I don’t know what is at top, but at the bottom and forward it is current level sounds definition. I didn’t hack the format, but I know that messing with this will affect all in-game sounds.

Now we’ll look at other sub-subfiles for sublevels that are similar to second one – 5,7,9

http://klimaleksus.narod.ru/Files/H/coll_28.png
First section is like contents of third subfile for a main level, but now for current sublevel. There are 3D animated objects… (instead of texture coordinates list)

Skip it, one jump:
http://klimaleksus.narod.ru/Files/H/coll_29.png

This is strange, but here is no section size. That jump points to a structure that is similar to a structure found at the header of level subfile. But now all pointers targeted to some parts of beginning of this sub-subfile instead – in header they points to 3D models in third subfile, but since for sublevel they are in the first section of 'second' (5,7,9), then this header-alike structure points there.

To get a next jump, we must skip all highlighted data. Next to it there is visible ground model for sublevel! Let’s jump:
http://klimaleksus.narod.ru/Files/H/coll_30.png

Polygonal data at the top, and something is following. Simple jump:
http://klimaleksus.narod.ru/Files/H/coll_31.png

Two more jumps and we are at the sublevel collision model!
That’s all, next jump will point to the end with 4-byte 0xFF. No sublevel sky (it can be only somewhere in the next sub-subfile).

So, let’s return to collision model (of main level). Since second sub-subfile is loaded in memory entirely and since we can compare files in WinHex at runtime, then we’ll easily distinguish all pointers and all non-pointers.
(real Epsxe1.7.0’s game memory is located from 0x94C020 to 0xB4C01F)

http://klimaleksus.narod.ru/Files/H/coll_32.png
This is for Sunrise Spring Home.
At cursor – beginning of collision model.
Then is 6 pointers:
First is just next to header, 36 bytes.
The last is zero, so it points TO the header (no idea why, but it is – see emulator’s memory in the bottom window).

All that means that the collision data consist of five parts. And yes, the first part if somehow related to groups of collideble triangles, and has many 0xFFFF.
Beginning of the second part:
http://klimaleksus.narod.ru/Files/H/coll_33.png

At top we can see an array of 2-byte data with many (as you said) empty cells = 0xFFFF.
So, what is next? It isn’t yours triangles and vertexes and it’s not 12-byte structures.

When I tried to change values at run-time – yeah, this is somehow change collision model. But I didn’t understand the format. I placed Spyro to one triangle (I believe) and tried to locate a minimal amount of memory that related to it. And for me that was more than 32 bytes long!

But maybe I mistaken. I know that corrupting some data in one place can affect to random following data just because of internal errors. For example in graphics model if we mess with vertex of color index if just one vertex – then any other part of model can become invisible. That doesn’t mean that it’s depending on that or related to that, it is just an unexpected error from game’s point of view.
That is everywhere, for example a wrong character in a printable string can cause destruction of all textures and VRAM data (just try to put "@$" in any string that is displayed).

Let’s jump to next part:
http://klimaleksus.narod.ru/Files/H/coll_34.png

We have a black zone where are many differences (between WAD file and RAM game), and they aren’t pointers.
I tried to mess it up, but there was no visible effect.

Next part:
http://klimaleksus.narod.ru/Files/H/coll_35.png

But this is not the fourth part that follows black zone! Actually, this is fifth and last.
For some reason, next part pointer also points here (after highlighted bytes), but it’s useless.

This type of data is very similar to RAW image, but I think that is not an image. Maybe a map array or something…
Looks like this is used (if any) only at level loading, because messing with this at runtime does nothing again.

So, your vertexes are in fourth part, just after the all different bytes:
http://klimaleksus.narod.ru/Files/H/coll_36.png

Well, I tried your code to parse it with GML (hey, what dialect of GML do you use? Why there are "i++" instead of "i+=1" and "var x1=0,x2=0;" instead of "var x1,x2;x1=0;x2=0;"?), and it’s WORKING!

http://klimaleksus.narod.ru/Files/H/cool_1.png
http://klimaleksus.narod.ru/Files/H/cool_2.png
http://klimaleksus.narod.ru/Files/H/cool_3.png

Amazing, you’re genius!

This is Sunrise Spring Home collision model, with normal-based lightning (I calculated flat face normals by myself) and with two-types coloring (for ground and water), also with different colors at triangles vertexes.

Wow, now there’s a lot to see! I need to write an extractor for SpyroWorldViewer…

And there’s a big question, what are doing all other parts of the collision section. Maybe it’s for some optimization, for example a map that helps game calculate only nearest triangles to current object, based on its position? Some 3D grid…

When I messed with second part and fill much of it to zero – the game became very slow (1-4 fps).
And we need more information about each ground type. All that stuff with lava, ramps, bridges and so on.

Thanks to CrystalFissure for linking me to this topic.

…wait a minute:

Index » Spyro 2: Ripto's Rage » Collision data hacking

Oh. Spyro2. So…
OK. There are some minor differences between Spyro3 and Spyro2.
First of all is subfile indexes.

Here we go again:
Subfile 001 contains a lot of 0x7C, which means that this is live 3D animation (Spyro again?)

Subfile 002 – memory card related binary logic code (only error strings, seems like without any menus).
Subfile 003 – don’t know what is this, looks like small RAW image or an array with many equal elements.
Subfile 004 – compressed splash logos, all in one file:
http://klimaleksus.narod.ru/Files/2/sp2logos.png

Subfile 005 – seems like for Atlas, with interface and icons of talismans.
Subfile 006 – this is like 097 of Spyro3 – with all skies from travelling between worlds and levels.
Subfile 007 – RAW image of "Game over" screen (even without a header).
Subfile 008 – "Too many moby tags" binary code file.
Subfile 009 – all of the images with title and an open book ("In the world of dragons…" and so on) in RAW 15bpp, as one big file.
Subfile 010 – binary code file, looks similar to those small files that are with levels and cutscenes.
Subfile 011 – main menu GUI and Spyro2 logo.
Subfile 012 – there is the music from credits… (I have no idea why. Is it played somewhere else?)
Subfile 013 – Atlas cover image.
Subfile 014 – all of level preview images for Atlas (I hope). Very big file! The format is similar to Spyro3 (not so simple to extract something, but possible).

Then, from pair 15-16 till 71-72 are levels. In natural order (I have no list, but you can simply use SpyroWorldViewer info to figure it out).

First file in a pair – small particularly encrypted code file, like in Spyro3 'next' subfile, but here they all are located before main level subfile. So, levels are 16, 18 .. 70, 72.

Every level consists only of 4 sub-subfiles, since in Spyro2 there is no sublevels.
(Also, in Spyro1 there are special sub-subfiles for each crystalled dragon with their animation and voice sound).

Then, from pair 73-74 till 95-96 there are cutscenes data. Every first file is empty, so real cutscened are 74, 76 .. 94, 96.

These are only level-typed parts of cutscenes, without animation track and music. But there are 4 sub-subfiles with textures, sky, ground of course. Maybe collision too.

Next, from 097 till 132 there are animation files for level mini-cutscenes (that is, played before ant after very first playing a level).

Also there is background music for them. So, it’s pretty easy to extract such music track, you can use my tools:
http://klimaleksus.narod.ru/Files/2/VagChange.rar
(drop cutscene’s subfile to "/tools/Cutscene.bat" and then use standard VAGEDIT.EXE to play extracted .vag file)

Main game cutscenes (their part with animation and dialogues) are located in groups by 5.
From 133-137 till 183-187.

First file is English version; second is the same but French. Three others are empty by some reason.
So, real cutscenes are 133, 138, .. 183.

And finally, from 188 till 197 are credits, just like in Spyro3 (and they are natively replaceable).
First one also contains credits music.

Seems that I also need to show sub-subfiles from Spyro2. OK, a level subfile:
http://klimaleksus.narod.ru/Files/H/coll_37.png

Four sub-subfiles and pointers data for 3D animated objects.
First sub-subfile is again just VRAM textures and game sounds, everything like in Spyro3.
Third sub-subfile still has an unknown format, seems random but at the end again has many areas with 0x7C bytes.
Fourth sub-subfile:
http://klimaleksus.narod.ru/Files/H/coll_38.png
Length of header is 44, and then again jump sections.
But after a number of jumps (it’s always constant for a game) we get the objects list:
http://klimaleksus.narod.ru/Files/H/coll_39.png
First one is highlighted. Their structure is the same as in Spyro3, so nothing special.
Next their variables and texts, at the end – list of pointers.

Second sub-subfile: texture coordinates, visible ground model, some other stuff, some empty sections, and after a few more jumps:
http://klimaleksus.narod.ru/Files/H/coll_40.png

The collision model. Its internal structure look the same as in Spyro3. Even better, now I see that the last pointer is point to something different than previous, but it has the same structure. Take a final look to parts:

http://klimaleksus.narod.ru/Files/H/coll_41.png
This is colored representation. Yellow is next jump pointer, the size of entire collision model.
Red is a header, I didn’t test what those three integers are doing.
Cyan is a relative pointer (relative to beginning of the collision data, that position is marked dark-green) that points to cyan zone just after all other pointers. There are much 0xFFFF cells.

http://klimaleksus.narod.ru/Files/H/coll_42.png
Green points to second mysteries part, I think this is needed for optimizations.

http://klimaleksus.narod.ru/Files/H/coll_43.png
Pink point to part where bytes in memory will be changed at runtime. After them there would be our 12-byte triangles.

http://klimaleksus.narod.ru/Files/H/coll_44.png
Blue points to first array at the end, and then purple:
http://klimaleksus.narod.ru/Files/H/coll_45.png
points to second part of it.

And here is the end of all data:
http://klimaleksus.narod.ru/Files/H/coll_46.png
The sky model is following at the bottom.

That’s all I want to say.
Now we can perform some tests, for example change positions of two triangles and see are they will still be collideble, or add a vertical offset to every triangle, or export collision model from every level and do some types investigations (for example, how works breakable walls or skatepark ramps).

And one more thing. If we look at 3D animated objects we can see that they are also collideble. It means that they also contain collision data, or they just calculate it at runtime. Anyway, third sub-subfile waits for our great hacks!


~Kly_Men_COmpany

Offline

#6 Aug 03, 2015 4:52 PM

Sheep
Member
Award: Skateboard Contest Winner
From: Norway
Registered: Jan 24, 2008
Posts: 983
Gems: 0
Birthday: 20 January
Age: 25 years old
Gender: Male
Website

Re: Collision data hacking

Thanks a lot for your response!
That's a huge amount of new(and useful) information to me, so I need to carefully read through your post a couple of times at the very least!

Well, I tried your code to parse it with GML (hey, what dialect of GML do you use? Why there are "i++" instead of "i+=1" and "var x1=0,x2=0;" instead of "var x1,x2;x1=0;x2=0;"?), and it’s WORKING!

http://klimaleksus.narod.ru/Files/H/cool_1.png
http://klimaleksus.narod.ru/Files/H/cool_2.png
http://klimaleksus.narod.ru/Files/H/cool_3.png

Amazing, you’re genius!

(I use GameMaker:Studio... GML has been slightly updated to include "coding shortcuts" like those.) Great to see that it worked for you as well, and even with Spyro 3 levels! big_smile

Offline

#7 Aug 03, 2015 9:33 PM

aleksusklim
Member
From: Uzbekistan
Registered: Aug 03, 2015
Posts: 12
Gems: 0
Birthday: 30 May
Age: 26 years old
Gender: Male
Website

Re: Collision data hacking

Sheep wrote:

Great to see that it worked for you as well, and even with Spyro 3 levels!

He-ey, I improved you formulas!
http://klimaleksus.narod.ru/Files/H/cool_4.jpg
Now there’re no cracks.

Sheep wrote:

there are cracks between the triangles, because I haven't yet figured out exactly how to use the mysterious pair of 2-bit values.

There are no separate two bits. They are just least significant bits of first relative vector. Which have length of 9 bits total, signed (two's complement).

Sheep wrote:

// For first relative x and y coordinates, move the most significant bit to the place of the least significant bit, multiply the thing by two, and subtract
    // 512 if the 2nd most significant bit is a 1, to make the value signed.
    // This transforms the value's range from
    // [0 --------------------------------------------255]
    //     to (roughly)
    // [0 ------- 255|-256 ----- 0 ----- 255|-256 ------0]
    // which oddly enoough is what seems to work.
    relativeX1[n] = ((rX1 & 128) >> 7 | (rX1 & 127) << 1)*2 - 512*((rX1 & 64)>>6);

How did you.. ever.. oh.
It’s simpler than it looks!

Let’s take 12 bytes as 3 words (32 bits each). Then we got that bytes order become inversed, and first byte is least significant.

Okay, and I tell you that absolute coordinate have length 14 bits, and two relative coordinates – 9 bits. Then we’ll divide our 32 bits to three parts – "9,9,14" (most significant bit is to the left).

That looks like this: 3333-3333=3222-2222=2211-1111=1111-1111

But since there are little endian, so bytes are inverted (4321) :

aaaa-aaaa = bbAa-aaaa = cBbb-bbbb = Cccc-cccc

Capital letter represents most significant bit (sign for X and Y). For Z coordinate we have:

aaaa-aaaa = ttAa-aaaa = Bbbb-bbbb = Cccc-cccc

To convert signed as 2’s complement for 9 bits we can use this formula: V=V-(1<<9)*((V&(1<<8))>0);

I’ll show all my code below. I didn’t simplify constants for better understanding what happens there. Also notice that ((1<<V)-1) is a number with V first bits = 1.

Hidden text
var wordX,wordY,wordZ,vertX1,vertX2,vertX3,vertY1,vertY2,vertY3,vertZ1,vertZ2,vertZ3,terrT,normX,normY,normZ,normL,col1,col2,col3;

wordX=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8)|(file_bin_read_byte(buffer)<<16)|(file_bin_read_byte(buffer)<<24);
wordY=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8)|(file_bin_read_byte(buffer)<<16)|(file_bin_read_byte(buffer)<<24);
wordZ=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8)|(file_bin_read_byte(buffer)<<16)|(file_bin_read_byte(buffer)<<24);

vertX1=wordX&((1<<14)-1);
vertY1=wordY&((1<<14)-1);

vertX2=(wordX>>14)&((1<<9)-1);
vertY2=(wordY>>14)&((1<<9)-1);

vertX3=(wordX>>(14+9))&((1<<9)-1);
vertY3=(wordY>>(14+9))&((1<<9)-1);

vertX2-=(1<<9)*((vertX2&(1<<8))>0);
vertX3-=(1<<9)*((vertX3&(1<<8))>0);
vertY2-=(1<<9)*((vertY2&(1<<8))>0);
vertY3-=(1<<9)*((vertY3&(1<<8))>0);

vertZ1=wordZ&((1<<14)-1);
terrT=(wordZ>>14)&((1<<2)-1);

vertZ2=(wordZ>>(14+2))&((1<<8)-1);
vertZ3=(wordZ>>(14+2+8))&((1<<8)-1);

normX=vertZ2*vertY3-vertY2*vertZ3;
normY=vertX2*vertZ3-vertZ2*vertX3;
normZ=vertY2*vertX3-vertX2*vertY3;
normL=sqrt(normX*normX+normY*normY+normZ*normZ);
normX/=normL;
normY/=normL;
normZ/=normL;

vertX2+=vertX1;
vertX3+=vertX1;
vertY2+=vertY1;
vertY3+=vertY1;
vertZ2+=vertZ1;
vertZ3+=vertZ1;

if terrT&1 {
col1=c_blue;
col2=c_aqua;
col3=c_fuchsia;
}else{
col1=c_lime;
col2=c_yellow;
col3=c_red;
}

d3d_model_vertex_normal_color(collmodel,vertY1,vertX1,vertZ1,normY,normX,normZ,col1,1);
d3d_model_vertex_normal_color(collmodel,vertY2,vertX2,vertZ2,normY,normX,normZ,col2,1);
d3d_model_vertex_normal_color(collmodel,vertY3,vertX3,vertZ3,normY,normX,normZ,col3,1);

And what about the collision data in Spyro1? ))


~Kly_Men_COmpany

Offline

#8 Aug 03, 2015 10:07 PM

Sheep
Member
Award: Skateboard Contest Winner
From: Norway
Registered: Jan 24, 2008
Posts: 983
Gems: 0
Birthday: 20 January
Age: 25 years old
Gender: Male
Website

Re: Collision data hacking

aleksusklim wrote:

He-ey, I improved you formulas!
http://klimaleksus.narod.ru/Files/H/cool_4.jpg
Now there’re no cracks.

(...)

There are no separate two bits. They are just least significant bits of first relative vector. Which have length of 9 bits total, signed (two's complement).

(...)

Ah, fantastic! ... Yeah, I suspected something like that - but clearly I put the bits in the wrong place. I initially thought that they would be the least significant bits for, and extend, each of the following bytes separately. This only gave me even worse results than what I initially had, though. Then I attempted something similar to what you did here, but somehow messed up again. I never did any bit level programming before this project, so I have some things to learn <_<

Anyway, I get it now... thanks for the explanation!

aleksusklim wrote:

And what about the collision data in Spyro1? ))

I haven't yet looked into it... adventures await big_smile

(
Edit:
After struggling for an hour with my own stupid typo, I got my crack free model. Thanks again, Kly! big_smile
)

Edit:
Just ran a test where I slowly stripped away the triangles in my model to see which type-specified triangles went first... The last terrain type bytes are for mountain triangles. I don't really see why they are listed, though, as their value is the same as for normal ground triangles. Both the mountains and all "normal" triangles higher up in the list have a value of 127. If there were any special-terrain triangles following them, it would make sense... but it really just looks like superfluous data... maybe the level initially had some more lava triangles?
And finally realized that the number of terrain type bytes was specified here: http://s30.postimg.org/cfy9eq569/terraintypebytes.png

Last edited by Sheep (Aug 04, 2015 10:59 AM)

Offline

#9 Aug 04, 2015 9:50 PM

aleksusklim
Member
From: Uzbekistan
Registered: Aug 03, 2015
Posts: 12
Gems: 0
Birthday: 30 May
Age: 26 years old
Gender: Male
Website

Re: Collision data hacking

Sheep wrote:

I never did any bit level programming before this project

Wow.
Most of our bit-level coding is related with RAW 15bpp images and yeah, 3D-modelas (we hacked some of object-3D models from Spyro1).

Sheep wrote:

After struggling for an hour with my own stupid typo, I got my crack free model.

Typos are everywhere! I often spend a lot of time to make my code working, even when I totally know the format and had deal with it before.

Sheep wrote:

I haven't yet looked into it...

I did.
A-a-and, it’s very good! Yes, I think, our algorithm is not bad. But there are three problems.

First: the most significant (second) bit of (what we call) a terrain type sometimes is very random. It’s not related to a water (which is the first bit), and it’s used rather rare. Sometimes kinda logically (f.e. flat vertical walls or very top areas, or in symmetrical order), but often – without any ideas why so: random lonely triangles and abstract patterns; maybe instead in huge area with this special triangles there is small random amount of normal triangles. Strange.
They all unique, there is no uniform explanation (not two-sided, not with different interaction, not improperly located) what this bit is doing.

Second: animated (something that once would change its form) part of a world seems to have really different collision format:
http://klimaleksus.narod.ru/Files/H/cool_05.jpg v.s. http://klimaleksus.narod.ru/Files/H/cool_06.jpg
http://klimaleksus.narod.ru/Files/H/cool_07.jpg v.s. http://klimaleksus.narod.ru/Files/H/cool_08.jpg
http://klimaleksus.narod.ru/Files/H/cool_09.jpg v.s. http://klimaleksus.narod.ru/Files/H/cool_10.jpg
True for Spyro1/2/3 and seems that everywhere is the same unknown format.

Third: sounds nonsense, but looks like in main levels of Seashell Shore, Enchanted Towers, Lost Fleet, Fireworks Factory, Desert Ruins, Dino Mines and Super Bonus Round there is no collision model at all. Impossible! It must be somewhere.
And it is in the third sub-subfile! Because these levels are very big, and second sub-subfile is always stored in memory, even when player is in a sublevel (with its own collision model). So if a main level collision data will be in a third sub-subfile, then its memory will be freed within sublevel.
Hot to get such big collision? Simple: seek at third sub-subfile; seek 4 bytes forward; make two jumps; seek 4 bytes forward; profit! Current value will be the size of collision data (next jump).

Sheep wrote:

adventures await

I wrote a good program to extract all collision data. It is very universal, it can handle Spyro1, Spyro2, Spyro3 and sublevels of Spyro3 (also technically cutscenes from Spyro2 and Spyro3; looks like cutscenes from Spyro1 have no collision model), can take already extracted subfile, or WAD-file and a number of interested subfile (though you should tell my program what a game it is).
As output it will give you entire collision model in one file and also its five parts in other separated files. Our file with triangles – third.

Here is the link, there are also 8 .bat files to make the extraction simpler. Drag-and-you’re your WAD to SpyroX_WAD.bat to extract levels; to SpyroX_CUT.bat to extract cutscenes; or drag-and-drop one already extracted level/cutscene subfile to SpyroX_SUB.bat to get collision data from it (results will be saved next to source, and not to subfolder next to program as before in multiple extraction):

http://klimaleksus.narod.ru/Files/3/Col … racker.rar
Also I’ll post source here, why not:

Hidden text
Language: Delphi7.
Example usage in batch: (file "Spyro3_WAD.bat")

@echo off
if _%1_ == __ goto :eof
cd /D %~dp0
set name=.\Spyro3\
mkdir %name%>nul
for /L %%i in (98,2,170) do (
for /L %%j in (1,1,5) do (
echo %%i %%j
CollisionCracker.exe %1 %%i %%j %name%Coll_%%i_%%j 
)
)
pause

____________________
program CollisionCracker;
{$APPTYPE CONSOLE}
uses Classes,SysUtils;

// <Color output console functions>
    function GetStdHandle(std:integer):integer;stdcall;external'kernel32.dll';
    function SetConsoleTextAttribute(handle,color:integer):boolean;stdcall;external'kernel32.dll';
    function GetConsoleScreenBufferInfo(handle:integer;csbi:Pointer):boolean;stdcall;external'kernel32.dll';
    function SetConsoleScreenBufferInfo(handle:integer;csbi:Pointer):boolean;stdcall;external'kernel32.dll';
    type _csbi=record c1,c2:integer;color:word;c3,c4,c5,c6:integer;end;var def:Word;con:integer;csbi:_csbi;
    procedure InitCol();begin con:=GetStdHandle(-11);GetConsoleScreenBufferInfo(con,@csbi);def:=csbi.color;end;
    procedure WriteCol(str:string;col:Word);begin SetConsoleTextAttribute(con,col);Writeln(str);SetConsoleTextAttribute(con,def);end;
    const cOK:Word=10;cInfo:Word=14;cErr:Word=12;
// </>

var
i,wad,sub:integer;
pos,size,pos_,size_:Cardinal;
stream:TFileStream;
tgt:string;
parts:array[1..6]of Cardinal;

procedure jump(cnt:Cardinal);
var i:integer;
off:Cardinal;
begin
for i:=1 to cnt do begin
stream.ReadBuffer(off,4);
stream.Seek(off-4,soFromCurrent);
if stream.Position>size then Abort;
end;
end;

procedure move(off:Integer);
begin
stream.Seek(off,soFromCurrent);
if stream.Position>size then Abort;
end;

procedure subfile(num:Cardinal);
var from,off,cnt:Cardinal;
begin
from:=stream.Position;
stream.Seek((num-1)*8,soFromCurrent);
stream.ReadBuffer(off,4);
stream.ReadBuffer(cnt,4);
if (off=0)or(cnt=0) then begin
WriteCol('Empty',cInfo);
Halt(0);
end;
if pos+off+cnt>size then Abort;
stream.Seek(from+off,soFromBeginning);
size:=from+off+cnt;
end;

procedure save2file(cnt:Cardinal;name:string);
var from:Cardinal;
save:TFileStream;
begin
if cnt=0 then exit;
if cnt>1024*1024*10 then Abort;
from:=stream.Position;
save:=TFileStream.Create(name,fmCreate);
save.CopyFrom(stream,cnt);
save.Free;
stream.Seek(from,soFromBeginning);
end;

function read4():cardinal;
begin
stream.ReadBuffer(Result,4);
if stream.Position>size then Abort;
end;

begin
if ParamCount<>4 then begin
Writeln('');
Writeln('CollisionCracker v1.0, Kly_Men_COmpany!');
Writeln('Usage: "input.wad" (subfile) (sublevel) "output"');
Writeln('"input.wad" = name of source WAD.WAD or an already extracted subfile.');
Writeln('(subfile) = 0 for WAD.WAD or number of subfile to take.');
Writeln('(sublevel) =0 for Spyro1, =-1 for Spyro2 and >=1 for Spyro3 sublevels.');
Writeln('"output" = main name to save resulst, also will be "output.1" .. "output.5"');
Writeln('');
exit;
end;

try
stream:=TFileStream.Create(ParamStr(1),fmOpenRead or fmShareDenyNone);
except Writeln('Open file error!');
exit;
end;

try
wad:=StrToInt64(ParamStr(2));
sub:=StrToInt64(ParamStr(3));
except Writeln('Wrong integer!');
exit;
end;

InitCol();
tgt:=ParamStr(4);
size:=stream.Size;

if wad>0 then try
subfile(wad);
except Writeln('WAD seeking error!');
exit;
end;

pos_:=stream.Position;
size_:=size;

try
if sub>1 then subfile(5+(sub-2)*2) else subfile(2);
except Writeln('SUB seeking error!');
exit;
end;

try
jump(1);
if sub>1 then move(384);
jump(3);
if sub>1 then jump(1);
if sub<0 then jump(2);
if (sub=1)or(sub=-1) then move(12);
except Writeln('SUB-SUB seeking error!');
exit;
end;

try
parts[6]:=read4()-4;
if parts[6]=0 then begin
stream.Seek(pos_,soFromBeginning);
size:=size_;
subfile(3);
move(4);
jump(2);
move(4);
parts[6]:=read4()-4;
end;
pos:=stream.Position;
save2file(parts[6],tgt);
if sub=0 then move(8) else move(12);
for i:=1 to 5 do parts[i]:=read4();
for i:=1 to 5 do begin
stream.Seek(pos+parts[i],soFromBeginning);
save2file(parts[i+1]-parts[i],tgt+'.'+IntToStr(i));
end;
except WriteCol('ERROR',cErr);
exit;
end;

WriteCol('Done!',cOK);
stream.Free;
end.

I did adopt it for SpyroWorldViewer (buggy beta version) and looked at all Spyro3 worlds. I found some very interesting moments than I want to post.

I wanna have an honor to be the first one who showed it! I’ll do it tomorrow.

Then I will post current beta version of SWV with this feature to let everyone investigate collision worlds.

If you don’t want to wait, you can adopt CollisionCracker to your own GML viewer and start to investigate Spyro2. Would be fair if we both find something that is interesting to others.

…and CrystalFissure will make a couple of awesome videos of course!

Sheep wrote:

And finally realized that the number of terrain type bytes was specified here:

What a file is it?
(Also, Spyro1 collision header has only two 4-byte integers, and not 3).

And be careful when loading whole list of triangles as a one big model (d3d_model_create) in Game Maker – if there are too much triangles then some of them will disappear. You have to divide them to several models.

edit: I got something really awesome.

I found a way how to transmit camera movement from Spyro game to SpyroWorldViwer at run-time. Also, how to transmit SpyroWorldViwer’s (great) camera to the game, and make the game accept it! This is dome by run-time ps-assembler patching emulator’s memory.

Will be suitable for any emulator, but a user need to know its RAM start address value – first virtual address of emulated memory. (Because I want to include a list of popular emulator’s start addresses, can you tell such information about emulators that you use? For example: Epsxe 1.7.0: "0x0094C020", Epsxe160: "0x005B6E40", XEBRA: "0x02111000", PCSX: "00BF0020")

Also I tested it only for Spyro3GH, but I will test for original Spyro3, Spyro3 Platinum, Spyro2 Ripto’s Rage, Spyro2 Japan (Tondemo Tours), Spyro1 and Spyro1 Japan. (I don’t have Spyro2 Gateway to Glimmer and any other versions except these).

I get not only the camera, but also Spyro’s coordinates and coordinates (plus some states) of every in-level object, also at runtime! This allows two cool things:

1) We can use SWV to move game camera at any point we want: in levels, in cutscenes or in demo-mode (but not in flying between portals – first it’s useless, and second it’s hard to implement)

2) We can run the game in background and look to SVW’s window to control Spyro. So every object will be visible (as a colored mark), and maybe also – collision triangles. And we can for example use it to place Spyro exactly to a collision triangle to perform additional investigation of collision data.

Maybe also I’ll make a list of every object type for Spyro1/2/3, so colors of mark would be meaningful. I know how to get such information, and I already have a tool…

So there won’t be a buggy beta, I’ll make the full new version of SWV!

Last edited by aleksusklim (Aug 06, 2015 8:55 PM)


~Kly_Men_COmpany

Offline

#10 Aug 06, 2015 10:17 PM

Sheep
Member
Award: Skateboard Contest Winner
From: Norway
Registered: Jan 24, 2008
Posts: 983
Gems: 0
Birthday: 20 January
Age: 25 years old
Gender: Male
Website

Re: Collision data hacking

aleksusklim wrote:

What a file is it?
(Also, Spyro1 collision header has only two 4-byte integers, and not 3).

And be careful when loading whole list of triangles as a one big model (d3d_model_create) in Game Maker – if there are too much triangles then some of them will disappear. You have to divide them to several models.

It was epsxe memory, around the header of Skelos Badlands' collision data.

Which version of GameMaker are you using? I haven't had any problems with too many triangles yet... also, in GM:Studio, you can create your own vertex formats(which must be rendered with custom shaders), and convert models of that format to buffers, which you can edit and convert back on the fly.

aleksusklim wrote:

edit: I got something really awesome.
(...)
So there won’t be a buggy beta, I’ll make the full new version of SWV!

Wow, that really is beyond awesome!
I only use epsxe 1.7.0

I have done a tiny bit more of investigating...

First I tried looking at the collision grid and see if I could find a sensible pattern of filled cells vs empty cells to see if it somehow matched with the level model, mapping it to a black and white bitmap, with different widths. Couldn't get anything particularly good out of it, just some vaguely repeating shapes: http://s18.postimg.org/slr5zngtl/gridbitmap.png Not sure what to make of it, so the grid is still quite a mystery.

Next I experimented with the terrain type bytes in Summer Forest, and found that 200 = climbing ladder. Changing these to 192 through 197-ish (can't remember exactly) made the ladder act as portals to the different Summer Forest levels! The portals also had their own bytes(192 through 197-ish), and changing these to 200 made it so that Spyro would climb on the portal rather than go through it. Values in the range 64-70 also result in the same level portals. I'm not sure if there is any difference, but maybe they are for the different sides of portals?

Anyway, the apparently non-terrain type related terrain type bit... changing it's value does not seem to have any visible effect on gameplay. I changed it to both 0 and to 1 in the whole level and got no reaction. Maybe it's used for something only at level loading?

Edit:
Oh, and one thing I want to test out as soon as I get the time, is to switch the places of two big chunks of triangles in the collision model's triangle list, and at the same time switch out the triangle indices in the optimization lists located directly before the model... Both to confirm that these latter lists do indeed contain triangle indices, and also to see the terrain type bytes affect completely different triangles.

Last edited by Sheep (Aug 07, 2015 11:41 PM)

Offline

#11 Aug 08, 2015 3:12 PM

aleksusklim
Member
From: Uzbekistan
Registered: Aug 03, 2015
Posts: 12
Gems: 0
Birthday: 30 May
Age: 26 years old
Gender: Male
Website

Re: Collision data hacking

Whoa, I just get everything.
How EVERYTHING works!

Manual reading of disassembled binary game code, you know...
There's a function at 0x80018638 (in GH) that deals with collisions...

I'm so excited, I want to show and explain everything!..

Sheep wrote:

Next I experimented with the terrain type bytes in Summer Forest

Where these bytes are located, again?


~Kly_Men_COmpany

Offline

#12 Aug 08, 2015 4:12 PM

Sheep
Member
Award: Skateboard Contest Winner
From: Norway
Registered: Jan 24, 2008
Posts: 983
Gems: 0
Birthday: 20 January
Age: 25 years old
Gender: Male
Website

Re: Collision data hacking

aleksusklim wrote:

Whoa, I just get everything.
How EVERYTHING works!

Manual reading of disassembled binary game code, you know...
There's a function at 0x80018638 (in GH) that deals with collisions...

I'm so excited, I want to show and explain everything!..

Sheep wrote:

Next I experimented with the terrain type bytes in Summer Forest

Where these bytes are located, again?

Oh, now I'm really looking forward to hear what you've got! big_smile

The terrain type bytes are located immediately after the last collision model triangle.

Offline

#13 Aug 08, 2015 6:06 PM

aleksusklim
Member
From: Uzbekistan
Registered: Aug 03, 2015
Posts: 12
Gems: 0
Birthday: 30 May
Age: 26 years old
Gender: Male
Website

Re: Collision data hacking

Sheep wrote:

The terrain type bytes are located immediately after the last collision model triangle.

This is 4-th and 5-th part?
And... as there as many bytes as the triangles?

But I remember that I tried to mess up that data, and there was no effect at run-time!


~Kly_Men_COmpany

Offline

#14 Aug 08, 2015 6:47 PM

Sheep
Member
Award: Skateboard Contest Winner
From: Norway
Registered: Jan 24, 2008
Posts: 983
Gems: 0
Birthday: 20 January
Age: 25 years old
Gender: Male
Website

Re: Collision data hacking

aleksusklim wrote:

This is 4-th and 5-th part?
And... as there as many bytes as the triangles?

But I remember that I tried to mess up that data, and there was no effect at run-time!

I don't know where it is in the wad files( haven't opened a .wad file yet), but in the game memory, at least, I always find it immediately after the triangles.

There are not as many bytes as there are triangles. The amount is specified somewhere in the header that is found immediately before the grid thing. In the headers I have looked at, the terrain type byte amount seemed to be in the long int following a long int that had a value equal to {the number of triangles in the model + 'some "random" value below 1000'}. The two headers I did look at, Skelos Badlands and Summer forest didn't seem to have the exact same format, though...

I'm honestly not 100% sure which Spyro 3 version this is(might be Greatest Hits), but it's an NTSC version... In the [pec] address range for the game while playing in Sunrise Spring, 800FCFC0 - 800FCFFF, there are some values belonging to the last triangles, and then some terrain type bytes. Those that are 192 are the Sunny Villa portal. Changing them to 255 makes Spyro just collide with the portal instead.

Edit:
Looking at the Sunrise Spring header:
The "number of triangles(and possibly plus some value)" long int says 10409, while the long int after it says 34. And it does look like only the 34 first bytes following the last triangle might be terrain types. It's somewhat hard to tell, since the chunk of data after the terrain type bytes also contain similar groups of repeating byte values.

Edit:
Experimented some in Midday Gardens... 198 is the terrain type value for ice. And 199 is climbable surface.

Edit:
Evening lake:
198 seems to be climbable surface, while 199 is the breakable wall. When I set everything to 199 and charge into a portal or into the ladder, the original breakable wall breaks!

Last edited by Sheep (Aug 09, 2015 8:01 AM)

Offline

#15 Aug 09, 2015 7:35 AM

CrystalFissure
Member
From: Adelaide
Registered: May 16, 2015
Posts: 77
Gems: 0
Gender: Male
Website

Re: Collision data hacking

aleksusklim wrote:

I get not only the camera, but also Spyro’s coordinates and coordinates (plus some states) of every in-level object, also at runtime! This allows two cool things:
1) We can use SWV to move game camera at any point we want: in levels, in cutscenes or in demo-mode (but not in flying between portals – first it’s useless, and second it’s hard to implement)

Holy *bleep*. That is incredible. I can't wait until I can make videos about what you guys have been doing.

I don't understand much of the technical talk, but I read all of it because it is truly fascinating. If there's anything you want / need me to do in terms of videos to bring more exposure, just let me know. Because I want your work to be even more popular!

Last edited by CrystalFissure (Aug 09, 2015 7:38 AM)

Offline

#16 Aug 12, 2015 10:49 PM

aleksusklim
Member
From: Uzbekistan
Registered: Aug 03, 2015
Posts: 12
Gems: 0
Birthday: 30 May
Age: 26 years old
Gender: Male
Website

Re: Collision data hacking

Sorry for pause, but it took me forever to just make a simple image… long story.
I just.. wanna show something. This is cool thing! But I want to make it work in all versions of Spyro that I have. And also in all emulators. Include ones that I don’t have.

Which means that I need to find a very simple way how to get "ramtop" start address of game memory in any emulator. And I got one! Just make a "game" that will fill some strings at beginning and at end of memory – what could be simpler?

And I also got a real compiler from "Blade Libraries" (so called "psonedevstudio2006" – http://klimaleksus.narod.ru/Files/4/pso … io2006.rar ). Well, I took one of the examples and wrote this C-code:

#include <bladeps.h>
u_char ot[2000];
void copy(char*from,char*to){
while(*from)*(to--)=*(from++);
}
void code(char*a0,char*a1,char*a2,char*a3){
copy(a0,(char*)0x8000000f);
copy(a1,(char*)0x801fffff);
copy(a2,(char*)0x1f80000f);
copy(a3,(char*)0x1f8003ff);
while(1);;
}
void main (void){
GsInitGraphQuick (320, 240);
GsSetWorkBase(&ot[0]);
GsSortClear(0,255,0);
GsDrawOT ((u_long *) &ot[0]);
code("!MARniaMfOtratS!","!!MARniaMfOdnE!!","!hctarcSfOtratS!","!!hctarcSfOdnE!!");
}
//   "!StartOfMainRAM!","!!EndOfMainRAM!!","!StartOfScratch!","!!EndOfScratch!!"

Worked perfect. (not really…)

Then I have to write a Windows-program that will search emulator’s memory for these strings.
Here is the result:

program RamtopFinder;

{$APPTYPE CONSOLE}

uses SysUtils,Windows,ProcessFinder // in ProcessFinder.pas
;

var
si:_SYSTEM_INFO;
mem:_MEMORY_BASIC_INFORMATION;
lpMem:Cardinal;
p:pointer;
h,rpid:Cardinal;
c,s,i,j,k,q:Cardinal;
f:Boolean;
pid,a:string;
r:array[0..3]of Cardinal;
t:array [0..3,0..3] of Cardinal;
w:integer;

const
tt:array [0..3] of string = ('!StartOfMainRAM!','!!EndOfMainRAM!!','!StartOfScratch!','!!EndOfScratch!!');
imax:integer=1; // imax:integer=3;

function get(p:Pointer;i:Cardinal):Cardinal;
begin
Result:=PCardinal(Cardinal(p)+i)^;
end;

begin
q:=0;
w:=2;
pid:='';
for i:=1 to ParamCount do begin
a:=ParamStr(i);
if (Pos('?',a)>0)or(a='-h')or(a='/h')or(a='--help') then begin
Writeln('');
Writeln('RamtopFinder v1.0, Kly_Men_COmpany!');
Writeln('A tool for getting real address of game RAM from');
Writeln('                      any PlayStation1 emulator. Usage:');
Writeln('1) Load "RAMTOP.EXE" or "RAMTOP.ISO" in your emulator and run it');
Writeln('2) You should see green-blue screen flickering in the game window');
Writeln('3) Pause emulation it you can; or not do it');
Writeln('4) Run this RamtopFinder.exe or RamtopFinder.bat!');
Writeln('');
Writeln('      Command-line options:');
Writeln('RamtopFinder.exe [name | pid] [/type]');
Writeln('"name/pid" = executable name or processID of emulator.');
Writeln('                   (default is the last created process)');
Writeln('"type" = /0 for decimal, others for hexadecimal:');
Writeln(' = /1 will add zeroes at beginning to make 8 digits;');
Writeln(' = /2 will add 0x and zeroes; [default]');
Writeln(' = /3 will add $ and zeroes;');
Writeln(' = /4 will add "h" at the end and zeroes;');
Writeln(' Use /-1, /-2, /-3, /-4 accordingly to NOT add leading zeroes.');
Writeln('');
Writeln('Program will print result to the standard output,');
Writeln('Result will be zero if game RAM is not found.');
Writeln('Also, result is returned as the program exit code (%errorlevel%)');
exit;
end;
if a[1]='/' then begin
j:=ord(a[Length(a)])-ord('0');
if (j<=4) then begin
if a[2]='-' then w:=-j else w:=j;
continue;
end else pid:=' ';
end;
if pid<>'' then begin
Writeln('Wrong usage. Try /? for help');
exit;
end;
pid:=a;
end;
for i:=0 to 3 do r[i]:=0;
try
for i:=0 to imax do for j:=0 to 3 do t[i,j]:=get(PChar(tt[i]),j*4);
rpid:=ProcessFind(pid);
if rpid=0 then Abort;
h:=OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,false,rpid);
if h=0 then Abort;
GetSystemInfo(si);
lpMem:=0;
while(lpMem<Cardinal(si.lpMaximumApplicationAddress))do begin
if VirtualQueryEx(h,Ptr(lpMem),mem,sizeof(mem))<1 then break;
s:=mem.RegionSize;
if ((mem.State and $1000)>0)and((mem.State and $10000)=0)and(s>=2*1024*1024)then begin
GetMem(p,s);
ReadProcessMemory(h,mem.BaseAddress,p,s,c);
if c<>s then Writeln('Err!') else begin
k:=0;
Dec(s,15);
While k<s do begin
for i:=0 to imax do begin
f:=true;
for j:=0 to 3 do if t[i,j]<>get(p,k+j*4) then begin
f:=false;
break;
end;
if f then begin
if r[i]>0 then r[i]:=Cardinal(-1) else r[i]:=k+Cardinal(mem.BaseAddress);
end;
end;
Inc(k,4);
end;
end;
FreeMem(p);
end;
lpMem:= Cardinal(mem.BaseAddress) + Cardinal(mem.RegionSize);
end;
except end;
if(r[0]>0)and(r[1]>0)and((r[1]-r[0]+16)=2*1024*1024)then q:=r[0];
if w=0 then Writeln(q) else begin
a:=IntToHex(q,8);
if w<0 then begin while(Length(a)>1)and(a[1]='0')do Delete(a,1,1);w:=-w;end;
if w=2 then a:='0x'+a;
if w=3 then a:='$'+a;
if w=4 then a:=a+'h';
Writeln(a);
end;
Halt(q);
end.

// ProcessFinder.pas

{
unit ProcessFinder;
interface
uses SysUtils,Windows;
function CreateToolhelp32Snapshot(flag,pid:Integer):integer;
stdcall; external 'kernel32.dll';
function Process32First(handle:Integer;pnt:pointer):Boolean;
stdcall; external 'kernel32.dll';
function Process32Next(handle:Integer;pnt:pointer):Boolean;
stdcall; external 'kernel32.dll';
function ProcessFind(var PidOrName:string):Cardinal;
implementation
function ProcessFind(var PidOrName:string):Cardinal;
var s:Cardinal;
p:string;
prc32:record
 dwSize: integer;
 cntUsage: integer;
 th32ProcessID: cardinal;
 th32DefaultHeapID: integer;
 th32ModuleID: integer;
 cntThreads: integer;
 th32ParentProcessID: cardinal;
 pcPriClassBase: integer;
 dwFlags: integer;
 szExeFile: array [0..259] of char;
end;
var cpid,prid:Cardinal;
npid,nexe:string;
begin
Result:=0;
prid:=0;
cpid:=GetCurrentProcessId();
PidOrName:=Trim(PidOrName);
p:=LowerCase(PidOrName);
s:=CreateToolhelp32Snapshot(2,0);
prc32.dwSize:=system.sizeof(prc32);
if not Process32First(s,@prc32) then exit;
while (Process32Next(s,@prc32)) do begin
if prc32.th32ProcessID=cpid then continue;
nexe:=prc32.szExeFile;
prid:=prc32.th32ProcessID;
npid:=inttostr(prid);
if(npid=PidOrName)or(nexe=PidOrName)or((LowerCase(nexe)=p)and(Result=0)) then begin
Result:=prid;
PidOrName:=nexe;
if(nexe=PidOrName)or(npid=PidOrName) then break;
end;
end;
if Result=0 then begin
Result:=prid;
PidOrName:=nexe;
end;
CloseHandle(s);
end;

end.
}

But some emulators do not have an option to "Run PSX-EXE", they requiring BIN/ISO images.
OK, I need to burn my EXE to image, and I know how! Well, I thought that I know…

Any my attempt to make an image (PSX.EXE, MAIN.PSX, SYSTEM.CNF – I tried everything!) gives me only "recompile block too large" error and emulator crashing. None of the examples from Blade Libraries would work too (works alone, not in the image). Also I tried assembler "spASM" ( http://www.psfake.com/PsxFiles ) with the same error. But I found out that official examples from PlayStation SDK (by Sony) are working – and alone and in the image.

So I had to write my code on their compiler (make, preprocess, translate, assemble, linker…). Here is the final version: (again I took one of the examples and deleted everything that I don’t need here)

void copy(char*from,char*to){
while(*from)*(to--)=*(from++);
}
void code(char*a0,char*a1,char*a2,char*a3){
copy(a0,(char*)0x8000000f);
copy(a1,(char*)0x801fffff);
copy(a2,(char*)0x1f80000f);
copy(a3,(char*)0x1f8003ff);
}

#include <r3000.h>
#include <asm.h>
#include <libapi.h>
#include <sys/file.h>
#include <sys/types.h>
#include <libgte.h>
#include <libgpu.h>
#include <libetc.h>

typedef struct {
	DRAWENV		draw;
	DISPENV		disp;
} DB;
DB	db[2];
DB	*cdb;

main()
{
	ResetGraph(0);
	SetDefDrawEnv(&db[0].draw, 0,   0, 320, 240);
	SetDefDispEnv(&db[0].disp, 0, 240, 320, 240);
	SetDefDrawEnv(&db[1].draw, 0, 240, 320, 240);
	SetDefDispEnv(&db[1].disp, 0,   0, 320, 240);

code("!MARniaMfOtratS!","!!MARniaMfOdnE!!","!hctarcSfOtratS!","!!hctarcSfOdnE!!");
//   "!StartOfMainRAM!","!!EndOfMainRAM!!","!StartOfScratch!","!!EndOfScratch!!"

	db[0].draw.isbg = 1;
	setRGB0(&db[0].draw, 0, 255, 0);
	db[1].draw.isbg = 1;
	setRGB0(&db[1].draw, 0, 0, 255);
	SetDispMask(1);
	while(1) {
		DrawSync(0);
		VSync(0);
		cdb = (cdb==db)?db+1:db;
		PutDrawEnv(&cdb->draw);
		PutDispEnv(&cdb->disp);
	}
}

What this does? Load .EXE or .ISO into any emulator and play. You should see green-blue screen flickering. That means everything is OK and you can run my Delphi program to dump an address of RAM from emulator’s memory.

Program copies string "!StartOfMainRAM!" to very first address (0x80000000) and string "!!EndOfMainRAM!!" to the very end (16 bytes before the end, so there’s no memory left after last character of the string). I thought that such messing could crash the "game", but seems that it’s working!
Also "!StartOfScratch!" and "!!EndOfScratch!!" are copied to scratchpad D-cache, you can find them manually by searching in memory, if you need.

Here is the link:
http://klimaleksus.narod.ru/Files/4/RamtopFinder.rar

Run RAMTOP in emulator and then drag and drop emulator’s executable to my .bat file.
For now – just to test that everything works fine.

***

So my next plan is this:
1) Release my new cool hack.
2) Explain disassembled collision function.
3) Finish new version of SpyroWorldViewer.

P.S.
Links to SDK:
http://klimaleksus.narod.ru/Files/4/PS_SDK_compiler.rar – compiler only;
http://klimaleksus.narod.ru/Files/4/PS_SDK_stuff.rar – everything else.

Sheep wrote:

there are some values belonging to the last triangles, and then some terrain type bytes. Those that are 192 are the Sunny Villa portal. Changing them to 255 makes Spyro just collide with the portal instead.

You right...


~Kly_Men_COmpany

Offline

#17 Aug 13, 2015 9:41 AM

Sheep
Member
Award: Skateboard Contest Winner
From: Norway
Registered: Jan 24, 2008
Posts: 983
Gems: 0
Birthday: 20 January
Age: 25 years old
Gender: Male
Website

Re: Collision data hacking

Wow, this is really way above my experience and skill level!! I can understand it took you a few days. Also, clever way of finding the addresses!

I ran the test, using ePSXe 1.7.0 and got:
0x0094C020
9748512

The screen flickering was really painful, though, so I suggest replacing it with something else if more people need to go through it tongue

Offline

#18 Aug 14, 2015 7:38 PM

aleksusklim
Member
From: Uzbekistan
Registered: Aug 03, 2015
Posts: 12
Gems: 0
Birthday: 30 May
Age: 26 years old
Gender: Male
Website

Re: Collision data hacking

Sheep wrote:

Which version of GameMaker are you using?

Game Maker 8.0. ( http://klimaleksus.narod.ru/Files/GML8.rar )

Ones I even tried to send a bug report

Sheep wrote:

in GM:Studio, you can create your own vertex formats(which must be rendered with custom shaders), and convert models of that format to buffers, which you can edit and convert back on the fly.

Wow! I definitely need it, maybe sometimes it will be helpful.
Can I have it, could you upload you version somewhere?

Sheep wrote:

The screen flickering was really painful, though, so I suggest replacing it with something else

Since I want to keep my code simple, I just decide to add ~1 sec vSync wait (but seems it worked not on every emulator). For Blade version I switched to double-buffering and it got a little wait by it’s own (no sync? strange…) I decide to release both Sony and Blade versions (.EXE and .PSX), also I burn two images with Sony version: BIN and ISO, first contains only PSX.EXE while second is build with SYSTEM.CNF plus an empty file at the end, because one emulator crashed with error "seek past end of disc"…

New version is here: http://klimaleksus.narod.ru/Files/4/RamtopFinder2.rar  (no need to download ‘cause everything is included in my next program below)

While my Delphi program isn’t essentially changed (only a printable description; and .bat file will now keep history of results and show it with standalone run), so here’s a copy of improved PlayStation C-code, both versions (I named Sony as .cpp and Blade as .c)

Hidden text
// for PS1 SDK

void copy(char*from,char*to){
while(*from)*(to--)=*(from++);
}
void code(char*a0,char*a1,char*a2,char*a3){
copy(a0,(char*)0x8000000f);
copy(a1,(char*)0x801fffff);
copy(a2,(char*)0x1f80000f);
copy(a3,(char*)0x1f8003ff);
}

#include <r3000.h>
#include <asm.h>
#include <libapi.h>
#include <sys/file.h>
#include <sys/types.h>
#include <libgte.h>
#include <libgpu.h>
#include <libetc.h>

typedef struct {
 DRAWENV draw;
 DISPENV disp;
} DB;
DB db[2];
DB *cdb;
int i;
main()
{
	ResetGraph(0);
	SetDefDrawEnv(&db[0].draw, 0,   0, 320, 240);
	SetDefDispEnv(&db[0].disp, 0, 240, 320, 240);
	SetDefDrawEnv(&db[1].draw, 0, 240, 320, 240);
	SetDefDispEnv(&db[1].disp, 0,   0, 320, 240);

code("!MARniaMfOtratS!","!!MARniaMfOdnE!!","!hctarcSfOtratS!","!!hctarcSfOdnE!!");
//   "!StartOfMainRAM!","!!EndOfMainRAM!!","!StartOfScratch!","!!EndOfScratch!!"

	db[0].draw.isbg = 1;
	setRGB0(&db[0].draw, 0, 255, 0);
	db[1].draw.isbg = 1;
	setRGB0(&db[1].draw, 0, 0, 255);
	SetDispMask(1);
	DrawSync(0);
	VSync(0);
	while(1) {
		cdb = (cdb==db)?db+1:db;
		PutDrawEnv(&cdb->draw);
		PutDispEnv(&cdb->disp);
		DrawSync(0);
		for(i=0;i<60;i++)VSync(0);
	}
}
// for Blade Libs

#include <bladeps.h>
u_char ot[2][2000];
int i,a,b;
void copy(char*from,char*to){
while(*from)*(to--)=*(from++);
}
void code(char*a0,char*a1,char*a2,char*a3){
copy(a0,(char*)0x8000000f);
copy(a1,(char*)0x801fffff);
copy(a2,(char*)0x1f80000f);
copy(a3,(char*)0x1f8003ff);
}
void main (void){
GsInitGraphQuick (320, 240);
code("!MARniaMfOtratS!","!!MARniaMfOdnE!!","!hctarcSfOtratS!","!!hctarcSfOdnE!!");
//  "!StartOfMainRAM!","!!EndOfMainRAM!!","!StartOfScratch!","!!EndOfScratch!!"
while(1){
a = GsGetActiveBuff ();
GsSetWorkBase(&ot[a][0]);
if(a)GsSortClear(0,0,255);else GsSortClear(0,255,0);
GsSwapDispBuff ();
GsDrawOT ((u_long *) &ot[a][0]);
}
}

SpyroFreezeChreat

Now everything is perfect for my hack. It’s hard and long to explain it all in details, but I know that I must do it. I need to make at least a brief description of MIPS and disassembly, because this cheat and then our collision function requires deep understanding of what happened inside Spyro code. And firstly I will explain my assembler code from this hack before I’ll do the same with collisions. But not right now))

Here’s just the result:
http://klimaleksus.narod.ru/Files/4/Spy … eCheat.rar

CrystalFissure, did you ask for something spectacular to make a video?

It allows to freeze all in-game objects (except Spyro). The second feature is to freeze dust-particle animation.

I did this hack earlier: ( http://klimaleksus.narod.ru/Files/2/freeze_1.jpg , http://klimaleksus.narod.ru/Files/2/freeze_2.jpg ,
http://klimaleksus.narod.ru/Files/G/cs_2.jpg ,
http://klimaleksus.narod.ru/Files/G/cs_0.jpg ), but now it’s fully integrated to the game and controlled by in-game controller (joystick) ! By default I bind keys to L1/L2/L3 and R1/R2/R3; optionally you can switch only to L3 and R3 if you could choose they in emulator settings.

How to use it? First, run one of my RAMTOP in your emulator. Then drag-and-drop emulator executable to RamtopFinder.bat, it will also set FreezeCheat to this emulator.

Then run any Spyro. Currently supported: Spyro1 USA (SCUS_942.28), Spyro1 JAP (SCPS_100.83), Spyro2 USA Ripto’s Rage (SCUS_944.25), Spyro2 JAP Tondemo Tours (SCPS_101.28), Spyro3 USA (SCUS_944.67), Spyro3 USA Greatest Hits (SCUS_944.67), Spyro3 EUP / Platinum (SCES_028.35) In other possible versions it’ll not work and probably will crash emulator.

Go to \settings\ folder and run a .CMD file according to your version of Spyro (default is GH version). Also there you can run Set_onlyL3R3.cmd to disable L1/L2/R1/R2 cheat controlling (only R3 and L3); or Set_allLR.cmd to restore default.

Then just run FreezeCheat.bat or (to be exact) drag and drop emulator’s .exe to it (in case if you already configured with RAMTOP several emulators then you don’t need do it again with RamtopFinder.bat, instead drag them to FreezeCheat.bat; running this standalone will use last used emulator name).

It’s almost done, return to game! Many some emulators require to make a savestate and load it again in order to refresh a cache.

Since my cheat only controlled by two keys, I will call them L and R (any of L1/L2/L3 = L represents the same effect, and R1/R2/R3 = R). Go to any level and press L+R. Then release them. Freeze will be active.

Objects will not move, and any dust will be frozen in mid-air. Then press or hold L to toggle objects, and R to toggle dust.

Since there are 60 (or 30…) frames per second, you should be very quick to just enable or disable something; or press it several times until it do what you want.

From the other side, this allows you to hold L or R to decrease animation speed by 50%. And you can run anywhere with pressed key! (Configure L3 and R3 to use this cheat without affecting the game camera).

To disable cheat again (keeping it active during all that stuff like screens, menus and scenes may cause crashing) hold L + R. You can even use this mechanism alone, without separated pressing.

There are two differences between games: big and small. In Spyro3 I did found only a pointer to table of object-type functions. If I zerofill it – game thinks that no one object have its code. BUT! It continue play current animation loop for every object. This is rather fun, you can freeze and look at any animation long enough (normally some animations will play only one pass, nut here they’ll be looped).
In Spyro1 and Spyro2 instead I found just a pointer to function that deals with objects, and not a list. I have I thought where’s a list, but I’m no sure where is the pointer… So, freezing will also freeze any animation. Everything becomes really frozen!

Dust animation is exactly the same in every version. My second minor difference is in Spyro1 – every function pointer (main objects or dust) must be replaced with a pointer to an empty function ("return;" / "jr ra; nop") and not be just zerofilled.

So, how this works I will explain later. I must say that it’s also possible to burn the patched game to image and disc to feed it to a real PlayStation. Also it’s easy to add any version that not here yet. I don’t know is it necessary.

There also included my program MemPatcher.exe that I made earlier specially for this patch but I want it to be more useful and powerful. For now it isn’t finished yet, but here it does what I need. Shortly, this tool can copy something from one file to another, and also can work with any process’ memory – patch or dump. There left unfinished only some stuff with backuping and user interface that isn’t needed here.

Since I’m not sure that I didn’t mess something up with data and pointers in patch files while tried to keep seven versions to work together, here’s (I hope…) the correct list of all constants:

Hidden text

1 USA (SCUS_942.28)
main=80075734
dust=800756bc
keys=80077380
jump=80012284

1 JAP (SCPS_100.83)
main=8007f2a4
dust=8007f214
keys=80080F80
jump=80011650

2 RR (SCUS_944.25)
main=800670F0
dust=800670F8
keys=8006A9AC
jump=80011b04

2 TT (SCPS_101.28)
main=80069814
dust=8006981C
keys=8006AF58
jump=80011fd8

3 USA (SCUS_944.67)
main=800742d8
dust=800742e8
keys=80071500
jump=80012034

3 GH (SCUS_944.67)
main=800743b8
dust=800743c8
keys=800715e0
jump=80012048

3 EUP (SCES_028.35)
main=80077794
dust=800777b4
keys=80071834
jump=80013fec

What we can do with FreezeCheat? For example, if it will be active during level load, then every object will be at it native position in default state. Go check where is Bianca ^^
In Spyro1 objects stay solid during charge while in 2/3 Spyro will charge through. But anyway, they all will die instantly when you unfreeze them!
Portals from Spyro1 behave like objects, and if you disable animation right after going through one – Spyro will be normally controllable and could walk away, but after enabling – he will be pushed back to portal by invisible force.

Removing dust animation allows you literally draw a pixelart with your headbash; or point with white ball every wall that you charge in. And tracing Sparx’s fly…
Sadly that there is finite number of particles, and adding more will randomly destroy others… but still it’s great!

I didn’t investigate further, despite it’s very interesting. Try by yourselves! Explode something and freeze everything))
Make a video if you find other cool things with freezing.

(a link, again: SpyroFreezeCheat.rar)

Last edited by aleksusklim (Aug 14, 2015 7:42 PM)


~Kly_Men_COmpany

Offline

#19 Aug 16, 2015 1:06 AM

spyroyo
Member
Registered: Nov 24, 2013
Posts: 16
Gems: 0

Re: Collision data hacking

aleksusklim wrote:
Sheep wrote:

Which version of GameMaker are you using?

Game Maker 8.0. ( http://klimaleksus.narod.ru/Files/GML8.rar )

Ones I even tried to send a bug report

Sheep wrote:

in GM:Studio, you can create your own vertex formats(which must be rendered with custom shaders), and convert models of that format to buffers, which you can edit and convert back on the fly.

Wow! I definitely need it, maybe sometimes it will be helpful.
Can I have it, could you upload you version somewhere?

Sheep wrote:

The screen flickering was really painful, though, so I suggest replacing it with something else

Since I want to keep my code simple, I just decide to add ~1 sec vSync wait (but seems it worked not on every emulator). For Blade version I switched to double-buffering and it got a little wait by it’s own (no sync? strange…) I decide to release both Sony and Blade versions (.EXE and .PSX), also I burn two images with Sony version: BIN and ISO, first contains only PSX.EXE while second is build with SYSTEM.CNF plus an empty file at the end, because one emulator crashed with error "seek past end of disc"…

New version is here: http://klimaleksus.narod.ru/Files/4/RamtopFinder2.rar  (no need to download ‘cause everything is included in my next program below)

While my Delphi program isn’t essentially changed (only a printable description; and .bat file will now keep history of results and show it with standalone run), so here’s a copy of improved PlayStation C-code, both versions (I named Sony as .cpp and Blade as .c)

Hidden text
// for PS1 SDK

void copy(char*from,char*to){
while(*from)*(to--)=*(from++);
}
void code(char*a0,char*a1,char*a2,char*a3){
copy(a0,(char*)0x8000000f);
copy(a1,(char*)0x801fffff);
copy(a2,(char*)0x1f80000f);
copy(a3,(char*)0x1f8003ff);
}

#include <r3000.h>
#include <asm.h>
#include <libapi.h>
#include <sys/file.h>
#include <sys/types.h>
#include <libgte.h>
#include <libgpu.h>
#include <libetc.h>

typedef struct {
 DRAWENV draw;
 DISPENV disp;
} DB;
DB db[2];
DB *cdb;
int i;
main()
{
	ResetGraph(0);
	SetDefDrawEnv(&db[0].draw, 0,   0, 320, 240);
	SetDefDispEnv(&db[0].disp, 0, 240, 320, 240);
	SetDefDrawEnv(&db[1].draw, 0, 240, 320, 240);
	SetDefDispEnv(&db[1].disp, 0,   0, 320, 240);

code("!MARniaMfOtratS!","!!MARniaMfOdnE!!","!hctarcSfOtratS!","!!hctarcSfOdnE!!");
//   "!StartOfMainRAM!","!!EndOfMainRAM!!","!StartOfScratch!","!!EndOfScratch!!"

	db[0].draw.isbg = 1;
	setRGB0(&db[0].draw, 0, 255, 0);
	db[1].draw.isbg = 1;
	setRGB0(&db[1].draw, 0, 0, 255);
	SetDispMask(1);
	DrawSync(0);
	VSync(0);
	while(1) {
		cdb = (cdb==db)?db+1:db;
		PutDrawEnv(&cdb->draw);
		PutDispEnv(&cdb->disp);
		DrawSync(0);
		for(i=0;i<60;i++)VSync(0);
	}
}
// for Blade Libs

#include <bladeps.h>
u_char ot[2][2000];
int i,a,b;
void copy(char*from,char*to){
while(*from)*(to--)=*(from++);
}
void code(char*a0,char*a1,char*a2,char*a3){
copy(a0,(char*)0x8000000f);
copy(a1,(char*)0x801fffff);
copy(a2,(char*)0x1f80000f);
copy(a3,(char*)0x1f8003ff);
}
void main (void){
GsInitGraphQuick (320, 240);
code("!MARniaMfOtratS!","!!MARniaMfOdnE!!","!hctarcSfOtratS!","!!hctarcSfOdnE!!");
//  "!StartOfMainRAM!","!!EndOfMainRAM!!","!StartOfScratch!","!!EndOfScratch!!"
while(1){
a = GsGetActiveBuff ();
GsSetWorkBase(&ot[a][0]);
if(a)GsSortClear(0,0,255);else GsSortClear(0,255,0);
GsSwapDispBuff ();
GsDrawOT ((u_long *) &ot[a][0]);
}
}

SpyroFreezeChreat

Now everything is perfect for my hack. It’s hard and long to explain it all in details, but I know that I must do it. I need to make at least a brief description of MIPS and disassembly, because this cheat and then our collision function requires deep understanding of what happened inside Spyro code. And firstly I will explain my assembler code from this hack before I’ll do the same with collisions. But not right now))

Here’s just the result:
http://klimaleksus.narod.ru/Files/4/Spy … eCheat.rar

CrystalFissure, did you ask for something spectacular to make a video?

It allows to freeze all in-game objects (except Spyro). The second feature is to freeze dust-particle animation.

I did this hack earlier: ( http://klimaleksus.narod.ru/Files/2/freeze_1.jpg , http://klimaleksus.narod.ru/Files/2/freeze_2.jpg ,
http://klimaleksus.narod.ru/Files/G/cs_2.jpg ,
http://klimaleksus.narod.ru/Files/G/cs_0.jpg ), but now it’s fully integrated to the game and controlled by in-game controller (joystick) ! By default I bind keys to L1/L2/L3 and R1/R2/R3; optionally you can switch only to L3 and R3 if you could choose they in emulator settings.

How to use it? First, run one of my RAMTOP in your emulator. Then drag-and-drop emulator executable to RamtopFinder.bat, it will also set FreezeCheat to this emulator.

Then run any Spyro. Currently supported: Spyro1 USA (SCUS_942.28), Spyro1 JAP (SCPS_100.83), Spyro2 USA Ripto’s Rage (SCUS_944.25), Spyro2 JAP Tondemo Tours (SCPS_101.28), Spyro3 USA (SCUS_944.67), Spyro3 USA Greatest Hits (SCUS_944.67), Spyro3 EUP / Platinum (SCES_028.35) In other possible versions it’ll not work and probably will crash emulator.

Go to \settings\ folder and run a .CMD file according to your version of Spyro (default is GH version). Also there you can run Set_onlyL3R3.cmd to disable L1/L2/R1/R2 cheat controlling (only R3 and L3); or Set_allLR.cmd to restore default.

Then just run FreezeCheat.bat or (to be exact) drag and drop emulator’s .exe to it (in case if you already configured with RAMTOP several emulators then you don’t need do it again with RamtopFinder.bat, instead drag them to FreezeCheat.bat; running this standalone will use last used emulator name).

It’s almost done, return to game! Many some emulators require to make a savestate and load it again in order to refresh a cache.

Since my cheat only controlled by two keys, I will call them L and R (any of L1/L2/L3 = L represents the same effect, and R1/R2/R3 = R). Go to any level and press L+R. Then release them. Freeze will be active.

Objects will not move, and any dust will be frozen in mid-air. Then press or hold L to toggle objects, and R to toggle dust.

Since there are 60 (or 30…) frames per second, you should be very quick to just enable or disable something; or press it several times until it do what you want.

From the other side, this allows you to hold L or R to decrease animation speed by 50%. And you can run anywhere with pressed key! (Configure L3 and R3 to use this cheat without affecting the game camera).

To disable cheat again (keeping it active during all that stuff like screens, menus and scenes may cause crashing) hold L + R. You can even use this mechanism alone, without separated pressing.

There are two differences between games: big and small. In Spyro3 I did found only a pointer to table of object-type functions. If I zerofill it – game thinks that no one object have its code. BUT! It continue play current animation loop for every object. This is rather fun, you can freeze and look at any animation long enough (normally some animations will play only one pass, nut here they’ll be looped).
In Spyro1 and Spyro2 instead I found just a pointer to function that deals with objects, and not a list. I have I thought where’s a list, but I’m no sure where is the pointer… So, freezing will also freeze any animation. Everything becomes really frozen!

Dust animation is exactly the same in every version. My second minor difference is in Spyro1 – every function pointer (main objects or dust) must be replaced with a pointer to an empty function ("return;" / "jr ra; nop") and not be just zerofilled.

So, how this works I will explain later. I must say that it’s also possible to burn the patched game to image and disc to feed it to a real PlayStation. Also it’s easy to add any version that not here yet. I don’t know is it necessary.

There also included my program MemPatcher.exe that I made earlier specially for this patch but I want it to be more useful and powerful. For now it isn’t finished yet, but here it does what I need. Shortly, this tool can copy something from one file to another, and also can work with any process’ memory – patch or dump. There left unfinished only some stuff with backuping and user interface that isn’t needed here.

Since I’m not sure that I didn’t mess something up with data and pointers in patch files while tried to keep seven versions to work together, here’s (I hope…) the correct list of all constants:

Hidden text

1 USA (SCUS_942.28)
main=80075734
dust=800756bc
keys=80077380
jump=80012284

1 JAP (SCPS_100.83)
main=8007f2a4
dust=8007f214
keys=80080F80
jump=80011650

2 RR (SCUS_944.25)
main=800670F0
dust=800670F8
keys=8006A9AC
jump=80011b04

2 TT (SCPS_101.28)
main=80069814
dust=8006981C
keys=8006AF58
jump=80011fd8

3 USA (SCUS_944.67)
main=800742d8
dust=800742e8
keys=80071500
jump=80012034

3 GH (SCUS_944.67)
main=800743b8
dust=800743c8
keys=800715e0
jump=80012048

3 EUP (SCES_028.35)
main=80077794
dust=800777b4
keys=80071834
jump=80013fec

What we can do with FreezeCheat? For example, if it will be active during level load, then every object will be at it native position in default state. Go check where is Bianca ^^
In Spyro1 objects stay solid during charge while in 2/3 Spyro will charge through. But anyway, they all will die instantly when you unfreeze them!
Portals from Spyro1 behave like objects, and if you disable animation right after going through one – Spyro will be normally controllable and could walk away, but after enabling – he will be pushed back to portal by invisible force.

Removing dust animation allows you literally draw a pixelart with your headbash; or point with white ball every wall that you charge in. And tracing Sparx’s fly…
Sadly that there is finite number of particles, and adding more will randomly destroy others… but still it’s great!

I didn’t investigate further, despite it’s very interesting. Try by yourselves! Explode something and freeze everything))
Make a video if you find other cool things with freezing.

(a link, again: SpyroFreezeCheat.rar)

Hi aleks,
I'm a big fan of your work. I was wondering if you could possibly make a program where you can load a level and make it show the texture measurements like so:

http://i.ytimg.com/vi/owEUiRSjn38/hqdefault.jpg

Offline

#20 Aug 16, 2015 11:23 AM

aleksusklim
Member
From: Uzbekistan
Registered: Aug 03, 2015
Posts: 12
Gems: 0
Birthday: 30 May
Age: 26 years old
Gender: Male
Website

Re: Collision data hacking

spyroyo wrote:

I'm a big fan of your work.

…I think it's wasn't necessary to quote my entire post))

spyroyo wrote:

I was wondering if you could possibly make a program where you can load a level and make it show the texture measurements like so:

What? You need texture number on each tile? Number of what – just an index in texture list? Or some coordinates from texture definition table from a level?
Why not just draw these numbers in .BMP extracted texture tiles?


~Kly_Men_COmpany

Offline

#21 Aug 16, 2015 3:13 PM

Sheep
Member
Award: Skateboard Contest Winner
From: Norway
Registered: Jan 24, 2008
Posts: 983
Gems: 0
Birthday: 20 January
Age: 25 years old
Gender: Male
Website

Re: Collision data hacking

Finally got some time today to test out your hack, aleksusklim... Wow, this is amazing big_smile
Like... really, really amazing! So fun to stop the time, kill enemies, and then see them die all at once. Spyro suddenly got super powers!
Edit:
Playing some in Sunny Villa... apart from the fact that they don't do damage, the iron bar gates turn into what would have been deadly traps. Slowly lift, *bam* fall down, slowly lift, *bam* fall down...

Edit:
Skateboarding: I don't know how it happened, and I'm not able to replicate it, but somehow the Skateboard got more disconnected from Spyro than usual, so it looks like Spyro casually slips away from it while in the air, and then impressively lands back onto it again. Looks awesome! (although while on the ground, he sat at the back end of the Skateboard, rather than on the middle)

Last edited by Sheep (Aug 16, 2015 3:34 PM)

Offline

#22 Aug 16, 2015 9:57 PM

aleksusklim
Member
From: Uzbekistan
Registered: Aug 03, 2015
Posts: 12
Gems: 0
Birthday: 30 May
Age: 26 years old
Gender: Male
Website

Re: Collision data hacking

Today I will explain some of MIPS-assembler language for PlayStation. The main used program is "ps2dis": http://klimaleksus.narod.ru/Files/1/ps2dis.rar

Earlier I tried to make my own (dis-)assembler, but that was rather hard work, and I didn’t finish yet. So, for making SpyroFreezeCheat (and for exploring the collision function) I used old-style original ps2dis and WinHex. And also, ePSXeDebug: http://klimaleksus.narod.ru/Files/3/ePSXeDebug.rar
About ePSXeDebug I got yet more big plans: to create run-time tracer, but it’s also still unfinished. Well, let’s use what we got…

OK, best thing is dump the entire 2Mb game memory at some point (in a level; hit F11 in ePSXeDebug, make savestate and "dump") and load "\dump\ram.bin " to ps2dis as-is. Then "Invoke Analyzer". Also, load dump to WinHex.
This will allow you to find something and immediately test it in the game. For example:

1) If you found some constant value that you want try to change, then just jump in WinHex to it (use the same absolute address just without 0x800…) and change. Then save file, go to ePSXeDebug (always pause the game by pressing F11, not Esc! Release by "Run"), do "load state" and "load" (load dumped memory file). And then "run" and check result. If emulator crashes, you can just load state (unmodified) again. To refresh file in WinHex – dump memory again (ps2dis will be unmodified, since it will not reload your file automatically). If you want your changes to be permanent, just do: "load state", "load", "save state". Watch for one thing: never let the game run between loading state and loading dumped memory (always load state right before loading the modified dump; If you need to change place of dumping – make a new savestate there and work with it).

2) If you found some function than you wont to try to disable – go to it’s address in WinHex, and replace first 8 bytes with "jr ra;nop" = "08-00-E0-03 = 00-00-00-00" (=65011720 dec = 0x3E00008). This will work with 99% of standard functions (that use default stack and calling conversion). So, modifying it by above scheme will change game behavior. If you disabled minor function (like one shows pause menu), you easily will notice something different. But if you messed up a global universal function (f.e. a helper procedure that copies 12 bytes from one pointer to another – it used with objects coordinates) – the game will crash or hang, because you broke a flow of many other functions that use it. In ps2dis mark the beginning of a function with "space" key and explore every place where it is called by F3. If there’s a lot of callers – better don’t disable this function. On the other hand, if caller is only one – you might try to disable this caller (parent function) instead of a child to get more significant result.

3) Sometimes it’s very useful to disable just one function call. If it was (in most cases) a normal call (with ra-return), then simply zerofill this "jal" instruction (go to its address in WinHex and put zeroes in 4 bytes). This will change code so that arguments are prepared for a call, but the target function will not run in this place. This is extremely helpful when target function is universal, and it’s not possible to disable it globally.

4) If you want to know, does the game execute a particular piece of code, just write its absolute address in "Set Breakpoints – PC" field of ePSXeDebug. Also "MemRead" and "MemWrite" could be very useful.

5) When you get the interested function and want to know, who is call it – in which place was the call to it – set breakpoint to end of function (jr ra)  and press "step" (instead of "run") several time. Then check current instruction address and type it to ps2dis to explore caller.

6) If you need to change a piece of code – do it in ps2dis. It’s not very handy (you can’t easily copy of move several lines to another place), but still. Then save your work! Then place cursor and mark between your changes and do "save binary" command from menu. (In some cases ps2dis can hang and crash – make sure you don’t have "byte" or other non-code line types around – hold "U"; also press "W" when you need to interpret a value as a number and not a code). Then load new file in WinHex and then replace by it a part of original dump at correct position (press Ctrl+B to write-replace).

Most of functions have one beginning and one end. After the end is a beginning of another function, and before the beginning is also the end of other. Several functions can have two beginnings (with different arguments set) or more then one end: http://klimaleksus.narod.ru/Files/H/fc_1.png

OK, let's learn some MIPS ^^

Almost every command use registers. There are a lot of them: "zero" always = 0; "sp" = stack pointer (in big functions at the beginning you can see a lot of "sw" to sp, and at ending – same amount of "lw" to sp); "ra" keeps return address after any function call. "a0", "a1", "a2", "a3" – function arguments, "v0", "v1" – return values; "t0", "t1", … , "t7" – temporary registers, "at" – just another temporary. "s0", "s1", … , "s7" and "t8", "t9" – saved registers. After every normal function call the contents of at, a(0-3), v(0-1) and t(0-7) may be destroyed (filled with some numbers in temporary calculation), but also your (current) function can use them without any harm to other functions. But after any (standard) call their contents are undefined, and current function shouldn’t rely on it. Registers s(0-7), t(8-9) and "k0", "k1" (for kernel usage), "gp", "fp" (global and frame pointers) + sp are saved in the stack (or anywhere else) and will remain after every function call. But this also means that current function (in case it needs them) also should store their previous value and restore it before returning to the caller. Also, a function should store "ra" before call any other function, to allow returning to caller (load ra before exit and jump to ra).

A command often takes three registers: first is destination and others are sources. Sometimes the third argument is a number (16-bit). Some of command use one register and a number, or even nothing.
To store a constant in a register, used next command: "addiu at, zero, $2" – makes "at=2". If a number is an address (32-bit), then in can be stored to a register with two commands:
"lui at, $8001" + " addiu at, at, $2345" equals to "at = 0x80012345". Numbers always signed, and "addiu at, zero, $ffff" makes "at = -1". (The "lui" command is just storing and shifting left to 16 bits)

To copy one register to another, used "addu v1, zero, v0" – makes "v1 = v0". It is the same "add" command to adding numbers. The letter "i" states that we’re working with constant last argument, and not with a register ("addiu t0, a0, $4" – "t0 = a0 + 4"; "addu t1, a1, s1" – "t1 = a1 + s1"). The letter "u" at the end makes sure than no errors will be encountered if an overflow occurs. Also if second argument is "zero" register, such "add" command often called "li" or "mov".

There is "subu" command to substract one register from another ("subu t2, t1, t0" – "t2 = t1 – t0"), but no "i" – (constant) equivalent because we can subtract a constant by "addiu" command with negative integer. Also when loading addresses, if lower 16-bit is a negative integer (0x8001b2f4: "0xb2f4" = -19724) then preceding "lui" command should be stated with +1 to high part: "lui a3, $8002; addiu a3, a3, $b2f4" – "a3 = 0x8001b2f4".

There is "and" (andi), "or" (ori), "xor" (xori) commands – bitwise operations.
Also bitwise shifting (<< and >>): "sll", "srl", "sra" (right shift by filling left bits with original sign bit). They take an integer 0-31 ("sll at, at, 8" – "at = at << 8"). If bit-shift amount is variable (register), use "sllv", "srlv" and "srlav" commands ("srlv a0, a1 ,a2" – "a0 = a1 >> a2").

To load a value from the memory, there are "lw", "lh" (lhu) and "lb" (lbu) commands. Syntax: "lw at, $4(sp)" – "at = load4(sp + 4)" (or "at=sp[4]"). So we must provide a constant (signed) offset from a pointer (register). To load just from pointer, use "lw a0, $0(a1)" – "a0 = load4 (a1 + 0)". The "lw" command load four bytes and target address must be 4-bytes aligned. Use "lh" (or "lhu" if unsigned) to load two bytes (from 2-bytes aligned address) or "lb" (lbu) to load one byte from any address ("lb v0, $ffff(v1)" – "v0 = load1(v1 - 1)").
To save a value to memory there is the similar way: "sw", "sh", "sb" ("sh ra, $0(sp)" – "save2(sp + 0) = ra" or "sp[0]=ra"). Notice that loading a value from memory requires two steps, and the contents of target register is undefined right after the command, so you can’t write something like "lw a0,$4(a1); addiu v0,v0,a0" because "a0" isn’t really loaded yet. You can just put empty "nop" command after "lw" or make something better – for example load another value to future calculations while waiting one step to load this. Such behavior is present in a real PlayStations but not in some emulators (Epsxe!), so if you forget about this rule ("don’t use a value right after loading it from memory") then your code may work in Epsxe but will fail on PlayStation. Be careful!

To test an expression and branch execution ("if" statement) there are a lot of "b*" instructions. Here they are: "beq a1,a2, $..." = branch if equal = "if(a1==a2)goto …"; "bne a1,a2,$..." = branch if not equal = "if(a1!=a2)goto …"; "bgtz a0,$..." = branch if greater than zero = "if(a0>0)goto …"; "bgez a0,$..." = branch if greater or equal to zero = "if(a0>=0)goto …"; "bltz (blez) a0,$..." = branch if less than (or equal to) zero = "if(a0<(=)0)goto …"; to test for zero (non)equality, could be used "beq (bne) a0,zero,$..." = "if(a0==0)goto … / if(a0!=0)goto …". There is no direct way to test something like a>b and r>I (where "i" is a constant), and before you should either subtract one register from another and compare result with zero; or use "slt at,v1,v2" (sltu, slti, sltiu – for unsigned or constant versions) that will result to 1 if v1 if less than v2 or 0 otherwise ("at=(v1<v2)").

To jump from current position to somewhere else, used "j $..." command. But there is one big difference between "b*" and "j" commands: jump value in "j" is always an absolute address (though shifted) of target instructions, while value if branching test is just a number of instructions to skip (down or up) relative to current. In ps2dis you can even not notice it (there you prompted to enter the absolute address in branching just like in jumps), but when you move a piece of code from one location to another (directly in WinHex, for example), then after it you will see that all of branching are moved too (so they aren’t point to the same lines), but all jumping is still has old addresses. This means that for relative jumps – for example to four lines forward, or a loop and jump back – you should use "beq zero,zero, $..." instead of "j $..." because when you will (and you will!) move your code to different location, all of relative addresses will remain correct. Use jump only to jump to really constant address (for example you can hack one of game functions and add a jump to your code located somewhere, and then jump back). But Sony’s compiler didn’t follow this rule, and in game there are a lot of near jumps in code.

To jump to address that contained in a register, use "jr v0" command (goto v0). Special version is "jr ra". Well… To call a function you should write "jal $..." – this will make a jump to $... and also store return address (+8 to current instruction address) into "ra" register. Then to get back and return from a function, you should write "jr ra" (and save old ra in case you will call other fucntions).

There is one big rule related to jumps, calls and branching. A line just after any jump is executer ALWAYS and before the actual jump (but after the value comparing). It means that to make a save jump (and any "if" b* conditions) you should put "nop" command to next line. Would be wise if there would be some other calculations to not waste space. But remember (and ps2dis will always highlight that) – next instruction is executed always. You can see how this is used rationally: http://klimaleksus.narod.ru/Files/H/fc_2.png
(last operation in highlighted function is placed after "jr ra" because it will be executed anyway). In most cases you can imagine that next line is really written before condition/goto, but there could be some crazy assembler tricks where you need to know how exactly it’s working in order to understand the program logic.

I think that’s all main rules and operations, let’s go look at my FreezeCheat function!

We have two target addressed of functions (or pointers to something) – main object movement and dust animation. They are located at constant addresses somewhere in memory. If we zerofill them (for Spyro1 – replace with our pointers) – then cheat is active and world is frozen. The problem is that the content of theses pointers is changed from level to level. For every level there is it’s own value. I want that during cheat is active, it should monitor the current value and if it changes – (when new level loaded) function should immediately change it to our, but also store original somewhere, to restore it at wish.

So we have two addresses of target functions (actually, to variables where are located pointers to functions, different from level to level) and two extra variables for each of them, to store there original value and current value (helper to make simple flip-flop when button is pressed). Logic is this: get current value; check is it equal to saved value: if not, then save current (and a copy to helper variable) and change target to our (zero). Check with the same way the second value. Then take helper variable and swap it with target if key is pressed; do the same with second pointer. So, active cheat will zerofill a new value but saves a two copy of it; if no key is pressed then in next iteration there would be same behavior, since target is zero (but this won’t really change anything). When key is pressed, the helper copy will be swapped with target, and target will be again a valid pointer. But first part of the logic will not touch it because now it is equal to saved copy. If key is still pressed then helper (zero) will be again swapped with target, restoring the frozen state. If target value will suddenly change – then first part of logic will zerofill it and again save two copies. This ensures that active cheat will always freeze a level right after entering in any level.

But how to check keys? I found out that PlayStation libraries require a pointer, to which keys data will be copies after every vSync. So, this memory is updated automatically and I don’t need to call anything – my task is just to find this address, where keys are stores. For some reason, in Spyro there are a lot of such places with equal values (may be some standard code for multiple controllers, or different parts of the game are reading their own copies), I took one of them. It’s 2-byte value, every bit is responsible for one button on controller. So if I want to track a button then I need to bitwise-and current value with this key-code, or with sum or several key-codes to check, is at least one key from my set is pressed.

Last question is: how make the game to execute my code every frame? I found so-called "main loop" in every version of Spyro – it’s just infinite loop with a couple of functions that executed every frame. Here it is: http://klimaleksus.narod.ru/Files/H/fc_3.png
It’s a jump with two calls. After it there is function ending – closing stack frame and jr-ra. But it will be newer executed because loop in infinite! We can make sure it is, because here is the parent caller: http://klimaleksus.narod.ru/Files/H/fc_4.png
See a "break"? Execution should never encounter it, so there would be no return from child. Which means that I can brutally extend a main loop with one more jump, by rewriting following dead code: http://klimaleksus.narod.ru/Files/H/fc_5.png
Jump command is really unchanged (absolute address), it is just shifted 8 byte forward. My call to 0x8000b200 is inserted instead. At $b200 there are a lot of free space from .EXE header after Sony’s copyright message, we can write a lot of code there…
(My GameplayTimer was also located there, I planned to make FreezeCheat to be compatible with GameplayTimer, but I slightly messed up with addresses… Well, try it out without FreezeCheat anyway – currently only for GH, but there is a demo savestate in case you don’t have Greatest Hits. Also, it’s possible to port GameplayTimer to Spyro2 and might be to Spyro1 Japanese…).

Actually, I didn’t explain very first logic part of function – how to activate the cheat. And here it is: let’s store the "state" (initially = 0; active =1; temporary is -1 and -2 ). If not both buttons are pressed then: if state is temporary (-1 or -2) and if no button is pressed then state+=2 (-1 becomes 1 and -2 becomes 0 – this will make sure that to switch state the user needs to release both keys) and return with saving new state but if one of buttons is still pressed – return without changing a state. If state wasn’t temporary, then return if state is zero, and run function man logic if state = 1. In case both keys was pressed: is state is temporary – just exit; If state was zero then make state = -1 (which will changed it to 1 by code above when both buttons will be released) and exit; otherwise let state = -2 and we should clear everything – store zeroes at two pairs of saved copies of pointers and restore original values to target addresses, then exit.

To make function portable, I didn’t hardcode any addresses to commands. Instead, I point one register to the end of function (after jrra-nop) and store there all constants. The order is this: main pointer, dust pointer, keys pointer (where we can get controller data), 2-bytes key-code of first button and 2-bytes for second button; then "frozen" value for main and next for dust pointer (they are equal to zero for Spyro2/3, but points to jr-ra from THIS function – a few lines above – for Spyro1 to make them be pointers to empty functions). Then free space is used to store the state (4 byte, despite used only first), then empty 4 bytes (to align to next 16 bytes for ergonomic purposes), then – saved value of main, saved value of dust, then temporary main and temporary dust.

And here is the function! http://klimaleksus.narod.ru/Files/H/fc_6.png
Disassembly: ("#" will show any non-empty command that is executed before preceding jump)

Hidden text

v1 = 0x8001
v1 = v1 + 0xb308 // pointer to a_main
t4 = load4(v1 + 0) // = a_main
t5 = load4(v1 + 4) // = a_dust
at = load4(v1 + 8) // = a_keys
t2 = load2(v1 + 12) // = key_L
t3 = load2(v1 + 14) // = key_R
at = load2(at) // = V_KEYS
t0 = load4(v1 + 32) // = s_main
t1 = load4(v1 + 36) // = s_dust
a0 = load4(t4) // V_MAIN
a1 = load4(t5) // V_DUST
v0 = load4(v1 + 24) // state
t6 = load4(v1 + 16) // f_main
t7 = load4(v1 + 20) // f_dust

a2 = at & t2 // a2 = first button trace
if (a2 == 0) goto :not_both_keys
# a3 = at & t2 // a2 = second button trace
if (a3 != 0) goto :both_keys
nop

:not_both_keys
if (v0 >= 0) goto :label // to main_logic if state=1 and exit if state=0
# at = a2 + a3 // zero only if none keys pressed
if (at != 0) goto :just_exit // keep status negative while any key still pressed
# v0 = v0 + 2 // will take effect only when no keys are presses

:label
if (v0 <= 0) goto :save_and_exit
nop
goto :main_logic
nop

:both_keys
if (v0 < 0) goto :save_and_exit // also could just_exit instead
nop
if (v0 == 0) goto :save_and_exit // make status=-1 if it was 0
# v0 = -1

v0 = -2 // make status=-2 if it was 1
save4(v4) = t0 // V_MAIN =
save4(v5) = t1 // V_DUST =
save4(v1 + 32) = 0 // s_main =
save4(v1 + 36) = 0 // s_dust =
save4(v1 + 40) = 0 // t_main =
save4(v1 + 44) = 0 // t_dust =
goto :save_and_exit // after restoring everything
nop

:main_logic
if (t0 == a0) goto :check_second // skip if saved is equal to current
nop
if (t6 == a0) goto :check_second // also skip if equal to our
# save4(t4) = t6 // V_MAIN
// copy otherwise:
save4(v1 + 32) = a0 // s_main =
save4(v1 + 40) = a0 // t_main =

:check_second
if (t1 == a0) goto :do_first // same here
nop
if (t7 == a0) goto :do_first // and here
# save4(t4) = t7 // V_DUST
save4(v1 + 36) = a0 // s_dust =
save4(v1 + 44) = a0 // t_dust =

:do_first
t0 = load4(v1 + 40) // = t_main
t1 = load4(v1 + 44) // = t_dust
if (a2 == 0) goto :do_second // skip if first key isn’t pressed
nop
// swap:
save4(v1 + 40) = a0 // t_main =
save4(t4) = t0 // V_MAIN =

:do_second
if (a3 == 0) goto :save_and_exit // skip if second key is up
nop
// second swap:
save4(v1 + 44) = a1 // t_dust =
save4(t5) = t1 // V_DUST =

:save_and_exit
save4(v1 + 24) = v0 // state =
:just_exit
return // here also will point t6 and t7 in Spyro1
nop

:a_main
...

I’m done. In my patching mechanism, the function is kept only in one file. All settings are binary files with values of constants for specific version of game – It’s easy to replace them since they all are one place.
Also, anyone can easily replace target addresses with something else (maybe more interesting thing than just freezing) to force function to change different things. But implementing, for example, a third key for something – will require a total revision.
And finally, this function can be moved to any other place in memory just by changing first two lines and of course that jump from main game loop.

Sheep wrote:

Slowly lift, *bam* fall down, slowly lift, *bam* fall down...

I noticed that. Today I tried at Summer Forest to freeze, then hit red button, then stand right below the DOOR and then release time. The door fall down to dragon and… nothing (no damage again, but would be funny otherwise) – Spyro just stand inside a wall (and poor helpless camera tried to get there many times too; from the inside of a door – the door is invisible to both sided) and can go anywhere, even jump.

Sheep wrote:

Skateboarding

Which cheating engine do you use when you want to modify a value at run-time (maybe with hotkey)?
I use ArtMoney, and I have a table (with hotkeys) for Greatest Hits skatepark: http://klimaleksus.narod.ru/Files/2/M8.rar (ArtMoney included).
There are some values to change skateboard boost and Spyro speed…


~Kly_Men_COmpany

Offline

#23 Aug 19, 2015 5:24 PM

Sheep
Member
Award: Skateboard Contest Winner
From: Norway
Registered: Jan 24, 2008
Posts: 983
Gems: 0
Birthday: 20 January
Age: 25 years old
Gender: Male
Website

Re: Collision data hacking

aleksusklim wrote:
Sheep wrote:

Skateboarding

Which cheating engine do you use when you want to modify a value at run-time (maybe with hotkey)?
I use ArtMoney, and I have a table (with hotkeys) for Greatest Hits skatepark: http://klimaleksus.narod.ru/Files/2/M8.rar (ArtMoney included).
There are some values to change skateboard boost and Spyro speed…

I currently really only use PEC, with the 'search for or edit value' tool, Ctrl+h... I guess I'll try ArtMoney, then smile

Offline

#24 Aug 19, 2015 9:16 PM

aleksusklim
Member
From: Uzbekistan
Registered: Aug 03, 2015
Posts: 12
Gems: 0
Birthday: 30 May
Age: 26 years old
Gender: Male
Website

Re: Collision data hacking

Finally I got the full file format of first and second parts of collision data!
(Use my CollisionCracker to get clean first and second parts instead of full data)

Earlier I was thinking about to post all disassembly and explanations, but it seem to be clueless if we will learn this function "straight forward". All of calculations look nonsense while we do not understand the result – for what purpose they are.

So, more useful for now will be format specification.

OK, we already know about triangles! It’s a list.
A big list without (exact) size and any marks. There is no any rule or order – it’s just a list. But every triangle has an index! Integer position in this array.

A few words about coordinates and scale…
Every XYZ is integers, there no floating point values. But there are three (or more) levels of scale: object level, model level and cell level.
At object level we have very big numbers (4 bytes), and at cell level – very small (1 byte).

Currently we’d worked at model-level scale. So, one model unit (length) – it’s our one pixel in GML (right?). But Game Maker in 3D mode is independent from the scale, and we just decoded coordinates of each vertex and use it as is – integer value (2 bytes, really).

Then, object coordinates are in 16-times more precise than model, so 1 model unit is equal to 16 object units. For example, if you try to get Spyro’s position and draw it on your model, you should do X/16 (X>>4) with XYZ.

Now, what is a cell? All collideble world is divided by 3D grid. Every cell contains 256 model units. So, to get current cell coordinates based for example on a vertex, you should do X/256 (x>>8) with XYZ.

Thus, if you have Spyro’s position and want to know in which cell he currently is, do X/4096 (X>>12).

Now imagine this grid. Since world isn’t close to ZERO – first cells will be empty. Then, let’s take all of triangles that belongs to one cell (I think it will be every triangle which any vertex is inside this cell and every triangle that intercept sides/edges of the cell) and put their indexes (positions in our global triangle list) in one group. We will do this with every non-empty cell, and we will get a list of groups: one group for a cell; in a group could be several triangles (one or many). Every index is 2-byte integer with empty sign bit (0.. 32767), and we will write all of them to a file.

The order is not important, but let’s mark a beginning of every group with sign bit set, and also we must remember the index of beginning each group. So, what we got: an array where every negative number represents new cell definition, then – list of all indexes of triangles in this cell (plus first negative number without sign-bit).

Now we need to quickly get a cell group when we know cell’s coordinates. We must divide all world ground to layers. Imagine Z coordinate of a grid. First level of blocks likely will be empty (the ground is far enough from OXY plane). Also, we can get Z coordinate of top-most cell. We will make an array (2-byte integers) with special structure:

In first element we will put maximum Z coordinate of non-empty cell minus one (this is really small number, for example 4 or 8 – it’s a height of level divided by 4096 and rounded down). Then will be Z elements that represent layers. We will mark empty layers (some first ones) with number -1. Every other non-empty layer in our array will store an index of sub-layer.

Now, we’ll consider first sub-layer (2D) – lower layer with constant Z coordinate. Here Y will be fixed, so we need to get Y coordinate of right-most cell. Again, put it as first number, and its current index in array we will write to parent sub-layer element (from which we came). Then would be elements for sub-sublayer (1D), empty ones again mark with -1.

For now, the content of element is not known, since it will be a future index in THIS array. But we will write what we can – the rest of Z-layers. When it’s done, the beginning of array won’t be empty anymore, and we can deal with Y-sublayers. Finally, there would be only X-sub-sublayers where instead of indexes of position of next dimension we will put an index of current cell definition in our previous array!

Now we have a structure that will help us to quickly check only nearest triangles from the list. That’s how it works:

1) Get coordinates of current cell.
2) Use Z as first key and jump to it position in array – get next pointer or -1 (or >= first *max* element) if cell is empty;
3) Use Y as secondary key and advance forward to it to get next pointer or fail;
4) Use X as the last key and finally get pointer to group of triangles;
5) Go through it until negative number is found (but ignore the sing-bit of first one);
6) Since these are indexes of triangles in a big list – just get everyone and check!

With a few more optimizations (based on discarding triangles that obviously can’t be collided – f.e. plane flat ground with z1=z2=z3 when our zFrom and zTo both bigger or smaller) this is exactly how it works in the game.

Game’s function takes two points – previous and new position, and then must check nearest triangles for interception. Firstly it gets cell coordinates of beginning and end, then stores all cells that can be collideble – in most cases it’s only one cell (when both ends are in one cell), but worst case is when line goes though corner diagonally from top to bottom – so, eight cells must be checked to get precise results.

Each result then used with first (part 1) file with grid, where output is a pointer to second file (part 2) with list of triangle indexes. Every one is taken from third file (part 3) and after some discarding math – they checked with matrix calculation.

I can’t currently disassemble matrixes, because they are working with 2-coprocessor (cop2 commands), and I don’t know how to read it.
But while we didn’t want to make our own Spyro with exact physics as original one, it’s not very important how line-triangle interceptions are resolved internally. Just somehow.

I tried to visualize the grid, but seems it isn’t very beautiful entirely:
http://klimaleksus.narod.ru/Files/H/grid_01.jpg

More useful is a projection:
http://klimaleksus.narod.ru/Files/H/grid_02.jpg
(used only the first part – exactly what you couldn’t represent on a bitmap while taking into account only empty cells)

I think it’s better to have an option to display only triangles in a specified cell, like this:
http://klimaleksus.narod.ru/Files/H/grid_03.jpg

I also added to SWV special Spyro-mode, when camera is targeting a mark (that represents the dragon in run-time working) that can be controlled. I think I will make that we could run game in emulator and then watch in SWV which collision triangles are located in current cell. Not bad for surprises!

But main question is – how to resemble this grid and index list? Significant part is only triangles list. We should check every triangle for every cell and correctly put indexes in two list… Too boring work. But I need do this anyway (maybe later), since I want the ability to change collision model. Trying to change something in such grid-map is useless, because adding something means expanding and shifting everything else, and all indexes will become invalid. Much easier is just to change some triangles and rebuild grid and index list at one click!
But I know nothing about types (4 and 5 part) and animated collisions (doors/bridges). Maybe there are also somehow rely on triangles position in a list, so I need more knowledge…

I will give some source code (just test versions) :

Hidden text

This is how to extract all non-empty cells:

buffer=NAME+'.1';
buffer=file_bin_open(buffer,0);
var zz,yy,xx,Z,Y,X,FF,Lz,Ly,Lx,Lv,mm;
mm=256;
FF=$FFFF;
Lz=0;
Z=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
for(zz=1;zz<=Z;zz+=1){
file_bin_seek(buffer,Lz+zz*2);
Ly=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
if Ly=FF then continue;
file_bin_seek(buffer,Ly);
Y=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
for(yy=1;yy<=Y;yy+=1){
file_bin_seek(buffer,Ly+yy*2);
Lx=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
if Lx=FF then continue;
file_bin_seek(buffer,Lx);
X=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
for(xx=1;xx<=X;xx+=1){
file_bin_seek(buffer,Lx+xx*2);
Lv=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
if Lv=FF then continue;
d3d_model_block(myboxcollmod,(yy-1)*mm,(xx-1)*mm,(zz-1)*mm,yy*mm,xx*mm,zz*mm,1,1);
}}}
file_bin_close(buffer);

This is how to display all of triangles in current position:

var px,py,pz,Z,Y,X,FFFF,Lz,Ly,Lx,Lv,mm,trn,trnc,v;
mm=256;
myIcol=0;
px=(camera.y0 div mm)+1;
py=(camera.x0 div mm)+1;
pz=(camera.z0 div mm)+1;
d3d_model_clear(myboxcollmod);
d3d_model_clear(mycollmod);
buffer=NAME+'.1';
buffer=file_bin_open(buffer,0);
FFFF=$FFFF;
Lv=FFFF;
Lz=0;
Z=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
if pz<Z{
file_bin_seek(buffer,Lz+pz*2);
Ly=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
if Ly<>FFFF {
file_bin_seek(buffer,Ly);
Y=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
if py<Y{
file_bin_seek(buffer,Ly+py*2);
Lx=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
if Lx<>FFFF {
file_bin_seek(buffer,Lx);
X=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
if px<X{
file_bin_seek(buffer,Lx+px*2);
Lv=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
if Lv<>FFFF {
d3d_model_block(myboxcollmod,(py-1)*mm,(px-1)*mm,(pz-1)*mm,py*mm,px*mm,pz*mm,1,1);
}}}}}}
file_bin_close(buffer);

if Lv<>FFFF {
buffer=NAME+'.2';
buffer=file_bin_open(buffer,0);
trnc=0;
trn[trnc]=0;
file_bin_seek(buffer,Lv*2);
v=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
v&=$7fff;
trn[trnc]=v;
trnc+=1;
while(1){
v=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)<<8);
if v&$8000 break;
trn[trnc]=v;
trnc+=1;
}
file_bin_close(buffer);

buffer=NAME+'.3';
if not file_exists(buffer)exit;
buffer=file_bin_open(buffer,0);
d3d_model_primitive_begin(mycollmod,pr_trianglelist);

for(v=0;v<trnc;v+=1){
file_bin_seek(buffer,12*trn[v]);

// …
// <Triangle decoding>  (sample posted by me earlier)
// …

d3d_model_vertex_normal_color(mycollmod,vertY1,vertX1,vertZ1,normY,normX,normZ,col1,1);
d3d_model_vertex_normal_color(mycollmod,vertY2,vertX2,vertZ2,normY,normX,normZ,col2,1);
d3d_model_vertex_normal_color(mycollmod,vertY3,vertX3,vertZ3,normY,normX,normZ,col3,1);
}
d3d_model_primitive_end(mycollmod);
file_bin_close(buffer);
}

It’s not a very good idea to reopen file over and over – better is to read entire array at once:

s=0;
gridlists=0;
gridlist[gridlists]=0;
f=file_bin_open(name+'1',0);
while(file_bin_position(f)<file_bin_size(f)){
v=file_bin_read_byte(f)|(file_bin_read_byte(f)<<8);
if(v=$FFFF)v=-2;
gridlist[s]=v;
s+=1;
}
gridlists=s;
file_bin_close(f);

s=0;
linklists=0;
linklist[linklists]=0;
f=file_bin_open(name+'2',0);
while(file_bin_position(f)<file_bin_size(f)){
v=file_bin_read_byte(f)|(file_bin_read_byte(f)<<8);
if(v&$8000) linklist[s]=-((v&$7FFF)+1) else linklist[s]=v+1;
s+=1;
}
linklists=s;
file_bin_close(f);

Now everytime we can just:

var xx,yy,zz,v,vv,t,tc;

xx=(y div 256)+1;
yy=(x div 256)+1;
zz=(z div 256)+1;

v=0;
vv=gridlist[0];
if zz>vv break;
v=gridlist[v+zz]/2;
if v<0 break;
vv=gridlist[v];
if yy>vv break;
v=gridlist[v+yy]/2;
if v<0 break;
vv=gridlist[v];
if xx>vv break;
v=gridlist[v+xx];
if v<0 break;

t[0]=-linklist[v];
tc=1;
while(true){
v+=1;
vv=linklist[v];
if(vv<0)break;
t[tc]=vv-1;
tc+=1;
}

for(j=0;j<tc;j+=1){
i=t[j];

// We have each triangle index i
 
}

Maybe when I make the final version of this for SWV, I’ll post more clean and readable code.

Also notice that every index in first part – is an offset in this file. I mean, it isn’t an index of 2-byte element (divide by two to get the index).
But resulting (after Z-Y-X grid checking) index – is really the index of 2-byte element if second part. So if you need an offset instead – multiply by two.
And finally, all indexes of triangles – are indexes of 12-byte element in third file. So to get an offset, multiply by twelve.


~Kly_Men_COmpany

Offline

#25 Aug 20, 2015 3:40 AM

CrystalFissure
Member
From: Adelaide
Registered: May 16, 2015
Posts: 77
Gems: 0
Gender: Male
Website

Re: Collision data hacking

I love all this information. I hardly understand a lot of it, but the process is interesting and hopefully more people who know more about hacking are able to use this info. I also like the proposed plans to update the Spyro World Viewer.

By the way, I made a video of the Spyro Freeze Cheat!

Keep up the fantastic work, guys.

Offline

Board footer

Powered by FluxBB