Idle Heroes Server Files Development Mobile 

🚫
Exiled
Joined
Feb 13, 2018
Messages
260
Reaction score
124
Hello Ragezone community,

After working on this on and off for several years, I can finally share my Idle Heroes Private Server project. What started as curiosity and personal research slowly turned into a full project that I’ve been planning to release properly for a long time.

This is not a quick setup server or a simple packet relay. The backend has been rebuilt from the ground up with the goal of closely matching the original Idle Heroes experience, while keeping the structure flexible enough to support future changes and custom features. I intentionally avoided rushing anything and focused first on getting the core systems right.

The idea behind this server is simple: provide a familiar Idle Heroes environment without many of the limitations found on official servers. To achieve that, I spent most of the time building a solid base where systems interact cleanly and can be expanded later without constant rewrites. Stability and consistency were always a higher priority than fast release.

From a technical perspective, the server is designed to be stable and predictable. Critical actions are handled server-side with validation and consistency checks to avoid abuse or data issues. While I do share high-level progress, sensitive implementation details are kept private for obvious reasons.

At this stage, a large portion of the core gameplay is already working. Players can log in, manage heroes and monsters, complete quests and challenges, chat with others, add friends, summon heroes, take part in events, and progress through systems like the Prophet Tree, Tavern, Altar, Wishing Fountain, and inventory management. These features already create a complete and familiar gameplay loop.

The project is still actively in development. Major systems such as Campaign, Arena, Guilds, Aspen Dungeon, Tower of Oblivion, Celestial Island, Blacksmith, Skins, and the Creation Circle are either planned or currently being worked on. According to the current roadmap, the goal is to finish these within roughly two months, followed by testing, balancing, and polishing.

Looking ahead, this server is not meant to be a 1:1 copy forever. I want to experiment with new event types, try out alternative arena systems, and introduce fresh ideas that still fit naturally into the Idle Heroes formula.

Already implemented:​

  • Authentication (registration, login, session handling)
  • Chat
  • Friends
  • VIP
  • Quests and daily/weekly challenges
  • Challenge modes
  • Altar
  • Prophet Tree
  • Tavern (tasks/contracts)
  • Wishing Fountain
  • Inventory / Bag
  • Monster system
  • Hero system
  • Event Raid
  • Mail system
  • Summon Circle
All of these are handled with proper server-side logic, not simple packet forwarding.

Planned / ongoing:​

  • Campaign (PvE progression)
  • Creation Circle
  • Celestial Island
  • Marketplace / Auction
  • Arena (ranked PvP)
  • Aspen Dungeon
  • Tower of Oblivion
  • Blacksmith
  • Skins / Cosmetics
  • Guilds

Under consideration:​

  • New event types
  • A revamped arena system with improved matchmaking and rewards
I’ll continue to share updates as development moves forward. Feedback and suggestions are always welcome.


Screenshot_167 - Idle Heroes Server Files Development - RaGEZONE Forums


Screenshot_164 - Idle Heroes Server Files Development - RaGEZONE Forums


Screenshot_165 - Idle Heroes Server Files Development - RaGEZONE Forums


Screenshot_166 - Idle Heroes Server Files Development - RaGEZONE Forums
 
Last edited:
I can offer my programming skills to help finish project as fast as possible. (For free ofc, i'm interested in this project as well) My Discord: sitxovski . Or if you are not interested i would ask you to share XOR+XXTea keys if you be so kind!

UPD: Found keys and studied encryption algorithm. Got decrypted files from latest version.
 
Last edited:
I can offer my programming skills to help finish project as fast as possible. (For free ofc, i'm interested in this project as well) My Discord: sitxovski . Or if you are not interested i would ask you to share XOR+XXTea keys if you be so kind!

UPD: Found keys and studied encryption algorithm. Got decrypted files from latest version.
You cannot decrypt the original apk, which is encrypted with a special opcode, only I can do this.
 
You cannot decrypt the original apk, which is encrypted with a special opcode, only I can do this.
Oh ,really? :))
1764857163358 - Idle Heroes Server Files Development - RaGEZONE Forums


You cannot decrypt the original apk, which is encrypted with a special opcode, only I can do this.
Nwm about i was being offering help to you. When work will be done - i'll upload all tools and files on GitHub

You cannot decrypt the original apk, which is encrypted with a special opcode, only I can do this.
It literally took me about 1 hour to understood how is encrypts and decrypts.
 
Last edited:
hi! looks great! which Lua decompiler did you use? I mean for already decrypted bytecode with fixed opcode order.

and can you share this only opcode-order-fixed file? looks like there are bugs in reordering I use… I resolved 1 issue with it (LEN "fixed" to CONCAT, but should be NEWTABLE), but looks like there are more. I'll finally fix it anyway, but you can save me time. I would be very grateful for your help, if possible
 
hi! looks great! which Lua decompiler did you use? I mean for already decrypted bytecode with fixed opcode order.

and can you share this only opcode-order-fixed file? looks like there are bugs in reordering I use… I resolved 1 issue with it (LEN "fixed" to CONCAT, but should be NEWTABLE), but looks like there are more. I'll finally fix it anyway, but you can save me time. I would be very grateful for your help, if possible
Hello, I have my own custom decompiler and I’m using that, so I don’t share it.
When the server development is finished, I might share the server here, but I’m not sure yet — I may also start my own server instead.

I decrypted the latest version successfully, but I’m working on an older version because the latest one is too large and complex for me.
 
Hello, I have my own custom decompiler and I’m using that, so I don’t share it.
When the server development is finished, I might share the server here, but I’m not sure yet — I may also start my own server instead.

I decrypted the latest version successfully, but I’m working on an older version because the latest one is too large and complex for me.
ok, I see. It seems I'll write my own too)) At least for app/config table data it's simple enough. It's even possible to write simplified Lua VM for that.
 
current live version of the game


# The final XOR key pattern
REALKEY = bytearray([
0x3C, 0xB5, 0x3C, 0x7F, 0x83, 0x94, 0xBA, 0x3B,
0x2B, 0xB2, 0x73, 0x5B, 0xEF, 0xEE, 0xE2, 0xA3,
0x3B, 0x2B, 0xCC, 0x66, 0x3D, 0xE5, 0x2C, 0xD7,
0x4D, 0x2E, 0x17, 0xE6, 0xF3
])


- `Magic_String`: A hardcoded string built into the client. For this version, it is: `rwmkxhgi6;578i650`
def get_password_hash(salt, password):
"""
Implements the legacy password hashing:
MD5(salt + "rwmkxhgi6;578i650" + MD5(password))
"""
if isinstance(salt, bytes):
salt = salt.decode('utf-8', errors='ignore')

md5_pass = hashlib.md5(password.encode('utf-8')).hexdigest()
final_str = f"{salt}rwmkxhgi6;578i650{md5_pass}"
return hashlib.md5(final_str.encode('utf-8')).hexdigest()

i also have all the game tables dumped via frida - source of truth for everything client side and the combat sequence 'streaming' working this includes animation keys for heroes etc.
 

Attachments

Last edited:
Hi, where can we get/download the files?
i attached two files this will allow you to decrypt the asssets or reencrypt them to patch things.

my patch server works by using this but for the assets pulled from the download server.

i dumped the tables using frida and the lua bytecode files (i think someone mentioned above)

the magic string is to have your client properly validate with a password against your private server


i have a fully working private server with combat and most of the main mechanics working and ive reversed and built a ton of the .proto files for my server - hero progression etc.

are you looking for other files or specific information regarding protocol etc? I can provide I just didn't want to drop everything (information overload) but i can pretty much answer 99.9% of the questions in regards or provide snippets, code, models.

I don't have the time to run a real private server but I'd like to give out the information so its not wasted in hopes someone might use it.
 
i attached two files this will allow you to decrypt the asssets or reencrypt them to patch things.

my patch server works by using this but for the assets pulled from the download server.

i dumped the tables using frida and the lua bytecode files (i think someone mentioned above)

the magic string is to have your client properly validate with a password against your private server


i have a fully working private server with combat and most of the main mechanics working and ive reversed and built a ton of the .proto files for my server - hero progression etc.

are you looking for other files or specific information regarding protocol etc? I can provide I just didn't want to drop everything (information overload) but i can pretty much answer 99.9% of the questions in regards or provide snippets, code, models.

I don't have the time to run a real private server but I'd like to give out the information so its not wasted in hopes someone might use it.
Thanks! I am actually looking for the server files, client files to test this out and hopefully can also submit fixes to the community.
 
here's some more details

use something like PCAPDroid port forward all trafic from the client on port 5000 to a server you setup to listen
this will allow you to take control or even 'proxy' the data (i also have a proxy)

***PATCH****
opcode
0x0303 will come in this is the version check

respond to the client with something like this (protbuf format)
GAME_VERSION = "1.34.194"
GAME_VERSION_HASH = "0bbb6144008245a59ffb2d530dbb6db3"
GAME_VERSION_UPDATE=" "


resp = {
"1": 0,
"2": GAME_VERSION,
"3": 0,
"4": GAME_VERSION_UPDATE,
"6": GAME_VERSION_HASH,
"7": 8,
"9": [GAME_VERSION_UPDATE, GAME_VERSION_UPDATE, GAME_VERSION_UPDATE]
}



have an http server running (your patch server) then you can use something like this to force the client to take your files

@app.route('/update/<file_hash>')
def serve_by_hash(file_hash):
global stats
stats["requests"] += 1
if file_hash in hash_to_path:
rel_path = hash_to_path[file_hash]
full_path = os.path.join(BASE_DIR, rel_path)

if os.path.exists(full_path):
stats["success"] += 1
print(f"[{stats['requests']}] Serving: {rel_path} (Hash: {file_hash})")
resp = send_file(full_path)
# Add Etag as seen in legacy code
resp.headers["Etag"] = "xxxxxxxxxx"
return resp
else:
stats["fail"] += 1
print(f"[{stats['requests']}] [!] File missing on disk: {rel_path}")
abort(404)
else:
# Ignore common browser noise
if file_hash not in ["favicon.ico", ""]:
stats["fail"] += 1
print(f"[{stats['requests']}] [-] Unknown Hash: {file_hash}")
abort(404)



the best way to get current files is (get the hash by proxying the data or some other way) then pull from one of the server (by hash) you also serve them by hash

MIRRORS = [
"...."
]



if you do this for example you could pull files, decrypt, modify, renecrypt (rebuild the manifest file) then your client would use your patched files

*the hash from the 0x0303 youll see is the current hash which is manifest.json and the mirrors are also there*


*FRIDA*
if you root your device for frida you'd want to do something like this
var libName = "libcocos2dlua.so";
function installHook() {
var module = Process.findModuleByName(libName);
if (module) {
console.log("[*] Found " + libName + ", injecting monster stream dumper...");
var ptr_pcall = module.findExportByName("lua_pcall") || module.findExportByName("lua_pcallk");
var ptr_tolstring = module.findExportByName("lua_tolstring");
var ptr_loadstring = module.findExportByName("luaL_loadstring");
if (ptr_loadstring && ptr_pcall && ptr_tolstring) {
var luaL_loadstring = new NativeFunction(ptr_loadstring, 'int', ['pointer', 'pointer']);
var lua_pcall = new NativeFunction(ptr_pcall, 'int', ['pointer', 'int', 'int', 'int']);
var lua_tolstring = new NativeFunction(ptr_tolstring, 'pointer', ['pointer', 'int', 'pointer']);


you can also force load alot of files if you have a list and dump the raw tables
achievement
activity
arena
arenabuff
arenareward
arenastore
birthday
blackmarket
booth
breaktree
brokenbossnew
buff
camp
celestialeffect
celestialgod
celestialskill
celestialtask
celestialway
challenge


python script combined with frida youd do something like this
try:
device = frida.get_usb_device()
print("[*] Spawning Idle Heroes...")
pid = device.spawn(["com.droidhang.ad"])
session = device.attach(pid)

with open("universal_dumper.js", "r") as f:
js_code = f.read()
script = session.create_script(js_code)
script.on('message', lambda m, d: on_message(m, d, script))
script.load()





****OTHER*****
if anyone has any questions I can provide snippets, help its alot of information.

but for example when the game loads you have diff sync packets for diff things - there is also the 0x0242 packet which is pretty massive with alot of information

here is a partial example of one of my implementations for my server

class Player(ProtoObject):
"""
Player Model (0x0242).
Clean, declarative mapping.
"""
status = IntField("1") # Usually 0
# Header Info (Nested in "2")
name = StringField("1", path=["2"])
avatar_id = IntField("2", path=["2"])
guild_id = IntField("3", path=["2"])
guild_name = StringField("4", path=["2"])
guild_unk = IntField("5", path=["2"])
guild_level = IntField("6", path=["2"])

# Items (Nested in "3")
items = ListField("1", ResourceItem, path=["3"])

# Equipment (Nested in "3")
#from models.hero import HeroEquipItem
equipment = ListField("2", ResourceItem, path=["3"])

# Stats/Properties & Activities (Key 16)
from models.common import ActivityItem
activities = ListField("16", ActivityItem)
# Monsters/Pets (Key 32, 33, 35)
active_monster_id = IntField("32")
from models.monster import MonsterItem
monsters = ListField("33", MonsterItem)
monster_resources = ListField("35", ResourceItem)
# Field 13 -> 1 is Friends list, 4 is Applications
friends = ListField("1", FriendItem, path=["13"])
friend_applications = ListField("4", FriendItem, path=["13"])

# Field 7 is Mail list (Root level)
mails = ListField("7", MailItem)
# Achievements (Field 17)
achievement_progress = ListField("1", int, path=["17"])
achievement_ids = ListField("2", int, path=["17"])
achievement_unknown = IntField("3", path=["17"])
# Global Challenges (Field 11)
global_challenges = ListField("11", int)

# Campaign (Field 9)
campaign = ObjectField("9", CampaignData)

# Daily Quests (Field 18)
from models.daily_quest import DailyQuest
daily_quests = ListField("18", DailyQuest)
# Daily Refresh Timer (Field 19)
daily_refresh_timer = IntField("19")
# Hand of Midas (Field 49)
from models.midas import MidasItem
midas_claims = ListField("49", MidasItem)
# Treasure Train (Field 66)
treasure_train = ListField("66", StatItem)
....
....

here is a python script download patch data based on hash - with this plus the other code i provided you can decrypt,reenrypt the files

here is a patch_tool script in python





***COMBAT****
here is the hardest part where everyone normally quits regarding the combat


this is a stream of bytes inside of the protobuf packet and looks like gibberish at first

these are 'key frames' or whatever and you have to build the combat seq almost perfect or the client will reject it.


the client itself doesn't use the heroid (actual heroid) it uses a unique id for the hero (in my case primary key in database so its always unique on client) and this is whats going to be used for most things like when you disassemble, use for food, start a battle


the round typically starts with
"b'010100'", #this would be round 1 it doens't matter if you put round 999 here or 1 whatever you use here is the round and can be injected again at any point

the other thing since combat is all server side what the client gets is basically keys on what to do for example
which animation to play, what buff icon to show, current health bar info, damage etc.
"b'01013000000743008009710000006446005700'",

then youll end up with some sort of what i call 'wrap it up'
"b'010110000004250000'"

this will trigger the 'victory/defeat screen'

I can share more information on it with exact byte to byte breakdown but figured I'd share this since its probably the hardest part and why most people will prob give on up on there private server.

*note the slot id for the target and the attacking hero are also part of these streams, including pet info, diff artifacts etc.*

combat_result = {
"1": 0,
"2": {
"1": combat_seq,
"2": win_outcome, # 1 = win, 0 = loss
"3": {
"1": rewards_formatted,
"2": {
"1": 14002,
"2": 1
}
},
"5": stats_list
}
}
return combat_result




for the animations for the heroes its based on spine so you have to use the right skillid etc. for them to actually seem like they are doing something

"65706": {
"hero_id": 65706,
"name": "Lord of Fear - Aspen",
"job": 1,
"group": 7,
"quality": 6,
"max_star": 6,
"base_stats": {
"hp": 14000,
"atk": 900,
"spd": 302,
"arm": 60
},
"skills": {
"active": {
"id": 1550,
"name": "Soul Shiver",
"description": "Active Skill: Deals (800% of Attack) Damage to a single enemy (prioritize the target in the same position) 2 times and inflicts Fear - Abyssal Gaze for 3 rounds. After the attack, if the target\u2019s HP is lower than 40%, deals (800% of Attack) Damage 2 times, and restores self HP equal to 100% of the damage dealt. This attack has a characteristic: Demon King's Might . If this attack kills the enemy, then deals extra (800% of Attack) Damage 2 times to all enemies. After this attack, deals extra (800% of Attack) Damage to enemies in the same position. \n\nDemon King's Might: \nAs the Lord of Fear, Aspen deals harsher damage to the enemies who dare to challenge his majesty. For Bosses who cannot be inflicted with Fear, Aspen deals extra 100% of the damage dealt by this skill to them and steals 25% of Attack from them. \nFear - Abyssal Gaze: \nControl effect: Fear. Heroes inflicted with Abyss Gaze are unable to launch their basic attacks and Crit.",
"visuals": {
"animation_id": 1550,
"sound": null,
"fx_main": [
"fx_103624",
"fx_103625"
],
"fx_hurt": []
},
 

Attachments

Last edited:
here's some more details

use something like PCAPDroid port forward all trafic from the client on port 5000 to a server you setup to listen
this will allow you to take control or even 'proxy' the data (i also have a proxy)

***PATCH****
opcode
0x0303 will come in this is the version check

respond to the client with something like this (protbuf format)
GAME_VERSION = "1.34.194"
GAME_VERSION_HASH = "0bbb6144008245a59ffb2d530dbb6db3"
GAME_VERSION_UPDATE=" "


resp = {
"1": 0,
"2": GAME_VERSION,
"3": 0,
"4": GAME_VERSION_UPDATE,
"6": GAME_VERSION_HASH,
"7": 8,
"9": [GAME_VERSION_UPDATE, GAME_VERSION_UPDATE, GAME_VERSION_UPDATE]
}



have an http server running (your patch server) then you can use something like this to force the client to take your files

@app.route('/update/<file_hash>')
def serve_by_hash(file_hash):
global stats
stats["requests"] += 1
if file_hash in hash_to_path:
rel_path = hash_to_path[file_hash]
full_path = os.path.join(BASE_DIR, rel_path)

if os.path.exists(full_path):
stats["success"] += 1
print(f"[{stats['requests']}] Serving: {rel_path} (Hash: {file_hash})")
resp = send_file(full_path)
# Add Etag as seen in legacy code
resp.headers["Etag"] = "xxxxxxxxxx"
return resp
else:
stats["fail"] += 1
print(f"[{stats['requests']}] [!] File missing on disk: {rel_path}")
abort(404)
else:
# Ignore common browser noise
if file_hash not in ["favicon.ico", ""]:
stats["fail"] += 1
print(f"[{stats['requests']}] [-] Unknown Hash: {file_hash}")
abort(404)



the best way to get current files is (get the hash by proxying the data or some other way) then pull from one of the server (by hash) you also serve them by hash

MIRRORS = [
"...."
]



if you do this for example you could pull files, decrypt, modify, renecrypt (rebuild the manifest file) then your client would use your patched files

*the hash from the 0x0303 youll see is the current hash which is manifest.json and the mirrors are also there*


*FRIDA*
if you root your device for frida you'd want to do something like this
var libName = "libcocos2dlua.so";
function installHook() {
var module = Process.findModuleByName(libName);
if (module) {
console.log("[*] Found " + libName + ", injecting monster stream dumper...");
var ptr_pcall = module.findExportByName("lua_pcall") || module.findExportByName("lua_pcallk");
var ptr_tolstring = module.findExportByName("lua_tolstring");
var ptr_loadstring = module.findExportByName("luaL_loadstring");
if (ptr_loadstring && ptr_pcall && ptr_tolstring) {
var luaL_loadstring = new NativeFunction(ptr_loadstring, 'int', ['pointer', 'pointer']);
var lua_pcall = new NativeFunction(ptr_pcall, 'int', ['pointer', 'int', 'int', 'int']);
var lua_tolstring = new NativeFunction(ptr_tolstring, 'pointer', ['pointer', 'int', 'pointer']);


you can also force load alot of files if you have a list and dump the raw tables
achievement
activity
arena
arenabuff
arenareward
arenastore
birthday
blackmarket
booth
breaktree
brokenbossnew
buff
camp
celestialeffect
celestialgod
celestialskill
celestialtask
celestialway
challenge


python script combined with frida youd do something like this
try:
device = frida.get_usb_device()
print("[*] Spawning Idle Heroes...")
pid = device.spawn(["com.droidhang.ad"])
session = device.attach(pid)

with open("universal_dumper.js", "r") as f:
js_code = f.read()
script = session.create_script(js_code)
script.on('message', lambda m, d: on_message(m, d, script))
script.load()





****OTHER*****
if anyone has any questions I can provide snippets, help its alot of information.

but for example when the game loads you have diff sync packets for diff things - there is also the 0x0242 packet which is pretty massive with alot of information

here is a partial example of one of my implementations for my server

class Player(ProtoObject):
"""
Player Model (0x0242).
Clean, declarative mapping.
"""
status = IntField("1") # Usually 0
# Header Info (Nested in "2")
name = StringField("1", path=["2"])
avatar_id = IntField("2", path=["2"])
guild_id = IntField("3", path=["2"])
guild_name = StringField("4", path=["2"])
guild_unk = IntField("5", path=["2"])
guild_level = IntField("6", path=["2"])

# Items (Nested in "3")
items = ListField("1", ResourceItem, path=["3"])

# Equipment (Nested in "3")
#from models.hero import HeroEquipItem
equipment = ListField("2", ResourceItem, path=["3"])

# Stats/Properties & Activities (Key 16)
from models.common import ActivityItem
activities = ListField("16", ActivityItem)
# Monsters/Pets (Key 32, 33, 35)
active_monster_id = IntField("32")
from models.monster import MonsterItem
monsters = ListField("33", MonsterItem)
monster_resources = ListField("35", ResourceItem)
# Field 13 -> 1 is Friends list, 4 is Applications
friends = ListField("1", FriendItem, path=["13"])
friend_applications = ListField("4", FriendItem, path=["13"])

# Field 7 is Mail list (Root level)
mails = ListField("7", MailItem)
# Achievements (Field 17)
achievement_progress = ListField("1", int, path=["17"])
achievement_ids = ListField("2", int, path=["17"])
achievement_unknown = IntField("3", path=["17"])
# Global Challenges (Field 11)
global_challenges = ListField("11", int)

# Campaign (Field 9)
campaign = ObjectField("9", CampaignData)

# Daily Quests (Field 18)
from models.daily_quest import DailyQuest
daily_quests = ListField("18", DailyQuest)
# Daily Refresh Timer (Field 19)
daily_refresh_timer = IntField("19")
# Hand of Midas (Field 49)
from models.midas import MidasItem
midas_claims = ListField("49", MidasItem)
# Treasure Train (Field 66)
treasure_train = ListField("66", StatItem)
....
....

here is a python script download patch data based on hash - with this plus the other code i provided you can decrypt,reenrypt the files

here is a patch_tool script in python





***COMBAT****
here is the hardest part where everyone normally quits regarding the combat


this is a stream of bytes inside of the protobuf packet and looks like gibberish at first

these are 'key frames' or whatever and you have to build the combat seq almost perfect or the client will reject it.


the client itself doesn't use the heroid (actual heroid) it uses a unique id for the hero (in my case primary key in database so its always unique on client) and this is whats going to be used for most things like when you disassemble, use for food, start a battle


the round typically starts with
"b'010100'", #this would be round 1 it doens't matter if you put round 999 here or 1 whatever you use here is the round and can be injected again at any point

the other thing since combat is all server side what the client gets is basically keys on what to do for example
which animation to play, what buff icon to show, current health bar info, damage etc.
"b'01013000000743008009710000006446005700'",

then youll end up with some sort of what i call 'wrap it up'
"b'010110000004250000'"

this will trigger the 'victory/defeat screen'

I can share more information on it with exact byte to byte breakdown but figured I'd share this since its probably the hardest part and why most people will prob give on up on there private server.

*note the slot id for the target and the attacking hero are also part of these streams, including pet info, diff artifacts etc.*

combat_result = {
"1": 0,
"2": {
"1": combat_seq,
"2": win_outcome, # 1 = win, 0 = loss
"3": {
"1": rewards_formatted,
"2": {
"1": 14002,
"2": 1
}
},
"5": stats_list
}
}
return combat_result




for the animations for the heroes its based on spine so you have to use the right skillid etc. for them to actually seem like they are doing something

"65706": {
"hero_id": 65706,
"name": "Lord of Fear - Aspen",
"job": 1,
"group": 7,
"quality": 6,
"max_star": 6,
"base_stats": {
"hp": 14000,
"atk": 900,
"spd": 302,
"arm": 60
},
"skills": {
"active": {
"id": 1550,
"name": "Soul Shiver",
"description": "Active Skill: Deals (800% of Attack) Damage to a single enemy (prioritize the target in the same position) 2 times and inflicts Fear - Abyssal Gaze for 3 rounds. After the attack, if the target\u2019s HP is lower than 40%, deals (800% of Attack) Damage 2 times, and restores self HP equal to 100% of the damage dealt. This attack has a characteristic: Demon King's Might . If this attack kills the enemy, then deals extra (800% of Attack) Damage 2 times to all enemies. After this attack, deals extra (800% of Attack) Damage to enemies in the same position. \n\nDemon King's Might: \nAs the Lord of Fear, Aspen deals harsher damage to the enemies who dare to challenge his majesty. For Bosses who cannot be inflicted with Fear, Aspen deals extra 100% of the damage dealt by this skill to them and steals 25% of Attack from them. \nFear - Abyssal Gaze: \nControl effect: Fear. Heroes inflicted with Abyss Gaze are unable to launch their basic attacks and Crit.",
"visuals": {
"animation_id": 1550,
"sound": null,
"fx_main": [
"fx_103624",
"fx_103625"
],
"fx_hurt": []
},
Why bother with all this trouble? The downloaded resources are already located in the root directory of your rooted device, so you can get them from there.
 
Why bother with all this trouble? The downloaded resources are already located in the root directory of your rooted device, so you can get them from there.
he describes uploading to device, not downloading ;)

# Header Info (Nested in "2")
name = StringField("1", path=["2"])
avatar_id = IntField("2", path=["2"])
guild_id = IntField("3", path=["2"])
guild_name = StringField("4", path=["2"])
guild_unk = IntField("5", path=["2"])
random-times really great job!
but seems you didn't restore protobuf names. above you describe "player" message with the following fields (original names):
Code:
"name",
"logo",
"gid",
"gname",
"border", # not related to guild. hello DH :)
"glv",
"country",
"sds",
"city"

can you share more info about combat_seq structure to pack/unpack it? as you said, it's not the easiest part, so I didn't start researching it yet. I would be grateful for any help.
also looking for official manifests (GAME_VERSION_HASH) for older versions. I want to do some historical research, but only have 10 or so newest hashes
 
Last edited:
he describes uploading to device, not downloading ;)


random-times really great job!
but seems you didn't restore protobuf names. above you describe "player" message with the following fields (original names):
Code:
"name",
"logo",
"gid",
"gname",
"border", # not related to guild. hello DH :)
"glv",
"country",
"sds",
"city"

can you share more info about combat_seq structure to pack/unpack it? as you said, it's not the easiest part, so I didn't start researching it yet. I would be grateful for any help.
also looking for official manifests (GAME_VERSION_HASH) for older versions. I want to do some historical research, but only have 10 or so newest hashes
yep good catch there's alot of weirdness like border then you think frame, avatar etc.


so the protobuf models I've created aren't 1:1 from the original client I did alot of trial and error and building them and in some cases used diff names.
I did extract the fields and such from the client with the real names (some of them dont make sense until you test it) - below (PbrspSync)

I have some things in my private server that just simply need renaming or dynamically driven for example - you can see here where im just hard coding the guild level to 35 and you can see i have .unk
def _handle_player_info(player, client_state):
"""Dynamic player info from DB (Key 2)."""
import database
conn = database.get_db()
cursor = conn.cursor()
test_account = game_state.TARGET_ACCOUNT
target_server = game_state.TARGET_SERVER
cursor.execute("""
SELECT p.*, g.name as guild_name
FROM players p
LEFT JOIN guilds g ON p.guild_id = g.id
WHERE p.account_id = ? AND p.server_id = ?
""", (test_account, target_server))
p_row = cursor.fetchone()
if p_row:
player.name = p_row['name']
player.avatar_id = p_row['avatar_id']
player.guild_id = p_row['guild_id']
player.guild_name = p_row['guild_name'] or ""
player.guild_unk = 51257
player.guild_level = 35
print(f"[PlayerSync] Loaded Player: {player.name}")
conn.close()




I have a simple database setup with diff servers that can be added, then you can have diff accounts the normal type of things. then for things such as awakens, enables etc. i store in json format in database.


the codes in a good state but def could use some love far as making things make more sense etc. my way of testing/validating is a bit of a circles in some cases.


the goal was to 100% control the client without touching the original APK, snapshot a patch version, patch client using the patch server with some files modified etc. like i removed some GUI elements alot of the (subscription UI and such) i dumbed down some of the UI's to remove some added features etc.



1.34.69 i believe (can't remember) is my original snap shot i used for the client - i did this just encase some big update comes in but as of now things still work with the newer client files and all the patching etc works (just securing a version that will always work so I do update the snapshots for newer ones after testing)


base version 1.34.0 or w/e i can't remember then we apply what patches we want via our patch server (in a real private server people wouldn't need to redownload an apk every update since its essentially the same as what the client does)


# --- PbrspSync ---
PbrspSync._OPCODES = ['0x242']
PbrspSync.hero_ids = ObjectField("6", PbMail) #list of heroes that youve seen/collected
PbrspSync.mails = ObjectField("7", PbMail)
PbrspSync.hook = ObjectField("8", PbHook)
PbrspSync.midas_cd = ObjectField("8", PbHook)
PbrspSync.bag = ObjectField("10", PbBag)
PbrspSync.trial = ObjectField("14", PbStrial) #tower 650 floors set to 651 opens tower of dream
PbrspSync.alogin = ObjectField("15", PbAlogin) #daily check in
PbrspSync.acts = ObjectField("16", PbAct)
PbrspSync.tasks = ObjectField("18", PbTask)
PbrspSync.task_cd = ObjectField("19", PbOnline)
PbrspSync.online = ObjectField("20", PbOnline)
PbrspSync.midas_flag = ObjectField("21", Any)
PbrspSync.web_flag = ObjectField("22", Any)
PbrspSync.video_ad = ObjectField("23", Any)
PbrspSync.limitacts = ObjectField("24", PbHtaskSync)
PbrspSync.htask = ObjectField("26", PbHtaskSync)
PbrspSync.buy_hlimit = ObjectField("27", Any)
PbrspSync.space_gacha = ObjectField("28", Any)
PbrspSync.cds = ObjectField("29", PbCd)
PbrspSync.final_rank = ObjectField("30", Any)
PbrspSync.hide_vip = ObjectField("31", Any)
PbrspSync.tutorial2 = ObjectField("32", PbPet)
PbrspSync.pets = ObjectField("33", PbPet)
PbrspSync.reddot = ObjectField("34", PbGskl)
PbrspSync.gskls = ObjectField("35", PbGskl)
PbrspSync.subscribed = ObjectField("36", PbItem)
PbrspSync.skin = ObjectField("37", PbItem)
PbrspSync.gsklcode = ObjectField("38", Any)
PbrspSync.chatblocks = ObjectField("39", PbLocale)
PbrspSync.locale = ObjectField("40", PbLocale)
PbrspSync.mact = ObjectField("41", PbMact)
PbrspSync.audit = ObjectField("42", PbSact)
PbrspSync.sact = ObjectField("43", PbSact)
PbrspSync.off_card = ObjectField("44", Any)
PbrspSync.video_cd = ObjectField("45", PbRact)
PbrspSync.ract = ObjectField("46", PbRact)
PbrspSync.use_hitem = ObjectField("47", PbReSync)
PbrspSync.re_sync = ObjectField("48", PbReSync)
PbrspSync.new_midas = ObjectField("49", PbMidas) #new midas midas new its new ok
PbrspSync.stele = ObjectField("50", PbStele)
PbrspSync.token = ObjectField("51", Any)
PbrspSync.stower_lv = ObjectField("52", Any)
PbrspSync.spet_ids = ObjectField("53", Any)
PbrspSync.skin_ids = ObjectField("54", PbHomeHeroes)
PbrspSync.home_heroes = ObjectField("55", PbHomeHeroes)
PbrspSync.coll_lv = ObjectField("56", Any)
PbrspSync.like = ObjectField("57", Any)
PbrspSync.care = ObjectField("58", Any)
PbrspSync.hteam_bag = ObjectField("59", Any)
PbrspSync.qlt_tasks = ObjectField("60", PbGiveOrder)
PbrspSync.give = ObjectField("61", PbGiveOrder)
PbrspSync.qscore = ObjectField("62", Any)
PbrspSync.mall_pwd = ObjectField("63", Any)
PbrspSync.qlt_pvp_market = ObjectField("64", Any)
PbrspSync.stower_lucky = ObjectField("65", Any)
PbrspSync.coll = ObjectField("66", PbKv)
PbrspSync.brave = ObjectField("67", Any)
PbrspSync.transform_num = ObjectField("68", Any)
PbrspSync.transform_choose = ObjectField("69", Any)
PbrspSync.god_lv = ObjectField("70", Any) #transition temple level
PbrspSync.energy = ObjectField("71", Any) #soul power
PbrspSync.gt_over = ObjectField("72", PbCele)
PbrspSync.cele = ObjectField("73", PbCele)
PbrspSync.ret = ObjectField("74", PbRet)
PbrspSync.tiro = ObjectField("75", PbRecommend)
PbrspSync.recommend = ObjectField("76", PbRecommend) #collabrative journey code
PbrspSync.seq = ObjectField("77", Any)
PbrspSync.achieve = ObjectField("Auto", Any)
PbrspSync.friends = ObjectField("Auto", Any)
PbrspSync.gacha = ObjectField("Auto", Any)
PbrspSync.midas_crstcd = ObjectField("Auto", Any)
PbrspSync.pay_num = ObjectField("Auto", PbFriend)
PbrspSync.player = ObjectField("Auto", Any)
PbrspSync.status = ObjectField("Auto", Any)
PbrspSync.tutorial = ObjectField("Auto", Any)



***COMBAT***
you're right I think this is usually the make or break when it comes to a private server since you really are just streaming a sequence of events and have to recreate all the server side combat logic (who goes first, who does what, what skill does damage wise, what buff it applies etc) alot of this is in the client but obviously the client only needs to know so much so you really have to recreate/balance things in sort of a way that makes sense server side and tell the client.

i have a full round by round combat system going its using the right animations for the hero abilities and such

the key insights here and you might have already seen some - "1" is the combat sequence something like this you'd generate
"b'010100'",
"b'01011000000bc2008009810000006f4f005700'",
"b'01015000000bde008009810000005f3d005700'",
"b'01012000000bc1008009810000003d31005700'",
"b'0101a000000bc5000109110000001761005700'",
"b'0101b000000bc500010911000000175f005700'",
"b'0101c000000bc500010911000000175d005700'",
"b'01013000000bd0008009810000002f28005700'",
"b'01014000000bc9008009810000001624005700'",
"b'0101800000040100010911000000135b0057000411002c00'",
"b'01016000000bd400800981000000271d005700'",

what ^ this is deserves a huge post in itself and I'll try to document it but for example the first thing here is saying 'what round'
then you go into diff things like 'what slot' is attacking, what slot is the target, current health information, animation key being used (skill), buffs etc..white damage, crit etc. its alot packed into the sequence (why we notice sometimes when we battle and it runs for alot of rounds or a long time the client seems to be stuck at waiting to enter the battle) - alot of this is probably obvious

since its all predetermined by the time you hit 'battle' the clients just replaying what I'm doing is simulating the entire sequene then shooting out the results to the client and it plays it back.


one important thing the client is very sensitive if not formatted right it will either 1. freeze the client, 2. enter combat and nothing happens you have to have the order of things the right slots etc. perfect - if client says hero 1 attack enemey 4 and enemey 4 has died 'on client' they just sit there idle.

i got a list of every stage and which heroes (enemies) are on those stages and what slots they take so things like above never happen so we always know the state of whos alive, who isnt, what slots are filled like if you do a. hero 1 vs 4 or hero 6 vs 2 your combat seq ends up not doing anything weird.

pet info also is included in there (which pet is used or monster whatever they call it)



you can't from the protocol tell the client you want to fight lineup valk, valk, valk, valk its all in the stage info i extracted (we could patch those files with my patching and then dictate whatever then the client will use the lineups we set)

not the hero id but our 'client id' for the hero is used when we set a team and hit 'battle' and thats whats referenced - to render in the hero etc.

you can use other hero skills on the wrong hero but you end up with spine diff so you never see animations - so i mapped all the hero ids to proper skills and such so you get the 'idle heroes like' experience like LFA throwing presents on the enemies head and such.

I apologize if I'm being a little vague with this but it's pretty complex and I need to document it better because its alot to digest so hopefully I answered some of the question but I'm def. willing to share more information and will provide something more formal when I get time to truly document it.


I need to cleanup some of my code
from battles import battle_builder that generates the combat and I'm willing to share that so it will make more sense.

I quit working on this project for now and would like to release all the source since its I'd say fully working in the sense you can progress a hero, pull heroes all the fun stuff and even battle through states and such (big thing here all the fancy if a hero does this it does that aren't implemented) its more generic for example a big animation from a hero might just do damage (aoe or to a single target - none of the convoluted crazy DH games lingo has been applied or things like this should 'reduce the enemies speed etc' thats all trival and part of the server side combat system that would need to be built but would not change much if anything on my battle builder)






def handle_battle_result(combat_seq, reward_list=None, stats_list=None, win_outcome=1):
rewards_formatted = []
# ONLY send rewards if the player actually won
if reward_list and win_outcome == 1:
for r in reward_list:
rewards_formatted.append({
"1": r.get("id"),
"2": r.get("num")
})

combat_result = {
"1": 0,
"2": {
"1": combat_seq,
"2": win_outcome, # 1 = win, 0 = loss
"3": {
"1": rewards_formatted, #right now eqipment reward wont work fi at later time
"2": {
"1": 14002,
"2": 1
}
},
"5": stats_list
}
}
return combat_result

Why bother with all this trouble? The downloaded resources are already located in the root directory of your rooted device, so you can get them from there.
I'm willing to share any information if you have question's but It looks like you might be thinking about this entire thing the wrong way and asking the wrong questions here.

here is a list of what i have implemented

*ability to patch files dictate what client is using*
*100% free from DH games servers*
*alot of subscription based UI and elements removed*
*ability to post custom events what i mean by custom is change the events preview - custom defined by whatever that would be maybe a kill 500 aspen event*

1. full login for user/email password (uses the hash key i posted to validate)
2. dynamic server list with flags for new etc. you can have one account with many servers
3. full sync on initial load to server things like is void open, what guild youre in, your level, hero bag, items etc.
4. glory challenge, treasure train, daily check in, daily quest, quest, star spawns other misc info that you get when logging in
5. celestial island (place holder template when loading)
6. cloud island - ability to unlock plots, add houses, upgrade houses, add heroes to houses (this includes updating them to also show they are home owners, tenants), ability to 'decorate' pretty much fully implemented.
7. campaign, void campaign battles (real lineups, real rewards)
8. battle as described above
9. fortune ruins altar, creation circle
10. hero upgraes can go from 1 to E5 with tree of origin full and core (no destiny)
11. ability to transcend a hero
12. summon circle, compass of transcended summon
13. marketplace
14. soul temple awakens
15. basic guild functions guild list, guild altar, tech
16. mail, friends list, chat
17....ton of other misc things that are either fully dynamic, implemented or place holders/figured out work

i gave up trying to mention everything after i realized the list would take forever but should be an idea on what I mean by pretty much fully working I'd call it an early alpha but playable.

most of everything is data driven this includes things like cloud island etc. so it persist.


*note*
i do not plan on starting a private server/hosting one or a community around it this has all been done as a side hobby project. I'am willing and want to share all the information I can in hopes that someone else might start a private server that has more time then me
 
Last edited:
Back