骑马与砍杀中文站论坛

 找回密码
 注册(Register!)

QQ登录

只需一步,快速开始

搜索
购买CDKEY 衣谷三国
查看: 220|回复: 1

[经验与教程] 骑砍1成就系统逆向研究

[复制链接]

0

主题

10

回帖

3

积分

平民

Rank: 1

UID
3035263
第纳尔
69
精华
0
互助
0
荣誉
0
贡献
0
魅力
0
注册时间
2018-10-13
鲜花(0) 鸡蛋(0)
发表于 3 天前 | 显示全部楼层 |阅读模式
本帖最后由 Glacier 于 2025-10-6 18:02 编辑

本帖原本在我的个人博客,在原帖上面观看体验更佳
https://flacier.us.kg/骑砍1成就系统逆向研究

问题描述
Mount & Blade: Warband(骑马与砍杀:战团)的macOS版本自 2014 年上线起便无法解锁任意 Steam 成就,而 Windows 版一切正常。多年来社区一直都有人在说无法正常解锁成就,但并没有任何实际有效解决方案,基本上已经可以判断为就是macOS版本本身不支持,但我还是想深入研究一下根本原因

相关讨论
Not Getting Achievements MacOs | TaleWorlds Forums
Achievements not working [Mac] :: Mount & Blade: Warband Help and Support
I am getting no Steam achievements on MacOS : r/mountandblade
Achievements not being unlocked :: Mount & Blade: Warband Help and Support

问题分析
首先我在创意工坊订阅Achievement grab这个能够解锁所有成就的剧本,在macOS版打开后仍然没有解锁成就,这即可说明就是macOS版本本身的问题

打开macOS版本的 Mount and Blade.app 里面的 Info.plist

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  3. <plist version="1.0">
  4. <dict>
  5. <key>BuildMachineOSBuild</key>
  6. <string>17G65</string>
  7. <key>CFBundleDevelopmentRegion</key>
  8. <string>en</string>
  9. <key>CFBundleExecutable</key>
  10. <string>Mount and Blade</string>
  11. <key>CFBundleIconFile</key>
  12. <string>[mbwarband]</string>
  13. <key>CFBundleIdentifier</key>
  14. <string>taleworlds.Mount-and-Blade</string>
  15. <key>CFBundleInfoDictionaryVersion</key>
  16. <string>6.0</string>
  17. <key>CFBundleName</key>
  18. <string>Mount and Blade</string>
  19. <key>CFBundlePackageType</key>
  20. <string>APPL</string>
  21. <key>CFBundleShortVersionString</key>
  22. <string>1.0</string>
  23. <key>CFBundleSignature</key>
  24. <string>????</string>
  25. <key>CFBundleSupportedPlatforms</key>
  26. <array>
  27.   <string>MacOSX</string>
  28. </array>
  29. <key>CFBundleVersion</key>
  30. <string>1</string>
  31. <key>DTCompiler</key>
  32. <string>com.apple.compilers.llvm.clang.1_0</string>
  33. <key>DTPlatformBuild</key>
  34. <string>9F2000</string>
  35. <key>DTPlatformVersion</key>
  36. <string>GM</string>
  37. <key>DTSDKBuild</key>
  38. <string>17E189</string>
  39. <key>DTSDKName</key>
  40. <string>macosx10.13</string>
  41. <key>DTXcode</key>
  42. <string>0941</string>
  43. <key>DTXcodeBuild</key>
  44. <string>9F2000</string>
  45. <key>LSApplicationCategoryType</key>
  46. <string>public.app-category.role-playing-games</string>
  47. <key>LSMinimumSystemVersion</key>
  48. <string>10.9</string>
  49. <key>NSHumanReadableCopyright</key>
  50. <string>Copyright © 2014 TaleWorlds Entertainment. All rights reserved.</string>
  51. <key>NSMainNibFile</key>
  52. <string>MainMenu</string>
  53. <key>NSPrincipalClass</key>
  54. <string>NSApplication</string>
  55. <key>UIDeviceFamily</key>
  56. <array>
  57.   <integer>1</integer>
  58.   <integer>2</integer>
  59. </array>
  60. </dict>
  61. </plist>
复制代码


BuildMachineOSBuild=17G65(macOS 10.13.6 High Sierra),DTPlatformBuild=9F2000DTCompiler=com.apple.compilers.llvm.clang.1_0指向 Xcode 9.4 时代的 Clang 工具链,时间差不多是2018年左右

从SteamDB上其实也能看到最近两次的更新,最近一次是2023年,但内容都是DLC相关的,再上一次就是2018年,由此可见后面基本上也是不可能再去管了

steamdb

steamdb


操作码分析
通过创意工坊的Achievement grab剧本,可以直接定位到战团脚本引擎用于解锁成就的内部指令:操作码 372

menus.txt 中可以看到连续的调用样例:
  1. 372 1 1
  2. 372 1 2
  3. ...
  4. 372 1 80
复制代码


由此可以得出:
  • 372 是用于成就的脚本操作码
  • 形如 372 1 N:中间的 1 表示参数个数,N=1..80 为成就数字 ID(枚举见 module_constants.py
  • 引擎将数字 ID 映射为字符串成就 ID(如 HOLY_DIVERGET_UP_STAND_UP 等),随后调用 Steam 成就接口解锁并 StoreStats 持久化


在官方 Module System 1.171 源码中也有明确定义:

header_operations.py

  1. get_achievement_stat = 370 # (get_achievement_stat, <destination>, <achievement_id>, <stat_index>),
  2. set_achievement_stat = 371 # (set_achievement_stat, <achievement_id>, <stat_index>, <value>),
  3. unlock_achievement   = 372 # (unlock_achievement, <achievement_id>),
复制代码


由此可知 372 即为解锁成就的操作码,1 后面的数字为achievement_id

module_constants.py(将成就定义为 1..80 的枚举)
  1. # NORMAL ACHIEVEMENTS

  2. ACHIEVEMENT_NONE_SHALL_PASS = 1,
  3. ACHIEVEMENT_MAN_EATER = 2,
  4. ACHIEVEMENT_THE_HOLY_HAND_GRENADE = 3,
  5. ACHIEVEMENT_LOOK_AT_THE_BONES = 4,
  6. ACHIEVEMENT_KHAAAN = 5,
  7. ACHIEVEMENT_GET_UP_STAND_UP = 6,
  8. ACHIEVEMENT_BARON_GOT_BACK = 7,
  9. ACHIEVEMENT_BEST_SERVED_COLD = 8,
  10. ACHIEVEMENT_TRICK_SHOT = 9,
  11. ACHIEVEMENT_GAMBIT = 10,
  12. ACHIEVEMENT_OLD_SCHOOL_SNIPER = 11,
  13. ACHIEVEMENT_CALRADIAN_ARMY_KNIFE = 12,
  14. ACHIEVEMENT_MOUNTAIN_BLADE = 13,
  15. ACHIEVEMENT_HOLY_DIVER = 14,
  16. ACHIEVEMENT_FORCE_OF_NATURE = 15,

  17. # SKILL RELATED ACHIEVEMENTS

  18. ACHIEVEMENT_BRING_OUT_YOUR_DEAD = 16,
  19. ACHIEVEMENT_MIGHT_MAKES_RIGHT = 17,
  20. ACHIEVEMENT_COMMUNITY_SERVICE = 18,
  21. ACHIEVEMENT_AGILE_WARRIOR = 19,
  22. ACHIEVEMENT_MELEE_MASTER = 20,
  23. ACHIEVEMENT_DEXTEROUS_DASTARD = 21,
  24. ACHIEVEMENT_MIND_ON_THE_MONEY = 22,
  25. ACHIEVEMENT_ART_OF_WAR = 23,
  26. ACHIEVEMENT_THE_RANGER = 24,
  27. ACHIEVEMENT_TROJAN_BUNNY_MAKER = 25,

  28. # MAP RELATED ACHIEVEMENTS

  29. ACHIEVEMENT_MIGRATING_COCONUTS = 26,
  30. ACHIEVEMENT_HELP_HELP_IM_BEING_REPRESSED = 27,
  31. ACHIEVEMENT_SARRANIDIAN_NIGHTS = 28,
  32. ACHIEVEMENT_OLD_DIRTY_SCOUNDREL = 29,
  33. ACHIEVEMENT_THE_BANDIT = 30,
  34. ACHIEVEMENT_GOT_MILK = 31,
  35. ACHIEVEMENT_SOLD_INTO_SLAVERY = 32,
  36. ACHIEVEMENT_MEDIEVAL_TIMES = 33,
  37. ACHIEVEMENT_GOOD_SAMARITAN = 34,
  38. ACHIEVEMENT_MORALE_LEADER = 35,
  39. ACHIEVEMENT_ABUNDANT_FEAST = 36,
  40. ACHIEVEMENT_BOOK_WORM = 37,
  41. ACHIEVEMENT_ROMANTIC_WARRIOR = 38,

  42. # POLITICALLY ORIENTED ACHIEVEMENTS

  43. ACHIEVEMENT_HAPPILY_EVER_AFTER = 39,
  44. ACHIEVEMENT_HEART_BREAKER = 40,
  45. ACHIEVEMENT_AUTONOMOUS_COLLECTIVE = 41,
  46. ACHIEVEMENT_I_DUB_THEE = 42,
  47. ACHIEVEMENT_SASSY = 43,
  48. ACHIEVEMENT_THE_GOLDEN_THRONE = 44,
  49. ACHIEVEMENT_KNIGHTS_OF_THE_ROUND = 45,
  50. ACHIEVEMENT_TALKING_HELPS = 46,
  51. ACHIEVEMENT_KINGMAKER = 47,
  52. ACHIEVEMENT_PUGNACIOUS_D = 48,
  53. ACHIEVEMENT_GOLD_FARMER = 49,
  54. ACHIEVEMENT_ROYALITY_PAYMENT = 50,
  55. ACHIEVEMENT_MEDIEVAL_EMLAK = 51,
  56. ACHIEVEMENT_CALRADIAN_TEA_PARTY = 52,
  57. ACHIEVEMENT_MANIFEST_DESTINY = 53,
  58. ACHIEVEMENT_CONCILIO_CALRADI = 54,
  59. ACHIEVEMENT_VICTUM_SEQUENS = 55,

  60. # MULTIPLAYER ACHIEVEMENTS

  61. ACHIEVEMENT_THIS_IS_OUR_LAND = 56,
  62. ACHIEVEMENT_SPOIL_THE_CHARGE = 57,
  63. ACHIEVEMENT_HARASSING_HORSEMAN = 58,
  64. ACHIEVEMENT_THROWING_STAR = 59,
  65. ACHIEVEMENT_SHISH_KEBAB = 60,
  66. ACHIEVEMENT_RUIN_THE_RAID = 61,
  67. ACHIEVEMENT_LAST_MAN_STANDING = 62,
  68. ACHIEVEMENT_EVERY_BREATH_YOU_TAKE = 63,
  69. ACHIEVEMENT_CHOPPY_CHOP_CHOP = 64,
  70. ACHIEVEMENT_MACE_IN_YER_FACE = 65,
  71. ACHIEVEMENT_THE_HUSCARL = 66,
  72. ACHIEVEMENT_GLORIOUS_MOTHER_FACTION = 67,
  73. ACHIEVEMENT_ELITE_WARRIOR = 68,

  74. # COMBINED ACHIEVEMENTS

  75. ACHIEVEMENT_SON_OF_ODIN = 69,
  76. ACHIEVEMENT_KING_ARTHUR = 70,
  77. ACHIEVEMENT_KASSAI_MASTER = 71,
  78. ACHIEVEMENT_IRON_BEAR = 72,
  79. ACHIEVEMENT_LEGENDARY_RASTAM = 73,
  80. ACHIEVEMENT_SVAROG_THE_MIGHTY = 74,

  81. ACHIEVEMENT_MAN_HANDLER = 75,
  82. ACHIEVEMENT_GIRL_POWER = 76,
  83. ACHIEVEMENT_QUEEN = 77,
  84. ACHIEVEMENT_EMPRESS = 78,
  85. ACHIEVEMENT_TALK_OF_THE_TOWN = 79,
  86. ACHIEVEMENT_LADY_OF_THE_LAKE = 80,
复制代码


触发器代码

module_game_menus.py
  1. (unlock_achievement, ACHIEVEMENT_LOOK_AT_THE_BONES)
  2. (set_achievement_stat, ACHIEVEMENT_THE_BANDIT, 1, ":number_of_caravan_raids")
  3. (get_achievement_stat, ":v", ACHIEVEMENT_BEST_SERVED_COLD, 0)
复制代码


菜单事件触发即刻执行
第一行解锁一次性成就
第二行把本地变量:number_of_caravan_raids写入成就THE_BANDIT的统计项index=1
第三行把成就BEST_SERVED_COLD的index=0进度读入变量:v,便于后续判断或展示

module_simple_triggers.py
  1. (unlock_achievement, ACHIEVEMENT_ABUNDANTFEAST)
复制代码


simple_trigger用于周期/条件性的集中检查,条件满足时统一解锁,避免在多处逻辑里重复判断与调用

module_scripts.py
  1. (get_achievement_stat, ":s", ACHIEVEMENT_SHISH_KEBAB, 0)
  2. (val_add, ":s", 1)
  3. (set_achievement_stat, ACHIEVEMENT_SHISH_KEBAB, 0, ":s")
  4. (unlock_achievement, ACHIEVEMENT_SHISH_KEBAB)
复制代码


成就进度更新,先读当前进度→累加→回写
达到阈值时再解锁

通过IDA也能找到 Windows 版本的导入与关键函数地址,作为上述机制的技术依据

Windows 版本数据(mb_warband.exe)
符号地址
SteamAPI_Init0x7be60c
SteamUserStats0x7be5f0
SteamUser0x7be624
SteamAPI_RunCallbacks0x7be608
SteamAPI_Shutdown0x7be618
SteamAPI_RestartAppIfNecessary0x7be61c
SteamClient0x7be610
SteamUtils0x7be5f8
SteamAPI_RegisterCallback0x7be5fc
SteamAPI_RegisterCallResult0x7be600
SteamAPI_UnregisterCallback0x7be5f4
SteamAPI_UnregisterCallResult0x7be628
SteamGameServer_Init0x7be5ec
SteamGameServer_RunCallbacks0x7be614
SteamGameServer_Shutdown0x7be620
SteamUGC0x7be630


成就系统关键函数
功能地址
成就管理器构造函数0x49BF40
获取用户统计0x49B410
成就解锁回调0x49B4B0
接收Steam统计0x49BCE0


成就相关字符串
字符串地址
"Achievement '%s' unlocked!"0x80e66c
"Achievement '%s' progress callback"0x80e63c
"Received stats and achievements from Steam"0x80e6e4


成就ID字符串地址
成就ID[/td[td]地址
GET_UP_STAND_UP0x80ebf8
TALKING_HELPS0x80e944
GOOD_SAMARITAN0x80ea10
HELP_HELP_IM_BEING_REPRESSED0x80ea84
COMMUNITY_SERVICE0x80eb2c
HOLY_DIVER0x80eb78
FORCE_OF_NATURE0x80eb68
MAN_EATER0x80ec3c
BARON_GOT_BACK0x80ebe8
BEST_SERVED_COLD0x80ebd4
MOUNTAIN_BLADE0x80eb84
THE_HOLY_HAND_GRENADE0x80ec24
CALRADIAN_ARMY_KNIFE0x80eb94
KHAAAN0x80ec08
GAMBIT0x80ebc0
OLD_SCHOOL_SNIPER0x80ebac
TRICK_SHOT0x80ebc8
DEXTEROUS_DASTARD0x80eaf8
MELEE_MASTER0x80eb0c
MIGHT_MAKES_RIGHT0x80eb40
AGILE_WARRIOR0x80eb1c
ART_OF_WAR0x80ead8
MIND_ON_THE_MONEY0x80eae4
THE_RANGER0x80eacc
BRING_OUT_YOUR_DEAD0x80eb54
TROJAN_BUNNY_MAKER0x80eab8
HAPPILY_EVER_AFTER0x80e9bc
HEART_BREAKER0x80e9ac
PUGNACIOUS_D0x80e928
OLD_DIRTY_SCOUNDREL0x80ea5c
I_DUB_THEE0x80e988
ROMANTIC_WARRIOR0x80e9d0
MORALE_LEADER0x80ea00
MIGRATING_COCONUTS0x80eaa4
SARRANIDIAN_NIGHTS0x80ea70
ABUNDANT_FEAST0x80e9f0
BOOK_WORM0x80e9e4
MEDIEVAL_TIMES0x80ea20
LOOK_AT_THE_BONES0x80ec10
GOLD_FARMER0x80e91c
SOLD_INTO_SLAVERY0x80ea30
THE_BANDIT0x80ea50
GOT_MILK0x80ea44
ROYALITY_PAYMENT0x80e908
MEDIEVAL_EMLAK0x80e8f8
NONE_SHALL_PASS0x80ec48
AUTONOMOUS_COLLECTIVE0x80e994
CALRADIAN_TEA_PARTY0x80e8e4
CONCILIO_CALRADI0x80e8bc
KINGMAKER0x80e938
MANIFEST_DESTINY0x80e8d0
THE_GOLDEN_THRONE0x80e96c
VICTUM_SEQUENS0x80e8ac
TALK_OF_THE_TOWN0x80e724
MAN_HANDLER0x80e754
SASSY0x80e980
QUEEN0x80e740
GIRL_POWER0x80e748
LADY_OF_THE_LAKE0x80e710
EMPRESS0x80e738
KNIGHTS_OF_THE_ROUND0x80e954
CHOPPY_CHOP_CHOP0x80e804
MACE_IN_YER_FACE0x80e7f0
THE_HUSCARL0x80e7e4
THROWING_STAR0x80e860
SPOIL_THE_CHARGE0x80e884
SHISH_KEBAB0x80e854
HARASSING_HORSEMAN0x80e870
EVERY_BREATH_YOU_TAKE0x80e818
ELITE_WARRIOR0x80e7bc
GLORIOUS_MOTHER_FACTION0x80e7cc
LAST_MAN_STANDING0x80e830
THIS_IS_OUR_LAND0x80e898
RUIN_THE_RAID0x80e844
SON_OF_ODIN0x80e7b0
IRON_BEAR0x80e788
KASSAI_MASTER0x80e794
SVAROG_THE_MIGHTY0x80e760
KING_ARTHUR0x80e7a4
LEGENDARY_RASTAM0x80e774


macOS 版本
实测 macOS 主程序(Mount and Blade.app/Contents/MacOS/Mount and Blade,x86_64,基址 0x100000000)在 otool -L 中确实列出了 @rpath/libsteam_api.dylib,bundle 内也能在 Contents/Resources/ 找到同名动态库libsteam_api.dylib

但用 IDA 检查导入表与字符串后可以确认,主程序没有任意 SteamAPI_*ISteamUserStats 等符号引用,也搜不到 SetAchievement、StoreStats 或 80 个成就 ID 的字符串,甚至没有 dlopen或dlsym 的动态加载痕迹,只有一些通用文本(如 steam_id、ui_authenticating_with_steam、(Steam Workshop))以及 @rpath/libsteam_api.dylib 字符串本体(无交叉引用)

功能应该就是对应了创意工坊相关和Shift+Tab的游戏内覆盖功能吧

这说明 macOS 版虽然把 Steam 库随包带上并在加载命令里声明了依赖,但实际上并未在代码路径中调用成就相关接口,同时缺少脚本操作码 372 的处理逻辑,成就系统在该构建目标中处于未启用状态

所以目前看来这个问题是解决不了了,除非T社给源码

  1. steamapps/common/MountBlade Warband
  2. ❯ otool -L "Mount and Blade.app/Contents/MacOS/Mount and Blade"

  3. Mount and Blade.app/Contents/MacOS/Mount and Blade:
  4.         /usr/lib/libcurl.4.dylib (compatibility version 7.0.0, current version 9.0.0)
  5.         @rpath/libsteam_api.dylib (compatibility version 1.0.0, current version 1.0.0)
  6.         @rpath/libfmodex.dylib (compatibility version 1.0.0, current version 1.0.0)
  7.         @rpath/GLEW.framework/Versions/2.1.0/GLEW (compatibility version 2.1.0, current version 2.1.0)
  8.         @rpath/SDL2.framework/Versions/A/SDL2 (compatibility version 1.0.0, current version 8.0.0)
  9.         /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 22.0.0)
  10.         /System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
  11.         /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.0)
  12.         /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)
  13.         /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 822.31.0)
复制代码


最终方案

上虚拟机Parallels Desktop或者CrossOver运行Windows版吧

5

主题

3051

回帖

952

积分

骑士

Rank: 4Rank: 4

UID
86936
第纳尔
12690
精华
0
互助
5
荣誉
1
贡献
0
魅力
71
注册时间
2008-8-10

原版正版勋章战团正版勋章元老骑士勋章汉匈决战正版勋章维京征服正版勋章霸主正版勋章

鲜花(65) 鸡蛋(1)
发表于 昨天 10:39 | 显示全部楼层
macOS上玩游戏,现在感觉比Linux上还麻烦……
您需要登录后才可以回帖 登录 | 注册(Register!)

本版积分规则

Archiver|手机版|小黑屋|骑马与砍杀中文站

GMT+8, 2025-10-9 03:36 , Processed in 0.101665 second(s), 21 queries , Gzip On, MemCached On.

Powered by Discuz! X3.4 Licensed

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表