├── resource.h
├── .gitmodules
├── README.md
├── LICENSE
├── PSFExtractor-cpp.vcxproj.filters
├── PSFExtractor.sln
├── PSFExtractor.rc
├── PSFExtractor-cpp.vcxproj
├── .gitignore
└── src
└── PSFExtractor.cpp
/resource.h:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Secant1006/PSFExtractor/HEAD/resource.h
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "libs/pugixml"]
2 | path = libs/pugixml
3 | url = https://github.com/zeux/pugixml.git
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PSFExtractor
2 | Extract patch storage file for Windows 2000+.
3 |
4 | Usage: PSFExtractor.exe <CABFile>
5 |
6 | PSFExtractor.exe -v[N] <PSFFile> <description file> <destination>
7 |
8 | You need to put CAB file alongside with its corresponding PSF file. After that you'll get a folder which contains extracted full update. You can either use it with dism /online /add-package directly or repack the files inside the folder into a CAB file.
9 |
10 | The tool requires Windows 2000 or above.
11 |
12 | Support any PSF file with PSM/XML description file.
13 |
14 | A Chinese version of usage can be found on https://blog.betaworld.cn/archives/5/.
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Secant1006
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PSFExtractor-cpp.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | 源文件
20 |
21 |
22 | 源文件
23 |
24 |
25 |
26 |
27 | 头文件
28 |
29 |
30 |
31 |
32 | 资源文件
33 |
34 |
35 |
--------------------------------------------------------------------------------
/PSFExtractor.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32804.467
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PSFExtractor-cpp", "PSFExtractor-cpp.vcxproj", "{FB0C00CB-7DDC-41AE-BDCE-DCB870C2D4C3}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x64 = Debug|x64
11 | Debug|x86 = Debug|x86
12 | Release|x64 = Release|x64
13 | Release|x86 = Release|x86
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {FB0C00CB-7DDC-41AE-BDCE-DCB870C2D4C3}.Debug|x64.ActiveCfg = Debug|x64
17 | {FB0C00CB-7DDC-41AE-BDCE-DCB870C2D4C3}.Debug|x64.Build.0 = Debug|x64
18 | {FB0C00CB-7DDC-41AE-BDCE-DCB870C2D4C3}.Debug|x86.ActiveCfg = Debug|Win32
19 | {FB0C00CB-7DDC-41AE-BDCE-DCB870C2D4C3}.Debug|x86.Build.0 = Debug|Win32
20 | {FB0C00CB-7DDC-41AE-BDCE-DCB870C2D4C3}.Release|x64.ActiveCfg = Release|x64
21 | {FB0C00CB-7DDC-41AE-BDCE-DCB870C2D4C3}.Release|x64.Build.0 = Release|x64
22 | {FB0C00CB-7DDC-41AE-BDCE-DCB870C2D4C3}.Release|x86.ActiveCfg = Release|Win32
23 | {FB0C00CB-7DDC-41AE-BDCE-DCB870C2D4C3}.Release|x86.Build.0 = Release|Win32
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {B11AE719-C2F4-4E64-A9C2-01A71E7B52C2}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/PSFExtractor.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #include "resource.h"
4 |
5 | #define APSTUDIO_READONLY_SYMBOLS
6 | /////////////////////////////////////////////////////////////////////////////
7 | //
8 | // Generated from the TEXTINCLUDE 2 resource.
9 | //
10 | #include "winres.h"
11 |
12 | /////////////////////////////////////////////////////////////////////////////
13 | #undef APSTUDIO_READONLY_SYMBOLS
14 |
15 | /////////////////////////////////////////////////////////////////////////////
16 | // resources
17 |
18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
19 |
20 | #ifdef APSTUDIO_INVOKED
21 | /////////////////////////////////////////////////////////////////////////////
22 | //
23 | // TEXTINCLUDE
24 | //
25 |
26 | 1 TEXTINCLUDE
27 | BEGIN
28 | "resource.h\0"
29 | END
30 |
31 | 2 TEXTINCLUDE
32 | BEGIN
33 | "#include ""winres.h""\r\n"
34 | "\0"
35 | END
36 |
37 | 3 TEXTINCLUDE
38 | BEGIN
39 | "\r\n"
40 | "\0"
41 | END
42 |
43 | #endif // APSTUDIO_INVOKED
44 |
45 |
46 | /////////////////////////////////////////////////////////////////////////////
47 | //
48 | // Version
49 | //
50 |
51 | VS_VERSION_INFO VERSIONINFO
52 | FILEVERSION 3,0,7,0
53 | PRODUCTVERSION 3,0,7,0
54 | FILEFLAGSMASK 0x3fL
55 | #ifdef _DEBUG
56 | FILEFLAGS 0x1L
57 | #else
58 | FILEFLAGS 0x0L
59 | #endif
60 | FILEOS 0x40004L
61 | FILETYPE 0x1L
62 | FILESUBTYPE 0x0L
63 | BEGIN
64 | BLOCK "StringFileInfo"
65 | BEGIN
66 | BLOCK "000004b0"
67 | BEGIN
68 | VALUE "CompanyName", "BetaWorld"
69 | VALUE "FileDescription", "PSFExtractor"
70 | VALUE "FileVersion", "3.0.7.0"
71 | VALUE "InternalName", "PSFExtractor.exe"
72 | VALUE "LegalCopyright", "Copyright (C) 2012-2022 BetaWorld"
73 | VALUE "OriginalFilename", "PSFExtractor.exe"
74 | VALUE "ProductName", "PSFExtractor"
75 | VALUE "ProductVersion", "3.0.7.0"
76 | END
77 | END
78 | BLOCK "VarFileInfo"
79 | BEGIN
80 | VALUE "Translation", 0x0, 1200
81 | END
82 | END
83 |
84 | #endif // resources
85 | /////////////////////////////////////////////////////////////////////////////
86 |
87 |
88 |
89 | #ifndef APSTUDIO_INVOKED
90 | /////////////////////////////////////////////////////////////////////////////
91 | //
92 | // Generated from the TEXTINCLUDE 3 resource.
93 | //
94 |
95 |
96 | /////////////////////////////////////////////////////////////////////////////
97 | #endif // not APSTUDIO_INVOKED
98 |
99 | #ifdef _M_AMD64
100 | IDR_DLL1 RCDATA "libs/PatchEngine/x64/mspatcha.dll"
101 | IDR_DLL2 RCDATA "libs/PatchEngine/x64/msdelta.dll"
102 | #elif defined _M_IX86
103 | IDR_DLL1 RCDATA "libs/PatchEngine/x86/mspatcha.dll"
104 | IDR_DLL2 RCDATA "libs/PatchEngine/x86/msdelta.dll"
105 | #endif
106 |
--------------------------------------------------------------------------------
/PSFExtractor-cpp.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | 16.0
23 | Win32Proj
24 | {fb0c00cb-7ddc-41ae-bdce-dcb870c2d4c3}
25 | PSFExtractorcpp
26 | 10.0
27 | PSFExtractor
28 |
29 |
30 |
31 | Application
32 | true
33 | v143
34 | Unicode
35 |
36 |
37 | Application
38 | false
39 | v143
40 | true
41 | Unicode
42 |
43 |
44 | Application
45 | true
46 | v143
47 | Unicode
48 |
49 |
50 | Application
51 | false
52 | v143
53 | true
54 | Unicode
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | Level3
77 | false
78 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
79 | true
80 |
81 |
82 | Console
83 | true
84 | Cabinet.Lib;%(AdditionalDependencies)
85 |
86 |
87 | _M_IX86;%(PreprocessorDefinitions)
88 |
89 |
90 |
91 |
92 | Level3
93 | true
94 | true
95 | true
96 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
97 | true
98 | MultiThreaded
99 |
100 |
101 | Console
102 | true
103 | true
104 | false
105 | Cabinet.Lib;%(AdditionalDependencies)
106 |
107 |
108 | _M_IX86;%(PreprocessorDefinitions)
109 |
110 |
111 |
112 |
113 | Level3
114 | false
115 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
116 | true
117 | Default
118 |
119 |
120 | Console
121 | true
122 | %(AddModuleNamesToAssembly)
123 | Cabinet.Lib;%(AdditionalDependencies)
124 |
125 |
126 | _M_AMD64;%(PreprocessorDefinitions)
127 |
128 |
129 |
130 |
131 | Level3
132 | true
133 | true
134 | true
135 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
136 | true
137 | MultiThreaded
138 |
139 |
140 | Console
141 | true
142 | true
143 | false
144 | Cabinet.Lib;%(AdditionalDependencies)
145 |
146 |
147 | _M_AMD64;%(PreprocessorDefinitions)
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.tlog
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
298 | *.vbp
299 |
300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
301 | *.dsw
302 | *.dsp
303 |
304 | # Visual Studio 6 technical files
305 | *.ncb
306 | *.aps
307 |
308 | # Visual Studio LightSwitch build output
309 | **/*.HTMLClient/GeneratedArtifacts
310 | **/*.DesktopClient/GeneratedArtifacts
311 | **/*.DesktopClient/ModelManifest.xml
312 | **/*.Server/GeneratedArtifacts
313 | **/*.Server/ModelManifest.xml
314 | _Pvt_Extensions
315 |
316 | # Paket dependency manager
317 | .paket/paket.exe
318 | paket-files/
319 |
320 | # FAKE - F# Make
321 | .fake/
322 |
323 | # CodeRush personal settings
324 | .cr/personal
325 |
326 | # Python Tools for Visual Studio (PTVS)
327 | __pycache__/
328 | *.pyc
329 |
330 | # Cake - Uncomment if you are using it
331 | # tools/**
332 | # !tools/packages.config
333 |
334 | # Tabs Studio
335 | *.tss
336 |
337 | # Telerik's JustMock configuration file
338 | *.jmconfig
339 |
340 | # BizTalk build output
341 | *.btp.cs
342 | *.btm.cs
343 | *.odx.cs
344 | *.xsd.cs
345 |
346 | # OpenCover UI analysis results
347 | OpenCover/
348 |
349 | # Azure Stream Analytics local run output
350 | ASALocalRun/
351 |
352 | # MSBuild Binary and Structured Log
353 | *.binlog
354 |
355 | # NVidia Nsight GPU debugger configuration file
356 | *.nvuser
357 |
358 | # MFractors (Xamarin productivity tool) working folder
359 | .mfractor/
360 |
361 | # Local History for Visual Studio
362 | .localhistory/
363 |
364 | # Visual Studio History (VSHistory) files
365 | .vshistory/
366 |
367 | # BeatPulse healthcheck temp database
368 | healthchecksdb
369 |
370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
371 | MigrationBackup/
372 |
373 | # Ionide (cross platform F# VS Code tools) working folder
374 | .ionide/
375 |
376 | # Fody - auto-generated XML schema
377 | FodyWeavers.xsd
378 |
379 | # VS Code files for those working on multiple tools
380 | .vscode/*
381 | !.vscode/settings.json
382 | !.vscode/tasks.json
383 | !.vscode/launch.json
384 | !.vscode/extensions.json
385 | *.code-workspace
386 |
387 | # Local History for Visual Studio Code
388 | .history/
389 |
390 | # Windows Installer files from build outputs
391 | *.cab
392 | *.msi
393 | *.msix
394 | *.msm
395 | *.msp
396 |
397 | # JetBrains Rider
398 | *.sln.iml
399 |
--------------------------------------------------------------------------------
/src/PSFExtractor.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include "../resource.h"
12 | #include "../libs/pugixml/src/pugixml.hpp"
13 |
14 | #define MAX_PATH_W 32767
15 | #define ProgressBarWidth 58
16 |
17 | using namespace std;
18 |
19 | const char PathSeparator = '\\';
20 | const char WrongPathSeparator = '/';
21 |
22 | // Strings
23 | const char* ProgramTitle = "PSFExtractor v3.07 (Sep 7 2022) by th1r5bvn23\nhttps://www.betaworld.cn/\n\n";
24 | const char* HelpInformation = "Usage:\n PSFExtractor.exe \n PSFExtractor.exe -v[N] \n\n Auto detect CAB file and corresponding PSF file which\n are in the same location with the same name.\n -v[N] Specify PSFX version. N = 1 | 2. PSFX v1 is for Windows\n 2000 to Server 2003, while PSFX v2 is for Windows Vista\n and above.\n Path to PSF payload file.\n Path to description file. For PSFX v1, the description\n file has an extension \".psm\". For PSFX v2, a standard\n XML document is used.\n Path to output folder. If the folder doesn\'t exist, it\n will be created automatically.\n";
25 |
26 | // Global settings
27 | int PSFXVersion;
28 | char* DescriptionFileName;
29 | char* PayloadFileName;
30 | char* TargetDirectoryName;
31 | bool MSPatchALoadFlag = false;
32 | bool MSDeltaLoadFlag = false;
33 |
34 | // Utility functions
35 | bool FileExists(const WCHAR* FileName) {
36 | return (GetFileAttributesW(FileName) != INVALID_FILE_ATTRIBUTES);
37 | }
38 |
39 | int CurrentPosition, CurrentProgress;
40 | HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
41 | CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo = { 0 };
42 |
43 | void CreateProgressBar() {
44 | CurrentPosition = 0;
45 | cout << '[';
46 | for (int i = 0; i < ProgressBarWidth; i++) {
47 | cout << ' ';
48 | }
49 | cout << "] 0%";
50 | cout.flush();
51 | GetConsoleScreenBufferInfo(hConsoleOutput, &ConsoleScreenBufferInfo);
52 | }
53 |
54 | void UpdateProgressBar(float progress) {
55 | COORD dwCursorPosition = ConsoleScreenBufferInfo.dwCursorPosition;
56 | bool FlushFlag = false;
57 | int pos = (int)(progress * (float)ProgressBarWidth);
58 | if (CurrentPosition != pos) {
59 | dwCursorPosition.X = CurrentPosition + 1;
60 | SetConsoleCursorPosition(hConsoleOutput, dwCursorPosition);
61 | for (int i = CurrentPosition; i < ProgressBarWidth; i++) {
62 | if (i < pos) {
63 | cout << '=';
64 | }
65 | else if (i == pos) {
66 | cout << '>';
67 | break;
68 | }
69 | }
70 | CurrentPosition = pos;
71 | FlushFlag = true;
72 | }
73 | if (CurrentProgress != (int)(progress * 100)) {
74 | dwCursorPosition.X = ProgressBarWidth + 3;
75 | SetConsoleCursorPosition(hConsoleOutput, dwCursorPosition);
76 | CurrentProgress = (int)(progress * 100);
77 | cout << CurrentProgress << '%';
78 | FlushFlag = true;
79 | }
80 | if (FlushFlag) {
81 | cout.flush();
82 | FlushFlag = false;
83 | }
84 | }
85 |
86 | void FinishProgressBar() {
87 | CurrentPosition = 0;
88 | cout << endl;
89 | }
90 |
91 | // Cabinet API functions
92 | FNALLOC(FDIAlloc) {
93 | return HeapAlloc(GetProcessHeap(), 0, cb);
94 | }
95 |
96 | FNFREE(FDIFree) {
97 | HeapFree(GetProcessHeap(), 0, pv);
98 | }
99 |
100 | void CreateDirectoryRecursive(const WCHAR* name) {
101 | WCHAR* path, * p;
102 | path = (WCHAR*)FDIAlloc((lstrlenW(name) + 1) * sizeof(WCHAR));
103 | lstrcpyW(path, name);
104 | p = wcschr(path, '\\');
105 | while (p != NULL) {
106 | *p = 0;
107 | CreateDirectoryW(path, NULL);
108 | *p = '\\';
109 | p = wcschr(p + 1, '\\');
110 | }
111 | FDIFree(path);
112 | }
113 |
114 | static WCHAR* strdupAtoW(UINT cp, const char* str) {
115 | WCHAR* res = NULL;
116 | if (str) {
117 | DWORD len = MultiByteToWideChar(cp, 0, str, -1, NULL, 0) + 2;
118 | if ((res = (WCHAR*)FDIAlloc(sizeof(WCHAR) * len))) {
119 | MultiByteToWideChar(cp, 0, str, -1, res, len);
120 | }
121 | }
122 | return res;
123 | }
124 |
125 | static char* strdupWtoA(UINT cp, const WCHAR* str) {
126 | char* res = NULL;
127 | if (str) {
128 | DWORD len = WideCharToMultiByte(cp, 0, str, -1, NULL, 0, NULL, NULL) + 2;
129 | if ((res = (char*)FDIAlloc(sizeof(char) * len))) {
130 | WideCharToMultiByte(cp, 0, str, -1, res, len, NULL, NULL);
131 | }
132 | }
133 | return res;
134 | }
135 |
136 | FNOPEN(FDIOpen) {
137 | HANDLE hf = NULL;
138 | DWORD dwDesiredAccess = 0;
139 | DWORD dwCreationDisposition = 0;
140 | UNREFERENCED_PARAMETER(pmode);
141 | if (oflag & _O_RDWR) {
142 | dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
143 | }
144 | else if (oflag & _O_WRONLY) {
145 | dwDesiredAccess = GENERIC_WRITE;
146 | }
147 | else {
148 | dwDesiredAccess = GENERIC_READ;
149 | }
150 | if (oflag & _O_CREAT) {
151 | dwCreationDisposition = CREATE_ALWAYS;
152 | }
153 | else {
154 | dwCreationDisposition = OPEN_EXISTING;
155 | }
156 | WCHAR* pszFileW = strdupAtoW(CP_ACP, pszFile);
157 | hf = CreateFileW(pszFileW, dwDesiredAccess, FILE_SHARE_READ, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
158 | FDIFree(pszFileW);
159 | return (INT_PTR)hf;
160 | }
161 |
162 | FNREAD(FDIRead) {
163 | DWORD num_read;
164 | if (!ReadFile((HANDLE)hf, pv, cb, &num_read, NULL)) {
165 | return -1;
166 | }
167 | return num_read;
168 | }
169 |
170 | FNWRITE(FDIWrite) {
171 | DWORD written;
172 | if (!WriteFile((HANDLE)hf, pv, cb, &written, NULL)) {
173 | return -1;
174 | }
175 | return written;
176 | }
177 |
178 | FNCLOSE(FDIClose) {
179 | if (!CloseHandle((HANDLE)hf)) {
180 | return -1;
181 | }
182 | return 0;
183 | }
184 |
185 | FNSEEK(FDISeek) {
186 | DWORD res;
187 | res = SetFilePointer((HANDLE)hf, dist, NULL, seektype);
188 | if (res == INVALID_SET_FILE_POINTER && GetLastError()) {
189 | return -1;
190 | }
191 | return res;
192 | }
193 |
194 | int CurrentFiles, TotalFiles;
195 |
196 | FNFDINOTIFY(FDINotify) {
197 | WCHAR* nameW = NULL, * file = NULL, * TargetDirectoryNameW = NULL, * TargetFilePath = NULL;
198 | FILETIME FileTime = { 0 };
199 | HANDLE hf = NULL;
200 | switch (fdint) {
201 | case fdintCABINET_INFO:
202 | return 0;
203 | case fdintCOPY_FILE:
204 | nameW = strdupAtoW((pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1);
205 | file = nameW;
206 | while (*file == '\\') {
207 | file++;
208 | }
209 | TargetDirectoryNameW = strdupAtoW(CP_ACP, TargetDirectoryName);
210 | TargetFilePath = (WCHAR*)FDIAlloc((lstrlenW(TargetDirectoryNameW) + lstrlenW(file) + 1) * sizeof(WCHAR));
211 | wcscpy_s(TargetFilePath, (lstrlenW(TargetDirectoryNameW) + lstrlenW(file) + 1), TargetDirectoryNameW);
212 | wcscat_s(TargetFilePath, (lstrlenW(TargetDirectoryNameW) + lstrlenW(file) + 1), file);
213 | CreateDirectoryRecursive(TargetFilePath);
214 | hf = CreateFileW(TargetFilePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
215 | FDIFree(nameW);
216 | FDIFree(TargetDirectoryNameW);
217 | FDIFree(TargetFilePath);
218 | return (INT_PTR)hf;
219 | case fdintCLOSE_FILE_INFO:
220 | DosDateTimeToFileTime(pfdin->date, pfdin->time, &FileTime);
221 | SetFileTime((HANDLE)(pfdin->hf), NULL, NULL, &FileTime);
222 | FDIClose(pfdin->hf);
223 | CurrentFiles++;
224 | UpdateProgressBar((float)CurrentFiles / (float)TotalFiles);
225 | return TRUE;
226 | case fdintPARTIAL_FILE:
227 | return -1;
228 | case fdintENUMERATE:
229 | return 0;
230 | case fdintNEXT_CABINET:
231 | return -1;
232 | default:
233 | return 0;
234 | }
235 | }
236 |
237 | bool ExtractCABFile(char* CABFilePart, char* CABPathPart) {
238 | // Create FDI context
239 | ERF* FDIErf = (ERF*)FDIAlloc(sizeof(ERF));
240 | HFDI FDIContext = FDICreate(FDIAlloc, FDIFree, FDIOpen, FDIRead, FDIWrite, FDIClose, FDISeek, cpuUNKNOWN, FDIErf);
241 | if (FDIContext == NULL) {
242 | return false;
243 | }
244 |
245 | // Get file number of CAB file
246 | char* CABFileFullName = (char*)FDIAlloc(sizeof(char) * MAX_PATH_W);
247 | strcpy_s(CABFileFullName, MAX_PATH_W, CABPathPart);
248 | strcat_s(CABFileFullName, MAX_PATH_W, CABFilePart);
249 | WCHAR* CABFileFullNameW = strdupAtoW(CP_ACP, CABFileFullName);
250 | FDIFree(CABFileFullName);
251 | HANDLE hf = CreateFileW(CABFileFullNameW, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
252 | FDIFree(CABFileFullNameW);
253 | FDICABINETINFO* CabinetInfo = (FDICABINETINFO*)FDIAlloc(sizeof(FDICABINETINFO));
254 | if (!FDIIsCabinet(FDIContext, (INT_PTR)hf, CabinetInfo)) {
255 | return false;
256 | }
257 | if (CabinetInfo->hasprev || CabinetInfo->hasnext) {
258 | return false;
259 | }
260 | TotalFiles = CabinetInfo->cFiles;
261 | cout << TotalFiles << " files..." << endl;
262 | FDIFree(CabinetInfo);
263 | CloseHandle(hf);
264 |
265 | // Extract file
266 | CurrentFiles = 0;
267 | CreateProgressBar();
268 | if (!FDICopy(FDIContext, CABFilePart, CABPathPart, 0, FDINotify, NULL, NULL)) {
269 | return false;
270 | }
271 | FinishProgressBar();
272 |
273 | // Destroy FDI context
274 | FDIDestroy(FDIContext);
275 |
276 | // Cleanup and return
277 | FDIFree(FDIErf);
278 | return true;
279 | }
280 |
281 | // Parsing data structures
282 | enum sourceType { RAW, PA19, PA30 };
283 |
284 | typedef struct {
285 | WCHAR* name;
286 | FILETIME time;
287 | sourceType type;
288 | ULARGE_INTEGER offset;
289 | DWORD length;
290 | } DeltaFile;
291 |
292 | vector DeltaFileList;
293 |
294 | // Description file parsing functions
295 | bool ParseDescriptionFileV1() {
296 | WCHAR* DescriptionFileNameW = strdupAtoW(CP_ACP, DescriptionFileName);
297 | HANDLE hf = CreateFileW(DescriptionFileNameW, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
298 | FDIFree(DescriptionFileNameW);
299 | if (hf == INVALID_HANDLE_VALUE) {
300 | return false;
301 | }
302 | LARGE_INTEGER FileSize;
303 | GetFileSizeEx(hf, &FileSize);
304 | char* FileBuffer = (char*)FDIAlloc(FileSize.QuadPart);
305 | DWORD temp = 0;
306 | if (!ReadFile(hf, FileBuffer, FileSize.QuadPart, &temp, NULL)) {
307 | return false;
308 | }
309 | stringstream s(FileBuffer);
310 | char* LineBuffer = (char*)FDIAlloc(MAX_PATH + 3);
311 | char* FileNameBuffer = (char*)FDIAlloc(MAX_PATH + 3);
312 | bool isReadingAFile = false;
313 | WCHAR* FileName = NULL;
314 | sourceType type;
315 | while (true) {
316 | s.getline(LineBuffer, MAX_PATH + 3);
317 | if (s.fail()) {
318 | if (s.eof()) {
319 | break;
320 | }
321 | else {
322 | return false;
323 | }
324 | }
325 | if (LineBuffer[0] == '[') {
326 | FileName = strdupAtoW(CP_ACP, &LineBuffer[1]);
327 | FileName[lstrlenW(FileName) - 2] = '\0';
328 | isReadingAFile = true;
329 | continue;
330 | }
331 | if (isReadingAFile) {
332 | char* p, * q;
333 | if (p = strstr(LineBuffer, "p0=")) {
334 | type = PA19;
335 | MSPatchALoadFlag = true;
336 | p += 3;
337 | }
338 | else if (p = strstr(LineBuffer, "full=")) {
339 | type = RAW;
340 | p += 5;
341 | }
342 | else {
343 | return false;
344 | }
345 | unsigned long long offset = _strtoui64(p, &q, 16);
346 | q++;
347 | unsigned int length = strtoul(q, &p, 16);
348 | ULARGE_INTEGER time_ularge = { 0 };
349 | FILETIME time = { time_ularge.LowPart, time_ularge.HighPart };
350 | ULARGE_INTEGER offset_ularge = { 0 };
351 | offset_ularge.QuadPart = offset;
352 | DeltaFile file;
353 | if (FileName) {
354 | file.name = FileName;
355 | file.time = time;
356 | file.type = type;
357 | file.offset = offset_ularge;
358 | file.length = (DWORD)length;
359 | }
360 | else {
361 | return false;
362 | }
363 | DeltaFileList.push_back(file);
364 | isReadingAFile = false;
365 | }
366 | }
367 | FDIFree(FileNameBuffer);
368 | FDIFree(LineBuffer);
369 | FDIFree(FileBuffer);
370 | CloseHandle(hf);
371 | return true;
372 | }
373 |
374 | bool ParseDescriptionFileV2() {
375 | using namespace pugi;
376 | xml_document XMLDocument;
377 | if (!XMLDocument.load_file(DescriptionFileName)) {
378 | return false;
379 | }
380 | WCHAR* FileName = NULL;
381 | xml_node FilesNode = XMLDocument.child("Container").child("Files");
382 | xml_node_iterator file;
383 | for (file = FilesNode.begin(); file != FilesNode.end(); file++) {
384 | FileName = strdupAtoW(CP_ACP, file->attribute("name").value());
385 | ULARGE_INTEGER time_ularge = { 0 };
386 | time_ularge.QuadPart = file->attribute("time").as_ullong();
387 | FILETIME FileTime = { time_ularge.LowPart, time_ularge.HighPart };
388 | xml_node SourceNode = file->child("Delta").child("Source");
389 | const char* type = SourceNode.attribute("type").value();
390 | sourceType FileType = RAW;
391 | if (!_strcmpi(type, "RAW")) {
392 | FileType = RAW;
393 | }
394 | else if (!_strcmpi(type, "PA19")) {
395 | FileType = PA19;
396 | MSPatchALoadFlag = true;
397 | }
398 | else if (!_strcmpi(type, "PA30")) {
399 | FileType = PA30;
400 | MSDeltaLoadFlag = true;
401 | }
402 | ULARGE_INTEGER FileOffset = { 0 };
403 | FileOffset.QuadPart = SourceNode.attribute("offset").as_ullong();
404 | unsigned long FileLength = SourceNode.attribute("length").as_uint();
405 | DeltaFile file = { FileName, FileTime, FileType, FileOffset, FileLength };
406 | DeltaFileList.push_back(file);
407 | }
408 | return true;
409 | }
410 |
411 | typedef BOOL(CALLBACK PATCH_PROGRESS_CALLBACK)(PVOID CallbackContext, ULONG CurrentPosition, ULONG MaximumPosition);
412 | typedef PATCH_PROGRESS_CALLBACK* PPATCH_PROGRESS_CALLBACK;
413 | typedef BOOL(WINAPI* ApplyPatchToFileByBuffersFunc)(PBYTE PatchFileMapped, ULONG PatchFileSize, PBYTE OldFileMapped, ULONG OldFileSize, PBYTE* NewFileBuffer, ULONG NewFileBufferSize, ULONG* NewFileActualSize, FILETIME* NewFileTime, ULONG ApplyOptionFlags, PPATCH_PROGRESS_CALLBACK ProgressCallback, PVOID CallbackContext);
414 |
415 | typedef struct _DELTA_INPUT {
416 | union {
417 | LPCVOID lpcStart;
418 | LPVOID lpStart;
419 | };
420 | SIZE_T uSize;
421 | BOOL Editable;
422 | } DELTA_INPUT;
423 | typedef struct _DELTA_OUTPUT {
424 | LPVOID lpStart;
425 | SIZE_T uSize;
426 | } DELTA_OUTPUT;
427 | typedef DELTA_OUTPUT* LPDELTA_OUTPUT;
428 | typedef __int64 DELTA_FLAG_TYPE;
429 | typedef BOOL(WINAPI* ApplyDeltaBFunc)(DELTA_FLAG_TYPE ApplyFlags, DELTA_INPUT Source, DELTA_INPUT Delta, LPDELTA_OUTPUT lpTarget);
430 | typedef BOOL(WINAPI* DeltaFreeFunc)(LPVOID lpMemory);
431 |
432 | bool LoadMSPatchA(ApplyPatchToFileByBuffersFunc* ApplyPatchToFileByBuffersAddr) {
433 | HMODULE MSPatchA = NULL;
434 | MSPatchA = LoadLibraryW(L"mspatcha.dll");
435 | if (MSPatchA) {
436 | *ApplyPatchToFileByBuffersAddr = (ApplyPatchToFileByBuffersFunc)GetProcAddress(MSPatchA, "ApplyPatchToFileByBuffers");
437 | if (*ApplyPatchToFileByBuffersAddr) {
438 | return true;
439 | }
440 | }
441 | HRSRC ResourceInformation = NULL;
442 | ResourceInformation = FindResourceW(NULL, MAKEINTRESOURCE(IDR_DLL1), RT_RCDATA);
443 | if (!ResourceInformation) {
444 | return false;
445 | }
446 | HGLOBAL hResourcesData = LoadResource(NULL, ResourceInformation);
447 | if (!hResourcesData) {
448 | return false;
449 | }
450 | LPVOID ResourcePointer = NULL;
451 | ResourcePointer = LockResource(hResourcesData);
452 | if (!ResourcePointer) {
453 | return false;
454 | }
455 | WCHAR MSPatchA_Temp[MAX_PATH + 2];
456 | if (!GetTempPathW(MAX_PATH + 2, MSPatchA_Temp)) {
457 | return false;
458 | }
459 | lstrcatW(MSPatchA_Temp, L"mspatcha.dll");
460 | HANDLE hf = CreateFileW(MSPatchA_Temp, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
461 | if (hf == INVALID_HANDLE_VALUE) {
462 | return false;
463 | }
464 | DWORD temp;
465 | if (!WriteFile(hf, ResourcePointer, SizeofResource(NULL, ResourceInformation), &temp, NULL)) {
466 | return false;
467 | }
468 | else {
469 | if (!CloseHandle(hf)) {
470 | return false;
471 | }
472 | }
473 | MSPatchA = LoadLibraryW(MSPatchA_Temp);
474 | if (MSPatchA) {
475 | *ApplyPatchToFileByBuffersAddr = (ApplyPatchToFileByBuffersFunc)GetProcAddress(MSPatchA, "ApplyPatchToFileByBuffers");
476 | if (*ApplyPatchToFileByBuffersAddr) {
477 | return true;
478 | }
479 | }
480 | return false;
481 | }
482 |
483 | bool LoadMSDelta(ApplyDeltaBFunc* ApplyDeltaBAddr, DeltaFreeFunc* DeltaFreeAddr) {
484 | HMODULE MSDelta = NULL;
485 | MSDelta = LoadLibraryW(L"msdelta.dll");
486 | if (MSDelta) {
487 | *ApplyDeltaBAddr = (ApplyDeltaBFunc)GetProcAddress(MSDelta, "ApplyDeltaB");
488 | *DeltaFreeAddr = (DeltaFreeFunc)GetProcAddress(MSDelta, "DeltaFree");
489 | if (*ApplyDeltaBAddr && *DeltaFreeAddr) {
490 | return true;
491 | }
492 | }
493 | HRSRC ResourceInformation = NULL;
494 | ResourceInformation = FindResourceW(NULL, MAKEINTRESOURCE(IDR_DLL2), RT_RCDATA);
495 | if (!ResourceInformation) {
496 | return false;
497 | }
498 | HGLOBAL hResourcesData = LoadResource(NULL, ResourceInformation);
499 | if (!hResourcesData) {
500 | return false;
501 | }
502 | LPVOID ResourcePointer = NULL;
503 | ResourcePointer = LockResource(hResourcesData);
504 | if (!ResourcePointer) {
505 | return false;
506 | }
507 | WCHAR MSDelta_Temp[MAX_PATH + 2];
508 | if (!GetTempPathW(MAX_PATH + 2, MSDelta_Temp)) {
509 | return false;
510 | }
511 | lstrcatW(MSDelta_Temp, L"msdelta.dll");
512 | HANDLE hf = CreateFileW(MSDelta_Temp, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
513 | if (hf == INVALID_HANDLE_VALUE) {
514 | return false;
515 | }
516 | DWORD temp;
517 | if (!WriteFile(hf, ResourcePointer, SizeofResource(NULL, ResourceInformation), &temp, NULL)) {
518 | return false;
519 | }
520 | else {
521 | if (!CloseHandle(hf)) {
522 | return false;
523 | }
524 | }
525 | MSDelta = LoadLibraryW(MSDelta_Temp);
526 | if (MSDelta) {
527 | *ApplyDeltaBAddr = (ApplyDeltaBFunc)GetProcAddress(MSDelta, "ApplyDeltaB");
528 | *DeltaFreeAddr = (DeltaFreeFunc)GetProcAddress(MSDelta, "DeltaFree");
529 | if (*ApplyDeltaBAddr && *DeltaFreeAddr) {
530 | return true;
531 | }
532 | }
533 | return false;
534 | }
535 |
536 | // Write output
537 | bool WriteOutput() {
538 | WCHAR* PayloadFileNameW = strdupAtoW(CP_ACP, PayloadFileName);
539 | HANDLE input = CreateFileW(PayloadFileNameW, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
540 | FDIFree(PayloadFileNameW);
541 | if (input == INVALID_HANDLE_VALUE) {
542 | return false;
543 | }
544 | WCHAR* file = NULL, * TargetDirectoryNameW = NULL, * TargetFilePath = NULL, * TargetFilePathWithPrefix = NULL;
545 | HANDLE output = NULL;
546 | void* buffer = NULL;
547 | DWORD temp;
548 | ApplyPatchToFileByBuffersFunc ApplyPatchToFileByBuffers = NULL;
549 | if (MSPatchALoadFlag) {
550 | if (!LoadMSPatchA(&ApplyPatchToFileByBuffers)) {
551 | return false;
552 | }
553 | }
554 | ApplyDeltaBFunc ApplyDeltaB = NULL;
555 | DeltaFreeFunc DeltaFree = NULL;
556 | if (MSDeltaLoadFlag) {
557 | if (!LoadMSDelta(&ApplyDeltaB, &DeltaFree)) {
558 | return false;
559 | }
560 | }
561 | CurrentFiles = 0;
562 | CreateProgressBar();
563 | for (vector::iterator item = DeltaFileList.begin(); item != DeltaFileList.end(); item++) {
564 | buffer = FDIAlloc(item->length);
565 | LARGE_INTEGER _offset = { 0 };
566 | _offset.QuadPart = item->offset.QuadPart;
567 | if (!SetFilePointerEx(input, _offset, NULL, FILE_BEGIN)) {
568 | return false;
569 | }
570 | if (!ReadFile(input, buffer, item->length, &temp, NULL)) {
571 | return false;
572 | }
573 | file = item->name;
574 | while (*file == '\\') {
575 | file++;
576 | }
577 | TargetDirectoryNameW = strdupAtoW(CP_ACP, TargetDirectoryName);
578 | TargetFilePath = (WCHAR*)FDIAlloc((lstrlenW(TargetDirectoryNameW) + lstrlenW(file) + 1) * sizeof(WCHAR));
579 | wcscpy_s(TargetFilePath, (lstrlenW(TargetDirectoryNameW) + lstrlenW(file) + 1), TargetDirectoryNameW);
580 | wcscat_s(TargetFilePath, (lstrlenW(TargetDirectoryNameW) + lstrlenW(file) + 1), file);
581 | CreateDirectoryRecursive(TargetFilePath);
582 | output = CreateFileW(TargetFilePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
583 | if (output == INVALID_HANDLE_VALUE || output == NULL) {
584 | return false;
585 | }
586 | FDIFree(TargetDirectoryNameW);
587 | FDIFree(TargetFilePath);
588 | if (item->type == PA19) {
589 | PBYTE PA19DeltaOutput = NULL;
590 | ULONG PA19TargetSize;
591 | if (!ApplyPatchToFileByBuffers((PBYTE)buffer, item->length, NULL, 0, &PA19DeltaOutput, 0, &PA19TargetSize, &item->time, NULL, NULL, NULL)) {
592 | return false;
593 | }
594 | if (!WriteFile(output, PA19DeltaOutput, PA19TargetSize, &temp, NULL)) {
595 | return false;
596 | }
597 | VirtualFree(PA19DeltaOutput, 0, MEM_RELEASE);
598 | }
599 | else if (item->type == PA30) {
600 | DELTA_INPUT PA30DeltaInput = { buffer, item->length, false };
601 | DELTA_INPUT PA30NullDeltaInput = { NULL, 0, false };
602 | DELTA_OUTPUT PA30DeltaOutput;
603 | if (!ApplyDeltaB(0, PA30NullDeltaInput, PA30DeltaInput, &PA30DeltaOutput)) {
604 | return false;
605 | }
606 | if (!WriteFile(output, PA30DeltaOutput.lpStart, PA30DeltaOutput.uSize, &temp, NULL)) {
607 | return false;
608 | }
609 | DeltaFree(PA30DeltaOutput.lpStart);
610 | }
611 | else {
612 | if (!WriteFile(output, buffer, item->length, &temp, NULL)) {
613 | return false;
614 | }
615 | }
616 | FDIFree(buffer);
617 | SetFileTime(output, NULL, NULL, &item->time);
618 | CloseHandle(output);
619 | CurrentFiles++;
620 | UpdateProgressBar((float)CurrentFiles / (float)TotalFiles);
621 | }
622 | CloseHandle(input);
623 | FinishProgressBar();
624 | return true;
625 | }
626 |
627 | // Main entry
628 | int wmain(int argc, WCHAR* argv[]) {
629 | cout << ProgramTitle;
630 | const WCHAR PSFXv1[] = L"-v1";
631 | const WCHAR PSFXv2[] = L"-v2";
632 | const WCHAR CABExtension[] = L".cab";
633 | const WCHAR DOSDevicePrefix[] = L"\\\\?\\";
634 |
635 | // Filter wrong path separator
636 | for (int i = 1; i < argc; i++) {
637 | for (WCHAR* p = argv[i]; *p; p++) {
638 | if (*p == '/') {
639 | *p = '\\';
640 | }
641 | }
642 | }
643 |
644 | // Parse arguments
645 | // Automatic mode
646 | if (argc == 2) {
647 | PSFXVersion = 2;
648 | WCHAR* CABFileName = argv[1];
649 |
650 | // Check CAB extension
651 | if (lstrlenW(CABFileName) >= 4) {
652 | if (lstrcmpiW(&CABFileName[lstrlenW(CABFileName) - 4], CABExtension)) {
653 | cout << "Error: Invalid CAB file name." << endl;
654 | }
655 | }
656 | else {
657 | cout << "Error: Invalid CAB file name." << endl;
658 | }
659 |
660 | // Check if CAB exists
661 | if (!FileExists(CABFileName)) {
662 | cout << "Error: CAB file does not exist." << endl;
663 | }
664 |
665 | // Get full path and split it into directory and file name
666 | WCHAR* CABFileFullName = (WCHAR*)FDIAlloc(sizeof(WCHAR) * MAX_PATH_W);
667 | WCHAR* CABFilePartPointer;
668 | char* CABFilePart;
669 | char* CABPathPart;
670 | if (!GetFullPathNameW(CABFileName, MAX_PATH_W, CABFileFullName, &CABFilePartPointer) || !CABFilePartPointer) {
671 | cout << "Error: Can't get full path of the CAB file." << endl;
672 | return 1;
673 | }
674 |
675 | // Generate payload file name
676 | PayloadFileName = strdupWtoA(CP_ACP, CABFileName);
677 | PayloadFileName[strlen(PayloadFileName) - 3] = '\0';
678 | strcat_s(PayloadFileName, strlen(PayloadFileName) + 4, "psf");
679 |
680 | // Generate target directory name
681 | WCHAR* TargetDirectoryNameW = (WCHAR*)FDIAlloc(sizeof(WCHAR) * MAX_PATH_W);
682 | wcscpy_s(TargetDirectoryNameW, MAX_PATH_W, DOSDevicePrefix);
683 | wcscat_s(TargetDirectoryNameW, MAX_PATH_W, CABFileFullName);
684 | size_t len = lstrlenW(TargetDirectoryNameW);
685 | TargetDirectoryNameW[len - 4] = '\\';
686 | TargetDirectoryNameW[len - 3] = '\0';
687 | TargetDirectoryName = strdupWtoA(CP_ACP, TargetDirectoryNameW);
688 | WCHAR* TargetDirectoryNameBuffer = (WCHAR*)FDIAlloc(sizeof(WCHAR) * MAX_PATH_W);
689 | GetFullPathNameW(TargetDirectoryNameW, MAX_PATH_W, TargetDirectoryNameBuffer, (LPWSTR*)NULL);
690 | TargetDirectoryName = strdupWtoA(CP_ACP, TargetDirectoryNameBuffer);
691 | if (TargetDirectoryName[strlen(TargetDirectoryName) - 1] != PathSeparator) {
692 | size_t len = strlen(TargetDirectoryName);
693 | TargetDirectoryName[len] = PathSeparator;
694 | TargetDirectoryName[len + 1] = '\0';
695 | }
696 | CreateDirectoryW(TargetDirectoryNameW, NULL);
697 | FDIFree(TargetDirectoryNameW);
698 |
699 | // Generate description file name
700 | WIN32_FIND_DATAA findData;
701 | HANDLE hFind = FindFirstFileA((std::string(TargetDirectoryName) + "*.psf.cix.xml").c_str(), &findData);
702 | std::string defaultResult = "express.psf.cix.xml";
703 | std::string result = "express.psf.cix.xml";
704 | if (hFind != INVALID_HANDLE_VALUE) {
705 | result = "";
706 | do {
707 | std::string fileName = findData.cFileName;
708 | if (result.empty()) {
709 | result = fileName;
710 | }
711 | if (fileName == defaultResult) {
712 | result = defaultResult;
713 | }
714 | } while (FindNextFileA(hFind, &findData) != 0);
715 | FindClose(hFind);
716 | }
717 | DescriptionFileName = (char*)FDIAlloc(sizeof(char) * strlen(TargetDirectoryName) + 2 + result.size());
718 | strcpy_s(DescriptionFileName, strlen(TargetDirectoryName) + 2 + result.size(), TargetDirectoryName);
719 | strcat_s(DescriptionFileName, strlen(TargetDirectoryName) + 2 + result.size(), result.c_str());
720 |
721 | // Execute CAB extracting function
722 | CABFilePart = strdupWtoA(CP_ACP, CABFilePartPointer);
723 | CABFilePartPointer[0] = '\0';
724 | CABPathPart = strdupWtoA(CP_ACP, CABFileFullName);
725 | FDIFree(CABFileFullName);
726 | cout << "Extracting: ";
727 | if (!ExtractCABFile(CABFilePart, CABPathPart)) {
728 | cout << "\nError: Error extracting CAB." << endl;
729 | return 1;
730 | }
731 |
732 | // Cleanup and return
733 | FDIFree(CABFilePart);
734 | FDIFree(CABPathPart);
735 | }
736 |
737 | // Manual mode
738 | else if (argc == 5) {
739 | if (!lstrcmpiW(argv[1], PSFXv1)) {
740 | PSFXVersion = 1;
741 | }
742 | else if (!lstrcmpiW(argv[1], PSFXv2)) {
743 | PSFXVersion = 2;
744 | }
745 | else {
746 | cout << "Error: Invalid argument \'" << argv[1] << "\'." << endl;
747 | cout << HelpInformation;
748 | return 1;
749 | }
750 | PayloadFileName = strdupWtoA(CP_ACP, argv[2]);
751 | DescriptionFileName = strdupWtoA(CP_ACP, argv[3]);
752 | WCHAR* TargetDirectoryNameW = (WCHAR*)FDIAlloc(sizeof(WCHAR) * MAX_PATH_W);
753 | wcscpy_s(TargetDirectoryNameW, MAX_PATH_W, DOSDevicePrefix);
754 | CreateDirectoryW(argv[4], NULL);
755 | int len = lstrlenW(TargetDirectoryNameW);
756 | GetFullPathNameW(argv[4], MAX_PATH_W - len, &TargetDirectoryNameW[len], NULL);
757 | TargetDirectoryName = strdupWtoA(CP_ACP, TargetDirectoryNameW);
758 | FDIFree(TargetDirectoryNameW);
759 | if (TargetDirectoryName[strlen(TargetDirectoryName) - 1] != PathSeparator) {
760 | size_t len = strlen(TargetDirectoryName);
761 | TargetDirectoryName[len] = PathSeparator;
762 | TargetDirectoryName[len + 1] = '\0';
763 | }
764 | }
765 |
766 | // No argument
767 | else if (argc == 1) {
768 | cout << HelpInformation;
769 | return 0;
770 | }
771 |
772 | // Wrong argument number
773 | else {
774 | cout << HelpInformation;
775 | return 1;
776 | }
777 |
778 | // Read description file
779 | cout << "Reading file info...";
780 | switch (PSFXVersion) {
781 | case 1:
782 | if (!ParseDescriptionFileV1()) {
783 | cout << "\nError: Error reading description file." << endl;
784 | return 1;
785 | }
786 | break;
787 | case 2:
788 | if (!ParseDescriptionFileV2()) {
789 | cout << "\nError: Error reading description file." << endl;
790 | return 1;
791 | }
792 | break;
793 | }
794 | cout << " OK." << endl;
795 |
796 | // Write output
797 | TotalFiles = DeltaFileList.size();
798 | if (TotalFiles) {
799 | cout << "Writing: " << TotalFiles << " files..." << endl;
800 | if (!WriteOutput()) {
801 | cout << "\nError: Error writing output files." << endl;
802 | return 1;
803 | }
804 | }
805 |
806 | // Cleanup and exit
807 | if (PayloadFileName) {
808 | FDIFree(PayloadFileName);
809 | }
810 | if (DescriptionFileName) {
811 | FDIFree(DescriptionFileName);
812 | }
813 | if (TargetDirectoryName) {
814 | FDIFree(TargetDirectoryName);
815 | }
816 | cout << "Finished." << endl;
817 | return 0;
818 | }
819 |
--------------------------------------------------------------------------------