FieldDataInstaller.cpp 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. /*
  2. * Copyright (C) 2022 The V-Gears Team
  3. *
  4. * This file is part of V-Gears
  5. *
  6. * V-Gears is free software: you can redistribute it and/or modify it under
  7. * terms of the GNU General Public License as published by the Free Software
  8. * Foundation, version 3.0 (GPLv3) of the License.
  9. *
  10. * V-Gears is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. */
  15. #include <string>
  16. #include <boost/filesystem.hpp>
  17. #include <QtCore/QDir>
  18. #include "FieldDataInstaller.h"
  19. #include "TexFile.h"
  20. #include "common/File.h"
  21. #include "data/VGearsMapListFile.h"
  22. #include "data/VGearsLZSFLevelFileManager.h"
  23. #include "data/VGearsHRCFileManager.h"
  24. #include "data/VGearsLGPArchive.h"
  25. #include "data/VGearsAFileManager.h"
  26. float FieldDataInstaller::LINE_SCALE_FACTOR(0.0078124970964f);
  27. std::string FieldDataInstaller::FIELD_MODELS_DIR("models/fields");
  28. std::string FieldDataInstaller::FIELD_MAPS_DIR("fields");
  29. int FieldDataInstaller::INACTIVE_GATEWAY_ID(32767);
  30. std::string FieldDataInstaller::CreateGateWayScript(
  31. const std::string& gateway_entity_name, const std::string& target_map_name,
  32. const std::string& source_spawn_point_name
  33. ){
  34. return
  35. "\nEntityContainer[ \"" + gateway_entity_name + "\" ] = {\n\n"
  36. " on_start = function(self)\n"
  37. " return 0\n"
  38. " end,\n\n"
  39. " on_approach = function(self, entity)\n"
  40. " return 0\n"
  41. " end,\n\n"
  42. " on_cross = function(self, entity)\n"
  43. " return 0\n"
  44. " end,\n\n"
  45. " on_near = function(self, entity)\n"
  46. " if entity == \"Cloud\" then\n"
  47. " if not Data.DisableGateways then\n"
  48. " load_field_map_request(\""
  49. + target_map_name + "\", \"" + source_spawn_point_name + "\")\n"
  50. " end\n"
  51. " end\n"
  52. " return 0\n"
  53. " end,\n\n"
  54. " on_leave = function(self, entity)\n"
  55. " return 0\n"
  56. " end,\n"
  57. "}\n\n";
  58. }
  59. size_t FieldDataInstaller::GetFieldId(
  60. const std::string& name, const std::vector<std::string>& field_id_to_name_lookup
  61. ){
  62. for (size_t i = 0; i < field_id_to_name_lookup.size(); i ++)
  63. if (field_id_to_name_lookup[i] == name) return i;
  64. throw std::runtime_error("No Id found for field name");
  65. }
  66. bool FieldDataInstaller::IsAFieldFile(const Ogre::String& resource_name){
  67. return (
  68. !VGears::StringUtil::endsWith(resource_name, ".tex")
  69. && !VGears::StringUtil::endsWith(resource_name, ".tut")
  70. && !VGears::StringUtil::endsWith(resource_name, ".siz")
  71. && resource_name != "maplist"
  72. );
  73. }
  74. bool FieldDataInstaller::WillCrash(const Ogre::String& resource_name){
  75. if (
  76. resource_name == "bugin1a" // crashes in back ground image generation
  77. || resource_name == "frcyo" // Hangs in decompliation
  78. || resource_name == "fr_e" // Background image crash
  79. || resource_name == "las4_2" // Background image crash
  80. || resource_name == "las4_3" // Background image crash
  81. || resource_name == "lastmap" // Background image crash
  82. || resource_name == "md_e1" // Background image crash
  83. || resource_name == "trnad_3" // Background image crash
  84. || resource_name == "bonevil2" // stof
  85. || resource_name == "coloin1" // boost::bad_format_string: format-string is ill-formed
  86. || resource_name == "del3" // Segmentation fault.
  87. || resource_name == "elmin4_2" // boost::bad_format_string: format-string is ill-formed
  88. ){return true;}
  89. return false;
  90. }
  91. bool FieldDataInstaller::IsTestField(const Ogre::String& resource_name){
  92. if (
  93. resource_name == "startmap"
  94. || resource_name == "md1stin"
  95. || resource_name == "md1_1"
  96. || resource_name == "md1_2"
  97. || resource_name == "nrthmk"
  98. || resource_name == "nmkin_1"
  99. || resource_name == "elevtr1"
  100. || resource_name == "nmkin_2"
  101. || resource_name == "nmkin_3"
  102. || resource_name == "nmkin_4"
  103. || resource_name == "nmkin_5"
  104. || resource_name == "southmk1"
  105. || resource_name == "southmk2"
  106. || resource_name == "smkin_1"
  107. || resource_name == "smkin_2"
  108. || resource_name == "smkin_3"
  109. || resource_name == "smkin_4"
  110. || resource_name == "smkin_5"
  111. || resource_name == "md8_1"
  112. || resource_name == "md8_2"
  113. || resource_name == "md8_3"
  114. || resource_name == "md8_4"
  115. || resource_name == "md8brdg"
  116. || resource_name == "cargoin"
  117. || resource_name == "tin_1"
  118. || resource_name == "tin_2"
  119. || resource_name == "tin_3"
  120. || resource_name == "tin_4"
  121. || resource_name == "rootmap"
  122. || resource_name == "mds7st1"
  123. || resource_name == "mds7st2"
  124. || resource_name == "mds7st3"
  125. || resource_name == "mds7st32"
  126. || resource_name == "mds7_w1"
  127. || resource_name == "mds7_w2"
  128. || resource_name == "mds7_w3"
  129. || resource_name == "mds7"
  130. ){return true;}
  131. return false;
  132. }
  133. void FieldDataInstaller::CreateDir(const std::string& path){
  134. QString target = QString::fromStdString(output_dir_ + path);
  135. QDir dir(target);
  136. if (!dir.mkpath(".")) throw std::runtime_error("Failed to mkpath");
  137. }
  138. void FieldDataInstaller::CollectFieldScaleFactors(
  139. VGears::FLevelFilePtr& field, FieldScaleFactorMap& scale_factors,
  140. const std::vector<std::string>& field_id_to_name_lookup
  141. ){
  142. scale_factors[GetFieldId(field->getName(), field_id_to_name_lookup)]
  143. = FieldDecompiler::ScaleFactor(field->GetRawScript());
  144. }
  145. void FieldDataInstaller::CollectSpawnPoints(
  146. VGears::FLevelFilePtr& field, const std::vector<std::string>& field_id_to_name_lookup,
  147. FieldSpawnPointsMap& spawn_points
  148. ){
  149. const size_t this_field_id = GetFieldId(field->getName(), field_id_to_name_lookup);
  150. // Decompile the script just so the MAPJUMPs can be collected. This is needed to construct the
  151. // full list of entries to each field.
  152. try{
  153. // Get the raw script bytes.
  154. FieldDecompiler::DecompiledScript decompiled;
  155. const std::vector<u8> raw_field_data = field->GetRawScript();
  156. // Decompile to LUA.
  157. FF7FieldScriptGatewayCollector formatter(
  158. field->getName(), field_id_to_name_lookup, spawn_points, this_field_id
  159. );
  160. decompiled = FieldDecompiler::Decompile(
  161. field->getName(), raw_field_data, formatter, "", "EntityContainer = {}\n\n"
  162. );
  163. }
  164. catch (const ::DecompilerException& ex){
  165. std::cerr << "CollectSpawnPoints: DecompilerException: " << ex.what() << std::endl;
  166. }
  167. const VGears::TriggersFilePtr& triggers = field->GetTriggers();
  168. const auto& gateways = triggers->GetGateways();
  169. for (size_t i = 0; i < gateways.size(); i ++){
  170. const VGears::TriggersFile::Gateway& gateway = gateways[i];
  171. if (gateway.destination_field_id != INACTIVE_GATEWAY_ID){
  172. auto it = spawn_points.find(gateway.destination_field_id);
  173. if (it != std::end(spawn_points)){
  174. // Add to the list of gateways that link to
  175. // destination_field_id.
  176. SpawnPointDb::Record rec;
  177. rec.field_id = this_field_id;
  178. rec.gateway = gateway;
  179. rec.gateway_index_or_map_jump_address = i;
  180. it->second.gateways_to_this_field.push_back(rec);
  181. }
  182. else{
  183. // Create a new record for destination_field_id.
  184. SpawnPointDb db;
  185. db.target_field_id = gateway.destination_field_id;
  186. SpawnPointDb::Record rec;
  187. rec.field_id = this_field_id;
  188. rec.gateway = gateway;
  189. rec.gateway_index_or_map_jump_address = i;
  190. db.gateways_to_this_field.push_back(rec);
  191. spawn_points.insert(std::make_pair(db.target_field_id, db));
  192. }
  193. }
  194. }
  195. }
  196. FieldDataInstaller::FieldDataInstaller(const std::string input_dir, const std::string output_dir):
  197. input_dir_(input_dir), output_dir_(output_dir)
  198. {}
  199. FieldDataInstaller::~FieldDataInstaller(){}
  200. int FieldDataInstaller::CollectSpawnAndScaleFactorsInit(Ogre::ResourceGroupManager* res_mgr){
  201. // List what's in the LGP archive.
  202. //flevel_file_list_ = application_.ResMgr()->listResourceNames("FFVIIFields", "*");
  203. flevel_file_list_ = res_mgr->listResourceNames("FFVIIFields", "*");
  204. // Load the map list field.
  205. VGears::MapListFilePtr map_list = VGears::MapListFileManager::GetSingleton().load(
  206. "maplist", "FFVIIFields"
  207. ).staticCast<VGears::MapListFile>();
  208. map_list_ = map_list->GetMapList();
  209. return flevel_file_list_->size();
  210. }
  211. void FieldDataInstaller::CollectSpawnAndScaleFactors(int field_index){
  212. // On the first pass collate required field information.
  213. progress_step_num_elements_ = flevel_file_list_->size();
  214. if (field_index >= flevel_file_list_->size()){
  215. // TODO: Show invalid index.
  216. return;
  217. }
  218. auto resource_name = (*flevel_file_list_)[field_index];
  219. // Exclude things that are not fields.
  220. //if (IsAFieldFile(resource_name) && !WillCrash(resource_name)){
  221. // TODO: DEBUG: Only testing fields.
  222. if (IsAFieldFile(resource_name) && IsTestField(resource_name) && !WillCrash(resource_name)){
  223. VGears::FLevelFilePtr field = VGears::LZSFLevelFileManager::GetSingleton().load(
  224. resource_name, "FFVIIFields"
  225. ).staticCast<VGears::FLevelFile>();
  226. CollectSpawnPoints(field, map_list_, spawn_points_);
  227. CollectFieldScaleFactors(field, scale_factors_, map_list_);
  228. }
  229. }
  230. void FieldDataInstaller::Convert(int field_index){
  231. // Do the full conversion with the collated data.
  232. if (field_index >= flevel_file_list_->size()){
  233. // TODO: Show invalid index.
  234. return;
  235. }
  236. auto resource_name = (*flevel_file_list_)[field_index];
  237. // Exclude things that are not fields.
  238. if (IsAFieldFile(resource_name)){
  239. //if (/*IsTestField(resource_name) &&*/ !WillCrash(resource_name)){
  240. // TODO: DEBUG: Only test fields
  241. if (IsTestField(resource_name) && !WillCrash(resource_name)){
  242. //write_output_line_("Converting field " + resource_name);
  243. CreateDir(FIELD_MAPS_DIR + "/" + resource_name);
  244. VGears::FLevelFilePtr field = VGears::LZSFLevelFileManager::GetSingleton().load(
  245. resource_name, "FFVIIFields"
  246. ).staticCast<VGears::FLevelFile>();
  247. PcFieldToVGearsField(field);
  248. }
  249. else{
  250. /*write_output_line_(
  251. "[ERROR] Skip field " + resource_name + " due to crash or hang issue."
  252. );*/
  253. std::cerr << "[ERROR] Skip field: " << resource_name
  254. << " due to crash or hang issue." << std::endl;
  255. }
  256. }
  257. }
  258. int FieldDataInstaller::WriteInit(){
  259. doc_ = std::make_unique<TiXmlDocument>();
  260. element_ = std::make_unique<TiXmlElement>("maps");
  261. return converted_map_list_.size();
  262. }
  263. void FieldDataInstaller::Write(int field_index){
  264. // TODO: Probably need to inject "empty" and "test" fields.
  265. if (field_index >= flevel_file_list_->size()){
  266. // TODO: Show invalid index.
  267. return;
  268. }
  269. auto map = converted_map_list_[field_index];
  270. //write_output_line_("Writing field " + FieldName(map));
  271. std::unique_ptr<TiXmlElement> xml_element(new TiXmlElement("map"));
  272. xml_element->SetAttribute("name", map);
  273. xml_element->SetAttribute("file_name", map + "/map.xml");
  274. element_->LinkEndChild(xml_element.release());
  275. }
  276. void FieldDataInstaller::WriteEnd(){
  277. doc_->LinkEndChild(element_.release());
  278. doc_->SaveFile(output_dir_ + "/fields/_fields.xml");
  279. }
  280. std::vector<std::string> FieldDataInstaller::ConvertModelsInit(){
  281. std::vector<std::string> models;
  282. // Open char_lgp as a lgp archive
  283. VGears::LGPArchive char_lgp(input_dir_ + "data/field/char.lgp", "LGP");
  284. char_lgp.open(input_dir_ + "data/field/char.lgp", true);
  285. char_lgp.load();
  286. // Also, open it as a file for reading
  287. File char_file(input_dir_ + "data/field/char.lgp");
  288. //Ogre::StringVectorPtr file_list = char_lgp.list(true, true);
  289. field_model_file_list_ = char_lgp.list(true, true);
  290. VGears::LGPArchive::FileList files = char_lgp.GetFiles();
  291. for (int i = 0; i < field_model_file_list_->size(); i ++){
  292. VGears::LGPArchive::FileEntry f = files.at(i);
  293. // Save the TEX File
  294. std::fstream out;
  295. out.open(output_dir_ + "temp/char/" + f.file_name, std::ios::out);
  296. char_file.SetOffset(f.data_offset);
  297. for (int j = 0; j < f.data_size; j ++) out << char_file.readU8();
  298. out.close();
  299. //field_model_file_list_->push_back(f.file_name);
  300. }
  301. for (
  302. auto it = used_models_and_anims_.map.begin(); it != used_models_and_anims_.map.end(); it ++
  303. ){
  304. models.push_back(it->first);
  305. }
  306. return models;
  307. }
  308. void FieldDataInstaller::ConvertModels(std::string model_name){
  309. auto model = used_models_and_anims_.map.find(model_name);
  310. try{
  311. Ogre::ResourcePtr hrc = VGears::HRCFileManager::GetSingleton().load(model->first, "FFVII");
  312. //Ogre::ResourcePtr hrc = VGears::HRCFile();
  313. Ogre::String base_name;
  314. VGears::StringUtil::splitBase(model->first, base_name);
  315. auto mesh_name = VGears::NameLookup::model(base_name) + ".mesh";
  316. Ogre::MeshPtr mesh(Ogre::MeshManager::getSingleton().load(mesh_name, "FFVII"));
  317. Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
  318. for (auto& anim : model->second){
  319. VGears::AFileManager &afl_mgr(VGears::AFileManager::GetSingleton());
  320. VGears::AFilePtr a = afl_mgr.load(anim, "FFVII").staticCast<VGears::AFile>();
  321. // Convert the FF7 name to a more readable name set in the meta data.
  322. VGears::StringUtil::splitBase(anim, base_name);
  323. a->AddTo(skeleton, VGears::NameLookup::Animation(base_name));
  324. }
  325. ExportMesh(output_dir_ + FIELD_MODELS_DIR + "/", mesh);
  326. }
  327. catch (const Ogre::Exception& ex){
  328. write_output_line_(
  329. "[ERROR] Ogre exception converting model " + model->first + ": " + ex.what()
  330. );
  331. std::cerr << "[ERROR] Ogre exception converting model "
  332. << model->first <<": " << ex.what() << std::endl;
  333. }
  334. catch (const std::exception& ex){
  335. write_output_line_("[ERROR] Exception converting model " + model->first + ": " + ex.what());
  336. std::cerr << "[ERROR] Exception converting model "
  337. << model->first << ": " << ex.what() << std::endl;
  338. }
  339. }
  340. void FieldDataInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPtr &mesh){
  341. // TODO: Share function with pc model exporter
  342. VGears::String base_mesh_name;
  343. VGears::StringUtil::splitFull(mesh->getName(), base_mesh_name);
  344. Ogre::MeshSerializer mesh_serializer;
  345. Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
  346. Ogre::SkeletonSerializer skeleton_serializer;
  347. skeleton_serializer.exportSkeleton(
  348. skeleton.getPointer(), outdir + base_mesh_name + ".skeleton"
  349. );
  350. mesh->setSkeletonName(base_mesh_name + ".skeleton");
  351. mesh_serializer.exportMesh(mesh.getPointer(), outdir + mesh->getName());
  352. Ogre::Mesh::SubMeshIterator it(mesh->getSubMeshIterator());
  353. Ogre::MaterialSerializer mat_ser;
  354. size_t i(0);
  355. std::set<std::string> textures;
  356. while (it.hasMoreElements()){
  357. Ogre::SubMesh *sub_mesh(it.getNext());
  358. Ogre::MaterialPtr mat(Ogre::MaterialManager::getSingleton().getByName(
  359. sub_mesh->getMaterialName())
  360. );
  361. if (mat != nullptr){
  362. for (size_t techs = 0; techs < mat->getNumTechniques(); techs ++){
  363. Ogre::Technique* tech = mat->getTechnique(techs);
  364. if (tech){
  365. for (size_t pass_num = 0; pass_num < tech->getNumPasses(); pass_num ++){
  366. Ogre::Pass* pass = tech->getPass(pass_num);
  367. if (pass){
  368. for (
  369. size_t texture_unit_num = 0;
  370. texture_unit_num < pass->getNumTextureUnitStates();
  371. texture_unit_num ++
  372. ){
  373. Ogre::TextureUnitState* unit
  374. = pass->getTextureUnitState(texture_unit_num);
  375. if (unit && unit->getTextureName().empty() == false){
  376. // Convert the texture from .tex to .png.
  377. TexFile tex(
  378. output_dir_ + "temp/char/" + unit->getTextureName()
  379. );
  380. // Ensure the output material script references png files
  381. // rather than tex files.
  382. Ogre::String base_name;
  383. VGears::StringUtil::splitBase(
  384. unit->getTextureName(), base_name
  385. );
  386. unit->setTextureName(base_mesh_name + "_" + base_name + ".png");
  387. tex.SavePng(
  388. output_dir_ + FIELD_MODELS_DIR + "/" + base_name + ".png", 0
  389. );
  390. // Copy subtexture (xxxx.png) to model_xxxx.png
  391. // TODO: obtain the "data" folder
  392. // programatically.
  393. boost::filesystem::copy_file(
  394. output_dir_ + FIELD_MODELS_DIR + "/" + base_name + ".png",
  395. output_dir_ + FIELD_MODELS_DIR + "/"
  396. + base_mesh_name + "_" + base_name + ".png",
  397. boost::filesystem::copy_option::overwrite_if_exists
  398. );
  399. textures.insert(unit->getTextureName());
  400. }
  401. }
  402. }
  403. }
  404. }
  405. }
  406. if (std::count(materials_.begin(), materials_.end(), sub_mesh->getMaterialName()) == 0){
  407. mat_ser.queueForExport(mat);
  408. materials_.push_back(sub_mesh->getMaterialName());
  409. }
  410. }
  411. ++ i;
  412. }
  413. mat_ser.exportQueued(outdir + base_mesh_name + VGears::EXT_MATERIAL);
  414. for (auto& texture_name : textures){
  415. std::string tex_name = texture_name.c_str();
  416. try{
  417. Ogre::TexturePtr texture_ptr
  418. = Ogre::TextureManager::getSingleton().load(tex_name, "FFVIITextures" /*"FFVII"*/);
  419. Ogre::Image image;
  420. texture_ptr->convertToImage(image);
  421. Ogre::String base_name;
  422. VGears::StringUtil::splitBase(texture_name, base_name);
  423. image.save(outdir + base_mesh_name + "_" + base_name + ".png");
  424. }
  425. catch (std::exception const& ex){
  426. std::cerr << "[ERROR] Exception: " << ex.what() << std::endl;
  427. }
  428. }
  429. }
  430. float FieldDataInstaller::GetFieldScaleFactor(size_t field_id){
  431. auto it = scale_factors_.find(field_id);
  432. if (it == std::end(scale_factors_))
  433. throw std::runtime_error("Scale factor not found for field id");
  434. return it->second;
  435. }
  436. std::vector<int> FieldDataInstaller::ExtractMusicTrackIds(VGears::FLevelFilePtr& field){
  437. // Search for the "AKAO" string in the raw field data.
  438. // The next byte is a track id.
  439. const std::vector<u8> raw = field->GetRawScript();
  440. std::vector<int> tracks;
  441. for (int i = 0; i < raw.size() - 5; i ++){
  442. if (raw[i] == 0x41){ // A
  443. if (raw[i + 1] == 0x4B){ // K
  444. if (raw[i + 2] == 0x41){ // A
  445. if (raw[i + 3] == 0x4f){ // O
  446. tracks.push_back(static_cast<int>(raw[i + 4]) - 2);
  447. }
  448. }
  449. }
  450. }
  451. }
  452. return tracks;
  453. }
  454. void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
  455. // Generate triggers script to insert into main
  456. // decompiled FF7 field -> LUA script.
  457. std::string gateway_script_data;
  458. const VGears::TriggersFilePtr& triggers = field->GetTriggers();
  459. const auto& gateways = triggers->GetGateways();
  460. for (size_t i = 0; i < gateways.size(); i ++){
  461. const VGears::TriggersFile::Gateway& gateway = gateways[i];
  462. if (gateway.destination_field_id != INACTIVE_GATEWAY_ID){
  463. const std::string gateway_entity_name = "Gateway" + std::to_string(i);
  464. gateway_script_data += CreateGateWayScript(
  465. gateway_entity_name,
  466. map_list_.at(gateway.destination_field_id),
  467. "Spawn_" + field->getName() + "_" + std::to_string(i)
  468. );
  469. }
  470. }
  471. const VGears::ModelListFilePtr& models = field->GetModelList();
  472. FieldDecompiler::DecompiledScript decompiled;
  473. FF7FieldScriptFormatter formatter(field->getName(), models, map_list_, spawn_points_);
  474. try{
  475. // Get the raw script bytes.
  476. const std::vector<u8> raw_field_data = field->GetRawScript();
  477. // Decompile to LUA.
  478. decompiled = FieldDecompiler::Decompile(
  479. field->getName(), raw_field_data, formatter, gateway_script_data,
  480. "EntityContainer = {}\n\n"
  481. );
  482. std::ofstream script_file(
  483. output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/script.lua"
  484. );
  485. if (script_file.is_open()){
  486. script_file << decompiled.luaScript;
  487. field_text_writer_.Begin(
  488. output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/text.xml"
  489. );
  490. try{field_text_writer_.Write(raw_field_data, field->getName());}
  491. catch (const std::out_of_range& ex){
  492. write_output_line_(
  493. "[ERROR] Failed to read texts from field " + field->getName() + ": " + ex.what()
  494. );
  495. std::cerr << "[ERROR] Failed to read texts from field "
  496. << field->getName() << ": " << ex.what() << std::endl;
  497. }
  498. field_text_writer_.End();
  499. }
  500. else{
  501. write_output_line_(
  502. "[ERROR] Failed to open script file from field " + field->getName() + " for writing."
  503. );
  504. std::cerr << "[ERROR] Failed to open script file from field "
  505. << field->getName() << "for writing." << std::endl;
  506. }
  507. }
  508. catch (const ::DecompilerException& ex){
  509. write_output_line_(
  510. "[ERROR] Internal decompiler error in field " + field->getName() + ": " + ex.what()
  511. );
  512. std::cerr << "[ERROR] Internal decompiler error in field "
  513. << field->getName() << ": " << ex.what() << std::endl;
  514. }
  515. // Write out the map file which links all the other files together for a given field.
  516. {
  517. TiXmlDocument doc;
  518. std::unique_ptr<TiXmlElement> element(new TiXmlElement("map"));
  519. // Script.
  520. std::unique_ptr<TiXmlElement> xml_script_element(new TiXmlElement("script"));
  521. xml_script_element->SetAttribute("file_name", field->getName() + "/script.lua");
  522. element->LinkEndChild(xml_script_element.release());
  523. // Background.
  524. std::unique_ptr<TiXmlElement> xml_background_2d(new TiXmlElement("background2d"));
  525. xml_background_2d->SetAttribute("file_name", field->getName() + "/bg.xml");
  526. element->LinkEndChild(xml_background_2d.release());
  527. // Texts.
  528. std::unique_ptr<TiXmlElement> xml_texts(new TiXmlElement("texts"));
  529. xml_texts->SetAttribute("file_name", field->getName() + "/text.xml");
  530. element->LinkEndChild(xml_texts.release());
  531. // Walkmesh.
  532. std::unique_ptr<TiXmlElement> xml_walkmesh(new TiXmlElement("walkmesh"));
  533. xml_walkmesh->SetAttribute("file_name", field->getName() + "/wm.xml");
  534. element->LinkEndChild(xml_walkmesh.release());
  535. // Forward direction (angle player moves when "up" is pressed)
  536. std::unique_ptr<TiXmlElement> xml_forward_direction(new TiXmlElement("movement_rotation"));
  537. xml_forward_direction->SetAttribute("degree", std::to_string(triggers->MovementRotation()));
  538. element->LinkEndChild(xml_forward_direction.release());
  539. // Get this fields Id.
  540. const size_t this_field_id = GetFieldId(field->getName(), map_list_);
  541. // Use the ID to find the pre-computed list of gateways in all other fields that link to
  542. // this field.
  543. auto spawn_iterator = spawn_points_.find(this_field_id);
  544. Ogre::Vector3 first_entity_point = Ogre::Vector3::ZERO;
  545. // If none found it probably just means no other fields have doors to this one.
  546. if (spawn_iterator != std::end(spawn_points_)){
  547. const std::vector<SpawnPointDb::Record>& spawn_point_records
  548. = spawn_iterator->second.gateways_to_this_field;
  549. for (size_t i = 0; i < spawn_point_records.size(); i ++){
  550. const VGears::TriggersFile::Gateway& gateway = spawn_point_records[i].gateway;
  551. // Entity_point:
  552. std::unique_ptr<TiXmlElement> xml_entity_point(new TiXmlElement("entity_point"));
  553. if (spawn_point_records[i].from_script){
  554. // Spawn points from map jumps have a another algorithm.
  555. xml_entity_point->SetAttribute(
  556. "name",
  557. map_list_.at(
  558. spawn_point_records[i].field_id) + "_" + spawn_point_records[i].entity_name
  559. + "_" + spawn_point_records[i].script_function_name + "_addr_"
  560. + std::to_string(spawn_point_records[i].gateway_index_or_map_jump_address)
  561. );
  562. }
  563. else{
  564. // Must also include the gateway index for the case where 2
  565. // fields have more than one door linking to each other.
  566. xml_entity_point->SetAttribute(
  567. "name",
  568. "Spawn_" + map_list_.at(spawn_point_records[i].field_id) + "_"
  569. + std::to_string(spawn_point_records[i].gateway_index_or_map_jump_address)
  570. );
  571. }
  572. const float downscaler_next = 128.0f * GetFieldScaleFactor(
  573. gateway.destination_field_id
  574. );
  575. // Position Z is actually the target walkmesh triangle ID, so this is tiny bit more
  576. // complex. Now "get the Z value of the triangle with that ID". Note that ID
  577. // actually just means index.
  578. unsigned int triangle_index = static_cast<unsigned int>(gateway.destination.z);
  579. if (
  580. triangle_index >= field->GetWalkmesh()->GetTriangles().size()
  581. ){
  582. write_output_line_(
  583. "[WARNING] In field " + field->getName() + ": Map jump triangle ("
  584. + std::to_string(triangle_index) + ") out of bounds ("
  585. + std::to_string(field->GetWalkmesh()->GetTriangles().size()) + ")"
  586. );
  587. std::cerr << "[WARNING] In field " << field->getName()
  588. << ": Map jump triangle (" << triangle_index << ") out of bounds ("
  589. << field->GetWalkmesh()->GetTriangles().size() << ")" << std::endl;
  590. triangle_index = 0;
  591. }
  592. const float z_of_triangle_with_id
  593. = field->GetWalkmesh()->GetTriangles().at(triangle_index).a.z;
  594. const Ogre::Vector3 position(
  595. gateway.destination.x / downscaler_next, gateway.destination.y / downscaler_next,
  596. z_of_triangle_with_id
  597. );
  598. if (position != Ogre::Vector3::ZERO && first_entity_point == Ogre::Vector3::ZERO)
  599. first_entity_point = position;
  600. xml_entity_point->SetAttribute(
  601. "position", Ogre::StringConverter::toString(position)
  602. );
  603. const float rotation = (360.0f * static_cast<float>(gateway.dir)) / 255.0f;
  604. xml_entity_point->SetAttribute("rotation", std::to_string(rotation));
  605. element->LinkEndChild(xml_entity_point.release());
  606. }
  607. }
  608. // Get lines. Add them to a list, so they aren't processed later as regular entities.
  609. std::vector<std::string> line_entities;
  610. for (FieldDecompiler::Line line : decompiled.lines){
  611. std::unique_ptr<TiXmlElement> xml_entity_trigger(new TiXmlElement("entity_trigger"));
  612. line_entities.push_back(line.name);
  613. xml_entity_trigger->SetAttribute("name", line.name);
  614. xml_entity_trigger->SetAttribute(
  615. "point1",
  616. std::to_string(line.point_a[0] * LINE_SCALE_FACTOR)
  617. + " " + std::to_string(line.point_a[1] * LINE_SCALE_FACTOR)
  618. + " " + std::to_string(line.point_a[2] * LINE_SCALE_FACTOR)
  619. );
  620. xml_entity_trigger->SetAttribute(
  621. "point2",
  622. std::to_string(line.point_b[0] * LINE_SCALE_FACTOR)
  623. + " " + std::to_string(line.point_b[1] * LINE_SCALE_FACTOR)
  624. + " " + std::to_string(line.point_b[2] * LINE_SCALE_FACTOR)
  625. );
  626. xml_entity_trigger->SetAttribute("enabled", "true");
  627. element->LinkEndChild(xml_entity_trigger.release());
  628. }
  629. // Get entities.
  630. for (FieldDecompiler::FieldEntity entity : decompiled.entities){
  631. // If the entity has been added as a line, skip.
  632. if (line_entities.size() > 0){
  633. if (
  634. *std::find(line_entities.begin(), line_entities.end(), entity.name) == entity.name
  635. ){
  636. continue;
  637. }
  638. }
  639. const int char_id = entity.char_id;
  640. if (char_id != -1){
  641. const VGears::ModelListFile::ModelDescription& desc
  642. = models->GetModels().at(char_id);
  643. auto& animations = used_models_and_anims_.ModelAnimations(desc.hrc_name);
  644. for (const auto& anim : desc.animations)
  645. animations.insert(used_models_and_anims_.NormalizeAnimationName(anim.name));
  646. std::unique_ptr<TiXmlElement> xml_entity_script(new TiXmlElement("entity_model"));
  647. xml_entity_script->SetAttribute("name", entity.name);
  648. // TODO: Add to list of HRC's to convert, obtain name of converted .mesh file.
  649. auto lower_case_hrc_name = desc.hrc_name;
  650. VGears::StringUtil::toLowerCase(lower_case_hrc_name);
  651. xml_entity_script->SetAttribute(
  652. "file_name", used_models_and_anims_.ModelMetaDataName(lower_case_hrc_name)
  653. );
  654. xml_entity_script->SetAttribute("index", entity.index);
  655. if (desc.type == VGears::ModelListFile::PLAYER){
  656. // For player models the position is set manually in the xml because if a map
  657. // is loaded manually this is where the player will end up. Hence we set the
  658. // position to the first entity_point that we have.
  659. xml_entity_script->SetAttribute(
  660. "position", Ogre::StringConverter::toString(first_entity_point)
  661. );
  662. }
  663. else{
  664. xml_entity_script->SetAttribute(
  665. "position", Ogre::StringConverter::toString(Ogre::Vector3::ZERO)
  666. );
  667. }
  668. xml_entity_script->SetAttribute("direction", "0");
  669. // TODO: This isn't quite right, the models animation
  670. // translation seems to be inverted?
  671. xml_entity_script->SetAttribute("scale", "0.03125 0.03125 0.03125");
  672. xml_entity_script->SetAttribute(
  673. "root_orientation", "0.7071067811865476 0.7071067811865476 0 0"
  674. );
  675. element->LinkEndChild(xml_entity_script.release());
  676. }
  677. else{
  678. std::unique_ptr<TiXmlElement> xml_entity_script(new TiXmlElement("entity_script"));
  679. xml_entity_script->SetAttribute("name", entity.name);//it.first);
  680. element->LinkEndChild(xml_entity_script.release());
  681. }
  682. }
  683. const float downscaler_this = 128.0f * GetFieldScaleFactor(this_field_id);
  684. for (size_t i = 0; i < gateways.size(); i ++){
  685. const VGears::TriggersFile::Gateway& gateway = gateways[i];
  686. // If non-inactive gateway:
  687. if (gateway.destination_field_id != INACTIVE_GATEWAY_ID){
  688. std::unique_ptr<TiXmlElement> xml_entity_trigger(
  689. new TiXmlElement("entity_trigger")
  690. );
  691. xml_entity_trigger->SetAttribute("name", "Gateway" + std::to_string(i));
  692. xml_entity_trigger->SetAttribute(
  693. "point1",
  694. Ogre::StringConverter::toString(
  695. Ogre::Vector3(
  696. gateway.exit_line[0].x,
  697. gateway.exit_line[0].y,
  698. gateway.exit_line[0].z) / downscaler_this
  699. )
  700. );
  701. xml_entity_trigger->SetAttribute(
  702. "point2",
  703. Ogre::StringConverter::toString(
  704. Ogre::Vector3(
  705. gateway.exit_line[1].x,
  706. gateway.exit_line[1].y,
  707. gateway.exit_line[1].z) / downscaler_this
  708. )
  709. );
  710. // Enabled is hard coded to true.
  711. xml_entity_trigger->SetAttribute("enabled", "true");
  712. element->LinkEndChild(xml_entity_trigger.release());
  713. }
  714. }
  715. // Get music tracks
  716. std::vector<int> tracks = ExtractMusicTrackIds(field);
  717. std::unique_ptr<TiXmlElement> xml_tracks(new TiXmlElement("tracks"));
  718. for (int t = 0; t < tracks.size(); t ++){
  719. std::unique_ptr<TiXmlElement> xml_track(new TiXmlElement("track"));
  720. xml_track->SetAttribute("id", t);
  721. xml_track->SetAttribute("track_id", tracks[t]);
  722. xml_tracks->LinkEndChild(xml_track.release());
  723. }
  724. element->LinkEndChild(xml_tracks.release());
  725. doc.LinkEndChild(element.release());
  726. doc.SaveFile(output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/map.xml");
  727. const VGears::PaletteFilePtr& pal = field->GetPalette();
  728. const VGears::BackgroundFilePtr& bg = field->GetBackground();
  729. std::unique_ptr<Ogre::Image> bg_image(bg->CreateImage(pal));
  730. bg_image->encode("png");
  731. bg_image->save(output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/tiles.png");
  732. {
  733. TiXmlDocument bg_doc;
  734. std::unique_ptr<TiXmlElement> bg_element(new TiXmlElement("background2d"));
  735. // Magic constants.
  736. const int BG_SCALE_UP_FACTOR = 3;
  737. const int BG_PSX_SCREEN_WIDTH = 320;
  738. const int BG_PSX_SCREEN_HEIGHT = 240;
  739. // Get texture atlas size.
  740. const int width = bg_image->getWidth();
  741. const int height = bg_image->getHeight();
  742. const VGears::CameraMatrixFilePtr& camera_matrix = field->GetCameraMatrix();
  743. const Ogre::Vector3 position
  744. = camera_matrix->GetPosition() / GetFieldScaleFactor(this_field_id);
  745. const Ogre::Quaternion orientation = camera_matrix->GetOrientation();
  746. const Ogre::Degree fov
  747. = camera_matrix->GetFov(static_cast<float>(BG_PSX_SCREEN_HEIGHT));
  748. const int min_x = triggers->GetCameraRange().left * BG_SCALE_UP_FACTOR;
  749. const int min_y = triggers->GetCameraRange().top * BG_SCALE_UP_FACTOR;
  750. const int max_x = triggers->GetCameraRange().right * BG_SCALE_UP_FACTOR;
  751. const int max_y = triggers->GetCameraRange().bottom * BG_SCALE_UP_FACTOR;
  752. bg_element->SetAttribute("image", field->getName() + "/tiles.png");
  753. bg_element->SetAttribute("position", Ogre::StringConverter::toString(position));
  754. bg_element->SetAttribute("orientation", Ogre::StringConverter::toString(orientation));
  755. bg_element->SetAttribute("fov", Ogre::StringConverter::toString(fov));
  756. bg_element->SetAttribute(
  757. "range",
  758. std::to_string(min_x) + " " + std::to_string(min_y) + " "
  759. + std::to_string(max_x) + " " + std::to_string(max_y)
  760. );
  761. bg_element->SetAttribute(
  762. "clip",
  763. std::to_string(BG_PSX_SCREEN_WIDTH * BG_SCALE_UP_FACTOR) + " "
  764. + std::to_string(BG_PSX_SCREEN_HEIGHT * BG_SCALE_UP_FACTOR)
  765. );
  766. // Write out the *_BG.XML data.
  767. auto& layers = bg->GetLayers();
  768. for (const auto& layer : layers){
  769. // TODO: Should only the tiles to construct enabled layers be outputted?
  770. // if (layer.enabled){
  771. for (const VGears::BackgroundFile::SpriteData& sprite : layer.sprites){
  772. std::unique_ptr<TiXmlElement> xml_element(new TiXmlElement("tile"));
  773. xml_element->SetAttribute(
  774. "destination",
  775. Ogre::StringConverter::toString(sprite.dst.x * BG_SCALE_UP_FACTOR) + " "
  776. + Ogre::StringConverter::toString(sprite.dst.y * BG_SCALE_UP_FACTOR)
  777. );
  778. xml_element->SetAttribute(
  779. "width", Ogre::StringConverter::toString(sprite.width * BG_SCALE_UP_FACTOR)
  780. );
  781. xml_element->SetAttribute(
  782. "height", Ogre::StringConverter::toString(sprite.height * BG_SCALE_UP_FACTOR)
  783. );
  784. // Each tile is added to a big texture atlas with hard coded size of 1024x1024,
  785. // convert UV's to the 0.0f to 1.0f range.
  786. const float u0 = static_cast<float>(sprite.src.x) / width;
  787. const float v0 = static_cast<float>(sprite.src.y) / height;
  788. const float u1 = static_cast<float>(sprite.src.x + sprite.width) / width;
  789. const float v1 = static_cast<float>(sprite.src.y + sprite.height) / height;
  790. xml_element->SetAttribute(
  791. "uv",
  792. Ogre::StringConverter::toString(u0) + " "
  793. + Ogre::StringConverter::toString(v0) + " "
  794. + Ogre::StringConverter::toString(u1) + " "
  795. + Ogre::StringConverter::toString(v1)
  796. );
  797. // TODO: It works (on FFVII PC), but why this number?
  798. xml_element->SetAttribute(
  799. "depth",
  800. Ogre::StringConverter::toString(static_cast<float>(sprite.depth) * (0.03125f))
  801. );
  802. // TODO: Copied from DatFile::AddTile.
  803. // Add to common method.
  804. Ogre::String blending_str = "";
  805. if (sprite.blending == 0) blending_str = "alpha";
  806. // 3 is source + 0.25 * destination:
  807. else if (sprite.blending == 1 || sprite.blending == 3) blending_str = "add";
  808. else if (sprite.blending == 2) blending_str = "subtract";
  809. // TODO: Should probably throw to fail conversion:
  810. else blending_str = "unknown";
  811. xml_element->SetAttribute("blending", blending_str);
  812. bg_element->LinkEndChild(xml_element.release());
  813. }
  814. //}
  815. }
  816. bg_doc.LinkEndChild(bg_element.release());
  817. bg_doc.SaveFile(
  818. output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/bg.xml"
  819. );
  820. }
  821. }
  822. {
  823. // Save out the walk mesh as XML.
  824. const VGears::WalkmeshFilePtr& walkmesh = field->GetWalkmesh();
  825. TiXmlDocument doc;
  826. std::unique_ptr<TiXmlElement> wmesh_element(new TiXmlElement("walkmesh"));
  827. for (VGears::WalkmeshFile::Triangle& tri : walkmesh->GetTriangles()){
  828. std::unique_ptr<TiXmlElement> xml_element(new TiXmlElement("triangle"));
  829. xml_element->SetAttribute("a", Ogre::StringConverter::toString(tri.a));
  830. xml_element->SetAttribute("b", Ogre::StringConverter::toString(tri.b));
  831. xml_element->SetAttribute("c", Ogre::StringConverter::toString(tri.c));
  832. xml_element->SetAttribute("a_b", std::to_string(tri.access_side[0]));
  833. xml_element->SetAttribute("b_c", std::to_string(tri.access_side[1]));
  834. xml_element->SetAttribute("c_a", std::to_string(tri.access_side[2]));
  835. wmesh_element->LinkEndChild(xml_element.release());
  836. }
  837. doc.LinkEndChild(wmesh_element.release());
  838. doc.SaveFile(output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/wm.xml");
  839. }
  840. converted_map_list_.push_back(field->getName());
  841. }