This manual patch applier was originally made to apply ISXPM (Inno Setup XDELTA Patch Maker) game patches. It is extremely useful in a scenario when ISXPM patch mismatches in one or several resulting files and then rolls back whole patch.
The default ISXPM behavior is
- Unpack xdeltas in PatchData folder into temporary subfolder in %TEMP% (if patch was made with "Place patch-data inside a patch" option)
- Apply all xdeltas and save all original unpatched files in Backup folder. If many big files were patched Backup folder will require lots of space to accomodate all of them!
- If there was some error or hash mismatch then roll back the patch - restore all unpatched files from Backup folder
My manual patcher was made to
- Remove the need for big Backup folder. If a file is successfuly patched (no error was reported by single-file patcher) then unpatched file can be immediately replaced with newer patched one (OVERWRITE_SOURCE_FILES variable)
- Not to roll back whole patch when all of the files except one or two were successfully patched and these mismatched files can be selectively obtained from some other source e.g. torrent download
How to use this for any ISXPM patch
- Create the .bat file (copy and paste the code below)
- Obtain PatchData folder (set patchdir variable accordingly if it is not named as PatchData by default). This folder is separate in patches created without "Place patch-data inside a patch" option. If it is not separate then it is packed inside the patch .exe/.bin and you need to do a trial install of the patch into some empty folder so that PatchData is unpacked into %TEMP% during patching and then you obtain it from %TEMP%.
- Identify xdelta file extension (patchext variable). Some files in PatchData e.g. those that are newly added are not xdeltas!
- Identify single-file patcher that is needed to apply xdeltas (patcher_name variable). ISXPM supports Xdelta, JojoDiff, HDiffPatch. Put the executable into PatchData or beside the .bat.
- Set patcher_params variable for specific single-file patcher if needed
This manual patch applier also supports the following ISXPM features, read the source for usage:
- PatchData on another drive. This feature is essential for small SSD which should only need space for biggest resulting file in the patch to successfully apply whole patch
- explicit _patch.lst (patch_lst_name variable) beside the .bat to apply only a subset of xdeltas
- key file used to check game version
- list of junked files or folders
- prepatch script used to rename/rearrange files before patching
- ExternalData subfolder with files in .arc archive. These are new files not covered by xdeltas in PatchData and compressed separately
- free space checking before applying patch
Now it is a part of USXPM (Unix-Style Xdelta Patch Maker) suite with more features added including free space checking before applying patch.
USXPM_PATCHER.bat
@echo off
echo USXPM_PATCHER v2.2 2026 Anton Shabunin https://ant-sh.blogspot.com
echo Part of UNIX-Style Xdelta Patch Maker (USXPM) suite
echo ==================================================================
rem Applies xdeltas from ISXPM-compatible PatchData folder
rem Formerly ISXPM_MANUAL.bat, now part of UNIX-Style Xdelta Patch Maker (USXPM) suite.
rem set IMDEBUG=1
rem PatchData is default folder name in ISXPM
set patchdir=PatchData
set external_data_foldername=ExternalData
rem Extension of xdeltas must be unique e.g. not be used by any files in patched set so that xdeltas could be identified
set patchext=._hdif_
set patcher_name=hpatchz-x64.exe
rem multi-thread Parallel mode -p-5
rem supports single compressed diffData(created by hdiffz -SD-stepSize)
rem or window diffData(HDIFFW26 created by -WD[-stepSize], needs v5.0 patcher)
set patcher_params=-p-5 -f
rem for xdelta3 set patcher_params=-d -s
set unarcexe_name=unarc.exe
set outdir=_new
rem if OVERWRITE_SOURCE_FILES set to 0 the set of patched files will be in %outdir% folder e.g.
rem it will require at least the same space as taken by set of source files
set OVERWRITE_SOURCE_FILES=1
rem Dynamically generate _patch.lst from patchdir.
rem Only done if there are no explicit _patch.lst files in game or PatchData folders.
rem Possible issue without explicit _patch.lst - if old xdeltas were left over in PatchData
rem then gen_patch_lst will include them into dynamic patch list, they will be rejected
rem and reported as errors!
set DYNAMIC_PATCH_LST=1
rem Will remove script itself, executables, lists, readme after successful patch application
set CLEANUP_AFTER=1
rem keyfile is a relative filename without "..."
rem If _keyfile.txt exists in %patchdir% or script folder it will override static keyfile and keyfilehash vars below
set keyfile=
set keyfilehash=
rem keyfilealgo will be set dynamically based on the length of keyfilehash
rem 32 byte hash is considered MD5
rem 40 byte hash is considered SHA1
rem These executables may only be needed under WINE which has no fully implemented certutil
set md5exe_name=md5sum.exe
set sha1exe_name=sha1sum.exe
rem if file in _patch folder (patchdir variable) is not an xdelta but
rem instead a ready file without %patchext% then it will be moved to _new (outdir variable)
rem or directly to destination if OVERWRITE_SOURCE_FILES set to 1
set notpatched_list=_patch.bad
set patch_lst_name=_patch.lst
set keyfile_txt_name=_keyfile.txt
set junked_lst_name=_junked.lst
rem Put commands to rearrange/rename/back up files before patching into _prepatch.cmd for example
rem if exist "test.txt" (
rem md new\folder\where\it\was\relocated
rem move test.txt new\folder\where\it\was\relocated\test_renamed.txt
rem )
set prepatch_cmd_name=_prepatch.cmd
rem File with free space requirements - CSV with columns.
rem Tirst two columns are mandatory and cannot be empty.
rem 1 2 3 (opt) 4 (opt)
rem mod_size_max mod_size_total junked_size_total added_size_total
rem (largest modified (size of all (size of files (size of new files)
rem file) modified files) to be deleted)
set freespace_req_name=_freespace_req.txt
rem Custom patchdir support
set DELETE_FILES_FROM_PATCHDIR=1
rem Default is to delete files from local patchdir
rem but not touch custom patchdir specified on command line
rem as it may be an archival copy stored on another drive.
rem If you want to move files from custom patchdir and delete it then
rem comment out or remove line set DELETE_FILES_FROM_PATCHDIR=0 below
set dirtype=default
if not "%~1"=="" (
set DELETE_FILES_FROM_PATCHDIR=0
set patchdir=%~1
set dirtype=custom
)
if not exist "%patchdir%" (
echo Patch data %dirtype% subfolder "%patchdir%" not found
echo Put it into game folder beside %~nx0 ^(it will be deleted^)
echo OR
echo specify path to it as a command line parameter to %~nx0
goto :exiting
) else (
echo Using %dirtype% patch data subfolder "%patchdir%" - DELETE_FILES_FROM_PATCHDIR is %DELETE_FILES_FROM_PATCHDIR%
)
rem ================ Here %patchdir% is good =========================
setlocal enabledelayedexpansion
rem Patcher exe must be suitable for xdeltas in %patchdir%
if exist "%patchdir%\%patcher_name%" set patcher_exe="%patchdir%\%patcher_name%"
if exist "%patcher_name%" set patcher_exe="%patcher_name%"
if not defined patcher_exe (
echo ERROR Single-file patcher executable %patcher_name% not found
echo Put it into "%patchdir%" or into game folder beside %~nx0
goto :exiting
)
set RUNNING_UNDER_WINE=0
rem findstr /i "wine_get_version" "%SystemRoot%\system32\ntdll.dll" >nul 2>&1
rem may not find Wine specific export string depending on iocharset of mounted fs
reg query "HKLM\Software\Wine" >nul 2>&1
if %errorlevel% equ 0 (
set RUNNING_UNDER_WINE=1
echo Running under WINE - %patchdir% must not have spaces or move hashers beside the script
rem assume that hashing exes are on PATH
set md5exe=%md5exe_name%
set sha1exe=%sha1exe_name%
if exist "%patchdir%\%md5exe_name%" set md5exe=%patchdir%\%md5exe_name%
if exist "%patchdir%\%sha1exe_name%" set sha1exe=%patchdir%\%sha1exe_name%
)
rem Before doing anything check keyfile if it is specified
if exist "%patchdir%\%keyfile_txt_name%" set keyfile_txt="%patchdir%\%keyfile_txt_name%"
if exist "%keyfile_txt_name%" set keyfile_txt="%keyfile_txt_name%"
if defined keyfile_txt (
rem without usebackq filename with spaces inside "... " is interpreted as literal string and not file name
for /f "usebackq tokens=1,*" %%A in (%keyfile_txt%) do (
set keyfile=%%B
set keyfilehash=%%A
rem check if filename extracted from _keyfile.txt starts with * then remove it
set firstchar=!keyfile:~0,1!
if {!firstchar!}=={*} (
if {%IMDEBUG%}=={1} echo removing leading ^* from keyfile name
set keyfile=!keyfile:~1!
)
)
)
if {%IMDEBUG%}=={1} (
echo ============================ DEBUG IS ENABLED ===========================
echo keyfile_txt is %keyfile_txt%
echo keyfile is %keyfile%, expected keyfilehash %keyfilehash%
)
if exist %notpatched_list% (
echo %notpatched_list% exists - renaming it as %patch_lst_name% and disabling keyfile check.
echo It is at least 2nd run so keyfile might have already been patched
rem _patch.lst (patch_lst_name) in game folder takes precedence
rem and will disable dynamic _patch.lst generation from %patchdir%
move /y %notpatched_list% %patch_lst_name%
set keyfile=
)
rem Actually checking hash of keyfile
if not "%keyfile%"=="" (
if not exist "%keyfile%" (
echo Warning - keyfile "%keyfile%" specified but not found! - exiting
goto :exiting
) else (
if not {%keyfilehash%}=={} (
rem Determine keyfilealgo
call :stringlength %keyfilehash% keyfilehashlen
if !keyfilehashlen!==40 (
if {%IMDEBUG%}=={1} echo detected keyfilealgo SHA1
set keyfilealgo=SHA1
set wine_hasher=%sha1exe%
) else (
if !keyfilehashlen!==32 (
if {%IMDEBUG%}=={1} echo detected keyfilealgo MD5
set keyfilealgo=MD5
set wine_hasher=%md5exe%
)
)
if defined keyfilealgo (
if {%RUNNING_UNDER_WINE%}=={1} (
if {%IMDEBUG%}=={1} echo Running !wine_hasher! "%keyfile%"
for /f "usebackq tokens=1,2" %%h in (`!wine_hasher! "%keyfile%"`) do (
echo Keyfile %keyfile% actual !keyfilealgo! hash is %%h
if /i {%keyfilehash%}=={%%h} (
echo Keyfile hash match OK - continuing
) else (
echo ERROR Keyfile specified but hash mismatches - expected %keyfilehash% - exiting
goto :exiting
)
)
) else (
rem certutil hash algorithms: MD2 MD4 MD5 SHA1 SHA256 SHA384 SHA512
for /f %%h in ('CertUtil -hashfile "%keyfile%" !keyfilealgo! ^| findstr "^[0-9a-z][0-9a-z]*$"') do (
echo Keyfile %keyfile% actual !keyfilealgo! hash is %%h
if /i {%keyfilehash%}=={%%h} (
echo Keyfile hash match OK - continuing
) else (
echo ERROR Keyfile specified but hash mismatches - expected %keyfilehash% - exiting
goto :exiting
)
)
)
) else (
echo ERROR keyfilehash %keyfilehash% with length !keyfilehashlen! is not supported - exiting
goto :exiting
)
) else (
echo Keyfile hash not specified - continuing
)
)
) else (
echo Keyfile not specified - continuing
)
rem counting extension length
call :stringlength %patchext% length
echo Xdelta extension is %patchext%, length %length%
if exist "%patchdir%\%junked_lst_name%" set junked_lst="%patchdir%\%junked_lst_name%"
if exist "%junked_lst_name%" set junked_lst="%junked_lst_name%"
echo junked_lst is %junked_lst%
if defined junked_lst (
for /f "usebackq tokens=*" %%f in (%junked_lst%) do (
if exist "%%~f" (
if not exist "%%~f\*" (
if {%IMDEBUG%}=={1} echo Deleting file "%%~f"
del /Q /F "%%~f"
) else (
if {%IMDEBUG%}=={1} echo Deleting folder "%%~f"
rd /s /q "%%~f"
)
)
)
if not {%IMDEBUG%}=={1} (
if exist "%junked_lst_name%" del /q "%junked_lst_name%"
if {%DELETE_FILES_FROM_PATCHDIR%}=={1} del /q %junked_lst%
)
)
rem Check if free space requirements are available for this patch
if exist "%patchdir%\%freespace_req_name%" set freespace_req_file="%patchdir%\%freespace_req_name%"
echo freespace_req_file is %freespace_req_file%
if not defined freespace_req_file (
echo WARNING no special file %freespace_req_name% with free space requirements found - continuing
goto :determine_patch_list
)
set mod_size_max=0
set mod_size_total=0
set junked_size_total=0
set added_size_total=0
rem space is needed in delims as freespace_req_file will have space at the end
for /f "usebackq tokens=1,2,3,4 delims=, " %%A in (%freespace_req_file%) do (
if {%IMDEBUG%}=={1} (
echo A max ch is "%%A"
echo B total ch is "%%B"
echo C junked is "%%C"
echo D added is "%%D"
)
set mod_size_max=%%A
set mod_size_total=%%B
set junked_size_total=%%C
set added_size_total=%%D
)
rem determine free space
set free_space=0
for /f "tokens=3" %%A in ('dir /-C') do set free_space=%%A
rem WMIC may be not installed --- for /f "tokens=2 delims==" %%A in ('wmic logicaldisk where "DeviceID='D:'" get FreeSpace /value') do set free_space=%%A
rem Take the last 20 characters of each for comparison as strings
set "pad1= %free_space%"
set "str1=%pad1:~-20%"
if {%OVERWRITE_SOURCE_FILES%}=={1} (
rem only need space for max size file (%mod_size_max%) or new files
call :get_max %mod_size_max% %added_size_total% size_to_check_for
) else (
rem need space for full set of modified files (%mod_size_total%) or new files
call :get_max %mod_size_total% %added_size_total% size_to_check_for
)
set "pad2= %size_to_check_for%"
set "str2=%pad2:~-20%"
if {%IMDEBUG%}=={1} echo str1 is %str1%, str2 is %str2%
if "%str1%" GTR "%str2%" (
echo Free space %free_space% bytes OK for %size_to_check_for% bytes required
) else (
echo Got %free_space% but needed at least %size_to_check_for% bytes free
echo ERROR not enough free space for patch - exiting
goto :exiting
)
:determine_patch_list
rem if {%IMDEBUG%}=={1} goto :eof
rem explicit _patch.lst in game folder takes precedence.
if exist "%patch_lst_name%" (
set patch_lst="%patch_lst_name%"
set DYNAMIC_PATCH_LST=0
goto :patching
)
rem if explict _patch.lst is in PatchData folder put by USXPM_PATCHDATA_MAKER.bat then use it.
rem Means to safeguard against old leftover xdeltas
if exist "%patchdir%\%patch_lst_name%" (
set patch_lst="%patchdir%\%patch_lst_name%"
set DYNAMIC_PATCH_LST=0
goto :patching
)
rem make temporary dynamic lst unique so that multiple patches could be run simultaneously
set patch_lst="%TEMP%\%RANDOM%%patch_lst_name%"
if {%DYNAMIC_PATCH_LST%}=={1} (
if {%IMDEBUG%}=={1} echo Dynamically making patchlist in !patch_lst!
rem Delayed expansion of !patch_lst! set in this if {...} doesn't work under WINE here
rem and errors witn Invalid name without calling gen_patch_lst
rem (call :gen_patch_lst) > !patch_lst!
(call :gen_patch_lst) > %patch_lst%
if {%IMDEBUG%}=={1} echo Made patchlist in !patch_lst!
)
:patching
call :countlines %patch_lst% total_xdeltas
echo DYNAMIC_PATCH_LST is %DYNAMIC_PATCH_LST%
echo patch_lst is %patch_lst%, total_xdeltas is %total_xdeltas%
rem set prepatch prepare script
if exist "%patchdir%\%prepatch_cmd_name%" set prepatch_cmd="%patchdir%\%prepatch_cmd_name%"
if exist "%prepatch_cmd_name%" set prepatch_cmd="%prepatch_cmd_name%"
echo prepatch_cmd is %prepatch_cmd%
rem n is a counter which includes prepatched/ready files
rem num_patched_files is actual number of successfully applied xdeltas
set /a n=0
set /a num_patched_files=0
if %total_xdeltas% GTR 0 (
rem Running prepatch prepare script only if there's something to do
if defined prepatch_cmd (
call %prepatch_cmd%
if not {%IMDEBUG%}=={1} (
if exist "%prepatch_cmd_name%" del /q "%prepatch_cmd_name%"
if {%DELETE_FILES_FROM_PATCHDIR%}=={1} del /q %prepatch_cmd%
)
)
if not exist "%outdir%" md "%outdir%"
for /f "usebackq tokens=*" %%A in (%patch_lst%) do (
set /a n+=1
TITLE Patching !n!/!total_xdeltas!
set filename=%%A
rem echo xdelta "!patchdir!\%%A"
set fileext=%%~xA
set filefolder=%%~dpA
set filefolder=!filefolder:~0,-1!
if {%IMDEBUG%}=={1} (
echo !filename! extension !fileext!
timeout /t 1 > nul
)
rem echo gamesubfolder for the file is !filefolder!
if /i {!fileext!}=={!patchext!} (
set filename=!filename:~0,-%length%!
rem Now filename var contains name of file being patched.
rem It and its relative path are expected to exist
rem echo oldfile "!filename!"
if not {%IMDEBUG%}=={1} (
rem There's no need to create subfolder structure in _new if OVERWRITE_SOURCE_FILES set to 1
rem because the patched temp file if it's good will be moved over old file anyway
rem ===== PATCHER RUN ===== jpatch [options] <original file> <patch file> [<output file>]
%patcher_exe% %patcher_params% "!filename!" "!patchdir!\%%A" "!outdir!\_patched.tmp"
IF !ERRORLEVEL! NEQ 0 (
rem new file in outdir is probably bad - need to delete it to prevent it from overwriting good source
if exist "!outdir!\_patched.tmp" del /q "!outdir!\_patched.tmp" 2>nul
echo ERROR creating patched file "!filename!"
echo %%A>> %notpatched_list%
) else (
set /a num_patched_files+=1
rem patch was applied successfully - "!outdir!\_patched.tmp" is valid
if {%OVERWRITE_SOURCE_FILES%}=={1} (
echo Overwriting old file "!filename!" in game folder using patched file from !outdir!
rem move ready (pre)patched file to game folder from _patch
move /y "!outdir!\_patched.tmp" "!filename!"
) else (
echo Preparing subfolder structure for "!outdir!\!filename!"
rem can't use "!outdir!\_patched.tmp" as source because xcopy will consider it a cyclic copy
xcopy "!filename!" "!outdir!\!filename!" /T /-I /Y 2>nul
move /y "!outdir!\_patched.tmp" "!outdir!\!filename!"
)
rem may delete xdelta from patchdir
if {%DELETE_FILES_FROM_PATCHDIR%}=={1} del /q "!patchdir!\%%A"
)
) else (
echo DEBUG printing only - no actual files patched
rem PRINT echo xcopy "!filename!" "!outdir!\!filename!" /T /-I /Y 2^>nul
echo %patcher_name% "!filename!"
echo "!patchdir!\%%A"
echo "!outdir!\!filename!"
)
) else (
rem File and/or its relative path may not exist in the game folder
rem if it is a new file .e.g. part of new DLC!
echo Considering "!filename!" ready
if {%OVERWRITE_SOURCE_FILES%}=={1} (
echo Overwriting old file in game folder
if {%DELETE_FILES_FROM_PATCHDIR%}=={1} (
if not exist "!filename!" (
rem prepare subfolder structure for new file or else move command below will fail
rem WINE doesn't support /-I and may ask if it is a folder or a file. Need to press F
xcopy "!patchdir!\%%A" "!filename!" /T /-I /Y
)
rem move ready (pre)patched file to game folder from _patch
if {%IMDEBUG%}=={1} echo move /y "!patchdir!\%%A" "!filename!"
rem May not work under WINE with filenames that have spaces
move /y "!patchdir!\%%A" "!filename!"
) else (
rem xcopy will create needed subfolder structure if it doesn't exist
xcopy "!patchdir!\%%A" "!filename!" /-I /R /Y
)
) else (
rem Moving/Copying ready file to outdir
rem Needed subfolder structure may not exist in outdir yet!
rem Can't use !filename! as source file in xcopy because it may be new
if {%DELETE_FILES_FROM_PATCHDIR%}=={1} (
echo Preparing subfolder structure before moving
xcopy "!patchdir!\%%A" "!outdir!\!filename!" /T /-I /Y 2>nul
rem echo move /y "!patchdir!\%%A" "!outdir!\!filename!"
move /y "!patchdir!\%%A" "!outdir!\!filename!"
) else (
xcopy "!patchdir!\%%A" "!outdir!\!filename!" /-I /R /Y
)
)
IF !ERRORLEVEL! NEQ 0 (
echo ERROR copying or moving ready file "!filename!"
echo %%A>> %notpatched_list%
)
)
echo ------
)
TITLE Done !n! files
rem Avoid cluttering %TEMP%
if {%DYNAMIC_PATCH_LST%}=={1} del /q %patch_lst%
)
if {%IMDEBUG%}=={1} goto :eof
rem Here %n% contains actual number of xdeltas/ready files processed
if {%n%}=={0} (
echo Expected to process %total_xdeltas% total xdeltas or files
echo ERROR nothing was processed - exiting
goto :exiting
) else (
if {%num_patched_files%}=={0} (
echo ERROR no xdeltas were applied - exiting
goto :exiting
)
)
rem ---------------------- postpatch finish section begin ----------------------
if exist "%patchdir%\%unarcexe_name%" set arcexe="%patchdir%\%unarcexe_name%"
if exist "%unarcexe_name%" set arcexe="%unarcexe_name%"
set ExternalDataFolder=%external_data_foldername%
rem if custom PatchData location specified look for ExternalData beside it too
if "%dirtype%"=="custom" (
rem DELETE_FILES_FROM_PATCHDIR is 0 as custom PatchData and ExternalData may be archival copies
rem set patchdir=%~1
if not exist "%ExternalDataFolder%" (
echo No default ExternalDataFolder found - trying beside %dirtype% %patchdir%
set ExternalDataFolder=%patchdir%\..\%external_data_foldername%
) else (
echo Using default ExternalDataFolder %ExternalDataFolder%
)
)
if exist "%ExternalDataFolder%\ExternalData.arc" (
if defined arcexe (
%arcexe% x -o+ "%ExternalDataFolder%\ExternalData.arc"
rem %arcexe% x ExternalData\ExternalData.arc --overwrite=+
if {%DELETE_FILES_FROM_PATCHDIR%}=={1} rmdir /q /s "%ExternalDataFolder%"
) else (
echo ERROR external data unpacker %unarcexe_name% not found - unpack ExternalData\ExternalData.arc manually!
)
)
rem ---------------------- postpatch finish section end ------------------------
if {%OVERWRITE_SOURCE_FILES%}=={1} (
rmdir /q /s "!outdir!"
echo Patched files are in their place in your game folder!
echo Verify them using new .sha1 or .md5 or .blake3 file
) else (
echo New patched files are in "!outdir!" folder !
echo Verify them using new .sha1 or .md5 or .blake3 file there.
echo If they are OK then manually move them to game folder
echo and overwrite old files
)
if exist %notpatched_list% (
echo Errors were found during patching in %notpatched_list%:
echo.
type %notpatched_list%
echo.
echo Make sure you have enough space for the patched file^(s^) above
echo OR
echo check "!patchdir!" folder for old leftover xdeltas from previous patches
echo and delete them.
echo.
echo Rerun %~nx0 - on next run %notpatched_list% will be automatically
echo renamed as %patch_lst_name% and keyfile check will be disabled
echo because keyfile might have already been patched
pause
) else (
pause
if {%DELETE_FILES_FROM_PATCHDIR%}=={1} (
echo Deleting %dirtype% patchdir "!patchdir!"
rmdir /q /s "!patchdir!"
) else (
echo Not touching %dirtype% patchdir "!patchdir!"
)
rem %patch_lst_name% %keyfile_txt_name% are in patchdir and handled by DELETE_FILES_FROM_PATCHDIR
if {%CLEANUP_AFTER%}=={1} if not {%IMDEBUG%}=={1} del /q %patch_lst% %patcher_name% %unarcexe_name% patch_apply_readme.txt %0
)
goto :eof
rem ---------- actual end of the script, only subroutines are below ---------
:check_special_file
rem special files will not be added to dynamic xdelta/prepatched list
rem %1 may contain spaces if game subfolders or files have ones
if /I "%~1"=="%patch_lst_name%" exit /B 1
if /I "%~1"=="%keyfile_txt_name%" exit /B 1
if /I "%~1"=="%junked_lst_name%" exit /B 1
if /I "%~1"=="%prepatch_cmd_name%" exit /B 1
if /I "%~1"=="%patcher_name%" exit /B 1
if /I "%~1"=="%unarcexe_name%" exit /B 1
if /I "%~1"=="%freespace_req_name%" exit /B 1
if /I "%~1"=="%md5exe_name%" exit /B 1
if /I "%~1"=="%sha1exe_name%" exit /B 1
exit /B 0
goto :eof
:gen_patch_lst
if {%IMDEBUG%}=={1} echo gen_patch_lst started 1>&2
rem Subroutine to print list of files in %patchdir% with relative paths
for /f "tokens=*" %%A in ("%patchdir%") do (
set path_to_patchdir=%%~fA\
)
rem if {%IMDEBUG%}=={1} echo gen_patch_lst - path_to_patchdir is %path_to_patchdir% 1>&2
rem if {%IMDEBUG%}=={1} echo command is dir /b /s /a-d "%patchdir%" 1>&2
rem counting patchdir prefix length
call :stringlength "%path_to_patchdir%" prefix_length
rem if {%IMDEBUG%}=={1} echo gen_patch_lst - PatchDir path prefix %path_to_patchdir% length is: %prefix_length% 1>&2
for /f "tokens=*" %%A in ('dir /b /s /a-d "%patchdir%"') do (
set fullfilename=%%A
set filename=!fullfilename:~%prefix_length%!
rem if {%IMDEBUG%}=={1} echo fullfilename is !fullfilename!, filename is !filename! 1>&2
call :check_special_file "!filename!"
IF !ERRORLEVEL! NEQ 0 (
if {%IMDEBUG%}=={1} echo Skipping special file %%A 1>&2
) else (
echo !filename!
)
)
goto :eof
:countlines
rem Subroutine to calculate number of lines in 1st argument file
rem Result will be assigned to variable NAME specified as 2nd argument
set /a %2=0
for /f "usebackq tokens=*" %%A in (%1) do set /a %2+=1
goto :eof
:stringlength
rem Subroutine to calculate length of string in 1st argument
rem Result will be assigned to variable NAME specified as 2nd argument
set tmpvar=%~1
set /a %2=0
:loop
if defined tmpvar (
rem Action Syntax Example Output (for "BatchFile")
rem First n characters %var:~0,n% %var:~0,5% Batch
rem Skip first n, get rest %var:~n% %var:~5% File
rem Last n characters %var:~-n% %var:~-4% File
rem All but last n %var:~0,-n% %var:~0,-4% Batch
set "tmpvar=%tmpvar:~1%"
set /a %2+=1
goto loop
)
goto :eof
:get_max
rem Subroutine to determine maximum of 1st and 2nd arguments
rem Result will be assigned to variable NAME specified as 3rd argument
set "get_maxpad1= %~1"
set "get_maxstr1=%get_maxpad1:~-20%"
set "get_maxpad2= %~2"
set "get_maxstr2=%get_maxpad2:~-20%"
if {%IMDEBUG%}=={1} echo str1 is %get_maxstr1%, str2 is %get_maxstr2%
if "%get_maxstr1%" GTR "%get_maxstr2%" (
set %3=%~1
) else (
set %3=%~2
)
goto :eof
:exiting
pause
Changelog
v2.2 Major WINE support. Correctly creates dynamic patchlist under WINE.
Uses md5sum.exe, sha1sum.exe for hash check under WINE because it doesn't have feature-complete certutil. Executes prepatch script only if there's something to patch,
unpacking ExternalDataFolder only if something was actually patched
v2.1a free space figure calc as max between single biggest file/all patched files and total added files. get_max subroutine.
v2.1 look for ExternalData beside custom PatchData
v2.0a improved DYNAMIC_PATCH_LST logic, set to 0 if explicit _patch.lst found
v2.0 supports reading freespace requirement file _freespace_req.txt in PatchData
CSV with columns
1 2 3 (opt) 4 (opt)
mod_size_max mod_size_total junked_size_total added_size_total
Can run multiple patchers, deleting temporary dynamic %RANDOM%_patch.lst from %TEMP%
v1.9 using smaller unarc.exe instead of bigger arc.exe as unpacker for ExternalData
%CLEANUP_AFTER% var to delete PatchData and script itself after successful patching.
v1.9a supports explicit _patch.lst inside PatchData to safeguard against leftover xdeltas
which otherwise would be included to dynamic patch list, rejected and reported as errors
v1.8 supports arc.exe inside patchdir and deleting folders in junked list
1.8a handles spaces in PatchData subpaths and filesnames
v1.7 Dynamically generates _patch.lst from patchdir.
Supports _keyfile.txt, _junked.lst, _prepatch.cmd inside patchdir.
v1.7a supports patcher exe inside patchdir
v1.6 external _prepatch.cmd and _keyfile.txt, keyfilealgo variable is assigned automatically
v1.5 supports keyfile hash check - specify keyfile, keyfilehash, keyfilealgo variables below
1.5a calculates patchext length
v1.4 supports _junked.lst which can be made from [JUNKED] section of ISXPM checker.ini
v1.3 supports postpatch arc extraction of ExternalData if it is copied into game folder
v1.2 supports custom patchdir specified as 1st parameter on the commandline and catches patcher errors
v1.1 supports overwriting source files for saving space and prepatched files in _patch folder
No comments:
Post a Comment