ips_layer.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. // Copyright 2018 yuzu emulator team
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <sstream>
  5. #include "common/assert.h"
  6. #include "common/hex_util.h"
  7. #include "common/swap.h"
  8. #include "core/file_sys/ips_layer.h"
  9. #include "core/file_sys/vfs_vector.h"
  10. namespace FileSys {
  11. enum class IPSFileType {
  12. IPS,
  13. IPS32,
  14. Error,
  15. };
  16. constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{
  17. std::pair{"\\a", "\a"}, {"\\b", "\b"}, {"\\f", "\f"}, {"\\n", "\n"},
  18. {"\\r", "\r"}, {"\\t", "\t"}, {"\\v", "\v"}, {"\\\\", "\\"},
  19. {"\\\'", "\'"}, {"\\\"", "\""}, {"\\\?", "\?"},
  20. };
  21. static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
  22. if (magic.size() != 5)
  23. return IPSFileType::Error;
  24. if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'})
  25. return IPSFileType::IPS;
  26. if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'})
  27. return IPSFileType::IPS32;
  28. return IPSFileType::Error;
  29. }
  30. VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
  31. if (in == nullptr || ips == nullptr)
  32. return nullptr;
  33. const auto type = IdentifyMagic(ips->ReadBytes(0x5));
  34. if (type == IPSFileType::Error)
  35. return nullptr;
  36. auto in_data = in->ReadAllBytes();
  37. std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
  38. u64 offset = 5; // After header
  39. while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
  40. offset += temp.size();
  41. if (type == IPSFileType::IPS32 && temp == std::vector<u8>{'E', 'E', 'O', 'F'} ||
  42. type == IPSFileType::IPS && temp == std::vector<u8>{'E', 'O', 'F'}) {
  43. break;
  44. }
  45. u32 real_offset{};
  46. if (type == IPSFileType::IPS32)
  47. real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3];
  48. else
  49. real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
  50. u16 data_size{};
  51. if (ips->ReadObject(&data_size, offset) != sizeof(u16))
  52. return nullptr;
  53. data_size = Common::swap16(data_size);
  54. offset += sizeof(u16);
  55. if (data_size == 0) { // RLE
  56. u16 rle_size{};
  57. if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
  58. return nullptr;
  59. rle_size = Common::swap16(data_size);
  60. offset += sizeof(u16);
  61. const auto data = ips->ReadByte(offset++);
  62. if (data == boost::none)
  63. return nullptr;
  64. if (real_offset + rle_size > in_data.size())
  65. rle_size = in_data.size() - real_offset;
  66. std::memset(in_data.data() + real_offset, data.get(), rle_size);
  67. } else { // Standard Patch
  68. auto read = data_size;
  69. if (real_offset + read > in_data.size())
  70. read = in_data.size() - real_offset;
  71. if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
  72. return nullptr;
  73. offset += data_size;
  74. }
  75. }
  76. if (temp != std::vector<u8>{'E', 'E', 'O', 'F'} && temp != std::vector<u8>{'E', 'O', 'F'})
  77. return nullptr;
  78. return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
  79. in->GetContainingDirectory());
  80. }
  81. IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
  82. Parse();
  83. }
  84. IPSwitchCompiler::~IPSwitchCompiler() = default;
  85. std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
  86. return nso_build_id;
  87. }
  88. bool IPSwitchCompiler::IsValid() const {
  89. return valid;
  90. }
  91. static bool StartsWith(std::string_view base, std::string_view check) {
  92. return base.size() >= check.size() && base.substr(0, check.size()) == check;
  93. }
  94. static std::string EscapeStringSequences(std::string in) {
  95. for (const auto& seq : ESCAPE_CHARACTER_MAP) {
  96. for (auto index = in.find(seq.first); index != std::string::npos;
  97. index = in.find(seq.first, index)) {
  98. in.replace(index, std::strlen(seq.first), seq.second);
  99. index += std::strlen(seq.second);
  100. }
  101. }
  102. return in;
  103. }
  104. void IPSwitchCompiler::ParseFlag(const std::string& line) {
  105. if (StartsWith(line, "@flag offset_shift ")) {
  106. // Offset Shift Flag
  107. offset_shift = std::stoll(line.substr(19), nullptr, 0);
  108. } else if (StartsWith(line, "@little-endian")) {
  109. // Set values to read as little endian
  110. is_little_endian = true;
  111. } else if (StartsWith(line, "@big-endian")) {
  112. // Set values to read as big endian
  113. is_little_endian = false;
  114. } else if (StartsWith(line, "@flag print_values")) {
  115. // Force printing of applied values
  116. print_values = true;
  117. }
  118. }
  119. void IPSwitchCompiler::Parse() {
  120. const auto bytes = patch_text->ReadAllBytes();
  121. std::stringstream s;
  122. s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
  123. std::vector<std::string> lines;
  124. std::string stream_line;
  125. while (std::getline(s, stream_line)) {
  126. // Remove a trailing \r
  127. if (!stream_line.empty() && stream_line.back() == '\r')
  128. stream_line.pop_back();
  129. lines.push_back(std::move(stream_line));
  130. }
  131. for (std::size_t i = 0; i < lines.size(); ++i) {
  132. auto line = lines[i];
  133. // Remove midline comments
  134. std::size_t comment_index = std::string::npos;
  135. bool within_string = false;
  136. for (std::size_t k = 0; k < line.size(); ++k) {
  137. if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
  138. within_string = !within_string;
  139. } else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
  140. comment_index = k;
  141. break;
  142. }
  143. }
  144. if (!StartsWith(line, "//") && comment_index != std::string::npos) {
  145. last_comment = line.substr(comment_index + 2);
  146. line = line.substr(0, comment_index);
  147. }
  148. if (StartsWith(line, "@stop")) {
  149. // Force stop
  150. break;
  151. } else if (StartsWith(line, "@nsobid-")) {
  152. // NSO Build ID Specifier
  153. auto raw_build_id = line.substr(8);
  154. if (raw_build_id.size() != 0x40)
  155. raw_build_id.resize(0x40, '0');
  156. nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
  157. } else if (StartsWith(line, "#")) {
  158. // Mandatory Comment
  159. LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
  160. patch_text->GetName(), line.substr(1));
  161. } else if (StartsWith(line, "//")) {
  162. // Normal Comment
  163. last_comment = line.substr(2);
  164. if (last_comment.find_first_not_of(' ') == std::string::npos)
  165. continue;
  166. if (last_comment.find_first_not_of(' ') != 0)
  167. last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
  168. } else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
  169. // Start of patch
  170. const auto enabled = StartsWith(line, "@enabled");
  171. if (i == 0)
  172. return;
  173. LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
  174. patch_text->GetName(), last_comment, line.substr(1));
  175. IPSwitchPatch patch{last_comment, enabled, {}};
  176. // Read rest of patch
  177. while (true) {
  178. if (i + 1 >= lines.size())
  179. break;
  180. const auto patch_line = lines[++i];
  181. // Start of new patch
  182. if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
  183. --i;
  184. break;
  185. }
  186. // Check for a flag
  187. if (StartsWith(patch_line, "@")) {
  188. ParseFlag(patch_line);
  189. continue;
  190. }
  191. // 11 - 8 hex digit offset + space + minimum two digit overwrite val
  192. if (patch_line.length() < 11)
  193. break;
  194. auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
  195. offset += offset_shift;
  196. std::vector<u8> replace;
  197. // 9 - first char of replacement val
  198. if (patch_line[9] == '\"') {
  199. // string replacement
  200. auto end_index = patch_line.find('\"', 10);
  201. if (end_index == std::string::npos || end_index < 10)
  202. return;
  203. while (patch_line[end_index - 1] == '\\') {
  204. end_index = patch_line.find('\"', end_index + 1);
  205. if (end_index == std::string::npos || end_index < 10)
  206. return;
  207. }
  208. auto value = patch_line.substr(10, end_index - 10);
  209. value = EscapeStringSequences(value);
  210. replace.reserve(value.size());
  211. std::copy(value.begin(), value.end(), std::back_inserter(replace));
  212. } else {
  213. // hex replacement
  214. const auto value = patch_line.substr(9);
  215. replace.reserve(value.size() / 2);
  216. replace = Common::HexStringToVector(value, is_little_endian);
  217. }
  218. if (print_values) {
  219. LOG_INFO(Loader,
  220. "[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
  221. "with byte string '{}'",
  222. patch_text->GetName(), offset, Common::HexVectorToString(replace));
  223. }
  224. patch.records.insert_or_assign(offset, std::move(replace));
  225. }
  226. patches.push_back(std::move(patch));
  227. } else if (StartsWith(line, "@")) {
  228. ParseFlag(line);
  229. }
  230. }
  231. valid = true;
  232. }
  233. VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
  234. if (in == nullptr || !valid)
  235. return nullptr;
  236. auto in_data = in->ReadAllBytes();
  237. for (const auto& patch : patches) {
  238. if (!patch.enabled)
  239. continue;
  240. for (const auto& record : patch.records) {
  241. if (record.first >= in_data.size())
  242. continue;
  243. auto replace_size = record.second.size();
  244. if (record.first + replace_size > in_data.size())
  245. replace_size = in_data.size() - record.first;
  246. for (std::size_t i = 0; i < replace_size; ++i)
  247. in_data[i + record.first] = record.second[i];
  248. }
  249. }
  250. return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
  251. in->GetContainingDirectory());
  252. }
  253. } // namespace FileSys