2024-08-30 21:17:18 +02:00
/*
* chroniclesextractor . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "chroniclesextractor.h"
2024-08-30 22:20:33 +02:00
# include "../../lib/VCMIDirs.h"
2024-08-31 00:44:20 +02:00
# include "../../lib/filesystem/CArchiveLoader.h"
2024-08-30 22:20:33 +02:00
2024-09-02 23:36:42 +02:00
# include "../innoextract.h"
2024-08-30 22:20:33 +02:00
ChroniclesExtractor : : ChroniclesExtractor ( QWidget * p , std : : function < void ( float percent ) > cb ) :
parent ( p ) , cb ( cb )
2024-08-30 21:17:18 +02:00
{
2024-08-30 22:20:33 +02:00
}
2024-09-02 23:36:42 +02:00
bool ChroniclesExtractor : : createTempDir ( )
2024-08-30 22:20:33 +02:00
{
2024-09-02 23:36:42 +02:00
tempDir = QDir ( pathToQString ( VCMIDirs : : get ( ) . userDataPath ( ) ) ) ;
if ( tempDir . cd ( " tmp " ) )
2024-08-30 21:17:18 +02:00
{
2024-09-02 23:36:42 +02:00
tempDir . removeRecursively ( ) ; // remove if already exists (e.g. previous run)
tempDir . cdUp ( ) ;
2024-08-30 22:20:33 +02:00
}
2024-09-02 23:36:42 +02:00
tempDir . mkdir ( " tmp " ) ;
if ( ! tempDir . cd ( " tmp " ) )
return false ; // should not happen - but avoid deleting wrong folder in any case
2024-08-30 22:20:33 +02:00
return true ;
}
2024-09-02 23:36:42 +02:00
void ChroniclesExtractor : : removeTempDir ( )
{
tempDir . removeRecursively ( ) ;
}
2024-08-30 22:20:33 +02:00
int ChroniclesExtractor : : getChronicleNo ( QFile & file )
{
if ( ! file . open ( QIODevice : : ReadOnly ) )
{
2024-12-06 15:20:45 +02:00
QMessageBox : : critical ( parent , tr ( " The file cannot be opened " ) , file . errorString ( ) ) ;
2024-08-30 22:20:33 +02:00
return 0 ;
}
2024-08-30 21:17:18 +02:00
2024-08-30 22:20:33 +02:00
QByteArray magic { " MZ " } ;
QByteArray magicFile = file . read ( magic . length ( ) ) ;
if ( ! magicFile . startsWith ( magic ) )
{
2024-12-06 15:20:45 +02:00
QMessageBox : : critical ( parent , tr ( " Invalid file selected " ) , tr ( " You have to select a gog installer file! " ) ) ;
2024-08-30 22:20:33 +02:00
return 0 ;
}
QByteArray dataBegin = file . read ( 1'000'000 ) ;
int chronicle = 0 ;
for ( const auto & kv : chronicles ) {
if ( dataBegin . contains ( kv . second ) )
2024-08-30 21:17:18 +02:00
{
2024-08-30 22:20:33 +02:00
chronicle = kv . first ;
break ;
2024-08-30 21:17:18 +02:00
}
2024-08-30 22:20:33 +02:00
}
if ( ! chronicle )
{
2024-12-06 15:20:45 +02:00
QMessageBox : : critical ( parent , tr ( " Invalid file selected " ) , tr ( " You have to select a Heroes Chronicles installer file! " ) ) ;
2024-08-30 22:20:33 +02:00
return 0 ;
}
return chronicle ;
}
2024-08-30 21:17:18 +02:00
2024-08-30 22:20:33 +02:00
bool ChroniclesExtractor : : extractGogInstaller ( QString file )
{
2024-09-02 23:36:42 +02:00
QString errorText = Innoextract : : extract ( file , tempDir . path ( ) , [ this ] ( float progress ) {
float overallProgress = ( ( 1.0 / static_cast < float > ( fileCount ) ) * static_cast < float > ( extractionFile ) ) + ( progress / static_cast < float > ( fileCount ) ) ;
if ( cb )
cb ( overallProgress ) ;
} ) ;
2024-08-30 22:20:33 +02:00
2024-09-02 23:36:42 +02:00
if ( ! errorText . isEmpty ( ) )
{
2024-12-02 21:29:17 +02:00
QString hashError = Innoextract : : getHashError ( file , { } , { } , { } ) ;
2024-09-02 23:36:42 +02:00
QMessageBox : : critical ( parent , tr ( " Extracting error! " ) , errorText ) ;
2024-12-02 00:46:03 +02:00
if ( ! hashError . isEmpty ( ) )
QMessageBox : : critical ( parent , tr ( " Hash error! " ) , hashError , QMessageBox : : Ok , QMessageBox : : Ok ) ;
2024-09-02 23:36:42 +02:00
return false ;
}
2024-08-30 22:20:33 +02:00
2024-09-02 23:36:42 +02:00
return true ;
2024-08-30 22:20:33 +02:00
}
2024-09-01 02:16:03 +02:00
void ChroniclesExtractor : : createBaseMod ( ) const
2024-08-31 00:44:20 +02:00
{
QDir dir ( pathToQString ( VCMIDirs : : get ( ) . userDataPath ( ) / " Mods " ) ) ;
dir . mkdir ( " chronicles " ) ;
dir . cd ( " chronicles " ) ;
dir . mkdir ( " Mods " ) ;
QJsonObject mod
{
2024-08-31 18:46:45 +02:00
{ " modType " , " Expansion " } ,
2024-09-02 23:36:42 +02:00
{ " name " , tr ( " Heroes Chronicles " ) } ,
{ " description " , tr ( " Heroes Chronicles " ) } ,
2024-08-31 00:44:20 +02:00
{ " author " , " 3DO " } ,
{ " version " , " 1.0 " } ,
2024-09-01 00:41:16 +02:00
{ " contact " , " vcmi.eu " } ,
2024-11-05 00:30:56 +02:00
{ " heroes " , QJsonArray ( { " config/portraitsChronicles.json " } ) } ,
2024-09-19 22:14:23 +02:00
{ " settings " , QJsonObject ( { { " mapFormat " , QJsonObject ( { { " chronicles " , QJsonObject ( { {
{ " supported " , true } ,
{ " portraits " , QJsonObject ( {
{ " portraitTarnumBarbarian " , 163 } ,
{ " portraitTarnumKnight " , 164 } ,
{ " portraitTarnumWizard " , 165 } ,
{ " portraitTarnumRanger " , 166 } ,
{ " portraitTarnumOverlord " , 167 } ,
{ " portraitTarnumBeastmaster " , 168 } ,
} ) } ,
} } ) } } ) } } ) } ,
2024-08-31 00:44:20 +02:00
} ;
QFile jsonFile ( dir . filePath ( " mod.json " ) ) ;
jsonFile . open ( QFile : : WriteOnly ) ;
jsonFile . write ( QJsonDocument ( mod ) . toJson ( ) ) ;
2024-11-05 00:30:56 +02:00
for ( auto & dataPath : VCMIDirs : : get ( ) . dataPaths ( ) )
{
2024-12-11 01:49:23 +02:00
auto file = pathToQString ( dataPath / " config " / " heroes " / " portraitsChronicles.json " ) ;
2024-11-05 00:30:56 +02:00
auto destFolder = VCMIDirs : : get ( ) . userDataPath ( ) / " Mods " / " chronicles " / " content " / " config " ;
2024-12-11 01:49:23 +02:00
auto destFile = pathToQString ( destFolder / " portraitsChronicles.json " ) ;
if ( QFile : : exists ( file ) )
2024-11-05 00:30:56 +02:00
{
2024-12-11 01:49:23 +02:00
QDir ( ) . mkpath ( pathToQString ( destFolder ) ) ;
QFile : : remove ( destFile ) ;
QFile : : copy ( file , destFile ) ;
2024-11-05 00:30:56 +02:00
}
}
2024-08-31 00:44:20 +02:00
}
void ChroniclesExtractor : : createChronicleMod ( int no )
{
2024-08-31 18:46:45 +02:00
QDir dir ( pathToQString ( VCMIDirs : : get ( ) . userDataPath ( ) / " Mods " / " chronicles " / " Mods " / ( " chronicles_ " + std : : to_string ( no ) ) ) ) ;
dir . removeRecursively ( ) ;
dir . mkpath ( " . " ) ;
2024-08-31 00:44:20 +02:00
2024-09-02 23:36:42 +02:00
QByteArray tmpChronicles = chronicles . at ( no ) ;
tmpChronicles . replace ( ' \0 ' , " " ) ;
2024-08-31 00:44:20 +02:00
QJsonObject mod
{
2024-08-31 18:46:45 +02:00
{ " modType " , " Expansion " } ,
2024-09-02 23:36:42 +02:00
{ " name " , QString : : number ( no ) + " - " + QString ( tmpChronicles ) } ,
{ " description " , tr ( " Heroes Chronicles " ) + " - " + QString : : number ( no ) + " - " + QString ( tmpChronicles ) } ,
2024-08-31 00:44:20 +02:00
{ " author " , " 3DO " } ,
{ " version " , " 1.0 " } ,
2024-09-01 00:41:16 +02:00
{ " contact " , " vcmi.eu " } ,
2024-08-31 00:44:20 +02:00
} ;
2024-08-31 02:52:34 +02:00
2024-08-31 00:44:20 +02:00
QFile jsonFile ( dir . filePath ( " mod.json " ) ) ;
jsonFile . open ( QFile : : WriteOnly ) ;
jsonFile . write ( QJsonDocument ( mod ) . toJson ( ) ) ;
2024-08-31 02:52:34 +02:00
dir . cd ( " content " ) ;
2024-08-31 18:46:45 +02:00
2024-08-31 00:44:20 +02:00
extractFiles ( no ) ;
}
2024-09-01 02:16:03 +02:00
void ChroniclesExtractor : : extractFiles ( int no ) const
2024-08-31 00:44:20 +02:00
{
QByteArray tmpChronicles = chronicles . at ( no ) ;
tmpChronicles . replace ( ' \0 ' , " " ) ;
2024-09-02 22:51:30 +02:00
std : : string chroniclesDir = " chronicles_ " + std : : to_string ( no ) ;
2024-08-31 00:44:20 +02:00
QDir tmpDir = tempDir . filePath ( tempDir . entryList ( { " app " } , QDir : : Filter : : Dirs ) . front ( ) ) ;
tmpDir . setPath ( tmpDir . filePath ( tmpDir . entryList ( { QString ( tmpChronicles ) } , QDir : : Filter : : Dirs ) . front ( ) ) ) ;
tmpDir . setPath ( tmpDir . filePath ( tmpDir . entryList ( { " data " } , QDir : : Filter : : Dirs ) . front ( ) ) ) ;
2024-09-02 22:51:30 +02:00
auto basePath = VCMIDirs : : get ( ) . userDataPath ( ) / " Mods " / " chronicles " / " Mods " / chroniclesDir / " content " ;
QDir outDirDataPortraits ( pathToQString ( VCMIDirs : : get ( ) . userDataPath ( ) / " Mods " / " chronicles " / " content " / " Data " ) ) ;
QDir outDirData ( pathToQString ( basePath / " Data " / chroniclesDir ) ) ;
QDir outDirSprites ( pathToQString ( basePath / " Sprites " / chroniclesDir ) ) ;
QDir outDirVideo ( pathToQString ( basePath / " Video " / chroniclesDir ) ) ;
QDir outDirSounds ( pathToQString ( basePath / " Sounds " / chroniclesDir ) ) ;
2024-09-24 21:41:37 +02:00
QDir outDirMaps ( pathToQString ( basePath / " Maps " / " Chronicles " ) ) ;
2024-08-31 16:27:39 +02:00
auto extract = [ ] ( QDir scrDir , QDir dest , QString file , std : : vector < std : : string > files = { } ) {
2024-08-31 15:25:45 +02:00
CArchiveLoader archive ( " " , scrDir . filePath ( scrDir . entryList ( { file } ) . front ( ) ) . toStdString ( ) , false ) ;
2024-08-31 02:52:34 +02:00
for ( auto & entry : archive . getEntries ( ) )
2024-08-31 15:25:45 +02:00
if ( files . empty ( ) )
archive . extractToFolder ( dest . absolutePath ( ) . toStdString ( ) , " " , entry . second , true ) ;
else
{
2024-09-01 02:16:03 +02:00
for ( const auto & item : files )
2024-09-02 22:51:30 +02:00
if ( boost : : algorithm : : to_lower_copy ( entry . second . name ) . find ( boost : : algorithm : : to_lower_copy ( item ) ) ! = std : : string : : npos )
2024-08-31 15:25:45 +02:00
archive . extractToFolder ( dest . absolutePath ( ) . toStdString ( ) , " " , entry . second , true ) ;
}
2024-08-31 02:52:34 +02:00
} ;
2024-08-31 15:25:45 +02:00
extract ( tmpDir , outDirData , " xBitmap.lod " ) ;
extract ( tmpDir , outDirData , " xlBitmap.lod " ) ;
extract ( tmpDir , outDirSprites , " xSprite.lod " ) ;
extract ( tmpDir , outDirSprites , " xlSprite.lod " ) ;
extract ( tmpDir , outDirVideo , " xVideo.vid " ) ;
extract ( tmpDir , outDirSounds , " xSound.snd " ) ;
2024-08-31 02:52:34 +02:00
tmpDir . cdUp ( ) ;
2024-09-02 22:51:30 +02:00
if ( tmpDir . entryList ( { " maps " } , QDir : : Filter : : Dirs ) . size ( ) ) // special case for "The World Tree": the map is in the "Maps" folder instead of inside the lod
2024-08-31 02:52:34 +02:00
{
QDir tmpDirMaps = tmpDir . filePath ( tmpDir . entryList ( { " maps " } , QDir : : Filter : : Dirs ) . front ( ) ) ;
2024-09-01 02:16:03 +02:00
for ( const auto & entry : tmpDirMaps . entryList ( ) )
2024-08-31 02:52:34 +02:00
QFile ( tmpDirMaps . filePath ( entry ) ) . copy ( outDirData . filePath ( entry ) ) ;
}
2024-08-31 15:25:45 +02:00
tmpDir . cdUp ( ) ;
QDir tmpDirData = tmpDir . filePath ( tmpDir . entryList ( { " data " } , QDir : : Filter : : Dirs ) . front ( ) ) ;
2024-09-02 22:51:30 +02:00
auto tarnumPortraits = std : : vector < std : : string > { " HPS137 " , " HPS138 " , " HPS139 " , " HPS140 " , " HPS141 " , " HPS142 " , " HPL137 " , " HPL138 " , " HPL139 " , " HPL140 " , " HPL141 " , " HPL142 " } ;
extract ( tmpDirData , outDirDataPortraits , " bitmap.lod " , tarnumPortraits ) ;
2024-08-31 17:16:42 +02:00
extract ( tmpDirData , outDirData , " lbitmap.lod " , std : : vector < std : : string > { " INTRORIM " } ) ;
2024-08-31 15:25:45 +02:00
2024-08-31 12:14:51 +02:00
if ( ! outDirMaps . exists ( ) )
outDirMaps . mkpath ( " . " ) ;
QString campaignFileName = " Hc " + QString : : number ( no ) + " _Main.h3c " ;
2024-09-02 22:51:30 +02:00
QFile ( outDirData . filePath ( outDirData . entryList ( { " Main.h3c " } ) . front ( ) ) ) . copy ( outDirMaps . filePath ( campaignFileName ) ) ;
2024-08-31 00:44:20 +02:00
}
2024-08-30 22:20:33 +02:00
void ChroniclesExtractor : : installChronicles ( QStringList exe )
{
extractionFile = - 1 ;
fileCount = exe . size ( ) ;
for ( QString f : exe )
{
extractionFile + + ;
2024-12-11 00:14:27 +02:00
if ( ! createTempDir ( ) )
2024-08-30 22:20:33 +02:00
continue ;
2024-12-11 00:14:27 +02:00
QString filepath = tempDir . filePath ( " chr.exe " ) ;
QFile ( f ) . copy ( filepath ) ;
QFile file ( filepath ) ;
2024-08-30 22:20:33 +02:00
2024-12-11 00:14:27 +02:00
int chronicleNo = getChronicleNo ( file ) ;
if ( ! chronicleNo )
2024-08-30 22:20:33 +02:00
continue ;
2024-12-11 00:14:27 +02:00
if ( ! extractGogInstaller ( filepath ) )
2024-08-30 22:20:33 +02:00
continue ;
2024-08-31 00:44:20 +02:00
createBaseMod ( ) ;
createChronicleMod ( chronicleNo ) ;
2024-08-30 22:20:33 +02:00
2024-09-02 23:36:42 +02:00
removeTempDir ( ) ;
2024-08-30 21:17:18 +02:00
}
2024-08-31 16:27:39 +02:00
}