|
Cheat Engine The Official Site of Cheat Engine
|
View previous topic :: View next topic |
Author |
Message |
happyTugs Cheater Reputation: 0
Joined: 23 Apr 2020 Posts: 26
|
Posted: Fri Jul 16, 2021 11:06 am Post subject: Visual Novel: kirikiri Engine & SpoilerAL |
|
|
Hello.
I am currently reversing the kirikiri/2 Engine, and I am a bit stuck.
a.) Using conventional pointer-scanning in CE will always result in zero finds, unless my settings are incorrect.
b.) Trying to find what accesses the address will always result with an instruction accessing hundreds of addresses.
Therefore, I have resorted to using SpoilerAL, a Japanese memory-editor that uses a format different from CE.
Instead of .xml, it uses .ssg.
Below is a code excerpt that I have used to try and understand the engine, and the game itself (dualtail VenusBlood Frontier International).
Code: | [group]ADDRESS_DAY
[:MName::VBFI.exe+0x003DFE7C:]+0x1C=>base;
0x48058FEE=>hash;
[:$base:]=>base;
[:$base+0x10:]=>tmp1;
[:$base+0x18:]=>tmp2;
($tmp1&$hash)*0x20=>tmp1;
$tmp2+$tmp1=>ad0;
[:$ad0+0x1C:]=>ad1; |
Converting the entire code snippet to lua led me to below...
Code: | [ENABLE]
local baseAddy = readPointer("VBFI.exe+0x003DFE7C") + 0x1C
local hash0 = 0x48058FEE --string key 'f'
local hash1 = 0x518E5C1C --string key 'used'
local hash2 = 0x4E9A9417 --?
local hash3 = 0x4D12DEE9 --string key 'resource.name'
local hash4 = 0x00BBECBA --string key 'now'
local function str2hex(str)
return string.format('%X',str)
end
local function returnAddressFromHash(base,hash)
local temp1 = readPointer(base+0x10)
local temp2 = readPointer(base+0x18)
local temp1 = (bAnd(temp1,hash)) * 0x20
local addressStart = temp1 + temp2
table1 = {addressStart} -- addresses
local addressNext = addressStart
for i =2,500,1 do -- 500 is an arbitrary number I used for the iterator
addressNext = readPointer(addressNext + 0x1C) or 0
table1[i] = addressNext
end
table2 = {} -- valid addresses
for i =1,500,1 do
addressNext = readPointer(table1[i] + 0x04)
if addressNext == hash then
return table1[i] + 0x10
end
end
end
local result1 = returnAddressFromHash(readPointer(baseAddy),hash0)
local result2 = returnAddressFromHash(readPointer(result1),hash1)
local result3 = returnAddressFromHash(readPointer(result2),hash2)
local result4 = returnAddressFromHash(readPointer(result3),hash3)
local result5 = returnAddressFromHash(readPointer(result4),hash4)
print(str2hex(result5))
[DISABLE] |
Firstly, my goal is to find a reliable way to access my character data (gold, hp, resources, etc) directly from CE and not SpoilerAL. I accomplished this by writing the lua script above. All that is needed is to input the 'base' and the needed 'hash(es)'.
However, I don't understand how the 'base' and 'hash' were found.
To find out more on this, I searched for relevant topics in the forum, and it led me here: https://forum.cheatengine.org/viewtopic.php?t=571274&highlight=
If I am not mistaken, these 'hashes' were found via string keys (f, used, resource.name, now, etc.).
If that is the case, does anyone know, or can suggest, how I can find these string keys along with the base and hash?
Thank you. _________________
This is a block of text that can be added to posts you make. There is a 300 character limit |
|
Back to top |
|
|
ParkourPenguin I post too much Reputation: 138
Joined: 06 Jul 2014 Posts: 4275
|
Posted: Fri Jul 16, 2021 11:52 am Post subject: |
|
|
If the data values can easily be found, I'd look up the callstack from accesses to those values and follow the pointer path as I go along.
You'd need to know how to read assembly as well as basic reverse engineering (e.g. identifying hash tables) to do that. _________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
Back to top |
|
|
happyTugs Cheater Reputation: 0
Joined: 23 Apr 2020 Posts: 26
|
Posted: Sat Jul 24, 2021 5:11 am Post subject: To ParkourPenguin |
|
|
Hello, sorry for the late response.
I have been looking more into this, and I have found out a few things.
Firstly, I simplified my LUA code (I should probably use recursion).
Code: | local function returnAddressFromHash(baseArray,hash)
local bitKey = readPointer(baseArray+0x10) --some hex key (0x7F, etc.)
local subnodeHash = readPointer(baseArray+0x18) --some address (0x01B66638, etc.)
local arrayOffset = (bAnd(bitKey,hash)) * 0x20 --algo to find index in dictionary/array
local subnodeArray = arrayOffset + subnodeHash -- add offset to subnode
local linkLimit = 0 -- arbitrary limit of 500
while (subnodeArray and readInteger(subnodeArray + 0x04) ~= hash and linkLimit < 500) do
subnodeArray = readPointer(subnodeArray + 0x1C) or 0
linkLimit = linkLimit + 1
end
return subnodeArray + 0x10
end |
Secondly, I found the answer to my first question (i.e. How were these hashes found?).
Krkr stores its data into hash tables, and they contain the name of the data it represents.
In most krkr games, you can access a hash table by finding an object (gold, energy, etc.), and accessing its this pointer.
The name can be found at offset 0x8 and its hash can be found at offset 0x3C.
Some hashes below...
Code: | stat | 4E9A9417
gold | 4D12DEE9
energy | E8269420
resource | 541F84B8
force | 2B56B5F7
population | D7DA0CC0
Tilca | DAAC821D
Ferna | 50E7D0C1
Thor | 38F2A99A
Fenrir | 8766694B
Hel | 1EDDD5C0
Ferna | 50E7D0C1
Odin | 51A8AE73
Regret | C22A91DB
Thor | 38F2A99A
Freya | 63E995FD
Menia | B16ACEBF
Jormu | A754C23F |
Then input them like so...
Code: | local hashT = {
['f'] = 0x48058FEE,
['used'] = 0x518E5C1C,
['game'] = 0x4E9A9417,
['stat'] = {
['ENERGY'] = 0xE8269420,
['FOOD'] = 0xA34B1939,
['GOLD'] = 0x4D12DEE9,
['RESOURCE'] = 0x541F84B8,
},
['now'] = 0x00BBECBA,
}
local function addResources()
for k,v in pairs(hashT['stat']) do
local f = returnAddressFromHash(readPointer(baseAddy),hashT['f'])
local used = returnAddressFromHash(readPointer(f),hashT['used'])
local game = returnAddressFromHash(readPointer(used),hashT['game'])
local d = returnAddressFromHash(readPointer(game),hashT['stat'][k])
local now = returnAddressFromHash(readPointer(d),hashT['now'])
registerAddress(k,now)
end
end |
I know it's ugly, but it will work for now.
Now, I am confused as to how the sequence of the structure was found (i.e. f -> used -> game -> gold -> now).
One way is to unpack the .xp3 files and sift through the .tjs and.ks files piece by piece (.tjs and .ks are modified versions of JavaScript).
But, suppose you don't do all of that.
How then would you find these particular sequences, and back-trace a piece of data (now) to its beginning (f)?
A few ideas..
a. Loop through the data's hash table(now) to see if it links back to the primary hash table(f) or its secondary (used, game, stat, etc.).
b. Finding out what instructions accesses the hash table and work from there (I am doing binary analysis with IDA, along with ClassInformer).
c. Pray for thyself to reach an epiphany. What do you think?
As always, thank you ParkourPenguin!
In the meantime, I will do some more research. _________________
This is a block of text that can be added to posts you make. There is a 300 character limit |
|
Back to top |
|
|
ParkourPenguin I post too much Reputation: 138
Joined: 06 Jul 2014 Posts: 4275
|
Posted: Sat Jul 24, 2021 12:14 pm Post subject: |
|
|
If the source code is available (GPL, so probably), you could simply look at that. There's some fork of kirikiri2 called kirikiriZ I found on github which implements some type of hash table here:
https://github.com/krkrz/krkrz/blob/48c1055852ec948fe13d760bf8482d0b7c6b6265/tjs2/tjsHashSearch.h#L128
Might be what you're looking for. I don't know.
If source code isn't available, resort to basic reverse engineering. I'm neither capable nor willing to teach software reverse engineering over a few forum posts. _________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
Back to top |
|
|
atom0s Moderator Reputation: 198
Joined: 25 Jan 2006 Posts: 8516 Location: 127.0.0.1
|
|
Back to top |
|
|
happyTugs Cheater Reputation: 0
Joined: 23 Apr 2020 Posts: 26
|
Posted: Sun Aug 15, 2021 5:07 am Post subject: To ParkourPenguin and atom0s |
|
|
I have been busy with work; sorry for the late response.
A few updates.
Firstly, I scoured through the source; the JOAAT algorithm does indeed hash the element's strings.
Below is my LUA 5.3 implementation.
Code: | function joaatHash(str)
local i,hash,length = 1,0,#str
while (i ~= length + 1) do
hash = (hash + string.byte(str,i)) & 0xFFFFFFFF
hash = (hash + (hash << 10)) & 0xFFFFFFFF
hash = (hash ~ (hash >> 6)) & 0xFFFFFFFF
i = i + 1
end
hash = (hash + (hash << 3)) & 0xFFFFFFFF
hash = (hash ~ (hash >> 11)) & 0xFFFFFFFF
hash = (hash + (hash << 15)) & 0xFFFFFFFF
return hash
end |
Furthermore, below is a snippet of the class of the tJSHashTable.
Code: | private:
struct element
{
tjs_uint32 Hash;
tjs_uint32 Flags; // management flag
char Key[sizeof(KeyT)];
char Value[sizeof(ValueT)];
element *Prev; // previous chain item
element *Next; // next chain item
element *NPrev; // previous item in the additional order
element *NNext; // next item in the additional order
} Elms[HashSize];
tjs_uint Count;
element *NFirst; // first item in the additional order
element *NLast; // last item in the additional order |
The krkr2 engine uses chaining to resolve collisions, which can explain why some elements point to other elements.
I'll have to research more on hash-tables to find out how the sequence of buckets are initialized, and how elements are inserted.
Secondly, I figured out how to find the top-level (global) object: tTjs.
Since krkr2 is based off the TJS2 Script Engine (VBFI being based off the 'Aug 10 2016' build), all classes are underneath the namespace of TJS.
There are at least two ways to find the tTJS object: vfTable or static address.
If the binary is compiled with MS Visual C++, and ships with RTTI, you can use IDA, along with ClassInformer, to find the TJS::tTJS::vfTable!
From there, memory scan for the vfTable address (ensure writable scans), and you find your tTJS object.
If not, there are several UNICODE strings used inside the tTJS constructor as shown below.
Code: | "version"
"the global object" (this x-ref is generally unique)
"Array"
"Dictionary"
|
The address for the tTJS vfTable is found in the prologue of the tTJS constructor, as shown below.
Code: | v40 = &v26;
v1 = this;
v38 = this;
*this = &TJS::tTJS::`vftable';
this[3] = 0;
this[4] = 0;
this[5] = 0;
v41 = 0;
this[1] = 1;
this[6] = 0;
this[2] = 0; |
Of course, there may be other, similar implementations, so please check yourself.
On the other hand, there is a static address that points to the tTJS object, and is x-ref'd numerous times.
However, there are krkr games compiled with Borland C++, as such, these techniques may not apply; I will investigate more when I have time.Some other notable information.
Kirikiri is a scripting engine, and utilizes the KAG (KiriKiri Adventure Game System) framework and and an object-oriented scripting language called TJS (Kirikiri TPV JavaScript).
VBFI is based off KiriKiri2 (krkr2) in conjunction with the KAG '3.32 stable rev. 2' framework, and the TJS2 scripting language.
Moreover, there are other implmentations of KiriKiri, such as in C#: https://github.com/fantasydr/krkr-cs
Thank you atom0s for mentioning the JOAAT hash algorithm!
I would not have made that connection, since I am not familar with hashes and hash-tables.
Also, thank you ParkourPenguin for sending me a link to the source code!
I learned significantly on krkr2's hash-table implementation, and made meaningful connections from source to binary.
In the meantime, I will do some more research. _________________
This is a block of text that can be added to posts you make. There is a 300 character limit |
|
Back to top |
|
|
happyTugs Cheater Reputation: 0
Joined: 23 Apr 2020 Posts: 26
|
Posted: Sun Aug 22, 2021 9:30 am Post subject: My Obsevations |
|
|
Hello again.
I finally figured out how to find the correct sequence of hashes to locate a certain element.
Because I have been busy, my write-up was delayed.
This will be a long post; hopefully, this will be helpful for those concerned in the future.
Firstly, the krkr engine has a top-level object that contains a global hash-table to other objects, functions, data, etc.
Respectively, they are called TJS::tTJS and the TJS::tTJSDictionaryObject.
In a previous post, I covered how to find the static address of tTJS by finding the address of TJS::tJS::vfTable.
All objects instantiated from the TJS::tTJS class have an address that points to the TJS::tJS::vfTable, generally found at its 'this' pointer.
By finding the TJS::tJS::vfTable address, and searching in memory which object contains that address, you effectively find a tTJS object (keep in mind, there is usually only one tTJS object).
Finding the tTJS object, you can then search in memory what static address points to the tTJS object.
Once you find the static address that points to tTJS, you can then read from this address and
add an offset to find tTJSDictionaryObject (tTJSDict for short).
Depending on the version of the game engine (krkr or krkr2), the offset for tTJSDict may vary.
From what I have dealt with, the offset for tTJSDict for krkr is at offset 0x30; for krkr2, tTJSDict is at offset 0x18.
Now, tTJSDictionaryObject is a hash-table: a data structure that maps keys to values, and is primarily defined by its hash algorithm.
Both krkr, and krkr2, use the Jenkins-One-At-A-Time (JOAAT) hash algorithm to calculate the index of a tTJSElement, or value, given a string.
If you are so inclined, a kind freelance programmer by the username of jin1016, posted the source code for krkr2, and is also developing krkrz (a fork of krkr), at github: https://github.com/krkrz/krkr2
Below are the class information and functions to retrieve a tTJSElement from a tTJSDictionaryObject for krkr.
Code: | ---------
--CLASS--
---------
local tTJSElement = {
This = 0x0,
Hash = 0x4,
Value = 0x10,
Type = 0x18,
Next = 0x1C,
}
local tTJSDictionaryObject = {
Size = 0x10,
Max = 0x14,
Array = 0x18,
}
local tNameArray = {
Name = 0x8,
}
-------------
--FUNCTIONS--
-------------
--[[
Function Overview: Hash a sequence of strings, delimited by a period(.)
with JOAAT, and use that hash to index a
TJSDictionaryObject to retrieve the respective
TJSElement.
a. joaatHash(str)
b. split(inputstr, sep)
c. hashString(str)
d. rAFH(TJSDictionaryObject,hashT) a.k.a returnAddressFromHash
]]
--https://en.wikipedia.org/wiki/Jenkins_hash_function
local function joaatHash(str)
local i,hash,length = 1,0,#str
while (i ~= length + 1) do
hash = (hash + string.byte(str,i)) & 0xFFFFFFFF
hash = (hash + (hash << 10)) & 0xFFFFFFFF
hash = (hash ~ (hash >> 6)) & 0xFFFFFFFF
i = i + 1
end
hash = (hash + (hash << 3)) & 0xFFFFFFFF
hash = (hash ~ (hash >> 11)) & 0xFFFFFFFF
hash = (hash + (hash << 15)) & 0xFFFFFFFF
return hash
end
--https://stackoverflow.com/questions/1426954/split-string-in-lua
--thank you, bart.
local function split(inputstr, sep)
sep=sep or '%s'
local t={}
for field,s in string.gmatch(inputstr, "([^"..sep.."]*)("..sep.."?)") do
table.insert(t,field)
if s=="" then
return t
end
end
end
local function hashString(str,sep)
local stringHash = {}
for k,v in pairs(split(str,sep)) do
stringHash[k] = joaatHash(v)
end
return stringHash
end
--given a table of hashes, return the value of a tTJSelement from a tTJSDictionaryObject
local function rAFH(TJSDictionaryObject,hashT)
local sz = readInteger(TJSDictionaryObject+tTJSDictionaryObject.Size)
local bucketArray = readPointer(TJSDictionaryObject+tTJSDictionaryObject.Array)
local arrayOffset = (bAnd(sz,hashT[1])) * 0x20
local TJSElement = bucketArray + arrayOffset
local counter = 0
while (TJSElement and
readInteger(TJSElement+ tTJSElement.Hash) ~= hashT[1] and
counter ~= sz) do
TJSElement = readPointer(TJSElement + tTJSElement.Next)
counter = counter + 1
end
if TJSElement == nil then
printf('The string hash (%08X) is not indexed in the dictionary (%08X).',hashT[1],TJSDictionaryObject)
return 0
end
if #hashT ~= 1 then
table.remove(hashT,1)
return rAFH(readPointer(TJSElement + tTJSElement.Value),hashT)
end
return TJSElement + tTJSElement.Value
end |
Below are some usage examples.
Code: | local tTJS = readPointer('game.exe+123456')
local tTJSDictionary = readPointer(tTJS+0x30)
local kingLifeAddress = rAFH(tTJSDictionary,hashString('game.country.person._life','.'))
printf('%X',kingLifeAddress) |
Secondly, how does one find the sequence of a hash (i.e. 'game.country.person._life')?
Since we know that tTJS is the top-level object that contains a tTJSDictionary, or hash-table, to other tTJSElements, we can loop through its hash-table, store the elements into a table, check if those elements contain another hash-table, and repeat the process for a user-defined amount.
I was able to find 'game.country.person._life' through this method.
I got this idea since I have dumped Unreal Engine 3 before, and decided to apply that knowledge here (which was rather exciting). Code: | --------
--DATA--
--------
local FOLDER_PATH = 'C:\\Users\\'..os.getenv('USERNAME')..'\\Desktop\\'..process
-------------
--FUNCTIONS--
-------------
--[[
Function Overview: Process a TJSDictionaryObject, and write its
results (i.e. TJSElements contained inside the TJSDictionaryObject)
onto a .txt file.
a. makeFile(FOLDER_PATH,fileName)
b. closeFile(handle)
c. writeStrToFile(handle,str)
d. checkForTJSDictionary(TJSElement,TJSElementValue)
e. TJSDictionaryDumper(TJSDictionaryObject,parentOf)
f. cycleElementBucket(fileHandle,TJSElementBucket,level,limit,tabStr)
(1)cycles through a dictionary for elements
(2)writes the attributes of an element to a .txt file
(3)check if the element has a dictionary
(4)cycle that dictionary
g. TJSDictionaryDumperMain(TJSDictionaryObject,parentName,loopLimit)
]]
local function makeFile(FOLDER_PATH,fileName)
os.execute('mkdir '..FOLDER_PATH)
local file,err = io.open(FOLDER_PATH..'\\'..fileName..'.txt', 'w+')
assert(file,err)
return file
end
local function closeFile(handle)
handle:close()
return
end
local function writeStrToFile(handle,str)
handle:write(str)
return
end
local function checkForTJSDictionary(TJSElement,TJSElementValue)
if readPointer(TJSElementValue) ~= nil then
local dictHasSize = readInteger(TJSElementValue +tTJSDictionaryObject.Size) ==
readInteger(TJSElementValue + tTJSDictionaryObject.Max)-1
if dictHasSize then return true end
end
return false
end
--return a table of elements contained in a TJSDictionaryObject
local function TJSDictionaryDumper(TJSDictionaryObject,parentOf)
if TJSDictionaryObject == nil then return nil end
local sz = readInteger(TJSDictionaryObject+tTJSDictionaryObject.Size)
local BucketArray = readPointer(TJSDictionaryObject+tTJSDictionaryObject.Array)
local elementBucket = {}
for index=0x0,sz,0x1 do
local ArrayOffset = index * 0x20
local TJSElement = ArrayOffset + BucketArray
local TJSElementValue = readPointer(TJSElement+tTJSElement.Value)
local TJSElementThis = readPointer(TJSElement + tTJSElement.This) or 0
local name = readString(TJSElementThis + tNameArray.Name,50,true)
if name ~= nil then
table.insert(elementBucket,{
['ParentOf'] = parentOf or '',
['Name'] = name,
['Address'] = TJSElement,
['Value'] = TJSElementValue,
['HasDictionary'] = checkForTJSDictionary(TJSElement,TJSElementValue)})
end
end
return elementBucket
end
--recursive function that cycles through an element bucket for dictionaries and
--writes them to a .txt file
local function cycleElementBucket(fileHandle,TJSElementBucket,level,limit,tabStr)
if level == limit then return end
for k,v in pairs(TJSElementBucket) do
local formatStr = string.format('%s%X -> %08X : %s\n',tabStr,v['Address'],v['Value'],v['ParentOf']..'.'..v['Name'])
writeStrToFile(fileHandle,formatStr)
if v['HasDictionary'] == true then
local nextDictionary = TJSDictionaryDumper(v['Value'],v['ParentOf']..'.'..v['Name'])
cycleElementBucket(fileHandle,nextDictionary,level+1,limit,tabStr..'\t')
end
end
return
end
local function TJSDictionaryDumperMain(TJSDictionaryObject,parentName,loopLimit)
local fileHandle = makeFile(FOLDER_PATH,parentName)
local elementBucket = TJSDictionaryDumper(TJSDictionaryObject,parentName)
local recursionLevel = 0 --default level for recursion
local emptyString = '' --used to concatenate tabs
cycleElementBucket(fileHandle,elementBucket,recursionLevel,loopLimit,emptyString)
closeFile(fileHandle)
return
end |
Below are some usage examples. Code: | local tTJS = readPointer('game.exe+123456')
local tTJSDictionary = readPointer(tTJS+0x30)
local name = '' -- name is an empty string since I am dumping from the tTJS object
local recursionLimit = 2 --cycle amount
TJSDictionaryDumperMain(tTJSDictionary,name,recursionLimit) |
Executing the above code results in the below snippet of the produced text file.
Code: | B87EAF8 -> 0A0F7610 : .game
18FF4220 -> 0A013BFC : .game.prisonCmdOpened
18FF4240 -> 086E0DE8 : .game.routeCostReset
18FF4280 -> 00000000 : .game.curChara
18FF43C0 -> 030048AC : .game.onStableStateChanged
18FF44E0 -> 00000000 : .game.winner
18FF4580 -> 0877A800 : .game.layersReset
18FF45A0 -> 0C69C704 : .game.troopList |
For the parent name of tTJS, I used an empty string because the tTJS object is the basis for all elements. If you are trying to find a hash, the actual hash string would be... Code: | .game.winner - > game.winner | If I am dumping the tTJSDictionaryObject from the 'game' element, then I would use the code below.
Code: | local tTJS = readPointer('game.exe+123456')
local gameAddressValue = rAFH(readPointer(baseAddress+0x30),hashString('game'))
local tTJSDictionary = readPointer(gameAddressValue)
local name = 'game'
local recursionLimit = 2 --cycle amount
TJSDictionaryDumperMain(tTJSDictionary,name,recursionLimit) |
Thirdly, krkr2 is slightly different.
While krkr2 does store hash-tables within elements, krkr2 stores the next element by chaining (i.e. a pointer to another tTJSElement, not a tTJSDictionaryObject).
I will have to investigate more on this later on when I have some time.
But, the overall process of calculating hashes and retrieving elements through a hashed string are still the same.
This brings me to the end; I will post more on krkr and krkr2 when I have some more time.
If you have any suggestions where I can improve my LUA, please let me know!
Thank you. _________________
This is a block of text that can be added to posts you make. There is a 300 character limit |
|
Back to top |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum You cannot attach files in this forum You can download files in this forum
|
|