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