It's simply a loader that will do all of the things my release does. It's just a practical application of what my Silkroad Development Series guides show, except with specific Silkroad patches.
Make sure to keep a 1.201 TSRO client so you can refer back to it in the future for updates as I won't be updating this. You can also use the other guides to add in your own custom console commands and output. Likewise, if you are a developer, you can integrate in my new and have a nice dev tool for TSRO.
Just as a reminder, the Loader's configuration file is stored in: "%appdata%\edxLabs\edxTsroMod\edxTsroMod.ini". So if you change the paths of the DLL and it no longer works, you need to clear the path to the DLL inside that file or just delete it and rechoose your locations.
Included is the source and a precompiled binary in the attached file. I have tested it out for a while on TSRO while playing a new character and it seems to work just fine.
Here's the relevant source code files for easy reference if you don't want to downlod anything. You can grab Common.h/Common.cpp from as well.
DLL.cpp
Code:
// This project based on my Silkroad development series guides: // http://www.elitepvpers.com/forum/sro-guides-templates/271405-guide-silkroad-development-series.html #include <windows.h> #include "../common/common.h" // Global instance handle to this DLL HMODULE gInstance = NULL; // Function prototype void UserOnInject(); // Main DLL entry point BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved) { UNREFERENCED_PARAMETER(lpReserved); if(ulReason == DLL_PROCESS_ATTACH) { gInstance = hModule; // Do not notify this DLL of thread based events DisableThreadLibraryCalls(hModule); } return TRUE; } // This is the main function that is called when the DLL is injected into the process extern "C" __declspec(dllexport) void OnInject(DWORD address, LPDWORD bytes) { // Restore the original bytes at the OEP DWORD wrote = 0; WriteProcessMemory(GetCurrentProcess(), UlongToPtr(address), bytes, 6, &wrote); // Call our user function to keep this function clean UserOnInject(); } //------------------------------------------------------------------------- // Function we use to obtain the global settings object (needs updating each patch) FARPROC GetGlobalSettingsFunc = (FARPROC)0x4631D0; DWORD codecave_EnglishPatch_ReturnAddress = 0; __declspec(naked) void codecave_EnglishPatch() { __asm pop codecave_EnglishPatch_ReturnAddress __asm pushad __asm call GetGlobalSettingsFunc __asm MOV DWORD PTR DS:[EAX + 0x160], 2 // Taiwan __asm popad __asm CMP DWORD PTR DS:[0xD3DF4C], 0 // original code, needs updating each client patch __asm push codecave_EnglishPatch_ReturnAddress __asm ret } //------------------------------------------------------------------------- // Function we use to obtain the global settings object (needs updating each patch) FARPROC AppendBytesFunc = (FARPROC)0x413D80; LPBYTE pMac; void Multiclient() { *((LPDWORD)(pMac + 2)) = GetTickCount(); } DWORD codecave_Multiclient_ReturnAddress = 0; __declspec(naked) void codecave_Multiclient() { __asm pop codecave_Multiclient_ReturnAddress __asm mov pMac, eax __asm pushad Multiclient(); __asm popad __asm call AppendBytesFunc // Original code __asm push codecave_Multiclient_ReturnAddress __asm ret } //------------------------------------------------------------------------- // The function where we place all our logic void UserOnInject() { // Create a debugging console //edx::CreateConsole("SilkroadFramework Debugging Console"); //------------------------------------------------------------------------- // Mutex for the launcher, no patches required to start Silkroad now CreateMutexA(0, 0, "Silkroad Online Launcher"); CreateMutexA(0, 0, "Ready"); //------------------------------------------------------------------------- // Mutex patch - multi-client 1, easy, above launcher check //006D5DE0 |. /75 47 JNZ SHORT [edx]sro.006D5E29 //006D5DE0 /EB 47 JMP SHORT [edx]sro.006D5E29 BYTE patch0[] = {0xEB, 0x47}; edx::WriteBytes(0x6D5DE0, patch0, 2); // MAC patch - multi-client 2, more tricky, above // 004363C1 |> \BF 1074C900 MOV EDI,sro_clie.00C97410 ; ASCII "DownloadServer" // and below: // 0043629A |> \BF 0474C900 MOV EDI,sro_clie.00C97404 ; ASCII "AgentServer" /*0043636A |. 6A 06 PUSH 6 <-- size of bytes 0043636C |. 8D4424 48 LEA EAX,DWORD PTR SS:[ESP+48] 00436370 |. 50 PUSH EAX <- buffer to write 00436371 |. 8BCF MOV ECX,EDI 00436373 |. E8 08DAFDFF CALL <sro_clie.AppendBytes> <- function we want to call (we codecave on it) */ edx::CreateCodeCave(0x436373, 5, codecave_Multiclient); // Bind patch - multi-client 3 /*006E7987 |. E8 D4192C00 CALL sro_clie.009A9360 ; <-- NOP 006E798C |. 85C0 TEST EAX,EAX ; <-- NOP 006E798E |. 75 6B JNZ SHORT sro_clie.006E79FB ; <-- Force JMP 006E7990 |. 53 PUSH EBX ; /Style 006E7991 |. 68 C061C500 PUSH sro_clie.00C561C0 ; |Title = "Silkroad" 006E7996 |. 68 A061C500 PUSH sro_clie.00C561A0 ; |Text = "½ÇÅ©·Îµå°¡ ÀÌ¹Ì ½ÇÇà Áß ÀÔ´Ï´Ù." 006E799B |. 53 PUSH EBX ; |hOwner 006E799C |. FF15 B433C100 CALL NEAR DWORD PTR DS:[<&USER32.Message>; \MessageBoxA*/ BYTE patch5[] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xEB, 0x6B}; edx::WriteBytes(0x6E7987, patch5, 9); //------------------------------------------------------------------------- // English patch - force English - see my post here: // http://www.elitepvpers.com/forum/sro-guides-templates/277221-guide-how-patch-any-client-into-english-client.html //006EA5B9 . /75 16 JNZ SHORT [edx]sro.006EA5D1 BYTE patch2[] = {0x90, 0x90}; edx::WriteBytes(0x6EA5B9, patch2, 2); // English patch - Stop non-English //006EA539 . /75 1D JNZ SHORT [edx]sro.006EA558 BYTE patch3[] = {0xEB, 0x1D}; edx::WriteBytes(0x6EA539, patch3, 2); // English patch - Fake foreign flag //006D5EC1 |. 833D 4CDFD300 00 CMP DWORD PTR DS:[D3DF4C],0 edx::CreateCodeCave(0x6D5EC1, 7, codecave_EnglishPatch); //------------------------------------------------------------------------- // Zoom hack - see my post here: // http://www.elitepvpers.com/forum/sro-ask-experts/298072-help-infinite-zoom.html#post2731209 //0063B206 . /7A 08 JPE SHORT sro_clie.0063B210 BYTE patch4[] = {0xEB, 0x08}; edx::WriteBytes(0x63B206, patch4, 2); //------------------------------------------------------------------------- // Swear filter - just find all 3 UIIT_MSG_CHATWND_MESSAGE_FILTER and patch JEs before them // Skip the one that is a different layout. //004C712C . /74 43 JE SHORT sro_clie.004C7171 //004FF166 . /74 44 JE SHORT sro_clie.004FF1AC //0066DD4B |. /74 43 JE SHORT sro_clie.0066DD90 BYTE patch6[] = {0xEB}; edx::WriteBytes(0x4C712C, patch6, 1); edx::WriteBytes(0x4FF166, patch6, 1); edx::WriteBytes(0x66DD4B, patch6, 1); //------------------------------------------------------------------------- // Nude patch - try binary search: 8B 84 EE 1C 01 00 00 3B 44 24 14 // NOP the check. //008CA74B |. /75 1A JNZ SHORT sro_clie.008CA767 BYTE patch7[] = {0x90, 0x90}; edx::WriteBytes(0x8CA74B, patch7, 2); }
Code:
#include <windows.h> #include <string> #include <sstream> #include "../common/common.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // Let's get rid of any extra warnings we don't need UNREFERENCED_PARAMETER(hInstance); UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); UNREFERENCED_PARAMETER(nCmdShow); STARTUPINFOA si = {0}; PROCESS_INFORMATION pi = {0}; std::string pathToClient; std::string pathToDll; edx::ConfigFile cf; bool bExists = false; std::stringstream ini; // Try to load the configuration file ini << edx::GetWriteableDirectory("edxTsroMod") << "edxTsroMod.ini"; cf.Open(ini.str(), false, bExists); if(bExists) { pathToClient = cf.Read("client"); pathToDll = cf.Read("dll"); } if(bExists == false || pathToClient.empty() || pathToDll.empty()) { // Choose the injection DLL if required if(pathToDll.empty()) { edx::FileChooser fc; fc.AddFilter("Dynamic-link library", "*.dll"); fc.SetInitialDirectory(edx::GetAbsoluteDirectoryPath().c_str()); fc.SetDialogTitle("Please choose the DLL to inject..."); if(fc.ShowChooseFile(true) == false) { MessageBoxA(0, "The DLL selection process was canceled. The program will now exit.", "Fatal Error", MB_ICONERROR); return 0; } pathToDll = fc.GetSelectedFilePath(); cf.Write("dll", pathToDll); } // Choose the game client if required if(pathToClient.empty()) { edx::FileChooser fc; fc.SetDefaultFileName("sro_client.exe"); fc.AddFilter("Executable Files", "*.exe"); fc.SetDialogTitle("Please choose your \"sro_client.exe\" executable..."); if(fc.ShowChooseFile(true) == false) { MessageBoxA(0, "The client selection process was canceled. The program will now exit.", "Fatal Error", MB_ICONERROR); return 0; } std::string title = fc.GetSelectedFileTitle(); if(title.find("sro_client") == std::string::npos) { std::stringstream ss; ss << "You selected the file \"" << fc.GetSelectedFileName() << "\". Are you sure this is your \"sro_client.exe\" file?\n\nIf this file is not your \"sro_client.exe\" file, please choose No to exit the loader and try again. Otherwise please choose Yes to continue."; int result = MessageBoxA(0, ss.str().c_str(), "Unexpected file name detected", MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); if(result == IDNO) { return 0; } } pathToClient = fc.GetSelectedFilePath(); cf.Write("client", pathToClient); } // We have to restart the application now since the GetOpenFileName function messes up the working directories. // Unfortunately on Vista/Win7, the old method of storing the previous working directory and reseting it // doesn't seem to work anymore. MessageBoxA(0, "The directory changes have been saved. Please relaunch the program now.", "Application restart required", MB_ICONINFORMATION); return 0; } // Since we want to use a few functions WSADATA wsaData = {0}; WSAStartup(MAKEWORD(2, 2), &wsaData); bool bConnected = false; // Figure out which login server we should use. This is what Silkroad.exe pretty much does // in figuring out which last parameter to send to the client. std::stringstream args; args << "0 /12 0 "; for(int x = 1; x <= 3; ++x) { std::stringstream ss; if(x == 1 || x == 2) { ss << "gw" << x << ".sro.com.tw"; } else { ss << "login.sro.com.tw"; } if(edx::CanGetHostFromAddress(ss.str())) { SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); hostent * host = edx::GetHost(ss.str().c_str()); if(host) { sockaddr_in svr = {0}; svr.sin_addr.s_addr = *((unsigned long*)host->h_addr); svr.sin_family = AF_INET; svr.sin_port = htons(15779); if(connect(s, (struct sockaddr*)&svr, sizeof(svr)) == 0) { bConnected = true; args << (x - 1); break; } } closesocket(s); } } if(bConnected == false) { WSACleanup(); MessageBoxA(0, "The address to the Silkroad servers could not be resolved. There are two causes for this error:\n\n1. The Silkroad servers are actually down and you will have to wait until they are up again.\n\n2.Your computer cannot obtain the address of the Silkroad servers. Please visit http://www.joymax.com/silkroad first and try launching the Loader again.\n\nIf that fails, try running Silkroad.exe and see if you get the Start button. If you get a Start button, try running the Loader again until it works. If you do not get a Start button, then there is an issue on your system preventing you from obtaining the addresses.", "Fatal Error", MB_ICONERROR); return 0; } WSACleanup(); // Launch the client in a suspended state so we can patch it std::string workingDir = pathToClient.substr(0, 1 + pathToClient.find_last_of("\\/")); bool result = edx::CreateSuspendedProcess(pathToClient, args.str(), si, pi); if(result == false) { MessageBoxA(0, "Could not start \"sro_client.exe\".", "Fatal Error", MB_ICONERROR); return 0; } // Inject the DLL so we can have some fun result = (FALSE != edx::InjectDLL(pi.hProcess, pathToDll.c_str(), "OnInject", static_cast<DWORD>(edx::GetEntryPoint(pathToClient.c_str())), false)); if(result == false) { TerminateThread(pi.hThread, 0); MessageBoxA(0, "Could not inject into the Silkroad client process.", "Fatal Error", MB_ICONERROR); return 0; } // Finally resume the client. ResumeThread(pi.hThread); ResumeThread(pi.hProcess); // All done! return 0; }
Enjoy!