2022-08-22 22:21:23 +02:00
# include "Cafe/GraphicPack/GraphicPack2.h"
2022-09-07 02:42:25 +02:00
# include "Common/FileStream.h"
2022-08-22 22:21:23 +02:00
# include "util/helpers/StringParser.h"
# include "Cemu/PPCAssembler/ppcAssembler.h"
# include "Cafe/OS/RPL/rpl_structs.h"
sint32 GraphicPack2 : : GetLengthWithoutComment ( const char * str , size_t length )
{
sint32 index = 0 ;
bool isInString = false ;
while ( index < length )
{
const char c = str [ index ] ;
if ( c = = ' \" ' )
isInString = ! isInString ;
else if ( c = = ' # ' | | c = = ' ; ' )
{
if ( ! isInString )
return index ;
}
index + + ;
}
return ( sint32 ) length ;
}
void GraphicPack2 : : LogPatchesSyntaxError ( sint32 lineNumber , std : : string_view errorMsg )
{
cemuLog_log ( LogType : : Force , fmt : : format ( L " Syntax error while parsing patch for graphic pack '{}': " , this - > GetFilename ( ) ) ) ;
if ( lineNumber > = 0 )
cemuLog_log ( LogType : : Force , fmt : : format ( " Line {0}: {1} " , lineNumber , errorMsg ) ) ;
else
cemuLog_log ( LogType : : Force , fmt : : format ( " {0} " , errorMsg ) ) ;
list_patchGroups . clear ( ) ;
}
void GraphicPack2 : : CancelParsingPatches ( )
{
// unload everything, set error flag
cemu_assert_debug ( false ) ;
}
void GraphicPack2 : : AddPatchGroup ( PatchGroup * group )
{
if ( group - > list_moduleMatches . empty ( ) )
{
LogPatchesSyntaxError ( - 1 , fmt : : format ( " Group \" {} \" has no moduleMatches definition " , group - > name ) ) ;
CancelParsingPatches ( ) ;
delete group ;
return ;
}
// calculate code cave size
uint32 codeCaveMaxAddr = 0 ;
for ( auto & itr : group - > list_patches )
{
PatchEntryInstruction * patchData = dynamic_cast < PatchEntryInstruction * > ( itr ) ;
if ( patchData )
{
uint32 patchAddr = patchData - > getAddr ( ) ;
if ( patchAddr < 0x00100000 )
{
// everything in low 1MB of memory we consider part of the code cave
codeCaveMaxAddr = std : : max ( codeCaveMaxAddr , patchAddr + patchData - > getSize ( ) ) ;
}
}
}
uint32 numEstimatedCodeCaveInstr = codeCaveMaxAddr / 4 ;
if ( group - > list_patches . size ( ) < ( numEstimatedCodeCaveInstr / 8 ) )
{
// if less than 1/8th of the code cave is filled print a warning
forceLog_printf ( " Graphic pack patches: Code cave for group [%s] in gfx pack \" %s \" ranges from 0 to 0x%x but has only few instructions. Is this intentional? " , group - > name . c_str ( ) , this - > m_name . c_str ( ) , codeCaveMaxAddr ) ;
}
group - > codeCaveSize = codeCaveMaxAddr ;
list_patchGroups . emplace_back ( group ) ;
}
void GraphicPack2 : : ParseCemuhookPatchesTxtInternal ( MemStreamReader & patchesStream )
{
sint32 lineNumber = 0 ;
PatchGroup * currentGroup = nullptr ;
while ( true )
{
auto lineStr = patchesStream . readLine ( ) ;
lineNumber + + ;
if ( patchesStream . hasError ( ) )
break ;
// trim comment
size_t lineLength = GetLengthWithoutComment ( lineStr . data ( ) , lineStr . size ( ) ) ;
StringTokenParser parser ( lineStr . data ( ) , ( sint32 ) lineLength ) ;
// skip whitespaces at the beginning
parser . skipWhitespaces ( ) ;
// parse line
if ( parser . isEndOfString ( ) )
continue ;
if ( parser . compareCharacter ( 0 , ' [ ' ) )
{
// group
parser . skipCharacters ( 1 ) ;
// find end of group name
const char * groupNameStr = parser . getCurrentPtr ( ) ;
sint32 groupNameLength = parser . skipToCharacter ( ' ] ' ) ;
if ( groupNameLength < 0 )
{
LogPatchesSyntaxError ( lineNumber , " Expected ']' " ) ;
CancelParsingPatches ( ) ;
return ;
}
parser . skipCharacters ( 1 ) ; // skip the ']'
parser . skipWhitespaces ( ) ;
if ( ! parser . isEndOfString ( ) )
{
LogPatchesSyntaxError ( lineNumber , " Unexpected characters after ']' " ) ;
CancelParsingPatches ( ) ;
return ;
}
// begin new group
if ( currentGroup )
{
AddPatchGroup ( currentGroup ) ;
}
currentGroup = new PatchGroup ( this , groupNameStr , groupNameLength ) ;
}
else if ( parser . compareCharacter ( 0 , ' 0 ' ) & & parser . compareCharacterI ( 1 , ' x ' ) )
{
// if the line starts with a hex address then it is a patched location
uint32 patchedAddress ;
if ( ! parser . parseU32 ( patchedAddress ) )
{
LogPatchesSyntaxError ( lineNumber , " Malformed address " ) ;
CancelParsingPatches ( ) ;
return ;
}
if ( parser . matchWordI ( " = " ) = = false )
{
LogPatchesSyntaxError ( lineNumber , " Expected '=' after address " ) ;
CancelParsingPatches ( ) ;
return ;
}
parser . skipWhitespaces ( ) ;
parser . trimWhitespaces ( ) ;
// assemble instruction
std : : string instrText ( parser . getCurrentPtr ( ) , parser . getCurrentLen ( ) ) ;
PPCAssemblerInOut ctx { } ;
ctx . virtualAddress = patchedAddress ;
if ( ! ppcAssembler_assembleSingleInstruction ( instrText . c_str ( ) , & ctx ) )
{
LogPatchesSyntaxError ( lineNumber , fmt : : format ( " Error in assembler: {} " , ctx . errorMsg ) ) ;
CancelParsingPatches ( ) ;
return ;
}
currentGroup - > list_patches . emplace_back ( new PatchEntryInstruction ( lineNumber , patchedAddress , { ctx . outputData . data ( ) , ctx . outputData . size ( ) } , ctx . list_relocs ) ) ;
}
else if ( parser . matchWordI ( " moduleMatches " ) )
{
if ( currentGroup = = nullptr )
{
LogPatchesSyntaxError ( lineNumber , " Specified 'ModuleMatches' outside of a group " ) ;
CancelParsingPatches ( ) ;
return ;
}
if ( parser . matchWordI ( " = " ) = = false )
{
LogPatchesSyntaxError ( lineNumber , " Expected '=' after ModuleMatches " ) ;
CancelParsingPatches ( ) ;
return ;
}
// read the checksums
while ( true )
{
uint32 checksum = 0 ;
if ( parser . parseU32 ( checksum ) = = false )
{
LogPatchesSyntaxError ( lineNumber , " Invalid value for ModuleMatches " ) ;
CancelParsingPatches ( ) ;
return ;
}
currentGroup - > list_moduleMatches . emplace_back ( checksum ) ;
if ( parser . matchWordI ( " , " ) = = false )
break ;
}
parser . skipWhitespaces ( ) ;
if ( ! parser . isEndOfString ( ) )
{
LogPatchesSyntaxError ( lineNumber , " Unexpected character in line " ) ;
CancelParsingPatches ( ) ;
return ;
}
continue ;
}
else
{
// Cemuhook requires that user defined symbols start with _ but we are more lenient and allow them to start with letters too
// the downside is that there is some ambiguity and parsing gets a little bit more complex
// check for <symbolName> = pattern
StringTokenParser bakParser ;
const char * symbolStr ;
sint32 symbolLen ;
parser . storeParserState ( & bakParser ) ;
if ( parser . parseSymbolName ( symbolStr , symbolLen ) & & parser . matchWordI ( " = " ) )
{
// matches pattern: <symbolName> = ...
parser . skipWhitespaces ( ) ;
parser . trimWhitespaces ( ) ;
const char * expressionStr = parser . getCurrentPtr ( ) ;
sint32 expressionLen = parser . getCurrentLen ( ) ;
// create entry for symbol value assignment
currentGroup - > list_patches . emplace_back ( new PatchEntryCemuhookSymbolValue ( lineNumber , symbolStr , symbolLen , expressionStr , expressionLen ) ) ;
continue ;
}
else
{
LogPatchesSyntaxError ( lineNumber , fmt : : format ( " Invalid syntax " ) ) ;
CancelParsingPatches ( ) ;
return ;
}
}
}
if ( currentGroup )
AddPatchGroup ( currentGroup ) ;
}
static inline uint32 INVALID_ORIGIN = 0xFFFFFFFF ;
bool GraphicPack2 : : ParseCemuPatchesTxtInternal ( MemStreamReader & patchesStream )
{
sint32 lineNumber = 0 ;
PatchGroup * currentGroup = nullptr ;
struct
{
void reset ( )
{
currentOrigin = INVALID_ORIGIN ;
codeCaveOrigin = 0 ;
}
void setOrigin ( uint32 origin )
{
currentOrigin = origin ;
}
void setOriginCodeCave ( )
{
currentOrigin = codeCaveOrigin ;
}
bool isValidOrigin ( )
{
return currentOrigin ! = INVALID_ORIGIN ;
}
void incrementOrigin ( uint32 size )
{
currentOrigin + = size ;
if ( currentOrigin < = 32 * 1024 * 1024 )
codeCaveOrigin = std : : max ( codeCaveOrigin , currentOrigin ) ;
}
uint32 currentOrigin { } ;
uint32 codeCaveOrigin { } ;
} originInfo ;
// labels dont get emitted immediately, instead they are assigned a VA after the next alignment zone
std : : vector < PatchEntryLabel * > scheduledLabels ;
// this is to prevent code like this from putting alignment bytes after the label. (The label 'sticks' to the data after it)
// .byte 123
// Label:
// BLR
auto flushLabels = [ & ] ( )
{
// flush remaining labels
for ( auto & itr : scheduledLabels )
{
itr - > setAssignedVA ( originInfo . currentOrigin ) ;
currentGroup - > list_patches . emplace_back ( itr ) ;
}
scheduledLabels . clear ( ) ;
} ;
while ( true )
{
size_t lineLength ;
auto lineStr = patchesStream . readLine ( ) ;
lineNumber + + ;
if ( patchesStream . hasError ( ) )
break ;
// trim comment
lineLength = GetLengthWithoutComment ( lineStr . data ( ) , lineStr . size ( ) ) ;
StringTokenParser parser ( lineStr . data ( ) , ( sint32 ) lineLength ) ;
// skip whitespaces at the beginning
parser . skipWhitespaces ( ) ;
// parse line
if ( parser . isEndOfString ( ) )
continue ;
if ( parser . compareCharacter ( 0 , ' [ ' ) )
{
// group
parser . skipCharacters ( 1 ) ;
// find end of group name
const char * groupNameStr = parser . getCurrentPtr ( ) ;
sint32 groupNameLength = parser . skipToCharacter ( ' ] ' ) ;
if ( groupNameLength < 0 )
{
LogPatchesSyntaxError ( lineNumber , " Expected ']' " ) ;
CancelParsingPatches ( ) ;
return false ;
}
parser . skipCharacters ( 1 ) ; // skip the ']'
parser . skipWhitespaces ( ) ;
if ( ! parser . isEndOfString ( ) )
{
LogPatchesSyntaxError ( lineNumber , " Unexpected characters after ']' " ) ;
CancelParsingPatches ( ) ;
return false ;
}
// begin new group
if ( currentGroup )
{
flushLabels ( ) ;
AddPatchGroup ( currentGroup ) ;
}
currentGroup = new PatchGroup ( this , groupNameStr , groupNameLength ) ;
// reset origin tracking
originInfo . reset ( ) ;
continue ;
}
else if ( parser . matchWordI ( " moduleMatches " ) )
{
if ( currentGroup = = nullptr )
{
LogPatchesSyntaxError ( lineNumber , " Specified 'ModuleMatches' outside of a group " ) ;
CancelParsingPatches ( ) ;
return false ;
}
if ( parser . matchWordI ( " = " ) = = false )
{
LogPatchesSyntaxError ( lineNumber , " Expected '=' after ModuleMatches " ) ;
CancelParsingPatches ( ) ;
return false ;
}
// read the checksums
while ( true )
{
uint32 checksum = 0 ;
if ( parser . parseU32 ( checksum ) = = false )
{
LogPatchesSyntaxError ( lineNumber , " Invalid value for ModuleMatches " ) ;
CancelParsingPatches ( ) ;
return false ;
}
currentGroup - > list_moduleMatches . emplace_back ( checksum ) ;
if ( parser . matchWordI ( " , " ) = = false )
break ;
}
parser . skipWhitespaces ( ) ;
if ( ! parser . isEndOfString ( ) )
{
LogPatchesSyntaxError ( lineNumber , " Unexpected character " ) ;
CancelParsingPatches ( ) ;
return false ;
}
continue ;
}
// if a line starts with <hex_address> = then it temporarily overwrites the origin for the current line
uint32 overwriteOrigin = INVALID_ORIGIN ;
if ( parser . compareCharacter ( 0 , ' 0 ' ) & & parser . compareCharacterI ( 1 , ' x ' ) )
{
uint32 patchedAddress ;
if ( ! parser . parseU32 ( patchedAddress ) )
{
LogPatchesSyntaxError ( lineNumber , " Malformed address " ) ;
CancelParsingPatches ( ) ;
return false ;
}
if ( parser . matchWordI ( " = " ) = = false )
{
LogPatchesSyntaxError ( lineNumber , " Expected '=' after address " ) ;
CancelParsingPatches ( ) ;
return false ;
}
parser . skipWhitespaces ( ) ;
parser . trimWhitespaces ( ) ;
overwriteOrigin = patchedAddress ;
}
// check for known directives
if ( parser . matchWordI ( " .origin " ) )
{
// .origin = <origin> directive
if ( overwriteOrigin ! = INVALID_ORIGIN )
{
LogPatchesSyntaxError ( lineNumber , fmt : : format ( " .origin directive must appear alone without <address> = prefix. " ) ) ;
CancelParsingPatches ( ) ;
return false ;
}
if ( ! parser . matchWordI ( " = " ) )
{
LogPatchesSyntaxError ( lineNumber , fmt : : format ( " Missing '=' after .origin " ) ) ;
CancelParsingPatches ( ) ;
return false ;
}
// parse origin
uint32 originAddress ;
if ( parser . matchWordI ( " codecave " ) )
{
// keyword codecave means we set the origin to the end of the current known codecave size
originInfo . setOriginCodeCave ( ) ;
}
else if ( parser . parseU32 ( originAddress ) )
{
// hex address
originInfo . setOrigin ( originAddress ) ;
}
else
{
LogPatchesSyntaxError ( lineNumber , fmt : : format ( " \' .origin = \' must be followed by the keyword codecave or a valid address " ) ) ;
CancelParsingPatches ( ) ;
return false ;
}
continue ;
}
// next we attempt to parse symbol assignment
// symbols can be labels or variables. The type is determined by what comes after the symbol name
// <symbolName> = <expression> defines a variable
// <symbolName>: defines a label
StringTokenParser bakParser ;
const char * symbolStr ;
sint32 symbolLen ;
parser . storeParserState ( & bakParser ) ;
// check for pattern <symbolName>:
if ( parser . parseSymbolName ( symbolStr , symbolLen ) & & parser . matchWordI ( " : " ) )
{
// label
parser . skipWhitespaces ( ) ;
if ( ! parser . isEndOfString ( ) )
{
LogPatchesSyntaxError ( lineNumber , fmt : : format ( " Unexpected characters after label " ) ) ;
CancelParsingPatches ( ) ;
return false ;
}
uint32 labelAddress ;
if ( overwriteOrigin ! = INVALID_ORIGIN )
labelAddress = overwriteOrigin ;
else
{
if ( ! originInfo . isValidOrigin ( ) )
{
LogPatchesSyntaxError ( lineNumber , fmt : : format ( " Defined label has no address assigned or there is no active .origin " ) ) ;
CancelParsingPatches ( ) ;
return false ;
}
labelAddress = originInfo . currentOrigin ;
}
if ( overwriteOrigin = = INVALID_ORIGIN )
{
// if label is part of code flow, delay emitting it until the next data instruction
// this is so we can avoid generating alignment padding, whose size is unknown in advance, between labels and data instructions
scheduledLabels . emplace_back ( new PatchEntryLabel ( lineNumber , symbolStr , symbolLen ) ) ;
}
else
{
PatchEntryLabel * patchLabel = new PatchEntryLabel ( lineNumber , symbolStr , symbolLen ) ;
patchLabel - > setAssignedVA ( labelAddress ) ;
currentGroup - > list_patches . emplace_back ( patchLabel ) ;
}
continue ;
}
parser . restoreParserState ( & bakParser ) ;
// check for pattern <symbolName> =
if ( parser . parseSymbolName ( symbolStr , symbolLen ) & & parser . matchWordI ( " = " ) )
{
// variable definition
parser . skipWhitespaces ( ) ;
parser . trimWhitespaces ( ) ;
const char * expressionStr = parser . getCurrentPtr ( ) ;
sint32 expressionLen = parser . getCurrentLen ( ) ;
// create entry for symbol/variable value assignment
currentGroup - > list_patches . emplace_back ( new PatchEntryVariableValue ( lineNumber , symbolStr , symbolLen , PATCHVARTYPE : : UINT , expressionStr , expressionLen ) ) ;
continue ;
}
// if all patterns mismatch then we assume it's an assembly instruction
parser . restoreParserState ( & bakParser ) ;
std : : string instrText ( parser . getCurrentPtr ( ) , parser . getCurrentLen ( ) ) ;
PPCAssemblerInOut ctx { } ;
ctx . forceNoAlignment = overwriteOrigin ! = INVALID_ORIGIN ; // dont auto-align when a fixed address is assigned
if ( overwriteOrigin ! = INVALID_ORIGIN )
ctx . virtualAddress = overwriteOrigin ;
else if ( originInfo . isValidOrigin ( ) )
ctx . virtualAddress = originInfo . currentOrigin ;
else
{
LogPatchesSyntaxError ( lineNumber , fmt : : format ( " Trying to assemble line but no address specified. (Declare .origin or prefix line with <address> = ) " ) ) ;
CancelParsingPatches ( ) ;
return false ;
}
if ( ! ppcAssembler_assembleSingleInstruction ( instrText . c_str ( ) , & ctx ) )
{
LogPatchesSyntaxError ( lineNumber , fmt : : format ( " Error in assembler: {} " , ctx . errorMsg ) ) ;
CancelParsingPatches ( ) ;
return false ;
}
cemu_assert_debug ( ctx . alignmentRequirement ! = 0 ) ;
if ( overwriteOrigin = = INVALID_ORIGIN )
{
originInfo . incrementOrigin ( ( sint32 ) ctx . alignmentPaddingSize ) ; // alignment padding
originInfo . incrementOrigin ( ( sint32 ) ctx . outputData . size ( ) ) ; // instruction size
}
// flush labels
for ( auto & itr : scheduledLabels )
{
itr - > setAssignedVA ( ctx . virtualAddressAligned ) ;
currentGroup - > list_patches . emplace_back ( itr ) ;
}
scheduledLabels . clear ( ) ;
// append instruction
currentGroup - > list_patches . emplace_back ( new PatchEntryInstruction ( lineNumber , ctx . virtualAddressAligned , { ctx . outputData . data ( ) , ctx . outputData . size ( ) } , ctx . list_relocs ) ) ;
}
flushLabels ( ) ;
if ( currentGroup )
AddPatchGroup ( currentGroup ) ;
return true ;
}