- 好友
- 1
- 在线时间
- 3 小时
- 最后登录
- 2025-10-8
平民

- UID
- 3035263
- 第纳尔
- 69
- 精华
- 0
- 互助
- 0
- 荣誉
- 0
- 贡献
- 0
- 魅力
- 0
- 注册时间
- 2018-10-13
 鲜花( 0)  鸡蛋( 0)
|
本帖最后由 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
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
- <plist version="1.0">
- <dict>
- <key>BuildMachineOSBuild</key>
- <string>17G65</string>
- <key>CFBundleDevelopmentRegion</key>
- <string>en</string>
- <key>CFBundleExecutable</key>
- <string>Mount and Blade</string>
- <key>CFBundleIconFile</key>
- <string>[mbwarband]</string>
- <key>CFBundleIdentifier</key>
- <string>taleworlds.Mount-and-Blade</string>
- <key>CFBundleInfoDictionaryVersion</key>
- <string>6.0</string>
- <key>CFBundleName</key>
- <string>Mount and Blade</string>
- <key>CFBundlePackageType</key>
- <string>APPL</string>
- <key>CFBundleShortVersionString</key>
- <string>1.0</string>
- <key>CFBundleSignature</key>
- <string>????</string>
- <key>CFBundleSupportedPlatforms</key>
- <array>
- <string>MacOSX</string>
- </array>
- <key>CFBundleVersion</key>
- <string>1</string>
- <key>DTCompiler</key>
- <string>com.apple.compilers.llvm.clang.1_0</string>
- <key>DTPlatformBuild</key>
- <string>9F2000</string>
- <key>DTPlatformVersion</key>
- <string>GM</string>
- <key>DTSDKBuild</key>
- <string>17E189</string>
- <key>DTSDKName</key>
- <string>macosx10.13</string>
- <key>DTXcode</key>
- <string>0941</string>
- <key>DTXcodeBuild</key>
- <string>9F2000</string>
- <key>LSApplicationCategoryType</key>
- <string>public.app-category.role-playing-games</string>
- <key>LSMinimumSystemVersion</key>
- <string>10.9</string>
- <key>NSHumanReadableCopyright</key>
- <string>Copyright © 2014 TaleWorlds Entertainment. All rights reserved.</string>
- <key>NSMainNibFile</key>
- <string>MainMenu</string>
- <key>NSPrincipalClass</key>
- <string>NSApplication</string>
- <key>UIDeviceFamily</key>
- <array>
- <integer>1</integer>
- <integer>2</integer>
- </array>
- </dict>
- </plist>
复制代码
BuildMachineOSBuild=17G65(macOS 10.13.6 High Sierra),DTPlatformBuild=9F2000+DTCompiler=com.apple.compilers.llvm.clang.1_0指向 Xcode 9.4 时代的 Clang 工具链,时间差不多是2018年左右
从SteamDB上其实也能看到最近两次的更新,最近一次是2023年,但内容都是DLC相关的,再上一次就是2018年,由此可见后面基本上也是不可能再去管了
steamdb
操作码分析
通过创意工坊的Achievement grab剧本,可以直接定位到战团脚本引擎用于解锁成就的内部指令:操作码 372
在 menus.txt 中可以看到连续的调用样例:
- 372 1 1
- 372 1 2
- ...
- 372 1 80
复制代码
由此可以得出:
- 372 是用于成就的脚本操作码
- 形如 372 1 N:中间的 1 表示参数个数,N=1..80 为成就数字 ID(枚举见 module_constants.py)
- 引擎将数字 ID 映射为字符串成就 ID(如 HOLY_DIVER、GET_UP_STAND_UP 等),随后调用 Steam 成就接口解锁并 StoreStats 持久化
在官方 Module System 1.171 源码中也有明确定义:
header_operations.py
- get_achievement_stat = 370 # (get_achievement_stat, <destination>, <achievement_id>, <stat_index>),
- set_achievement_stat = 371 # (set_achievement_stat, <achievement_id>, <stat_index>, <value>),
- unlock_achievement = 372 # (unlock_achievement, <achievement_id>),
复制代码
由此可知 372 即为解锁成就的操作码,1 后面的数字为achievement_id
module_constants.py(将成就定义为 1..80 的枚举)
- # NORMAL ACHIEVEMENTS
- ACHIEVEMENT_NONE_SHALL_PASS = 1,
- ACHIEVEMENT_MAN_EATER = 2,
- ACHIEVEMENT_THE_HOLY_HAND_GRENADE = 3,
- ACHIEVEMENT_LOOK_AT_THE_BONES = 4,
- ACHIEVEMENT_KHAAAN = 5,
- ACHIEVEMENT_GET_UP_STAND_UP = 6,
- ACHIEVEMENT_BARON_GOT_BACK = 7,
- ACHIEVEMENT_BEST_SERVED_COLD = 8,
- ACHIEVEMENT_TRICK_SHOT = 9,
- ACHIEVEMENT_GAMBIT = 10,
- ACHIEVEMENT_OLD_SCHOOL_SNIPER = 11,
- ACHIEVEMENT_CALRADIAN_ARMY_KNIFE = 12,
- ACHIEVEMENT_MOUNTAIN_BLADE = 13,
- ACHIEVEMENT_HOLY_DIVER = 14,
- ACHIEVEMENT_FORCE_OF_NATURE = 15,
- # SKILL RELATED ACHIEVEMENTS
- ACHIEVEMENT_BRING_OUT_YOUR_DEAD = 16,
- ACHIEVEMENT_MIGHT_MAKES_RIGHT = 17,
- ACHIEVEMENT_COMMUNITY_SERVICE = 18,
- ACHIEVEMENT_AGILE_WARRIOR = 19,
- ACHIEVEMENT_MELEE_MASTER = 20,
- ACHIEVEMENT_DEXTEROUS_DASTARD = 21,
- ACHIEVEMENT_MIND_ON_THE_MONEY = 22,
- ACHIEVEMENT_ART_OF_WAR = 23,
- ACHIEVEMENT_THE_RANGER = 24,
- ACHIEVEMENT_TROJAN_BUNNY_MAKER = 25,
- # MAP RELATED ACHIEVEMENTS
- ACHIEVEMENT_MIGRATING_COCONUTS = 26,
- ACHIEVEMENT_HELP_HELP_IM_BEING_REPRESSED = 27,
- ACHIEVEMENT_SARRANIDIAN_NIGHTS = 28,
- ACHIEVEMENT_OLD_DIRTY_SCOUNDREL = 29,
- ACHIEVEMENT_THE_BANDIT = 30,
- ACHIEVEMENT_GOT_MILK = 31,
- ACHIEVEMENT_SOLD_INTO_SLAVERY = 32,
- ACHIEVEMENT_MEDIEVAL_TIMES = 33,
- ACHIEVEMENT_GOOD_SAMARITAN = 34,
- ACHIEVEMENT_MORALE_LEADER = 35,
- ACHIEVEMENT_ABUNDANT_FEAST = 36,
- ACHIEVEMENT_BOOK_WORM = 37,
- ACHIEVEMENT_ROMANTIC_WARRIOR = 38,
- # POLITICALLY ORIENTED ACHIEVEMENTS
- ACHIEVEMENT_HAPPILY_EVER_AFTER = 39,
- ACHIEVEMENT_HEART_BREAKER = 40,
- ACHIEVEMENT_AUTONOMOUS_COLLECTIVE = 41,
- ACHIEVEMENT_I_DUB_THEE = 42,
- ACHIEVEMENT_SASSY = 43,
- ACHIEVEMENT_THE_GOLDEN_THRONE = 44,
- ACHIEVEMENT_KNIGHTS_OF_THE_ROUND = 45,
- ACHIEVEMENT_TALKING_HELPS = 46,
- ACHIEVEMENT_KINGMAKER = 47,
- ACHIEVEMENT_PUGNACIOUS_D = 48,
- ACHIEVEMENT_GOLD_FARMER = 49,
- ACHIEVEMENT_ROYALITY_PAYMENT = 50,
- ACHIEVEMENT_MEDIEVAL_EMLAK = 51,
- ACHIEVEMENT_CALRADIAN_TEA_PARTY = 52,
- ACHIEVEMENT_MANIFEST_DESTINY = 53,
- ACHIEVEMENT_CONCILIO_CALRADI = 54,
- ACHIEVEMENT_VICTUM_SEQUENS = 55,
- # MULTIPLAYER ACHIEVEMENTS
- ACHIEVEMENT_THIS_IS_OUR_LAND = 56,
- ACHIEVEMENT_SPOIL_THE_CHARGE = 57,
- ACHIEVEMENT_HARASSING_HORSEMAN = 58,
- ACHIEVEMENT_THROWING_STAR = 59,
- ACHIEVEMENT_SHISH_KEBAB = 60,
- ACHIEVEMENT_RUIN_THE_RAID = 61,
- ACHIEVEMENT_LAST_MAN_STANDING = 62,
- ACHIEVEMENT_EVERY_BREATH_YOU_TAKE = 63,
- ACHIEVEMENT_CHOPPY_CHOP_CHOP = 64,
- ACHIEVEMENT_MACE_IN_YER_FACE = 65,
- ACHIEVEMENT_THE_HUSCARL = 66,
- ACHIEVEMENT_GLORIOUS_MOTHER_FACTION = 67,
- ACHIEVEMENT_ELITE_WARRIOR = 68,
- # COMBINED ACHIEVEMENTS
- ACHIEVEMENT_SON_OF_ODIN = 69,
- ACHIEVEMENT_KING_ARTHUR = 70,
- ACHIEVEMENT_KASSAI_MASTER = 71,
- ACHIEVEMENT_IRON_BEAR = 72,
- ACHIEVEMENT_LEGENDARY_RASTAM = 73,
- ACHIEVEMENT_SVAROG_THE_MIGHTY = 74,
- ACHIEVEMENT_MAN_HANDLER = 75,
- ACHIEVEMENT_GIRL_POWER = 76,
- ACHIEVEMENT_QUEEN = 77,
- ACHIEVEMENT_EMPRESS = 78,
- ACHIEVEMENT_TALK_OF_THE_TOWN = 79,
- ACHIEVEMENT_LADY_OF_THE_LAKE = 80,
复制代码
触发器代码
module_game_menus.py
- (unlock_achievement, ACHIEVEMENT_LOOK_AT_THE_BONES)
- (set_achievement_stat, ACHIEVEMENT_THE_BANDIT, 1, ":number_of_caravan_raids")
- (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
- (unlock_achievement, ACHIEVEMENT_ABUNDANTFEAST)
复制代码
simple_trigger用于周期/条件性的集中检查,条件满足时统一解锁,避免在多处逻辑里重复判断与调用
module_scripts.py
- (get_achievement_stat, ":s", ACHIEVEMENT_SHISH_KEBAB, 0)
- (val_add, ":s", 1)
- (set_achievement_stat, ACHIEVEMENT_SHISH_KEBAB, 0, ":s")
- (unlock_achievement, ACHIEVEMENT_SHISH_KEBAB)
复制代码
成就进度更新,先读当前进度→累加→回写
达到阈值时再解锁
通过IDA也能找到 Windows 版本的导入与关键函数地址,作为上述机制的技术依据
Windows 版本数据(mb_warband.exe)
符号 | 地址 | SteamAPI_Init | 0x7be60c | SteamUserStats | 0x7be5f0 | SteamUser | 0x7be624 | SteamAPI_RunCallbacks | 0x7be608 | SteamAPI_Shutdown | 0x7be618 | SteamAPI_RestartAppIfNecessary | 0x7be61c | SteamClient | 0x7be610 | SteamUtils | 0x7be5f8 | SteamAPI_RegisterCallback | 0x7be5fc | SteamAPI_RegisterCallResult | 0x7be600 | SteamAPI_UnregisterCallback | 0x7be5f4 | SteamAPI_UnregisterCallResult | 0x7be628 | SteamGameServer_Init | 0x7be5ec | SteamGameServer_RunCallbacks | 0x7be614 | SteamGameServer_Shutdown | 0x7be620 | SteamUGC | 0x7be630 |
成就系统关键函数
功能 | 地址 | 成就管理器构造函数 | 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_UP | 0x80ebf8 | TALKING_HELPS | 0x80e944 | GOOD_SAMARITAN | 0x80ea10 | HELP_HELP_IM_BEING_REPRESSED | 0x80ea84 | COMMUNITY_SERVICE | 0x80eb2c | HOLY_DIVER | 0x80eb78 | FORCE_OF_NATURE | 0x80eb68 | MAN_EATER | 0x80ec3c | BARON_GOT_BACK | 0x80ebe8 | BEST_SERVED_COLD | 0x80ebd4 | MOUNTAIN_BLADE | 0x80eb84 | THE_HOLY_HAND_GRENADE | 0x80ec24 | CALRADIAN_ARMY_KNIFE | 0x80eb94 | KHAAAN | 0x80ec08 | GAMBIT | 0x80ebc0 | OLD_SCHOOL_SNIPER | 0x80ebac | TRICK_SHOT | 0x80ebc8 | DEXTEROUS_DASTARD | 0x80eaf8 | MELEE_MASTER | 0x80eb0c | MIGHT_MAKES_RIGHT | 0x80eb40 | AGILE_WARRIOR | 0x80eb1c | ART_OF_WAR | 0x80ead8 | MIND_ON_THE_MONEY | 0x80eae4 | THE_RANGER | 0x80eacc | BRING_OUT_YOUR_DEAD | 0x80eb54 | TROJAN_BUNNY_MAKER | 0x80eab8 | HAPPILY_EVER_AFTER | 0x80e9bc | HEART_BREAKER | 0x80e9ac | PUGNACIOUS_D | 0x80e928 | OLD_DIRTY_SCOUNDREL | 0x80ea5c | I_DUB_THEE | 0x80e988 | ROMANTIC_WARRIOR | 0x80e9d0 | MORALE_LEADER | 0x80ea00 | MIGRATING_COCONUTS | 0x80eaa4 | SARRANIDIAN_NIGHTS | 0x80ea70 | ABUNDANT_FEAST | 0x80e9f0 | BOOK_WORM | 0x80e9e4 | MEDIEVAL_TIMES | 0x80ea20 | LOOK_AT_THE_BONES | 0x80ec10 | GOLD_FARMER | 0x80e91c | SOLD_INTO_SLAVERY | 0x80ea30 | THE_BANDIT | 0x80ea50 | GOT_MILK | 0x80ea44 | ROYALITY_PAYMENT | 0x80e908 | MEDIEVAL_EMLAK | 0x80e8f8 | NONE_SHALL_PASS | 0x80ec48 | AUTONOMOUS_COLLECTIVE | 0x80e994 | CALRADIAN_TEA_PARTY | 0x80e8e4 | CONCILIO_CALRADI | 0x80e8bc | KINGMAKER | 0x80e938 | MANIFEST_DESTINY | 0x80e8d0 | THE_GOLDEN_THRONE | 0x80e96c | VICTUM_SEQUENS | 0x80e8ac | TALK_OF_THE_TOWN | 0x80e724 | MAN_HANDLER | 0x80e754 | SASSY | 0x80e980 | QUEEN | 0x80e740 | GIRL_POWER | 0x80e748 | LADY_OF_THE_LAKE | 0x80e710 | EMPRESS | 0x80e738 | KNIGHTS_OF_THE_ROUND | 0x80e954 | CHOPPY_CHOP_CHOP | 0x80e804 | MACE_IN_YER_FACE | 0x80e7f0 | THE_HUSCARL | 0x80e7e4 | THROWING_STAR | 0x80e860 | SPOIL_THE_CHARGE | 0x80e884 | SHISH_KEBAB | 0x80e854 | HARASSING_HORSEMAN | 0x80e870 | EVERY_BREATH_YOU_TAKE | 0x80e818 | ELITE_WARRIOR | 0x80e7bc | GLORIOUS_MOTHER_FACTION | 0x80e7cc | LAST_MAN_STANDING | 0x80e830 | THIS_IS_OUR_LAND | 0x80e898 | RUIN_THE_RAID | 0x80e844 | SON_OF_ODIN | 0x80e7b0 | IRON_BEAR | 0x80e788 | KASSAI_MASTER | 0x80e794 | SVAROG_THE_MIGHTY | 0x80e760 | KING_ARTHUR | 0x80e7a4 | LEGENDARY_RASTAM | 0x80e774 |
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社给源码
- steamapps/common/MountBlade Warband
- ❯ otool -L "Mount and Blade.app/Contents/MacOS/Mount and Blade"
- Mount and Blade.app/Contents/MacOS/Mount and Blade:
- /usr/lib/libcurl.4.dylib (compatibility version 7.0.0, current version 9.0.0)
- @rpath/libsteam_api.dylib (compatibility version 1.0.0, current version 1.0.0)
- @rpath/libfmodex.dylib (compatibility version 1.0.0, current version 1.0.0)
- @rpath/GLEW.framework/Versions/2.1.0/GLEW (compatibility version 2.1.0, current version 2.1.0)
- @rpath/SDL2.framework/Versions/A/SDL2 (compatibility version 1.0.0, current version 8.0.0)
- /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 22.0.0)
- /System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
- /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.0)
- /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)
- /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 822.31.0)
复制代码
最终方案
上虚拟机Parallels Desktop或者CrossOver运行Windows版吧 |
|