Mercenaries Mod and Mac

Flintlock1415

Emperor
Joined
Feb 24, 2008
Messages
1,057
Location
MN
I noticed that a little while ago some people converted The Lopez's Mercenaries Mod to BtS 3.19. This mod uses mostly all Python, but it includes the ini file parser, which imports from a module called _winreg. This module is completely Windows specific, so I was wondering if anyone here knows of a workaround so that Mac users can play this mod.

I think I've seen this being discussed before, but I can't find the thread. I can also ask in the main C&C forum, but I was wondering if anyone here has a solution.
Thanks!

EDIT: Here is the link to the mod
 
Here's a modified version of CvPath.py from INIParser as included in Mercenaries Mod.

The mod loads and starts a game if you replace the text in that file with the text below. Just copy and paste it into your favourite text editor, save it as CvPath.py, and use that file instead of the one in Mercenary Mod/Assets/Python/INIParser/

Spoiler :

Code:
## Copyright (c) 2006, Gillmer J. Derge.

## This file is part of Civilization IV Alerts mod.
##
## Civilization IV Alerts mod is free software; you can redistribute
## it and/or modify it under the terms of the GNU General Public
## License as published by the Free Software Foundation; either
## version 2 of the License, or (at your option) any later version.
##
## Civilization IV Alerts mod is distributed in the hope that it will
## be useful, but WITHOUT ANY WARRANTY; without even the implied
## warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
## See the GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Civilization IV Alerts mod; if not, write to the Free
## Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
## 02110-1301 USA

__version__ = "$Revision$"
# $Source$

"""Implements a collection of utility methods and variables for determining
the location of Civilization 4 components.

The following variables are exposed.

* activeModName: the name of the currently active mod or None if no mod has
  been loaded.
  
  NOTE: activeModName does not currently work in a completely automated 
  fashion.  There does not appear to be a way to determine the active mod 
  programmatically from Python code.  A mod that wishes to export its name 
  to this module must create a Python module called CvModName that contains 
  a string variable named modName set to the name of the mod.  A sample 
  CvModName is shown below.

  # CvModName.py
  modName = "Mercenaries Mod"

  Of course, a CvModName Python module should only be used if the mod is 
  indeed installed in the Mods directory, not when it is installed in 
  CustomAssets.  Furthermore, if the value of the modName variable does not
  correctly match the mod directory name, the path variables will not be
  set properly.

* userDir: the user's Civilization 4 directory, typically
  C:\Documents and Settings\User\My Documents\My Games\Sid Meier's Civilization 4
  
* userAssetsDir: <userDir>\CustomAssets

* userModsDir: <userDir>\Mods

* userActiveModDir: <userDir>\Mods\<activeModName>

* userActiveModAssetsDir: <userDir>\Mods\<activeModName>\Assets

* installDir: the Civilization 4 installation directory, typically
  C:\Program Files\Firaxis Games\Sid Meier's Civilization 4

* installAssetsDir: <installDir>\Assets

* installModsDir: <installDir>\Mods

* installActiveModDir: <installDir>\Mods\<activeModName>

* installActiveModAssetsDir: <installDir>\Mods\<activeModName>\Assets

* assetsPath: a list containing all Assets directories that appear on the
  game's load paths.  Typically [userAssetsDir, installAssetsDir] or
  [userActiveModAssetsDir, installActiveModAssetsDir, userAssetsDir, installAssetsDir]

* pythonPath: a list containing all directories that appear on the
  game's Python load path.  The game's Python module loader does not support
  Python packages, so this list includes not only the Python subdirectory
  of each element of the assetsPath but also all non-empty subdirectories.

September 1, 2009: Amended by AlanH to run in Mac OS X, where there's no Register.

"""


import os
import os.path
import sys

if (sys.platform == 'darwin'):
	
	def _getInstallDir():
		return os.getcwd()
		
	def _getUserDir():
		civ4Dir = os.path.basename(_getInstallDir())
		return os.path.join(os.environ['HOME'], "Documents", "Civilization IV "+civ4Dir)
		
else:

	import _winreg
	
	def __getRegValue(root, subkey, name):
		key = _winreg.OpenKey(root, subkey)
		try:
			value = _winreg.QueryValueEx(key, name)
			return value[0]
		finally:
			key.Close()
	
	def _getUserDir():
		myDocuments = __getRegValue(_winreg.HKEY_CURRENT_USER, 
				r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
				"Personal")
		civ4Dir = os.path.basename(_getInstallDir())
		return os.path.join(os.path.join(myDocuments, "My Games"), civ4Dir)
	
	def _getInstallDir():
		return __getRegValue(_winreg.HKEY_LOCAL_MACHINE, 
				r"Software\Firaxis Games\Sid Meier's Civilization 4",
				"INSTALLDIR")


activeModName = None
try:
    import CvModName
    activeModName = CvModName.modName
except:
    pass

userDir = _getUserDir()

userAssetsDir = os.path.join(userDir, "CustomAssets")

userModsDir = os.path.join(userDir, "Mods")

userActiveModDir = None

userActiveModAssetsDir = None

installDir = _getInstallDir()

installAssetsDir = os.path.join(installDir, "Assets")

installModsDir = os.path.join(installDir, "Mods")

installActiveModDir = None

installActiveModAssetsDir = None

assetsPath = [userAssetsDir, installAssetsDir]

if (activeModName != None):
    userActiveModDir = os.path.join(userModsDir, activeModName)
    userActiveModAssetsDir = os.path.join(userActiveModDir, "Assets")
    installActiveModDir = os.path.join(installModsDir, activeModName)
    installActiveModAssetsDir = os.path.join(installActiveModDir, "Assets")
    assetsPath.insert(0, userActiveModAssetsDir)
    assetsPath.insert(1, installActiveModAssetsDir)

pythonPath = []
for dir in [os.path.join(d, "Python") for d in assetsPath]:
    for root, subdirs, files in os.walk(dir):
        if (len(files) > 0):
            pythonPath.append(root)


def _test():
    print "activeModName = " + str(activeModName)
    print "userDir = " + userDir
    print "userAssetsDir = " + userAssetsDir
    print "userModsDir = " + userModsDir
    print "userActiveModDir = " + str(userActiveModDir)
    print "userActiveModAssetsDir = " + str(userActiveModAssetsDir)
    print "installDir = " + installDir
    print "installAssetsDir = " + installAssetsDir
    print "installModsDir = " + installModsDir
    print "installActiveModDir = " + str(installActiveModDir)
    print "installActiveModAssetsDir = " + str(installActiveModAssetsDir)
    print "assetsPath = " 
    for dir in assetsPath:
        print "  " + dir
    print "pythonPath = "
    for dir in pythonPath:
        print "  " + dir

if __name__ == "__main__": 
    _test()
 
Perfect, thanks! I'll test it out in a bit, but a couple of questions.

First, I'm assuming the if/else statement about checking the sys.platform allows this to work on Windows and Mac, am I right? Second, do I only need to change the modname if I were to transform other mods with the ini parser? Or are there other changes need specific to the mod?

Anyway, thanks again Alan! :goodjob:
 
1. Correct, I haven't retested in Windows, but this version should be cross platform. I tried to persuade the modding community to adopt this structure a few years ago so that their mods might come closer to working on Macs, but it obviously fell on deaf ears. My attempts were posted in a thread on the Civ4lerts mod that's now included in HoF Mods and BUFFY, and the INIParser version is based on that same code, since the comments refer to it.

Note that my fix will need to be changed in order to work in vanilla, as it uses the application's folder name to identify the user/Documents folder path. That doesn't work for Mac vanilla, only for Warlords and BtS. You would have to amend the code to remove ' + civ4Dir' from the second line of _getUserDir() in a vanilla mod, or find a way to make the code work across all three variants.

2. CvPath.py gets the mod name from a file/module called CvModName.py.

Code:
#CvModName.py
modName = "Mercenaries Mod"
You need to edit this file with your own mod name if you want to use INIParser with your mod.
 
Ok, the mod loads up and I can use the Mercenary screen to hire a new unit, but as soon as the game tries to place the unit on the map, I get the spinning wheel followed by a crash. I am at a loss here, because I'm not getting any help from my logs. (I have Python logging enabled, is there something else I should turn on?)

I can actually see the unit appear in my capital, which leads me to believe that the error is occuring when the stats of the unit are being set. The most likely things the I can think of are either setting the mercenary promotion or setting the unique name.

Any suggestions?
 
How much of a game would I have to play to get to that point? Alternatively, do you have a save that illustrates the problem?
 
Well, I played a bit from 4000 BC, but didn't see a mercenary action button anywhere. But I did experience a crash - first part of the crash report below. Does this resemble yours?

Spoiler :

Code:
Process:         Civilization IV Beyond the Sword [18994]
Path:            /Applications/Civilization IV/Beyond the Sword/Civilization IV Beyond the Sword.app/Contents/MacOS/Civilization IV Beyond the Sword
Identifier:      com.aspyr.civ4bts
Version:         3.19 (92131)
Code Type:       X86 (Native)
Parent Process:  launchd [253]

Date/Time:       2009-09-02 02:57:41.597 +0100
OS Version:      Mac OS X 10.6 (10A432)
Report Version:  6

Interval Since Last Report:          311408 sec
Crashes Since Last Report:           2
Per-App Interval Since Last Report:  4236 sec
Per-App Crashes Since Last Report:   1
Anonymous UUID:                      BFE4BA3E-603C-450F-9924-5BAC47540C89

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Codes: KERN_PROTECTION_FAILURE at 0x0000000000000011
Crashed Thread:  0  Dispatch queue: com.apple.main-thread

Thread 0 Crashed:  Dispatch queue: com.apple.main-thread
0   com.aspyr.civ4bts             	0x001a1329 CvActionInfo::getHotkeyInfo() const + 11
1   com.aspyr.civ4bts             	0x001bc21a CvActionInfo::getHotKeyDescription() const + 26
2   com.aspyr.civ4bts             	0x004f359d CvDLLWidgetData::parseActionHelp(CvWidgetDataStruct&, CvWStringBuffer&) + 169
3   com.aspyr.civ4bts             	0x004f9430 CvDLLWidgetData::parseHelp(CvWStringBuffer&, CvWidgetDataStruct&) + 1048
4   com.aspyr.civ4bts             	0x00b886d9 CvWidgetData::parseHelp(CvWStringBuffer&) + 39
5   com.aspyr.civ4bts             	0x00b88e98 CvWidgetData::parseHelp(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >&) + 46
6   com.aspyr.civ4bts             	0x00b99270 CvGInterfaceScreenImpl::appendMultiListButton(char const*, char const*, int, WidgetTypes, int, int, bool) + 206
7   com.aspyr.civ4bts             	0x005c1dad _object* boost::python::detail::invoke<int, void (CyGInterfaceScreen::*)(char const*, char const*, int, WidgetTypes, int, int, bool), boost::python::arg_from_python<CyGInterfaceScreen&>, boost::python::arg_from_python<char const*>, boost::python::arg_from_python<char const*>, boost::python::arg_from_python<int>, boost::python::arg_from_python<WidgetTypes>, boost::python::arg_from_python<int>, boost::python::arg_from_python<int>, boost::python::arg_from_python<bool> >(boost::python::detail::invoke_tag_<true, true>, int const&, void (CyGInterfaceScreen::*&)(char const*, char const*, int, WidgetTypes, int, int, bool), boost::python::arg_from_python<CyGInterfaceScreen&>&, boost::python::arg_from_python<char const*>&, boost::python::arg_from_python<char const*>&, boost::python::arg_from_python<int>&, boost::python::arg_from_python<WidgetTypes>&, boost::python::arg_from_python<int>&, boost::python::arg_from_python<int>&, boost::python::arg_from_python<bool>&) + 211
8   com.aspyr.civ4bts             	0x005c90c9 boost::python::detail::caller_arity<8u>::impl<void (CyGInterfaceScreen::*)(char const*, char const*, int, WidgetTypes, int, int, bool), boost::python::default_call_policies, boost::mpl::vector9<void, CyGInterfaceScreen&, char const*, char const*, int, WidgetTypes, int, int, bool> >::operator()(_object*, _object*) + 397
9   com.aspyr.civ4bts             	0x005b129e boost::python::objects::function::call(_object*, _object*) const + 698
10  com.aspyr.civ4bts             	0x005b3071 boost::detail::function::void_function_obj_invoker0<boost::python::objects::(anonymous namespace)::bind_return, void>::invoke(boost::detail::function::any_pointer) + 35
11  com.aspyr.civ4bts             	0x005ace0f boost::function0<void, std::allocator<boost::function_base> >::operator()() const + 79
12  com.aspyr.civ4bts             	0x005acab6 boost::python::handle_exception_impl(boost::function0<void, std::allocator<boost::function_base> >) + 54
13  com.aspyr.civ4bts             	0x005b1ebf bool boost::python::handle_exception<boost::python::objects::(anonymous namespace)::bind_return>(boost::python::objects::(anonymous namespace)::bind_return) + 41
14  com.aspyr.civ4bts             	0x005b018c function_call + 58
15  org.python.python             	0x024c6049 PyObject_Call + 50
16  org.python.python             	0x02518d7b PyEval_InitThreads + 19151
17  org.python.python             	0x025179d0 PyEval_InitThreads + 14116
18  org.python.python             	0x025179d0 PyEval_InitThreads + 14116
19  org.python.python             	0x02519550 PyEval_EvalCodeEx + 1840
20  org.python.python             	0x024d9de1 PyClassMethod_New + 1916
21  org.python.python             	0x024c6049 PyObject_Call + 50
22  org.python.python             	0x025138d0 PyEval_CallObjectWithKeywords + 208
23  org.python.python             	0x024c6805 PyObject_CallObject + 32
24  com.aspyr.civ4bts             	0x00678e6f FPythonMgr::ICallFunction(_object*, std::string const&, _object*, _object**) const + 79
25  com.aspyr.civ4bts             	0x006790e7 FPythonMgr::ICallFunction(_object*, std::string const&, _object*, long*) const + 57
26  com.aspyr.civ4bts             	0x00679744 FPythonMgr::CallFunction(std::string const&, std::string const&, _object*, long*) const + 180
27  com.aspyr.civ4bts             	0x00b1d445 CyPythonMgr::callFunction(FStringA const&, FStringA const&, void*) + 177
28  com.aspyr.civ4bts             	0x00b48178 CvInterface::redrawPythonInterface() + 176
29  com.aspyr.civ4bts             	0x00b4b972 CvInterface::redraw() + 32
30  com.aspyr.civ4bts             	0x00a91fa3 CvApp::OnIdle() + 1481
31  com.aspyr.civ4bts             	0x0066ea41 FWinApp::Run() + 31
32  com.aspyr.civ4bts             	0x00b1ee52 WinMain + 50
33  com.aspyr.civ4bts             	0x000a7f99 sEventLoopEventHandler(OpaqueEventHandlerCallRef*, OpaqueEventRef*, void*) + 111
34  com.apple.HIToolbox           	0x9492d129 DispatchEventToHandlers(EventTargetRec*, OpaqueEventRef*, HandlerCallRec*) + 1567
35  com.apple.HIToolbox           	0x9492c3f0 SendEventToEventTargetInternal(OpaqueEventRef*, OpaqueEventTargetRef*, HandlerCallRec*) + 411
36  com.apple.HIToolbox           	0x9492c24f SendEventToEventTargetWithOptions + 58
37  com.apple.HIToolbox           	0x94960c0c ToolboxEventDispatcherHandler(OpaqueEventHandlerCallRef*, OpaqueEventRef*, void*) + 3006
38  com.apple.HIToolbox           	0x9492d57a DispatchEventToHandlers(EventTargetRec*, OpaqueEventRef*, HandlerCallRec*) + 2672
39  com.apple.HIToolbox           	0x9492c3f0 SendEventToEventTargetInternal(OpaqueEventRef*, OpaqueEventTargetRef*, HandlerCallRec*) + 411
40  com.apple.HIToolbox           	0x9494ea81 SendEventToEventTarget + 52
41  com.apple.HIToolbox           	0x94ad78f7 ToolboxEventDispatcher + 86
42  com.apple.HIToolbox           	0x94ad7a2f RunApplicationEventLoop + 243
43  com.aspyr.civ4bts             	0x000a8052 InstallEventsAndRunGameLoop() + 166
44  com.aspyr.civ4bts             	0x000a83ea main + 356
45  com.aspyr.civ4bts             	0x00002642 _start + 216
46  com.aspyr.civ4bts             	0x00002569 start + 41
 
The mercenary action is located right next to the event log button (under the treasury counter.) I was able to hire a mercenary in a matter of a few turns, I just set the research slider to 0 and wait until I have enough gold. The mercenaries available vary each turn in type, name, level and price, so once you get about 100 gold start looking for a cheap one. I think you have to wait a minimum of about 3 turns for the unit to arrive in your capital, but it really doesn't take that long.

When did you get your crash? Did you do anything specific? I was able to play normally, until the unit tried showing up. And yea, my crash log looks like yours.
 
My crash was around the time of completion of a warrior unit in my second city. It must have been trying to open a pop-up screen, as my crash log indicates that the game was attempting to display a help panel of some kind, and failed. Since your is the same, maybe mine was trying to signal the arrival of a random mercenary - do those happen?

The usual reason for crashes like this is that Python is attempting to call uninitialised C++ code. It seems the Windows versions of Civ4 can handle these exceptions better than the Mac versions. It's not going to be trivial to track it down. I'll have to put trace debug calls into the Python code to narrow down what's happening just before the crash. I'll have a play with it tonight.
 
Back
Top Bottom