hostile-takeover/game/drm.cpp
2016-01-03 23:19:26 -08:00

606 lines
12 KiB
C++

#include "ht.h"
namespace wi {
// The code is derived from the Owner name. The Owner name is backed up and is the same
// after a cold boot; that's why we use it. The Code is simply a non-obvious binary
// translation of the name into a form that can be validated.
struct Code // code
{
byte ab[4];
byte bXor;
byte bSum;
byte abHash[2];
};
// DRM key that gets saved to / loaded from prefs
Key gkey;
bool gfDemo = false;
bool GetCode(char *szName, Code *pcode, bool fShowError = true) secDrm;
bool ValidateCode(Code *pcode) secDrm;
void GetKeyFromCode(Code *pcode, Key *pkey) secDrm;
bool CompareKeys(Key *pkeyA, Key *pkeyB) secDrm;
dword HashBytes(byte *pb, int cb) secDrm;
void FillTemplate(char *psz, char *pszFormat, byte *pb) secDrm;
bool ValidateRetailKey(Key *pkey) secDrm;
dword HashBytes(byte *pb, int cb)
{
dword dwHash = 0;
while (cb-- != 0)
dwHash = (dwHash << 5) + dwHash + *pb++;
return dwHash;
}
int DrmGetRandom(long *plSeed) secDrm;
int DrmGetRandom(long *plSeed)
{
return (((*plSeed) = (*plSeed) * 214013L + 2531011L) >> 16) & 0x7fff;
}
bool GetCode(char *szName, Code *pcode, bool fShowError)
{
if (szName == NULL) {
char szNameT[64];
if (!HostGetOwnerName(szNameT, sizeof(szNameT), fShowError))
return false;
szName = szNameT;
}
long lSeed = (long)HashBytes((byte *)szName, (int)strlen(szName));
// Serialize in endian independent way
Code code;
memset(&code, 0, sizeof(code));
int n;
for (n = 0; n < 4; n++) {
code.ab[n] = (byte)DrmGetRandom(&lSeed);
code.bXor ^= code.ab[n];
code.bSum += code.ab[n];
}
// Hash what we have so far - more validation
word wHash = (word)(HashBytes(code.ab, 6) >> 16);
code.abHash[0] = (byte)wHash;
code.abHash[1] = (byte)(wHash >> 8);
// Now xor the first 6 with abHash[0]
byte *pb = (byte *)&code;
for (n = 0; n < 6; n++)
*pb++ ^= code.abHash[0];
// Now xor the first 7 with abHash[1]
pb = (byte *)&code;
for (n = 0; n < 7; n++)
*pb++ ^= code.abHash[1];
// Done
*pcode = code;
Assert(ValidateCode(&code));
return true;
}
bool ValidateCode(Code *pcode)
{
Code code = *pcode;
// Now xor the first 7 with abHash[1]
byte *pb = (byte *)&code;
int n;
for (n = 0; n < 7; n++)
*pb++ ^= code.abHash[1];
// Now xor the first 6 with abHash[0]
pb = (byte *)&code;
for (n = 0; n < 6; n++)
*pb++ ^= code.abHash[0];
// Hash the first 6 and compare to our hash
word wHash = (word)(HashBytes(code.ab, 6) >> 16);
if (code.abHash[0] != (byte)wHash)
return false;
if (code.abHash[1] != (byte)(wHash >> 8))
return false;
// Xor and add the first 4
byte bXor = 0;
byte bSum = 0;
for (n = 0; n < 4; n++) {
bXor ^= code.ab[n];
bSum += code.ab[n];
}
if (bXor != code.bXor)
return false;
if (bSum != code.bSum)
return false;
return true;
}
void GetKeyFromCode(Code *pcode, Key *pkey)
{
byte *abCode = (byte *)pcode;
int cbCode = sizeof(*pcode);
// Generate a seed
long lSeed = 0x29a;
int n;
for (n = 0; n < cbCode; n++)
lSeed = lSeed * abCode[n] + n;
// Initialize rotors
byte abR1[256];
byte abR2[256];
byte abR3[256];
for (n = 0; n < 256; n++)
abR1[n] = n;
memset(abR2, 0, sizeof(abR2));
memset(abR3, 0, sizeof(abR3));
for (n = 0; n < 256; n++) {
lSeed = 3 * lSeed + abCode[n & 7];
long lRandom = lSeed % 65521;
int ibR1 = (lRandom & 255) % (256 - n);
lRandom >>= 8;
byte bTemp = abR1[255 - n];
abR1[255 - n] = abR1[ibR1];
abR1[ibR1] = bTemp;
if (abR3[255 - n] == 0) {
int nT = (lRandom & 255) % (255 - n);
while (abR3[nT] != 0)
nT = (nT + 1) % (255 - n);
abR3[255 - n] = nT & 255;
abR3[nT] = 255 - n;
}
}
for (n = 0; n < 256; n++)
abR2[abR1[n]] = n;
// Encrypt the code into a key
int nT = 0;
byte *abKey = (byte *)pkey;
int cbKey = sizeof(*pkey);
for (n = 0; n < cbKey; n++) {
int nSlotR1 = abR1[(n + nT) & 255];
int nSlotR3 = abR3[nSlotR1 & 255];
int nSlotR2 = abR2[nSlotR3 & 255];
abKey[n] = abR2[nSlotR2 & 255] - nT;
nT = (nT + 1) & 255;
}
}
bool CompareKeys(Key *pkeyA, Key *pkeyB)
{
byte *pbA = (byte *)pkeyA;
byte *pbB = (byte *)pkeyB;
int cb = sizeof(*pkeyA);
while (cb-- != 0) {
if (*pbA++ != *pbB++)
return false;
}
return true;
}
bool ValidateRetailKey(Key *pkey)
{
#ifdef DRM_KEYONLY
char szT[4];
// Derive the "name" from the key
szT[0] = pkey->ab[0];
long lSeed = (long)szT[0];
szT[1] = pkey->ab[1] ^ (byte)DrmGetRandom(&lSeed);
szT[2] = pkey->ab[2] ^ (byte)DrmGetRandom(&lSeed);
szT[3] = 0;
// Fill in zeros
int n;
for (n = 0; n < sizeof(szT) - 1; n++) {
while (szT[n] == 0)
szT[n] = (byte)DrmGetRandom(&lSeed);
}
// Get code
Code code;
GetCode(szT, &code);
// Gen full key from this
Key keyT;
GetKeyFromCode(&code, &keyT);
// Compare keys - compares the last 6 bytes because we copy over the first 3
Key keyReconstructed = *pkey;
for (n = 0; n < sizeof(szT) - 1; n++)
keyReconstructed.ab[n] = keyT.ab[n];
// Now compare these two keys
return CompareKeys(&keyT, &keyReconstructed);
#else
return false;
#endif
}
//
// UI
//
class DrmCodeForm : public ShellForm
{
public:
virtual bool Init(FormMgr *pfrmm, IniReader *pini, word idf) secDrm;
virtual void OnControlSelected(word idc) secDrm;
};
class DrmKeyForm : public ShellForm
{
public:
virtual bool Init(FormMgr *pfrmm, IniReader *pini, word idf) secDrm;
virtual void OnControlSelected(word idc) secDrm;
private:
void OnBackspace() secDrm;
void OnEnterNumber(int nNumber) secDrm;
void ParseKey(Key *pkey) secDrm;
};
bool DrmValidate()
{
#ifndef DRM
return true;
#else
#ifdef DRM_KEYONLY
// Validate key
if (ValidateRetailKey(&gkey))
return true;
// Key Invalid, bring up UI
ShellForm *pfrm = (ShellForm *)gpmfrmm->LoadForm(gpiniForms, kidfDrmKey, new DrmKeyForm());
if (pfrm == NULL)
return false;
bool fSuccess = pfrm->DoModal();
delete pfrm;
return fSuccess;
#else
#ifdef PIL
// Special Palm codepath to check for FILR resource
DmOpenRef pdb;
int nRec = (short)DmSearchResource('FILR', 0, NULL, &pdb);
bool fHasFILR = (nRec >= 0);
// If our key is valid, return success
Code code;
if (!GetCode(NULL, &code, !fHasFILR)) {
// No code, which means not hotsynced yet. If we have a FILR resource, let copy protect succeed.
// We'll mark this binary later after the device has been hotsynced. Note there is a window where by
// someone could receive WI before hotsyncing, run it, and then beam it to someone else who could also
// run it.
if (fHasFILR)
return true;
// Go into demo mode
HtMessageBox(kfMbClearDib, "Hostile Takeover", "License key not entered. Entering demo mode.");
gfDemo = true;
return true;
}
Key keyT;
GetKeyFromCode(&code, &keyT);
// Check for special resource. If present, mark the resource, copy a working key into gkey, save prefs
// and return. This way if someone beams this game to another device without the key or resource, the
// DRM form will come up
if (fHasFILR) {
// Found resource; make sure it is on our .prc
LocalID lidRes;
DmOpenDatabaseInfo(pdb, &lidRes, NULL, NULL, NULL, NULL);
LocalID lidApp;
UInt16 nCard;
SysCurAppDatabase (&nCard, &lidApp);
if (lidApp == lidRes) {
// If it starts with a 0, it's been poked already
// Otherwise it is first time; copy over a valid retail key
#define memHeapFlagReadOnly 0x01
MemHandle hMem = DmGetResourceIndex(pdb, nRec);
UInt16 idHeap = MemHandleHeapID(hMem);
if (!(MemHeapFlags(idHeap) & memHeapFlagReadOnly)) {
char *psz = (char *)MemHandleLock(hMem);
if (*psz != 0) {
char ch = 0;
DmWrite(psz, 0, &ch, 1);
gkey = keyT;
ggame.SavePreferences();
}
MemHandleUnlock(hMem);
}
}
}
#else
// If our key is valid, return success
Code code;
if (!GetCode(NULL, &code))
return false;
Key keyT;
GetKeyFromCode(&code, &keyT);
#endif
// Compare keys
if (CompareKeys(&gkey, &keyT))
return true;
// No valid key; bring up UI
ShellForm *pfrm = (ShellForm *)gpmfrmm->LoadForm(gpiniForms, kidfDrmCode, new DrmCodeForm());
if (pfrm == NULL)
return false;
gfDemo = !pfrm->DoModal();
delete pfrm;
return true;
#endif
#endif
}
void FillTemplate(char *psz, char *pszFormat, byte *pb)
{
char *pchSrc = pszFormat;
char *pchDst = psz;
bool fMSB = true;
byte bT = 0;
int cb = (int)strlen(pszFormat) + 1;
while (cb-- != 0) {
if (*pchSrc != '?') {
*pchDst++ = *pchSrc++;
continue;
}
int nNibble = 0;
if (fMSB) {
fMSB = false;
bT = *pb++;
nNibble = (bT >> 4) & 0x0f;
} else {
fMSB = true;
nNibble = (bT & 0x0f);
}
char chT;
if (nNibble < 0x0a) {
chT = '0' + nNibble;
} else {
chT = 'A' + nNibble - 0x0a;
}
*pchDst++ = chT;
pchSrc++;
}
}
//
// DrmCodeForm implementation
//
bool DrmCodeForm::Init(FormMgr *pfrmm, IniReader *pini, word idf)
{
if (!ShellForm::Init(pfrmm, pini, idf))
return false;
Code code;
if (!GetCode(NULL, &code))
return false;
char szT[32];
FillTemplate(szT, "C-\?\?\?\?-\?\?\?\?-\?\?\?\?-\?\?\?\?", (byte *)&code);
LabelControl *plbl = (LabelControl *)GetControlPtr(kidcCode);
plbl->SetText(szT);
return true;
}
void DrmCodeForm::OnControlSelected(word idc)
{
switch (idc) {
case kidcPlayDemo:
break;
case kidcEnterKey:
{
ShellForm *pfrm = (ShellForm *)gpmfrmm->LoadForm(gpiniForms, kidfDrmKey, new DrmKeyForm());
if (pfrm == NULL)
break;
bool f = pfrm->DoModal();
delete pfrm;
if (f) {
m_idcLast = kidcOk;
break;
}
return;
}
break;
}
ShellForm::OnControlSelected(idc);
}
//
// DrmKeyForm implementation
//
bool DrmKeyForm::Init(FormMgr *pfrmm, IniReader *pini, word idf)
{
return ShellForm::Init(pfrmm, pini, idf);
}
void DrmKeyForm::OnEnterNumber(int nNumber)
{
char szT[32];
LabelControl *plbl = (LabelControl *)GetControlPtr(kidcKey);
strncpyz(szT, plbl->GetText(), sizeof(szT));
// Find first '?'
int cch = (int)strlen(szT);
int n;
for (n = 0; n < cch; n++) {
if (szT[n] == '?')
break;
}
if (n == cch)
return;
// Put new number there
char ch = nNumber < 0xa ? '0' + nNumber : 'A' + nNumber - 0xa;
szT[n] = ch;
plbl->SetText(szT);
}
void DrmKeyForm::OnBackspace()
{
char szT[32];
LabelControl *plbl = (LabelControl *)GetControlPtr(kidcKey);
strncpyz(szT, plbl->GetText(), sizeof(szT));
// Find last number
int cch = (int)strlen(szT);
int n;
for (n = cch - 1; n >= 0; n--) {
if ((szT[n] >= '0' && szT[n] <= '9') || (szT[n] >= 'A' && szT[n] <= 'F'))
break;
}
if (n < 0)
return;
// Put '?' there
szT[n] = '?';
plbl->SetText(szT);
}
void DrmKeyForm::ParseKey(Key *pkey)
{
// Init key to 0
byte *pb = (byte *)pkey;
int cb = sizeof(*pkey);
memset(pb, 0, cb);
// Parse out key
LabelControl *plbl = (LabelControl *)GetControlPtr(kidcKey);
const char *psz = plbl->GetText();
// Find last number
int cch = (int)strlen(psz);
int n;
bool fMSB = true;
for (n = 0; n < cch; n++) {
if ((psz[n] >= '0' && psz[n] <= '9') || (psz[n] >= 'A' && psz[n] <= 'F')) {
int nNibble;
if (psz[n] >= 'A') {
nNibble = psz[n] - 'A' + 0xa;
} else {
nNibble = psz[n] - '0';
}
if (fMSB) {
fMSB = false;
*pb = nNibble << 4;
} else {
fMSB = true;
*pb |= nNibble;
pb++;
}
}
}
}
void DrmKeyForm::OnControlSelected(word idc)
{
switch (idc) {
case kidcOk:
{
// Valid?
Key key;
ParseKey(&key);
#ifdef DRM_KEYONLY
bool fSuccess = ValidateRetailKey(&key);
#else
Code code;
GetCode(NULL, &code);
Key keyT;
GetKeyFromCode(&code, &keyT);
bool fSuccess = CompareKeys(&key, &keyT);
#endif
if (!fSuccess) {
HtMessageBox(kfMbWhiteBorder, "License Key", "The key you have entered is invalid!");
return;
}
// Good key. Save prefs.
HtMessageBox(kfMbWhiteBorder, "License Key", "Thank you.");
gkey = key;
ggame.SavePreferences();
}
// fall through
case kidcCancel:
ShellForm::OnControlSelected(idc);
break;
case kidcBackspace:
OnBackspace();
break;
default:
OnEnterNumber(idc - kidc0);
break;
}
}
} // namespace wi