Implement installer with support for ISO, STFS and SVOD. Also implement XEX Patcher. (#5)

This commit is contained in:
Darío 2024-11-29 17:47:30 -03:00 committed by GitHub
parent d36aa26bac
commit 3215e47279
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 13171 additions and 11 deletions

3
.gitmodules vendored
View file

@ -7,3 +7,6 @@
[submodule "thirdparty/ShaderRecomp"]
path = thirdparty/ShaderRecomp
url = https://github.com/hedge-dev/ShaderRecomp.git
[submodule "thirdparty/libmspack"]
path = thirdparty/libmspack
url = https://github.com/kyz/libmspack

View file

@ -79,6 +79,30 @@ set(SWA_UI_CXX_SOURCES
"ui/window.cpp"
)
set(SWA_INSTALL_CXX_SOURCES
"install/installer.cpp"
"install/iso_file_system.cpp"
"install/memory_mapped_file.cpp"
"install/xcontent_file_system.cpp"
"install/xex_patcher.cpp"
"install/hashes/apotos_shamar.cpp"
"install/hashes/chunnan.cpp"
"install/hashes/empire_city_adabat.cpp"
"install/hashes/game.cpp"
"install/hashes/holoska.cpp"
"install/hashes/mazuri.cpp"
"install/hashes/spagonia.cpp"
"install/hashes/update.cpp"
)
set(LIBMSPACK_PATH ${SWA_THIRDPARTY_ROOT}/libmspack/libmspack/mspack)
set(LIBMSPACK_C_SOURCES
${LIBMSPACK_PATH}/lzxd.c
)
set_source_files_properties(${LIBMSPACK_C_SOURCES} PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
set(SMOLV_SOURCE_DIR "${SWA_THIRDPARTY_ROOT}/ShaderRecomp/thirdparty/smol-v/source")
set(SWA_CXX_SOURCES
@ -95,7 +119,8 @@ set(SWA_CXX_SOURCES
${SWA_HID_CXX_SOURCES}
${SWA_PATCHES_CXX_SOURCES}
${SWA_UI_CXX_SOURCES}
${SWA_INSTALL_CXX_SOURCES}
${LIBMSPACK_C_SOURCES}
"${SMOLV_SOURCE_DIR}/smolv.cpp"
)
@ -127,6 +152,7 @@ find_package(Stb REQUIRED)
find_package(unofficial-concurrentqueue REQUIRED)
find_package(imgui CONFIG REQUIRED)
find_package(magic_enum CONFIG REQUIRED)
find_package(unofficial-tiny-aes-c CONFIG REQUIRED)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12)
add_custom_command(TARGET UnleashedRecomp POST_BUILD
@ -163,12 +189,15 @@ target_link_libraries(UnleashedRecomp PRIVATE
Synchronization
imgui::imgui
magic_enum::magic_enum
unofficial::tiny-aes-c::tiny-aes-c
)
target_include_directories(UnleashedRecomp PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/api
${SWA_THIRDPARTY_ROOT}/ddspp
${SWA_THIRDPARTY_ROOT}/ddspp
${SWA_THIRDPARTY_ROOT}/TinySHA1
${LIBMSPACK_PATH}
${Stb_INCLUDE_DIR}
${SMOLV_SOURCE_DIR}
)

View file

@ -0,0 +1,65 @@
#pragma once
#include <filesystem>
#include "virtual_file_system.h"
struct DirectoryFileSystem : VirtualFileSystem
{
std::filesystem::path directoryPath;
DirectoryFileSystem(const std::filesystem::path &directoryPath)
{
this->directoryPath = directoryPath;
}
bool load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const override
{
std::ifstream fileStream(directoryPath / std::filesystem::path(std::u8string_view((const char8_t *)(path.c_str()))), std::ios::binary);
if (fileStream.is_open())
{
fileStream.read((char *)(fileData), fileDataMaxByteCount);
return !fileStream.bad();
}
else
{
return false;
}
}
size_t getSize(const std::string &path) const override
{
std::error_code ec;
size_t fileSize = std::filesystem::file_size(directoryPath / std::filesystem::path(std::u8string_view((const char8_t *)(path.c_str()))), ec);
if (!ec)
{
return fileSize;
}
else
{
return 0;
}
}
bool exists(const std::string &path) const override
{
if (path.empty())
{
return false;
}
return std::filesystem::exists(directoryPath / std::filesystem::path(std::u8string_view((const char8_t *)(path.c_str()))));
}
static std::unique_ptr<VirtualFileSystem> create(const std::filesystem::path &directoryPath)
{
if (std::filesystem::exists(directoryPath))
{
return std::make_unique<DirectoryFileSystem>(directoryPath);
}
else
{
return nullptr;
}
}
};

View file

@ -0,0 +1,297 @@
// File automatically generated by fshasher
#include <utility>
extern const uint64_t ApotosShamarHashes[];
extern const std::pair<const char *, uint32_t> ApotosShamarFiles[];
extern const size_t ApotosShamarFilesSize;
const uint64_t ApotosShamarHashes[] = {
5161782899687054855ULL,
10848638096963692019ULL,
9473129523580090227ULL,
14507476319569850326ULL,
7984304958810014204ULL,
17285119940264119717ULL,
2388648365684266872ULL,
7072117265505496768ULL,
7300804046379422574ULL,
11024341213616898389ULL,
2269719192208918419ULL,
11277103027538800966ULL,
3503444980189584178ULL,
4165664752696059934ULL,
7145613245284078465ULL,
8956692682572097337ULL,
467673557758517075ULL,
5865394160581371462ULL,
2388648365684266872ULL,
7072117265505496768ULL,
4651933531844120545ULL,
16366432125719702966ULL,
6204045984421832366ULL,
7455071839519300924ULL,
1601475090088501102ULL,
6476812967046504881ULL,
10110818077789761124ULL,
12473493932267894529ULL,
192734448958995711ULL,
4979345902860045881ULL,
2388648365684266872ULL,
7072117265505496768ULL,
7354426500253318588ULL,
13436608285401838874ULL,
5455808863724849103ULL,
13912048957214708709ULL,
3577115447410096717ULL,
9167108726914458134ULL,
269064862329040576ULL,
13733247789252130917ULL,
1908075175783995052ULL,
17569164706309240857ULL,
2687257361293581496ULL,
15703575149297583018ULL,
13596487015352587574ULL,
15226982905233731417ULL,
7318040371083150448ULL,
14626638887488090768ULL,
1919723919369387380ULL,
17373239717886557228ULL,
8218928162239233105ULL,
12366520936282200873ULL,
10467517237606579536ULL,
11504181969379809881ULL,
913379962765093751ULL,
13184977455228849096ULL,
994534313191316773ULL,
1161598282996799627ULL,
8028384967928659483ULL,
9167275059460261476ULL,
12329170430565474464ULL,
14614493918698922602ULL,
8835009808275053059ULL,
9227869334598201915ULL,
6637379186446622324ULL,
9397642730708705852ULL,
10630484262545617745ULL,
10797068556411890259ULL,
6361872786799807915ULL,
7387444947377556158ULL,
8230353453873378338ULL,
12403999963358558507ULL,
232254748410986870ULL,
13070561742440061598ULL,
10958136070207580182ULL,
15790689719855919515ULL,
14585362614190785186ULL,
17761667637871173377ULL,
9234510757818947078ULL,
15427471912569301087ULL,
9320397015075772917ULL,
5202741620127019351ULL,
6987027253392466959ULL,
5818570466966793416ULL,
11818189498451458264ULL,
8227907329904191071ULL,
8485734709208759762ULL,
7322832926430412082ULL,
252821791924020792ULL,
10750524204772481581ULL,
9618439767240157908ULL,
6136980604454707359ULL,
7851876072887711328ULL,
7126123337902320477ULL,
16732438051503528656ULL,
9626404695949647173ULL,
14630178898376646697ULL,
18071570395979705668ULL,
14290188031476453509ULL,
5082129473071351035ULL,
226647428045111345ULL,
11645732680053564481ULL,
5363987804539005071ULL,
15998860607353466394ULL,
9167368126469307383ULL,
2744550015698589133ULL,
10260598734212408368ULL,
2300802232392089767ULL,
10949176600743863488ULL,
1118205454506633106ULL,
5969867951434607877ULL,
6671314176692954687ULL,
14530713737956734950ULL,
2004571052229566869ULL,
9708958471851417571ULL,
2142992686316263474ULL,
7168715241575146342ULL,
1976917309517312226ULL,
18038718853485103300ULL,
7044835724604442413ULL,
15531130920102660109ULL,
4462669497430426931ULL,
13428469011561492337ULL,
3214238300098346592ULL,
16176599498653256643ULL,
5326927580226746490ULL,
15047534843332950162ULL,
11636822788287294319ULL,
12554314680841874379ULL,
13981752751371535335ULL,
12254474770223375774ULL,
7961887113788376965ULL,
5360854891214543592ULL,
9184741739495532010ULL,
1210820029035423219ULL,
3779694736357686174ULL,
7289857685129858177ULL,
17552766072613442781ULL,
878947465360421687ULL,
7265659051859092189ULL,
11427964425552083037ULL,
12982062630479208399ULL,
11441566204903265308ULL,
14705280727085431572ULL,
455364757374368469ULL,
5573968245771556687ULL,
1250381038431950088ULL,
2410996856079368646ULL,
5259209343569281884ULL,
8112015537375387576ULL,
6878422699214478955ULL,
8949331867975860441ULL,
1985690755616441830ULL,
9351064623690489389ULL,
8896435929557957139ULL,
11542596339951462459ULL,
1985690755616441830ULL,
9351064623690489389ULL,
3185071971562944151ULL,
9949170060646427581ULL,
3811554303412706475ULL,
13858258875735338844ULL,
11056011717482380676ULL,
16815456012040865536ULL,
7271530458034274660ULL,
13079014928873717024ULL,
5685165921037143623ULL,
18141751546323874463ULL,
6983633615275701371ULL,
13085359633765678358ULL,
6682199648914577543ULL,
16715523554730788031ULL,
9945450437595332438ULL,
14162527193937280238ULL,
2442142878432699094ULL,
9645678184877844291ULL,
10217667783481469232ULL,
12548875758281895729ULL,
};
const std::pair<const char *, uint32_t> ApotosShamarFiles[] = {
{ "#ActD_SubMykonos_02.ar.00", 2 },
{ "#ActD_SubMykonos_02.arl", 2 },
{ "#ActD_SubMykonos_03.ar.00", 2 },
{ "#ActD_SubMykonos_03.arl", 2 },
{ "#ActD_SubMykonos_04.ar.00", 2 },
{ "#ActD_SubMykonos_04.arl", 2 },
{ "#ActD_SubMykonos_05.ar.00", 2 },
{ "#ActD_SubMykonos_05.arl", 2 },
{ "#ActD_SubMykonos_06.ar.00", 2 },
{ "#ActD_SubMykonos_06.arl", 2 },
{ "#ActD_SubPetra_02.ar.00", 2 },
{ "#ActD_SubPetra_02.arl", 2 },
{ "#ActD_SubPetra_04.ar.00", 2 },
{ "#ActD_SubPetra_04.arl", 2 },
{ "#ActN_SubMykonos_02.ar.00", 2 },
{ "#ActN_SubMykonos_02.arl", 2 },
{ "#ActN_SubMykonos_03.ar.00", 2 },
{ "#ActN_SubMykonos_03.arl", 2 },
{ "#ActN_SubMykonos_04.ar.00", 2 },
{ "#ActN_SubMykonos_04.arl", 2 },
{ "#ActN_SubPetra_02.ar.00", 2 },
{ "#ActN_SubPetra_02.arl", 2 },
{ "#ActN_SubPetra_03.ar.00", 2 },
{ "#ActN_SubPetra_03.arl", 2 },
{ "#Application.ar.00", 2 },
{ "#Application.arl", 2 },
{ "ActD_SubMykonos_02.ar.00", 2 },
{ "ActD_SubMykonos_02.arl", 2 },
{ "ActD_SubPetra_02.ar.00", 2 },
{ "ActD_SubPetra_02.arl", 2 },
{ "ActN_SubMykonos_03.ar.00", 2 },
{ "ActN_SubMykonos_03.arl", 2 },
{ "ActN_SubMykonos_04.ar.00", 2 },
{ "ActN_SubMykonos_04.arl", 2 },
{ "ActN_SubPetra_02.ar.00", 2 },
{ "ActN_SubPetra_02.ar.01", 2 },
{ "ActN_SubPetra_02.arl", 2 },
{ "ActN_SubPetra_03.ar.00", 2 },
{ "ActN_SubPetra_03.arl", 2 },
{ "Additional/ActD_MykonosAct1/Stage-Add.pfd", 1 },
{ "Additional/ActD_MykonosAct2/Stage-Add.pfd", 1 },
{ "Additional/ActD_Petra/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubMykonos_01/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubMykonos_02/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubPetra_01/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubPetra_02/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubPetra_03/Stage-Add.pfd", 1 },
{ "Additional/ActN_MykonosEvil/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubMykonos_01/Stage-Add.pfd", 1 },
{ "Additional/BossPetra/Stage-Add.pfd", 1 },
{ "Additional/Event_M0_06_myk/Stage-Add.pfd", 1 },
{ "Additional/Event_M6_01_temple/Stage-Add.pfd", 1 },
{ "Additional/Event_M8_16_myk/Stage-Add.pfd", 1 },
{ "Additional/ExStageTails1/Stage-Add.pfd", 1 },
{ "Additional/ExStageTails2/Stage-Add.pfd", 1 },
{ "Additional/Town_Mykonos/Stage-Add.pfd", 1 },
{ "Additional/Town_MykonosETF/Stage-Add.pfd", 1 },
{ "Additional/Town_MykonosETF_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_Mykonos_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_PetraCapital/Stage-Add.pfd", 1 },
{ "Additional/Town_PetraCapitalETF/Stage-Add.pfd", 1 },
{ "Additional/Town_PetraCapitalETF_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_PetraCapital_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_PetraLabo/Stage-Add.pfd", 1 },
{ "Additional/Town_PetraLabo_Night/Stage-Add.pfd", 1 },
{ "DLC.xml", 1 },
{ "Languages/English/WorldMap.ar.00", 2 },
{ "Languages/English/WorldMap.arl", 2 },
{ "Languages/French/WorldMap.ar.00", 2 },
{ "Languages/French/WorldMap.arl", 2 },
{ "Languages/German/WorldMap.ar.00", 2 },
{ "Languages/German/WorldMap.arl", 2 },
{ "Languages/Italian/WorldMap.ar.00", 2 },
{ "Languages/Italian/WorldMap.arl", 2 },
{ "Languages/Japanese/WorldMap.ar.00", 2 },
{ "Languages/Japanese/WorldMap.arl", 2 },
{ "Languages/Spanish/WorldMap.ar.00", 2 },
{ "Languages/Spanish/WorldMap.arl", 2 },
{ "Packed/ActD_SubMykonos_02/Stage.pfd", 1 },
{ "Packed/ActD_SubPetra_02/Stage.pfd", 1 },
{ "Packed/ActN_SubPetra_02/Stage.pfd", 1 },
{ "WorldMap.ar.00", 2 },
{ "WorldMap.arl", 2 },
{ "work/ActD_MykonosAct1/Terrain.prm.xml", 2 },
{ "work/ActD_MykonosAct2/Terrain.prm.xml", 2 },
{ "work/ActD_Petra/Terrain.prm.xml", 2 },
{ "work/ActD_SubMykonos_01/Terrain.prm.xml", 2 },
{ "work/ActD_SubPetra_03/Terrain.prm.xml", 2 },
{ "work/ActN_MykonosEvil/Terrain.prm.xml", 2 },
{ "work/ActN_PetraEvil/Terrain.prm.xml", 2 },
{ "work/ActN_SubMykonos_01/Terrain.prm.xml", 2 },
{ "work/Event_M8_16_myk/Terrain.prm.xml", 2 },
{ "work/Event_temple/Terrain.prm.xml", 2 },
{ "work/StaffRoll/Terrain.prm.xml", 2 },
{ "work/Town_Mykonos/Terrain.prm.xml", 2 },
{ "work/Town_MykonosETF/Terrain.prm.xml", 2 },
{ "work/Town_MykonosETF_Night/Terrain.prm.xml", 2 },
{ "work/Town_Mykonos_Night/Terrain.prm.xml", 2 },
{ "work/Town_PetraCapital/Terrain.prm.xml", 2 },
{ "work/Town_PetraCapitalETF/Terrain.prm.xml", 2 },
{ "work/Town_PetraCapitalETF_Night/Terrain.prm.xml", 2 },
{ "work/Town_PetraCapital_Night/Terrain.prm.xml", 2 },
{ "work/Town_PetraLabo/Terrain.prm.xml", 2 },
{ "work/Town_PetraLabo_Night/Terrain.prm.xml", 2 },
};
const size_t ApotosShamarFilesSize = std::size(ApotosShamarFiles);

View file

@ -0,0 +1,10 @@
// File automatically generated by fshasher
#pragma once
#include <utility>
extern const uint64_t ApotosShamarHashes[];
extern const std::pair<const char *, uint32_t> ApotosShamarFiles[];
extern const size_t ApotosShamarFilesSize;

View file

@ -0,0 +1,211 @@
// File automatically generated by fshasher
#include <utility>
extern const uint64_t ChunnanHashes[];
extern const std::pair<const char *, uint32_t> ChunnanFiles[];
extern const size_t ChunnanFilesSize;
const uint64_t ChunnanHashes[] = {
4916287666769501565ULL,
11970519140178822377ULL,
1036602527484778521ULL,
14798433086191189521ULL,
10646557629458609476ULL,
10713656744393722857ULL,
763540214073867667ULL,
3077520808973995505ULL,
3650022750694984496ULL,
17646887566993148783ULL,
5922433511303640285ULL,
6042200465914054108ULL,
10857350734717065794ULL,
14989231061973888057ULL,
1039624040955487627ULL,
14044814300419760090ULL,
5803112655842355018ULL,
6547906886493755080ULL,
2653940158411132556ULL,
6001214908824901338ULL,
12495882025135198599ULL,
12639105290841965223ULL,
8006441485547456063ULL,
17680405693218986079ULL,
787195009054753821ULL,
6894519290185704169ULL,
8761940786378535518ULL,
14409827195925710801ULL,
9932979175820467217ULL,
17354914869214236495ULL,
7393046528943419306ULL,
10318961341152276648ULL,
15227553401250387405ULL,
16697499576601945061ULL,
2725724513251979147ULL,
2959262702089257674ULL,
13005022849156685393ULL,
15761951700770049152ULL,
1543933356731858728ULL,
11918274797446795400ULL,
8565138620607953150ULL,
12303972293118122590ULL,
3894262225055971808ULL,
15748923097789202806ULL,
7445113579724918237ULL,
9352139437145177775ULL,
13354327698439508785ULL,
14395299404885352996ULL,
1717181541369804485ULL,
13580134637576898558ULL,
7869123835192762123ULL,
17965909040401498617ULL,
8052995111566596790ULL,
12914551890455320561ULL,
4117682086191216416ULL,
6338289071186062965ULL,
5814176097875431103ULL,
14826385593100095233ULL,
10541047035071991113ULL,
6190214339491074327ULL,
10842054931648009233ULL,
5939715546527939616ULL,
7892483490571988259ULL,
5706834027745974113ULL,
5279702511609911835ULL,
9429007351441093145ULL,
9003299904399604102ULL,
17387825652548649779ULL,
15599237468898715461ULL,
9636847268497273922ULL,
6553990919897350057ULL,
13702604427410293191ULL,
2951702790388654203ULL,
11840626484159722915ULL,
2545196762095857746ULL,
3245381935646466890ULL,
14924004818419014054ULL,
17307026898787526320ULL,
505706103741995403ULL,
1910775063020669898ULL,
7076255593390973200ULL,
12112449858466219362ULL,
14742510104650318633ULL,
18061657763370298501ULL,
481489007567700054ULL,
16400619344256757493ULL,
2276480650877074293ULL,
17657490758851414958ULL,
10375576356736793341ULL,
11978364073664959090ULL,
6962787816486957283ULL,
8945389641396629891ULL,
10333276603165380084ULL,
10766868630468389150ULL,
13638842475286731623ULL,
7586779683423382399ULL,
7959765828778244231ULL,
809929751227234334ULL,
2646002627166349152ULL,
11780180320854559998ULL,
13855197810473133678ULL,
5061966727887963421ULL,
8831329945074475835ULL,
1092659380955694324ULL,
8406669250393168781ULL,
1210820029035423219ULL,
3779694736357686174ULL,
8953286534157132517ULL,
15926591678820634559ULL,
6892281684169093025ULL,
11798154340507794264ULL,
3383667935658249174ULL,
8090374027476087947ULL,
1344287774476569420ULL,
16036817376283104799ULL,
15708461104025072747ULL,
17647438730951324648ULL,
3271559932170326270ULL,
3440032660435501456ULL,
15209001562045336524ULL,
16527226617411200894ULL,
16083303942902816114ULL,
16998804538680658636ULL,
211876019454739059ULL,
17900510319720696725ULL,
};
const std::pair<const char *, uint32_t> ChunnanFiles[] = {
{ "#ActD_SubChina_01.ar.00", 2 },
{ "#ActD_SubChina_01.arl", 2 },
{ "#ActD_SubChina_02.ar.00", 2 },
{ "#ActD_SubChina_02.arl", 2 },
{ "#ActD_SubChina_05.ar.00", 2 },
{ "#ActD_SubChina_05.arl", 2 },
{ "#ActD_SubChina_06.ar.00", 2 },
{ "#ActD_SubChina_06.arl", 2 },
{ "#ActN_SubChina_02.ar.00", 2 },
{ "#ActN_SubChina_02.arl", 2 },
{ "#ActN_SubChina_03.ar.00", 2 },
{ "#ActN_SubChina_03.arl", 2 },
{ "#Application.ar.00", 2 },
{ "#Application.arl", 2 },
{ "#SonicActionCommon_China.ar.00", 2 },
{ "#SonicActionCommon_China.arl", 2 },
{ "ActD_SubChina_01.ar.00", 2 },
{ "ActD_SubChina_01.arl", 2 },
{ "ActD_SubChina_02.ar.00", 2 },
{ "ActD_SubChina_02.arl", 2 },
{ "ActD_SubChina_06.ar.00", 2 },
{ "ActD_SubChina_06.arl", 2 },
{ "ActN_SubChina_02.ar.00", 2 },
{ "ActN_SubChina_02.ar.01", 2 },
{ "ActN_SubChina_02.ar.02", 2 },
{ "ActN_SubChina_02.arl", 2 },
{ "ActN_SubChina_03.ar.00", 2 },
{ "ActN_SubChina_03.arl", 2 },
{ "Additional/ActD_China/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubChina_01/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubChina_02/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubChina_03/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubChina_04/Stage-Add.pfd", 1 },
{ "Additional/ActN_ChinaEvil/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubChina_01/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubChina_02/Stage-Add.pfd", 1 },
{ "Additional/Town_China/Stage-Add.pfd", 1 },
{ "Additional/Town_ChinaETF/Stage-Add.pfd", 1 },
{ "Additional/Town_ChinaETF_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_China_Night/Stage-Add.pfd", 1 },
{ "China.dmy", 1 },
{ "DLC.xml", 1 },
{ "Languages/English/WorldMap.ar.00", 2 },
{ "Languages/English/WorldMap.arl", 2 },
{ "Languages/French/WorldMap.ar.00", 2 },
{ "Languages/French/WorldMap.arl", 2 },
{ "Languages/German/WorldMap.ar.00", 2 },
{ "Languages/German/WorldMap.arl", 2 },
{ "Languages/Italian/WorldMap.ar.00", 2 },
{ "Languages/Italian/WorldMap.arl", 2 },
{ "Languages/Japanese/WorldMap.ar.00", 2 },
{ "Languages/Japanese/WorldMap.arl", 2 },
{ "Languages/Spanish/WorldMap.ar.00", 2 },
{ "Languages/Spanish/WorldMap.arl", 2 },
{ "Packed/ActD_SubChina_01/Stage.pfd", 1 },
{ "Packed/ActD_SubChina_02/Stage.pfd", 1 },
{ "Packed/ActN_SubChina_02/Stage.pfd", 1 },
{ "SonicActionCommon_China.ar.00", 2 },
{ "SonicActionCommon_China.ar.01", 2 },
{ "SonicActionCommon_China.arl", 2 },
{ "WorldMap.ar.00", 2 },
{ "WorldMap.arl", 2 },
{ "work/ActD_China/Terrain.prm.xml", 2 },
{ "work/ActD_SubChina_03/Terrain.prm.xml", 2 },
{ "work/ActD_SubChina_04/Terrain.prm.xml", 2 },
{ "work/ActN_ChinaEvil/Terrain.prm.xml", 2 },
{ "work/ActN_SubChina_01/Terrain.prm.xml", 2 },
{ "work/Town_China/Terrain.prm.xml", 2 },
{ "work/Town_ChinaETF/Terrain.prm.xml", 2 },
{ "work/Town_ChinaETF_Night/Terrain.prm.xml", 2 },
{ "work/Town_China_Night/Terrain.prm.xml", 2 },
};
const size_t ChunnanFilesSize = std::size(ChunnanFiles);

View file

@ -0,0 +1,10 @@
// File automatically generated by fshasher
#pragma once
#include <utility>
extern const uint64_t ChunnanHashes[];
extern const std::pair<const char *, uint32_t> ChunnanFiles[];
extern const size_t ChunnanFilesSize;

View file

@ -0,0 +1,301 @@
// File automatically generated by fshasher
#include <utility>
extern const uint64_t EmpireCityAdabatHashes[];
extern const std::pair<const char *, uint32_t> EmpireCityAdabatFiles[];
extern const size_t EmpireCityAdabatFilesSize;
const uint64_t EmpireCityAdabatHashes[] = {
4618484507458881259ULL,
8182943251133517605ULL,
4541536790551220736ULL,
17406849580710840362ULL,
13446786864141547866ULL,
18394961398924760842ULL,
7232487182893775796ULL,
14381110835230961016ULL,
4274945889038837581ULL,
7808510065756945406ULL,
1610509086024531696ULL,
8803475239916562653ULL,
10167668386744802426ULL,
13956299527641656682ULL,
996767587458817188ULL,
1288788687784703445ULL,
1545319301186643948ULL,
3463553283246285904ULL,
2329886274870127790ULL,
10808429212560901966ULL,
1527041476422438351ULL,
2162146429294502965ULL,
15296556067580089ULL,
16098667829436141937ULL,
744838819720957427ULL,
7423469708976263310ULL,
3529776342881389937ULL,
4461759958287006047ULL,
8807875940438298613ULL,
16843338913535213802ULL,
58256850107590821ULL,
13358292493270923606ULL,
3407070953812175890ULL,
17745212169422172237ULL,
7003077550452454334ULL,
8088383338758466757ULL,
1919723919369387380ULL,
17373239717886557228ULL,
8218928162239233105ULL,
12366520936282200873ULL,
11042839363452044257ULL,
15356169009822064376ULL,
9138597922789664708ULL,
16547445367137541751ULL,
13108520045170224668ULL,
15310644750109062057ULL,
777887060161745667ULL,
13298530423883017862ULL,
10405571379268351804ULL,
17852400551605150551ULL,
8485124434733981977ULL,
12177189339342254810ULL,
3082211076406596763ULL,
6711541791173565389ULL,
1392400380375046924ULL,
8082351014158359034ULL,
9938956644170626460ULL,
16820751035068431553ULL,
6498223167578513931ULL,
15573567680447777044ULL,
818541743678319259ULL,
13162066552036570425ULL,
4470546825230084927ULL,
16933853415047689351ULL,
17047164819104329366ULL,
11164964391272112273ULL,
10214344852494979677ULL,
8749232225003494592ULL,
10588995228676795372ULL,
17946424022152922370ULL,
7684379787895597340ULL,
17137410643095877226ULL,
2892218193839563307ULL,
17360308384459188410ULL,
9618439767240157908ULL,
12538237685986535590ULL,
10783954654629912125ULL,
11628381209642954065ULL,
1492167910272475344ULL,
2464069474108457492ULL,
13481409826628489247ULL,
6970423075361710992ULL,
6267765362270814284ULL,
9012032776579276367ULL,
2744550015698589133ULL,
10260598734212408368ULL,
2300802232392089767ULL,
10949176600743863488ULL,
1118205454506633106ULL,
5969867951434607877ULL,
6671314176692954687ULL,
14530713737956734950ULL,
2004571052229566869ULL,
9708958471851417571ULL,
2142992686316263474ULL,
7168715241575146342ULL,
1976917309517312226ULL,
18038718853485103300ULL,
7044835724604442413ULL,
15531130920102660109ULL,
4462669497430426931ULL,
13428469011561492337ULL,
3214238300098346592ULL,
16176599498653256643ULL,
5326927580226746490ULL,
15047534843332950162ULL,
11636822788287294319ULL,
12554314680841874379ULL,
8706113903054062682ULL,
18207042628229284597ULL,
5213300754710184598ULL,
8479681542825232071ULL,
5360854891214543592ULL,
9184741739495532010ULL,
1210820029035423219ULL,
3779694736357686174ULL,
3837095400824054399ULL,
9794004182896121787ULL,
12515494881385509791ULL,
15063249629029871557ULL,
13061295136017828432ULL,
17824323001364842093ULL,
1014938187367296966ULL,
1452881609817169700ULL,
3555492069376698022ULL,
12805106068875273513ULL,
10312311736007919080ULL,
17030922443332175658ULL,
10695305817718672781ULL,
13804284662675589878ULL,
9089169043898385347ULL,
13234787173486497799ULL,
8604409041610624441ULL,
12146239465411938672ULL,
2721757041275179953ULL,
11198739556793737443ULL,
15712221005744556715ULL,
18094691449976637288ULL,
15547655397439064196ULL,
17270814844989703830ULL,
3140658107263566967ULL,
4592986651443127450ULL,
5456798863030720610ULL,
15886814922191774691ULL,
3949234515704675807ULL,
11955725616840921688ULL,
275537481032843773ULL,
9312649863415127630ULL,
10341982185761145488ULL,
11616013853856219024ULL,
747890237293655409ULL,
16207842125850922960ULL,
7337489688872005573ULL,
9028255494965930705ULL,
15360353024836444627ULL,
16078477132817971317ULL,
3457659339231167623ULL,
15718276630192910248ULL,
9554347366501308641ULL,
9964745841368142774ULL,
12984301129292863167ULL,
15594004427009479633ULL,
8763624932769278216ULL,
9531499636450705927ULL,
6901020296706243673ULL,
6939812494265815615ULL,
3081980174784719188ULL,
17328496666218534737ULL,
2803515411835567103ULL,
4886896048325757224ULL,
16083303942902816114ULL,
16998804538680658636ULL,
4511521600139320416ULL,
13420895488136723132ULL,
16636596484105747721ULL,
18203853164184025820ULL,
15091774185752181796ULL,
16492958331247638847ULL,
3414648039055001172ULL,
16364348268655095621ULL,
3485883307755363459ULL,
10861246708191214529ULL,
};
const std::pair<const char *, uint32_t> EmpireCityAdabatFiles[] = {
{ "#ActD_SubBeach_01.ar.00", 2 },
{ "#ActD_SubBeach_01.arl", 2 },
{ "#ActD_SubBeach_03.ar.00", 2 },
{ "#ActD_SubBeach_03.arl", 2 },
{ "#ActD_SubBeach_05.ar.00", 2 },
{ "#ActD_SubBeach_05.arl", 2 },
{ "#ActD_SubNY_02.ar.00", 2 },
{ "#ActD_SubNY_02.arl", 2 },
{ "#ActD_SubNY_03.ar.00", 2 },
{ "#ActD_SubNY_03.arl", 2 },
{ "#ActN_SubBeach_02.ar.00", 2 },
{ "#ActN_SubBeach_02.arl", 2 },
{ "#ActN_SubBeach_03.ar.00", 2 },
{ "#ActN_SubBeach_03.arl", 2 },
{ "#ActN_SubNY_01.ar.00", 2 },
{ "#ActN_SubNY_01.arl", 2 },
{ "#ActN_SubNY_02.ar.00", 2 },
{ "#ActN_SubNY_02.arl", 2 },
{ "#Application.ar.00", 2 },
{ "#Application.arl", 2 },
{ "ActD_SubBeach_01.ar.00", 2 },
{ "ActD_SubBeach_01.arl", 2 },
{ "ActD_SubBeach_03.ar.00", 2 },
{ "ActD_SubBeach_03.arl", 2 },
{ "ActD_SubBeach_05.ar.00", 2 },
{ "ActD_SubBeach_05.arl", 2 },
{ "ActD_SubNY_02.ar.00", 2 },
{ "ActD_SubNY_02.arl", 2 },
{ "ActN_SubNY_01.ar.00", 2 },
{ "ActN_SubNY_01.ar.01", 2 },
{ "ActN_SubNY_01.arl", 2 },
{ "Additional/ActD_Beach/Stage-Add.pfd", 1 },
{ "Additional/ActD_NY/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubBeach_01/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubBeach_02/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubBeach_03/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubBeach_04/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubNY_01/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubNY_02/Stage-Add.pfd", 1 },
{ "Additional/ActN_BeachEvil/Stage-Add.pfd", 1 },
{ "Additional/ActN_NYEvil/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubBeach_01/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubNY_01/Stage-Add.pfd", 1 },
{ "Additional/Event_M6_01_temple/Stage-Add.pfd", 1 },
{ "Additional/Town_NYCity/Stage-Add.pfd", 1 },
{ "Additional/Town_NYCityETF/Stage-Add.pfd", 1 },
{ "Additional/Town_NYCityETF_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_NYCity_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_SouthEastAsia/Stage-Add.pfd", 1 },
{ "Additional/Town_SouthEastAsiaETF/Stage-Add.pfd", 1 },
{ "Additional/Town_SouthEastAsiaETF_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_SouthEastAsia_Night/Stage-Add.pfd", 1 },
{ "DLC.xml", 1 },
{ "Languages/English/WorldMap.ar.00", 2 },
{ "Languages/English/WorldMap.arl", 2 },
{ "Languages/French/WorldMap.ar.00", 2 },
{ "Languages/French/WorldMap.arl", 2 },
{ "Languages/German/WorldMap.ar.00", 2 },
{ "Languages/German/WorldMap.arl", 2 },
{ "Languages/Italian/WorldMap.ar.00", 2 },
{ "Languages/Italian/WorldMap.arl", 2 },
{ "Languages/Japanese/WorldMap.ar.00", 2 },
{ "Languages/Japanese/WorldMap.arl", 2 },
{ "Languages/Spanish/WorldMap.ar.00", 2 },
{ "Languages/Spanish/WorldMap.arl", 2 },
{ "Packed/ActD_SubBeach_01/Stage.pfd", 1 },
{ "Packed/ActD_SubBeach_03/Stage.pfd", 1 },
{ "Packed/ActD_SubNY_02/Stage.pfd", 1 },
{ "Packed/ActN_SubNY_01/Stage.pfd", 1 },
{ "WorldMap.ar.00", 2 },
{ "WorldMap.arl", 2 },
{ "work/ActD_Beach/Terrain.prm.xml", 2 },
{ "work/ActD_NY/Terrain.prm.xml", 2 },
{ "work/ActD_SubBeach_02/Terrain.prm.xml", 2 },
{ "work/ActD_SubBeach_04/Terrain.prm.xml", 2 },
{ "work/ActD_SubNY_01/Terrain.prm.xml", 2 },
{ "work/ActN_BeachEvil/Terrain.prm.xml", 2 },
{ "work/ActN_BeachEvil/evl_sea_obj_st_waterCircle.model", 2 },
{ "work/ActN_BeachEvil/sea_water_circle-0000.texture", 2 },
{ "work/ActN_BeachEvil/sea_water_circle-0000.uv-anim", 2 },
{ "work/ActN_BeachEvil/sea_water_circle-0001.texture", 2 },
{ "work/ActN_BeachEvil/sea_water_circle-0001.uv-anim", 2 },
{ "work/ActN_BeachEvil/sea_water_circle-0002.texture", 2 },
{ "work/ActN_BeachEvil/sea_water_circle-0002.uv-anim", 2 },
{ "work/ActN_BeachEvil/sea_water_circle-0003.texture", 2 },
{ "work/ActN_BeachEvil/sea_water_circle.material", 2 },
{ "work/ActN_BeachEvil/sea_water_circle.texset", 2 },
{ "work/ActN_BeachEvil/sea_water_kt_MotionWater_dif.dds", 2 },
{ "work/ActN_BeachEvil/sea_water_kt_MotionWater_env.dds", 2 },
{ "work/ActN_NYEvil/Terrain.prm.xml", 2 },
{ "work/ActN_SubBeach_01/Terrain.prm.xml", 2 },
{ "work/ActN_SubNY_01/Terrain.prm.xml", 2 },
{ "work/BossEggLancer/Terrain.prm.xml", 2 },
{ "work/Event_M4_01_egb_hideout/Terrain.prm.xml", 2 },
{ "work/Event_M6_01_temple/Terrain.prm.xml", 2 },
{ "work/Event_egb_hidout_exterior/Terrain.prm.xml", 2 },
{ "work/Town_NYCity/Terrain.prm.xml", 2 },
{ "work/Town_NYCityETF/Terrain.prm.xml", 2 },
{ "work/Town_NYCityETF_Night/Terrain.prm.xml", 2 },
{ "work/Town_NYCity_Night/Terrain.prm.xml", 2 },
{ "work/Town_SouthEastAsia/Terrain.prm.xml", 2 },
{ "work/Town_SouthEastAsiaETF/Terrain.prm.xml", 2 },
{ "work/Town_SouthEastAsiaETF_Night/Terrain.prm.xml", 2 },
{ "work/Town_SouthEastAsia_Night/Terrain.prm.xml", 2 },
};
const size_t EmpireCityAdabatFilesSize = std::size(EmpireCityAdabatFiles);

View file

@ -0,0 +1,10 @@
// File automatically generated by fshasher
#pragma once
#include <utility>
extern const uint64_t EmpireCityAdabatHashes[];
extern const std::pair<const char *, uint32_t> EmpireCityAdabatFiles[];
extern const size_t EmpireCityAdabatFilesSize;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
// File automatically generated by fshasher
#pragma once
#include <utility>
extern const uint64_t GameHashes[];
extern const std::pair<const char *, uint32_t> GameFiles[];
extern const size_t GameFilesSize;

View file

@ -0,0 +1,198 @@
// File automatically generated by fshasher
#include <utility>
extern const uint64_t HoloskaHashes[];
extern const std::pair<const char *, uint32_t> HoloskaFiles[];
extern const size_t HoloskaFilesSize;
const uint64_t HoloskaHashes[] = {
2675323900039702065ULL,
4277145918507575318ULL,
37003434483691695ULL,
173772618141122105ULL,
10014684039097108933ULL,
12951885659934974458ULL,
5420475312037398025ULL,
8990457325890346487ULL,
5828311441007099177ULL,
15895929894456691275ULL,
10944334660972947767ULL,
12306207387774270676ULL,
1133020226384470961ULL,
10978642704870093657ULL,
6571493200930941260ULL,
12775456849440773110ULL,
10993350723710351594ULL,
18266525158058610108ULL,
5224405159313498915ULL,
7030149124581670257ULL,
743509372682842317ULL,
6268515345337947410ULL,
621840898472918401ULL,
7793408857629121219ULL,
3628810224077277661ULL,
4735419277840395906ULL,
6298408288867498004ULL,
9672923561522739380ULL,
8577458973002786949ULL,
8580672557326042084ULL,
905264783309208510ULL,
5442721461459141559ULL,
5665558615477908883ULL,
9138114750084480028ULL,
10560390528404575839ULL,
13571388347857105531ULL,
759267540240315808ULL,
1395028858241468056ULL,
4101501475292075848ULL,
13453093997934088664ULL,
16436427612349862673ULL,
16475130472640188313ULL,
1009427446083976444ULL,
6227277608980222296ULL,
13005423191223021574ULL,
7227050263030209202ULL,
9777930758670277699ULL,
877007860174867856ULL,
18238103507153996510ULL,
17930956651100144422ULL,
9455059678942766083ULL,
11513193284629003124ULL,
14582773429927700438ULL,
7832492987792635365ULL,
14922683551437897222ULL,
2218036959448726520ULL,
5915675802417501794ULL,
4734985573199693091ULL,
13894625369955491525ULL,
10164406532505859673ULL,
11738098787596070522ULL,
2506038633852497536ULL,
3066872557705014675ULL,
13577051447279183181ULL,
15995491349078614893ULL,
7170529702240695373ULL,
9610481190777925774ULL,
9843976124448789757ULL,
15999881082141829716ULL,
1705967380110975175ULL,
1945492401042462005ULL,
12826054609787136918ULL,
14947608392493777252ULL,
8454834516025688631ULL,
16412196874867098033ULL,
1302335439956347422ULL,
5904634235948759380ULL,
837467602803878420ULL,
11838669303496931932ULL,
1026342585625625691ULL,
17104669198514697031ULL,
6226633771929098644ULL,
11434391196365401368ULL,
1825358492785453273ULL,
10935112159483090915ULL,
10368247242044046510ULL,
5360854891214543592ULL,
9184741739495532010ULL,
1210820029035423219ULL,
3779694736357686174ULL,
11026144482164398929ULL,
17624209453030784775ULL,
1268371966612623612ULL,
17620869642251638393ULL,
7500432003580343090ULL,
8925351885022805937ULL,
4545712040089568182ULL,
16087391183543862251ULL,
7153771647341077160ULL,
15835374550237816233ULL,
10806012310881440072ULL,
14061367079592692873ULL,
16856865969950237558ULL,
16918398385542560356ULL,
7838032685469261389ULL,
14087988214801845031ULL,
287254560976480823ULL,
4291829565728637795ULL,
1833361518702399357ULL,
9945701995133200218ULL,
12195458283103638803ULL,
18193324349862755987ULL,
3992878524104250195ULL,
9683778847586532057ULL,
640455243454193529ULL,
9845312495153161302ULL,
};
const std::pair<const char *, uint32_t> HoloskaFiles[] = {
{ "#ActD_SubSnow_02.ar.00", 2 },
{ "#ActD_SubSnow_02.arl", 2 },
{ "#ActD_SubSnow_03.ar.00", 2 },
{ "#ActD_SubSnow_03.arl", 2 },
{ "#ActD_SubSnow_04.ar.00", 2 },
{ "#ActD_SubSnow_04.arl", 2 },
{ "#ActD_SubSnow_05.ar.00", 2 },
{ "#ActD_SubSnow_05.arl", 2 },
{ "#ActN_SubSnow_01.ar.00", 2 },
{ "#ActN_SubSnow_01.arl", 2 },
{ "#ActN_SubSnow_02.ar.00", 2 },
{ "#ActN_SubSnow_02.arl", 2 },
{ "#Application.ar.00", 2 },
{ "#Application.arl", 2 },
{ "ActD_SubSnow_02.ar.00", 2 },
{ "ActD_SubSnow_02.arl", 2 },
{ "ActD_SubSnow_03.ar.00", 2 },
{ "ActD_SubSnow_03.arl", 2 },
{ "ActN_SubSnow_01.ar.00", 2 },
{ "ActN_SubSnow_01.arl", 2 },
{ "ActN_SubSnow_02.ar.00", 2 },
{ "ActN_SubSnow_02.arl", 2 },
{ "Additional/ActD_Snow/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubSnow_01/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubSnow_02/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubSnow_03/Stage-Add.pfd", 1 },
{ "Additional/ActN_SnowEvil/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubSnow_01/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubSnow_02/Stage-Add.pfd", 1 },
{ "Additional/BossDarkGaiaMoray/Stage-Add.pfd", 1 },
{ "Additional/Town_Snow/Stage-Add.pfd", 1 },
{ "Additional/Town_SnowETF/Stage-Add.pfd", 1 },
{ "Additional/Town_SnowETF_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_Snow_Night/Stage-Add.pfd", 1 },
{ "DLC.xml", 1 },
{ "Languages/English/WorldMap.ar.00", 2 },
{ "Languages/English/WorldMap.arl", 2 },
{ "Languages/French/WorldMap.ar.00", 2 },
{ "Languages/French/WorldMap.arl", 2 },
{ "Languages/German/WorldMap.ar.00", 2 },
{ "Languages/German/WorldMap.arl", 2 },
{ "Languages/Italian/WorldMap.ar.00", 2 },
{ "Languages/Italian/WorldMap.arl", 2 },
{ "Languages/Japanese/WorldMap.ar.00", 2 },
{ "Languages/Japanese/WorldMap.arl", 2 },
{ "Languages/Spanish/WorldMap.ar.00", 2 },
{ "Languages/Spanish/WorldMap.arl", 2 },
{ "Packed/ActD_SubSnow_02/Stage.pfd", 1 },
{ "Packed/ActD_SubSnow_03/Stage.pfd", 1 },
{ "Packed/ActN_SubSnow_01/Stage.pfd", 1 },
{ "Packed/ActN_SubSnow_02/Stage.pfd", 1 },
{ "Snow.dmy", 1 },
{ "WorldMap.ar.00", 2 },
{ "WorldMap.arl", 2 },
{ "work/ActD_Snow/Terrain.prm.xml", 2 },
{ "work/ActD_SubSnow_01/Terrain.prm.xml", 2 },
{ "work/ActN_SnowEvil/Terrain.prm.xml", 2 },
{ "work/SystemCommon/SR_EnterSnowDayActionSub02.seq.xml", 2 },
{ "work/SystemCommon/SR_EnterSnowDayActionSub03.seq.xml", 2 },
{ "work/SystemCommon/SR_EnterSnowDayActionSub04.seq.xml", 2 },
{ "work/SystemCommon/SR_EnterSnowDayActionSub05.seq.xml", 2 },
{ "work/SystemCommon/SR_EnterSnowNightActionSub01.seq.xml", 2 },
{ "work/SystemCommon/SR_EnterSnowNightActionSub02.seq.xml", 2 },
{ "work/Town_Snow/Terrain.prm.xml", 2 },
{ "work/Town_SnowETF/Terrain.prm.xml", 2 },
{ "work/Town_SnowETF_Night/Terrain.prm.xml", 2 },
{ "work/Town_Snow_Night/Terrain.prm.xml", 2 },
};
const size_t HoloskaFilesSize = std::size(HoloskaFiles);

View file

@ -0,0 +1,10 @@
// File automatically generated by fshasher
#pragma once
#include <utility>
extern const uint64_t HoloskaHashes[];
extern const std::pair<const char *, uint32_t> HoloskaFiles[];
extern const size_t HoloskaFilesSize;

View file

@ -0,0 +1,207 @@
// File automatically generated by fshasher
#include <utility>
extern const uint64_t MazuriHashes[];
extern const std::pair<const char *, uint32_t> MazuriFiles[];
extern const size_t MazuriFilesSize;
const uint64_t MazuriHashes[] = {
8310122492208490409ULL,
14334946716403322379ULL,
6359932846637962320ULL,
15564392554394963521ULL,
4145580004644844748ULL,
5886649247807377109ULL,
8972853618768766550ULL,
16274784949553698740ULL,
7644922894813285241ULL,
16792340405196435317ULL,
7219363224850733712ULL,
14783821508665540659ULL,
90578118658326728ULL,
322485799435034401ULL,
11426516458306753692ULL,
16875540838976300804ULL,
691543308196576824ULL,
12748185913217986160ULL,
181497269294558465ULL,
13384184373605717208ULL,
9680733245723597610ULL,
11319512019915225753ULL,
5156260857743175296ULL,
17024263853193085482ULL,
9161916068418754052ULL,
15099371411684664534ULL,
8811039586506246550ULL,
13344975442165302144ULL,
11616101478448229001ULL,
16890442647900693500ULL,
10613805266180977971ULL,
12232891231073729485ULL,
5827659615582798219ULL,
10044950643067304005ULL,
10405571379268351804ULL,
17852400551605150551ULL,
8485124434733981977ULL,
12177189339342254810ULL,
6914067631133882133ULL,
8516330542230824299ULL,
5227234300746783898ULL,
11316739451124421549ULL,
4006017088443300321ULL,
8144466479720785800ULL,
14807147190400377590ULL,
17032461497328235598ULL,
3485364854384528759ULL,
12314750532364104301ULL,
18171772732457610158ULL,
15511223977167516261ULL,
3733997387449100578ULL,
3127614471276641962ULL,
16473237911443301576ULL,
8519032476298984207ULL,
1282659255712538566ULL,
9032400057270978659ULL,
1438891209736948095ULL,
9445272093697834862ULL,
17042589027198683899ULL,
17834939632591927531ULL,
17963312833258248676ULL,
11212796985379953935ULL,
8520618928746918706ULL,
12620044629892189723ULL,
17231920840550500769ULL,
14751571373966723331ULL,
15192962011256471386ULL,
5641128099468064081ULL,
15353897587627765076ULL,
9694642992346267925ULL,
12849306083654146943ULL,
12611788236218891748ULL,
13815021343500055451ULL,
17210166018872705038ULL,
17856279508890497700ULL,
15236163838001295487ULL,
15463039469068597027ULL,
3188082504873605346ULL,
10762266561438768212ULL,
842630621715284677ULL,
1961824913476492580ULL,
5152443868555296771ULL,
10324715709102329399ULL,
1763695362403970051ULL,
3584532033227831137ULL,
5851561062160893814ULL,
10927370304760079285ULL,
13011799481510971519ULL,
17677776816961090167ULL,
13923999678594617040ULL,
5360854891214543592ULL,
9184741739495532010ULL,
1210820029035423219ULL,
3779694736357686174ULL,
45507338756965322ULL,
17034560519917147503ULL,
615638415508415245ULL,
12821211946029354361ULL,
173839052608557733ULL,
6298694170641867436ULL,
12773486509288875531ULL,
12866025506444541977ULL,
3427216651702511068ULL,
8503894857294129869ULL,
8658756214229842885ULL,
12517977842558558441ULL,
8772357560481233720ULL,
9298201679300251287ULL,
1413776131937655221ULL,
16241630498833235183ULL,
3107620193063708762ULL,
18350383620420541255ULL,
108006630585092978ULL,
171415973667919914ULL,
1817978790908311521ULL,
7165219417836909758ULL,
5462363977947174456ULL,
8840489652156575872ULL,
10345266752933627695ULL,
15321193045322335042ULL,
471637519459967124ULL,
6817966383869009387ULL,
};
const std::pair<const char *, uint32_t> MazuriFiles[] = {
{ "#ActD_SubAfrica_02.ar.00", 2 },
{ "#ActD_SubAfrica_02.arl", 2 },
{ "#ActD_SubAfrica_04.ar.00", 2 },
{ "#ActD_SubAfrica_04.arl", 2 },
{ "#ActD_SubAfrica_05.ar.00", 2 },
{ "#ActD_SubAfrica_05.arl", 2 },
{ "#ActD_SubAfrica_06.ar.00", 2 },
{ "#ActD_SubAfrica_06.arl", 2 },
{ "#ActN_SubAfrica_02.ar.00", 2 },
{ "#ActN_SubAfrica_02.arl", 2 },
{ "#ActN_SubAfrica_03.ar.00", 2 },
{ "#ActN_SubAfrica_03.arl", 2 },
{ "#Application.ar.00", 2 },
{ "#Application.arl", 2 },
{ "ActD_SubAfrica_02.ar.00", 2 },
{ "ActD_SubAfrica_02.ar.01", 2 },
{ "ActD_SubAfrica_02.arl", 2 },
{ "ActD_SubAfrica_06.ar.00", 2 },
{ "ActD_SubAfrica_06.arl", 2 },
{ "ActN_SubAfrica_02.ar.00", 2 },
{ "ActN_SubAfrica_02.arl", 2 },
{ "ActN_SubAfrica_03.ar.00", 2 },
{ "ActN_SubAfrica_03.ar.01", 2 },
{ "ActN_SubAfrica_03.arl", 2 },
{ "Additional/ActD_Africa/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubAfrica_01/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubAfrica_02/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubAfrica_03/Stage-Add.pfd", 1 },
{ "Additional/ActN_AfricaEvil/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubAfrica_01/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubAfrica_02/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubAfrica_03/Stage-Add.pfd", 1 },
{ "Additional/BossEggBeetle/Stage-Add.pfd", 1 },
{ "Additional/Event_afr_hideout/Stage-Add.pfd", 1 },
{ "Additional/Town_Africa/Stage-Add.pfd", 1 },
{ "Additional/Town_AfricaETF/Stage-Add.pfd", 1 },
{ "Additional/Town_AfricaETF_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_Africa_Night/Stage-Add.pfd", 1 },
{ "DLC.xml", 1 },
{ "Languages/English/WorldMap.ar.00", 2 },
{ "Languages/English/WorldMap.arl", 2 },
{ "Languages/French/WorldMap.ar.00", 2 },
{ "Languages/French/WorldMap.arl", 2 },
{ "Languages/German/WorldMap.ar.00", 2 },
{ "Languages/German/WorldMap.arl", 2 },
{ "Languages/Italian/WorldMap.ar.00", 2 },
{ "Languages/Italian/WorldMap.arl", 2 },
{ "Languages/Japanese/WorldMap.ar.00", 2 },
{ "Languages/Japanese/WorldMap.arl", 2 },
{ "Languages/Spanish/WorldMap.ar.00", 2 },
{ "Languages/Spanish/WorldMap.arl", 2 },
{ "Packed/ActD_SubAfrica_02/Stage.pfd", 1 },
{ "Packed/ActN_SubAfrica_02/Stage.pfd", 1 },
{ "Packed/ActN_SubAfrica_03/Stage.pfd", 1 },
{ "WorldMap.ar.00", 2 },
{ "WorldMap.arl", 2 },
{ "work/ActD_Africa/Terrain.prm.xml", 2 },
{ "work/ActD_SubAfrica_01/Terrain.prm.xml", 2 },
{ "work/ActD_SubAfrica_02/Terrain.prm.xml", 2 },
{ "work/ActD_SubAfrica_03/Terrain.prm.xml", 2 },
{ "work/ActN_AfricaEvil/Terrain.prm.xml", 2 },
{ "work/ActN_SubAfrica_01/Terrain.prm.xml", 2 },
{ "work/ActN_SubAfrica_02/Terrain.prm.xml", 2 },
{ "work/ActN_SubAfrica_03/Terrain.prm.xml", 2 },
{ "work/BossEggBeetle/Terrain.prm.xml", 2 },
{ "work/Event_afr_hideout/Terrain.prm.xml", 2 },
{ "work/Town_Africa/Terrain.prm.xml", 2 },
{ "work/Town_AfricaETF/Terrain.prm.xml", 2 },
{ "work/Town_AfricaETF_Night/Terrain.prm.xml", 2 },
{ "work/Town_Africa_Night/Terrain.prm.xml", 2 },
};
const size_t MazuriFilesSize = std::size(MazuriFiles);

View file

@ -0,0 +1,10 @@
// File automatically generated by fshasher
#pragma once
#include <utility>
extern const uint64_t MazuriHashes[];
extern const std::pair<const char *, uint32_t> MazuriFiles[];
extern const size_t MazuriFilesSize;

View file

@ -0,0 +1,207 @@
// File automatically generated by fshasher
#include <utility>
extern const uint64_t SpagoniaHashes[];
extern const std::pair<const char *, uint32_t> SpagoniaFiles[];
extern const size_t SpagoniaFilesSize;
const uint64_t SpagoniaHashes[] = {
14867694217129324738ULL,
17100842773888706873ULL,
9752656502718674446ULL,
15671159208413511075ULL,
1812603898025740639ULL,
12613450881360770457ULL,
1154935869782185816ULL,
8219769246658808506ULL,
9695895323794780829ULL,
15823038424497033867ULL,
5817217669193198611ULL,
6497441520538379134ULL,
10139383215379183246ULL,
13620914181664931155ULL,
4668974212184368466ULL,
10500741838109391509ULL,
647311874653915105ULL,
7661921675863949967ULL,
323394983647000743ULL,
797187618373021610ULL,
353294754399582215ULL,
16740492856758014010ULL,
1952614254672843308ULL,
2667940006400122486ULL,
12564343012022085590ULL,
16154216407056286457ULL,
6298408288867498004ULL,
9672923561522739380ULL,
14834906762895569303ULL,
16854111345280229225ULL,
4837640393588267349ULL,
16098597153130008996ULL,
12600320757740560262ULL,
12939475623775993690ULL,
10477103196696189363ULL,
12732657904265336737ULL,
13095277609263620069ULL,
16750231076154973610ULL,
2022042165098050912ULL,
17839881966578661233ULL,
5534774080348504073ULL,
14030101124033407301ULL,
6718729617636463209ULL,
12440200088588141894ULL,
6369118057772660528ULL,
7315135301096821941ULL,
7602114569538282692ULL,
11336647640667052949ULL,
10152111535756487098ULL,
5265464758157500744ULL,
5234945774616460434ULL,
10795075404967515916ULL,
13538969501315954190ULL,
17102608468773106653ULL,
14121317735511216399ULL,
5157803794135169420ULL,
7199840892697349122ULL,
1225096793739555247ULL,
9128945470099535129ULL,
4734985573199693091ULL,
13894625369955491525ULL,
10164406532505859673ULL,
11738098787596070522ULL,
2506038633852497536ULL,
3066872557705014675ULL,
13577051447279183181ULL,
15995491349078614893ULL,
7170529702240695373ULL,
9610481190777925774ULL,
9843976124448789757ULL,
15999881082141829716ULL,
1705967380110975175ULL,
1945492401042462005ULL,
12826054609787136918ULL,
14947608392493777252ULL,
8454834516025688631ULL,
16412196874867098033ULL,
1302335439956347422ULL,
5904634235948759380ULL,
837467602803878420ULL,
11838669303496931932ULL,
1026342585625625691ULL,
17104669198514697031ULL,
3709548792784456596ULL,
16703932146892522057ULL,
3247313303103704726ULL,
5360854891214543592ULL,
9184741739495532010ULL,
1210820029035423219ULL,
3779694736357686174ULL,
1269303531989512502ULL,
15044160000628794537ULL,
8716580408330791802ULL,
16789543858655671716ULL,
2003346667352944922ULL,
15313758642971562051ULL,
2739790431130553532ULL,
6783670011455890702ULL,
697852188952975609ULL,
17185281492289182605ULL,
5699074391444795502ULL,
6148412876775458906ULL,
2511824900179921974ULL,
9520550565124263974ULL,
1879590205302053732ULL,
16445653421013634259ULL,
163664281434034891ULL,
14992345757081400517ULL,
3472557166861866135ULL,
7070790832398904932ULL,
14233035291283305411ULL,
17367771094700951804ULL,
14233035291283305411ULL,
17367771094700951804ULL,
4805103552407476622ULL,
10627247764815196413ULL,
4664450768495384140ULL,
11104978684669875710ULL,
4664450768495384140ULL,
11104978684669875710ULL,
16106838734002406075ULL,
16965457262918815126ULL,
};
const std::pair<const char *, uint32_t> SpagoniaFiles[] = {
{ "#ActD_SubEU_03.ar.00", 2 },
{ "#ActD_SubEU_03.arl", 2 },
{ "#ActD_SubEU_04.ar.00", 2 },
{ "#ActD_SubEU_04.arl", 2 },
{ "#ActD_SubEU_05.ar.00", 2 },
{ "#ActD_SubEU_05.arl", 2 },
{ "#ActD_SubEU_06.ar.00", 2 },
{ "#ActD_SubEU_06.arl", 2 },
{ "#ActN_SubEU_01.ar.00", 2 },
{ "#ActN_SubEU_01.arl", 2 },
{ "#ActN_SubEU_02.ar.00", 2 },
{ "#ActN_SubEU_02.arl", 2 },
{ "#Application.ar.00", 2 },
{ "#Application.arl", 2 },
{ "ActD_SubEU_03.ar.00", 2 },
{ "ActD_SubEU_03.ar.01", 2 },
{ "ActD_SubEU_03.arl", 2 },
{ "ActD_SubEU_04.ar.00", 2 },
{ "ActD_SubEU_04.ar.01", 2 },
{ "ActD_SubEU_04.arl", 2 },
{ "ActN_SubEU_01.ar.00", 2 },
{ "ActN_SubEU_01.arl", 2 },
{ "Additional/ActD_EU/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubEU_01/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubEU_02/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubEU_03/Stage-Add.pfd", 1 },
{ "Additional/ActD_SubEU_04/Stage-Add.pfd", 1 },
{ "Additional/ActN_EUEvil/Stage-Add.pfd", 1 },
{ "Additional/ActN_SubEU_01/Stage-Add.pfd", 1 },
{ "Additional/Event_M2_01_professor_room_new/Stage-Add.pfd", 1 },
{ "Additional/Town_EULabo/Stage-Add.pfd", 1 },
{ "Additional/Town_EULabo_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_EuropeanCity/Stage-Add.pfd", 1 },
{ "Additional/Town_EuropeanCityETF/Stage-Add.pfd", 1 },
{ "Additional/Town_EuropeanCityETF_Night/Stage-Add.pfd", 1 },
{ "Additional/Town_EuropeanCity_Night/Stage-Add.pfd", 1 },
{ "DLC.xml", 1 },
{ "Languages/English/WorldMap.ar.00", 2 },
{ "Languages/English/WorldMap.arl", 2 },
{ "Languages/French/WorldMap.ar.00", 2 },
{ "Languages/French/WorldMap.arl", 2 },
{ "Languages/German/WorldMap.ar.00", 2 },
{ "Languages/German/WorldMap.arl", 2 },
{ "Languages/Italian/WorldMap.ar.00", 2 },
{ "Languages/Italian/WorldMap.arl", 2 },
{ "Languages/Japanese/WorldMap.ar.00", 2 },
{ "Languages/Japanese/WorldMap.arl", 2 },
{ "Languages/Spanish/WorldMap.ar.00", 2 },
{ "Languages/Spanish/WorldMap.arl", 2 },
{ "Packed/ActD_SubEU_03/Stage.pfd", 1 },
{ "Packed/ActD_SubEU_04/Stage.pfd", 1 },
{ "Packed/ActN_SubEU_01/Stage.pfd", 1 },
{ "WorldMap.ar.00", 2 },
{ "WorldMap.arl", 2 },
{ "work/ActD_EU/Terrain.prm.xml", 2 },
{ "work/ActD_SubEU_01/Terrain.prm.xml", 2 },
{ "work/ActD_SubEU_02/Terrain.prm.xml", 2 },
{ "work/ActN_EUEvil/Terrain.prm.xml", 2 },
{ "work/BossEggRayBird/Terrain.prm.xml", 2 },
{ "work/Event_M2_01_professor_room_new/Terrain.prm.xml", 2 },
{ "work/SystemCommon/SR_EnterEUDayActionSub03.seq.xml", 2 },
{ "work/SystemCommon/SR_EnterEUDayActionSub04.seq.xml", 2 },
{ "work/SystemCommon/SR_EnterEUDayActionSub05.seq.xml", 2 },
{ "work/SystemCommon/SR_EnterEUNightActionSub02.seq.xml", 2 },
{ "work/Town_EULabo/Terrain.prm.xml", 2 },
{ "work/Town_EULabo_Night/Terrain.prm.xml", 2 },
{ "work/Town_EuropeanCity/Terrain.prm.xml", 2 },
{ "work/Town_EuropeanCityETF/Terrain.prm.xml", 2 },
{ "work/Town_EuropeanCityETF_Night/Terrain.prm.xml", 2 },
{ "work/Town_EuropeanCity_Night/Terrain.prm.xml", 2 },
};
const size_t SpagoniaFilesSize = std::size(SpagoniaFiles);

View file

@ -0,0 +1,10 @@
// File automatically generated by fshasher
#pragma once
#include <utility>
extern const uint64_t SpagoniaHashes[];
extern const std::pair<const char *, uint32_t> SpagoniaFiles[];
extern const size_t SpagoniaFilesSize;

View file

@ -0,0 +1,89 @@
// File automatically generated by fshasher
#include <utility>
extern const uint64_t UpdateHashes[];
extern const std::pair<const char *, uint32_t> UpdateFiles[];
extern const size_t UpdateFilesSize;
const uint64_t UpdateHashes[] = {
6914273463875662709ULL,
15542186142639918255ULL,
17773094197787397017ULL,
5694064368761413534ULL,
10660633045276223515ULL,
6259845327508088719ULL,
12140399948272279979ULL,
7418638713822288133ULL,
14117267789244251538ULL,
15188168166630393460ULL,
3161308809281010162ULL,
5322614488283725236ULL,
4606314145682200491ULL,
11955725616840921688ULL,
11797277641169528627ULL,
16412181293791116322ULL,
11038823378333165548ULL,
5479982606338303627ULL,
868771203788675788ULL,
17001824000099573976ULL,
9159616851251538481ULL,
15568568481517853886ULL,
10521442355407577670ULL,
9513615512117450320ULL,
4920031480332452542ULL,
17152633532642839320ULL,
1430448682009298272ULL,
2819842435698853770ULL,
1430448682009298272ULL,
2819842435698853770ULL,
13038055622504309783ULL,
1430448682009298272ULL,
2819842435698853770ULL,
3623875508130760747ULL,
1400141374194792546ULL,
3560111161029067800ULL,
2610060740632518377ULL,
2610060740632518377ULL,
};
const std::pair<const char *, uint32_t> UpdateFiles[] = {
{ "default.xexp", 3 },
{ "work/ActD_MykonosAct1/Base.set.xml", 1 },
{ "work/ActD_NY/Mission_NYCity_S20_10.set.xml", 1 },
{ "work/ActD_SubEU_01/Set2.set.xml", 1 },
{ "work/ActD_SubNY_01/Set.set.xml", 1 },
{ "work/ActD_SubNY_01/Stage.stg.xml", 1 },
{ "work/ActD_SubNY_01/ny_sub_path.path.xml", 1 },
{ "work/ActD_SubSnow_01/Sub_Layer1_Set.set.xml", 1 },
{ "work/ActD_SubSnow_01/Sub_Layer2_Set.set.xml", 1 },
{ "work/ActD_SubSnow_01/Sub_Layer3_Set.set.xml", 1 },
{ "work/ActN_BeachEvil/evl_sea_obj_st_waterCircle.model", 1 },
{ "work/ActN_BeachEvil/sea_water_circle.material", 1 },
{ "work/ActN_ChinaEvil/area06_enemyset.set.xml", 1 },
{ "work/ActN_ChinaEvil/area07_enemyset.set.xml", 1 },
{ "work/ActN_ChinaEvil/system.set.xml", 1 },
{ "work/ActN_EUEvil/area16_gimmickset.set.xml", 1 },
{ "work/ActN_EUEvil/area18_gimmickset.set.xml", 1 },
{ "work/ActN_EUEvil/system.set.xml", 1 },
{ "work/ActN_PetraEvil/system.set.xml", 1 },
{ "work/ActN_SubChina_01/area01_enemyset.set.xml", 1 },
{ "work/ActN_SubChina_01/area01_gimmickset.set.xml", 1 },
{ "work/Act_EggmanLand/BaseEvil.set.xml", 1 },
{ "work/Act_EggmanLand/system.set.xml", 1 },
{ "work/Application/SR_AdjustTownState.seq.xml", 1 },
{ "work/CmnTown_Mykonos/myk_obj_soc_paperboxABC.dds", 1 },
{ "work/CmnTown_Snow/snw_obj_snowman04_dif.dds", 1 },
{ "work/EvilActionCommon_Mykonos/myk_obj_soc_paperboxABC.dds", 1 },
{ "work/EvilActionCommon_Snow/snw_obj_snowman04_dif.dds", 1 },
{ "work/Inspire/scene/evrt_m5_02/evrt_m5_02.inspire_resource.xml", 1 },
{ "work/SonicActionCommon_Mykonos/myk_obj_soc_paperboxABC.dds", 1 },
{ "work/SonicActionCommon_Snow/snw_obj_snowman04_dif.dds", 1 },
{ "work/Title/mat_mainmenu_common_001.dds", 1 },
{ "work/Town_EuropeanCity_Dispel/Mission_EuropeanCity_S30_10.set.xml", 1 },
{ "work/Town_NYCity_Dispel/Mission_MoveMission_S20_10.set.xml", 1 },
{ "work/Town_SouthEastAsiaETF/CommonOBJ.set.xml", 1 },
{ "work/Town_SouthEastAsiaETF_Night/CommonOBJ.set.xml", 1 },
};
const size_t UpdateFilesSize = std::size(UpdateFiles);

View file

@ -0,0 +1,10 @@
// File automatically generated by fshasher
#pragma once
#include <utility>
extern const uint64_t UpdateHashes[];
extern const std::pair<const char *, uint32_t> UpdateFiles[];
extern const size_t UpdateFilesSize;

View file

@ -0,0 +1,492 @@
#include "installer.h"
#include <xxh3.h>
#include "directory_file_system.h"
#include "iso_file_system.h"
#include "xcontent_file_system.h"
#include "hashes/apotos_shamar.h"
#include "hashes/chunnan.h"
#include "hashes/empire_city_adabat.h"
#include "hashes/game.h"
#include "hashes/holoska.h"
#include "hashes/mazuri.h"
#include "hashes/spagonia.h"
#include "hashes/update.h"
static const std::string GameDirectory = "game";
static const std::string DLCDirectory = "dlc";
static const std::string ApotosShamarDirectory = DLCDirectory + "/Apotos & Shamar Adventure Pack";
static const std::string ChunnanDirectory = DLCDirectory + "/Chunnan Adventure Pack";
static const std::string EmpireCityAdabatDirectory = DLCDirectory + "/Empire City & Adabat Adventure Pack";
static const std::string HoloskaDirectory = DLCDirectory + "/Holoska Adventure Pack";
static const std::string MazuriDirectory = DLCDirectory + "/Mazuri Adventure Pack";
static const std::string SpagoniaDirectory = DLCDirectory + "/Spagonia Adventure Pack";
static const std::string UpdateDirectory = "update";
static const std::string GameExecutableFile = "default.xex";
static const std::string DLCValidationFile = "DLC.xml";
static const std::string UpdateExecutablePatchFile = "default.xexp";
static const std::string ISOExtension = ".iso";
static const std::string OldExtension = ".old";
static const std::string TempExtension = ".tmp";
static std::string fromU8(const std::u8string &str)
{
return std::string(str.begin(), str.end());
}
static std::string fromPath(const std::filesystem::path &path)
{
return fromU8(path.u8string());
}
static std::string toLower(std::string str) {
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); });
return str;
};
static std::unique_ptr<VirtualFileSystem> createFileSystemFromPath(const std::filesystem::path &path)
{
if (XContentFileSystem::check(path))
{
return XContentFileSystem::create(path);
}
else if (toLower(path.extension().string()) == ISOExtension)
{
return ISOFileSystem::create(path);
}
else if (std::filesystem::is_directory(path))
{
return DirectoryFileSystem::create(path);
}
else
{
return nullptr;
}
}
static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector<uint8_t> &fileData, Journal &journal, const std::function<void(uint32_t)> &progressCallback) {
const std::string filename(pair.first);
const uint32_t hashCount = pair.second;
if (!sourceVfs.exists(filename))
{
journal.lastResult = Journal::Result::FileMissing;
journal.lastErrorMessage = std::format("File {} does not exist in the file system.", filename);
return false;
}
if (!sourceVfs.load(filename, fileData))
{
journal.lastResult = Journal::Result::FileReadFailed;
journal.lastErrorMessage = std::format("Failed to read file {} from the file system.", filename);
return false;
}
if (!skipHashChecks)
{
uint64_t fileHash = XXH3_64bits(fileData.data(), fileData.size());
bool fileHashFound = false;
for (uint32_t i = 0; i < hashCount && !fileHashFound; i++)
{
fileHashFound = fileHash == fileHashes[i];
}
if (!fileHashFound)
{
journal.lastResult = Journal::Result::FileHashFailed;
journal.lastErrorMessage = std::format("File {} from the file system did not match any of the known hashes.", filename);
return false;
}
}
std::filesystem::path targetPath = targetDirectory / std::filesystem::path(std::u8string_view((const char8_t *)(pair.first)));
std::filesystem::path parentPath = targetPath.parent_path();
if (!std::filesystem::exists(parentPath))
{
std::filesystem::create_directories(parentPath);
}
while (!parentPath.empty()) {
journal.createdDirectories.insert(parentPath);
if (parentPath != targetDirectory) {
parentPath = parentPath.parent_path();
}
else {
parentPath = std::filesystem::path();
}
}
std::ofstream outStream(targetPath, std::ios::binary);
if (!outStream.is_open())
{
journal.lastResult = Journal::Result::FileCreationFailed;
journal.lastErrorMessage = std::format("Failed to create file at {}.", targetPath.string());
return false;
}
journal.createdFiles.push_back(targetPath);
outStream.write((const char *)(fileData.data()), fileData.size());
if (outStream.bad())
{
journal.lastResult = Journal::Result::FileWriteFailed;
journal.lastErrorMessage = std::format("Failed to create file at {}.", targetPath.string());
return false;
}
progressCallback(++journal.progressCounter);
return true;
}
static DLC detectDLC(const std::filesystem::path &sourcePath, VirtualFileSystem &sourceVfs, Journal &journal)
{
std::vector<uint8_t> dlcXmlBytes;
if (!sourceVfs.load(DLCValidationFile, dlcXmlBytes))
{
journal.lastResult = Journal::Result::FileMissing;
journal.lastErrorMessage = std::format("File {} does not exist in the file system.", DLCValidationFile);
return DLC::Unknown;
}
const char TypeStartString[] = "<Type>";
const char TypeEndString[] = "</Type>";
size_t dlcByteCount = dlcXmlBytes.size();
dlcXmlBytes.resize(dlcByteCount + 1);
dlcXmlBytes[dlcByteCount] = '\0';
const char *typeStartLocation = strstr((const char *)(dlcXmlBytes.data()), TypeStartString);
const char *typeEndLocation = typeStartLocation != nullptr ? strstr(typeStartLocation, TypeEndString) : nullptr;
if (typeStartLocation == nullptr || typeEndLocation == nullptr)
{
journal.lastResult = Journal::Result::DLCParsingFailed;
journal.lastErrorMessage = "Failed to find DLC type for " + sourcePath.string() + ".";
return DLC::Unknown;
}
const char *typeNumberLocation = typeStartLocation + strlen(TypeStartString);
size_t typeNumberCount = typeEndLocation - typeNumberLocation;
if (typeNumberCount != 1)
{
journal.lastResult = Journal::Result::UnknownDLCType;
journal.lastErrorMessage = "DLC type for " + sourcePath.string() + " is unknown.";
return DLC::Unknown;
}
switch (*typeNumberLocation)
{
case '1':
return DLC::Spagonia;
case '2':
return DLC::Chunnan;
case '3':
return DLC::Mazuri;
case '4':
return DLC::Holoska;
case '5':
return DLC::ApotosShamar;
case '7':
return DLC::EmpireCityAdabat;
default:
journal.lastResult = Journal::Result::UnknownDLCType;
journal.lastErrorMessage = "DLC type for " + sourcePath.string() + " is unknown.";
return DLC::Unknown;
}
}
bool Installer::checkGameInstall(const std::filesystem::path &baseDirectory)
{
return std::filesystem::exists(baseDirectory / GameDirectory / GameExecutableFile);
}
bool Installer::copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void(uint32_t)> &progressCallback)
{
if (!std::filesystem::exists(targetDirectory) && !std::filesystem::create_directories(targetDirectory))
{
journal.lastResult = Journal::Result::DirectoryCreationFailed;
journal.lastErrorMessage = "Unable to create directory at " + fromPath(targetDirectory);
return false;
}
FilePair validationPair = {};
uint32_t validationHashIndex = 0;
uint32_t hashIndex = 0;
uint32_t hashCount = 0;
std::vector<uint8_t> fileData;
for (FilePair pair : filePairs)
{
hashIndex = hashCount;
hashCount += pair.second;
if (validationFile.compare(pair.first) == 0)
{
validationPair = pair;
validationHashIndex = hashIndex;
continue;
}
if (!copyFile(pair, &fileHashes[hashIndex], sourceVfs, targetDirectory, skipHashChecks, fileData, journal, progressCallback))
{
return false;
}
}
// Validation file is copied last after all other files have been copied.
if (validationPair.first != nullptr)
{
if (!copyFile(validationPair, &fileHashes[validationHashIndex], sourceVfs, targetDirectory, skipHashChecks, fileData, journal, progressCallback))
{
return false;
}
}
else
{
journal.lastResult = Journal::Result::ValidationFileMissing;
journal.lastErrorMessage = std::format("Unable to find validation file {} in file system.", validationFile);
return false;
}
return true;
}
bool Installer::parseContent(const std::filesystem::path &sourcePath, std::unique_ptr<VirtualFileSystem> &targetVfs, Journal &journal)
{
targetVfs = createFileSystemFromPath(sourcePath);
if (targetVfs != nullptr)
{
return true;
}
else
{
journal.lastResult = Journal::Result::VirtualFileSystemFailed;
journal.lastErrorMessage = "Unable to open file system at " + fromPath(sourcePath);
return false;
}
}
bool Installer::install(const Input &input, const std::filesystem::path &targetDirectory, Journal &journal, const std::function<void(uint32_t)> &progressCallback)
{
// Parse the contents of the base game.
std::unique_ptr<VirtualFileSystem> gameSource;
if (!input.gameSource.empty())
{
if (!parseContent(input.gameSource, gameSource, journal))
{
return false;
}
journal.progressTotal += GameFilesSize;
}
// Parse the contents of Update.
std::unique_ptr<VirtualFileSystem> updateSource;
if (!input.updateSource.empty())
{
if (!parseContent(input.updateSource, updateSource, journal))
{
return false;
}
journal.progressTotal += UpdateFilesSize;
}
// Parse the contents of the DLC Packs.
struct DLCSource {
std::unique_ptr<VirtualFileSystem> sourceVfs;
std::span<const FilePair> filePairs;
const uint64_t *fileHashes = nullptr;
std::string targetSubDirectory;
};
std::vector<DLCSource> dlcSources;
for (const auto &path : input.dlcSources)
{
dlcSources.emplace_back();
DLCSource &dlcSource = dlcSources.back();
if (!parseContent(path, dlcSource.sourceVfs, journal))
{
return false;
}
DLC dlc = detectDLC(path, *dlcSource.sourceVfs, journal);
switch (dlc)
{
case DLC::Spagonia:
dlcSource.filePairs = { SpagoniaFiles, SpagoniaFilesSize };
dlcSource.fileHashes = SpagoniaHashes;
dlcSource.targetSubDirectory = SpagoniaDirectory;
break;
case DLC::Chunnan:
dlcSource.filePairs = { ChunnanFiles, ChunnanFilesSize };
dlcSource.fileHashes = ChunnanHashes;
dlcSource.targetSubDirectory = ChunnanDirectory;
break;
case DLC::Mazuri:
dlcSource.filePairs = { MazuriFiles, MazuriFilesSize };
dlcSource.fileHashes = MazuriHashes;
dlcSource.targetSubDirectory = MazuriDirectory;
break;
case DLC::Holoska:
dlcSource.filePairs = { HoloskaFiles, HoloskaFilesSize };
dlcSource.fileHashes = HoloskaHashes;
dlcSource.targetSubDirectory = HoloskaDirectory;
break;
case DLC::ApotosShamar:
dlcSource.filePairs = { ApotosShamarFiles, ApotosShamarFilesSize };
dlcSource.fileHashes = ApotosShamarHashes;
dlcSource.targetSubDirectory = ApotosShamarDirectory;
break;
case DLC::EmpireCityAdabat:
dlcSource.filePairs = { EmpireCityAdabatFiles, EmpireCityAdabatFilesSize };
dlcSource.fileHashes = EmpireCityAdabatHashes;
dlcSource.targetSubDirectory = EmpireCityAdabatDirectory;
break;
default:
return false;
}
journal.progressTotal += dlcSource.filePairs.size();
}
// Install the base game.
if (!copyFiles({ GameFiles, GameFilesSize }, GameHashes, *gameSource, targetDirectory / GameDirectory, GameExecutableFile, input.skipHashChecks, journal, progressCallback))
{
return false;
}
// Install the update.
if (!copyFiles({ UpdateFiles, UpdateFilesSize }, UpdateHashes, *updateSource, targetDirectory / UpdateDirectory, UpdateExecutablePatchFile, input.skipHashChecks, journal, progressCallback))
{
return false;
}
// Patch the executable with the update's file.
std::filesystem::path baseXexPath = targetDirectory / GameDirectory / GameExecutableFile;
std::filesystem::path patchPath = targetDirectory / UpdateDirectory / UpdateExecutablePatchFile;
std::filesystem::path patchedXexPath = targetDirectory / GameDirectory / (GameExecutableFile + TempExtension);
XexPatcher::Result patcherResult = XexPatcher::apply(baseXexPath, patchPath, patchedXexPath);
if (patcherResult != XexPatcher::Result::Success)
{
journal.lastResult = Journal::Result::PatchProcessFailed;
journal.lastPatcherResult = patcherResult;
journal.lastErrorMessage = "Patch process failed.";
return false;
}
// Replace the executable by renaming and deleting in a safe way.
std::error_code ec;
std::filesystem::path oldXexPath = targetDirectory / GameDirectory / (GameExecutableFile + OldExtension);
std::filesystem::rename(baseXexPath, oldXexPath, ec);
if (ec)
{
journal.lastResult = Journal::Result::PatchReplacementFailed;
journal.lastErrorMessage = "Failed to rename executable.";
return false;
}
std::filesystem::rename(patchedXexPath, baseXexPath, ec);
if (ec)
{
std::filesystem::rename(oldXexPath, baseXexPath, ec);
journal.lastResult = Journal::Result::PatchReplacementFailed;
journal.lastErrorMessage = "Failed to rename executable.";
return false;
}
std::filesystem::remove(oldXexPath);
// Install the DLC.
if (!dlcSources.empty())
{
journal.createdDirectories.insert(targetDirectory / DLCDirectory);
}
for (const DLCSource &dlcSource : dlcSources)
{
if (!copyFiles(dlcSource.filePairs, dlcSource.fileHashes, *dlcSource.sourceVfs, targetDirectory / dlcSource.targetSubDirectory, DLCValidationFile, input.skipHashChecks, journal, progressCallback))
{
return false;
}
}
return true;
}
void Installer::rollback(Journal &journal)
{
std::error_code ec;
for (const auto &path : journal.createdFiles)
{
std::filesystem::remove(path, ec);
}
for (auto it = journal.createdDirectories.rbegin(); it != journal.createdDirectories.rend(); it++)
{
std::filesystem::remove(*it, ec);
}
}
bool Installer::parseGame(const std::filesystem::path &sourcePath)
{
std::unique_ptr<VirtualFileSystem> sourceVfs = createFileSystemFromPath(sourcePath);
if (sourceVfs == nullptr)
{
return false;
}
return sourceVfs->exists(GameExecutableFile);
}
bool Installer::parseUpdate(const std::filesystem::path &sourcePath)
{
std::unique_ptr<VirtualFileSystem> sourceVfs = createFileSystemFromPath(sourcePath);
if (sourceVfs == nullptr)
{
return false;
}
return sourceVfs->exists(UpdateExecutablePatchFile);
}
DLC Installer::parseDLC(const std::filesystem::path &sourcePath)
{
Journal journal;
std::unique_ptr<VirtualFileSystem> sourceVfs = createFileSystemFromPath(sourcePath);
if (sourceVfs == nullptr)
{
return DLC::Unknown;
}
return detectDLC(sourcePath, *sourceVfs, journal);
}
XexPatcher::Result Installer::checkGameUpdateCompatibility(const std::filesystem::path &gameSourcePath, const std::filesystem::path &updateSourcePath)
{
std::unique_ptr<VirtualFileSystem> gameSourceVfs = createFileSystemFromPath(gameSourcePath);
if (gameSourceVfs == nullptr)
{
return XexPatcher::Result::FileOpenFailed;
}
std::unique_ptr<VirtualFileSystem> updateSourceVfs = createFileSystemFromPath(updateSourcePath);
if (updateSourceVfs == nullptr)
{
return XexPatcher::Result::FileOpenFailed;
}
std::vector<uint8_t> xexBytes;
std::vector<uint8_t> patchBytes;
if (!gameSourceVfs->load(GameExecutableFile, xexBytes))
{
return XexPatcher::Result::FileOpenFailed;
}
if (!updateSourceVfs->load(UpdateExecutablePatchFile, patchBytes))
{
return XexPatcher::Result::FileOpenFailed;
}
std::vector<uint8_t> patchedBytes;
return XexPatcher::apply(xexBytes, patchBytes, patchedBytes, true);
}

View file

@ -0,0 +1,76 @@
#pragma once
#include <span>
#include <set>
#include "virtual_file_system.h"
#include "xex_patcher.h"
enum class DLC {
Unknown,
Spagonia,
Chunnan,
Mazuri,
Holoska,
ApotosShamar,
EmpireCityAdabat
};
struct Journal
{
enum class Result
{
Success,
VirtualFileSystemFailed,
DirectoryCreationFailed,
FileMissing,
FileReadFailed,
FileHashFailed,
FileCreationFailed,
FileWriteFailed,
ValidationFileMissing,
DLCParsingFailed,
PatchProcessFailed,
PatchReplacementFailed,
UnknownDLCType
};
uint32_t progressCounter = 0;
uint32_t progressTotal = 0;
std::list<std::filesystem::path> createdFiles;
std::set<std::filesystem::path> createdDirectories;
Result lastResult = Result::Success;
XexPatcher::Result lastPatcherResult = XexPatcher::Result::Success;
std::string lastErrorMessage;
};
using FilePair = std::pair<const char *, uint32_t>;
struct Installer
{
struct Input
{
std::filesystem::path gameSource;
std::filesystem::path updateSource;
std::list<std::filesystem::path> dlcSources;
bool skipHashChecks = false;
};
static bool checkGameInstall(const std::filesystem::path &baseDirectory);
static bool copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void(uint32_t)> &progressCallback);
static bool parseContent(const std::filesystem::path &sourcePath, std::unique_ptr<VirtualFileSystem> &targetVfs, Journal &journal);
static bool install(const Input &input, const std::filesystem::path &targetDirectory, Journal &journal, const std::function<void(uint32_t)> &progressCallback);
static void rollback(Journal &journal);
// Convenience method for checking if the specified file contains the game. This should be used when the user selects the file.
static bool parseGame(const std::filesystem::path &sourcePath);
// Convenience method for checking if the specified file contains the update. This should be used when the user selects the file.
static bool parseUpdate(const std::filesystem::path &sourcePath);
// Convenience method for the installer to check which DLC the file that was specified corresponds to. This should be used when the user selects the file.
static DLC parseDLC(const std::filesystem::path &sourcePath);
// Convenience method for checking if a game and an update are compatible. This should be used when the user presses next during installation.
static XexPatcher::Result checkGameUpdateCompatibility(const std::filesystem::path &gameSourcePath, const std::filesystem::path &updateSourcePath);
};

View file

@ -0,0 +1,191 @@
// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/vfs/devices/disc_image_device.cc
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "iso_file_system.h"
#include <stack>
ISOFileSystem::ISOFileSystem(const std::filesystem::path &isoPath)
{
mappedFile.open(isoPath);
if (!mappedFile.isOpen())
{
return;
}
// Find root sector.
const uint8_t *mappedFileData = mappedFile.data();
uint32_t gameOffset = 0;
const size_t XeSectorSize = 2048;
static const size_t PossibleOffsets[] = { 0x00000000, 0x0000FB20, 0x00020600, 0x02080000, 0x0FD90000, };
bool magicFound = false;
const char RefMagic[] = "MICROSOFT*XBOX*MEDIA";
for (size_t i = 0; i < std::size(PossibleOffsets); i++)
{
size_t fileOffset = PossibleOffsets[i] + (32 * XeSectorSize);
if ((fileOffset + strlen(RefMagic)) > mappedFile.size())
{
continue;
}
if (std::memcmp(&mappedFileData[fileOffset], RefMagic, strlen(RefMagic)) == 0)
{
gameOffset = PossibleOffsets[i];
magicFound = true;
}
}
size_t rootInfoOffset = gameOffset + (32 * XeSectorSize) + 20;
if (!magicFound || (rootInfoOffset + 8) > mappedFile.size())
{
mappedFile.close();
return;
}
// Parse root information.
uint32_t rootSector = *(uint32_t *)(&mappedFileData[rootInfoOffset + 0]);
uint32_t rootSize = *(uint32_t *)(&mappedFileData[rootInfoOffset + 4]);
size_t rootOffset = gameOffset + (rootSector * XeSectorSize);
const uint32_t MinRootSize = 13;
const uint32_t MaxRootSize = 32 * 1024 * 1024;
if ((rootSize < MinRootSize) || (rootSize > MaxRootSize))
{
mappedFile.close();
return;
}
struct IterationStep
{
std::string fileNameBase;
size_t nodeOffset = 0;
size_t entryOffset = 0;
IterationStep() = default;
IterationStep(std::string fileNameBase, size_t nodeOffset, size_t entryOffset) : fileNameBase(fileNameBase), nodeOffset(nodeOffset), entryOffset(entryOffset) { }
};
std::stack<IterationStep> iterationStack;
iterationStack.emplace("", rootOffset, 0);
IterationStep step;
uint16_t nodeL, nodeR;
uint32_t sector, length;
uint8_t attributes, nameLength;
char fileName[256];
const uint8_t FileAttributeDirectory = 0x10;
while (!iterationStack.empty())
{
step = iterationStack.top();
iterationStack.pop();
size_t infoOffset = step.nodeOffset + step.entryOffset;
if ((infoOffset + 14) > mappedFile.size())
{
mappedFile.close();
return;
}
nodeL = *(uint16_t *)(&mappedFileData[infoOffset + 0]);
nodeR = *(uint16_t *)(&mappedFileData[infoOffset + 2]);
sector = *(uint32_t *)(&mappedFileData[infoOffset + 4]);
length = *(uint32_t *)(&mappedFileData[infoOffset + 8]);
attributes = *(uint8_t *)(&mappedFileData[infoOffset + 12]);
nameLength = *(uint8_t *)(&mappedFileData[infoOffset + 13]);
size_t nameOffset = infoOffset + 14;
if ((nameOffset + nameLength) > mappedFile.size())
{
mappedFile.close();
return;
}
memcpy(fileName, &mappedFileData[nameOffset], nameLength);
fileName[nameLength] = '\0';
if (nodeL)
{
iterationStack.emplace(step.fileNameBase, step.nodeOffset, nodeL * 4);
}
if (nodeR)
{
iterationStack.emplace(step.fileNameBase, step.nodeOffset, nodeR * 4);
}
std::string fileNameUTF8 = step.fileNameBase + fileName;
if (attributes & FileAttributeDirectory)
{
if (length > 0)
{
iterationStack.emplace(fileNameUTF8 + "/", gameOffset + sector * XeSectorSize, 0);
}
}
else
{
fileMap[fileNameUTF8] = { gameOffset + sector * XeSectorSize, length};
}
}
}
bool ISOFileSystem::load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const
{
auto it = fileMap.find(path);
if (it != fileMap.end())
{
if (fileDataMaxByteCount < std::get<1>(it->second))
{
return false;
}
const uint8_t *mappedFileData = mappedFile.data();
memcpy(fileData, &mappedFileData[std::get<0>(it->second)], std::get<1>(it->second));
return true;
}
else
{
return false;
}
}
size_t ISOFileSystem::getSize(const std::string &path) const
{
auto it = fileMap.find(path);
if (it != fileMap.end())
{
return std::get<1>(it->second);
}
else
{
return 0;
}
}
bool ISOFileSystem::exists(const std::string &path) const
{
return fileMap.find(path) != fileMap.end();
}
bool ISOFileSystem::empty() const
{
return !mappedFile.isOpen();
}
std::unique_ptr<ISOFileSystem> ISOFileSystem::create(const std::filesystem::path &isoPath) {
std::unique_ptr<ISOFileSystem> isoFs = std::make_unique<ISOFileSystem>(isoPath);
if (!isoFs->empty())
{
return isoFs;
}
else
{
return nullptr;
}
}

View file

@ -0,0 +1,33 @@
// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/vfs/devices/disc_image_device.cc
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#pragma once
#include <filesystem>
#include <map>
#include "virtual_file_system.h"
#include "memory_mapped_file.h"
struct ISOFileSystem : VirtualFileSystem
{
MemoryMappedFile mappedFile;
std::map<std::string, std::tuple<size_t, size_t>> fileMap;
ISOFileSystem(const std::filesystem::path &isoPath);
bool load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const override;
size_t getSize(const std::string &path) const override;
bool exists(const std::string &path) const override;
bool empty() const;
static std::unique_ptr<ISOFileSystem> create(const std::filesystem::path &isoPath);
};

View file

@ -0,0 +1,169 @@
#include "memory_mapped_file.h"
#if !defined(_WIN32)
# include <cstring>
# include <cstdio>
# include <fcntl.h>
# include <unistd.h>
#endif
MemoryMappedFile::MemoryMappedFile()
{
// Default constructor.
}
MemoryMappedFile::MemoryMappedFile(const std::filesystem::path &path)
{
open(path);
}
MemoryMappedFile::~MemoryMappedFile()
{
close();
}
MemoryMappedFile::MemoryMappedFile(MemoryMappedFile &&other)
{
#if defined(_WIN32)
fileHandle = other.fileHandle;
fileMappingHandle = other.fileMappingHandle;
fileView = other.fileView;
fileSize = other.fileSize;
other.fileHandle = nullptr;
other.fileMappingHandle = nullptr;
other.fileView = nullptr;
other.fileSize.QuadPart = 0;
#else
fileHandle = other.fileHandle;
fileView = other.fileView;
fileSize = other.fileSize;
other.fileHandle = -1;
other.fileView = MAP_FAILED;
other.fileSize = 0;
#endif
}
bool MemoryMappedFile::open(const std::filesystem::path &path)
{
#if defined(_WIN32)
fileHandle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (fileHandle == INVALID_HANDLE_VALUE)
{
fprintf(stderr, "CreateFileW failed with error %lu.\n", GetLastError());
fileHandle = nullptr;
return false;
}
if (!GetFileSizeEx(fileHandle, &fileSize))
{
fprintf(stderr, "GetFileSizeEx failed with error %lu.\n", GetLastError());
CloseHandle(fileHandle);
fileHandle = nullptr;
return false;
}
fileMappingHandle = CreateFileMappingW(fileHandle, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (fileMappingHandle == nullptr)
{
fprintf(stderr, "CreateFileMappingW failed with error %lu.\n", GetLastError());
CloseHandle(fileHandle);
fileHandle = nullptr;
return false;
}
fileView = MapViewOfFile(fileMappingHandle, FILE_MAP_READ, 0, 0, 0);
if (fileView == nullptr)
{
fprintf(stderr, "MapViewOfFile failed with error %lu.\n", GetLastError());
CloseHandle(fileMappingHandle);
CloseHandle(fileHandle);
fileMappingHandle = nullptr;
fileHandle = nullptr;
return false;
}
return true;
#else
fileHandle = ::open(path.c_str(), O_RDONLY);
if (fileHandle == -1)
{
fprintf(stderr, "open for %s failed with error %s.\n", path.c_str(), strerror(errno));
return false;
}
fileSize = lseek(fileHandle, 0, SEEK_END);
if (fileSize == (off_t)(-1))
{
fprintf(stderr, "lseek failed with error %s.\n", strerror(errno));
close(fileHandle);
fileHandle = -1;
return false;
}
fileView = mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fileHandle, 0);
if (fileView == MAP_FAILED)
{
fprintf(stderr, "mmap failed with error %s.\n", strerror(errno));
close(fileHandle);
fileHandle = -1;
return false;
}
return true;
#endif
}
void MemoryMappedFile::close()
{
#if defined(_WIN32)
if (fileView != nullptr)
{
UnmapViewOfFile(fileView);
}
if (fileMappingHandle != nullptr)
{
CloseHandle(fileMappingHandle);
}
if (fileHandle != nullptr)
{
CloseHandle(fileHandle);
}
#else
if (fileView != MAP_FAILED)
{
munmap(fileView, fileSize);
}
if (fileHandle != -1)
{
close(fileHandle);
}
#endif
}
bool MemoryMappedFile::isOpen() const
{
#if defined(_WIN32)
return (fileView != nullptr);
#else
return (fileView != MAP_FAILED);
#endif
}
uint8_t *MemoryMappedFile::data() const
{
return reinterpret_cast<uint8_t *>(fileView);
}
size_t MemoryMappedFile::size() const
{
#if defined(_WIN32)
return fileSize.QuadPart;
#else
return static_cast<size_t>(fileSize);
#endif
}

View file

@ -0,0 +1,32 @@
#pragma once
#include <filesystem>
#if defined(_WIN32)
# include <Windows.h>
#else
# include <sys/mman.h>
#endif
struct MemoryMappedFile {
#if defined(_WIN32)
HANDLE fileHandle = nullptr;
HANDLE fileMappingHandle = nullptr;
LPVOID fileView = nullptr;
LARGE_INTEGER fileSize = {};
#else
int fileHandle = -1;
void *fileView = MAP_FAILED;
off_t fileSize = 0;
#endif
MemoryMappedFile();
MemoryMappedFile(const std::filesystem::path &path);
MemoryMappedFile(MemoryMappedFile &&other);
~MemoryMappedFile();
bool open(const std::filesystem::path &path);
void close();
bool isOpen() const;
uint8_t *data() const;
size_t size() const;
};

View file

@ -0,0 +1,24 @@
#pragma once
#include <algorithm>
#include <memory>
struct VirtualFileSystem {
virtual ~VirtualFileSystem() { };
virtual bool load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const = 0;
virtual size_t getSize(const std::string &path) const = 0;
virtual bool exists(const std::string &path) const = 0;
// Concrete implementation shortcut.
bool load(const std::string &path, std::vector<uint8_t> &fileData)
{
size_t fileDataSize = getSize(path);
if (fileDataSize == 0)
{
return false;
}
fileData.resize(fileDataSize);
return load(path, fileData.data(), fileDataSize);
}
};

View file

@ -0,0 +1,641 @@
// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/vfs/devices/xcontent_container_device.cc
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xcontent_file_system.h"
#include <bit>
#include <set>
#include <stack>
enum class XContentPackageType
{
CON = 0x434F4E20,
PIRS = 0x50495253,
LIVE = 0x4C495645,
};
struct XContentLicense
{
be<uint64_t> licenseId;
be<uint32_t> licenseBits;
be<uint32_t> licenseFlags;
};
#pragma pack(push, 1)
struct XContentHeader
{
be<uint32_t> magic;
uint8_t signature[0x228];
XContentLicense licenses[0x10];
uint8_t contentId[0x14];
be<uint32_t> headerSize;
};
static_assert(sizeof(XContentHeader) == 0x344);
struct StfsVolumeDescriptor
{
uint8_t descriptorLength;
uint8_t version;
union
{
uint8_t asByte;
struct
{
uint8_t readOnlyFormat : 1;
uint8_t rootActiveIndex : 1;
uint8_t directoryOverallocated : 1;
uint8_t directoryIndexBoundsValid : 1;
} bits;
} flags;
uint16_t fileTableBlockCount;
uint8_t fileTableBlockNumberRaw[3];
uint8_t topHashTableHash[0x14];
be<uint32_t> totalBlockCount;
be<uint32_t> freeBlockCount;
};
static_assert(sizeof(StfsVolumeDescriptor) == 0x24);
struct StfsDirectoryEntry {
char name[40];
struct
{
uint8_t nameLength : 6;
uint8_t contiguous : 1;
uint8_t directory : 1;
} flags;
uint8_t validDataBlocksRaw[3];
uint8_t allocatedDataBlocksRaw[3];
uint8_t startBlockNumberRaw[3];
be<uint16_t> directoryIndex;
be<uint32_t> length;
be<uint16_t> createDate;
be<uint16_t> createTime;
be<uint16_t> modifiedDate;
be<uint16_t> modifiedTime;
};
static_assert(sizeof(StfsDirectoryEntry) == 0x40);
struct StfsDirectoryBlock {
StfsDirectoryEntry entries[0x40];
};
static_assert(sizeof(StfsDirectoryBlock) == 0x1000);
struct StfsHashEntry {
uint8_t sha1[0x14];
be<uint32_t> infoRaw;
};
static_assert(sizeof(StfsHashEntry) == 0x18);
struct StfsHashTable {
StfsHashEntry entries[170];
be<uint32_t> numBlocks;
uint8_t padding[12];
};
static_assert(sizeof(StfsHashTable) == 0x1000);
struct SvodDeviceDescriptor {
uint8_t descriptorLength;
uint8_t blockCacheElementCount;
uint8_t workerThreadProcessor;
uint8_t workerThreadPriority;
uint8_t firstFragmentHashEntry[0x14];
union {
uint8_t asByte;
struct {
uint8_t mustBeZeroForFutureUsage : 6;
uint8_t enhancedGdfLayout : 1;
uint8_t zeroForDownlevelClients : 1;
} bits;
} features;
uint8_t numDataBlocksRaw[3];
uint8_t startDataBlockRaw[3];
uint8_t reserved[5];
};
static_assert(sizeof(SvodDeviceDescriptor) == 0x24);
struct SvodDirectoryEntry {
uint16_t nodeL;
uint16_t nodeR;
uint32_t dataBlock;
uint32_t length;
uint8_t attributes;
uint8_t nameLength;
};
static_assert(sizeof(SvodDirectoryEntry) == 0xE);
struct XContentMetadata
{
be<uint32_t> contentType;
be<uint32_t> metadataVersion;
be<uint64_t> contentSize;
uint8_t executionInfo[24];
uint8_t consoleId[5];
be<uint64_t> profileId;
union {
StfsVolumeDescriptor stfsVolumeDescriptor;
SvodDeviceDescriptor svodDeviceDescriptor;
};
be<uint32_t> dataFileCount;
be<uint64_t> dataFileSize;
be<uint32_t> volumeType;
be<uint64_t> onlineCreator;
be<uint32_t> category;
};
static_assert(sizeof(XContentMetadata) == 0x75);
#pragma pack(pop)
struct XContentContainerHeader
{
XContentHeader contentHeader;
XContentMetadata contentMetadata;
};
const uint32_t StfsBlockSize = 0x1000;
const uint32_t StfsBlocksHashLevelAmount = 3;
const uint32_t StfsBlocksPerHashLevel[StfsBlocksHashLevelAmount] = { 170, 28900, 4913000 };
const uint32_t StfsEndOfChain = 0xFFFFFF;
const uint32_t StfsEntriesPerDirectoryBlock = StfsBlockSize / sizeof(StfsDirectoryEntry);
uint32_t parseUint24(const uint8_t *bytes) {
return bytes[0] | (bytes[1] << 8U) | (bytes[2] << 16U);
}
size_t blockIndexToOffset(uint64_t baseOffset, uint64_t blockIndex)
{
uint64_t block = blockIndex;
for (uint32_t i = 0; i < StfsBlocksHashLevelAmount; i++)
{
uint32_t levelBase = StfsBlocksPerHashLevel[i];
block += ((blockIndex + levelBase) / levelBase);
if (blockIndex < levelBase)
{
break;
}
}
return baseOffset + (block << 12);
}
uint32_t blockIndexToHashBlockNumber(uint32_t blockIndex) {
if (blockIndex < StfsBlocksPerHashLevel[0])
{
return 0;
}
uint32_t block = (blockIndex / StfsBlocksPerHashLevel[0]) * (StfsBlocksPerHashLevel[0] + 1);
block += ((blockIndex / StfsBlocksPerHashLevel[1]) + 1);
if (blockIndex < StfsBlocksPerHashLevel[1])
{
return block;
}
return block + 1;
}
size_t blockIndexToHashBlockOffset(uint64_t baseOffset, uint32_t blockIndex)
{
size_t blockNumber = blockIndexToHashBlockNumber(blockIndex);
return baseOffset + (blockNumber << 12);
}
const StfsHashEntry *hashEntryFromBlockIndex(const uint8_t *fileData, uint64_t baseOffset, uint64_t blockIndex)
{
size_t hashOffset = blockIndexToHashBlockOffset(baseOffset, blockIndex);
const StfsHashTable *hashTable = (const StfsHashTable *)(&fileData[hashOffset]);
return &hashTable->entries[blockIndex % StfsBlocksPerHashLevel[0]];
}
void blockToOffsetAndFile(SvodLayoutType svodLayoutType, size_t svodStartDataBlock, size_t svodBaseOffset, size_t block, size_t &outOffset, size_t &outFileIndex)
{
const size_t BlockSize = 0x800;
const size_t HashBlockSize = 0x1000;
const size_t BlocksPerL0Hash = 0x198;
const size_t HashesPerL1Hash = 0xA1C4;
const size_t BlocksPerFile = 0x14388;
const size_t MaxFileSize = 0xA290000;
size_t trueBlock = block - (svodStartDataBlock * 2);
if (svodLayoutType == SvodLayoutType::EnhancedGDF)
{
trueBlock += 0x2;
}
size_t fileBlock = trueBlock % BlocksPerFile;
outFileIndex = trueBlock / BlocksPerFile;
size_t offset = 0;
size_t level0TableCount = (fileBlock / BlocksPerL0Hash) + 1;
offset += level0TableCount * HashBlockSize;
size_t level1TableCount = (level0TableCount / HashesPerL1Hash) + 1;
offset += level1TableCount * HashBlockSize;
if (svodLayoutType == SvodLayoutType::SingleFile)
{
offset += svodBaseOffset;
}
outOffset = (fileBlock * BlockSize) + offset;
if (outOffset >= MaxFileSize)
{
outOffset = (outOffset % MaxFileSize) + 0x2000;
outFileIndex++;
}
}
XContentFileSystem::XContentFileSystem(const std::filesystem::path &contentPath)
{
mappedFiles.emplace_back();
MemoryMappedFile &rootMappedFile = mappedFiles.back();
rootMappedFile.open(contentPath);
if (!rootMappedFile.isOpen())
{
return;
}
const uint8_t *rootMappedFileData = rootMappedFile.data();
if (sizeof(XContentContainerHeader) > rootMappedFile.size())
{
mappedFiles.clear();
return;
}
XContentContainerHeader contentContainerHeader = *(const XContentContainerHeader *)(rootMappedFileData);
XContentPackageType packageType = XContentPackageType(contentContainerHeader.contentHeader.magic.get());
if (packageType != XContentPackageType::CON && packageType != XContentPackageType::LIVE && packageType != XContentPackageType::PIRS)
{
mappedFiles.clear();
return;
}
const XContentMetadata &metadata = contentContainerHeader.contentMetadata;
volumeType = XContentVolumeType(metadata.volumeType.get());
if (volumeType == XContentVolumeType::STFS)
{
const StfsVolumeDescriptor &descriptor = metadata.stfsVolumeDescriptor;
if (descriptor.descriptorLength != sizeof(StfsVolumeDescriptor) || !descriptor.flags.bits.readOnlyFormat)
{
mappedFiles.clear();
return;
}
baseOffset = ((contentContainerHeader.contentHeader.headerSize + StfsBlockSize - 1) / StfsBlockSize) * StfsBlockSize;
uint32_t entryCount = 0;
uint32_t tableBlockIndex = parseUint24(descriptor.fileTableBlockNumberRaw);
uint32_t tableBlockCount = descriptor.fileTableBlockCount;
std::map<uint32_t, std::string> directoryNames;
for (uint32_t i = 0; i < tableBlockCount; i++)
{
size_t offset = blockIndexToOffset(baseOffset, tableBlockIndex);
if (offset + sizeof(StfsDirectoryBlock) > rootMappedFile.size())
{
mappedFiles.clear();
return;
}
StfsDirectoryBlock *directoryBlock = (StfsDirectoryBlock *)(&rootMappedFileData[offset]);
for (uint32_t j = 0; j < StfsEntriesPerDirectoryBlock; j++)
{
const StfsDirectoryEntry &directoryEntry = directoryBlock->entries[j];
if (directoryEntry.name[0] == '\0')
{
break;
}
std::string fileNameBase = directoryNames[directoryEntry.directoryIndex];
std::string fileName(directoryEntry.name, directoryEntry.flags.nameLength & 0x3F);
if (directoryEntry.flags.directory)
{
directoryNames[entryCount++] = fileNameBase + fileName + "/";
continue;
}
uint32_t fileBlockIndex = parseUint24(directoryEntry.startBlockNumberRaw);
uint32_t fileBlockCount = parseUint24(directoryEntry.allocatedDataBlocksRaw);
fileMap[fileNameBase + fileName] = { directoryEntry.length, fileBlockIndex, fileBlockCount };
entryCount++;
}
const StfsHashEntry *hashEntry = hashEntryFromBlockIndex(rootMappedFileData, baseOffset, tableBlockIndex);
tableBlockIndex = hashEntry->infoRaw & 0xFFFFFF;
if (tableBlockIndex == StfsEndOfChain)
{
break;
}
}
}
else if (volumeType == XContentVolumeType::SVOD)
{
mappedFiles.clear();
// Close the root file and open all the files inside the directory with the same name instead.
std::filesystem::path dataDirectory(contentPath.u8string() + u8".data");
if (!std::filesystem::is_directory(dataDirectory))
{
return;
}
// Find all data files inside the directory.
std::set<std::filesystem::path> orderedPaths;
for (auto &entry : std::filesystem::directory_iterator(dataDirectory))
{
if (!entry.is_regular_file())
{
continue;
}
orderedPaths.emplace(entry.path());
}
// Memory map all the files that were found.
for (auto &path : orderedPaths)
{
mappedFiles.emplace_back();
if (!mappedFiles.back().open(path))
{
mappedFiles.clear();
return;
}
}
if (mappedFiles.empty())
{
return;
}
// Determine the layout of the SVOD from the first file.
MemoryMappedFile &firstMappedFile = mappedFiles.front();
const uint8_t *firstMappedFileData = firstMappedFile.data();
const char *RefMagic = "MICROSOFT*XBOX*MEDIA";
size_t RefXSFMagicOffset = 0x12000;
size_t SingleFileMagicOffset = 0xD000;
if (metadata.svodDeviceDescriptor.features.bits.enhancedGdfLayout)
{
size_t EGDFMagicOffset = 0x2000;
if (EGDFMagicOffset >= firstMappedFile.size() || std::memcmp(&firstMappedFileData[EGDFMagicOffset], RefMagic, strlen(RefMagic)) != 0)
{
mappedFiles.clear();
return;
}
svodBaseOffset = 0;
svodMagicOffset = EGDFMagicOffset;
svodLayoutType = SvodLayoutType::EnhancedGDF;
}
else if (RefXSFMagicOffset < firstMappedFile.size() && std::memcmp(&firstMappedFileData[RefXSFMagicOffset], RefMagic, strlen(RefMagic)) == 0)
{
const char *XSFMagic = "XSF";
size_t XSFMagicOffset = 0x2000;
svodBaseOffset = 0x10000;
svodMagicOffset = 0x12000;
if (std::memcmp(&firstMappedFileData[XSFMagicOffset], XSFMagic, strlen(XSFMagic)) == 0)
{
svodLayoutType = SvodLayoutType::XSF;
}
else
{
svodLayoutType = SvodLayoutType::Unknown;
}
}
else if (SingleFileMagicOffset < firstMappedFile.size() && std::memcmp(&firstMappedFileData[SingleFileMagicOffset], RefMagic, strlen(RefMagic)) == 0)
{
svodBaseOffset = 0xB000;
svodMagicOffset = 0xD000;
svodLayoutType = SvodLayoutType::SingleFile;
}
else {
mappedFiles.clear();
return;
}
svodStartDataBlock = parseUint24(metadata.svodDeviceDescriptor.startDataBlockRaw);
struct IterationStep
{
std::string fileNameBase;
uint32_t blockIndex = 0;
uint32_t ordinalIndex = 0;
IterationStep() = default;
IterationStep(std::string fileNameBase, uint32_t blockIndex, uint32_t ordinalIndex) : fileNameBase(fileNameBase), blockIndex(blockIndex), ordinalIndex(ordinalIndex) { }
};
std::stack<IterationStep> iterationStack;
uint32_t rootBlock = *(uint32_t *)(&firstMappedFileData[svodMagicOffset + 0x14]);
iterationStack.emplace("", rootBlock, 0);
IterationStep step;
size_t fileOffset, fileIndex;
char fileName[256];
const uint8_t FileAttributeDirectory = 0x10;
while (!iterationStack.empty())
{
step = iterationStack.top();
iterationStack.pop();
size_t ordinalOffset = step.ordinalIndex * 0x4;
size_t blockOffset = ordinalOffset / 0x800;
size_t trueOrdinalOffset = ordinalOffset % 0x800;
blockToOffsetAndFile(svodLayoutType, svodStartDataBlock, svodBaseOffset, step.blockIndex + blockOffset, fileOffset, fileIndex);
fileOffset += trueOrdinalOffset;
if (fileIndex >= mappedFiles.size())
{
mappedFiles.clear();
return;
}
const MemoryMappedFile &mappedFile = mappedFiles[fileIndex];
if ((fileOffset + sizeof(SvodDirectoryEntry)) > mappedFile.size())
{
mappedFiles.clear();
return;
}
const uint8_t *mappedFileData = mappedFile.data();
const SvodDirectoryEntry *directoryEntry = (const SvodDirectoryEntry *)(&mappedFileData[fileOffset]);
size_t nameOffset = fileOffset + sizeof(SvodDirectoryEntry);
if ((nameOffset + directoryEntry->nameLength) > mappedFile.size())
{
mappedFiles.clear();
return;
}
memcpy(fileName, &mappedFileData[nameOffset], directoryEntry->nameLength);
fileName[directoryEntry->nameLength] = '\0';
if (directoryEntry->nodeL)
{
iterationStack.emplace(step.fileNameBase, step.blockIndex, directoryEntry->nodeL);
}
if (directoryEntry->nodeR)
{
iterationStack.emplace(step.fileNameBase, step.blockIndex, directoryEntry->nodeR);
}
std::string fileNameUTF8 = step.fileNameBase + fileName;
if (directoryEntry->attributes & FileAttributeDirectory)
{
if (directoryEntry->length > 0)
{
iterationStack.emplace(fileNameUTF8 + "/", directoryEntry->dataBlock, 0);
}
}
else
{
fileMap[fileNameUTF8] = { directoryEntry->length, directoryEntry->dataBlock, 0 };
}
}
}
else
{
mappedFiles.clear();
}
}
bool XContentFileSystem::load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const
{
auto it = fileMap.find(path);
if (it != fileMap.end())
{
if (fileDataMaxByteCount < it->second.size)
{
return false;
}
if (volumeType == XContentVolumeType::STFS)
{
const MemoryMappedFile &rootMappedFile = mappedFiles.back();
const uint8_t *rootMappedFileData = rootMappedFile.data();
size_t fileDataOffset = 0;
size_t remainingSize = it->second.size;
uint32_t fileBlockIndex = it->second.blockIndex;
for (uint32_t i = 0; i < it->second.blockCount && fileBlockIndex != StfsEndOfChain; i++)
{
size_t blockSize = std::min(size_t(StfsBlockSize), remainingSize);
size_t blockOffset = blockIndexToOffset(baseOffset, fileBlockIndex);
if (blockOffset + blockSize > rootMappedFile.size())
{
return false;
}
memcpy(&fileData[fileDataOffset], &rootMappedFileData[blockOffset], blockSize);
const StfsHashEntry *hashEntry = hashEntryFromBlockIndex(rootMappedFileData, baseOffset, fileBlockIndex);
fileBlockIndex = hashEntry->infoRaw & 0xFFFFFF;
fileDataOffset += blockSize;
remainingSize -= blockSize;
}
return remainingSize == 0;
}
else if (volumeType == XContentVolumeType::SVOD)
{
size_t fileDataOffset = 0;
size_t remainingSize = it->second.size;
size_t currentBlock = it->second.blockIndex;
while (remainingSize > 0)
{
size_t blockFileOffset, blockFileIndex;
blockToOffsetAndFile(svodLayoutType, svodStartDataBlock, svodBaseOffset, currentBlock, blockFileOffset, blockFileIndex);
if (blockFileIndex >= mappedFiles.size())
{
return false;
}
const MemoryMappedFile &mappedFile = mappedFiles[blockFileIndex];
const uint8_t *mappedFileData = mappedFile.data();
size_t blockSize = std::min(size_t(0x800), remainingSize);
if (blockFileOffset + blockSize > mappedFile.size())
{
return false;
}
memcpy(&fileData[fileDataOffset], &mappedFileData[blockFileOffset], blockSize);
fileDataOffset += blockSize;
remainingSize -= blockSize;
currentBlock++;
}
return remainingSize == 0;
}
else
{
return false;
}
}
else
{
return false;
}
}
size_t XContentFileSystem::getSize(const std::string &path) const
{
auto it = fileMap.find(path);
if (it != fileMap.end())
{
return it->second.size;
}
else
{
return 0;
}
}
bool XContentFileSystem::exists(const std::string &path) const
{
return fileMap.find(path) != fileMap.end();
}
bool XContentFileSystem::empty() const
{
return mappedFiles.empty();
}
std::unique_ptr<XContentFileSystem> XContentFileSystem::create(const std::filesystem::path &contentPath)
{
std::unique_ptr<XContentFileSystem> xContentFS = std::make_unique<XContentFileSystem>(contentPath);
if (!xContentFS->empty())
{
return xContentFS;
}
else
{
return nullptr;
}
}
bool XContentFileSystem::check(const std::filesystem::path &contentPath)
{
std::ifstream contentStream(contentPath, std::ios::binary);
if (!contentStream.is_open())
{
return false;
}
uint32_t packageTypeUint = 0;
contentStream.read((char *)(&packageTypeUint), sizeof(uint32_t));
packageTypeUint = std::byteswap(packageTypeUint);
XContentPackageType packageType = XContentPackageType(packageTypeUint);
return packageType == XContentPackageType::CON || packageType == XContentPackageType::LIVE || packageType == XContentPackageType::PIRS;
}

View file

@ -0,0 +1,61 @@
// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/vfs/devices/xcontent_container_device.cc
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#pragma once
#include <filesystem>
#include <map>
#include "virtual_file_system.h"
#include "memory_mapped_file.h"
enum class XContentVolumeType
{
STFS = 0,
SVOD = 1,
};
enum class SvodLayoutType
{
Unknown = 0x0,
EnhancedGDF = 0x1,
XSF = 0x2,
SingleFile = 0x4,
};
struct XContentFileSystem : VirtualFileSystem
{
struct File
{
size_t size = 0;
uint32_t blockIndex = 0;
uint32_t blockCount = 0;
};
XContentVolumeType volumeType = XContentVolumeType::STFS;
SvodLayoutType svodLayoutType = SvodLayoutType::Unknown;
size_t svodStartDataBlock = 0;
size_t svodBaseOffset = 0;
size_t svodMagicOffset = 0;
std::vector<MemoryMappedFile> mappedFiles;
uint64_t baseOffset = 0;
std::map<std::string, File> fileMap;
XContentFileSystem(const std::filesystem::path &contentPath);
bool load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const override;
size_t getSize(const std::string &path) const override;
bool exists(const std::string &path) const override;
bool empty() const;
static std::unique_ptr<XContentFileSystem> create(const std::filesystem::path &contentPath);
static bool check(const std::filesystem::path &contentPath);
};

View file

@ -0,0 +1,693 @@
// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/cpu/xex_module.cc
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xex_patcher.h"
#include <bit>
#include <cassert>
#include <aes.hpp>
#include <lzx.h>
#include <mspack.h>
#include <TinySHA1.hpp>
#include "memory_mapped_file.h"
enum Xex2ModuleFlags
{
XEX_MODULE_MODULE_PATCH = 0x10,
XEX_MODULE_PATCH_FULL = 0x20,
XEX_MODULE_PATCH_DELTA = 0x40,
};
enum Xex2HeaderKeys
{
XEX_HEADER_FILE_FORMAT_INFO = 0x3FF,
XEX_HEADER_DELTA_PATCH_DESCRIPTOR = 0x5FF,
};
enum Xex2EncryptionType
{
XEX_ENCRYPTION_NONE = 0,
XEX_ENCRYPTION_NORMAL = 1,
};
enum Xex2CompressionType
{
XEX_COMPRESSION_NONE = 0,
XEX_COMPRESSION_BASIC = 1,
XEX_COMPRESSION_NORMAL = 2,
XEX_COMPRESSION_DELTA = 3,
};
enum Xex2SectionType
{
XEX_SECTION_CODE = 1,
XEX_SECTION_DATA = 2,
XEX_SECTION_READONLY_DATA = 3,
};
struct Xex2OptHeader
{
be<uint32_t> key;
union
{
be<uint32_t> value;
be<uint32_t> offset;
};
};
struct Xex2Header
{
be<uint32_t> magic;
be<uint32_t> moduleFlags;
be<uint32_t> headerSize;
be<uint32_t> reserved;
be<uint32_t> securityOffset;
be<uint32_t> headerCount;
Xex2OptHeader headers[1];
};
struct Xex2PageDescriptor
{
union
{
// Must be endian-swapped before reading the bitfield.
uint32_t beValue;
struct
{
uint32_t info : 4;
uint32_t pageCount : 28;
};
};
char dataDigest[0x14];
};
struct Xex2SecurityInfo
{
be<uint32_t> headerSize;
be<uint32_t> imageSize;
char rsaSignature[0x100];
be<uint32_t> unknown;
be<uint32_t> imageFlags;
be<uint32_t> loadAddress;
char sectionDigest[0x14];
be<uint32_t> importTableCount;
char importTableDigest[0x14];
char xgd2MediaId[0x10];
char aesKey[0x10];
be<uint32_t> exportTable;
char headerDigest[0x14];
be<uint32_t> region;
be<uint32_t> allowedMediaTypes;
be<uint32_t> pageDescriptorCount;
Xex2PageDescriptor pageDescriptors[1];
};
struct Xex2DeltaPatch
{
be<uint32_t> oldAddress;
be<uint32_t> newAddress;
be<uint16_t> uncompressedLength;
be<uint16_t> compressedLength;
char patchData[1];
};
struct Xex2OptDeltaPatchDescriptor
{
be<uint32_t> size;
be<uint32_t> targetVersionValue;
be<uint32_t> sourceVersionValue;
uint8_t digestSource[0x14];
uint8_t imageKeySource[0x10];
be<uint32_t> sizeOfTargetHeaders;
be<uint32_t> deltaHeadersSourceOffset;
be<uint32_t> deltaHeadersSourceSize;
be<uint32_t> deltaHeadersTargetOffset;
be<uint32_t> deltaImageSourceOffset;
be<uint32_t> deltaImageSourceSize;
be<uint32_t> deltaImageTargetOffset;
Xex2DeltaPatch info;
};
struct Xex2FileBasicCompressionBlock
{
be<uint32_t> dataSize;
be<uint32_t> zeroSize;
};
struct Xex2FileBasicCompressionInfo
{
Xex2FileBasicCompressionBlock firstBlock;
};
struct Xex2CompressedBlockInfo
{
be<uint32_t> blockSize;
uint8_t blockHash[20];
};
struct Xex2FileNormalCompressionInfo
{
be<uint32_t> windowSize;
Xex2CompressedBlockInfo firstBlock;
};
struct Xex2OptFileFormatInfo
{
be<uint32_t> infoSize;
be<uint16_t> encryptionType;
be<uint16_t> compressionType;
union
{
Xex2FileBasicCompressionInfo basic;
Xex2FileNormalCompressionInfo normal;
} compressionInfo;
};
static const void *getOptHeaderPtr(std::span<const uint8_t> moduleBytes, uint32_t headerKey)
{
if ((headerKey & 0xFF) == 0)
{
assert(false && "Wrong type of method for this key. Expected return value is a number.");
return nullptr;
}
const Xex2Header *xex2Header = (const Xex2Header *)(moduleBytes.data());
for (uint32_t i = 0; i < xex2Header->headerCount; i++)
{
const Xex2OptHeader &optHeader = xex2Header->headers[i];
if (optHeader.key == headerKey)
{
if ((headerKey & 0xFF) == 1)
{
return &optHeader.value;
}
else
{
return &moduleBytes.data()[optHeader.offset];
}
}
}
return nullptr;
}
struct mspack_memory_file
{
mspack_system sys;
void *buffer;
size_t bufferSize;
size_t offset;
};
static mspack_memory_file *mspack_memory_open(mspack_system *sys, void *buffer, size_t bufferSize)
{
assert(bufferSize < INT_MAX);
if (bufferSize >= INT_MAX)
{
return nullptr;
}
mspack_memory_file *memoryFile = (mspack_memory_file *)(std::calloc(1, sizeof(mspack_memory_file)));
if (memoryFile == nullptr)
{
return memoryFile;
}
memoryFile->buffer = buffer;
memoryFile->bufferSize = bufferSize;
memoryFile->offset = 0;
return memoryFile;
}
static void mspack_memory_close(mspack_memory_file *file)
{
std::free(file);
}
static int mspack_memory_read(mspack_file *file, void *buffer, int chars)
{
mspack_memory_file *memoryFile = (mspack_memory_file *)(file);
const size_t remaining = memoryFile->bufferSize - memoryFile->offset;
const size_t total = std::min(size_t(chars), remaining);
std::memcpy(buffer, (uint8_t *)(memoryFile->buffer) + memoryFile->offset, total);
memoryFile->offset += total;
return int(total);
}
static int mspack_memory_write(mspack_file *file, void *buffer, int chars)
{
mspack_memory_file *memoryFile = (mspack_memory_file *)(file);
const size_t remaining = memoryFile->bufferSize - memoryFile->offset;
const size_t total = std::min(size_t(chars), remaining);
std::memcpy((uint8_t *)(memoryFile->buffer) + memoryFile->offset, buffer, total);
memoryFile->offset += total;
return int(total);
}
static void *mspack_memory_alloc(mspack_system *sys, size_t chars)
{
return std::calloc(chars, 1);
}
static void mspack_memory_free(void *ptr)
{
std::free(ptr);
}
static void mspack_memory_copy(void *src, void *dest, size_t chars)
{
std::memcpy(dest, src, chars);
}
static mspack_system *mspack_memory_sys_create()
{
auto sys = (mspack_system *)(std::calloc(1, sizeof(mspack_system)));
if (!sys)
{
return nullptr;
}
sys->read = mspack_memory_read;
sys->write = mspack_memory_write;
sys->alloc = mspack_memory_alloc;
sys->free = mspack_memory_free;
sys->copy = mspack_memory_copy;
return sys;
}
static void mspack_memory_sys_destroy(struct mspack_system *sys)
{
free(sys);
}
#if defined(_WIN32)
inline bool bitScanForward(uint32_t v, uint32_t *outFirstSetIndex)
{
return _BitScanForward((unsigned long *)(outFirstSetIndex), v) != 0;
}
inline bool bitScanForward(uint64_t v, uint32_t *outFirstSetIndex)
{
return _BitScanForward64((unsigned long *)(outFirstSetIndex), v) != 0;
}
#else
inline bool bitScanForward(uint32_t v, uint32_t *outFirstSetIndex)
{
int i = ffs(v);
*out_first_set_index = i - 1;
return i != 0;
}
inline bool bitScanForward(uint64_t v, uint32_t *outFirstSetIndex)
{
int i = __builtin_ffsll(v);
*out_first_set_index = i - 1;
return i != 0;
}
#endif
static int lzxDecompress(const void *lzxData, size_t lzxLength, void *dst, size_t dstLength, uint32_t windowSize, void *windowData, size_t windowDataLength)
{
int resultCode = 1;
uint32_t windowBits;
if (!bitScanForward(windowSize, &windowBits)) {
return resultCode;
}
mspack_system *sys = mspack_memory_sys_create();
mspack_memory_file *lzxSrc = mspack_memory_open(sys, (void *)(lzxData), lzxLength);
mspack_memory_file *lzxDst = mspack_memory_open(sys, dst, dstLength);
lzxd_stream *lzxd = lzxd_init(sys, (mspack_file *)(lzxSrc), (mspack_file *)(lzxDst), windowBits, 0, 0x8000, dstLength, 0);
if (lzxd != nullptr) {
if (windowData != nullptr) {
size_t paddingLength = windowSize - windowDataLength;
std::memset(&lzxd->window[0], 0, paddingLength);
std::memcpy(&lzxd->window[paddingLength], windowData, windowDataLength);
lzxd->ref_data_size = windowSize;
}
resultCode = lzxd_decompress(lzxd, dstLength);
lzxd_free(lzxd);
}
if (lzxSrc) {
mspack_memory_close(lzxSrc);
}
if (lzxDst) {
mspack_memory_close(lzxDst);
}
if (sys) {
mspack_memory_sys_destroy(sys);
}
return resultCode;
}
static int lzxDeltaApplyPatch(const Xex2DeltaPatch *deltaPatch, uint32_t patchLength, uint32_t windowSize, uint8_t *dstData)
{
const void *patchEnd = (const uint8_t *)(deltaPatch) + patchLength;
const Xex2DeltaPatch *curPatch = deltaPatch;
while (patchEnd > curPatch)
{
int patchSize = -4;
if (curPatch->compressedLength == 0 && curPatch->uncompressedLength == 0 && curPatch->newAddress == 0 && curPatch->oldAddress == 0)
{
// End of patch.
break;
}
switch (curPatch->compressedLength)
{
case 0:
// Set the data to zeroes.
std::memset(&dstData[curPatch->newAddress], 0, curPatch->uncompressedLength);
break;
case 1:
// Move the data.
std::memcpy(&dstData[curPatch->newAddress], &dstData[curPatch->oldAddress], curPatch->uncompressedLength);
break;
default:
// Decompress the data into the destination.
patchSize = curPatch->compressedLength - 4;
int result = lzxDecompress(curPatch->patchData, curPatch->compressedLength, &dstData[curPatch->newAddress], curPatch->uncompressedLength, windowSize, &dstData[curPatch->oldAddress], curPatch->uncompressedLength);
if (result != 0)
{
return result;
}
break;
}
curPatch++;
curPatch = (const Xex2DeltaPatch *)((const uint8_t *)(curPatch) + patchSize);
}
return 0;
}
XexPatcher::Result XexPatcher::apply(std::span<const uint8_t> xexBytes, std::span<const uint8_t> patchBytes, std::vector<uint8_t> &outBytes, bool skipData)
{
// Validate headers.
static const char Xex2Magic[] = "XEX2";
const Xex2Header *xexHeader = (const Xex2Header *)(xexBytes.data());
if (memcmp(xexBytes.data(), Xex2Magic, 4) != 0)
{
return Result::XexFileInvalid;
}
const Xex2Header *patchHeader = (const Xex2Header *)(patchBytes.data());
if (memcmp(patchBytes.data(), Xex2Magic, 4) != 0)
{
return Result::PatchFileInvalid;
}
if ((patchHeader->moduleFlags & (XEX_MODULE_MODULE_PATCH | XEX_MODULE_PATCH_DELTA | XEX_MODULE_PATCH_FULL)) == 0)
{
return Result::PatchFileInvalid;
}
// Validate patch.
const Xex2OptDeltaPatchDescriptor *patchDescriptor = (const Xex2OptDeltaPatchDescriptor *)(getOptHeaderPtr(patchBytes, XEX_HEADER_DELTA_PATCH_DESCRIPTOR));
if (patchDescriptor == nullptr)
{
return Result::PatchFileInvalid;
}
const Xex2OptFileFormatInfo *patchFileFormatInfo = (const Xex2OptFileFormatInfo *)(getOptHeaderPtr(patchBytes, XEX_HEADER_FILE_FORMAT_INFO));
if (patchFileFormatInfo == nullptr)
{
return Result::PatchFileInvalid;
}
if (patchFileFormatInfo->compressionType != XEX_COMPRESSION_DELTA)
{
return Result::PatchFileInvalid;
}
if (patchDescriptor->deltaHeadersSourceOffset > xexHeader->headerSize)
{
return Result::PatchIncompatible;
}
if (patchDescriptor->deltaHeadersSourceSize > (xexHeader->headerSize - patchDescriptor->deltaHeadersSourceOffset))
{
return Result::PatchIncompatible;
}
if (patchDescriptor->deltaHeadersTargetOffset > patchDescriptor->sizeOfTargetHeaders)
{
return Result::PatchIncompatible;
}
uint32_t deltaTargetSize = patchDescriptor->sizeOfTargetHeaders - patchDescriptor->deltaHeadersTargetOffset;
if (patchDescriptor->deltaHeadersSourceSize > deltaTargetSize)
{
return Result::PatchIncompatible;
}
// Apply patch.
uint32_t headerTargetSize = patchDescriptor->sizeOfTargetHeaders;
if (headerTargetSize == 0)
{
headerTargetSize = patchDescriptor->deltaHeadersTargetOffset + patchDescriptor->deltaHeadersSourceSize;
}
// Create the bytes for the new XEX header. Copy over the existing data.
uint32_t newXexHeaderSize = std::max(headerTargetSize, xexHeader->headerSize.get());
outBytes.resize(newXexHeaderSize);
memset(outBytes.data(), 0, newXexHeaderSize);
memcpy(outBytes.data(), xexBytes.data(), headerTargetSize);
Xex2Header *newXexHeader = (Xex2Header *)(outBytes.data());
if (patchDescriptor->deltaHeadersSourceOffset > 0)
{
memcpy(&outBytes[patchDescriptor->deltaHeadersTargetOffset], &outBytes[patchDescriptor->deltaHeadersSourceOffset], patchDescriptor->deltaHeadersSourceSize);
}
int resultCode = lzxDeltaApplyPatch(&patchDescriptor->info, patchDescriptor->size, patchFileFormatInfo->compressionInfo.normal.windowSize, outBytes.data());
if (resultCode != 0)
{
return Result::PatchFailed;
}
// Make the header the specified size by the patch.
outBytes.resize(headerTargetSize);
newXexHeader = (Xex2Header *)(outBytes.data());
// Copy the rest of the data.
const Xex2SecurityInfo *newSecurityInfo = (const Xex2SecurityInfo *)(&outBytes[newXexHeader->securityOffset]);
outBytes.resize(outBytes.size() + newSecurityInfo->imageSize);
memset(&outBytes[headerTargetSize], 0, outBytes.size() - headerTargetSize);
memcpy(&outBytes[headerTargetSize], &xexBytes[xexHeader->headerSize], xexBytes.size() - xexHeader->headerSize);
newXexHeader = (Xex2Header *)(outBytes.data());
newSecurityInfo = (const Xex2SecurityInfo *)(&outBytes[newXexHeader->securityOffset]);
// Decrypt the keys and validate that the patch is compatible with the base file.
static const uint32_t KeySize = 16;
static const uint8_t Xex2RetailKey[16] = { 0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3, 0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91 };
static const uint8_t AESBlankIV[AES_BLOCKLEN] = {};
const Xex2SecurityInfo *originalSecurityInfo = (const Xex2SecurityInfo *)(&xexBytes[xexHeader->securityOffset]);
const Xex2SecurityInfo *patchSecurityInfo = (const Xex2SecurityInfo *)(&patchBytes[patchHeader->securityOffset]);
uint8_t decryptedOriginalKey[KeySize];
uint8_t decryptedNewKey[KeySize];
uint8_t decryptedPatchKey[KeySize];
uint8_t decrpytedImageKeySource[KeySize];
memcpy(decryptedOriginalKey, originalSecurityInfo->aesKey, KeySize);
memcpy(decryptedNewKey, newSecurityInfo->aesKey, KeySize);
memcpy(decryptedPatchKey, patchSecurityInfo->aesKey, KeySize);
memcpy(decrpytedImageKeySource, patchDescriptor->imageKeySource, KeySize);
AES_ctx aesContext;
AES_init_ctx_iv(&aesContext, Xex2RetailKey, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, decryptedOriginalKey, KeySize);
AES_ctx_set_iv(&aesContext, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, decryptedNewKey, KeySize);
AES_init_ctx_iv(&aesContext, decryptedNewKey, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, decryptedPatchKey, KeySize);
AES_ctx_set_iv(&aesContext, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, decrpytedImageKeySource, KeySize);
// Validate the patch's key matches the one from the original XEX.
if (memcmp(decrpytedImageKeySource, decryptedOriginalKey, KeySize) != 0)
{
return Result::PatchIncompatible;
}
// Don't process the rest of the patch.
if (skipData)
{
return Result::Success;
}
// Decrypt base XEX if necessary.
const Xex2OptFileFormatInfo *fileFormatInfo = (const Xex2OptFileFormatInfo *)(getOptHeaderPtr(xexBytes, XEX_HEADER_FILE_FORMAT_INFO));
if (fileFormatInfo == nullptr)
{
return Result::XexFileInvalid;
}
if (fileFormatInfo->encryptionType == XEX_ENCRYPTION_NORMAL)
{
AES_init_ctx_iv(&aesContext, decryptedOriginalKey, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, &outBytes[headerTargetSize], xexBytes.size() - xexHeader->headerSize);
}
else if (fileFormatInfo->encryptionType != XEX_ENCRYPTION_NONE)
{
return Result::XexFileInvalid;
}
// Decompress base XEX if necessary.
if (fileFormatInfo->compressionType == XEX_COMPRESSION_BASIC)
{
const Xex2FileBasicCompressionBlock *blocks = &fileFormatInfo->compressionInfo.basic.firstBlock;
int32_t numBlocks = (fileFormatInfo->infoSize / sizeof(Xex2FileBasicCompressionBlock)) - 1;
int32_t baseCompressedSize = 0;
int32_t baseImageSize = 0;
for (int32_t i = 0; i < numBlocks; i++) {
baseCompressedSize += blocks[i].dataSize;
baseImageSize += blocks[i].dataSize + blocks[i].zeroSize;
}
if (outBytes.size() < (headerTargetSize + baseImageSize))
{
return Result::XexFileInvalid;
}
// Reverse iteration allows to perform this decompression in place.
uint8_t *srcDataCursor = outBytes.data() + headerTargetSize + baseCompressedSize;
uint8_t *outDataCursor = outBytes.data() + headerTargetSize + baseImageSize;
for (int32_t i = numBlocks - 1; i >= 0; i--)
{
outDataCursor -= blocks[i].zeroSize;
memset(outDataCursor, 0, blocks[i].zeroSize);
outDataCursor -= blocks[i].dataSize;
srcDataCursor -= blocks[i].dataSize;
memmove(outDataCursor, srcDataCursor, blocks[i].dataSize);
}
}
else if (fileFormatInfo->compressionType == XEX_COMPRESSION_NORMAL || fileFormatInfo->compressionType == XEX_COMPRESSION_DELTA)
{
return Result::XexFileUnsupported;
}
else if (fileFormatInfo->compressionType != XEX_COMPRESSION_NONE)
{
return Result::XexFileInvalid;
}
Xex2OptFileFormatInfo *newFileFormatInfo = (Xex2OptFileFormatInfo *)(getOptHeaderPtr(outBytes, XEX_HEADER_FILE_FORMAT_INFO));
if (newFileFormatInfo == nullptr)
{
return Result::PatchFailed;
}
// Update the header to indicate no encryption or compression is used.
newFileFormatInfo->encryptionType = XEX_ENCRYPTION_NONE;
newFileFormatInfo->compressionType = XEX_COMPRESSION_NONE;
// Copy and decrypt patch data if necessary.
std::vector<uint8_t> patchData;
patchData.resize(patchBytes.size() - patchHeader->headerSize);
memcpy(patchData.data(), &patchBytes[patchHeader->headerSize], patchData.size());
if (patchFileFormatInfo->encryptionType == XEX_ENCRYPTION_NORMAL)
{
AES_init_ctx_iv(&aesContext, decryptedPatchKey, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, patchData.data(), patchData.size());
}
else if (patchFileFormatInfo->encryptionType != XEX_ENCRYPTION_NONE)
{
return Result::PatchFileInvalid;
}
const Xex2CompressedBlockInfo *currentBlock = &patchFileFormatInfo->compressionInfo.normal.firstBlock;
uint8_t *outExe = &outBytes[newXexHeader->headerSize];
if (patchDescriptor->deltaImageSourceOffset > 0)
{
memcpy(&outExe[patchDescriptor->deltaImageTargetOffset], &outExe[patchDescriptor->deltaImageSourceOffset], patchDescriptor->deltaImageSourceSize);
}
static const uint32_t DigestSize = 20;
uint8_t sha1Digest[DigestSize];
sha1::SHA1 sha1Context;
uint8_t *patchDataCursor = patchData.data();
while (currentBlock->blockSize > 0)
{
const Xex2CompressedBlockInfo *nextBlock = (const Xex2CompressedBlockInfo *)(patchDataCursor);
// Hash and validate the block.
sha1Context.reset();
sha1Context.processBytes(patchDataCursor, currentBlock->blockSize);
sha1Context.finalize(sha1Digest);
if (memcmp(sha1Digest, currentBlock->blockHash, DigestSize) != 0)
{
return Result::PatchFailed;
}
patchDataCursor += 24;
// Apply the block's patch data.
uint32_t blockDataSize = currentBlock->blockSize - 24;
if (lzxDeltaApplyPatch((const Xex2DeltaPatch *)(patchDataCursor), blockDataSize, patchFileFormatInfo->compressionInfo.normal.windowSize, outExe) != 0)
{
return Result::PatchFailed;
}
patchDataCursor += blockDataSize;
currentBlock = nextBlock;
}
return Result::Success;
}
XexPatcher::Result XexPatcher::apply(const std::filesystem::path &baseXexPath, const std::filesystem::path &patchXexPath, const std::filesystem::path &newXexPath)
{
MemoryMappedFile baseXexFile(baseXexPath);
MemoryMappedFile patchFile(patchXexPath);
if (!baseXexFile.isOpen() || !patchFile.isOpen())
{
return Result::FileOpenFailed;
}
std::vector<uint8_t> newXexBytes;
Result result = apply({ baseXexFile.data(), baseXexFile.size() }, { patchFile.data(), patchFile.size() }, newXexBytes, false);
if (result != Result::Success)
{
return result;
}
std::ofstream newXexFile(newXexPath, std::ios::binary);
if (!newXexFile.is_open())
{
return Result::FileOpenFailed;
}
newXexFile.write((const char *)(newXexBytes.data()), newXexBytes.size());
newXexFile.close();
if (newXexFile.bad())
{
std::filesystem::remove(newXexPath);
return Result::FileWriteFailed;
}
return Result::Success;
}

View file

@ -0,0 +1,35 @@
// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/cpu/xex_module.cc
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#pragma once
#include <cstdint>
#include <filesystem>
#include <span>
#include <vector>
struct XexPatcher
{
enum class Result {
Success,
FileOpenFailed,
FileWriteFailed,
XexFileUnsupported,
XexFileInvalid,
PatchFileInvalid,
PatchIncompatible,
PatchFailed,
PatchUnsupported
};
static Result apply(std::span<const uint8_t> xexBytes, std::span<const uint8_t> patchBytes, std::vector<uint8_t> &outBytes, bool skipData);
static Result apply(const std::filesystem::path &baseXexPath, const std::filesystem::path &patchXexPath, const std::filesystem::path &newXexPath);
};

View file

@ -12,6 +12,7 @@
#include <apu/audio.h>
#include <hid/hid.h>
#include <cfg/config.h>
#include <install/installer.h>
#define GAME_XEX_PATH "game:\\default.xex"
@ -96,13 +97,15 @@ uint32_t LdrLoadModule(const char* path)
auto format = Xex2FindOptionalHeader<XEX_FILE_FORMAT_INFO>(xex, XEX_HEADER_FILE_FORMAT_INFO);
auto entry = *Xex2FindOptionalHeader<uint32_t>(xex, XEX_HEADER_ENTRY_POINT);
ByteSwap(entry);
assert(format->CompressionType >= 1);
if (format->CompressionType == 1)
auto srcData = (char *)xex + xex->SizeOfHeader;
auto destData = (char *)g_memory.Translate(security->ImageBase);
if (format->CompressionType == 0)
{
memcpy(destData, srcData, security->SizeOfImage);
}
else if (format->CompressionType == 1)
{
auto srcData = (char*)xex + xex->SizeOfHeader;
auto destData = (char*)g_memory.Translate(security->ImageBase);
auto numBlocks = (format->SizeOfHeader / sizeof(XEX_BASIC_FILE_COMPRESSION_INFO)) - 1;
auto blocks = reinterpret_cast<const XEX_BASIC_FILE_COMPRESSION_INFO*>(format + 1);
@ -117,6 +120,10 @@ uint32_t LdrLoadModule(const char* path)
destData += blocks[i].SizeOfPadding;
}
}
else
{
assert(false && "Unknown compression type.");
}
return entry;
}

View file

@ -1,3 +1,4 @@
add_subdirectory(${SWA_THIRDPARTY_ROOT}/PowerRecomp)
add_subdirectory(${SWA_THIRDPARTY_ROOT}/ShaderRecomp)
add_subdirectory(${SWA_THIRDPARTY_ROOT}/o1heap)
add_subdirectory(${SWA_THIRDPARTY_ROOT}/ShaderRecomp)
add_subdirectory(${SWA_THIRDPARTY_ROOT}/o1heap)
add_subdirectory(${SWA_THIRDPARTY_ROOT}/fshasher)

@ -1 +1 @@
Subproject commit 7dd4f91ac635b001a56cc7a27af48f0436bbad3f
Subproject commit 675b482ec4852b873590fb999d24b426bade2b3a

@ -1 +1 @@
Subproject commit f936ed2212d8291439003eb0c0d8edc0ecafd24d
Subproject commit 30f598604767602e3afce56b947e99dba2b51211

223
thirdparty/TinySHA1/TinySHA1.hpp vendored Normal file
View file

@ -0,0 +1,223 @@
/*
*
* TinySHA1 - a header only implementation of the SHA1 algorithm in C++. Based
* on the implementation in boost::uuid::details.
*
* SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1
*
* Copyright (c) 2012-22 SAURAV MOHAPATRA <mohaps@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Taken from https://github.com/mohaps/TinySHA1
* Modified for use by Xenia
*/
#ifndef _TINY_SHA1_HPP_
#define _TINY_SHA1_HPP_
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdint.h>
namespace sha1 {
class SHA1 {
public:
typedef uint32_t digest32_t[5];
typedef uint8_t digest8_t[20];
inline static uint32_t LeftRotate(uint32_t value, size_t count) {
return (value << count) ^ (value >> (32 - count));
}
SHA1() { reset(); }
virtual ~SHA1() {}
SHA1(const SHA1& s) { *this = s; }
const SHA1& operator=(const SHA1& s) {
memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t));
memcpy(m_block, s.m_block, 64);
m_blockByteIndex = s.m_blockByteIndex;
m_byteCount = s.m_byteCount;
return *this;
}
SHA1& init(const uint32_t digest[5], const uint8_t block[64],
uint32_t count) {
std::memcpy(m_digest, digest, 20);
std::memcpy(m_block, block, count % 64);
m_byteCount = count;
m_blockByteIndex = count % 64;
return *this;
}
const uint32_t* getDigest() const { return m_digest; }
const uint8_t* getBlock() const { return m_block; }
size_t getBlockByteIndex() const { return m_blockByteIndex; }
size_t getByteCount() const { return m_byteCount; }
SHA1& reset() {
m_digest[0] = 0x67452301;
m_digest[1] = 0xEFCDAB89;
m_digest[2] = 0x98BADCFE;
m_digest[3] = 0x10325476;
m_digest[4] = 0xC3D2E1F0;
m_blockByteIndex = 0;
m_byteCount = 0;
return *this;
}
SHA1& processByte(uint8_t octet) {
this->m_block[this->m_blockByteIndex++] = octet;
++this->m_byteCount;
if (m_blockByteIndex == 64) {
this->m_blockByteIndex = 0;
processBlock();
}
return *this;
}
SHA1& processBlock(const void* const start, const void* const end) {
const uint8_t* begin = static_cast<const uint8_t*>(start);
const uint8_t* finish = static_cast<const uint8_t*>(end);
while (begin != finish) {
processByte(*begin);
begin++;
}
return *this;
}
SHA1& processBytes(const void* const data, size_t len) {
const uint8_t* block = static_cast<const uint8_t*>(data);
processBlock(block, block + len);
return *this;
}
const uint32_t* finalize(digest32_t digest) {
size_t bitCount = this->m_byteCount * 8;
processByte(0x80);
if (this->m_blockByteIndex > 56) {
while (m_blockByteIndex != 0) {
processByte(0);
}
while (m_blockByteIndex < 56) {
processByte(0);
}
} else {
while (m_blockByteIndex < 56) {
processByte(0);
}
}
processByte(0);
processByte(0);
processByte(0);
processByte(0);
processByte(static_cast<unsigned char>((bitCount >> 24) & 0xFF));
processByte(static_cast<unsigned char>((bitCount >> 16) & 0xFF));
processByte(static_cast<unsigned char>((bitCount >> 8) & 0xFF));
processByte(static_cast<unsigned char>((bitCount)&0xFF));
memcpy(digest, m_digest, 5 * sizeof(uint32_t));
return digest;
}
const uint8_t* finalize(digest8_t digest) {
digest32_t d32;
finalize(d32);
size_t di = 0;
digest[di++] = ((d32[0] >> 24) & 0xFF);
digest[di++] = ((d32[0] >> 16) & 0xFF);
digest[di++] = ((d32[0] >> 8) & 0xFF);
digest[di++] = ((d32[0]) & 0xFF);
digest[di++] = ((d32[1] >> 24) & 0xFF);
digest[di++] = ((d32[1] >> 16) & 0xFF);
digest[di++] = ((d32[1] >> 8) & 0xFF);
digest[di++] = ((d32[1]) & 0xFF);
digest[di++] = ((d32[2] >> 24) & 0xFF);
digest[di++] = ((d32[2] >> 16) & 0xFF);
digest[di++] = ((d32[2] >> 8) & 0xFF);
digest[di++] = ((d32[2]) & 0xFF);
digest[di++] = ((d32[3] >> 24) & 0xFF);
digest[di++] = ((d32[3] >> 16) & 0xFF);
digest[di++] = ((d32[3] >> 8) & 0xFF);
digest[di++] = ((d32[3]) & 0xFF);
digest[di++] = ((d32[4] >> 24) & 0xFF);
digest[di++] = ((d32[4] >> 16) & 0xFF);
digest[di++] = ((d32[4] >> 8) & 0xFF);
digest[di++] = ((d32[4]) & 0xFF);
return digest;
}
protected:
void processBlock() {
uint32_t w[80];
for (size_t i = 0; i < 16; i++) {
w[i] = (m_block[i * 4 + 0] << 24);
w[i] |= (m_block[i * 4 + 1] << 16);
w[i] |= (m_block[i * 4 + 2] << 8);
w[i] |= (m_block[i * 4 + 3]);
}
for (size_t i = 16; i < 80; i++) {
w[i] = LeftRotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1);
}
uint32_t a = m_digest[0];
uint32_t b = m_digest[1];
uint32_t c = m_digest[2];
uint32_t d = m_digest[3];
uint32_t e = m_digest[4];
for (std::size_t i = 0; i < 80; ++i) {
uint32_t f = 0;
uint32_t k = 0;
if (i < 20) {
f = (b & c) | (~b & d);
k = 0x5A827999;
} else if (i < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (i < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i];
e = d;
d = c;
c = LeftRotate(b, 30);
b = a;
a = temp;
}
m_digest[0] += a;
m_digest[1] += b;
m_digest[2] += c;
m_digest[3] += d;
m_digest[4] += e;
}
private:
digest32_t m_digest;
uint8_t m_block[64];
size_t m_blockByteIndex;
size_t m_byteCount;
};
}
#endif

7
thirdparty/fshasher/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,7 @@
project("fshasher")
add_executable(fshasher "fshasher.cpp")
find_package(xxhash CONFIG REQUIRED)
target_link_libraries(fshasher PRIVATE xxHash::xxhash)

203
thirdparty/fshasher/fshasher.cpp vendored Normal file
View file

@ -0,0 +1,203 @@
//
// fshasher - CLI tool to generate a hash map from a file system.
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <xxh3.h>
#include "plainargs.h"
void showHelp() {
std::cout << "fshasher --directory <directory1 directory2 ...> --source <source file> --header <header file> --variable <variable name>" << std::endl;
}
int process(const std::list<std::filesystem::path> &searchDirectories, std::ofstream &outputSourceStream, std::ofstream &outputHeaderStream, const std::string &variableName) {
auto writeExterns = [&](std::ofstream &outputStream)
{
outputStream << "extern const uint64_t " << variableName << "Hashes[];" << std::endl;
outputStream << "extern const std::pair<const char *, uint32_t> " << variableName << "Files[];" << std::endl;
outputStream << "extern const size_t " << variableName << "FilesSize;" << std::endl << std::endl;
};
// Generate header.
outputHeaderStream << "// File automatically generated by fshasher" << std::endl << std::endl;
outputHeaderStream << "#pragma once" << std::endl << std::endl;
outputHeaderStream << "#include <utility>" << std::endl << std::endl;
writeExterns(outputHeaderStream);
if (outputHeaderStream.bad())
{
std::cerr << "Failed to write to output header." << std::endl;
return 1;
}
outputSourceStream << "// File automatically generated by fshasher" << std::endl << std::endl;
outputSourceStream << "#include <utility>" << std::endl << std::endl;
writeExterns(outputSourceStream);
std::map<std::u8string, std::set<uint64_t>> fileHashSets;
char fileData[65536];
XXH3_state_t xxh3;
for (const std::filesystem::path &searchDirectory : searchDirectories)
{
if (!std::filesystem::is_directory(searchDirectory))
{
std::cerr << "Specified directory " << searchDirectory << " does not exist." << std::endl;
return 1;
}
for (const std::filesystem::directory_entry &entry : std::filesystem::recursive_directory_iterator(searchDirectory))
{
if (!entry.is_regular_file())
{
continue;
}
std::filesystem::path entryPath = entry.path();
std::filesystem::path entryRelative = std::filesystem::relative(entryPath, searchDirectory);
std::ifstream entryStream(entryPath, std::ios::binary);
if (!entryStream.is_open())
{
std::cerr << "Could not open " << entryPath << " for reading." << std::endl;
return 1;
}
std::cout << "Reading " << entryRelative << "." << std::endl;
XXH3_64bits_reset(&xxh3);
while (!entryStream.eof() && !entryStream.bad())
{
entryStream.read(fileData, sizeof(fileData));
XXH3_64bits_update(&xxh3, fileData, entryStream.gcount());
}
if (entryStream.bad())
{
std::cerr << "Could not read " << entryPath << " successfully." << std::endl;
return 1;
}
std::u8string entryRelativeU8 = entryRelative.u8string();
std::replace(entryRelativeU8.begin(), entryRelativeU8.end(), '\\', '/');
fileHashSets[entryRelativeU8].insert(XXH3_64bits_digest(&xxh3));
}
}
outputSourceStream << "const uint64_t " << variableName << "Hashes[] = {" << std::endl;
for (auto &it : fileHashSets)
{
for (uint64_t hash : it.second)
{
outputSourceStream << " " << hash << "ULL," << std::endl;
}
if (outputSourceStream.bad())
{
std::cerr << "Failed to write to output source." << std::endl;
return 1;
}
}
outputSourceStream << "};" << std::endl << std::endl;
outputSourceStream << "const std::pair<const char *, uint32_t> " << variableName << "Files[] = {" << std::endl;
for (const auto &it : fileHashSets)
{
outputSourceStream << " { \"" << (const char *)(it.first.c_str()) << "\", " << it.second.size() << " }," << std::endl;
if (outputSourceStream.bad())
{
std::cerr << "Failed to write to output source." << std::endl;
return 1;
}
}
outputSourceStream << "};" << std::endl << std::endl;
outputSourceStream << "const size_t " << variableName << "FilesSize = std::size(" << variableName << "Files);" << std::endl;
if (outputSourceStream.bad())
{
std::cerr << "Failed to write to output source." << std::endl;
return 1;
}
return 0;
}
int main(int argc, char *argv[])
{
plainargs::Result argsResult = plainargs::parse(argc, argv);
std::vector<std::string> directories = argsResult.getValues("directory", "d");
std::string variable = argsResult.getValue("variable", "v");
std::string source = argsResult.getValue("source", "s");
std::string header = argsResult.getValue("header", "h");
if (directories.empty() || variable.empty() || source.empty() || header.empty())
{
showHelp();
return 1;
}
std::filesystem::path sourcePath(source);
std::ofstream sourceStream(sourcePath);
if (!sourceStream.is_open())
{
std::cerr << "Could not open " << sourcePath << " for writing." << std::endl;
return 1;
}
std::filesystem::path headerPath(header);
std::ofstream headerStream(headerPath);
if (!headerStream.is_open())
{
std::cerr << "Could not open " << headerPath << " for writing." << std::endl;
return 1;
}
std::list<std::filesystem::path> searchDirectories;
for (std::string &directory : directories)
{
searchDirectories.emplace_back(directory);
}
int resultCode = process(searchDirectories, sourceStream, headerStream, variable);
sourceStream.close();
headerStream.close();
if (resultCode != 0)
{
std::cerr << "Failed to generate " << sourcePath << "and" << headerPath << "." << std::endl;
std::filesystem::remove(sourcePath);
std::filesystem::remove(headerPath);
}
return resultCode;
}

147
thirdparty/fshasher/plainargs.h vendored Normal file
View file

@ -0,0 +1,147 @@
//
// plainargs - A very plain CLI arguments parsing header-only library.
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#include <string>
#include <unordered_map>
namespace plainargs {
class Result {
private:
struct Option {
uint32_t keyIndex;
uint32_t valueCount;
};
std::string directory;
std::vector<std::string> arguments;
std::vector<Option> options;
std::unordered_map<std::string, uint32_t> shortKeyMap;
std::unordered_map<std::string, uint32_t> longKeyMap;
public:
// Arguments are the same as main().
Result(int argc, char *argv[]) {
if (argc < 1) {
return;
}
directory = argv[0];
arguments.resize(size_t(argc - 1));
for (uint32_t i = 1; i < uint32_t(argc); i++) {
std::string &argument = arguments[i - 1];
argument = std::string(argv[i]);
if (!argument.empty()) {
bool shortKey = (argument.size() > 1) && (argument[0] == '-');
bool longKey = (argument.size() > 2) && (argument[0] == '-') && (argument[1] == '-');
if (longKey) {
longKeyMap[argument.substr(2)] = uint32_t(options.size());
options.emplace_back(Option{ i - 1, 0 });
}
else if (shortKey) {
shortKeyMap[argument.substr(1)] = uint32_t(options.size());
options.emplace_back(Option{ i - 1, 0 });
}
else if (!options.empty()) {
options.back().valueCount++;
}
}
}
}
// Return all the values associated to the long key or the short key in order.
std::vector<std::string> getValues(const std::string &longKey, const std::string &shortKey = "", uint32_t maxValues = 0) const {
std::vector<std::string> values;
auto optionIt = options.end();
if (!longKey.empty()) {
auto it = longKeyMap.find(longKey);
if (it != longKeyMap.end()) {
optionIt = options.begin() + it->second;
}
}
if ((optionIt == options.end()) && !shortKey.empty()) {
auto it = shortKeyMap.find(shortKey);
if (it != shortKeyMap.end()) {
optionIt = options.begin() + it->second;
}
}
if (optionIt != options.end()) {
uint32_t valueCount = optionIt->valueCount;
if ((maxValues > 0) && (valueCount > maxValues)) {
valueCount = maxValues;
}
values.resize(valueCount);
for (uint32_t i = 0; i < valueCount; i++) {
values[i] = arguments[optionIt->keyIndex + i + 1];
}
}
return values;
}
std::string getValue(const std::string &longKey, const std::string &shortKey = "") const {
std::vector<std::string> values = getValues(longKey, shortKey, 1);
return !values.empty() ? values.front() : std::string();
}
// Return whether an option with the long key or short key was specified.
bool hasOption(const std::string &longKey, const std::string &shortKey = "") const {
if (!longKey.empty() && (longKeyMap.find(longKey) != longKeyMap.end())) {
return true;
}
else if (!shortKey.empty() && (shortKeyMap.find(shortKey) != shortKeyMap.end())) {
return true;
}
else {
return false;
}
}
// Corresponds to argv[0].
const std::string &getDirectory() const {
return directory;
}
// No bounds checking, must be a valid index.
const std::string getArgument(uint32_t index) const {
return arguments[index];
}
// Will be one less than argc.
uint32_t getArgumentCount() const {
return arguments.size();
}
};
// Parse and return the arguments in a structure that can be queried easily. Does not perform any validation of the arguments.
Result parse(int argc, char *argv[]) {
return Result(argc, argv);
}
};

1
thirdparty/libmspack vendored Submodule

@ -0,0 +1 @@
Subproject commit 305907723a4e7ab2018e58040059ffb5e77db837

View file

@ -16,6 +16,7 @@
"zstd",
"stb",
"concurrentqueue",
"tiny-aes-c",
{
"name": "imgui",
"features": [ "sdl2-binding" ]