mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-23 06:57:23 +00:00
606 lines
12 KiB
C++
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
|