354 lines
14 KiB
C
354 lines
14 KiB
C
// This file is part of SynthXEX, one component of the
|
|
// FreeChainXenon development toolchain
|
|
//
|
|
// Copyright (c) 2024-25 Aiden Isik
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program 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 Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#include "optheaders.h"
|
|
|
|
void setBasefileFormat(struct basefileFormat *basefileFormat, struct secInfoHeader *secInfoHeader)
|
|
{
|
|
basefileFormat->size = (1 * 8) + 8; // (Block count * size of raw data descriptor) + size of data descriptor
|
|
basefileFormat->encType = 0x0; // No encryption
|
|
basefileFormat->compType = 0x1; // No compression
|
|
basefileFormat->dataSize = secInfoHeader->peSize;
|
|
basefileFormat->zeroSize = 0x0; // We aren't going to be removing any zeroes. TODO: implement this, it can make files much smaller
|
|
}
|
|
|
|
// STUB. TLS info not supported.
|
|
void setTLSInfo(struct tlsInfo *tlsInfo)
|
|
{
|
|
tlsInfo->slotCount = 0x40;
|
|
tlsInfo->rawDataAddr = 0x0;
|
|
tlsInfo->dataSize = 0x0;
|
|
tlsInfo->rawDataSize = 0x0;
|
|
}
|
|
|
|
int setImportLibsInfo(struct importLibraries *importLibraries, struct peImportInfo *peImportInfo, struct secInfoHeader *secInfoHeader)
|
|
{
|
|
// Set table count and allocate enough memory for all tables
|
|
importLibraries->tableCount = peImportInfo->tableCount;
|
|
secInfoHeader->importTableCount = peImportInfo->tableCount;
|
|
|
|
importLibraries->importTables = calloc(importLibraries->tableCount, sizeof(struct importTable));
|
|
|
|
if(!importLibraries->importTables)
|
|
{ return ERR_OUT_OF_MEM; }
|
|
|
|
if(peImportInfo->tableCount <= 0 || peImportInfo->tableCount > 65535)
|
|
{ return ERR_OUT_OF_MEM; }
|
|
|
|
// Use this to avoid dereferencing an unaligned pointer
|
|
struct importTable *importTables = importLibraries->importTables;
|
|
|
|
// Initialise the size of the import libraries to just the size of the header (- 2 * sizeof(void*) to exclude addresses for internal use only)
|
|
importLibraries->size = (sizeof(struct importLibraries) + importLibraries->nameTableSize) - (2 * sizeof(void *));
|
|
|
|
int ret = ERR_INVALID_IMPORT_NAME;
|
|
|
|
// Allocate name list
|
|
char **names = calloc(importLibraries->tableCount, sizeof(char *));
|
|
|
|
if(!names)
|
|
{ goto cleanup_tables; }
|
|
|
|
// Populate each table, then compute it's hash and store in the previous table
|
|
for(int64_t i = importLibraries->tableCount - 1; i >= 0; i--)
|
|
{
|
|
// Set the table index field to the current index
|
|
importTables[i].tableIndex = i;
|
|
|
|
// Extract the name, target, and minimum versions from the name string
|
|
// Major and minor version are always 2 and 0, respectively
|
|
// Strings are definitely null-terminated as otherwise they couldn't have been read in
|
|
const uint8_t majorVer = 2;
|
|
const uint8_t minorVer = 0;
|
|
char *targetBuildVerStr = NULL;
|
|
char *targetHotfixVerStr = NULL;
|
|
char *minimumBuildVerStr = NULL;
|
|
char *minimumHotfixVerStr = NULL;
|
|
uint16_t buildVer = 0;
|
|
uint8_t hotfixVer = 0;
|
|
char *strtoulRet = NULL;
|
|
|
|
// Get the name
|
|
uint32_t oldNameLen = strlen(peImportInfo->tables[i].name);
|
|
names[i] = strtok(peImportInfo->tables[i].name, "@");
|
|
|
|
if(!peImportInfo->tables[i].name)
|
|
{ goto cleanup_names_invalid; } // Encountered '\0', not '@'
|
|
|
|
// Target versions first
|
|
targetBuildVerStr = strtok(NULL, ".");
|
|
|
|
if(!targetBuildVerStr)
|
|
{ goto cleanup_names_invalid; }
|
|
|
|
if(strlen(names[i]) + 1 + strlen(targetBuildVerStr) == oldNameLen)
|
|
{ goto cleanup_names_invalid; } // Encountered null terminator instead of '.'
|
|
|
|
buildVer = (uint16_t)strtoul(targetBuildVerStr, &strtoulRet, 10);
|
|
|
|
if(*strtoulRet != 0 || strtoulRet == targetBuildVerStr)
|
|
{ goto cleanup_names_invalid; } // Encountered a non-number, or string was empty
|
|
|
|
targetHotfixVerStr = strtok(NULL, "+");
|
|
|
|
if(!targetHotfixVerStr)
|
|
{ goto cleanup_names_invalid; }
|
|
|
|
if(strlen(names[i]) + 1 + strlen(targetBuildVerStr) + 1 + strlen(targetHotfixVerStr) == oldNameLen)
|
|
{ goto cleanup_names_invalid; }
|
|
|
|
hotfixVer = (uint8_t)strtoul(targetHotfixVerStr, &strtoulRet, 10);
|
|
|
|
if(*strtoulRet != 0 || strtoulRet == targetHotfixVerStr)
|
|
{ goto cleanup_names_invalid; }
|
|
|
|
// Now pack these into the target version bitfield
|
|
importTables[i].targetVer =
|
|
((majorVer & 0xF) << 28) |
|
|
((minorVer & 0xF) << 24) |
|
|
(buildVer << 8) |
|
|
hotfixVer;
|
|
|
|
// Now onto the minimum versions, this works much the same
|
|
minimumBuildVerStr = strtok(NULL, ".");
|
|
|
|
if(!minimumBuildVerStr)
|
|
{ goto cleanup_names_invalid; } // No more tokens
|
|
|
|
if(strlen(names[i]) + 1 + strlen(targetBuildVerStr) + 1 + strlen(targetHotfixVerStr)
|
|
+ 1 + strlen(minimumBuildVerStr) == oldNameLen)
|
|
{ goto cleanup_names_invalid; } // Encountered null terminator instead of '.'
|
|
|
|
buildVer = (uint16_t)strtoul(minimumBuildVerStr, &strtoulRet, 10);
|
|
|
|
if(*strtoulRet != 0 || strtoulRet == minimumBuildVerStr)
|
|
{ goto cleanup_names_invalid; } // Encountered a non-number, or string was empty
|
|
|
|
minimumHotfixVerStr = strtok(NULL, "\0");
|
|
|
|
if(!minimumHotfixVerStr)
|
|
{ goto cleanup_names_invalid; }
|
|
|
|
hotfixVer = (uint8_t)strtoul(minimumHotfixVerStr, &strtoulRet, 10);
|
|
|
|
if(*strtoulRet != 0 || strtoulRet == minimumHotfixVerStr)
|
|
{ goto cleanup_names_invalid; }
|
|
|
|
// Now pack these into the minimum version bitfield
|
|
importTables[i].minimumVer =
|
|
((majorVer & 0xF) << 28) |
|
|
((minorVer & 0xF) << 24) |
|
|
(buildVer << 8) |
|
|
hotfixVer;
|
|
|
|
// Hardcode a currently unknown value. TODO: find out how this is calculated.
|
|
if(strcmp(names[i], "xboxkrnl.exe") == 0)
|
|
{ importTables[i].unknown = 0x45DC17E0; }
|
|
else if(strcmp(names[i], "xam.xex") == 0)
|
|
{ importTables[i].unknown = 0xFCA15C76; }
|
|
else if(strcmp(names[i], "xbdm.xex") == 0)
|
|
{ importTables[i].unknown = 0xECEB8109; }
|
|
else
|
|
{ goto cleanup_names_invalid; }
|
|
|
|
// Determine the number of addresses
|
|
importTables[i].addressCount = peImportInfo->tables[i].importCount;
|
|
|
|
// Allocate enough memory for the addresses
|
|
importTables[i].addresses = calloc(importTables[i].addressCount, sizeof(uint32_t));
|
|
|
|
if(!importTables[i].addresses)
|
|
{ goto cleanup_names_invalid; }
|
|
|
|
uint32_t *addresses = importTables[i].addresses; // Use this to avoid dereferencing an unaligned pointer
|
|
uint16_t currentAddr = 0;
|
|
|
|
// Populate the addresses
|
|
for(uint16_t j = 0; j < peImportInfo->tables[i].importCount; j++)
|
|
{
|
|
if(currentAddr >= importTables[i].addressCount)
|
|
{ goto cleanup_names_invalid; }
|
|
|
|
addresses[currentAddr++] = peImportInfo->tables[i].imports[j].iatAddr;
|
|
}
|
|
|
|
// Determine the total size, in bytes, of the current table (- sizeof(void*) to exclude address to addresses at the end)
|
|
importTables[i].size = (sizeof(struct importTable) - sizeof(void *) + (importTables[i].addressCount *sizeof(uint32_t)));
|
|
importLibraries->size += importTables[i].size;
|
|
|
|
// Init sha1 hash
|
|
struct sha1_ctx shaContext;
|
|
memset(&shaContext, 0, sizeof(shaContext));
|
|
sha1_init(&shaContext);
|
|
|
|
// On little endian this ensures the byteswapped address count doesn't cause any trouble when using it.
|
|
// On big endian it's pointless but here so the code isn't too complex with differences between endianness.
|
|
uint16_t addressCount = importTables[i].addressCount;
|
|
|
|
// If we're on a little endian system, swap everything into big endian for hashing
|
|
#ifdef LITTLE_ENDIAN_SYSTEM
|
|
importTables[i].size = __builtin_bswap32(importTables[i].size);
|
|
importTables[i].unknown = __builtin_bswap32(importTables[i].unknown);
|
|
importTables[i].targetVer = __builtin_bswap32(importTables[i].targetVer);
|
|
importTables[i].minimumVer = __builtin_bswap32(importTables[i].minimumVer);
|
|
importTables[i].addressCount = __builtin_bswap16(importTables[i].addressCount);
|
|
|
|
// Byteswap the addresses
|
|
for(uint16_t j = 0; j < addressCount; j++)
|
|
{ addresses[j] = __builtin_bswap32(addresses[j]); }
|
|
|
|
#endif
|
|
// - sizeof(void*) to exclude the address to the addresses at the end (not part of the XEX format)
|
|
// +/- sizeof(uint32_t) to exclude table size from hash
|
|
sha1_update(&shaContext, sizeof(struct importTable) - sizeof(void *) - sizeof(uint32_t), (void *)&importTables[i] + sizeof(uint32_t));
|
|
sha1_update(&shaContext, addressCount *sizeof(uint32_t), (void *)addresses);
|
|
|
|
// If we're on a little endian system, swap everything back into little endian
|
|
#ifdef LITTLE_ENDIAN_SYSTEM
|
|
importTables[i].size = __builtin_bswap32(importTables[i].size);
|
|
importTables[i].unknown = __builtin_bswap32(importTables[i].unknown);
|
|
importTables[i].targetVer = __builtin_bswap32(importTables[i].targetVer);
|
|
importTables[i].minimumVer = __builtin_bswap32(importTables[i].minimumVer);
|
|
importTables[i].addressCount = __builtin_bswap16(importTables[i].addressCount);
|
|
|
|
// Byteswap the addresses
|
|
for(uint16_t j = 0; j < addressCount; j++)
|
|
{ addresses[j] = __builtin_bswap32(addresses[j]); }
|
|
|
|
#endif
|
|
|
|
sha1_digest(&shaContext, 0x14, i != 0 ? importTables[i - 1].sha1 : secInfoHeader->importTableSha1);
|
|
}
|
|
|
|
// Allocate offset table
|
|
uint32_t *nameOffsets = calloc(importLibraries->tableCount, sizeof(uint32_t));
|
|
|
|
if(!nameOffsets)
|
|
{ goto cleanup_names; }
|
|
|
|
for(uint32_t i = 0; i < importLibraries->tableCount; i++)
|
|
{
|
|
nameOffsets[i] = importLibraries->nameTableSize;
|
|
importLibraries->nameTableSize += getNextAligned(strlen(names[i]) + 1, sizeof(uint32_t));
|
|
}
|
|
|
|
importLibraries->size += importLibraries->nameTableSize;
|
|
importLibraries->nameTable = calloc(importLibraries->nameTableSize, sizeof(char));
|
|
|
|
if(!importLibraries->nameTable)
|
|
{ goto cleanup_offsets; }
|
|
|
|
// Use this to avoid dereferencing an unaligned pointer
|
|
char *nameTable = importLibraries->nameTable;
|
|
|
|
// Populate the name table
|
|
for(uint32_t i = 0; i < importLibraries->tableCount; i++)
|
|
{ strcpy(&(nameTable[nameOffsets[i]]), names[i]); }
|
|
|
|
nullAndFree((void **)&nameOffsets);
|
|
nullAndFree((void **)&names);
|
|
return SUCCESS;
|
|
|
|
cleanup_offsets:
|
|
nullAndFree((void **)&nameOffsets);
|
|
cleanup_names:
|
|
|
|
for(uint32_t i = 0; i < importLibraries->tableCount; i++)
|
|
{ nullAndFree((void **) & (importTables[i].addresses)); }
|
|
|
|
cleanup_names_invalid:
|
|
nullAndFree((void **)&names);
|
|
cleanup_tables:
|
|
nullAndFree((void **) & (importLibraries->importTables));
|
|
importLibraries->importTables = NULL;
|
|
return ret;
|
|
}
|
|
|
|
// STUB. TODO: Dynamically select, and/or allow user to select, flags
|
|
void setSysFlags(uint32_t *flags)
|
|
{
|
|
if(flags == NULL)
|
|
{ return; }
|
|
|
|
*flags = XEX_SYS_GAMEPAD_DISCONNECT |
|
|
XEX_SYS_INSECURE_SOCKETS |
|
|
XEX_SYS_XAM_HOOKS |
|
|
XEX_SYS_BACKGROUND_DL |
|
|
XEX_SYS_ALLOW_CONTROL_SWAP;
|
|
}
|
|
|
|
int setOptHeaders(struct secInfoHeader *secInfoHeader, struct peData *peData, struct optHeaderEntries *optHeaderEntries, struct optHeaders *optHeaders)
|
|
{
|
|
bool importsPresent = (peData->peImportInfo.totalImportCount > 0) ? true : false;
|
|
|
|
optHeaderEntries->count = 4;
|
|
|
|
if(importsPresent)
|
|
{ optHeaderEntries->count++; }
|
|
|
|
optHeaderEntries->optHeaderEntry = calloc(optHeaderEntries->count, sizeof(struct optHeaderEntry));
|
|
|
|
if(optHeaderEntries->optHeaderEntry == NULL)
|
|
{ return ERR_OUT_OF_MEM; }
|
|
|
|
uint32_t currentHeader = 0;
|
|
|
|
// NOTE: Make sure that these headers are handled IN ORDER OF ID. The loader will reject the XEX if they are not.
|
|
|
|
// Basefile format (0x003FF)
|
|
setBasefileFormat(&(optHeaders->basefileFormat), secInfoHeader);
|
|
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_BASEFILE_FORMAT;
|
|
currentHeader++;
|
|
|
|
// Entrypoint (0x10100)
|
|
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_ENTRYPOINT;
|
|
optHeaderEntries->optHeaderEntry[currentHeader].dataOrOffset = secInfoHeader->baseAddr + peData->entryPoint;
|
|
currentHeader++;
|
|
|
|
// Import libraries (0x103FF)
|
|
if(importsPresent)
|
|
{
|
|
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_IMPORT_LIBS;
|
|
int ret = setImportLibsInfo(&(optHeaders->importLibraries), &(peData->peImportInfo), secInfoHeader);
|
|
|
|
if(ret != SUCCESS)
|
|
{ return ret; }
|
|
|
|
currentHeader++;
|
|
}
|
|
|
|
// TLS info (0x20104)
|
|
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_TLS_INFO;
|
|
setTLSInfo(&(optHeaders->tlsInfo));
|
|
currentHeader++;
|
|
|
|
// System flags (0x30000)
|
|
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_SYS_FLAGS;
|
|
|
|
// We're using the name of the first element of the struct for clarity,
|
|
// it can never be an unaligned access, so ignore that warning.
|
|
// Also works for Clang.
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Waddress-of-packed-member"
|
|
setSysFlags(&(optHeaderEntries->optHeaderEntry[currentHeader].dataOrOffset));
|
|
#pragma GCC diagnostic pop
|
|
|
|
//currentHeader++;
|
|
|
|
return SUCCESS;
|
|
}
|