mirror of
				https://github.com/hedge-dev/UnleashedRecomp.git
				synced 2025-10-30 07:11:05 +00:00 
			
		
		
		
	Merge branch 'hedge-dev:main' into ControllerHotplugImprovements
This commit is contained in:
		
						commit
						6e7a2f6a13
					
				
					 10 changed files with 193 additions and 32 deletions
				
			
		|  | @ -31,6 +31,7 @@ | |||
| #include <user/config.h> | ||||
| #include <sdl_listener.h> | ||||
| #include <xxHashMap.h> | ||||
| #include <os/process.h> | ||||
| 
 | ||||
| #if defined(ASYNC_PSO_DEBUG) || defined(PSO_CACHING) | ||||
| #include <magic_enum/magic_enum.hpp> | ||||
|  | @ -735,10 +736,13 @@ static void DestructTempResources() | |||
| } | ||||
| 
 | ||||
| static std::thread::id g_presentThreadId = std::this_thread::get_id(); | ||||
| static std::atomic<bool> g_readyForCommands; | ||||
| 
 | ||||
| PPC_FUNC_IMPL(__imp__sub_824ECA00); | ||||
| PPC_FUNC(sub_824ECA00) | ||||
| { | ||||
|     // Guard against thread ownership changes when between command lists.
 | ||||
|     g_readyForCommands.wait(false); | ||||
|     g_presentThreadId = std::this_thread::get_id(); | ||||
|     __imp__sub_824ECA00(ctx, base); | ||||
| } | ||||
|  | @ -1623,6 +1627,9 @@ static void BeginCommandList() | |||
|     commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 1); | ||||
|     commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 2); | ||||
|     commandList->setGraphicsDescriptorSet(g_samplerDescriptorSet.get(), 3); | ||||
| 
 | ||||
|     g_readyForCommands = true; | ||||
|     g_readyForCommands.notify_one(); | ||||
| } | ||||
| 
 | ||||
| template<typename T> | ||||
|  | @ -1652,7 +1659,7 @@ static void ApplyLowEndDefaults() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Video::CreateHostDevice(const char *sdlVideoDriver) | ||||
| bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) | ||||
| { | ||||
|     for (uint32_t i = 0; i < 16; i++) | ||||
|         g_inputSlots[i].index = i; | ||||
|  | @ -1672,17 +1679,39 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver) | |||
|     std::vector<RenderInterfaceFunction *> interfaceFunctions; | ||||
| 
 | ||||
| #ifdef UNLEASHED_RECOMP_D3D12 | ||||
|     bool allowVulkanRedirection = true; | ||||
| 
 | ||||
|     if (graphicsApiRetry) | ||||
|     { | ||||
|         // If we are attempting to create again after a reboot due to a crash, swap the order.
 | ||||
|         g_vulkan = !g_vulkan; | ||||
| 
 | ||||
|         // Don't allow redirection to Vulkan if we are retrying after a crash, 
 | ||||
|         // so the user can at least boot the game with D3D12 if Vulkan fails to work.
 | ||||
|         allowVulkanRedirection = false; | ||||
|     } | ||||
| 
 | ||||
|     interfaceFunctions.push_back(g_vulkan ? CreateVulkanInterfaceWrapper : CreateD3D12Interface); | ||||
|     interfaceFunctions.push_back(g_vulkan ? CreateD3D12Interface : CreateVulkanInterfaceWrapper); | ||||
| #else | ||||
|     interfaceFunctions.push_back(CreateVulkanInterfaceWrapper); | ||||
| #endif | ||||
| 
 | ||||
|     for (RenderInterfaceFunction *interfaceFunction : interfaceFunctions) | ||||
|     for (size_t i = 0; i < interfaceFunctions.size(); i++) | ||||
|     { | ||||
|         g_interface = interfaceFunction(); | ||||
|         if (g_interface != nullptr) | ||||
|         RenderInterfaceFunction* interfaceFunction = interfaceFunctions[i]; | ||||
| 
 | ||||
| #ifdef UNLEASHED_RECOMP_D3D12 | ||||
|         // Wrap the device creation in __try/__except to survive from driver crashes.
 | ||||
|         __try | ||||
| #endif | ||||
|         { | ||||
|             g_interface = interfaceFunction(); | ||||
|             if (g_interface == nullptr) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             g_device = g_interface->createDevice(Config::GraphicsDevice); | ||||
|             if (g_device != nullptr) | ||||
|             { | ||||
|  | @ -1691,16 +1720,40 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver) | |||
| #ifdef UNLEASHED_RECOMP_D3D12 | ||||
|                 if (interfaceFunction == CreateD3D12Interface) | ||||
|                 { | ||||
|                     if (deviceDescription.vendor == RenderDeviceVendor::AMD) | ||||
|                     if (allowVulkanRedirection) | ||||
|                     { | ||||
|                         // AMD Drivers before this version have a known issue where MSAA resolve targets will fail to work correctly.
 | ||||
|                         // If no specific graphics API was selected, we silently destroy this one and move to the next option as it'll
 | ||||
|                         // just work incorrectly otherwise and result in visual glitches and 3D rendering not working in general.
 | ||||
|                         constexpr uint64_t MinimumAMDDriverVersion = 0x1F00005DC2005CULL; // 31.0.24002.92
 | ||||
|                         if ((Config::GraphicsAPI == EGraphicsAPI::Auto) && (deviceDescription.driverVersion < MinimumAMDDriverVersion)) | ||||
|                         bool redirectToVulkan = false; | ||||
| 
 | ||||
|                         if (deviceDescription.vendor == RenderDeviceVendor::AMD) | ||||
|                         { | ||||
|                             // AMD Drivers before this version have a known issue where MSAA resolve targets will fail to work correctly.
 | ||||
|                             // If no specific graphics API was selected, we silently destroy this one and move to the next option as it'll
 | ||||
|                             // just work incorrectly otherwise and result in visual glitches and 3D rendering not working in general.
 | ||||
|                             constexpr uint64_t MinimumAMDDriverVersion = 0x1F00005DC2005CULL; // 31.0.24002.92
 | ||||
|                             if ((Config::GraphicsAPI == EGraphicsAPI::Auto) && (deviceDescription.driverVersion < MinimumAMDDriverVersion)) | ||||
|                                 redirectToVulkan = true; | ||||
|                         } | ||||
|                         else if (deviceDescription.vendor == RenderDeviceVendor::INTEL) | ||||
|                         { | ||||
|                             // Intel drivers on D3D12 are extremely buggy, introducing various graphical glitches.
 | ||||
|                             // We will redirect users to Vulkan until a workaround can be found.
 | ||||
|                             if (Config::GraphicsAPI == EGraphicsAPI::Auto) | ||||
|                                 redirectToVulkan = true; | ||||
|                         } | ||||
| 
 | ||||
|                         if (redirectToVulkan) | ||||
|                         { | ||||
|                             g_device.reset(); | ||||
|                             g_interface.reset(); | ||||
| 
 | ||||
|                             // In case Vulkan fails to initialize, we will try D3D12 again afterwards, 
 | ||||
|                             // just to get the game to boot. This only really happens in very old Intel GPU drivers.
 | ||||
|                             if (!g_vulkan) | ||||
|                             { | ||||
|                                 interfaceFunctions.push_back(CreateD3D12Interface); | ||||
|                                 allowVulkanRedirection = false; | ||||
|                             } | ||||
| 
 | ||||
|                             continue; | ||||
|                         } | ||||
|                     } | ||||
|  | @ -1719,6 +1772,22 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver) | |||
|                 break; | ||||
|             } | ||||
|         } | ||||
| #ifdef UNLEASHED_RECOMP_D3D12 | ||||
|         __except (EXCEPTION_EXECUTE_HANDLER) | ||||
|         { | ||||
|             if (graphicsApiRetry) | ||||
|             { | ||||
|                 // If we were retrying, and this also failed, then we'll show the user neither of the graphics APIs succeeded.
 | ||||
|                 return false; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // If this is the first crash we ran into, reboot and try the other graphics API.
 | ||||
|                 os::process::StartProcess(os::process::GetExecutablePath(), { "--graphics-api-retry" }); | ||||
|                 std::_Exit(0); | ||||
|             } | ||||
|         } | ||||
| #endif | ||||
|     } | ||||
| 
 | ||||
|     if (g_device == nullptr) | ||||
|  | @ -1726,6 +1795,14 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver) | |||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| #ifdef UNLEASHED_RECOMP_D3D12 | ||||
|     if (graphicsApiRetry) | ||||
|     { | ||||
|         // If we managed to create a device after retrying it in a reboot, remember the one we picked.
 | ||||
|         Config::GraphicsAPI = g_vulkan ? EGraphicsAPI::Vulkan : EGraphicsAPI::D3D12; | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     g_capabilities = g_device->getCapabilities(); | ||||
| 
 | ||||
|     LoadEmbeddedResources(); | ||||
|  | @ -2360,18 +2437,22 @@ static void DrawProfiler() | |||
| 
 | ||||
|         ImGui::NewLine(); | ||||
| 
 | ||||
|         O1HeapDiagnostics diagnostics, physicalDiagnostics; | ||||
|         if (g_userHeap.heap != nullptr && g_userHeap.physicalHeap != nullptr) | ||||
|         { | ||||
|             std::lock_guard lock(g_userHeap.mutex); | ||||
|             diagnostics = o1heapGetDiagnostics(g_userHeap.heap); | ||||
|             O1HeapDiagnostics diagnostics, physicalDiagnostics; | ||||
|             { | ||||
|                 std::lock_guard lock(g_userHeap.mutex); | ||||
|                 diagnostics = o1heapGetDiagnostics(g_userHeap.heap); | ||||
|             } | ||||
|             { | ||||
|                 std::lock_guard lock(g_userHeap.physicalMutex); | ||||
|                 physicalDiagnostics = o1heapGetDiagnostics(g_userHeap.physicalHeap); | ||||
|             } | ||||
| 
 | ||||
|             ImGui::Text("Heap Allocated: %d MB", int32_t(diagnostics.allocated / (1024 * 1024))); | ||||
|             ImGui::Text("Physical Heap Allocated: %d MB", int32_t(physicalDiagnostics.allocated / (1024 * 1024))); | ||||
|         } | ||||
|         { | ||||
|             std::lock_guard lock(g_userHeap.physicalMutex); | ||||
|             physicalDiagnostics = o1heapGetDiagnostics(g_userHeap.physicalHeap); | ||||
|         } | ||||
|          | ||||
|         ImGui::Text("Heap Allocated: %d MB", int32_t(diagnostics.allocated / (1024 * 1024))); | ||||
|         ImGui::Text("Physical Heap Allocated: %d MB", int32_t(physicalDiagnostics.allocated / (1024 * 1024))); | ||||
| 
 | ||||
|         ImGui::Text("GPU Waits: %d", int32_t(g_waitForGPUCount)); | ||||
|         ImGui::Text("Buffer Uploads: %d", int32_t(g_bufferUploadCount)); | ||||
|         ImGui::NewLine(); | ||||
|  | @ -2710,6 +2791,8 @@ static std::atomic<bool> g_executedCommandList; | |||
| 
 | ||||
| void Video::Present()  | ||||
| { | ||||
|     g_readyForCommands = false; | ||||
| 
 | ||||
|     RenderCommand cmd; | ||||
|     cmd.type = RenderCommandType::ExecutePendingStretchRectCommands; | ||||
|     g_renderQueue.enqueue(cmd); | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ struct Video | |||
|     static inline uint32_t s_viewportWidth; | ||||
|     static inline uint32_t s_viewportHeight; | ||||
| 
 | ||||
|     static bool CreateHostDevice(const char *sdlVideoDriver); | ||||
|     static bool CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry); | ||||
|     static void WaitOnSwapChain(); | ||||
|     static void Present(); | ||||
|     static void StartPipelinePrecompilation(); | ||||
|  |  | |||
|  | @ -233,7 +233,7 @@ CONFIG_DEFINE_ENUM_LOCALE(EControllerIcons) | |||
|     { | ||||
|         ELanguage::English, | ||||
|         { | ||||
|             { EControllerIcons::Auto,        { "AUTO", "Auto : the game will determine which icons to use based on the current input device." } }, | ||||
|             { EControllerIcons::Auto,        { "AUTO", "Auto: the game will determine which icons to use based on the current input device." } }, | ||||
|             { EControllerIcons::Xbox,        { "XBOX", "" } }, | ||||
|             { EControllerIcons::PlayStation, { "PLAYSTATION", "" } } | ||||
|         } | ||||
|  | @ -407,7 +407,7 @@ CONFIG_DEFINE_LOCALE(EffectsVolume) | |||
| CONFIG_DEFINE_LOCALE(MusicAttenuation) | ||||
| { | ||||
|     { ELanguage::English,  { "Music Attenuation", "Fade out the game's music when external media is playing." } }, | ||||
|     { ELanguage::Japanese, { "BGM[減衰:げんすい]", "[外部:がいぶ]メディアを\u200B[再生:さいせい]すると\u200Bゲームの\u200B[音楽:おんがく]を\u200Bフェードアウトします" } }, | ||||
|     { ELanguage::Japanese, { "BGM[減衰:げんすい]", "[外部:がいぶ]メディアを\u200B[再生:さいせい]すると\u200Bゲームの\u200B[音楽:おんがく]を\u200Bフェードアウト\u200Bします" } }, | ||||
|     { ELanguage::German,   { "Musikdämpfung", "Stelle die Musik des Spiels stumm während externe Medien abgespielt werden." } }, | ||||
|     { ELanguage::French,   { "Atténuation audio", "Abaisse le volume des musiques du jeu lorsqu'un média externe est en cours de lecture." } }, | ||||
|     { ELanguage::Spanish,  { "Atenuación de música", "Atenúa la música del juego cuando un reproductor multimedia se encuentra activo." } }, | ||||
|  | @ -508,7 +508,7 @@ CONFIG_DEFINE_LOCALE(BattleTheme) | |||
| CONFIG_DEFINE_LOCALE(WindowSize) | ||||
| { | ||||
|     { ELanguage::English,  { "Window Size", "Adjust the size of the game window in windowed mode." } }, | ||||
|     { ELanguage::Japanese, { "ウィンドウサイズ", "ウィンドウモードでの\u200Bゲームの\u200Bウィンドウサイズを\u200B[調整:ちょうせい]できます" } }, | ||||
|     { ELanguage::Japanese, { "ウィンドウサイズ", "ウィンドウ\u200Bモードでの\u200Bゲームの\u200Bウィンドウサイズを\u200B[調整:ちょうせい]できます" } }, | ||||
|     { ELanguage::German,   { "Fenstergröße", "Ändere die Größe des Spielfensters im Fenstermodus." } }, | ||||
|     { ELanguage::French,   { "Taille de la fenêtre", "Modifie la taille de la fenêtre de jeu en mode fenêtré." } }, | ||||
|     { ELanguage::Spanish,  { "Tamaño de ventana", "Ajusta el tamaño de la ventana de juego." } }, | ||||
|  |  | |||
|  | @ -366,7 +366,7 @@ std::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>> | |||
|             { ELanguage::English,  "Installation complete!\nThis project is brought to you by:" }, | ||||
|             { ELanguage::Japanese, "インストール[完了:かんりょう]!\nプロジェクト[制作:せいさく]:" }, | ||||
|             { ELanguage::German,   "Installation abgeschlossen!\nDieses Projekt wird präsentiert von:" }, | ||||
|             { ELanguage::French,   "Installation terminée !\nCe projet vous est présenté par :" }, | ||||
|             { ELanguage::French,   "Installation terminée !\nCe projet vous est présenté\npar :" }, | ||||
|             { ELanguage::Spanish,  "¡Instalación completada!\nEste proyecto ha sido posible gracias a:" }, | ||||
|             { ELanguage::Italian,  "Installazione completata!\nQuesto progetto è stato creato da:" } | ||||
|         } | ||||
|  |  | |||
|  | @ -208,6 +208,7 @@ int main(int argc, char *argv[]) | |||
|     bool forceDLCInstaller = false; | ||||
|     bool useDefaultWorkingDirectory = false; | ||||
|     bool forceInstallationCheck = false; | ||||
|     bool graphicsApiRetry = false; | ||||
|     const char *sdlVideoDriver = nullptr; | ||||
| 
 | ||||
|     for (uint32_t i = 1; i < argc; i++) | ||||
|  | @ -216,6 +217,7 @@ int main(int argc, char *argv[]) | |||
|         forceDLCInstaller = forceDLCInstaller || (strcmp(argv[i], "--install-dlc") == 0); | ||||
|         useDefaultWorkingDirectory = useDefaultWorkingDirectory || (strcmp(argv[i], "--use-cwd") == 0); | ||||
|         forceInstallationCheck = forceInstallationCheck || (strcmp(argv[i], "--install-check") == 0); | ||||
|         graphicsApiRetry = graphicsApiRetry || (strcmp(argv[i], "--graphics-api-retry") == 0); | ||||
| 
 | ||||
|         if (strcmp(argv[i], "--sdl-video-driver") == 0) | ||||
|         { | ||||
|  | @ -234,9 +236,6 @@ int main(int argc, char *argv[]) | |||
|     } | ||||
| 
 | ||||
|     Config::Load(); | ||||
|      | ||||
|     if (!PersistentStorageManager::LoadBinary()) | ||||
|         LOGFN_ERROR("Failed to load persistent storage binary... (status code {})", (int)PersistentStorageManager::BinStatus); | ||||
| 
 | ||||
|     if (forceInstallationCheck) | ||||
|     { | ||||
|  | @ -326,7 +325,7 @@ int main(int argc, char *argv[]) | |||
|     bool runInstallerWizard = forceInstaller || forceDLCInstaller || !isGameInstalled; | ||||
|     if (runInstallerWizard) | ||||
|     { | ||||
|         if (!Video::CreateHostDevice(sdlVideoDriver)) | ||||
|         if (!Video::CreateHostDevice(sdlVideoDriver, graphicsApiRetry)) | ||||
|         { | ||||
|             SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise("Video_BackendError").c_str(), GameWindow::s_pWindow); | ||||
|             std::_Exit(1); | ||||
|  | @ -340,13 +339,16 @@ int main(int argc, char *argv[]) | |||
| 
 | ||||
|     ModLoader::Init(); | ||||
| 
 | ||||
|     if (!PersistentStorageManager::LoadBinary()) | ||||
|         LOGFN_ERROR("Failed to load persistent storage binary... (status code {})", (int)PersistentStorageManager::BinStatus); | ||||
| 
 | ||||
|     KiSystemStartup(); | ||||
| 
 | ||||
|     uint32_t entry = LdrLoadModule(modulePath); | ||||
| 
 | ||||
|     if (!runInstallerWizard) | ||||
|     { | ||||
|         if (!Video::CreateHostDevice(sdlVideoDriver)) | ||||
|         if (!Video::CreateHostDevice(sdlVideoDriver, graphicsApiRetry)) | ||||
|         { | ||||
|             SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise("Video_BackendError").c_str(), GameWindow::s_pWindow); | ||||
|             std::_Exit(1); | ||||
|  |  | |||
|  | @ -1650,3 +1650,39 @@ PPC_FUNC(sub_82E54950) | |||
|         __imp__sub_82E54950(ctx, base); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Credits while Sonic is running are offseted by 133 pixels at 16:9.
 | ||||
| // We can make this dynamic by remembering the anchoring and shifting accordingly.
 | ||||
| void EndingTextAllocMidAsmHook(PPCRegister& r3) | ||||
| { | ||||
|     r3.u32 += sizeof(uint32_t); | ||||
| } | ||||
| 
 | ||||
| static constexpr uint32_t ENDING_TEXT_SIZE = 0x164; | ||||
| 
 | ||||
| void EndingTextCtorRightMidAsmHook(PPCRegister& r3) | ||||
| { | ||||
|     *reinterpret_cast<uint32_t*>(g_memory.base + r3.u32 + ENDING_TEXT_SIZE) = ALIGN_RIGHT; | ||||
| } | ||||
| 
 | ||||
| void EndingTextCtorLeftMidAsmHook(PPCRegister& r3) | ||||
| { | ||||
|     *reinterpret_cast<uint32_t*>(g_memory.base + r3.u32 + ENDING_TEXT_SIZE) = ALIGN_LEFT; | ||||
| } | ||||
| 
 | ||||
| void EndingTextCtorCenterMidAsmHook(PPCRegister& r3) | ||||
| { | ||||
|     *reinterpret_cast<uint32_t*>(g_memory.base + r3.u32 + ENDING_TEXT_SIZE) = ALIGN_CENTER; | ||||
| } | ||||
| 
 | ||||
| void EndingTextPositionMidAsmHook(PPCRegister& r31, PPCRegister& f13) | ||||
| { | ||||
|     uint32_t align = *reinterpret_cast<uint32_t*>(g_memory.base + r31.u32 + ENDING_TEXT_SIZE); | ||||
| 
 | ||||
|     // Since widescreen is always forced, 133 offset will always be part of the position.
 | ||||
|     if (align == ALIGN_RIGHT) | ||||
|         f13.f64 += -133.0 * (1.0 - g_aspectRatioNarrowScale); | ||||
| 
 | ||||
|     else if (align == ALIGN_LEFT) | ||||
|         f13.f64 += 133.0 * (1.0 - g_aspectRatioNarrowScale); | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| VERSION_MILESTONE="" | ||||
| VERSION_MAJOR=1 | ||||
| VERSION_MINOR=0 | ||||
| VERSION_REVISION=2 | ||||
| VERSION_REVISION=3 | ||||
|  |  | |||
|  | @ -510,6 +510,11 @@ std::vector<std::string> Split(const char* strStart, const ImFont* font, float f | |||
|                 if (*str == '\n') | ||||
|                     str++; | ||||
| 
 | ||||
|                 if (strncmp(str, "\u200B", 3) == 0) | ||||
|                 { | ||||
|                     str += 3; | ||||
|                 } | ||||
| 
 | ||||
|                 lineStart = str; | ||||
|                 continue; | ||||
|             } | ||||
|  |  | |||
|  | @ -1106,3 +1106,38 @@ name = "UseAlternateTitleMidAsmHook" | |||
| address = 0x82580F44 | ||||
| jump_address_on_true = 0x82580F48 | ||||
| jump_address_on_false = 0x82580FA0 | ||||
| 
 | ||||
| [[midasm_hook]] | ||||
| name = "EndingTextAllocMidAsmHook" | ||||
| address = 0x8257E284 | ||||
| registers = ["r3"] | ||||
| 
 | ||||
| [[midasm_hook]] | ||||
| name = "EndingTextAllocMidAsmHook" | ||||
| address = 0x8257E45C | ||||
| registers = ["r3"] | ||||
| 
 | ||||
| [[midasm_hook]] | ||||
| name = "EndingTextAllocMidAsmHook" | ||||
| address = 0x8257EDD8 | ||||
| registers = ["r3"] | ||||
| 
 | ||||
| [[midasm_hook]] | ||||
| name = "EndingTextCtorRightMidAsmHook" | ||||
| address = 0x8257E304 | ||||
| registers = ["r3"] | ||||
| 
 | ||||
| [[midasm_hook]] | ||||
| name = "EndingTextCtorLeftMidAsmHook" | ||||
| address = 0x8257E4DC | ||||
| registers = ["r3"] | ||||
| 
 | ||||
| [[midasm_hook]] | ||||
| name = "EndingTextCtorCenterMidAsmHook" | ||||
| address = 0x8257EE40 | ||||
| registers = ["r3"] | ||||
| 
 | ||||
| [[midasm_hook]] | ||||
| name = "EndingTextPositionMidAsmHook" | ||||
| address = 0x82580168 | ||||
| registers = ["r31", "f13"] | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| Subproject commit 4897cf7ef2070120310c28a1a672b427d745dad8 | ||||
| Subproject commit 56738e5893ed7c4dc108996590475c52726623e3 | ||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Al. Lopez
						Al. Lopez