<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="https://www.spyroforum.com/extern.php?action=feed&amp;tid=14783&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Spyro the Dragon Forums / Collision data hacking]]></title>
		<link>https://www.spyroforum.com/viewtopic.php?id=14783</link>
		<description><![CDATA[The most recent posts in Collision data hacking.]]></description>
		<lastBuildDate>Sat, 29 Aug 2015 08:49:19 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432429#p432429</link>
			<description><![CDATA[<p>Yet again, good job! <img src="https://www.spyroforum.com/img/smilies/big_smile.png" width="15" height="auto" alt="big_smile" /></p><p>Sorry for being inactive and not contributing in a while... I started school again two weeks ago and am generally too tired to do <em>anything</em> at the moment <img src="https://www.spyroforum.com/img/smilies/hmm.png" width="15" height="auto" alt="hmm" /></p>]]></description>
			<author><![CDATA[dummy@example.com (Sheep)]]></author>
			<pubDate>Sat, 29 Aug 2015 08:49:19 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432429#p432429</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432358#p432358</link>
			<description><![CDATA[<p>Good news! Now I know how collision-world animation is implied.</p><p>Every level can have some block of data, that represents animated parts of collision ground, for example bridges or portals (that appears only after collecting enough eggs). Every block consists of two (or more) frames that represent &quot;states&quot;. For example, a portal can be disabled underground, or be enabled at its proper place.</p><p>Earlier I found out that all animated parts in collision model – is a total mess! Only relative place of bunch of triangles is hardly represents the original object. Then I dumped the collision model from emulator memory and loaded it to viewer. All triangles were on their normal positions! That means that model in memory is changed at level loading time.</p><p>So I searched a function that changes them. I found two things: one is working after an action and changes one block (a portal right after talking to person with enough eggs); second changes everything at level-loading time. Bingo!</p><p>There was a function that goes through fourth (4,6,8,10) sub-subfile and &quot;jumps&quot; (sections with sizeof() as first integer) with changing something. Also seems that this thing cares about visible world model too, and after it – collision. Actually, collision is the last thing that is modified at this function. So I had to decompile and lean about hundred of assembler lines of code.</p><p>Wow, I forgot to explain some of MIPS when I explained it last time. There is a multiplication command &quot;mult a0,v0&quot;, which multiplicities two registers and stores the result to special &quot;LO&quot; register. There’s also &quot;HI&quot; register where will be upper part of 64-bit multiplication result (it like a pair of two 32-bit registers that works like a 64-bit register, and the product is stored there). Also division works with them, giving integer result and reminder.</p><p>But MIPS specification said that result will not be ready immediately, but need to take an undefined (platform-specific) amount of additional cycles (lines) to prepare. Looks like the game gives multiplication a three line to process operation. But those were small numbers and was used only LO-part, so maybe for big multipliers somewhere else would be more lines between actual multiplication and taking the product. (Again I think that in Epsxe &quot;mult&quot; will be done in 1 instruction just as &quot;lw&quot; that originally needs 2 instructions, but I never tested any my code with integer multiplication or division. And how to take sqrt() or sin() I don’t know either…)<br />There are commands to use LO-HI registers: to put and get any value – &quot;mflo at&quot; (&quot;at = LO&quot;); &quot;mtlo t1&quot; (&quot;LO = t1&quot;);&quot;mfhi ra&quot; (&quot;ra = HI&quot;);&quot;mthi s3&quot; (&quot;HI = s3&quot;);</p><p>So, OK. That function multiplies length of data in one frame-state to current frame-state index with &quot;mult&quot;. Also it uses LO register as a temporary storage for a value (I see this first time). But I don’t want to show disassembly this time, I just can say that for GH version this function is at 0x80021a94, but collision-related stuff located from 0x80021ce8 till 0x80021ef4. There are two main part of logic: the short and tricky reading of header, and then long but simple processing the triangles’ data.</p><p>The header of data is very uncommon; this function uses not all bytes of it (maybe others are for in-game changing of frame-states or even for interpolating vertexes). Main part is 12-triangles in more strange format that ours in collision list. Every triangle has to be parsed to X1/X2/X3/ Y1/Y2/Y3/Z1/Z2/Z3/T components and then combined again. Here is the format for Spyro3 (32-bits; not in-data bytes order, which is inverted)</p><p><strong>Cccc-cccc = cBbb-bbbb = bbTA-aaaa = aaaa-aaaa</strong></p><p>C = 3-vertex (relative), 9-bits<br />B = 2-vertex (relative), 9-bits<br />A = 1-vertex (absolute), 13-bits<br />T = water-type bit.</p><p>This format is very close to X and Y, but needs to be fixed for Z coordinate (where 2-3 vertexes are 8-bit). Water-type is shifted one bit to left, and then the most significant bit (15) of our types (that one bit left after water-type) is turned to =1. So, all resulting triangles will have that strange bit set. And neither will be &quot;water&quot; (at least, not portals. Maybe in Spyro2 at Aquaria Towers…), because T-bit = 0 (where I looked).</p><p>You know what? In any of z2 or z3 was negative (all 9 bits are used) – here comes the pain!! Everything is messed up with subtraction and addition… Well, I’ll show the code:</p><p>(also look at my article about implementing arithmetic right shift: <a href="http://stackoverflow.com/questions/32189509/" rel="nofollow">http://stackoverflow.com/questions/32189509/</a> )</p><div class="codebox"><pre class="vscroll"><code>uses Classes,SysUtils;

function srl(a,b:Integer):Integer;overload;
begin
Result := (a shr b) and ((1 shl (32-b))-1);
end;

function sra(a,b:Integer):Integer;overload;
begin
Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b));
end;

function srl(a,b:Cardinal):Cardinal;overload;
begin
Result := (a shr b) and ((1 shl (32-b))-1);
end;

function sra(a,b:Cardinal):Cardinal;overload;
begin
Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b));
end;

function sll(a,b:Cardinal):Cardinal;overload;
begin
Result := a shl b;
end;

function sll(a,b:Integer):Integer;overload;
begin
Result := a shl b;
end;

function bits(b:integer):integer;
begin
Result:=(1 shl b)-1;
end;

var
s1,s2:string;
stream,save:Tfilestream;
cnt,oldpos,start,next,word1,word2,word3,mul1,mul2,off1,off2,x,y,z,z1,z2,z3,x1,x2,x3,y1,y2,y3,h:integer;

procedure jump(cnt:integer);
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&gt;size then Abort;
end;
end;

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


begin
s1:=&#039;4_sub-subfile_of_Spyro&#039;;
s2:=&#039;collision_third_part.3&#039;;
stream:=TFileStream.Create(s1,fmOpenRead or fmShareDenyNone);
save:=TFileStream.Create(s2,fmOpenReadWrite or fmShareDenyNone);
stream.Seek(0,soFromBeginning); // Assume we are at fourth sub-subfile

move(48); // for Spyro3
jump(5);
{
move(44); // for Spyro2 instead
jump(4);

move(136); // for Spyro1 instead
jump(4);
}

move(4);
start:=stream.Position;
stream.ReadBuffer(cnt,4);
while(cnt&gt;0)do begin
Dec(cnt);
stream.ReadBuffer(next,4);
oldpos:=stream.Position;
stream.Seek(start+next,soFromBeginning);
stream.ReadBuffer(word1,4);
stream.ReadBuffer(word2,4);
stream.ReadBuffer(word3,4);
mul1:=srl(word1,16)and bits(8);
off1:=word2 and bits(16);
off2:=srl(word2,16);
stream.Seek(mul1*8+5,soFromCurrent);
stream.ReadBuffer(mul2,1);
// mul2:=0; // uncomment this line to use the first frame-state
stream.Seek(start+next+word3+off1*12*mul2,soFromBeginning);
save.Seek(off2*12,soFromBeginning);
while(off1&gt;0)do begin
Dec(off1);
stream.ReadBuffer(x,4);
stream.ReadBuffer(y,4);
stream.ReadBuffer(z,4);
h:=sll((z and (1 shl 13)),1) or (1 shl 15);
z3:=sra(z,23);
z2:=sra(sll(z,9),23);
z1:=z and bits(13);
if(z2&gt;=0)and(z3&gt;=0)then begin
z2:=sll(z2,16);
z3:=sll(z3,24);
z:=z1 or z2 or z3 or h;
end else begin
x3:=sra(x,23);
x2:=sra(sll(x,9),23);
x1:=x and bits(14);
y3:=sra(y,23);
y2:=sra(sll(y,9),23);
y1:=y and bits(14);
if z3&gt;=z2 then begin
Inc(x1,x2);
x:=sll(-x2,23);
x2:=sll((x3-x2)and bits(9),14);
x3:=x;
x:=x1 or x2 or x3;
y:=sll(-y2,23);
Inc(y1,y2);
y2:=sll((y3-y2)and bits(9),14);
y3:=y;
y:=y1 or y2 or y3;
Inc(z1,z2);
z:=sll(-z2,24);
z2:=sll((z3-z2)and bits(9),16);
z3:=z;
z:=z1 or z2 or z3 or h;
end else begin
Inc(x1,x3);
x:=sll(x2-x3,23);
x2:=sll((-x3)and bits(9),14);
x3:=x;
x:=x1 or x2 or x3;
Inc(y1,y3);
y:=sll(y2-y3,23);
y2:=sll((-y3)and bits(9),14);
y3:=y;
y:=y1 or y2 or y3;
Inc(z1,z3);
z:=sll(z2-z3,24);
z2:=sll((-z3)and bits(9),16);
z3:=z;
z:=z1 or z2 or z3 or h;
end;
end;
save.WriteBuffer(x,4);
save.WriteBuffer(y,4);
save.WriteBuffer(z,4);
end;
stream.Seek(oldpos,soFromBeginning);
end;
stream.Free;
save.Free;
end.</code></pre></div><p>Assume we decoded x1,x2,x3;y1,y2,y3;z1,z2,z3 as signed integers from animated data. Then if both z1&gt;=0 and z2&gt;=0 we can just use it and encode as triangle for a list.</p><p>If not, then we check, z2&lt;z3? If so, then: Nc1=c1+c2; Nc2=c3-c2; Nc3=-c2; if not then: Nc1=c1+c3; Nc2=-c3; Nc3=c2-c3. Where &quot;c&quot; is x,y,z – I mean we must do this with every coordinate. So, Nc1, Nc2, Nc3 now are proper values for X,Y or Z, and we should encode them to a triangle.</p><p>It’s not so hard, but (especially in game assembler) rather long. Also I don’t know, for what all of it? Maybe in animation there are &quot;normalized&quot; values for proper interpolation between frames, but in final collision model there must be &quot;optimized&quot; vertexes, where z1&lt;=z3&lt;=z2 for quick discarding far triangles from collision calculations.</p><p>Now about the header…<br />First integer = P = number of parts here. Then there are P integers = pointers to every part relative to beginning of data (from P, not from preceding &quot;jump&quot; value). Now for every part:</p><p>Third byte = S = show a number of 8-byte blocks that will follow below later. 2-byte values at 4-5 = N and 6-7 = F bytes (half-words of second word) = number of triangles in a frame; and triangle index in list from where this data must be written.</p><p>Then, if we’ll seek to &quot;12+S*8+5&quot; from beginning of this part, we somehow will get special byte that represents &quot;number of frames - 1&quot; = M. Usually it = 1 (two states, for portals).</p><p>Integer at 8-11 bytes (third word) = L= is a relative (to this part) pointer to actual data. So to get it, we should seek in this part to &quot;L+N*12*K&quot;, where K must be 0&lt;=K&lt;=M. I plan to use only K=0 and K=M to get last and initial frame-states.</p><p>Offset of writing triangles in list (third part of collision data) is just &quot;F*12&quot;.</p><p>At run-time pointers to parts became absolute:<br /><a href="http://klimaleksus.narod.ru/Files/H/live.png" rel="nofollow">http://klimaleksus.narod.ru/Files/H/live.png</a></p><p>Well, looks like I need to make a new version of CollisionCracker!</p><p>Here’s some screens with different states:<br /><a href="http://klimaleksus.narod.ru/Files/H/live_0.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/live_0.jpg</a><br /><a href="http://klimaleksus.narod.ru/Files/H/live_1.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/live_1.jpg</a><br /><a href="http://klimaleksus.narod.ru/Files/H/live_2.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/live_2.jpg</a><br /><a href="http://klimaleksus.narod.ru/Files/H/live_3.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/live_3.jpg</a><br /><a href="http://klimaleksus.narod.ru/Files/H/live_4.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/live_4.jpg</a><br /><a href="http://klimaleksus.narod.ru/Files/H/live_5.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/live_5.jpg</a><br /><a href="http://klimaleksus.narod.ru/Files/H/live_6.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/live_6.jpg</a></p><p>I think I got why in a list there’s such mess and not for example initial state instead. For grid! Since triangles are moving and optimization grid is not – every triangle should be included in every cell where it could possibly be. Simplest way is to make a mask – the very long triangle that just overlaps everything on its way – and then calculate grid with masks.</p><p>I suppose that grid creating was an automated process, so these masks must be kept to the very end, and there is no reason why developers would delete them: space must be preserved, and every state will be controlled by the logic at level loading. They could just zerofill masks, but for some reason they decided to keep them as unused data, because they were used long lime ago, at game-compilation time.</p>]]></description>
			<author><![CDATA[dummy@example.com (aleksusklim)]]></author>
			<pubDate>Tue, 25 Aug 2015 05:38:39 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432358#p432358</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432333#p432333</link>
			<description><![CDATA[<div class="quotebox"><cite>aleksusklim wrote:</cite><blockquote><div><p>But I wonder why you didn’t show what happens if a level would be loaded with active cheat (when all gems are red and Sparx is black).</p></div></blockquote></div><p>I didn&#039;t realise you could do that! So do you pause everything while in a loading screen, then load the level? I&#039;ll re-read the Readme to see if you mention it, and then I&#039;ll make a video. Keep up the great work!<br />Also, someone pointed out that this freeze cheat has been done before, years ago. I actually commented on it:<br /><iframe width="480" height="390" src="http://www.youtube.com/embed/IsuJ14Lsq9U?rel=0" frameborder="0"></iframe></p><p>But I&#039;m pretty sure yours is better, as you can manually choose to unfreeze particles, and also slow down movements. Not sure whether this freeze hack can do the same thing with the superflame!</p>]]></description>
			<author><![CDATA[dummy@example.com (CrystalFissure)]]></author>
			<pubDate>Sun, 23 Aug 2015 06:13:15 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432333#p432333</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432321#p432321</link>
			<description><![CDATA[<div class="quotebox"><cite>aleksusklim wrote:</cite><blockquote><div><p>So, my theory is that first N triangles are typed triangles, where N is the size of 4-part.</p></div></blockquote></div><p>Yep, this has seemed quite clear to me as well for a while <img src="https://www.spyroforum.com/img/smilies/smile.png" width="15" height="auto" alt="smile" /> All special triangles(like ramps, lava etc.), and also all the completely normal triangles that are listed before or between special triangles, are given a value. Are you able to tell how the &#039;normal terrain&#039; type value is used in the game code?</p><div class="quotebox"><cite>aleksusklim wrote:</cite><blockquote><div><p>New questions: how types are working? Seem that if it’s a small number – it is an offset to something; if big – maybe bit-mask. But I’m not sure. F.e. 01 was blue ramp, 02 orange ramp, 03 = crash, 04 = working. Looks like bit-mask (floor cant be blue+orange=1+2=3), but values like FF or 3F are working too. Also, type &quot;3F&quot; is hard-coded in game (and then number is replaced with FF, or shifted left by 2 bits if was not equal to 3F – this is in one of functions that reads this stored earlier value from memory, and not directly from triangle).</p></div></blockquote></div><p>All I know is that a value does not necessarily mean the same thing in different levels. And for portals, there are two different ranges of values that both work as portal values, leading to the same levels. So could be they either just lead through the portal from two different sides, or maybe some of the bits are &quot;ignored&quot;.</p><p>Also, if you&#039;re able to edit the game&#039;s files and <em>then</em> run it, does anything appear different if you change the value of the water-bit&#039;s twin? Since nothing seemed to change when I edited it at run time, I suspect they are used only when the level loads.</p>]]></description>
			<author><![CDATA[dummy@example.com (Sheep)]]></author>
			<pubDate>Sat, 22 Aug 2015 11:09:23 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432321#p432321</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432288#p432288</link>
			<description><![CDATA[<p>I played with ground types in Sunny Villa’s skatepark. Types are stored in fourth part.<br />I found out thah some of numbers are really responsible for orange/blue ramp, so filling all the values with the same number can give me that everything physically turns into blue ramp (then every my jump from a corner would pull me straight up) or orange ramp (so I hen jump really far); but I couldn’t jump by X key where I want – only from corners.</p><p>Other values mean nothing special (no ramps at all), some values cause emulator crash.<br />I looked at disassembly – main collision function just read one of the values (likely one that belongs to current triangle where collision occurs) but simply stores it somewhere in the memory. It used by other functions…</p><p>Then I found a function that uses this value as an offset to pointer, where stored other numbers…</p><p>But one question is bothered me all the time: if every triangle seems to have a type, then why there’s less element in types array that triangles in a list?</p><p>In the beginning of collision data, there are number of triangles in 3-part, number of bytes in 4-part and number of bytes in 5-part (almost everywhere in game binary code 4-part and 5-part are somehow used together). For Sunny Villa Skatepark it’s: 6030 triangles, 5474 types and 5340 units in 5-part.</p><p>Why there are more triangles then types!?</p><p>Then I got a crazy idea: what if in our big list of triangles, there are &quot;essential&quot; ones located before; and then – &quot;simple&quot; triangles with no specific type? So, I loaded full model in (still beta…)&#160; SWV and also compared it with model only with 5474-triangles:</p><p><a href="http://klimaleksus.narod.ru/Files/H/grid_04.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/grid_04.jpg</a><br /><a href="http://klimaleksus.narod.ru/Files/H/grid_05.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/grid_05.jpg</a></p><p>It DOES make sense! There are invisible walls that restrict skatepark arena (to disallow player from falling off the level). And every other triangle has a type, but these restrictions are not! Really, they don’t need a type.</p><p>Also, they are more often have that strange type-bit (in triangle, that is a pair to water-bit, yellow on my screens).</p><p>So, my theory is that first N triangles are typed triangles, where N is the size of 4-part.</p><p>New questions: how types are working? Seem that if it’s a small number – it is an offset to something; if big – maybe bit-mask. But I’m not sure. F.e. 01 was blue ramp, 02 orange ramp, 03 = crash, 04 = working. Looks like bit-mask (floor cant be blue+orange=1+2=3), but values like FF or 3F are working too. Also, type &quot;3F&quot; is hard-coded in game (and then number is replaced with FF, or shifted left by 2 bits if was not equal to 3F – this is in one of functions that reads this stored earlier value from memory, and not directly from triangle).</p><div class="quotebox"><cite>CrystalFissure wrote:</cite><blockquote><div><p>By the way, I made a video of the Spyro Freeze Cheat!</p></div></blockquote></div><p>Thanks a lot!<br />But I wonder why you didn’t show what happens if a level would be loaded with active cheat (when all gems are red and Sparx is black).</p><p>Also, mirror my download link (re-upload my file to any different place) if you can – some people sometimes tell that they for some reason couldn’t download anything from my site.</p><div class="quotebox"><cite>CaptainBee wrote:</cite><blockquote><div><p>im stuck at the ramtop.bat part, it says &quot;ram not found&quot;</p></div></blockquote></div><p>Are you sure that you did everything correct? What kind of RAMTOP did you use: EXE/PSX/BIN/ISO ?</p>]]></description>
			<author><![CDATA[dummy@example.com (aleksusklim)]]></author>
			<pubDate>Thu, 20 Aug 2015 21:25:50 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432288#p432288</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432276#p432276</link>
			<description><![CDATA[<p>Try the new ePSXe. But it should work with the older versions, not sure why it doesn&#039;t.</p>]]></description>
			<author><![CDATA[dummy@example.com (CrystalFissure)]]></author>
			<pubDate>Thu, 20 Aug 2015 05:09:36 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432276#p432276</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432275#p432275</link>
			<description><![CDATA[<p>does this work with epsxe 180? i downloaded version 160 to try and i still cant get this to work. epsxe 160 just crashes and epsxe 180 just flashes blue and green. im stuck at the ramtop.bat part, it says &quot;ram not found&quot;</p>]]></description>
			<author><![CDATA[dummy@example.com (CaptainBee)]]></author>
			<pubDate>Thu, 20 Aug 2015 04:18:41 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432275#p432275</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432274#p432274</link>
			<description><![CDATA[<p>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.</p><p>By the way, I made a video of the Spyro Freeze Cheat!<br /><iframe width="480" height="390" src="http://www.youtube.com/embed/wFwjaDkH-OY?rel=0" frameborder="0"></iframe></p><p>Keep up the fantastic work, guys.</p>]]></description>
			<author><![CDATA[dummy@example.com (CrystalFissure)]]></author>
			<pubDate>Thu, 20 Aug 2015 03:40:01 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432274#p432274</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432268#p432268</link>
			<description><![CDATA[<p>Finally I got the full file format of first and second parts of collision data!<br />(Use my <a href="http://klimaleksus.narod.ru/Files/3/CollisionCracker.rar" rel="nofollow"> CollisionCracker</a> to get clean first and second parts instead of full data)</p><p>Earlier I was thinking about to post all disassembly and explanations, but it seem to be clueless if we will learn this function &quot;straight forward&quot;. All of calculations look nonsense while we do not understand the result – for what purpose they are.</p><p>So, more useful for now will be format specification.</p><p>OK, we already know about triangles! It’s a list.<br />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.</p><p>A few words about coordinates and scale…<br />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.<br />At object level we have very big numbers (4 bytes), and at cell level – very small (1 byte).</p><p>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).</p><p>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&gt;&gt;4) with XYZ.</p><p>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&gt;&gt;8) with XYZ.</p><p>Thus, if you have Spyro’s position and want to know in which cell he currently is, do X/4096 (X&gt;&gt;12).</p><p>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.</p><p>The order is not important, but let’s mark a beginning of every group with sign bit set, and also we must remember the <em>index</em> 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).</p><p>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:</p><p>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.</p><p>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.</p><p>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!</p><p>Now we have a structure that will help us to quickly check only nearest triangles from the list. That’s how it works:</p><p>1) Get coordinates of current cell.<br />2) Use Z as first key and jump to it position in array – get next pointer or -1 (or &gt;= first *max* element) if cell is empty;<br />3) Use Y as secondary key and advance forward to it to get next pointer or fail;<br />4) Use X as the last key and finally get pointer to group of triangles;<br />5) Go through it until negative number is found (but ignore the sing-bit of first one);<br />6) Since these are indexes of triangles in a big list – just get everyone and check!</p><p>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.</p><p>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.</p><p>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.</p><p>I can’t currently disassemble matrixes, because they are working with 2-coprocessor (cop2 commands), and I don’t know how to read it.<br />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.</p><p>I tried to visualize the grid, but seems it isn’t very beautiful entirely:<br /><a href="http://klimaleksus.narod.ru/Files/H/grid_01.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/grid_01.jpg</a></p><p>More useful is a projection:<br /><a href="http://klimaleksus.narod.ru/Files/H/grid_02.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/grid_02.jpg</a><br />(used only the first part – exactly what you couldn’t represent on a bitmap while taking into account only empty cells)</p><p>I think it’s better to have an option to display only triangles in a specified cell, like this:<br /><a href="http://klimaleksus.narod.ru/Files/H/grid_03.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/H/grid_03.jpg</a></p><p>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!</p><p>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!<br />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…</p><p>I will give some source code (just test versions) :</p><div class="quotebox" style="padding: 0px;"><div onclick="var e,d,c=this.parentNode,a=c.getElementsByTagName('div')[1],b=this.getElementsByTagName('span')[0];if(a.style.display!=''){while(c.parentNode&&(!d||!e||d==e)){e=d;d=(window.getComputedStyle?getComputedStyle(c, null):c.currentStyle)['backgroundColor'];if(d=='transparent'||d=='rgba(0, 0, 0, 0)')d=e;c=c.parentNode;}a.style.display='';a.style.backgroundColor=d;b.innerHTML='&#9650;';}else{a.style.display='none';b.innerHTML='&#9660;';}" style="font-weight: bold; cursor: pointer; font-size: 0.9em;"><span style="padding: 0 5px;">&#9660;</span>Hidden text</div><div style="padding: 6px; margin: 0; display: none;"><p>This is how to extract all non-empty cells:</p><div class="codebox"><pre><code>buffer=NAME+&#039;.1&#039;;
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)&lt;&lt;8);
for(zz=1;zz&lt;=Z;zz+=1){
file_bin_seek(buffer,Lz+zz*2);
Ly=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
if Ly=FF then continue;
file_bin_seek(buffer,Ly);
Y=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
for(yy=1;yy&lt;=Y;yy+=1){
file_bin_seek(buffer,Ly+yy*2);
Lx=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
if Lx=FF then continue;
file_bin_seek(buffer,Lx);
X=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
for(xx=1;xx&lt;=X;xx+=1){
file_bin_seek(buffer,Lx+xx*2);
Lv=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;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);</code></pre></div><p>This is how to display all of triangles in current position:</p><div class="codebox"><pre class="vscroll"><code>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+&#039;.1&#039;;
buffer=file_bin_open(buffer,0);
FFFF=$FFFF;
Lv=FFFF;
Lz=0;
Z=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
if pz&lt;Z{
file_bin_seek(buffer,Lz+pz*2);
Ly=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
if Ly&lt;&gt;FFFF {
file_bin_seek(buffer,Ly);
Y=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
if py&lt;Y{
file_bin_seek(buffer,Ly+py*2);
Lx=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
if Lx&lt;&gt;FFFF {
file_bin_seek(buffer,Lx);
X=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
if px&lt;X{
file_bin_seek(buffer,Lx+px*2);
Lv=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
if Lv&lt;&gt;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&lt;&gt;FFFF {
buffer=NAME+&#039;.2&#039;;
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)&lt;&lt;8);
v&amp;=$7fff;
trn[trnc]=v;
trnc+=1;
while(1){
v=file_bin_read_byte(buffer)|(file_bin_read_byte(buffer)&lt;&lt;8);
if v&amp;$8000 break;
trn[trnc]=v;
trnc+=1;
}
file_bin_close(buffer);

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

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

// …
// &lt;Triangle decoding&gt;  (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);
}</code></pre></div><p>It’s not a very good idea to reopen file over and over – better is to read entire array at once:</p><div class="codebox"><pre><code>s=0;
gridlists=0;
gridlist[gridlists]=0;
f=file_bin_open(name+&#039;1&#039;,0);
while(file_bin_position(f)&lt;file_bin_size(f)){
v=file_bin_read_byte(f)|(file_bin_read_byte(f)&lt;&lt;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+&#039;2&#039;,0);
while(file_bin_position(f)&lt;file_bin_size(f)){
v=file_bin_read_byte(f)|(file_bin_read_byte(f)&lt;&lt;8);
if(v&amp;$8000) linklist[s]=-((v&amp;$7FFF)+1) else linklist[s]=v+1;
s+=1;
}
linklists=s;
file_bin_close(f);</code></pre></div><p>Now everytime we can just:</p><div class="codebox"><pre class="vscroll"><code>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&gt;vv break;
v=gridlist[v+zz]/2;
if v&lt;0 break;
vv=gridlist[v];
if yy&gt;vv break;
v=gridlist[v+yy]/2;
if v&lt;0 break;
vv=gridlist[v];
if xx&gt;vv break;
v=gridlist[v+xx];
if v&lt;0 break;

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

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

// We have each triangle index i
 
}</code></pre></div><p>Maybe when I make the final version of this for SWV, I’ll post more clean and readable code.</p></div></div><p>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).<br />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.<br />And finally, all indexes of triangles – are indexes of 12-byte element in third file. So to get an offset, multiply by twelve.</p>]]></description>
			<author><![CDATA[dummy@example.com (aleksusklim)]]></author>
			<pubDate>Wed, 19 Aug 2015 21:16:19 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432268#p432268</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432267#p432267</link>
			<description><![CDATA[<div class="quotebox"><cite>aleksusklim wrote:</cite><blockquote><div><div class="quotebox"><cite>Sheep wrote:</cite><blockquote><div><p>Skateboarding</p></div></blockquote></div><p>Which cheating engine do you use when you want to modify a value at run-time (maybe with hotkey)?<br />I use ArtMoney, and I have a table (with hotkeys) for Greatest Hits skatepark: <a href="http://klimaleksus.narod.ru/Files/2/M8.rar" rel="nofollow">http://klimaleksus.narod.ru/Files/2/M8.rar</a> (ArtMoney included).<br />There are some values to change skateboard boost and Spyro speed…</p></div></blockquote></div><p>I currently really only use PEC, with the &#039;search for or edit value&#039; tool, Ctrl+h... I guess I&#039;ll try ArtMoney, then <img src="https://www.spyroforum.com/img/smilies/smile.png" width="15" height="auto" alt="smile" /></p>]]></description>
			<author><![CDATA[dummy@example.com (Sheep)]]></author>
			<pubDate>Wed, 19 Aug 2015 17:24:44 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432267#p432267</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432214#p432214</link>
			<description><![CDATA[<p>Today I will explain some of MIPS-assembler language for PlayStation. The main used program is &quot;ps2dis&quot;: <a href="http://klimaleksus.narod.ru/Files/1/ps2dis.rar" rel="nofollow">http://klimaleksus.narod.ru/Files/1/ps2dis.rar</a></p><p>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: <a href="http://klimaleksus.narod.ru/Files/3/ePSXeDebug.rar" rel="nofollow">http://klimaleksus.narod.ru/Files/3/ePSXeDebug.rar</a><br />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…</p><p>OK, best thing is dump the entire 2Mb game memory at some point (in a level; hit F11 in ePSXeDebug, make savestate and &quot;dump&quot;) and load &quot;\dump\ram.bin &quot; to ps2dis as-is. Then &quot;Invoke Analyzer&quot;. Also, load dump to WinHex.<br />This will allow you to find something and immediately test it in the game. For example:</p><p>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 &quot;Run&quot;), do &quot;load state&quot; and &quot;load&quot; (load dumped memory file). And then &quot;run&quot; 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: &quot;load state&quot;, &quot;load&quot;, &quot;save state&quot;. 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).</p><p>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 &quot;jr ra;nop&quot; = &quot;08-00-E0-03 = 00-00-00-00&quot; (=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 &quot;space&quot; 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.</p><p>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 &quot;jal&quot; 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.</p><p>4) If you want to know, does the game execute a particular piece of code, just write its absolute address in &quot;Set Breakpoints – PC&quot; field of ePSXeDebug. Also &quot;MemRead&quot; and &quot;MemWrite&quot; could be very useful.</p><p>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)&#160; and press &quot;step&quot; (instead of &quot;run&quot;) several time. Then check current instruction address and type it to ps2dis to explore caller.</p><p>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 &quot;save binary&quot; command from menu. (In some cases ps2dis can hang and crash – make sure you don’t have &quot;byte&quot; or other non-code line types around – hold &quot;U&quot;; also press &quot;W&quot; 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).</p><p>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: <a href="http://klimaleksus.narod.ru/Files/H/fc_1.png" rel="nofollow">http://klimaleksus.narod.ru/Files/H/fc_1.png</a></p><p>OK, let&#039;s learn some MIPS ^^</p><p>Almost every command use registers. There are a lot of them: &quot;zero&quot; always = 0; &quot;sp&quot; = stack pointer (in big functions at the beginning you can see a lot of &quot;sw&quot; to sp, and at ending – same amount of &quot;lw&quot; to sp); &quot;ra&quot; keeps return address after any function call. &quot;a0&quot;, &quot;a1&quot;, &quot;a2&quot;, &quot;a3&quot; – function arguments, &quot;v0&quot;, &quot;v1&quot; – return values; &quot;t0&quot;, &quot;t1&quot;, … , &quot;t7&quot; – temporary registers, &quot;at&quot; – just another temporary. &quot;s0&quot;, &quot;s1&quot;, … , &quot;s7&quot; and &quot;t8&quot;, &quot;t9&quot; – 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 &quot;k0&quot;, &quot;k1&quot; (for kernel usage), &quot;gp&quot;, &quot;fp&quot; (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 &quot;ra&quot; before call any other function, to allow returning to caller (load ra before exit and jump to ra).</p><p>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.<br />To store a constant in a register, used next command: &quot;addiu at, zero, $2&quot; – makes &quot;at=2&quot;. If a number is an address (32-bit), then in can be stored to a register with two commands:<br />&quot;lui at, $8001&quot; + &quot; addiu at, at, $2345&quot; equals to &quot;at = 0x80012345&quot;. Numbers always signed, and &quot;addiu at, zero, $ffff&quot; makes &quot;at = -1&quot;. (The &quot;lui&quot; command is just storing and shifting left to 16 bits)</p><p>To copy one register to another, used &quot;addu v1, zero, v0&quot; – makes &quot;v1 = v0&quot;. It is the same &quot;add&quot; command to adding numbers. The letter &quot;i&quot; states that we’re working with constant last argument, and not with a register (&quot;addiu t0, a0, $4&quot; – &quot;t0 = a0 + 4&quot;; &quot;addu t1, a1, s1&quot; – &quot;t1 = a1 + s1&quot;). The letter &quot;u&quot; at the end makes sure than no errors will be encountered if an overflow occurs. Also if second argument is &quot;zero&quot; register, such &quot;add&quot; command often called &quot;li&quot; or &quot;mov&quot;.</p><p>There is &quot;subu&quot; command to substract one register from another (&quot;subu t2, t1, t0&quot; – &quot;t2 = t1 – t0&quot;), but no &quot;i&quot; – (constant) equivalent because we can subtract a constant by &quot;addiu&quot; command with negative integer. Also when loading addresses, if lower 16-bit is a negative integer (0x8001b2f4: &quot;0xb2f4&quot; = -19724) then preceding &quot;lui&quot; command should be stated with +1 to high part: &quot;lui a3, $8002; addiu a3, a3, $b2f4&quot; – &quot;a3 = 0x8001b2f4&quot;.</p><p>There is &quot;and&quot; (andi), &quot;or&quot; (ori), &quot;xor&quot; (xori) commands – bitwise operations.<br />Also bitwise shifting (&lt;&lt; and &gt;&gt;): &quot;sll&quot;, &quot;srl&quot;, &quot;sra&quot; (right shift by filling left bits with original sign bit). They take an integer 0-31 (&quot;sll at, at, 8&quot; – &quot;at = at &lt;&lt; 8&quot;). If bit-shift amount is variable (register), use &quot;sllv&quot;, &quot;srlv&quot; and &quot;srlav&quot; commands (&quot;srlv a0, a1 ,a2&quot; – &quot;a0 = a1 &gt;&gt; a2&quot;).</p><p>To load a value from the memory, there are &quot;lw&quot;, &quot;lh&quot; (lhu) and &quot;lb&quot; (lbu) commands. Syntax: &quot;lw at, $4(sp)&quot; – &quot;at = load4(sp + 4)&quot; (or &quot;at=sp[4]&quot;). So we must provide a constant (signed) offset from a pointer (register). To load just from pointer, use &quot;lw a0, $0(a1)&quot; – &quot;a0 = load4 (a1 + 0)&quot;. The &quot;lw&quot; command load four bytes and target address must be 4-bytes aligned. Use &quot;lh&quot; (or &quot;lhu&quot; if unsigned) to load two bytes (from 2-bytes aligned address) or &quot;lb&quot; (lbu) to load one byte from any address (&quot;lb v0, $ffff(v1)&quot; – &quot;v0 = load1(v1 - 1)&quot;).<br />To save a value to memory there is the similar way: &quot;sw&quot;, &quot;sh&quot;, &quot;sb&quot; (&quot;sh ra, $0(sp)&quot; – &quot;save2(sp + 0) = ra&quot; or &quot;sp[0]=ra&quot;). 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 &quot;lw a0,$4(a1); addiu v0,v0,a0&quot; because &quot;a0&quot; isn’t really loaded yet. You can just put empty &quot;nop&quot; command after &quot;lw&quot; 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 (&quot;don’t use a value right after loading it from memory&quot;) then your code may work in Epsxe but will fail on PlayStation. Be careful!</p><p>To test an expression and branch execution (&quot;if&quot; statement) there are a lot of &quot;b*&quot; instructions. Here they are: &quot;beq a1,a2, $...&quot; = branch if equal = &quot;if(a1==a2)goto …&quot;; &quot;bne a1,a2,$...&quot; = branch if not equal = &quot;if(a1!=a2)goto …&quot;; &quot;bgtz a0,$...&quot; = branch if greater than zero = &quot;if(a0&gt;0)goto …&quot;; &quot;bgez a0,$...&quot; = branch if greater or equal to zero = &quot;if(a0&gt;=0)goto …&quot;; &quot;bltz (blez) a0,$...&quot; = branch if less than (or equal to) zero = &quot;if(a0&lt;(=)0)goto …&quot;; to test for zero (non)equality, could be used &quot;beq (bne) a0,zero,$...&quot; = &quot;if(a0==0)goto … / if(a0!=0)goto …&quot;. There is no direct way to test something like a&gt;b and r&gt;I (where &quot;i&quot; is a constant), and before you should either subtract one register from another and compare result with zero; or use &quot;slt at,v1,v2&quot; (sltu, slti, sltiu – for unsigned or constant versions) that will result to 1 if v1 if less than v2 or 0 otherwise (&quot;at=(v1&lt;v2)&quot;).</p><p>To jump from current position to somewhere else, used &quot;j $...&quot; command. But there is one big difference between &quot;b*&quot; and &quot;j&quot; commands: jump value in &quot;j&quot; 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 &quot;beq zero,zero, $...&quot; instead of &quot;j $...&quot; 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.</p><p>To jump to address that contained in a register, use &quot;jr v0&quot; command (goto v0). Special version is &quot;jr ra&quot;. Well… To call a function you should write &quot;jal $...&quot; – this will make a jump to $... and also store return address (+8 to current instruction address) into &quot;ra&quot; register. Then to get back and return from a function, you should write &quot;jr ra&quot; (and save old ra in case you will call other fucntions).</p><p>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 &quot;if&quot; b* conditions) you should put &quot;nop&quot; 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: <a href="http://klimaleksus.narod.ru/Files/H/fc_2.png" rel="nofollow">http://klimaleksus.narod.ru/Files/H/fc_2.png</a><br />(last operation in highlighted function is placed after &quot;jr ra&quot; 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.</p><p>I think that’s all main rules and operations, let’s go look at my FreezeCheat function!</p><p>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.</p><p>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.</p><p>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.</p><p>Last question is: how make the game to execute my code every frame? I found so-called &quot;main loop&quot; in every version of Spyro – it’s just infinite loop with a couple of functions that executed every frame. Here it is: <a href="http://klimaleksus.narod.ru/Files/H/fc_3.png" rel="nofollow">http://klimaleksus.narod.ru/Files/H/fc_3.png</a><br />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: <a href="http://klimaleksus.narod.ru/Files/H/fc_4.png" rel="nofollow">http://klimaleksus.narod.ru/Files/H/fc_4.png</a><br />See a &quot;break&quot;? 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: <a href="http://klimaleksus.narod.ru/Files/H/fc_5.png" rel="nofollow">http://klimaleksus.narod.ru/Files/H/fc_5.png</a><br />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…<br />(My <a href="http://klimaleksus.narod.ru/Files/3/GameplayTimer.rar" rel="nofollow">GameplayTimer</a> 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 <em>might be</em> to Spyro1 Japanese…).</p><p>Actually, I didn’t explain very first logic part of function – how to activate the cheat. And here it is: let’s store the &quot;state&quot; (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.</p><p>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 &quot;frozen&quot; 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.</p><p>And here is the function! <a href="http://klimaleksus.narod.ru/Files/H/fc_6.png" rel="nofollow">http://klimaleksus.narod.ru/Files/H/fc_6.png</a><br />Disassembly: (&quot;#&quot; will show any non-empty command that is executed before preceding jump)</p><div class="quotebox" style="padding: 0px;"><div onclick="var e,d,c=this.parentNode,a=c.getElementsByTagName('div')[1],b=this.getElementsByTagName('span')[0];if(a.style.display!=''){while(c.parentNode&&(!d||!e||d==e)){e=d;d=(window.getComputedStyle?getComputedStyle(c, null):c.currentStyle)['backgroundColor'];if(d=='transparent'||d=='rgba(0, 0, 0, 0)')d=e;c=c.parentNode;}a.style.display='';a.style.backgroundColor=d;b.innerHTML='&#9650;';}else{a.style.display='none';b.innerHTML='&#9660;';}" style="font-weight: bold; cursor: pointer; font-size: 0.9em;"><span style="padding: 0 5px;">&#9660;</span>Hidden text</div><div style="padding: 6px; margin: 0; display: none;"><p>v1 = 0x8001<br />v1 = v1 + 0xb308 // pointer to a_main<br />t4 = load4(v1 + 0) // = a_main<br />t5 = load4(v1 + 4) // = a_dust<br />at = load4(v1 + 8) // = a_keys<br />t2 = load2(v1 + 12) // = key_L<br />t3 = load2(v1 + 14) // = key_R<br />at = load2(at) // = V_KEYS<br />t0 = load4(v1 + 32) // = s_main<br />t1 = load4(v1 + 36) // = s_dust<br />a0 = load4(t4) // V_MAIN<br />a1 = load4(t5) // V_DUST<br />v0 = load4(v1 + 24) // state<br />t6 = load4(v1 + 16) // f_main<br />t7 = load4(v1 + 20) // f_dust</p><p>a2 = at &amp; t2 // a2 = first button trace<br />if (a2 == 0) goto :not_both_keys<br /># a3 = at &amp; t2 // a2 = second button trace<br />if (a3 != 0) goto :both_keys<br />nop</p><p>:not_both_keys<br />if (v0 &gt;= 0) goto :label // to main_logic if state=1 and exit if state=0<br /># at = a2 + a3 // zero only if none keys pressed<br />if (at != 0) goto :just_exit // keep status negative while any key still pressed<br /># v0 = v0 + 2 // will take effect only when no keys are presses</p><p>:label<br />if (v0 &lt;= 0) goto :save_and_exit<br />nop<br />goto :main_logic<br />nop</p><p>:both_keys<br />if (v0 &lt; 0) goto :save_and_exit // also could just_exit instead<br />nop<br />if (v0 == 0) goto :save_and_exit // make status=-1 if it was 0<br /># v0 = -1</p><p>v0 = -2 // make status=-2 if it was 1<br />save4(v4) = t0 // V_MAIN =<br />save4(v5) = t1 // V_DUST =<br />save4(v1 + 32) = 0 // s_main =<br />save4(v1 + 36) = 0 // s_dust =<br />save4(v1 + 40) = 0 // t_main =<br />save4(v1 + 44) = 0 // t_dust =<br />goto :save_and_exit // after restoring everything<br />nop</p><p>:main_logic<br />if (t0 == a0) goto :check_second // skip if saved is equal to current<br />nop<br />if (t6 == a0) goto :check_second // also skip if equal to our<br /># save4(t4) = t6 // V_MAIN<br />// copy otherwise:<br />save4(v1 + 32) = a0 // s_main =<br />save4(v1 + 40) = a0 // t_main =</p><p>:check_second<br />if (t1 == a0) goto :do_first // same here<br />nop<br />if (t7 == a0) goto :do_first // and here<br /># save4(t4) = t7 // V_DUST<br />save4(v1 + 36) = a0 // s_dust =<br />save4(v1 + 44) = a0 // t_dust =</p><p>:do_first<br />t0 = load4(v1 + 40) // = t_main<br />t1 = load4(v1 + 44) // = t_dust<br />if (a2 == 0) goto :do_second // skip if first key isn’t pressed<br />nop<br />// swap:<br />save4(v1 + 40) = a0 // t_main =<br />save4(t4) = t0 // V_MAIN =</p><p>:do_second<br />if (a3 == 0) goto :save_and_exit // skip if second key is up<br />nop<br />// second swap:<br />save4(v1 + 44) = a1 // t_dust =<br />save4(t5) = t1 // V_DUST =</p><p>:save_and_exit<br />save4(v1 + 24) = v0 // state =<br />:just_exit<br />return // here also will point t6 and t7 in Spyro1<br />nop</p><p>:a_main<br />...</p></div></div><p>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.<br />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.<br />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.</p><div class="quotebox"><cite>Sheep wrote:</cite><blockquote><div><p>Slowly lift, *bam* fall down, slowly lift, *bam* fall down...</p></div></blockquote></div><p>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.</p><div class="quotebox"><cite>Sheep wrote:</cite><blockquote><div><p>Skateboarding</p></div></blockquote></div><p>Which cheating engine do you use when you want to modify a value at run-time (maybe with hotkey)?<br />I use ArtMoney, and I have a table (with hotkeys) for Greatest Hits skatepark: <a href="http://klimaleksus.narod.ru/Files/2/M8.rar" rel="nofollow">http://klimaleksus.narod.ru/Files/2/M8.rar</a> (ArtMoney included).<br />There are some values to change skateboard boost and Spyro speed…</p>]]></description>
			<author><![CDATA[dummy@example.com (aleksusklim)]]></author>
			<pubDate>Sun, 16 Aug 2015 21:57:16 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432214#p432214</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432208#p432208</link>
			<description><![CDATA[<p>Finally got some time today to test out your hack, aleksusklim... Wow, this is amazing <img src="https://www.spyroforum.com/img/smilies/big_smile.png" width="15" height="auto" alt="big_smile" /><br />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!<br />Edit:<br />Playing some in Sunny Villa... apart from the fact that they don&#039;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...</p><p>Edit:<br />Skateboarding: I don&#039;t know how it happened, and I&#039;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)</p>]]></description>
			<author><![CDATA[dummy@example.com (Sheep)]]></author>
			<pubDate>Sun, 16 Aug 2015 15:13:07 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432208#p432208</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432203#p432203</link>
			<description><![CDATA[<div class="quotebox"><cite>spyroyo wrote:</cite><blockquote><div><p>I&#039;m a big fan of your work.</p></div></blockquote></div><p>…I think it&#039;s wasn&#039;t necessary to quote my entire post))</p><div class="quotebox"><cite>spyroyo wrote:</cite><blockquote><div><p>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:</p></div></blockquote></div><p>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?<br />Why not just draw these numbers in .BMP extracted texture tiles?</p>]]></description>
			<author><![CDATA[dummy@example.com (aleksusklim)]]></author>
			<pubDate>Sun, 16 Aug 2015 11:23:30 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432203#p432203</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432198#p432198</link>
			<description><![CDATA[<div class="quotebox"><cite>aleksusklim wrote:</cite><blockquote><div><div class="quotebox"><cite>Sheep wrote:</cite><blockquote><div><p>Which version of GameMaker are you using?</p></div></blockquote></div><p>Game Maker 8.0. ( <a href="http://klimaleksus.narod.ru/Files/GML8.rar" rel="nofollow">http://klimaleksus.narod.ru/Files/GML8.rar</a> )</p><p>Ones I even tried to send a <a href="http://klimaleksus.narod.ru/Files/1/gmb8.txt" rel="nofollow">bug report</a>… </p><div class="quotebox"><cite>Sheep wrote:</cite><blockquote><div><p>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.</p></div></blockquote></div><p>Wow! I definitely need it, maybe sometimes it will be helpful.<br />Can I have it, could you upload you version somewhere?</p><div class="quotebox"><cite>Sheep wrote:</cite><blockquote><div><p>The screen flickering was really painful, though, so I suggest replacing it with something else</p></div></blockquote></div><p>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 &quot;seek past end of disc&quot;…</p><p>New version is here: <a href="http://klimaleksus.narod.ru/Files/4/RamtopFinder2.rar" rel="nofollow">http://klimaleksus.narod.ru/Files/4/RamtopFinder2.rar</a>&#160; (no need to download ‘cause everything is included in my next program below)</p><p>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)</p><div class="quotebox" style="padding: 0px;"><div onclick="var e,d,c=this.parentNode,a=c.getElementsByTagName('div')[1],b=this.getElementsByTagName('span')[0];if(a.style.display!=''){while(c.parentNode&&(!d||!e||d==e)){e=d;d=(window.getComputedStyle?getComputedStyle(c, null):c.currentStyle)['backgroundColor'];if(d=='transparent'||d=='rgba(0, 0, 0, 0)')d=e;c=c.parentNode;}a.style.display='';a.style.backgroundColor=d;b.innerHTML='&#9650;';}else{a.style.display='none';b.innerHTML='&#9660;';}" style="font-weight: bold; cursor: pointer; font-size: 0.9em;"><span style="padding: 0 5px;">&#9660;</span>Hidden text</div><div style="padding: 6px; margin: 0; display: none;"><div class="codebox"><pre class="vscroll"><code>// 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 &lt;r3000.h&gt;
#include &lt;asm.h&gt;
#include &lt;libapi.h&gt;
#include &lt;sys/file.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;libgte.h&gt;
#include &lt;libgpu.h&gt;
#include &lt;libetc.h&gt;

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

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

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

#include &lt;bladeps.h&gt;
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(&quot;!MARniaMfOtratS!&quot;,&quot;!!MARniaMfOdnE!!&quot;,&quot;!hctarcSfOtratS!&quot;,&quot;!!hctarcSfOdnE!!&quot;);
//  &quot;!StartOfMainRAM!&quot;,&quot;!!EndOfMainRAM!!&quot;,&quot;!StartOfScratch!&quot;,&quot;!!EndOfScratch!!&quot;
while(1){
a = GsGetActiveBuff ();
GsSetWorkBase(&amp;ot[a][0]);
if(a)GsSortClear(0,0,255);else GsSortClear(0,255,0);
GsSwapDispBuff ();
GsDrawOT ((u_long *) &amp;ot[a][0]);
}
}</code></pre></div></div></div><p><strong>SpyroFreezeChreat</strong></p><p>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))</p><p>Here’s just the result:<br /><a href="http://klimaleksus.narod.ru/Files/4/SpyroFreezeCheat.rar" rel="nofollow">http://klimaleksus.narod.ru/Files/4/Spy … eCheat.rar</a></p><p>CrystalFissure, did you ask for something spectacular to make a video?</p><p>It allows to freeze all in-game objects (except Spyro). The second feature is to freeze dust-particle animation.</p><p>I did this hack earlier: ( <a href="http://klimaleksus.narod.ru/Files/2/freeze_1.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/2/freeze_1.jpg</a> , <a href="http://klimaleksus.narod.ru/Files/2/freeze_2.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/2/freeze_2.jpg</a> ,<br /><a href="http://klimaleksus.narod.ru/Files/G/cs_2.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/G/cs_2.jpg</a> ,<br /><a href="http://klimaleksus.narod.ru/Files/G/cs_0.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/G/cs_0.jpg</a> ), 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.</p><p>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.</p><p>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.</p><p>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.</p><p>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).</p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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).</p><p>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.</p><p>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).<br />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!</p><p>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 (&quot;return;&quot; / &quot;jr ra; nop&quot;) and not be just zerofilled.</p><p>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.</p><p>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.</p><p>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:</p><div class="quotebox" style="padding: 0px;"><div onclick="var e,d,c=this.parentNode,a=c.getElementsByTagName('div')[1],b=this.getElementsByTagName('span')[0];if(a.style.display!=''){while(c.parentNode&&(!d||!e||d==e)){e=d;d=(window.getComputedStyle?getComputedStyle(c, null):c.currentStyle)['backgroundColor'];if(d=='transparent'||d=='rgba(0, 0, 0, 0)')d=e;c=c.parentNode;}a.style.display='';a.style.backgroundColor=d;b.innerHTML='&#9650;';}else{a.style.display='none';b.innerHTML='&#9660;';}" style="font-weight: bold; cursor: pointer; font-size: 0.9em;"><span style="padding: 0 5px;">&#9660;</span>Hidden text</div><div style="padding: 6px; margin: 0; display: none;"><p>1 USA (SCUS_942.28)<br />main=80075734<br />dust=800756bc<br />keys=80077380<br />jump=80012284</p><p>1 JAP (SCPS_100.83)<br />main=8007f2a4<br />dust=8007f214<br />keys=80080F80<br />jump=80011650</p><p>2 RR (SCUS_944.25)<br />main=800670F0<br />dust=800670F8<br />keys=8006A9AC<br />jump=80011b04</p><p>2 TT (SCPS_101.28)<br />main=80069814<br />dust=8006981C<br />keys=8006AF58<br />jump=80011fd8</p><p>3 USA (SCUS_944.67)<br />main=800742d8<br />dust=800742e8<br />keys=80071500<br />jump=80012034</p><p>3 GH (SCUS_944.67)<br />main=800743b8<br />dust=800743c8<br />keys=800715e0<br />jump=80012048</p><p>3 EUP (SCES_028.35)<br />main=80077794<br />dust=800777b4<br />keys=80071834<br />jump=80013fec</p></div></div><p>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 ^^<br />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!<br />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.</p><p>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…<br />Sadly that there is finite number of particles, and adding more will randomly destroy others… but still it’s great!</p><p>I didn’t investigate further, despite it’s very interesting. Try by yourselves! Explode something and freeze everything))<br />Make a video if you find other cool things with freezing.</p><p>(a link, again: <a href="http://klimaleksus.narod.ru/Files/4/SpyroFreezeCheat.rar" rel="nofollow">SpyroFreezeCheat.rar</a>)</p></div></blockquote></div><p>Hi aleks,<br />I&#039;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:</p><p><a href="http://i.ytimg.com/vi/owEUiRSjn38/hqdefault.jpg" rel="nofollow">http://i.ytimg.com/vi/owEUiRSjn38/hqdefault.jpg</a></p>]]></description>
			<author><![CDATA[dummy@example.com (spyroyo)]]></author>
			<pubDate>Sun, 16 Aug 2015 01:06:13 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432198#p432198</guid>
		</item>
		<item>
			<title><![CDATA[Re: Collision data hacking]]></title>
			<link>https://www.spyroforum.com/viewtopic.php?pid=432157#p432157</link>
			<description><![CDATA[<div class="quotebox"><cite>Sheep wrote:</cite><blockquote><div><p>Which version of GameMaker are you using?</p></div></blockquote></div><p>Game Maker 8.0. ( <a href="http://klimaleksus.narod.ru/Files/GML8.rar" rel="nofollow">http://klimaleksus.narod.ru/Files/GML8.rar</a> )</p><p>Ones I even tried to send a <a href="http://klimaleksus.narod.ru/Files/1/gmb8.txt" rel="nofollow">bug report</a>… </p><div class="quotebox"><cite>Sheep wrote:</cite><blockquote><div><p>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.</p></div></blockquote></div><p>Wow! I definitely need it, maybe sometimes it will be helpful.<br />Can I have it, could you upload you version somewhere?</p><div class="quotebox"><cite>Sheep wrote:</cite><blockquote><div><p>The screen flickering was really painful, though, so I suggest replacing it with something else</p></div></blockquote></div><p>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 &quot;seek past end of disc&quot;…</p><p>New version is here: <a href="http://klimaleksus.narod.ru/Files/4/RamtopFinder2.rar" rel="nofollow">http://klimaleksus.narod.ru/Files/4/RamtopFinder2.rar</a>&#160; (no need to download ‘cause everything is included in my next program below)</p><p>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)</p><div class="quotebox" style="padding: 0px;"><div onclick="var e,d,c=this.parentNode,a=c.getElementsByTagName('div')[1],b=this.getElementsByTagName('span')[0];if(a.style.display!=''){while(c.parentNode&&(!d||!e||d==e)){e=d;d=(window.getComputedStyle?getComputedStyle(c, null):c.currentStyle)['backgroundColor'];if(d=='transparent'||d=='rgba(0, 0, 0, 0)')d=e;c=c.parentNode;}a.style.display='';a.style.backgroundColor=d;b.innerHTML='&#9650;';}else{a.style.display='none';b.innerHTML='&#9660;';}" style="font-weight: bold; cursor: pointer; font-size: 0.9em;"><span style="padding: 0 5px;">&#9660;</span>Hidden text</div><div style="padding: 6px; margin: 0; display: none;"><div class="codebox"><pre class="vscroll"><code>// 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 &lt;r3000.h&gt;
#include &lt;asm.h&gt;
#include &lt;libapi.h&gt;
#include &lt;sys/file.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;libgte.h&gt;
#include &lt;libgpu.h&gt;
#include &lt;libetc.h&gt;

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

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

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

#include &lt;bladeps.h&gt;
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(&quot;!MARniaMfOtratS!&quot;,&quot;!!MARniaMfOdnE!!&quot;,&quot;!hctarcSfOtratS!&quot;,&quot;!!hctarcSfOdnE!!&quot;);
//  &quot;!StartOfMainRAM!&quot;,&quot;!!EndOfMainRAM!!&quot;,&quot;!StartOfScratch!&quot;,&quot;!!EndOfScratch!!&quot;
while(1){
a = GsGetActiveBuff ();
GsSetWorkBase(&amp;ot[a][0]);
if(a)GsSortClear(0,0,255);else GsSortClear(0,255,0);
GsSwapDispBuff ();
GsDrawOT ((u_long *) &amp;ot[a][0]);
}
}</code></pre></div></div></div><p><strong>SpyroFreezeChreat</strong></p><p>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))</p><p>Here’s just the result:<br /><a href="http://klimaleksus.narod.ru/Files/4/SpyroFreezeCheat.rar" rel="nofollow">http://klimaleksus.narod.ru/Files/4/Spy … eCheat.rar</a></p><p>CrystalFissure, did you ask for something spectacular to make a video?</p><p>It allows to freeze all in-game objects (except Spyro). The second feature is to freeze dust-particle animation.</p><p>I did this hack earlier: ( <a href="http://klimaleksus.narod.ru/Files/2/freeze_1.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/2/freeze_1.jpg</a> , <a href="http://klimaleksus.narod.ru/Files/2/freeze_2.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/2/freeze_2.jpg</a> ,<br /><a href="http://klimaleksus.narod.ru/Files/G/cs_2.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/G/cs_2.jpg</a> ,<br /><a href="http://klimaleksus.narod.ru/Files/G/cs_0.jpg" rel="nofollow">http://klimaleksus.narod.ru/Files/G/cs_0.jpg</a> ), 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.</p><p>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.</p><p>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.</p><p>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.</p><p>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).</p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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).</p><p>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.</p><p>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).<br />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!</p><p>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 (&quot;return;&quot; / &quot;jr ra; nop&quot;) and not be just zerofilled.</p><p>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.</p><p>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.</p><p>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:</p><div class="quotebox" style="padding: 0px;"><div onclick="var e,d,c=this.parentNode,a=c.getElementsByTagName('div')[1],b=this.getElementsByTagName('span')[0];if(a.style.display!=''){while(c.parentNode&&(!d||!e||d==e)){e=d;d=(window.getComputedStyle?getComputedStyle(c, null):c.currentStyle)['backgroundColor'];if(d=='transparent'||d=='rgba(0, 0, 0, 0)')d=e;c=c.parentNode;}a.style.display='';a.style.backgroundColor=d;b.innerHTML='&#9650;';}else{a.style.display='none';b.innerHTML='&#9660;';}" style="font-weight: bold; cursor: pointer; font-size: 0.9em;"><span style="padding: 0 5px;">&#9660;</span>Hidden text</div><div style="padding: 6px; margin: 0; display: none;"><p>1 USA (SCUS_942.28)<br />main=80075734<br />dust=800756bc<br />keys=80077380<br />jump=80012284</p><p>1 JAP (SCPS_100.83)<br />main=8007f2a4<br />dust=8007f214<br />keys=80080F80<br />jump=80011650</p><p>2 RR (SCUS_944.25)<br />main=800670F0<br />dust=800670F8<br />keys=8006A9AC<br />jump=80011b04</p><p>2 TT (SCPS_101.28)<br />main=80069814<br />dust=8006981C<br />keys=8006AF58<br />jump=80011fd8</p><p>3 USA (SCUS_944.67)<br />main=800742d8<br />dust=800742e8<br />keys=80071500<br />jump=80012034</p><p>3 GH (SCUS_944.67)<br />main=800743b8<br />dust=800743c8<br />keys=800715e0<br />jump=80012048</p><p>3 EUP (SCES_028.35)<br />main=80077794<br />dust=800777b4<br />keys=80071834<br />jump=80013fec</p></div></div><p>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 ^^<br />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!<br />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.</p><p>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…<br />Sadly that there is finite number of particles, and adding more will randomly destroy others… but still it’s great!</p><p>I didn’t investigate further, despite it’s very interesting. Try by yourselves! Explode something and freeze everything))<br />Make a video if you find other cool things with freezing.</p><p>(a link, again: <a href="http://klimaleksus.narod.ru/Files/4/SpyroFreezeCheat.rar" rel="nofollow">SpyroFreezeCheat.rar</a>)</p>]]></description>
			<author><![CDATA[dummy@example.com (aleksusklim)]]></author>
			<pubDate>Fri, 14 Aug 2015 19:38:54 +0000</pubDate>
			<guid>https://www.spyroforum.com/viewtopic.php?pid=432157#p432157</guid>
		</item>
	</channel>
</rss>
