├── Patch.zip ├── Patch ├── Mods │ ├── AddNewModsHere.txt │ ├── FlyingMountSPUsage │ │ ├── Enabled.txt │ │ └── Scripts │ │ │ └── main.lua │ ├── MapUnlocker │ │ ├── Enabled.txt │ │ └── Scripts │ │ │ └── main.lua │ ├── PlayerPointsPerLevel │ │ ├── Enabled.txt │ │ └── Scripts │ │ │ └── main.lua │ ├── PlayerSPUsage │ │ ├── Enabled.txt │ │ └── Scripts │ │ │ └── main.lua │ ├── PlayerWeight │ │ ├── Enabled.txt │ │ └── Scripts │ │ │ └── main.lua │ ├── RarePalAppearRate_AndLevel │ │ ├── Enabled.txt │ │ └── Scripts │ │ │ └── main.lua │ ├── Test │ │ ├── Enabled.txt │ │ └── Scripts │ │ │ └── main.lua │ └── mods.txt ├── UE4SS-settings.ini └── UE4SS_Signatures │ ├── FName_Constructor.lua │ ├── FName_ToString.lua.example │ └── GUObjectArray.lua.example ├── README.md ├── gui.py ├── helper.py └── output ├── Patch.zip └── gui.exe /Patch.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NattKh/PalWorld-Tools/fff30809ec2cfcee42ee1a2ce39015225f727fd9/Patch.zip -------------------------------------------------------------------------------- /Patch/Mods/AddNewModsHere.txt: -------------------------------------------------------------------------------- 1 | Feel free to upload your Mods folder here help build up the mods list. 2 | -------------------------------------------------------------------------------- /Patch/Mods/FlyingMountSPUsage/Enabled.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Patch/Mods/FlyingMountSPUsage/Scripts/main.lua: -------------------------------------------------------------------------------- 1 | 2 | --Mount Flying SP Cost 3 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 4 | PalGameSetting.FlyMaxHeight = 999999 5 | end) 6 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 7 | PalGameSetting.FlyHover_SP = 0 8 | end) 9 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 10 | PalGameSetting.FlyHorizon_SP = 0 11 | end) 12 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 13 | PalGameSetting.FlyHorizon_Dash_SP = 0 14 | end) 15 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 16 | PalGameSetting.FlyVertical_SP = 0 17 | end) 18 | --Mount Flying SP Cost 19 | -------------------------------------------------------------------------------- /Patch/Mods/MapUnlocker/Enabled.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Patch/Mods/MapUnlocker/Scripts/main.lua: -------------------------------------------------------------------------------- 1 | --Open entire map 2 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 3 | PalGameSetting.worldmapUIMaskClearSize = 999999 4 | end) 5 | --Open entire map -------------------------------------------------------------------------------- /Patch/Mods/PlayerPointsPerLevel/Enabled.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Patch/Mods/PlayerPointsPerLevel/Scripts/main.lua: -------------------------------------------------------------------------------- 1 | --Level up modifier 2 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 3 | PalGameSetting.technologyPointPerLevel = 50 4 | end) 5 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 6 | PalGameSetting.StatusPointPerLevel = 50 7 | end) 8 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 9 | PalGameSetting.TechnologyPoint_UnlockFastTravel = 10 10 | end) 11 | --Level up modifier -------------------------------------------------------------------------------- /Patch/Mods/PlayerSPUsage/Enabled.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Patch/Mods/PlayerSPUsage/Scripts/main.lua: -------------------------------------------------------------------------------- 1 | --Player SP COST 2 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 3 | PalGameSetting.SprintSP = 0 4 | end) 5 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 6 | PalGameSetting.GliderSP = 0 --don't work for some reason. 7 | end) 8 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 9 | PalGameSetting.Swimming_SP_Swim = 0 10 | end) 11 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 12 | PalGameSetting.MeleeAttackSP = 0 13 | end) 14 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 15 | PalGameSetting.JumpSP = 0 16 | end) 17 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 18 | PalGameSetting.StepSP = 0 19 | end) 20 | --Player SP COST -------------------------------------------------------------------------------- /Patch/Mods/PlayerWeight/Enabled.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Patch/Mods/PlayerWeight/Scripts/main.lua: -------------------------------------------------------------------------------- 1 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 2 | PalGameSetting.DefaultMaxInventoryWeight = 1000 3 | PalGameSetting.AddMaxInventoryWeightPerStatusPoint = 50 4 | end) -------------------------------------------------------------------------------- /Patch/Mods/RarePalAppearRate_AndLevel/Enabled.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Patch/Mods/RarePalAppearRate_AndLevel/Scripts/main.lua: -------------------------------------------------------------------------------- 1 | --Rate Appearance 2 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 3 | PalGameSetting.RarePal_AppearanceProbability = 10.0 4 | end) 5 | -- Level multipler 6 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 7 | PalGameSetting.RarePal_LevelMultiply = 30.0 8 | end) -------------------------------------------------------------------------------- /Patch/Mods/Test/Enabled.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Patch/Mods/Test/Scripts/main.lua: -------------------------------------------------------------------------------- 1 | 2 | --random testing 3 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 4 | PalGameSetting.BuildingProgressInterpolationSpeed = 1000 5 | end) 6 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 7 | PalGameSetting.AimingSpeedRateInRide = 100 8 | end) 9 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 10 | PalGameSetting.WorkerCollectResourceStackMaxNum = 100 11 | end) 12 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 13 | PalGameSetting.BuildObj_HatchedPalCharacterLevel = 100 14 | end) 15 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 16 | PalGameSetting.AddWorkSpeedPerStatusPoint = 1000 17 | end) 18 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 19 | PalGameSetting.BuildingMaxZ = 999999 20 | end) 21 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 22 | PalGameSetting.VisitorNPCProbability = 100 23 | end) 24 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 25 | PalGameSetting.PalRotateSpeedToWork = 100 26 | end) 27 | --random testing 28 | 29 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 30 | PalGameSetting.StatusCalculate_TribeMultiply_CraftSpeed = 100 31 | end) 32 | 33 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 34 | PalGameSetting.CharacterMaxRank = 100 35 | end) 36 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 37 | PalGameSetting.OtomoLevelSyncAddMaxLevel = 100 38 | end) 39 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 40 | PalGameSetting.OtomoSlotNum = 100 41 | end) 42 | NotifyOnNewObject("/Script/Pal.PalUtility", function(PalUtility) 43 | PalUtility.GetDebugBotBaseCampWorkerCount = 100 44 | end) 45 | NotifyOnNewObject("/Script/Pal.PalBaseCampFacilityCountPair", function(FPalBaseCampFacilityCountPair) 46 | FPalBaseCampFacilityCountPair.FacilityCount = 100 47 | end) 48 | NotifyOnNewObject("/Script/Pal.FPalBaseCampLevelMasterData", function(FPalBaseCampLevelMasterData) 49 | FPalBaseCampLevelMasterData.WorkerMaxNum = 100 50 | end) 51 | NotifyOnNewObject("/Script/Pal.FPalOptionWorldStaticSettings", function(FPalOptionWorldStaticSettings) 52 | FPalOptionWorldStaticSettings.BaseCampWorkerMaxNum = 100 53 | end) 54 | NotifyOnNewObject("/Script/Pal.FPalOptionWorldStaticSettings", function(FPalOptionWorldStaticSettings) 55 | FPalOptionWorldStaticSettings.PalEggDefaultHatchingTime = 0 56 | end) 57 | NotifyOnNewObject("/Script/Pal.FPalOptionWorldStaticSettings", function(FPalOptionWorldStaticSettings) 58 | FPalOptionWorldStaticSettings.PalCaptureRate = 100 59 | end) 60 | NotifyOnNewObject("/Script/Pal.FPalOptionWorldSettings", function(FPalOptionWorldSettings) 61 | FPalOptionWorldSettings.BaseCampMaxNum = 100 62 | end) 63 | NotifyOnNewObject("/Script/Pal.FPalOptionWorldSettings", function(FPalOptionWorldSettings) 64 | FPalOptionWorldSettings.BaseCampWorkerMaxNum = 100 65 | end) 66 | 67 | NotifyOnNewObject("/Script/Pal.FPalOptionWorldSettings", function(EPalOptionWorldDifficulty) 68 | EPalOptionWorldDifficulty.BaseCampWorkerMaxNum = 100 69 | EPalOptionWorldDifficulty.BaseCampMaxNum = 100 70 | end) 71 | NotifyOnNewObject("/Script/Pal.PalGameSetting", function(PalGameSetting) 72 | PalGameSetting.BaseCampWorkerMaxNum = 100 73 | PalGameSetting.BaseCampMaxNum = 100 74 | PalGameSetting.WorkerMaxNum = 100 75 | end) -------------------------------------------------------------------------------- /Patch/Mods/mods.txt: -------------------------------------------------------------------------------- 1 | Test : 0 2 | FlyingMountSPUsage : 1 3 | MapUnlocker : 1 4 | PlayerPointsPerLevel : 1 5 | PlayerSPUsage : 1 6 | PlayerWeight : 1 7 | RarePalAppearRate_AndLevel : 1 -------------------------------------------------------------------------------- /Patch/UE4SS-settings.ini: -------------------------------------------------------------------------------- 1 | [Overrides] 2 | ; Path to the 'Mods' folder 3 | ; Default: /Mods 4 | ModsFolderPath = 5 | 6 | [General] 7 | EnableHotReloadSystem = 0 8 | 9 | ; Whether caches will be invalidated if ue4ss.dll has changed 10 | ; Default: 1 11 | InvalidateCacheIfDLLDiffers = 1 12 | 13 | ; The maximum number attempts the scanner will try before erroring out if an aob isn't found 14 | ; Default: 60 15 | MaxScanAttemptsNormal = 60 16 | 17 | ; The maximum number attempts the scanner will try for modular games before erroring out if an aob isn't found 18 | ; Default: 2000 19 | MaxScanAttemptsModular = 2500 20 | 21 | ; Whether to create UObject listeners in GUObjectArray to create a fast cache for use instead of iterating GUObjectArray. 22 | ; Setting this to false can help if you're experiencing a crash on startup. 23 | ; Default: true 24 | bUseUObjectArrayCache = true 25 | 26 | [EngineVersionOverride] 27 | MajorVersion = 28 | MinorVersion = 29 | 30 | [ObjectDumper] 31 | ; Whether to force all assets to be loaded before dumping objects 32 | ; WARNING: Can require multiple gigabytes of extra memory 33 | ; WARNING: Is not stable & will crash the game if you load past the main menu after dumping 34 | ; Default: 0 35 | LoadAllAssetsBeforeDumpingObjects = 0 36 | 37 | [CXXHeaderGenerator] 38 | ; Whether to property offsets and sizes 39 | ; Default: 1 40 | DumpOffsetsAndSizes = 1 41 | 42 | ; Whether memory layouts of classes and structs should be accurate 43 | ; This must be set to 1, if you want to use the generated headers in an actual C++ project 44 | ; When set to 0, padding member variables will not be generated 45 | ; NOTE: A VALUE OF 1 HAS NO PURPOSE YET! MEMORY LAYOUT IS NOT ACCURATE EITHER WAY! 46 | ; Default: 0 47 | KeepMemoryLayout = 0 48 | 49 | ; Whether to force all assets to be loaded before generating headers 50 | ; WARNING: Can require multiple gigabytes of extra memory 51 | ; WARNING: Is not stable & will crash the game if you load past the main menu after dumping 52 | ; Default: 0 53 | LoadAllAssetsBeforeGeneratingCXXHeaders = 0 54 | 55 | [UHTHeaderGenerator] 56 | ; Whether to skip generating packages that belong to the engine 57 | ; Some games make alterations to the engine and for those games you might want to set this to 0 58 | ; Default: 0 59 | IgnoreAllCoreEngineModules = 0 60 | 61 | ; Whether to skip generating the "Engine" and "CoreUObject" packages 62 | ; Default: 1 63 | IgnoreEngineAndCoreUObject = 1 64 | 65 | ; Whether to force all UFUNCTION macros to have "BlueprintCallable" 66 | ; Note: This will cause some errors in the generated headers that you will need to manually fix 67 | ; Default: 1 68 | MakeAllFunctionsBlueprintCallable = 1 69 | 70 | ; Whether to force all UPROPERTY macros to have "BlueprintReadWrite" 71 | ; Also forces all UPROPERTY macros to have "meta=(AllowPrivateAccess=true)" 72 | ; Default: 1 73 | MakeAllPropertyBlueprintsReadWrite = 1 74 | 75 | ; Whether to force UENUM macros on enums to have 'BlueprintType' if the underlying type was implicit or uint8 76 | ; Note: This also forces the underlying type to be uint8 where the type would otherwise be implicit 77 | ; Default: 1 78 | MakeEnumClassesBlueprintType = 1 79 | 80 | ; Whether to force "Config = Engine" on all UCLASS macros that use either one of: 81 | ; "DefaultConfig", "GlobalUserConfig" or "ProjectUserConfig" 82 | ; Default: 1 83 | MakeAllConfigsEngineConfig = 1 84 | 85 | [Debug] 86 | ; Whether to enable the external UE4SS debug console. 87 | ConsoleEnabled = 1 88 | GuiConsoleEnabled = 1 89 | GuiConsoleVisible = 1 90 | 91 | ; Multiplier for Font Size within the Debug Gui 92 | ; Default: 1 93 | GuiConsoleFontScaling = 1 94 | 95 | ; The API that will be used to render the GUI debug window. 96 | ; Valid values (case-insensitive): dx11, d3d11, opengl 97 | ; Default: opengl 98 | GraphicsAPI = opengl 99 | 100 | ; How many objects to put in each group in the live view. 101 | ; A lower number means more groups but less lag when a group is open. 102 | ; A higher number means less groups but more lag when a group is open. 103 | ; Default: 32768 104 | LiveViewObjectsPerGroup = 32768 105 | 106 | [Threads] 107 | ; The number of threads that the sig scanner will use (not real cpu threads, can be over your physical & hyperthreading max) 108 | ; If the game is modular then multi-threading will always be off regardless of the settings in this file 109 | ; Min: 1 110 | ; Max: 4294967295 111 | ; Default: 8 112 | SigScannerNumThreads = 8 113 | 114 | ; The minimum size that a module has to be in order for multi-threading to be enabled 115 | ; This should be large enough so that the cost of creating threads won't out-weigh the speed gained from scanning in multiple threads 116 | ; Min: 0 117 | ; Max: 4294967295 118 | ; Default: 16777216 119 | SigScannerMultithreadingModuleSizeThreshold = 16777216 120 | 121 | [Memory] 122 | ; The maximum memory usage (in percentage, see Task Manager %) allowed before asset loading (when LoadAllAssetsBefore* is 1) cannot happen. 123 | ; Once this percentage is reached, the asset loader will stop loading and whatever operation was in progress (object dump, or cxx generator) will continue. 124 | ; Default: 85 125 | MaxMemoryUsageDuringAssetLoading = 80 126 | 127 | [Hooks] 128 | HookProcessInternal = 1 129 | HookProcessLocalScriptFunction = 1 130 | HookInitGameState = 1 131 | HookCallFunctionByNameWithArguments = 1 132 | HookBeginPlay = 1 133 | HookLocalPlayerExec = 1 134 | FExecVTableOffsetInLocalPlayer = 0x28 135 | -------------------------------------------------------------------------------- /Patch/UE4SS_Signatures/FName_Constructor.lua: -------------------------------------------------------------------------------- 1 | function Register() 2 | return "48 89 5C 24 08 57 48 83 EC 30 48 8B D9 48 89 54 24 20 33 C9" 3 | end 4 | 5 | function OnMatchFound(MatchAddress) 6 | return MatchAddress 7 | end 8 | -------------------------------------------------------------------------------- /Patch/UE4SS_Signatures/FName_ToString.lua.example: -------------------------------------------------------------------------------- 1 | function Register() 2 | return "C 3/3 3/C 0/4 8/8 D/5 4/2 4/2 0/4 8/8 B/C F/4 8/8 9/4 4/2 4/2 0/4 8/8 9/4 4/2 4/2 8/E 8" 3 | end 4 | 5 | function OnMatchFound(MatchAddress) 6 | local AOBSize = 22 7 | local CallInstr = MatchAddress + AOBSize - 1 8 | local InstrSize = 5 9 | local NextInstr = CallInstr + InstrSize 10 | local Offset = DerefToInt32(CallInstr + 1) 11 | local ToStringAddress = NextInstr + Offset 12 | return ToStringAddress 13 | end -------------------------------------------------------------------------------- /Patch/UE4SS_Signatures/GUObjectArray.lua.example: -------------------------------------------------------------------------------- 1 | function Register() 2 | return "8 B/5 1/0 4/8 5/D 2/7 4/5 A/4 8/6 3/0 1/8 5/C 0/7 8/5 3/4 4/8 B/? ?/? ?/? ?/? ?/? ?/4 1/3 B/C 0/7 D/4 7/4 C/8 B" 3 | end 4 | 5 | function OnMatchFound(matchAddress) 6 | local movInstr = matchAddress + 0x1A 7 | local nextInstr = movInstr + 0x7 8 | local offset = movInstr + 0x3 9 | local dataMoved = nextInstr + DerefToInt32(offset) - 0x10 10 | 11 | return dataMoved 12 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #OUTDATED- PalWorlds Mod Patcher - WARNING THE Tool is just a downloader for UE4SS and an unpacker for my Lua Mod setup. YOU CAN ALWAYS DO THIS MANUALLY! Please use this flow instead. https://github.com/localcc/PalworldModdingKit 2 | I do not have the time to update the repo/build it every time UE4SS releases a new version because it would require me to test it to make sure it all still works. So I'm leaving this up just as a show-case in how you make a downloader and unzipper :) 3 | This is meant to be a Starter Pack, a basic organized setup for those who want to start tinkering 4 | 5 | FlyingMountSPUsage - NO SP COST While flying, 6 | MapUnlocker - Unlock the entire map, 7 | PlayerPointsPerLevel - Modify points increase per level, 8 | PlayerSPUsage - No SP usage for player "except gliding for some reason", 9 | PlayerWeight - Weight modifier "Default value may get reset when increased via points", 10 | RarePalAppearRate_AndLevel - "Change Rare Rate Appearance rate and its level" 11 | 12 | Modify the respective mods main.lua for the different modifiers. 13 | 14 | ## Overview 15 | 16 | PalWorlds Mod Patcher is a PyQt5-based GUI tool designed to simplify modding for the game PalWorld. It automates the process of downloading and applying a custom patch, managing game modifications, and reverting changes as needed. 17 | 18 | ## Features 19 | 20 | - **Download and Apply Patch**: Automatically downloads and applies the UE4SS DevKit patch to the specified PalWorld game directory. 21 | - **Manage Mods**: Enables users to easily enable or disable mods by editing the `Mods.txt` file in a user-friendly manner. 22 | - **Revert Changes**: Provides an option to 'Unpatch' the game, reverting it back to its pre-modded state. 23 | 24 | ## Setup 25 | 26 | 1. **Clone the Repository**: 27 | ```sh 28 | git clone https://github.com/your-github-username/palworlds-mod-patcher.git 29 | 30 | Install Dependencies: 31 | Ensure Python is installed on your system. 32 | Install required Python packages 33 | 34 | Usage 35 | Run the Tool: 36 | 37 | Navigate to the cloned repository's directory. 38 | Execute the main script: 39 | sh 40 | Copy code 41 | python gui.py 42 | Select Game Path: 43 | 44 | Use the 'Select Game Path' button to choose the PalWorld game directory. 45 | Patch Game: 46 | 47 | Click 'Patch Game' to download and apply the patch. 48 | Manage Mods: 49 | 50 | Use the 'Manage Mods' button to enable/disable mods as desired. 51 | Unpatch Game (Optional): 52 | 53 | To revert all changes, use the 'Unpatch Everything' button or check json file. 54 | 55 | Contributions / Credits 56 | https://gist.github.com/DRayX 57 | https://github.com/UE4SS-RE/RE-UE4SS 58 | 59 | Contributions, issues, and feature requests are welcome. Feel free to check issues page if you want to contribute. 60 | 61 | License 62 | Distributed under the MIT License. See LICENSE for more information. 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import requests 4 | import zipfile, tempfile, shutil 5 | from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QFileDialog, QVBoxLayout, QLabel, QMessageBox 6 | import json 7 | import helper # Import the helper module 8 | 9 | class Downloader(QWidget): 10 | def __init__(self): 11 | super().__init__() 12 | self.gameFolderPath = None 13 | self.addedFiles = [] # List to keep track of added files 14 | self.initUI() 15 | self.handle_errors(self.load_startup_config) 16 | 17 | def handle_errors(self, func, *args, **kwargs): 18 | try: 19 | func(*args, **kwargs) 20 | except Exception as e: 21 | self.infoLabel.setText(f'Error: {e}') 22 | def load_startup_config(self): 23 | patch_data = self.load_patch_data() 24 | if patch_data: 25 | self.gameFolderPath = patch_data['folder_path'] 26 | self.addedFiles = patch_data['added_files'] 27 | self.folderPathLabel.setText(f'Selected Folder: {self.gameFolderPath}') 28 | self.patchGameBtn.setEnabled(True) 29 | self.unpatchBtn.setEnabled(True) 30 | else: 31 | self.folderPathLabel.setText('No folder selected') 32 | self.patchGameBtn.setEnabled(False) 33 | self.unpatchBtn.setEnabled(False) 34 | 35 | def initUI(self): 36 | self.setWindowTitle('PalWorlds Mod Patcher') 37 | layout = QVBoxLayout() 38 | 39 | self.infoLabel = QLabel('INFO: Pal World default Game path: Steam\steamapps\common\Palworld\Pal\Binaries\Win64') 40 | layout.addWidget(self.infoLabel) 41 | self.infoLabel2 = QLabel('WARNING: This will download "UE4SS DevKit" and apply the custom patch made to the game.') 42 | layout.addWidget(self.infoLabel2) 43 | self.infoLabel3 = QLabel('INFO: Unpatch will only work with Admin folder permission to delete the files added by the patch, for manual deletion check json file for added files.') 44 | layout.addWidget(self.infoLabel3) 45 | 46 | self.infoLabel6 = QLabel('STEP 1. Select PalWorld Game Path, STEP 2. Patch Game, STEP 3. Modify Mods.txt, STEP 4. Restart Game') 47 | layout.addWidget(self.infoLabel6) 48 | self.folderPathLabel = QLabel('No folder selected please ') 49 | layout.addWidget(self.folderPathLabel) 50 | 51 | self.selectFolderBtn = QPushButton('Select PalWorld Game Path', self) 52 | self.selectFolderBtn.clicked.connect(self.select_folder) 53 | layout.addWidget(self.selectFolderBtn) 54 | 55 | self.patchGameBtn = QPushButton('Patch Game', self) 56 | self.patchGameBtn.clicked.connect(self.patch_game) 57 | self.patchGameBtn.setEnabled(False) # Disable this button initially 58 | layout.addWidget(self.patchGameBtn) 59 | 60 | self.unpatchBtn = QPushButton('Unpatch Everything', self) 61 | self.unpatchBtn.clicked.connect(self.unpatch_game) 62 | self.unpatchBtn.setEnabled(False) # Disable this button initially 63 | layout.addWidget(self.unpatchBtn) 64 | 65 | 66 | # Button for managing mods 67 | self.infoLabel4 = QLabel('INFO: Enable/Disable Below - 0 = Disable, 1 = Enable.') 68 | layout.addWidget(self.infoLabel4) 69 | self.infoLabel5 = QLabel('INFO: Restart Game for it to take effect.') 70 | layout.addWidget(self.infoLabel5) 71 | self.manageModsBtn = QPushButton('Manage Mods.txt', self) 72 | self.manageModsBtn.clicked.connect(self.manage_mods) 73 | layout.addWidget(self.manageModsBtn) 74 | 75 | self.setLayout(layout) 76 | self.show() 77 | 78 | def manage_mods(self): 79 | self.handle_errors(self._manage_mods) 80 | 81 | def _manage_mods(self): 82 | result = helper.manage_mods(self.gameFolderPath) 83 | QMessageBox.information(self, "Manage Mods", result) 84 | 85 | 86 | def select_folder(self): 87 | self.gameFolderPath = QFileDialog.getExistingDirectory(self, "Select Game Directory") 88 | if self.gameFolderPath: 89 | self.folderPathLabel.setText(f'Selected Folder: {self.gameFolderPath}') 90 | self.patchGameBtn.setEnabled(True) 91 | self.unpatchBtn.setEnabled(True) 92 | self.save_patch_data() 93 | else: 94 | self.folderPathLabel.setText('No folder selected') 95 | self.patchGameBtn.setEnabled(False) 96 | self.unpatchBtn.setEnabled(False) 97 | 98 | def patch_game(self): 99 | if self.gameFolderPath: 100 | self.handle_errors(self._perform_patching) 101 | 102 | def _perform_patching(self): 103 | self.infoLabel.setText('Downloading...') 104 | zip_url = 'https://github.com/UE4SS-RE/RE-UE4SS/releases/download/v2.5.2/zDEV-UE4SS_Xinput_v2.5.2.zip' 105 | zip_path = os.path.join(tempfile.gettempdir(), 'downloaded.zip') 106 | self.download_file(zip_url, zip_path) 107 | self.infoLabel.setText('Unzipping tool...') 108 | 109 | with tempfile.TemporaryDirectory() as temp_dir: 110 | self.unzip_file(zip_path, temp_dir) 111 | self.copy_contents(temp_dir, self.gameFolderPath) 112 | 113 | # Delete existing Mods folder and UE4SS-settings.ini file if they exist 114 | mods_folder_path = os.path.join(self.gameFolderPath, 'Mods') 115 | settings_ini_path = os.path.join(self.gameFolderPath, 'UE4SS-settings.ini') 116 | if os.path.exists(mods_folder_path): 117 | shutil.rmtree(mods_folder_path) 118 | if os.path.exists(settings_ini_path): 119 | os.remove(settings_ini_path) 120 | 121 | self.infoLabel.setText('Applying mods...') 122 | self.apply_mods(self.gameFolderPath) 123 | self.save_patch_data() 124 | self.infoLabel.setText('Process completed!') 125 | 126 | 127 | def copy_contents(self, src, dst): 128 | # Identify the correct folder to copy from 129 | src_folder = os.path.join(src, 'DEV-UE4SS_Xinput_v2.5.2') 130 | if not os.path.exists(src_folder): 131 | raise FileNotFoundError(f"Expected folder not found in the zip: {src_folder}") 132 | 133 | for item in os.listdir(src_folder): 134 | s = os.path.join(src_folder, item) 135 | d = os.path.join(dst, item) 136 | if os.path.isdir(s): 137 | shutil.copytree(s, d, dirs_exist_ok=True) 138 | else: 139 | shutil.copy2(s, d) 140 | if not item.lower().endswith('.zip'): # Don't add ZIP files to addedFiles 141 | self.addedFiles.append(os.path.relpath(d, dst)) 142 | 143 | 144 | def download_file(self, url, path): 145 | response = requests.get(url) 146 | with open(path, 'wb') as file: 147 | file.write(response.content) 148 | 149 | def unzip_file(self, zip_path, extract_to, exclude_from_added_files=False): 150 | with zipfile.ZipFile(zip_path, 'r') as zip_ref: 151 | zip_ref.extractall(extract_to) 152 | if not exclude_from_added_files: 153 | self.addedFiles.extend(zip_ref.namelist()) 154 | 155 | def apply_mods(self, target_folder): 156 | mods_zip_path = 'Patch.zip' #Patch file 157 | self.unzip_file(mods_zip_path, target_folder, exclude_from_added_files=True) 158 | 159 | 160 | def unpatch_game(self): 161 | self.handle_errors(self._unpatch_game) 162 | 163 | def _unpatch_game(self): 164 | patch_data = self.load_patch_data() 165 | if patch_data: 166 | folder_path = patch_data['folder_path'] 167 | for file in patch_data['added_files']: 168 | try: 169 | file_path = os.path.join(folder_path, file) 170 | if os.path.exists(file_path): 171 | os.remove(file_path) 172 | except Exception as e: 173 | self.infoLabel.setText(f'Error deleting file {file_path}: {e}') 174 | return 175 | self.infoLabel.setText('All patched files have been removed.') 176 | else: 177 | self.infoLabel.setText('No patch data found.') 178 | 179 | 180 | 181 | def save_patch_data(self): 182 | patch_data = { 183 | 'folder_path': self.gameFolderPath, 184 | 'added_files': self.addedFiles 185 | } 186 | with open('patch_data.json', 'w') as file: 187 | json.dump(patch_data, file) 188 | 189 | def load_patch_data(self): 190 | try: 191 | with open('patch_data.json', 'r') as file: 192 | return json.load(file) 193 | except FileNotFoundError: 194 | return None 195 | 196 | 197 | if __name__ == '__main__': 198 | app = QApplication(sys.argv) 199 | ex = Downloader() 200 | sys.exit(app.exec_()) 201 | -------------------------------------------------------------------------------- /helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import webbrowser 3 | 4 | def manage_mods(game_path): 5 | mods_file_path = os.path.join(game_path, 'Mods', 'Mods.txt') 6 | 7 | if not os.path.exists(mods_file_path): 8 | return f"Mods.txt not found at: {mods_file_path}" 9 | 10 | # Attempt to open Mods.txt in the default text editor 11 | try: 12 | webbrowser.open(mods_file_path) 13 | return "Mods.txt opened in the default text editor. Make sure to save the file before exiting the editor." 14 | except Exception as e: 15 | return f"Error opening Mods.txt: {e}" 16 | -------------------------------------------------------------------------------- /output/Patch.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NattKh/PalWorld-Tools/fff30809ec2cfcee42ee1a2ce39015225f727fd9/output/Patch.zip -------------------------------------------------------------------------------- /output/gui.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NattKh/PalWorld-Tools/fff30809ec2cfcee42ee1a2ce39015225f727fd9/output/gui.exe --------------------------------------------------------------------------------