mbed HUD for Crysis
.
This is a variation of my FlightSimInstrument project, but this time using the USBHID interface from Crysis!
My aim is to display the player's current health and remaining ammunition on a Text LCD display. This will be done by writing a Crysis mod with a custom game DLL.
mbed + crysis in action (well, in the Sandbox 2 editor - the godmode came in handy while trying to take a picture ;-) )
mbed code
We'll be displaying two values, health & remaining ammo, so a fairly simple USB HID report will do:
main.cpp
union hid_report_t { uint8_t bytes[sizeof(int16_t)*2]; struct { uint16_t health; uint16_t ammo; } data; };
I'm using a union to make it both easy for USBHID to write stuff into the structure (you can simply pass it a pointer to the bytes
array, and easy for us to read the health and remaining ammo values from it. Here's the full mbed code:
Import programCrysisHUD
This code will display two unsigned shorts (representing health & remaining ammo) on a TextLCD display. The mbed receives them via the USB HID interface.
The only "oddity" I encountered was that I needed to flip the bytes on the mbed side, even though both my Intel desktop and the mbed are little-endian. The __REV16()
function did this just fine though, using an assembly instruction (see core_cmInstr.h).
PC code
I'm using Crysis 1 - You'll need Crysis patched to v1.2, the Crysis Mod SDK v1.2 and Visual Studio (any version above 2005 should be OK, and some people appear to have managed to compile game DLLs with free tools, too: some basic info). It's also worthwhile installing the Sandbox 2 editor (it's on the Crysis DVD), since it makes debugging a bit easier.
Unfortunately, the links on Crytek's website seem to be broken - hopefully they'll fix them soon - but other sites (fileplanet etc) still seem to have a copy. I luckily still had the Mod SDK installed on my HDD from long ago, though I'm not entirely sure where I got it from. Anyway, the installer's MD5 checksum is 436334c74e0bb7364c6fc3724be9d50a
, and the SHA1 sum is 3e09ba3e5018f8a071c64e012d4a3238ddcaa4fa
, in case you want to cross-check :-)
Basic setup (if you get stuck anywhere, this video tutorial might be useful):
- Compile the HIDAPI project (see the USBHID C bindings cookbook page) - you'll need its files in a few steps time. I'll call HIDAPI's directory
<hidapi dir>
. Copy the<hidapi dir>\windows\Debug\hidapi.dll
to<Crysis install dir>\Bin32\
. - Under
<Crysis install dir>\Mods
, there's a folder calledCrysisMod
- copy this, and rename it tombedLcdMod
- Open
mbedLcdMod\Code\GameDll.vcproj
in Visual Studio; if necessary, follow the steps of the conversion wizard. - Hit F6 (Build->Build solution), and check that everything compiles. If you're getting a compiler error and you're using VS2010, this post explains how to fix it.
- Open the project properties (Project->Properties), and set the
Configuration
toAll configurations
. Then, underLinker
changeOutput File
to$(OutDir)mbedLcdMod.dll
. UnderLinker->Input
, add thehidapi.lib
(it's in<hidapi dir>\windows\Debug
) file toAdditional Dependencies
. UnderC/C++
, add<hidapi dir>\hidapi
toAdditional include directories
. - It's time for some code changes! Create a new class (Project->Add Class) called
CTextLcd
. Copy the source code from below into theTextLcd.h
andTextLcd.cpp
files.
TextLcd.h
#pragma once #include <hidapi.h> //the PC will need an extra byte for the reportID in the report union hid_report_t { unsigned char bytes[sizeof(unsigned short)*2 +1]; struct { unsigned char reportID; unsigned short health; unsigned short ammo; } data; }; class CTextLcd { public: CTextLcd(void); ~CTextLcd(void); void Init(void); void logDeviceInfo(void); void updateHealth(float h); void updateAmmo(int a); private: //handle to mbed hid_device *m_handle; //the report we'll be sending to it hid_report_t m_report; //mbed USB vendor ID, product ID, Serial Number. const unsigned short mbed_vid, mbed_pid; wchar_t* mbed_SN; void sendReport(void); };
TextLcd.cpp
#include "StdAfx.h" #include "TextLcd.h" #include <ISystem.h> CTextLcd::CTextLcd(void) : mbed_vid(0x1234), mbed_pid(0x6), mbed_SN(NULL), m_handle(NULL) { //initialise report - this will also set the report ID to 0. memset(m_report.bytes, 0x0, sizeof(m_report.bytes)); } void CTextLcd::Init(void) { if (m_handle) return; m_handle = hid_open(mbed_vid, mbed_pid, mbed_SN); if (!m_handle) { //for a production system, use proper exceptions. throw "unable to open device"; } } CTextLcd::~CTextLcd(void) { if (m_handle) { //close HID device hid_close(m_handle); } //Free static HIDAPI objects. hid_exit(); } void CTextLcd::logDeviceInfo(void) { if(!m_handle) { CryLogAlways("[MBED]: Could not log device info, since we don't have a handle to the device!"); return; } const unsigned int max_str_len = 256; wchar_t wstr[max_str_len]; int res; //this is just from the example, but useful so that we know we correctly connected to the mbed. // Read the Manufacturer String wstr[0] = 0x0000; res = hid_get_manufacturer_string(m_handle, wstr, max_str_len); if (res < 0) CryLogAlways("[MBED]: Unable to read manufacturer string"); else CryLogAlways("[MBED]: Manufacturer String: %ls", wstr); // Read the Product String wstr[0] = 0x0000; res = hid_get_product_string(m_handle, wstr, max_str_len); if (res < 0) CryLogAlways("[MBED]: Unable to read product string"); else CryLogAlways("[MBED]: Product String: %ls", wstr); // Read the Serial Number String wstr[0] = 0x0000; res = hid_get_serial_number_string(m_handle, wstr, max_str_len); if (res < 0) CryLogAlways("[MBED]: Unable to read serial number string"); else CryLogAlways("[MBED]: Serial Number String: (%d) %ls", wstr[0], wstr); } void CTextLcd::updateHealth(float h) { m_report.data.health = floor(h); sendReport(); } void CTextLcd::updateAmmo(int a) { if (a < 0) a = 0; m_report.data.ammo = (unsigned short)a; sendReport(); } /* * send USB HID report to mbed Text LCD. */ void CTextLcd::sendReport(void) { if (!m_handle) return; //it's important to use the .bytes, since the whole report structure might contain // some padding. int res = hid_write(m_handle, m_report.bytes, sizeof(m_report.bytes)); if (res < 0) CryLogAlways("[MBED]: Could not send HID report to mbed: %ls", hid_error(m_handle)); }
Now that we've got a class that will communicate with our mbed, we just need to call it's functions from Crysis. Open the HUD\HUD.h
file, and make the following changes:
HUD.h
/*near the other includes at the top, add: */ #include "TextLcd.h" /*In the private section of class CHUD, add: */ CTextLcd* m_pTextLcd;
Now, open HUD.cpp
, and make the following changes:
HUD.cpp
/*In the constructor of CHUD (CHUD::CHUD()), add the following towards the end: */ //mbed m_pTextLcd = NULL; /*In the destructor CHUD::~CHUD(), add the following near the other SAFE_DELETEs: */ SAFE_DELETE(m_pTextLcd);//dispose of mbed lcd /*In the function bool CHUD::Init(), add the following to the end, just before 'return true': */ //initialise mbed display. CryLogAlways("[MBED]: Initialising mbed..."); try { m_pTextLcd = new CTextLcd(); m_pTextLcd->Init(); m_pTextLcd->logDeviceInfo(); } catch(const char* err_msg) { CryLogAlways("[MBED]: Failed to initialise mbed: %s", err_msg); } catch(...) { CryLogAlways("[MBED]: Failed to initialise mbed: unknown exception"); } /* In void CHUD::UpdateHealth(), add the following into the "if(m_fHealth != fHealth || m_bFirstFrame)"-block, after the setHealth invocation: */ //update mbed health display if (m_pTextLcd) m_pTextLcd->updateHealth(fHealth - 1.0f);//compensate for +1 above. /* At the end of void CHUD::UpdatePlayerAmmo(), add the following: */ //update mbed if (m_pTextLcd) m_pTextLcd->updateAmmo(m_playerAmmo);
Hit Build->Build solution again. If all went well, it should compile.
Now, to test your mod, it's easiest to create a copy of either the Crysis or the Sandbox 2 Editor shortcut, right-click it and select properties
, and add -mod mbedLcdMod
to the "Target" field. Alternatively, here are some instructions on how to debug your Crysis C++ mods. Now, just load a level, and enjoy your extra HUD!
Where to go from here?
Here are just some ideas for making your own, custom HID device: - Try something similar for your favourite game/serious application - Add some buttons and make them "do stuff" in the game. - Add LEDs, a speaker or other "notification device" to alert you of e.g. helicopters in the area. - your creativity is the only limit (and well, what SDKs / mod kits allow you to do) :)
Thanks for reading, hope you enjoyed it and feel free to post any feedback in the comments!
PS: I'll try to make a binary of the mod available in the near future.
7 comments on mbed HUD for Crysis:
Please log in to post comments.
That looks really cool, I've just started building a PC monitoring system that'll display CPU/GPU temperatures on a touchscreen and I'm currently planning on just using a serial connection over the USB port to pass the data, but this looks like a really nice alternative.
Matt