├── DoubleFine_Explorer.dpr ├── DoubleFine_Explorer.dproj ├── DoubleFine_Explorer.dres ├── DoubleFine_Explorer.otares ├── DoubleFine_Explorer.res ├── DoubleFine_ExplorerResource.rc ├── DoublefineExplorer.ini ├── Images and Icons ├── Compressed Versions │ ├── AboutHeaderNew.png │ ├── DF Explorer Working Text.fw.png │ ├── DFAboutFooter.fw.png │ ├── DFExp_FullBackground.fw.png │ └── Doublefine Explorer Side Logo.fw.png └── Originals │ ├── AboutHeaderNew.png │ ├── DF Explorer Working Text.fw.png │ ├── DFAboutFooter.fw.png │ ├── DFExp_FullBackground.bmp │ ├── DFExp_FullBackground.fw.png │ ├── DFExplorer.ico │ └── Doublefine Explorer Side Logo.fw.png ├── License.txt ├── README.md ├── Readme └── Double Fine Explorer.html ├── bass.dll ├── frmAbout.dfm ├── frmAbout.pas ├── frmMain.dfm ├── frmMain.pas ├── uDFExplorer_Base.pas ├── uDFExplorer_BaseBundleManager.pas ├── uDFExplorer_Const.pas ├── uDFExplorer_FSBManager.pas ├── uDFExplorer_Funcs.pas ├── uDFExplorer_ISBManager.pas ├── uDFExplorer_LABManager.pas ├── uDFExplorer_LPAKManager.pas ├── uDFExplorer_PAKManager.pas ├── uDFExplorer_PCKManager.pas ├── uDFExplorer_PKGManager.pas ├── uDFExplorer_PPAKManager.pas ├── uDFExplorer_Types.pas ├── uFileReader.pas ├── uMPEGHeaderCheck.pas ├── uMemReader.pas ├── uVimaDecode.pas ├── uWaveWriter.pas ├── uXCompress.pas ├── uXboxAdpcmDecoder.pas └── uZlib.pas /DoubleFine_Explorer.dpr: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | program DoubleFine_Explorer; 15 | 16 | 17 | 18 | {$R *.dres} 19 | 20 | uses 21 | Forms, 22 | frmMain in 'frmMain.pas' {formMain}, 23 | uDFExplorer_Const in 'uDFExplorer_Const.pas', 24 | uDFExplorer_PAKManager in 'uDFExplorer_PAKManager.pas', 25 | uDFExplorer_Types in 'uDFExplorer_Types.pas', 26 | uDFExplorer_Base in 'uDFExplorer_Base.pas', 27 | uDFExplorer_Funcs in 'uDFExplorer_Funcs.pas', 28 | uDFExplorer_FSBManager in 'uDFExplorer_FSBManager.pas', 29 | frmAbout in 'frmAbout.pas' {Aboutfrm}, 30 | uDFExplorer_BaseBundleManager in 'uDFExplorer_BaseBundleManager.pas', 31 | uDFExplorer_PPAKManager in 'uDFExplorer_PPAKManager.pas', 32 | uDFExplorer_LPAKManager in 'uDFExplorer_LPAKManager.pas', 33 | uDFExplorer_PKGManager in 'uDFExplorer_PKGManager.pas', 34 | uDFExplorer_LABManager in 'uDFExplorer_LABManager.pas', 35 | uVimaDecode in 'uVimaDecode.pas', 36 | uDFExplorer_PCKManager in 'uDFExplorer_PCKManager.pas', 37 | uDFExplorer_ISBManager in 'uDFExplorer_ISBManager.pas'; 38 | 39 | {$R *.res} 40 | 41 | begin 42 | Application.Initialize; 43 | Application.MainFormOnTaskbar := True; 44 | Application.Title := 'DoubleFine Explorer'; 45 | Application.CreateForm(TformMain, formMain); 46 | Application.CreateForm(TAboutfrm, Aboutfrm); 47 | Application.Run; 48 | end. 49 | -------------------------------------------------------------------------------- /DoubleFine_Explorer.dres: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/DoubleFine_Explorer.dres -------------------------------------------------------------------------------- /DoubleFine_Explorer.otares: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/DoubleFine_Explorer.otares -------------------------------------------------------------------------------- /DoubleFine_Explorer.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/DoubleFine_Explorer.res -------------------------------------------------------------------------------- /DoubleFine_ExplorerResource.rc: -------------------------------------------------------------------------------- 1 | luadec RCData "Resources\\luadec.exe" 2 | xcompressdll RCDATA "Resources\\xcompress.dll" 3 | -------------------------------------------------------------------------------- /DoublefineExplorer.ini: -------------------------------------------------------------------------------- 1 | [Settings] 2 | Hexeditor=C:\Program Files\BreakPoint Software\Hex Workshop v6.8\HWorks64.exe -------------------------------------------------------------------------------- /Images and Icons/Compressed Versions/AboutHeaderNew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Compressed Versions/AboutHeaderNew.png -------------------------------------------------------------------------------- /Images and Icons/Compressed Versions/DF Explorer Working Text.fw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Compressed Versions/DF Explorer Working Text.fw.png -------------------------------------------------------------------------------- /Images and Icons/Compressed Versions/DFAboutFooter.fw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Compressed Versions/DFAboutFooter.fw.png -------------------------------------------------------------------------------- /Images and Icons/Compressed Versions/DFExp_FullBackground.fw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Compressed Versions/DFExp_FullBackground.fw.png -------------------------------------------------------------------------------- /Images and Icons/Compressed Versions/Doublefine Explorer Side Logo.fw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Compressed Versions/Doublefine Explorer Side Logo.fw.png -------------------------------------------------------------------------------- /Images and Icons/Originals/AboutHeaderNew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Originals/AboutHeaderNew.png -------------------------------------------------------------------------------- /Images and Icons/Originals/DF Explorer Working Text.fw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Originals/DF Explorer Working Text.fw.png -------------------------------------------------------------------------------- /Images and Icons/Originals/DFAboutFooter.fw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Originals/DFAboutFooter.fw.png -------------------------------------------------------------------------------- /Images and Icons/Originals/DFExp_FullBackground.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Originals/DFExp_FullBackground.bmp -------------------------------------------------------------------------------- /Images and Icons/Originals/DFExp_FullBackground.fw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Originals/DFExp_FullBackground.fw.png -------------------------------------------------------------------------------- /Images and Icons/Originals/DFExplorer.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Originals/DFExplorer.ico -------------------------------------------------------------------------------- /Images and Icons/Originals/Doublefine Explorer Side Logo.fw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/Images and Icons/Originals/Doublefine Explorer Side Logo.fw.png -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DoubleFine Explorer 2 | Version 1.4.1
By Bennyboy
[Quick and Easy Software](http://quickandeasysoftware.net/) 3 | 4 | An explorer tool for games by Double Fine Productions. It supports games that use the Moai, Buddha and Remonkeyed engines. That's most games the company has released. 5 | 6 | It enables you to view, extract and convert resources. This includes text, speech, music, scripts and images. 7 | 8 | 9 | ## New in this version 10 | - Added support for Kinect Party and most of the filetypes inside. However textures aren't yet supported. 11 | 12 | ## How to use it 13 | 14 | - Click **Open** and either click on **open folder** or choose one of the games and the game folder will automatically be found. Choose one of the resource files. 15 | 16 | - Click **Save File** to save a file. Different options will appear here depending on the type of file you've selected. 17 | 18 | - Click **Save All Files** to save all files. Different options will be available here depending on the type of files available in the currently open resource file. 19 | 20 | 21 | ### Filtering the files: 22 | 23 | - Click on the **View** button to show a list of all the different file types, selecting one of these changes the view so that only these file types are visible. 24 | 25 | - To reset the view and show all files, click the button again and click **View All Files**. 26 | 27 | ### Searching the files: 28 | 29 | - To search just type in the search box. Searching ignores any filter that you've used. I.e. the view will be reset to 'All Files'. The search looks in file names and is an 'instant search' - the search happens as you type. 30 | 31 | - To reset the view either delete the text in the search box or click the **View** button and select **View all files**. 32 | 33 | ### Extra hidden stuff: 34 | There is a hidden button that can automatically extract a file and open it in a hex editor. 35 | To enable this feature you need to make a DoublefineExplorer.ini file in the same place as the program. Inside it should look like this: 36 | 37 | [Settings] 38 | Hexeditor=C:\\Program Files\\BreakPoint Software\\Hex Workshop v6\\HWorks32.exe 39 | 40 | 41 | Obviously Hexeditor should point to the path of your chosen hex editor. 42 | 43 | When the ini file is present the **Send to Hex Ed** button will appear. Files that are sent to the hex editor are first dumped to the temp folder. The program keeps track of what files it has saved here and when you open a new file or close the program it will delete them (though it obviously cant delete any files that are still open in the hex editor so close that first). 44 | 45 | 46 | ## What games are supported? 47 | The pc versions of the following games are supported. Versions for other platforms usually work but are largely untested. 48 | 49 | - Broken Age 50 | - Brutal Legend 51 | - Costume Quest 52 | - Costume Quest 2 53 | - Day of the Tentacle Remastered 54 | - Full Throttle Remastered 55 | - Grim Fandango Remastered 56 | - Headlander 57 | - Iron Brigade 58 | - Massive Chalice 59 | - Psychonauts 1 (The newer version that's on Steam and GOG. See the Limitations section below for more information). 60 | - Stacking 61 | - The Cave 62 | - The Amnesia Fortnight prototypes seem to work but aren't really tested 63 | - Kinect Party (Xbox) 64 | 65 | 66 | ## Limitations and bugs 67 | 68 | - Psychonauts 1: The new version of Psychonauts 1 that's on Steam and GOG changed the format of the level pack files (.ppf). DoubleFine Explorer only supports this newer version, If you have the original version you can open all the level files in [Psychonauts Explorer.](http://quickandeasysoftware.net/software/psychonauts-explorer) 69 | 70 | - There are a few audio files that don't decode correctly. This is probably because they use a different codec. There are known problems with UI.fsb in The Cave and with 5 fsb files in Iron Brigade. 71 | 72 | - Grim Fandango Remastered: I've concentrated on adding support for the newer stuff introduced in the Remastered version. The original files can still be decoded with [SCUMM Revisited.](https://quickandeasysoftware.net/the-vault) 73 | 74 | 75 | 76 | ## Source 77 | Available from my [Github.](https://github.com/bgbennyboy/DoubleFine-Explorer) 78 | 79 | ## Licence 80 | This program and its source is released under the terms of the [Mozilla Public License v. 2.0.](https://www.mozilla.org/MPL/2.0/) 81 | 82 | ## Thanks 83 | - [Jimmi Thøgersen (Serge)](http://www.jither.net/) for his Vima decoding code. 84 | - [Luigi Auriemma](http://aluigi.altervista.org) for infomation on FSB files. 85 | - Oliver Franzke for being kind enough to solve the mystery of how images are encoded in DOTT. 86 | 87 | ## Support: 88 | [Contact me](http://quickandeasysoftware.net/contact). 89 | 90 | All my software is completely free. If you find this program useful please consider making a donation. This can be done on my [website](http://quickandeasysoftware.net). 91 | 92 | ## Disclaimer: 93 | The software is provided "as-is" and without warranty of any kind, express, implied or otherwise, including without limitation, any warranty of merchantability or fitness for a particular purpose. In no event shall the initial developer or any other contributor be liable for any special, incidental, indirect or consequential damages of any kind, or any damages whatsoever resulting from loss of use, data or profits, whether or not advised of the possibility of damage, and on any theory of liability, arising out of or in connection with the use or performance of this software. 94 | 95 |

96 | Last updated 05/10/23 97 | -------------------------------------------------------------------------------- /Readme/Double Fine Explorer.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Double Fine Explorer 8 | 10 | 12 | 13 | 14 | 15 | 16 |

DoubleFine Explorer

17 |

Version 1.4.1
By Bennyboy
Quick and Easy Software

18 |

An explorer tool for games by Double Fine Productions. It supports games that use the Moai, Buddha and Remonkeyed engines. That’s most games the company has released.

19 |

It enables you to view, extract and convert resources. This includes text, speech, music, scripts and images.

20 |

New in this version

21 | 24 |

How to use it

25 | 36 |

Filtering the files:

37 | 45 |

Searching the files:

46 | 54 |

Extra hidden stuff:

55 |

There is a hidden button that can automatically extract a file and open it in a hex editor.
56 | To enable this feature you need to make a DoublefineExplorer.ini file in the same place as the program. Inside it should look like this:

57 |
[Settings]  
 58 | Hexeditor=C:\\Program Files\\BreakPoint Software\\Hex Workshop v6\\HWorks32.exe  
 59 | 
60 |

Obviously Hexeditor should point to the path of your chosen hex editor.

61 |

When the ini file is present the Send to Hex Ed button will appear. Files that are sent to the hex editor are first dumped to the temp folder. The program keeps track of what files it has saved here and when you open a new file or close the program it will delete them (though it obviously cant delete any files that are still open in the hex editor so close that first).

62 |

What games are supported?

63 |

The pc versions of the following games are supported. Versions for other platforms usually work but are largely untested.

64 | 81 |

Limitations and bugs

82 | 93 |

Source

94 |

Available from my Github.

95 |

Licence

96 |

This program and its source is released under the terms of the Mozilla Public License v. 2.0.

97 |

Thanks

98 | 103 |

Support:

104 |

Contact me.

105 |

All my software is completely free. If you find this program useful please consider making a donation. This can be done on my website.

106 |

Disclaimer:

107 |

The software is provided “as-is” and without warranty of any kind, express, implied or otherwise, including without limitation, any warranty of merchantability or fitness for a particular purpose. In no event shall the initial developer or any other contributor be liable for any special, incidental, indirect or consequential damages of any kind, or any damages whatsoever resulting from loss of use, data or profits, whether or not advised of the possibility of damage, and on any theory of liability, arising out of or in connection with the use or performance of this software.

108 |




109 | Last updated 05/10/23

110 |
111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /bass.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/bass.dll -------------------------------------------------------------------------------- /frmAbout.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | unit frmAbout; 15 | 16 | interface 17 | 18 | uses 19 | Windows, Forms, Controls, Classes, Graphics, 20 | ExtCtrls, JvExControls, JvScrollText, 21 | JCLShell, 22 | uDFExplorer_Const, pngimage; 23 | 24 | 25 | type 26 | TAboutfrm = class(TForm) 27 | Image1: TImage; 28 | Image2: TImage; 29 | JvScrollText1: TJvScrollText; 30 | procedure FormCreate(Sender: TObject); 31 | procedure FormShow(Sender: TObject); 32 | procedure FormHide(Sender: TObject); 33 | procedure Image1Click(Sender: TObject); 34 | private 35 | { Private declarations } 36 | public 37 | { Public declarations } 38 | end; 39 | 40 | var 41 | Aboutfrm: TAboutfrm; 42 | 43 | implementation 44 | 45 | {$R *.dfm} 46 | 47 | procedure TAboutfrm.FormCreate(Sender: TObject); 48 | begin 49 | //Add the version to the scrolling text 50 | JVScrollText1.Items.Strings[2]:='Version ' + strAppVersion; 51 | //Add a huge empty string to force scrolltext to align properly 52 | JVScrollText1.Items.Add(' '); 53 | JVScrollText1.Font.Color:=clWhite; 54 | JVScrollText1.Font.Size:=14; 55 | 56 | Aboutfrm.Caption:='About ' + strAppName; 57 | end; 58 | 59 | procedure TAboutfrm.FormHide(Sender: TObject); 60 | begin 61 | //JVScrollText1.Active:=false; 62 | end; 63 | 64 | procedure TAboutfrm.FormShow(Sender: TObject); 65 | begin 66 | JVScrollText1.Active:=true; 67 | end; 68 | 69 | procedure TAboutfrm.Image1Click(Sender: TObject); 70 | begin 71 | shellexec(0, 'open', 'Http://quickandeasysoftware.net','', '', SW_SHOWNORMAL); 72 | end; 73 | 74 | end. 75 | -------------------------------------------------------------------------------- /uDFExplorer_BaseBundleManager.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | unit uDFExplorer_BaseBundleManager; 15 | 16 | interface 17 | 18 | uses 19 | classes, Contnrs, 20 | uFileReader, uDFExplorer_Types; 21 | 22 | type 23 | TBundleManager = class 24 | protected 25 | fBigEndian: boolean; 26 | fBundle: TExplorerFileStream; 27 | fBundleFileName: string; 28 | fonDoneLoading: TOnDoneLoading; 29 | fonProgress: TProgressEvent; 30 | fonDebug: TDebugEvent; 31 | function DetectBundle: boolean; Virtual; Abstract; 32 | function GetFilesCount: integer; Virtual; Abstract; 33 | function GetFileName(Index: integer): string; Virtual; Abstract; 34 | function GetFileSize(Index: integer): integer; Virtual; Abstract; 35 | function GetFileOffset(Index: integer): int64; Virtual; Abstract; 36 | function GetFileExtension(Index: integer): string; Virtual; Abstract; 37 | function GetFileType(Index: integer): TFiletype; Virtual; Abstract; 38 | procedure Log(Text: string); Virtual; Abstract; 39 | public 40 | BundleFiles: TObjectList; 41 | constructor Create(ResourceFile: string); Virtual; Abstract; 42 | destructor Destroy; override; 43 | procedure ParseFiles; Virtual; Abstract; 44 | procedure SaveFile(FileNo: integer; DestDir, FileName: string); Virtual; Abstract; 45 | procedure SaveFileToStream(FileNo: integer; DestStream: TStream); Virtual; Abstract; 46 | procedure SaveFiles(DestDir: string); Virtual; Abstract; 47 | property Count: integer read GetFilesCount; 48 | property OnDoneLoading: TOnDoneLoading read FOnDoneLoading write FOnDoneLoading; 49 | property OnProgress: TProgressEvent read FOnProgress write FOnProgress; 50 | property OnDebug: TDebugEvent read FOnDebug write FOnDebug; 51 | property FileName[Index: integer]: string read GetFileName; 52 | property FileSize[Index: integer]: integer read GetFileSize; 53 | property FileOffset[Index: integer]: int64 read GetFileOffset; 54 | property FileExtension[Index: integer]: string read GetFileExtension; 55 | property FileType[Index: integer]: TFileType read GetFileType; 56 | property BigEndian: boolean read fBigEndian; 57 | end; 58 | 59 | const 60 | strErrFileSize: string = 'File size <=0 Save cancelled for this file.'; 61 | strErrFileNo: string = 'Invalid file number! Save cancelled for this file.'; 62 | strSavingFile: string = 'Saving file '; 63 | 64 | implementation 65 | 66 | destructor TBundleManager.Destroy; 67 | begin 68 | 69 | inherited; 70 | end; 71 | 72 | end. 73 | -------------------------------------------------------------------------------- /uDFExplorer_Const.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | unit uDFExplorer_Const; 15 | 16 | interface 17 | 18 | const 19 | strAppName: string = 'Double Fine Explorer'; 20 | strAppVersion: string = '1.4.1'; 21 | strAppURL: string = 'http://quickandeasysoftware.net'; 22 | strCmdLineOpenAndDelete: string = '/OPENFILEANDDELETELATER'; 23 | 24 | 25 | {**********************************Main Form***********************************} 26 | strDumpingAllFiles: string = 'Dumping all files...'; 27 | strDumpingAllVisibleFiles: string = 'Dumping all visible files...'; 28 | strDumpingAllImages: string = 'Dumping all images...'; 29 | strDumpingAllDDSImages: string = 'Dumping all dds images...'; 30 | strDumpingAllText: string = 'Dumping all text...'; 31 | strDumpingAllAudio: string = 'Dumping all audio...'; 32 | strDumpingAllLua: string = 'Decompiling all lua...WARNING this may take a while (at least 5 minutes to decompile all lua files in pdata.pck'; 33 | strDone: string = '...done!'; 34 | strChooseAFolder: string = 'Choose a folder'; 35 | strViewAllFiles: string = 'View all files'; 36 | strViewSavedGameFiles: string = 'Saved games'; 37 | strSavingFile: string = 'Saving file '; 38 | strSendingToHex: string = 'Sending file to hex editor: '; 39 | strErrorHexEditorPath: string = 'Error - hex editor path not found in ini!'; 40 | strErrHexFileExists: string = 'File already exists and in use! Saving as '; 41 | strIncorrectBASSVersion: string = 'An incorrect version of BASS.DLL was loaded'; 42 | strErrorInitializingAudio: string = 'Error initializing audio!'; 43 | strErrorUnrecognisedAudioType: string = 'File not a recognised audio type'; 44 | strErrorPlayingStreamCode: string = 'Error playing stream! Error code:'; 45 | strErrorInvalidFileNoLuaDec: string = 'Invalid file number! Lua decompile failed.'; 46 | 47 | implementation 48 | 49 | end. 50 | -------------------------------------------------------------------------------- /uDFExplorer_FSBManager.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | unit uDFExplorer_FSBManager; 15 | 16 | interface 17 | 18 | uses 19 | classes, sysutils, Contnrs, forms, 20 | uFileReader, uMemReader, uDFExplorer_BaseBundleManager, uDFExplorer_Types, 21 | uDFExplorer_Funcs, uWaveWriter, JCLSysInfo, JCLShell, Windows, uMPEGHeaderCheck; 22 | 23 | type 24 | TFSBManager = class (TBundleManager) 25 | protected 26 | fBundle: TExplorerFileStream; 27 | fMemoryBundle: TExplorerMemoryStream; 28 | fBundleFileName: string; 29 | fEncrypted: boolean; 30 | function DetectBundle: boolean; override; 31 | function GetFilesCount: integer; override; 32 | function GetFileName(Index: integer): string; override; 33 | function GetFileSize(Index: integer): integer; override; 34 | function GetFileOffset(Index: integer): int64; override; 35 | function DecryptFSB(InStream: TStream; Offset, Size: integer; OutStream: TStream; 36 | Key: Array of byte; KeyOffset: integer = -1): boolean; 37 | function GetFileType(Index: integer): TFiletype; override; 38 | function GetFileExtension(Index: integer): string; override; 39 | function GetFSB5Offset(InValue: Dword): DWord; 40 | procedure Log(Text: string); override; 41 | procedure ParseFSB5; 42 | procedure ParseFSB4; 43 | procedure SaveFixedMP3Stream(InStream, OutStream: TStream; FileSize, 44 | Channels: integer); 45 | public 46 | BundleFiles: TObjectList; 47 | constructor Create(ResourceFile: string); override; 48 | destructor Destroy; override; 49 | procedure ParseFiles; override; 50 | procedure SaveFile(FileNo: integer; DestDir, FileName: string); override; 51 | procedure SaveFileToStream(FileNo: integer; DestStream: TStream); override; 52 | procedure SaveFiles(DestDir: string); override; 53 | property Count: integer read GetFilesCount; 54 | property FileName[Index: integer]: string read GetFileName; 55 | property FileSize[Index: integer]: integer read GetFileSize; 56 | property FileOffset[Index: integer]: int64 read GetFileOffset; 57 | property FileType[Index: integer]: TFileType read GetFileType; 58 | property FileExtension[Index: integer]: string read GetFileExtension; 59 | end; 60 | 61 | const 62 | strErrInvalidFile: string = 'Not a valid FSB file'; 63 | FSBKey: array [0..9] of byte = ($44, $46, $6D, $33, $74, $34, $6C, $46, $54, $57); //DFm3t4lFTW 64 | var 65 | WaveBankVersion: integer; 66 | 67 | implementation 68 | 69 | 70 | constructor TFSBManager.Create(ResourceFile: string); 71 | begin 72 | try 73 | fBundle:=TExplorerFileStream.Create(ResourceFile); 74 | except on E: EInvalidFile do 75 | raise; 76 | end; 77 | 78 | fBundleFileName:=ExtractFileName(ResourceFile); 79 | BundleFiles:=TObjectList.Create(true); 80 | 81 | if DetectBundle = false then 82 | raise EInvalidFile.Create( strErrInvalidFile ); 83 | end; 84 | 85 | destructor TFSBManager.Destroy; 86 | begin 87 | if BundleFiles <> nil then 88 | begin 89 | BundleFiles.Free; 90 | BundleFiles:=nil; 91 | end; 92 | 93 | if fMemoryBundle <> nil then 94 | fMemoryBundle.free; 95 | 96 | if fBundle <> nil then 97 | fBundle.free; 98 | 99 | inherited; 100 | end; 101 | 102 | function TFSBManager.DetectBundle: boolean; 103 | var 104 | Temp: string; 105 | FilesOffset: integer; 106 | begin 107 | Result := false; 108 | fEncrypted := true; 109 | FilesOffset := 0; 110 | 111 | Temp := fBundle.ReadBlockName; 112 | if (Temp = 'FSB5') or (Temp = 'FSB4') then //Unencrypted 113 | begin 114 | fEncrypted := false; 115 | fMemoryBundle := TExplorerMemoryStream.Create; 116 | 117 | if Temp = 'FSB4' then 118 | begin 119 | //Read the size of the sample headers to work out where the header ends and file data starts 120 | fBundle.Position := 8; 121 | FilesOffset := fBundle.ReadDWord + 48; 122 | end 123 | else 124 | if Temp = 'FSB5' then 125 | begin 126 | //Read the size of the sample + name size headers to work out where the header ends and file data starts 127 | fBundle.Position := 12; 128 | FilesOffset := fBundle.ReadDWord + fBundle.ReadDWord + 60; 129 | end; 130 | 131 | //Then only copy out the header information 132 | fBundle.Position := 0; 133 | fMemoryBundle.CopyFrom(fBundle, FilesOffset); //Keep everything in memory - faster to read from and easier than dealing with different cases for file/memory stream 134 | Result := true; 135 | end 136 | else 137 | begin 138 | fBundle.Position := 0; 139 | fMemoryBundle := TExplorerMemoryStream.Create; 140 | 141 | if DecryptFSB(fBundle, 0, 4, fMemoryBundle, FSBKey) then 142 | begin 143 | //Then check again 144 | fMemoryBundle.Position := 0; 145 | Temp := fMemoryBundle.ReadBlockName; 146 | if (Temp = 'FSB5') or (Temp = 'FSB4') then 147 | begin 148 | Result := true; 149 | 150 | //Decrypt the first few bytes so we can work out how big the header is 151 | fMemoryBundle.Clear; 152 | DecryptFSB(fBundle, 0, 20, fMemoryBundle, FSBKey); 153 | 154 | if Temp = 'FSB4' then 155 | begin 156 | //Read the size of the sample headers to work out where the header ends and file data starts 157 | fMemoryBundle.Position := 8; 158 | FilesOffset := fMemoryBundle.ReadDWord + 48; 159 | end 160 | else 161 | if Temp = 'FSB5' then 162 | begin 163 | //Read the size of the sample + name size headers to work out where the header ends and file data starts 164 | fMemoryBundle.Position := 12; 165 | FilesOffset := fMemoryBundle.ReadDWord + fMemoryBundle.ReadDWord + 60; 166 | end; 167 | 168 | //Then decrypt the full header 169 | fMemoryBundle.Clear; 170 | DecryptFSB(fBundle, 0, FilesOffset, fMemoryBundle, FSBKey) 171 | end; 172 | end; 173 | //fMemoryBundle.SaveToFile('c:\users\ben\desktop\decrypted.fsb'); 174 | end; 175 | end; 176 | 177 | function TFSBManager.DecryptFSB(InStream: TStream; Offset, Size: integer; 178 | OutStream: TStream; Key: Array of byte; KeyOffset: integer = -1): boolean; 179 | 180 | function ReverseBitsInByte(input: byte): byte; inline; 181 | var 182 | i: integer; 183 | begin 184 | result := 0; 185 | for I := 0 to 7 do 186 | begin 187 | result := result shl 1; 188 | result := result or (input and 1); 189 | input := input shr 1; 190 | end; 191 | end; 192 | 193 | var //TODO - read it in blocks and write whole lot at once 194 | i, j: integer; 195 | TempByte: byte; 196 | begin 197 | Result := false; 198 | 199 | Instream.Position := Offset; 200 | 201 | if KeyOffset = -1 then 202 | KeyOffset := Offset; //If no key offset provided then we assume that we're dealing with the original file and calculate the key offset based on the file offset 203 | 204 | j := KeyOffset mod length(Key); //Calculate what part of the key to start from 205 | for i := 0 to Size - 1 do 206 | begin 207 | InStream.Read(TempByte, 1); 208 | TempByte := ReverseBitsInByte(TempByte) xor Key[j]; 209 | inc(j); 210 | if j= length(Key) then j:= 0; 211 | 212 | OutStream.Write(TempByte, 1); 213 | end; 214 | 215 | if Size = OutStream.Size then result := true; 216 | end; 217 | 218 | function TFSBManager.GetFileExtension(Index: integer): string; 219 | begin 220 | if (not assigned(BundleFiles)) or 221 | (index < 0) or 222 | (index > GetFilesCount) then 223 | result:= '' 224 | else 225 | result:=TFSBFile(BundleFiles.Items[Index]).FileExtension; 226 | end; 227 | 228 | function TFSBManager.GetFileName(Index: integer): string; 229 | begin 230 | if (not assigned(BundleFiles)) or 231 | (index < 0) or 232 | (index > GetFilesCount) then 233 | result:= '' 234 | else 235 | result:=TFSBFile(BundleFiles.Items[Index]).FileName; 236 | end; 237 | 238 | function TFSBManager.GetFileOffset(Index: integer): int64; 239 | begin 240 | if (not assigned(BundleFiles)) or 241 | (index < 0) or 242 | (index > GetFilesCount) then 243 | result:=0 244 | else 245 | result:=TFSBFile(BundleFiles.Items[Index]).offset; 246 | end; 247 | 248 | function TFSBManager.GetFilesCount: integer; 249 | begin 250 | if BundleFiles <> nil then 251 | result:=BundleFiles.Count 252 | else 253 | result:=0; 254 | end; 255 | 256 | function TFSBManager.GetFileSize(Index: integer): integer; 257 | begin 258 | if (not assigned(BundleFiles)) or 259 | (index < 0) or 260 | (index > GetFilesCount) then 261 | result:=-1 262 | else 263 | result:=TFSBFile(BundleFiles.Items[Index]).size; 264 | end; 265 | 266 | function TFSBManager.GetFileType(Index: integer): TFiletype; 267 | begin 268 | if (not assigned(BundleFiles)) or 269 | (index < 0) or 270 | (index > GetFilesCount) then 271 | result:= ft_Unknown 272 | else 273 | result:=TFSBFile(BundleFiles.Items[Index]).FileType; 274 | end; 275 | 276 | procedure TFSBManager.Log(Text: string); 277 | begin 278 | if assigned(fOnDebug) then fOnDebug(Text); 279 | end; 280 | 281 | procedure TFSBManager.ParseFiles; 282 | var 283 | strMagic: string; 284 | begin 285 | fMemoryBundle.Position := 0; 286 | strMagic := fMemoryBundle.ReadBlockName; 287 | fMemoryBundle.Position := 0; 288 | 289 | if strMagic ='FSB5' then 290 | ParseFSB5 291 | else 292 | if strMagic ='FSB4' then 293 | ParseFSB4; 294 | 295 | if (Assigned(FOnDoneLoading)) then 296 | FOnDoneLoading(BundleFiles.Count); 297 | end; 298 | 299 | 300 | procedure TFSBManager.ParseFSB4; 301 | //Based on FSBExt by Luigi Auriemma 302 | const 303 | FSB_FileNameLength: integer = 30; 304 | FSOUND_DELTA = $00000200; 305 | FSOUND_8BITS = $00000008; 306 | FSOUND_16BITS = $00000010; 307 | FSOUND_MONO = $00000020; 308 | FSOUND_STEREO = $00000040; 309 | var 310 | TempStr, FileExt: string; 311 | FileObject: TFSBFile; 312 | RecordSize, NumSamples, Mode, SampleHeaderSize, Datasize, NameOffset, 313 | FileOffset, i, Size, Samples, PrevOffsetAndSize, Channels, Bits, Frequency: integer; 314 | Version, HeadMode: longword; 315 | Codec: TFSBCodec; 316 | begin 317 | fMemoryBundle.Position := 0; 318 | TempStr := fMemoryBundle.ReadBlockName; 319 | if TempStr <> 'FSB4' then 320 | begin 321 | Log('Not an FSB4 header!: ' + TempStr); 322 | raise EInvalidFile.Create( strErrInvalidFile ); 323 | end; 324 | 325 | 326 | NumSamples := fMemoryBundle.ReadDWord; 327 | SampleHeaderSize := fMemoryBundle.ReadDWord; 328 | Datasize := fMemoryBundle.ReadDWord; 329 | Version := fMemoryBundle.ReadDWord; 330 | HeadMode := fMemoryBundle.ReadDWord; 331 | fMemoryBundle.Seek(24, soFromCurrent); //8x zero bytes + hash 332 | 333 | NameOffset := 48; //size of initial header 334 | FileOffset := 48 + SampleHeaderSize; 335 | PrevOffsetAndSize := 0; 336 | 337 | if (HeadMode and $08) <> 0 then 338 | Log('BIG ENDIAN MODE DETECTED: TODO - IMPLEMENT SUPPORT FOR THIS !!!!!'); 339 | 340 | if(HeadMode and $00000002) <> 0 then 341 | Log('Basic headers detected in this FSB!'); 342 | 343 | 344 | Mode := 0 ; 345 | Frequency := 0; 346 | for I := 0 to NumSamples - 1 do 347 | begin 348 | if ((HeadMode and $00000002) <> 0) and (i <> 0) then 349 | begin 350 | //Log('Basic headers mode'); 351 | Samples := fMemoryBundle.ReadDWord; 352 | Size := fMemoryBundle.ReadDWord; 353 | TempStr := inttostr(i); 354 | //freq, chans, mode and moresize are the same as the first file 355 | end 356 | else 357 | begin 358 | RecordSize :=fMemoryBundle.ReadWord; //size of this record, inclusive 359 | TempStr := PChar(fMemoryBundle.ReadString(FSB_FileNameLength)); 360 | Samples := fMemoryBundle.ReadDWord; 361 | Size := fMemoryBundle.ReadDWord; 362 | fMemoryBundle.Seek(8, soFromCurrent); //loopstart and loopend 363 | Mode := fMemoryBundle.ReadDWord; 364 | Frequency := fMemoryBundle.ReadDWord; 365 | fMemoryBundle.Seek(24, soFromCurrent); //Unused data for this 366 | if RecordSize > 80 then //Some files have extra data 367 | fMemoryBundle.Seek(RecordSize - 80, soFromCurrent); 368 | end; 369 | 370 | //Now work out the codec it uses - for DF games its almost always MPEG 371 | Codec := FMOD_SOUND_FORMAT_PCM16; 372 | if (Mode and FSOUND_DELTA) <> 0 then 373 | Codec := FMOD_SOUND_FORMAT_MPEG 374 | else 375 | if ((Mode and FSOUND_8BITS) <> 0) and (Codec = FMOD_SOUND_FORMAT_PCM16) then 376 | Codec := FMOD_SOUND_FORMAT_PCM8; 377 | 378 | //Match codec to file extension 379 | case Codec of 380 | FMOD_SOUND_FORMAT_PCM8: FileExt := 'WAV'; 381 | FMOD_SOUND_FORMAT_PCM16: FileExt := 'WAV'; 382 | FMOD_SOUND_FORMAT_MPEG: FileExt := 'MP3' 383 | else 384 | begin 385 | Log('Unknown codec'); 386 | FileExt := 'WAV'; 387 | end; 388 | end; 389 | 390 | //Get no of channels 391 | Channels := 1; 392 | if (Mode and FSOUND_MONO) <> 0 then 393 | Channels := 1 394 | else 395 | if (Mode and FSOUND_STEREO) <> 0 then 396 | Channels := 2; 397 | 398 | //Get bits 399 | Bits := 16; 400 | if (Mode and FSOUND_8BITS) <> 0 then 401 | Bits := 8 402 | else 403 | if (Mode and FSOUND_16BITS) <> 0 then 404 | Bits := 16; 405 | 406 | FileObject := TFSBFile.Create; 407 | FileObject.size := Size; 408 | if i = 0 then 409 | FileObject.offset := FileOffset 410 | else 411 | FileObject.offset := PrevOffsetAndSize; 412 | PrevOffsetAndSize := FileObject.size + FileObject.Offset; 413 | FileObject.FileName := ChangeFileExt(Tempstr, '.' + Lowercase(FileExt)); 414 | FileObject.FileType := ft_Audio; 415 | FileObject.FileExtension := FileExt; 416 | FileObject.Codec := Codec; 417 | FileObject.Channels := Channels; 418 | FileObject.Bits := Bits; 419 | FileObject.Freq := Frequency; 420 | 421 | BundleFiles.Add(FileObject); 422 | end; 423 | end; 424 | 425 | procedure TFSBManager.ParseFSB5; 426 | //Based on FSBExt by Luigi Auriemma - doesnt include all FSB5 dumping stuff - just enough to work for known DF FSB files 427 | var 428 | Version, NumSamples, SampleHeaderSize, NameSize, Datasize, Mode, i, 429 | Len, NameOffset, Frequency: integer; 430 | 431 | Offset, Samples, TheType, TempDWord, Size, FileOff: dword; 432 | Channels: word; 433 | TempQWord: uint64; 434 | TempStr: string; 435 | FileObject: TFSBFile; 436 | begin 437 | fMemoryBundle.Position := 0; 438 | TempStr := fMemoryBundle.ReadBlockName; 439 | if TempStr <> 'FSB5' then 440 | begin 441 | Log('Not an FSB5 header!: ' + TempStr); 442 | raise EInvalidFile.Create( strErrInvalidFile ); 443 | end; 444 | 445 | Version := fMemoryBundle.ReadDWord; 446 | NumSamples := fMemoryBundle.ReadDWord; 447 | SampleHeaderSize := fMemoryBundle.ReadDWord; 448 | NameSize := fMemoryBundle.ReadDWord; 449 | Datasize := fMemoryBundle.ReadDWord; 450 | Mode := fMemoryBundle.ReadDWord; 451 | NameOffset := 60 + SampleHeaderSize; //60 is first header size 452 | 453 | {Log('Version ' + inttostr(Version)); 454 | Log('NumSamples ' + inttostr(NumSamples)); 455 | Log('SampleHeaderSize ' + inttostr(SampleHeaderSize)); 456 | Log('NameSize ' + inttostr(NameSize)); 457 | Log('Datasize ' + inttostr(Datasize)); 458 | Log('Mode ' + inttostr(Mode));} 459 | 460 | 461 | 462 | fMemoryBundle.Seek(32, sofromcurrent); //now at end of file header 463 | 464 | for I := 0 to NumSamples - 1 do 465 | begin 466 | Offset := fMemoryBundle.ReadDWord; 467 | Samples := fMemoryBundle.ReadDWord shr 2; //Used in XMA 468 | 469 | TheType := Offset and ((1 shl 7) -1); 470 | Offset := GetFSB5Offset(Offset); 471 | Channels := (TheType shr 5) + 1; 472 | 473 | case ((TheType shr 1) and ((1 shl 4) - 1)) of 474 | 0: Frequency := 4000; 475 | 1: Frequency := 8000; 476 | 2: Frequency := 11000; 477 | 3: Frequency := 12000; 478 | 4: Frequency := 16000; 479 | 5: Frequency := 22050; 480 | 6: Frequency := 24000; 481 | 7: Frequency := 32000; 482 | 8: Frequency := 44100; 483 | 9: Frequency := 48000; 484 | 10: Frequency := 96000; 485 | else Frequency := 44100; 486 | end; 487 | { 488 | Gone in fsbext 0.3.3 489 | TheType := Offset and $FF; 490 | Offset := Offset shr 8; 491 | Offset := Offset * $40;} //64; 492 | 493 | {Log('Offset ' + Inttostr( Offset)); 494 | Log('Samples ' + Inttostr( Samples)); 495 | Log('Type ' + Inttostr( TheType));} 496 | 497 | while (TheType and 1 > 0) do 498 | begin 499 | TempDWord := fMemoryBundle.ReadDWord; 500 | TheType := TempDWord and 1; 501 | Len := (TempDWord and $ffffff) shr 1; 502 | TempDWord := TempDWord shr 24; 503 | TempQWord := fMemoryBundle.Position; 504 | case TempDWord of 505 | 2: Channels := fMemoryBundle.ReadByte; //fMemoryBundle.Seek(1, sofromcurrent); //channels 506 | 4: Frequency := fMemoryBundle.ReadDWord; //fMemoryBundle.Seek(4, sofromcurrent); //frequency 507 | 6: begin 508 | fMemoryBundle.Seek(4, sofromcurrent); //unknown 509 | fMemoryBundle.Seek(4, sofromcurrent); //unknown 510 | end; 511 | 20: ;//xwma data 512 | end; 513 | 514 | TempQWord := TempQWord + Len; 515 | fMemoryBundle.Seek(TempQWord, sofrombeginning); 516 | end; 517 | 518 | 519 | TempQWord := fMemoryBundle.Position; 520 | if fMemoryBundle.Position < NameOffset then //nameoffset 521 | begin 522 | Size := fMemoryBundle.ReadDWord; 523 | if Size = 0 then //not sure about this 524 | begin 525 | Size := fBundle.Size; //Size of the original file (remember MemoryBundle only contains the header) 526 | end 527 | else 528 | begin 529 | {Size := Size shr 8; 530 | Size := Size * $40; 531 | Size := Size + (NameOffset + NameSize);} //base offset 532 | Size := GetFSB5Offset(Size) + (NameOffset + NameSize); //base offset 533 | end 534 | end 535 | else 536 | begin 537 | Size := fBundle.Size; //Size of the original file (remember MemoryBundle only contains the header) 538 | end; 539 | 540 | fMemoryBundle.Seek(TempQWord, sofrombeginning); 541 | FileOff := (NameOffset + NameSize) + Offset; //offset + base offset 542 | Size := Size - FileOff; 543 | 544 | 545 | //Now get name if there is one 546 | //TODO check flags to make sure there is a name in the FSB 547 | TempQWord := fMemoryBundle.Position; //store old position 548 | fMemoryBundle.Position := NameOffset + (i * 4); //nameoff 549 | fMemoryBundle.Seek(NameOffset + fMemoryBundle.ReadDWord, soFromBeginning); 550 | Tempstr := PChar(fMemoryBundle.ReadString(255)); 551 | fMemoryBundle.Position := TempQWord; //seek back to old position 552 | 553 | 554 | FileObject := TFSBFile.Create; 555 | FileObject.size := Size; 556 | FileObject.offset := FileOff; 557 | FileObject.FileName := Tempstr + '.mp3'; 558 | FileObject.FileType := ft_Audio; 559 | FileObject.Channels := Channels; 560 | FileObject.FileExtension := 'MP3'; 561 | FileObject.Codec := FMOD_SOUND_FORMAT_MPEG; //TODO - other codec detection for FSB5 - not needed by any games seen so far 562 | 563 | BundleFiles.Add(FileObject); 564 | end; 565 | 566 | end; 567 | 568 | function TFSBManager.GetFSB5Offset(InValue: Dword): DWord; 569 | begin 570 | result := (InValue shr 7) * $20; 571 | end; 572 | 573 | procedure TFSBManager.SaveFile(FileNo: integer; DestDir, FileName: string); 574 | var 575 | SaveFile: TFileStream; 576 | begin 577 | if TFSBFile(BundleFiles.Items[FileNo]).Size <= 0 then 578 | begin 579 | Log(strErrFileSize); 580 | exit; 581 | end; 582 | 583 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 584 | begin 585 | Log(strErrFileNo); 586 | exit; 587 | end; 588 | 589 | Log(strSavingFile + FileName); 590 | 591 | SaveFile:=tfilestream.Create(IncludeTrailingPathDelimiter(DestDir) + FileName, 592 | fmOpenWrite or fmCreate); 593 | try 594 | SaveFileToStream(FileNo,SaveFile); 595 | finally 596 | SaveFile.Free; 597 | end; 598 | 599 | 600 | end; 601 | 602 | procedure TFSBManager.SaveFiles(DestDir: string); 603 | var 604 | i: integer; 605 | SaveFile: TFileStream; 606 | begin 607 | for I := 0 to BundleFiles.Count - 1 do 608 | begin 609 | ForceDirectories(extractfilepath(IncludeTrailingPathDelimiter(DestDir) + 610 | ExtractPartialPath( TFSBFile(BundleFiles.Items[i]).FileName))); 611 | SaveFile:=TFileStream.Create(IncludeTrailingPathDelimiter(DestDir) + 612 | TFSBFile(BundleFiles.Items[i]).FileName , fmOpenWrite or fmCreate); 613 | try 614 | SaveFileToStream(i, SaveFile); 615 | finally 616 | SaveFile.free; 617 | if Assigned(FOnProgress) then FOnProgress(GetFilesCount -1, i); 618 | Application.Processmessages; 619 | end; 620 | end; 621 | 622 | end; 623 | 624 | procedure TFSBManager.SaveFileToStream(FileNo: integer; DestStream: TStream); 625 | var 626 | Ext: string; 627 | WavStream: TWaveStream; 628 | TempStream: TMemoryStream; 629 | begin 630 | if TFSBFile(BundleFiles.Items[FileNo]).Size <= 0 then 631 | begin 632 | Log(strErrFileSize); 633 | exit; 634 | end; 635 | 636 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 637 | begin 638 | Log(strErrFileNo); 639 | exit; 640 | end; 641 | 642 | //Fill fMemoryBundle with the new file - decrypting if necessary 643 | fMemoryBundle.Clear; 644 | if fEncrypted then 645 | begin 646 | //much quicker to copy to memory then decrypt than do it from disk 647 | TempStream := TMemoryStream.Create; 648 | try 649 | fBundle.Position := TFSBFile(BundleFiles.Items[FileNo]).Offset; 650 | TempStream.CopyFrom(fBundle, TFSBFile(BundleFiles.Items[FileNo]).size); 651 | DecryptFSB(Tempstream, 0, TempStream.size, fMemoryBundle, FSBKey, 652 | TFSBFile(BundleFiles.Items[FileNo]).Offset); //Provide the offset as last param so we know where the key should start from in the original file 653 | finally 654 | TempStream.Free; 655 | end; 656 | end 657 | else 658 | begin 659 | fBundle.Position := TFSBFile(BundleFiles.Items[FileNo]).Offset; 660 | fMemoryBundle.CopyFrom(fBundle, TFSBFile(BundleFiles.Items[FileNo]).size); 661 | end; 662 | 663 | fMemoryBundle.Position := 0; 664 | 665 | Ext:=Uppercase(ExtractFileExt(TFSBFile(BundleFiles.Items[FileNo]).FileName)); 666 | 667 | if (TFSBFile(BundleFiles.Items[FileNo]).Codec = FMOD_SOUND_FORMAT_PCM8) or 668 | ((TFSBFile(BundleFiles.Items[FileNo]).Codec = FMOD_SOUND_FORMAT_PCM16)) then 669 | begin 670 | WavStream := TWaveStream.Create(DestStream, 671 | TFSBFile(BundleFiles.Items[FileNo]).Channels, 672 | TFSBFile(BundleFiles.Items[FileNo]).Bits, 673 | TFSBFile(BundleFiles.Items[FileNo]).Freq ); 674 | try 675 | WavStream.CopyFrom(fMemoryBundle, TFSBFile(BundleFiles.Items[FileNo]).Size); 676 | finally 677 | WavStream.Free; 678 | end; 679 | end 680 | else 681 | if (TFSBFile(BundleFiles.Items[FileNo]).Codec = FMOD_SOUND_FORMAT_MPEG) then //fix broken mp3's 682 | begin 683 | SaveFixedMP3Stream(fMemoryBundle, DestStream, 684 | TFSBFile(BundleFiles.Items[FileNo]).Size, 685 | TFSBFile(BundleFiles.Items[FileNo]).Channels); 686 | end 687 | else 688 | DestStream.CopyFrom(fMemoryBundle, TFSBFile(BundleFiles.Items[FileNo]).Size); 689 | 690 | 691 | DestStream.Position:=0; 692 | end; 693 | 694 | function ShouldDownmixChannels(Channels, Frame: integer): boolean; 695 | var 696 | ChansResult: integer; 697 | begin 698 | if (Channels and 1) = 1 then 699 | ChansResult := Channels 700 | else 701 | ChansResult := Channels div 2; 702 | 703 | ChansResult := frame mod ChansResult; 704 | 705 | if (Channels <= 2) or (ChansResult = 0) then 706 | result := true 707 | else 708 | result := false; 709 | end; 710 | 711 | procedure TFSBManager.SaveFixedMP3Stream(InStream, OutStream: TStream; FileSize, 712 | Channels: integer); 713 | var 714 | Frame, FrameSize, n: integer; 715 | Buffer: TBuffer; 716 | TempBuffer: TBuffer; 717 | tmpMpegHeader: TMpegHeader; 718 | TempByte: Byte; 719 | begin 720 | Frame := 0; 721 | while FileSize > 0 do 722 | begin 723 | SetLength(buffer, 3); 724 | if InStream.Read(Buffer[0], 3) <> 3 then //bytes read 725 | break; 726 | 727 | Dec(FileSize, 3); 728 | FrameSize := 0; 729 | //read 3 bytes, if invalid header then read another and shuffle the bytes up in the buffer and check again. 730 | while FileSize > 0 do 731 | begin 732 | tmpMpegHeader := GetValidatedHeader(Buffer, 0); 733 | FrameSize := tmpMpegHeader.framelength; 734 | if tmpMpegHeader.valid = true then 735 | if FrameSize > 0 then break; 736 | 737 | if InStream.Size - InStream.Position < 1 then //Just in case 738 | begin 739 | Log('Tried to read beyond stream in SaveFixedMP3Stream()'); 740 | exit; 741 | end; 742 | InStream.Read(TempByte, 1); 743 | 744 | Dec(FileSize, 1); 745 | 746 | Buffer[0] := Buffer[1]; 747 | Buffer[1] := Buffer[2]; 748 | Buffer[2] := tempbyte; 749 | end; 750 | 751 | if FileSize < 0 then break; 752 | 753 | dec(FrameSize, 3); 754 | 755 | if ShouldDownmixChannels(Channels, Frame) then 756 | Outstream.Write(Buffer[0], 3); 757 | 758 | if FrameSize > 0 then 759 | begin 760 | SetLength(TempBuffer,FrameSize); 761 | n := InStream.Read(TempBuffer[0], FrameSize); 762 | dec(FileSize, n); 763 | 764 | if ShouldDownmixChannels(Channels, Frame) then 765 | OutStream.Write(TempBuffer[0], n); 766 | 767 | if n <> FrameSize then 768 | break; 769 | end; 770 | 771 | inc(Frame); 772 | end; 773 | 774 | end; 775 | 776 | end. 777 | -------------------------------------------------------------------------------- /uDFExplorer_ISBManager.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | { 15 | Used in Psychonauts pc version for music/speech/sfx. 16 | Xbox version audio is different, its wavebanks, its supported in Psychonauts Explorer. 17 | TODO - Save raw files still does the decoding. Need to look at base class default parameter RawDump= for this maybe 18 | } 19 | unit uDFExplorer_ISBManager; 20 | 21 | interface 22 | 23 | uses 24 | classes, sysutils, contnrs, forms, 25 | JCLstrings, JCLFileUtils, 26 | uWaveWriter, uXboxAdpcmDecoder, uFileReader, uMemReader, 27 | uDFExplorer_BaseBundleManager, uDFExplorer_Types, uDFExplorer_Funcs; 28 | 29 | type 30 | TAudioFormat = ( 31 | PCM, 32 | OGG, 33 | XBOX_ADPCM 34 | ); 35 | 36 | TISBAudio = class 37 | Format: TAudioFormat; 38 | Channels: integer; 39 | Samplerate: integer; 40 | end; 41 | 42 | TISBManager = class (TBundleManager) 43 | protected 44 | AudioInfos: TObjectList; //Stores info about each audio file for dumping later 45 | fMemoryBundle: TExplorerMemoryStream; 46 | function DetectBundle: boolean; override; 47 | function GetFilesCount: integer; override; 48 | function GetFileName(Index: integer): string; override; 49 | function GetFileSize(Index: integer): integer; override; 50 | function GetFileOffset(Index: integer): int64; override; 51 | function GetFileType(Index: integer): TFiletype; override; 52 | function GetFileExtension(Index: integer): string; override; 53 | procedure Log(Text: string); override; 54 | procedure ParseISBFile; 55 | public 56 | BundleFiles: TObjectList; 57 | constructor Create(ResourceFile: string); override; 58 | destructor Destroy; override; 59 | procedure ParseFiles; override; 60 | procedure SaveFile(FileNo: integer; DestDir, FileName: string); override; 61 | procedure SaveFileToStream(FileNo: integer; DestStream: TStream); override; 62 | procedure SaveFiles(DestDir: string); override; 63 | property Count: integer read GetFilesCount; 64 | property FileName[Index: integer]: string read GetFileName; 65 | property FileSize[Index: integer]: integer read GetFileSize; 66 | property FileOffset[Index: integer]: int64 read GetFileOffset; 67 | property FileType[Index: integer]: TFileType read GetFileType; 68 | property FileExtension[Index: integer]: string read GetFileExtension; 69 | 70 | end; 71 | 72 | const 73 | strErrInvalidFile: string = 'Not a valid bundle'; 74 | 75 | implementation 76 | 77 | constructor TISBManager.Create(ResourceFile: string); 78 | begin 79 | try 80 | fBundle:=TExplorerFileStream.Create(ResourceFile); 81 | except on E: EInvalidFile do 82 | raise; 83 | end; 84 | 85 | fBundleFileName := ExtractFileName(ResourceFile); 86 | BundleFiles := TObjectList.Create(True); 87 | AudioInfos := TObjectList.Create(True); 88 | 89 | if DetectBundle = false then 90 | raise EInvalidFile.Create( strErrInvalidFile ); 91 | end; 92 | 93 | destructor TISBManager.Destroy; 94 | begin 95 | if BundleFiles <> nil then 96 | begin 97 | BundleFiles.Free; 98 | BundleFiles := nil; 99 | end; 100 | 101 | if AudioInfos <> nil then 102 | begin 103 | AudioInfos.Free; 104 | AudioInfos := nil; 105 | end; 106 | 107 | if fMemoryBundle <> nil then 108 | fMemoryBundle.free; 109 | 110 | if fBundle <> nil then 111 | fBundle.free; 112 | 113 | inherited; 114 | end; 115 | 116 | function TISBManager.DetectBundle: boolean; 117 | begin 118 | Result := false; 119 | 120 | if fBundle.ReadBlockName = 'RIFF' then 121 | begin 122 | fBundle.Position := 8; 123 | if fBundle.ReadBlockName = 'isbf' then 124 | begin 125 | fBundle.BigEndian := false; 126 | fBigEndian := false; 127 | 128 | //Keep everything in memory - Biggest isb file is 92mb. Dumping is slow and this helps speed it up a little. 129 | fMemoryBundle := TExplorerMemoryStream.Create; 130 | fBundle.Position := 0; 131 | fMemoryBundle.CopyFrom(fBundle, fBundle.Size); 132 | fMemoryBundle.Position := 0; 133 | 134 | Result := true; 135 | end; 136 | end; 137 | end; 138 | 139 | function TISBManager.GetFileExtension(Index: integer): string; 140 | begin 141 | if (not assigned(BundleFiles)) or 142 | (index < 0) or 143 | (index > GetFilesCount) then 144 | result:= '' 145 | else 146 | result:=TDFFile(BundleFiles.Items[Index]).FileExtension; 147 | end; 148 | 149 | function TISBManager.GetFileName(Index: integer): string; 150 | begin 151 | if (not assigned(BundleFiles)) or 152 | (index < 0) or 153 | (index > GetFilesCount) then 154 | result:= '' 155 | else 156 | result:=TDFFile(BundleFiles.Items[Index]).FileName; 157 | end; 158 | 159 | function TISBManager.GetFileOffset(Index: integer): int64; 160 | begin 161 | if (not assigned(BundleFiles)) or 162 | (index < 0) or 163 | (index > GetFilesCount) then 164 | result:=0 165 | else 166 | result:=TDFFile(BundleFiles.Items[Index]).offset; 167 | end; 168 | 169 | function TISBManager.GetFilesCount: integer; 170 | begin 171 | if BundleFiles <> nil then 172 | result:=BundleFiles.Count 173 | else 174 | result:=0; 175 | end; 176 | 177 | function TISBManager.GetFileSize(Index: integer): integer; 178 | begin 179 | if (not assigned(BundleFiles)) or 180 | (index < 0) or 181 | (index > GetFilesCount) then 182 | result:=-1 183 | else 184 | result:=TDFFile(BundleFiles.Items[Index]).size; 185 | end; 186 | 187 | function TISBManager.GetFileType(Index: integer): TFileType; 188 | begin 189 | if (not assigned(BundleFiles)) or 190 | (index < 0) or 191 | (index > GetFilesCount) then 192 | result:= ft_Unknown 193 | else 194 | result:=TDFFile(BundleFiles.Items[Index]).FileType; 195 | end; 196 | 197 | procedure TISBManager.Log(Text: string); 198 | begin 199 | if assigned(fOnDebug) then fOnDebug(Text); 200 | end; 201 | 202 | procedure TISBManager.ParseFiles; 203 | begin 204 | if fBigEndian then //All LE reading auto converted to BE 205 | Log('Detected as : big endian'); 206 | 207 | ParseISBFile; 208 | end; 209 | 210 | 211 | procedure TISBManager.ParseISBFile; 212 | var 213 | BlockSize, i, Samplerate, Channels: integer; 214 | BlockName, FileName, TempExt: string; 215 | NextChar: Char; 216 | FileObject: TDFFile; 217 | AudioObject: TISBAudio; 218 | TempFormat: TAudioFormat; 219 | begin 220 | fMemoryBundle.Position := 16; //After 'isbftitle' string 221 | BlockSize := fMemoryBundle.ReadDWord; //Size of text block 222 | fMemoryBundle.Position := fMemoryBundle.Position + BlockSize; //Seek past the text block 223 | 224 | TempFormat := XBOX_ADPCM; //Default value. If its pcm or ogg this will get changed below 225 | Samplerate := 0; 226 | Channels := 0; 227 | while fMemoryBundle.Position < fMemoryBundle.Size do 228 | begin 229 | BlockName := fMemoryBundle.ReadBlockName; 230 | 231 | if BlockName = 'LIST' then 232 | begin 233 | fMemoryBundle.Seek(12, sofromcurrent); //list block size + ''isbftitl'' bytes 234 | BlockSize := fMemoryBundle.ReadDWord; 235 | 236 | FileName := ''; 237 | for i := 0 to BlockSize - 1 do 238 | begin 239 | NextChar := chr(fMemoryBundle.ReadByte); 240 | if NextChar <> #0 then //Name string has null byte between each character 241 | FileName := FileName + NextChar; 242 | end; 243 | end 244 | else 245 | if blockname='sinf' then 246 | begin 247 | fMemoryBundle.Seek(12, sofromcurrent); 248 | Samplerate := fMemoryBundle.ReadDWord; 249 | fMemoryBundle.Seek(8, sofromcurrent); 250 | end 251 | else 252 | if blockname='chnk' then 253 | begin 254 | fMemoryBundle.Seek(4, sofromcurrent); 255 | Channels := fMemoryBundle.ReadDWord; 256 | end 257 | else 258 | if blockname='cmpi' then 259 | begin 260 | fMemoryBundle.Seek(24, sofromcurrent); 261 | if fMemoryBundle.ReadDWord = 1053609165 then 262 | TempFormat := PCM; 263 | end 264 | else 265 | if blockname='data' then 266 | begin 267 | BlockSize := fMemoryBundle.ReadDWord; 268 | if BlockSize mod 2 <> 0 then 269 | BlockSize := BlockSize +1; 270 | 271 | if fMemoryBundle.ReadBlockName='OggS' then 272 | TempFormat := OGG; 273 | 274 | fMemoryBundle.Seek(-4, sofromcurrent); //Now at start of audio data 275 | 276 | {Some filenames have 'loop' in the file extension eg .aif-loop 277 | Since the file extension gets stripped when they are dumped this means 278 | you can have 2 files with the same name eg AsylumExt.aif and AsylumExt.aif-loop 279 | both will have the same name when dumped. So: 280 | Delete loop from the file extension and add '-Loop' to the filename. 281 | } 282 | TempExt := ExtractFileExt( FileName ); 283 | if stripos('loop', TempExt) > 0 then //It has 'loop' in the file extension. 284 | begin 285 | TempExt := StringReplace(TempExt, '-loop', '', [rfIgnoreCase]); 286 | Filename := PathExtractFileNameNoExt( FileName ) + '-Loop' + TempExt; //Add 'loop' to filename 287 | end; 288 | 289 | FileObject := TDFFile.Create; 290 | FileObject.Compressed := false; 291 | FileObject.FileTypeIndex := -1; 292 | //FileObject.FileName := FileName; 293 | FileObject.Offset := fMemoryBundle.Position; 294 | FileObject.Size := BlockSize; 295 | FileObject.FileType := ft_Audio; 296 | 297 | {Psychonauts uses .wav .aif. cdda for file extensions inside isb bundles. 298 | To avoid checks in the main form for different extensions its easiest to 299 | just change the extension to its actual type here 300 | } 301 | if TempFormat = OGG then 302 | begin 303 | FileObject.FileExtension := 'ogg'; 304 | FileObject.FileName := ChangeFileExt(FileName, '.ogg'); 305 | end 306 | else 307 | begin 308 | FileObject.FileExtension := 'wav'; 309 | FileObject.FileName := ChangeFileExt(FileName, '.wav'); 310 | end; 311 | BundleFiles.Add(FileObject); 312 | 313 | AudioObject := TISBAudio.Create; 314 | AudioObject.Channels := Channels; 315 | AudioObject.Samplerate := Samplerate; 316 | AudioObject.Format := TempFormat; 317 | AudioInfos.Add(AudioObject); 318 | 319 | //Reset the variables 320 | TempFormat := XBOX_ADPCM; //Want this to be the default value if PCM/OGG dont get set 321 | Channels := 0; //Not really necessary but helpful for debugging 322 | Samplerate := 0; 323 | 324 | fMemoryBundle.Seek(BlockSize, sofromcurrent); 325 | end 326 | else //Unknown block - seek past 327 | begin 328 | blocksize := fMemoryBundle.ReadDWord; 329 | fMemoryBundle.Seek(blocksize, sofromcurrent) 330 | end; 331 | end; 332 | 333 | if (Assigned(FOnDoneLoading)) then 334 | FOnDoneLoading(BundleFiles.Count); 335 | end; 336 | 337 | procedure TISBManager.SaveFile(FileNo: integer; DestDir, FileName: string); 338 | var 339 | SaveFile: TFileStream; 340 | begin 341 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 342 | begin 343 | Log(strErrFileSize); 344 | exit; 345 | end; 346 | 347 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 348 | begin 349 | Log(strErrFileNo); 350 | exit; 351 | end; 352 | 353 | //Log(strSavingFile + FileName); 354 | 355 | SaveFile := tfilestream.Create(IncludeTrailingPathDelimiter(DestDir) + FileName, 356 | fmOpenWrite or fmCreate); 357 | try 358 | SaveFileToStream(FileNo,SaveFile); 359 | finally 360 | SaveFile.Free; 361 | end; 362 | end; 363 | 364 | procedure TISBManager.SaveFiles(DestDir: string); 365 | var 366 | i: integer; 367 | SaveFile: TFileStream; 368 | begin 369 | for I := 0 to BundleFiles.Count - 1 do 370 | begin 371 | ForceDirectories(extractfilepath(IncludeTrailingPathDelimiter(DestDir) + 372 | ExtractPartialPath( TDFFile(BundleFiles.Items[i]).FileName))); 373 | SaveFile:=TFileStream.Create(IncludeTrailingPathDelimiter(DestDir) + 374 | TDFFile(BundleFiles.Items[i]).FileName , fmOpenWrite or fmCreate); 375 | try 376 | SaveFileToStream(i, SaveFile); 377 | finally 378 | SaveFile.free; 379 | if Assigned(FOnProgress) then FOnProgress(GetFilesCount -1, i); 380 | Application.Processmessages; 381 | end; 382 | end; 383 | end; 384 | 385 | procedure TISBManager.SaveFileToStream(FileNo: integer; DestStream: TStream); 386 | var 387 | WavStream: TWaveStream; 388 | TempStream: TMemoryStream; 389 | XboxAdpcmDecoder: TXboxAdpcmDecoder; 390 | begin 391 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 392 | begin 393 | Log(strErrFileSize); 394 | exit; 395 | end; 396 | 397 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 398 | begin 399 | Log(strErrFileNo); 400 | exit; 401 | end; 402 | 403 | fMemoryBundle.Position := TDFFile(BundleFiles.Items[FileNo]).Offset; 404 | 405 | if TISBAudio(AudioInfos[FileNo]).Format = OGG then 406 | begin 407 | DestStream.CopyFrom(fMemoryBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 408 | end 409 | else 410 | if TISBAudio(AudioInfos[FileNo]).Format = PCM then 411 | begin 412 | WavStream := TWaveStream.Create(DestStream, 413 | TISBAudio(AudioInfos[FileNo]).Channels, 16, TISBAudio(AudioInfos[FileNo]).Samplerate ); 414 | try 415 | WavStream.CopyFrom(fMemoryBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 416 | finally 417 | WavStream.Free; 418 | end; 419 | end 420 | else 421 | if TISBAudio(AudioInfos[FileNo]).Format = XBOX_ADPCM then 422 | begin 423 | WavStream := TWaveStream.Create(DestStream, 424 | TISBAudio(AudioInfos[FileNo]).Channels, 16, TISBAudio(AudioInfos[FileNo]).Samplerate ); 425 | try 426 | XboxAdpcmDecoder := TXboxAdpcmDecoder.Create(TISBAudio(AudioInfos[FileNo]).Channels); 427 | try 428 | XboxAdpcmDecoder.Decode(fMemoryBundle, WavStream, fMemoryBundle.Position, TDFFile(BundleFiles.Items[FileNo]).Size); 429 | finally 430 | XboxAdpcmDecoder.Free; 431 | end; 432 | finally 433 | WavStream.Free; 434 | end; 435 | end 436 | else 437 | begin 438 | Log('Unknown audio format! Save aborted'); 439 | exit; 440 | end; 441 | 442 | DestStream.Position:=0; 443 | end; 444 | 445 | end. 446 | -------------------------------------------------------------------------------- /uDFExplorer_LABManager.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | { 15 | LAB Files - Grim Remastered (plus old versions of Grim Fandango and EMI) 16 | } 17 | 18 | 19 | unit uDFExplorer_lABManager; 20 | 21 | interface 22 | 23 | uses 24 | classes, sysutils, Contnrs, forms, 25 | uDFExplorer_BaseBundleManager, uFileReader, uMemReader, uDFExplorer_Types, 26 | uDFExplorer_Funcs, uZlib; 27 | 28 | type 29 | TLABManager = class (TBundleManager) 30 | private 31 | fBigEndian: boolean; 32 | protected 33 | fBundle: TExplorerFileStream; 34 | fBundleFileName: string; 35 | function DetectBundle: boolean; override; 36 | function GetFilesCount: integer; override; 37 | function GetFileName(Index: integer): string; override; 38 | function GetFileSize(Index: integer): integer; override; 39 | function GetFileOffset(Index: integer): int64; override; 40 | function GetFileType(Index: integer): TFiletype; override; 41 | function GetFileExtension(Index: integer): string; override; 42 | procedure Log(Text: string); override; 43 | procedure ParseLAB; 44 | public 45 | BundleFiles: TObjectList; 46 | constructor Create(ResourceFile: string); override; 47 | destructor Destroy; override; 48 | procedure ParseFiles; override; 49 | procedure SaveFile(FileNo: integer; DestDir, FileName: string); override; 50 | procedure SaveFileToStream(FileNo: integer; DestStream: TStream); override; 51 | procedure SaveFiles(DestDir: string); override; 52 | property Count: integer read GetFilesCount; 53 | property FileName[Index: integer]: string read GetFileName; 54 | property FileSize[Index: integer]: integer read GetFileSize; 55 | property FileOffset[Index: integer]: int64 read GetFileOffset; 56 | property FileType[Index: integer]: TFileType read GetFileType; 57 | property FileExtension[Index: integer]: string read GetFileExtension; 58 | property BigEndian: boolean read fBigEndian; 59 | end; 60 | 61 | const 62 | strErrInvalidFile: string = 'Not a valid bundle'; 63 | 64 | implementation 65 | 66 | { TBundleManager } 67 | 68 | 69 | constructor TLABManager.Create(ResourceFile: string); 70 | begin 71 | try 72 | fBundle:=TExplorerFileStream.Create(ResourceFile); 73 | except on E: EInvalidFile do 74 | raise; 75 | end; 76 | 77 | fBundleFileName:=ExtractFileName(ResourceFile); 78 | BundleFiles:=TObjectList.Create(true); 79 | 80 | if DetectBundle = false then 81 | raise EInvalidFile.Create( strErrInvalidFile ); 82 | end; 83 | 84 | destructor TLABManager.Destroy; 85 | begin 86 | if BundleFiles <> nil then 87 | begin 88 | BundleFiles.Free; 89 | BundleFiles:=nil; 90 | end; 91 | 92 | if fBundle <> nil then 93 | fBundle.free; 94 | 95 | inherited; 96 | end; 97 | 98 | function TLABManager.DetectBundle: boolean; 99 | var 100 | BlockHeader: integer; 101 | begin 102 | Result := false; 103 | BlockHeader := fBundle.ReadDWord; 104 | 105 | if BlockHeader = 1312964940 then //LABN 106 | begin 107 | Result := true; 108 | fBundle.BigEndian := false; 109 | fBigEndian := false; 110 | end 111 | end; 112 | 113 | function TLABManager.GetFileExtension(Index: integer): string; 114 | begin 115 | if (not assigned(BundleFiles)) or 116 | (index < 0) or 117 | (index > GetFilesCount) then 118 | result:= '' 119 | else 120 | result:=TDFFile(BundleFiles.Items[Index]).FileExtension; 121 | end; 122 | 123 | function TLABManager.GetFileName(Index: integer): string; 124 | begin 125 | if (not assigned(BundleFiles)) or 126 | (index < 0) or 127 | (index > GetFilesCount) then 128 | result:= '' 129 | else 130 | result:=TDFFile(BundleFiles.Items[Index]).FileName; 131 | end; 132 | 133 | function TLABManager.GetFileOffset(Index: integer): int64; 134 | begin 135 | if (not assigned(BundleFiles)) or 136 | (index < 0) or 137 | (index > GetFilesCount) then 138 | result:=0 139 | else 140 | result:=TDFFile(BundleFiles.Items[Index]).offset; 141 | end; 142 | 143 | function TLABManager.GetFilesCount: integer; 144 | begin 145 | if BundleFiles <> nil then 146 | result:=BundleFiles.Count 147 | else 148 | result:=0; 149 | end; 150 | 151 | function TLABManager.GetFileSize(Index: integer): integer; 152 | begin 153 | if (not assigned(BundleFiles)) or 154 | (index < 0) or 155 | (index > GetFilesCount) then 156 | result:=-1 157 | else 158 | result:=TDFFile(BundleFiles.Items[Index]).size; 159 | end; 160 | 161 | function TLABManager.GetFileType(Index: integer): TFileType; 162 | begin 163 | if (not assigned(BundleFiles)) or 164 | (index < 0) or 165 | (index > GetFilesCount) then 166 | result:= ft_Unknown 167 | else 168 | result:=TDFFile(BundleFiles.Items[Index]).FileType; 169 | end; 170 | 171 | procedure TLABManager.Log(Text: string); 172 | begin 173 | if assigned(fOnDebug) then fOnDebug(Text); 174 | end; 175 | 176 | procedure TLABManager.ParseFiles; 177 | begin 178 | if fBigEndian then //All LE reading auto converted to BE 179 | Log('Detected as : big endian'); 180 | 181 | 182 | ParseLAB; 183 | end; 184 | 185 | procedure TLABManager.ParseLAB; 186 | var 187 | NumFiles, NameDirSize, i, OldPosition, FilenameOffset: integer; 188 | FileObject: TDFFile; 189 | begin 190 | fBundle.Position:=0; 191 | if fBundle.ReadBlockName <> 'LABN' then 192 | begin 193 | raise EInvalidFile.Create( strErrInvalidFile ); 194 | end; 195 | 196 | //Read Header 197 | fBundle.seek(4, sofromcurrent); //version 198 | NumFiles := fBundle.ReadDWord; //number of files 199 | NameDirSize := fBundle.ReadDWord; //name directory size 200 | 201 | //Parse File Records 202 | for I := 0 to NumFiles -1 do 203 | begin 204 | FileObject := TDFFile.Create; 205 | FileObject.Compressed := false; 206 | FileObject.CompressionType := 0; 207 | FileObject.FileTypeIndex := -1; 208 | 209 | FilenameOffset := fBundle.ReadDWord; //Offset of name in name directory 210 | fBundle.Seek(-4, soFromCurrent); 211 | 212 | //Now get filename and filetype 213 | OldPosition := fBundle.Position; 214 | fBundle.Position := fBundle.Position + ((NumFiles - i) * 16) + FilenameOffset; //Should be at the filename now 215 | FileObject.FileName := PChar(fBundle.ReadString(100)); //Null terminated - wont actually be 100 chars 216 | FileObject.FileExtension := ExtractFileExt( FileObject.FileName ); 217 | 218 | //Get the file type 219 | //Big hack for Grim Vima wav files 220 | if Uppercase(FileObject.FileExtension) = '.WAV' then 221 | FileObject.FileType := GetFileTypeFromFileExtension( FileObject.FileExtension, 'GRIMWAV') 222 | else 223 | FileObject.FileType := GetFileTypeFromFileExtension( FileObject.FileExtension); 224 | 225 | if (FileObject.FileType = ft_Unknown) and (FileObject.FileExtension <> '') then 226 | Log('Unknown file type ' + FileObject.FileExtension); 227 | 228 | //Correct the file extension 229 | //Dont want the . on the file extension 230 | //if (length(FileObject.FileExtension)>0) and (FileObject.FileExtension[1]='.') then 231 | // delete(FileObject.FileExtension,1,1); 232 | 233 | 234 | fBundle.Position := OldPosition + 4; //dont need namedir offset anymore 235 | FileObject.Offset := fBundle.ReadDWord; 236 | FileObject.Size := fBundle.ReadDWord; 237 | fBundle.Seek(4, soFromCurrent); 238 | 239 | BundleFiles.Add(FileObject); 240 | end; 241 | 242 | if (Assigned(FOnDoneLoading)) then 243 | FOnDoneLoading(NumFiles); 244 | end; 245 | 246 | procedure TLABManager.SaveFile(FileNo: integer; DestDir, FileName: string); 247 | var 248 | SaveFile: TFileStream; 249 | begin 250 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 251 | begin 252 | Log(strErrFileSize); 253 | exit; 254 | end; 255 | 256 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 257 | begin 258 | Log(strErrFileNo); 259 | exit; 260 | end; 261 | 262 | SaveFile:=tfilestream.Create(IncludeTrailingPathDelimiter(DestDir) + FileName, 263 | fmOpenWrite or fmCreate); 264 | try 265 | SaveFileToStream(FileNo,SaveFile); 266 | finally 267 | SaveFile.Free; 268 | end; 269 | 270 | end; 271 | 272 | procedure TLABManager.SaveFiles(DestDir: string); 273 | var 274 | i: integer; 275 | SaveFile: TFileStream; 276 | begin 277 | for I := 0 to BundleFiles.Count - 1 do 278 | begin 279 | ForceDirectories(extractfilepath(IncludeTrailingPathDelimiter(DestDir) + 280 | ExtractPartialPath( TDFFile(BundleFiles.Items[i]).FileName))); 281 | 282 | SaveFile:=TFileStream.Create(IncludeTrailingPathDelimiter(DestDir) + 283 | TDFFile(BundleFiles.Items[i]).FileName , fmOpenWrite or fmCreate); 284 | try 285 | SaveFileToStream(i, SaveFile); 286 | finally 287 | SaveFile.free; 288 | if Assigned(FOnProgress) then FOnProgress(GetFilesCount -1, i); 289 | Application.Processmessages; 290 | end; 291 | end; 292 | 293 | end; 294 | 295 | procedure TLABManager.SaveFileToStream(FileNo: integer; DestStream: TStream); 296 | var 297 | Ext: string; 298 | TempStream: TMemoryStream; 299 | begin 300 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 301 | begin 302 | Log(strErrFileSize); 303 | exit; 304 | end; 305 | 306 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 307 | begin 308 | Log(strErrFileNo); 309 | exit; 310 | end; 311 | 312 | Ext:=Uppercase(ExtractFileExt(TDFFile(BundleFiles.Items[FileNo]).FileName)); 313 | 314 | fBundle.Seek(TDFFile(BundleFiles.Items[FileNo]).Offset, sofrombeginning); 315 | 316 | 317 | if TDFFile(BundleFiles.Items[FileNo]).Compressed then 318 | begin 319 | TempStream := tmemorystream.Create; 320 | try 321 | TempStream.CopyFrom(fBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 322 | //tempstream.SaveToFile('c:\users\ben\desktop\testfile'); 323 | Tempstream.Position := 0; 324 | DecompressZLib(TempStream, TDFFile(BundleFiles.Items[FileNo]).UnCompressedSize, 325 | DestStream); 326 | finally 327 | TempStream.Free; 328 | end 329 | end 330 | else 331 | DestStream.CopyFrom(fBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 332 | 333 | DestStream.Position:=0; 334 | end; 335 | 336 | end. 337 | -------------------------------------------------------------------------------- /uDFExplorer_LPAKManager.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | { 15 | Used in DOTT Remastered - same format as used in MI Special Editions 16 | } 17 | 18 | unit uDFExplorer_LPAKManager; 19 | 20 | interface 21 | 22 | uses 23 | classes, sysutils, Contnrs, forms, 24 | uDFExplorer_BaseBundleManager, uFileReader, uMemReader, uDFExplorer_Types, 25 | uDFExplorer_Funcs, uZlib; 26 | 27 | type 28 | TLPAKManager = class (TBundleManager) 29 | private 30 | fBigEndian: boolean; 31 | protected 32 | fBundle: TExplorerFileStream; 33 | fBundleFileName: string; 34 | function DetectBundle: boolean; override; 35 | function GetFilesCount: integer; override; 36 | function GetFileName(Index: integer): string; override; 37 | function GetFileSize(Index: integer): integer; override; 38 | function GetFileOffset(Index: integer): int64; override; 39 | function GetFileType(Index: integer): TFiletype; override; 40 | function GetFileExtension(Index: integer): string; override; 41 | procedure Log(Text: string); override; 42 | public 43 | BundleFiles: TObjectList; 44 | constructor Create(ResourceFile: string); override; 45 | destructor Destroy; override; 46 | procedure ParseFiles; override; 47 | procedure ParseFilesV1; 48 | procedure ParseFilesFullThrottle; 49 | procedure SaveFile(FileNo: integer; DestDir, FileName: string); override; 50 | procedure SaveFileToStream(FileNo: integer; DestStream: TStream); override; 51 | procedure SaveFiles(DestDir: string); override; 52 | property Count: integer read GetFilesCount; 53 | property FileName[Index: integer]: string read GetFileName; 54 | property FileSize[Index: integer]: integer read GetFileSize; 55 | property FileOffset[Index: integer]: int64 read GetFileOffset; 56 | property FileType[Index: integer]: TFileType read GetFileType; 57 | property FileExtension[Index: integer]: string read GetFileExtension; 58 | property BigEndian: boolean read fBigEndian; 59 | end; 60 | 61 | const 62 | strErrInvalidFile: string = 'Not a valid bundle'; 63 | 64 | implementation 65 | 66 | { TBundleManager } 67 | 68 | 69 | constructor TLPAKManager.Create(ResourceFile: string); 70 | begin 71 | try 72 | fBundle:=TExplorerFileStream.Create(ResourceFile); 73 | except on E: EInvalidFile do 74 | raise; 75 | end; 76 | 77 | fBundleFileName:=ExtractFileName(ResourceFile); 78 | BundleFiles:=TObjectList.Create(true); 79 | 80 | if DetectBundle = false then 81 | raise EInvalidFile.Create( strErrInvalidFile ); 82 | end; 83 | 84 | destructor TLPAKManager.Destroy; 85 | begin 86 | if BundleFiles <> nil then 87 | begin 88 | BundleFiles.Free; 89 | BundleFiles:=nil; 90 | end; 91 | 92 | if fBundle <> nil then 93 | fBundle.free; 94 | 95 | inherited; 96 | end; 97 | 98 | function TLPAKManager.DetectBundle: boolean; 99 | var 100 | Blockname: string; 101 | begin 102 | Result := false; 103 | BlockName := fBundle.ReadBlockName; 104 | 105 | if BlockName = 'KAPL' then 106 | begin 107 | Result := true; 108 | fBundle.BigEndian := false; 109 | fBigEndian := false; 110 | end 111 | else 112 | if BlockName = 'LPAK' then 113 | begin 114 | Result := true; 115 | fBundle.BigEndian := true; 116 | fBigEndian := true; 117 | end; 118 | 119 | end; 120 | 121 | function TLPAKManager.GetFileExtension(Index: integer): string; 122 | begin 123 | if (not assigned(BundleFiles)) or 124 | (index < 0) or 125 | (index > GetFilesCount) then 126 | result:= '' 127 | else 128 | result:=TDFFile(BundleFiles.Items[Index]).FileExtension; 129 | end; 130 | 131 | function TLPAKManager.GetFileName(Index: integer): string; 132 | begin 133 | if (not assigned(BundleFiles)) or 134 | (index < 0) or 135 | (index > GetFilesCount) then 136 | result:= '' 137 | else 138 | result:=TDFFile(BundleFiles.Items[Index]).FileName; 139 | end; 140 | 141 | function TLPAKManager.GetFileOffset(Index: integer): int64; 142 | begin 143 | if (not assigned(BundleFiles)) or 144 | (index < 0) or 145 | (index > GetFilesCount) then 146 | result:=0 147 | else 148 | result:=TDFFile(BundleFiles.Items[Index]).offset; 149 | end; 150 | 151 | function TLPAKManager.GetFilesCount: integer; 152 | begin 153 | if BundleFiles <> nil then 154 | result:=BundleFiles.Count 155 | else 156 | result:=0; 157 | end; 158 | 159 | function TLPAKManager.GetFileSize(Index: integer): integer; 160 | begin 161 | if (not assigned(BundleFiles)) or 162 | (index < 0) or 163 | (index > GetFilesCount) then 164 | result:=-1 165 | else 166 | result:=TDFFile(BundleFiles.Items[Index]).size; 167 | end; 168 | 169 | function TLPAKManager.GetFileType(Index: integer): TFileType; 170 | begin 171 | if (not assigned(BundleFiles)) or 172 | (index < 0) or 173 | (index > GetFilesCount) then 174 | result:= ft_Unknown 175 | else 176 | result:=TDFFile(BundleFiles.Items[Index]).FileType; 177 | end; 178 | 179 | procedure TLPAKManager.Log(Text: string); 180 | begin 181 | if assigned(fOnDebug) then fOnDebug(Text); 182 | end; 183 | 184 | 185 | 186 | procedure TLPAKManager.ParseFiles; 187 | var 188 | version: word; 189 | begin 190 | fBundle.Position := 6; 191 | version := fBundle.readword; 192 | if version >= 16320 then 193 | ParseFilesFullThrottle 194 | else 195 | ParseFilesV1; 196 | end; 197 | 198 | procedure TLPAKManager.ParseFilesV1; //Pre Full Throttle 199 | var 200 | startOfFileEntries, startOfFileNames, sizeOfIndex, sizeOfFileEntries, sizeOfFileNames, 201 | sizeOfData: integer; 202 | startOfData: cardinal; 203 | numFiles, i, {nameOffs,} currNameOffset: integer; 204 | FileExt, FileName: string; 205 | FileObject: TDFFile; 206 | FileType: TFileType; 207 | const 208 | sizeOfFileRecord: integer = 20; 209 | begin 210 | { PakHeader = record 211 | DWORD magic; (* KAPL -> "LPAK" *) 212 | DWORD version; 213 | DWORD startOfIndex; (* -> 1 DWORD per file *) 214 | DWORD startOfFileEntries; (* -> 5 DWORD per file *) 215 | DWORD startOfFileNames; (* zero-terminated string *) 216 | DWORD startOfData; 217 | DWORD sizeOfIndex; 218 | DWORD sizeOfFileEntries; 219 | DWORD sizeOfFileNames; 220 | DWORD sizeOfData; 221 | end; 222 | PakFileEntry = record 223 | DWORD fileDataPos; (* + startOfData *) 224 | DWORD fileNamePos; (* + startOfFileNames *) 225 | DWORD dataSize; 226 | DWORD dataSize2; (* real size? (always =dataSize) *) 227 | DWORD compressed; (* compressed? (always 0) *) 228 | end; 229 | PakFileEntry = PakFileEntry;} 230 | 231 | if fBigEndian then //All LE reading auto converted to BE 232 | Log('Detected as : big endian'); 233 | 234 | 235 | //Read header 236 | fBundle.Position := 12; 237 | startOfFileEntries := fBundle.ReadDWord; 238 | startOfFileNames := fBundle.ReadDWord; 239 | startOfData := fBundle.ReadDWord; 240 | sizeOfIndex := fBundle.ReadDWord; 241 | sizeOfFileEntries := fBundle.ReadDWord; 242 | sizeOfFileNames := fBundle.ReadDWord; 243 | sizeOfData := fBundle.ReadDWord; 244 | 245 | numFiles := sizeOfFileEntries div sizeOfFileRecord; 246 | 247 | currNameOffset := 0; 248 | 249 | //Parse files 250 | for I := 0 to numFiles - 1 do 251 | begin 252 | fBundle.Position := startOfFileEntries + (sizeOfFileRecord * i); 253 | FileObject := TDFFile.Create; 254 | FileObject.Offset := fBundle.ReadDWord + startOfData; 255 | //nameOffs := fBundle.ReadDWord; 256 | fBundle.Seek(4, soFromCurrent); //Past nameOffs 257 | FileObject.Size := fBundle.ReadDWord; 258 | fBundle.Seek(4, soFromCurrent); // Compressed size? 259 | if fBundle.ReadDWord <> 0 then 260 | begin 261 | Log('Compressed file found in file ' + inttostr(i) + ' at offset ' + 262 | inttostr(FileObject.Offset) + ' hurry up and add support for this!'); 263 | FileObject.Compressed := true; 264 | end; 265 | 266 | //Get filename from filenames table 267 | //In MI2SE - nameOffs is broken - so just ignore it - luckily filenames are stored 268 | //in the same order as the entries in the file records 269 | fBundle.Position := startOfFileNames + currNameOffset; 270 | FileName := PChar(fBundle.ReadString(255)); 271 | inc(currNameOffset, length(FileName) + 1); //+1 because each filename is null terminated 272 | FileObject.FileName := FileName; 273 | 274 | //Correct the file extension 275 | FileExt := ExtractFileExt(FileName); //Dont want the . on the file extension 276 | if (length(FileExt)>0) and (FileExt[1]='.') then 277 | delete(FileExt,1,1); 278 | 279 | FileObject.FileExtension := FileExt; 280 | 281 | 282 | //Correct the file type 283 | FileType := GetFileTypeFromFileExtension( FileExt, Uppercase(ExtractFileExt(Filename))); 284 | //if (FileType = ft_Unknown) and (ExtractFileExt(FileName) <> '') then 285 | // Log('Unknown file type ' + FileExt); 286 | 287 | FileObject.FileType := FileType; 288 | 289 | BundleFiles.Add(FileObject); 290 | end; 291 | 292 | 293 | if (Assigned(FOnDoneLoading)) then 294 | FOnDoneLoading(numFiles); 295 | end; 296 | 297 | procedure TLPAKManager.ParseFilesFullThrottle; 298 | var 299 | startOfFileEntries, startOfFileNames, sizeOfIndex, sizeOfFileEntries, sizeOfFileNames, 300 | sizeOfData: integer; 301 | startOfData: cardinal; 302 | numFiles, i, currNameOffset: integer; 303 | FileExt, FileName: string; 304 | FileObject: TDFFile; 305 | FileType: TFileType; 306 | const 307 | sizeOfFileRecord: integer = 24; //4 bytes bigger than previously 308 | begin 309 | { 310 | Tweaked format - file records are now where index entries were, file record size larger and size field is 8 bytes 311 | 312 | header 313 | file records 6 dwords per file 314 | Index entries 1 dword per file 315 | File names 316 | File data 317 | 318 | 319 | Header: 320 | 4 KAPL 321 | 4 Version? BE? 322 | 4 startOfFileEntries 323 | 4 startOfIndex 324 | 4 startOfFileNames 325 | 4 startOfData 326 | 4 sizeOfIndex 327 | 4 sizeOfFileEntries 328 | 4 sizeOfFileNames 329 | 4 sizeOfData 330 | 8 unknown 331 | } 332 | 333 | if fBigEndian then //All LE reading auto converted to BE 334 | Log('Detected as : big endian'); 335 | 336 | 337 | //Read header 338 | fBundle.Position := 8; 339 | startOfFileEntries := fBundle.ReadDWord; 340 | fBundle.Seek(4, soFromCurrent); //Past startOfIndex 341 | startOfFileNames := fBundle.ReadDWord; 342 | startOfData := fBundle.ReadDWord; 343 | sizeOfIndex := fBundle.ReadDWord; 344 | sizeOfFileEntries := fBundle.ReadDWord; 345 | sizeOfFileNames := fBundle.ReadDWord; 346 | sizeOfData := fBundle.ReadDWord; 347 | 348 | numFiles := sizeOfFileEntries div sizeOfFileRecord; 349 | 350 | currNameOffset := 0; 351 | 352 | //Parse files 353 | for I := 0 to numFiles - 1 do 354 | begin 355 | fBundle.Position := startOfFileEntries + (sizeOfFileRecord * i); 356 | FileObject := TDFFile.Create; 357 | FileObject.Offset := fBundle.ReadQWord + startOfData; 358 | 359 | fBundle.Seek(4, soFromCurrent); //Past nameOffs 360 | FileObject.Size := fBundle.ReadDWord; 361 | if fBundle.ReadDWord <> FileObject.Size then //If size and 'compressed size' differ 362 | begin 363 | Log('Compressed file found in file ' + inttostr(i) + ' at offset ' + 364 | inttostr(FileObject.Offset) + ' hurry up and add support for this!'); 365 | FileObject.Compressed := true; 366 | end; 367 | 368 | //Get filename from filenames table 369 | fBundle.Position := startOfFileNames + currNameOffset; 370 | FileName := PChar(fBundle.ReadString(255)); 371 | inc(currNameOffset, length(FileName) + 1); //+1 because each filename is null terminated 372 | FileObject.FileName := FileName; 373 | 374 | //Correct the file extension 375 | FileExt := ExtractFileExt(FileName); //Dont want the . on the file extension 376 | if (length(FileExt)>0) and (FileExt[1]='.') then 377 | delete(FileExt,1,1); 378 | 379 | FileObject.FileExtension := FileExt; 380 | 381 | //Correct the file type 382 | FileType := GetFileTypeFromFileExtension( FileExt, Uppercase(ExtractFileExt(Filename))); 383 | 384 | 385 | //if (FileType = ft_Unknown) and (ExtractFileExt(FileName) <> '') then 386 | // Log('Unknown file type ' + FileExt); 387 | 388 | FileObject.FileType := FileType; 389 | 390 | BundleFiles.Add(FileObject); 391 | end; 392 | 393 | 394 | if (Assigned(FOnDoneLoading)) then 395 | FOnDoneLoading(numFiles); 396 | end; 397 | 398 | procedure TLPAKManager.SaveFile(FileNo: integer; DestDir, FileName: string); 399 | var 400 | SaveFile: TFileStream; 401 | begin 402 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 403 | begin 404 | Log(strErrFileSize); 405 | exit; 406 | end; 407 | 408 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 409 | begin 410 | Log(strErrFileNo); 411 | exit; 412 | end; 413 | 414 | SaveFile:=tfilestream.Create(IncludeTrailingPathDelimiter(DestDir) + FileName, 415 | fmOpenWrite or fmCreate); 416 | try 417 | SaveFileToStream(FileNo,SaveFile); 418 | finally 419 | SaveFile.Free; 420 | end; 421 | 422 | end; 423 | 424 | procedure TLPAKManager.SaveFiles(DestDir: string); 425 | var 426 | i: integer; 427 | SaveFile: TFileStream; 428 | begin 429 | for I := 0 to BundleFiles.Count - 1 do 430 | begin 431 | ForceDirectories(extractfilepath(IncludeTrailingPathDelimiter(DestDir) + 432 | ExtractPartialPath( TDFFile(BundleFiles.Items[i]).FileName))); 433 | SaveFile:=TFileStream.Create(IncludeTrailingPathDelimiter(DestDir) + 434 | TDFFile(BundleFiles.Items[i]).FileName , fmOpenWrite or fmCreate); 435 | try 436 | SaveFileToStream(i, SaveFile); 437 | finally 438 | SaveFile.free; 439 | if Assigned(FOnProgress) then FOnProgress(GetFilesCount -1, i); 440 | Application.Processmessages; 441 | end; 442 | end; 443 | 444 | end; 445 | 446 | procedure TLPAKManager.SaveFileToStream(FileNo: integer; DestStream: TStream); 447 | var 448 | Ext: string; 449 | TempStream: TMemoryStream; 450 | begin 451 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 452 | begin 453 | Log(strErrFileSize); 454 | exit; 455 | end; 456 | 457 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 458 | begin 459 | Log(strErrFileNo); 460 | exit; 461 | end; 462 | 463 | Ext:=Uppercase(ExtractFileExt(TDFFile(BundleFiles.Items[FileNo]).FileName)); 464 | 465 | fBundle.Position := TDFFile(BundleFiles.Items[FileNo]).Offset; 466 | 467 | //TEMPORARY HACK FOR DECOMPRESSING TEX TEXTURES************************************************************************* 468 | {if TDFFile(BundleFiles.Items[FileNo]).FileExtension = 'tex' then 469 | begin 470 | TDFFile(BundleFiles.Items[FileNo]).Compressed := true; 471 | fBundle.Seek(16, soFromCurrent); 472 | TDFFile(BundleFiles.Items[FileNo]).UncompressedSize := fBundle.ReadDWord; 473 | fBundle.Seek(12, soFromCurrent); 474 | end;} 475 | //*********************************************************************************************************************** 476 | 477 | 478 | //if TDFFile(BundleFiles.Items[FileNo]).UncompressedSize <> TDFFile(BundleFiles.Items[FileNo]).Size then 479 | if TDFFile(BundleFiles.Items[FileNo]).Compressed then 480 | begin 481 | TempStream := tmemorystream.Create; 482 | try 483 | TempStream.CopyFrom(fBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 484 | //tempstream.SaveToFile('c:\users\ben\desktop\testfile'); 485 | Tempstream.Position := 0; 486 | DecompressZLib(TempStream, TDFFile(BundleFiles.Items[FileNo]).UnCompressedSize, 487 | DestStream); 488 | finally 489 | TempStream.Free; 490 | end 491 | end 492 | else 493 | DestStream.CopyFrom(fBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 494 | 495 | DestStream.Position:=0; 496 | end; 497 | 498 | end. 499 | -------------------------------------------------------------------------------- /uDFExplorer_PAKManager.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | unit uDFExplorer_PAKManager; 15 | 16 | interface 17 | 18 | uses 19 | classes, sysutils, Contnrs, forms, 20 | uDFExplorer_BaseBundleManager, uFileReader, uMemReader, uDFExplorer_Types, 21 | uDFExplorer_Funcs, uZlib, uXCompress; 22 | 23 | type 24 | TPAKManager = class (TBundleManager) 25 | private 26 | fBigEndian: boolean; 27 | protected 28 | fBundle, fDataBundle: TExplorerFileStream; 29 | fBundleFileName: string; 30 | function DetectBundle: boolean; override; 31 | function GetFilesCount: integer; override; 32 | function GetFileName(Index: integer): string; override; 33 | function GetFileSize(Index: integer): integer; override; 34 | function GetFileOffset(Index: integer): int64; override; 35 | function GetFileType(Index: integer): TFiletype; override; 36 | function GetFileExtension(Index: integer): string; override; 37 | procedure Log(Text: string); override; 38 | procedure ReadV2Bundle; 39 | procedure ReadV5Bundle; 40 | procedure ReadV6Bundle; 41 | public 42 | BundleFiles: TObjectList; 43 | constructor Create(ResourceFile: string); override; 44 | destructor Destroy; override; 45 | procedure ParseFiles; override; 46 | procedure SaveFile(FileNo: integer; DestDir, FileName: string); override; 47 | procedure SaveFileToStream(FileNo: integer; DestStream: TStream); override; 48 | procedure SaveFiles(DestDir: string); override; 49 | property Count: integer read GetFilesCount; 50 | property FileName[Index: integer]: string read GetFileName; 51 | property FileSize[Index: integer]: integer read GetFileSize; 52 | property FileOffset[Index: integer]: int64 read GetFileOffset; 53 | property FileType[Index: integer]: TFileType read GetFileType; 54 | property FileExtension[Index: integer]: string read GetFileExtension; 55 | property BigEndian: boolean read fBigEndian; 56 | end; 57 | 58 | const 59 | strErrInvalidFile: string = 'Not a valid bundle'; 60 | 61 | implementation 62 | 63 | { TBundleManager } 64 | 65 | 66 | constructor TPAKManager.Create(ResourceFile: string); 67 | var 68 | DataBundleNameAndPath: string; 69 | begin 70 | try 71 | fBundle:=TExplorerFileStream.Create(ResourceFile); 72 | except on E: EInvalidFile do 73 | raise; 74 | end; 75 | 76 | //Now check if matching .p file is there and if we can open it 77 | DataBundleNameAndPath := ChangeFileExt(ResourceFile, '.~p'); 78 | if FileExists( DataBundleNameAndPath ) = false then 79 | raise EInvalidFile.Create('Couldnt find matching .~p bundle'); 80 | 81 | //Open the second bundle 82 | try 83 | fDataBundle:=TExplorerFileStream.Create(DataBundleNameAndPath); 84 | except on E: EInvalidFile do 85 | raise; 86 | end; 87 | 88 | 89 | fBundleFileName:=ExtractFileName(ResourceFile); 90 | BundleFiles:=TObjectList.Create(true); 91 | 92 | //Load it here just in case XMemcompress used 93 | XCompress_Load_DLL; 94 | 95 | if DetectBundle = false then 96 | raise EInvalidFile.Create( strErrInvalidFile ); 97 | end; 98 | 99 | destructor TPAKManager.Destroy; 100 | begin 101 | if BundleFiles <> nil then 102 | begin 103 | BundleFiles.Free; 104 | BundleFiles:=nil; 105 | end; 106 | 107 | if fBundle <> nil then 108 | fBundle.free; 109 | 110 | if fDataBundle <> nil then 111 | fDataBundle.free; 112 | 113 | inherited; 114 | end; 115 | 116 | function TPAKManager.DetectBundle: boolean; 117 | var 118 | BlockName: string; 119 | begin 120 | Result := false; 121 | BlockName := fBundle.ReadBlockName; 122 | 123 | if BlockName = 'dfpf' then 124 | begin 125 | Result := true; 126 | fBundle.BigEndian := true; 127 | fDataBundle.BigEndian := true; 128 | fBigEndian := true; 129 | end 130 | end; 131 | 132 | function TPAKManager.GetFileExtension(Index: integer): string; 133 | begin 134 | if (not assigned(BundleFiles)) or 135 | (index < 0) or 136 | (index > GetFilesCount) then 137 | result:= '' 138 | else 139 | result:=TDFFile(BundleFiles.Items[Index]).FileExtension; 140 | end; 141 | 142 | function TPAKManager.GetFileName(Index: integer): string; 143 | begin 144 | if (not assigned(BundleFiles)) or 145 | (index < 0) or 146 | (index > GetFilesCount) then 147 | result:= '' 148 | else 149 | result:=TDFFile(BundleFiles.Items[Index]).FileName; 150 | end; 151 | 152 | function TPAKManager.GetFileOffset(Index: integer): int64; 153 | begin 154 | if (not assigned(BundleFiles)) or 155 | (index < 0) or 156 | (index > GetFilesCount) then 157 | result:=0 158 | else 159 | result:=TDFFile(BundleFiles.Items[Index]).offset; 160 | end; 161 | 162 | function TPAKManager.GetFilesCount: integer; 163 | begin 164 | if BundleFiles <> nil then 165 | result:=BundleFiles.Count 166 | else 167 | result:=0; 168 | end; 169 | 170 | function TPAKManager.GetFileSize(Index: integer): integer; 171 | begin 172 | if (not assigned(BundleFiles)) or 173 | (index < 0) or 174 | (index > GetFilesCount) then 175 | result:=-1 176 | else 177 | result:=TDFFile(BundleFiles.Items[Index]).size; 178 | end; 179 | 180 | function TPAKManager.GetFileType(Index: integer): TFileType; 181 | begin 182 | if (not assigned(BundleFiles)) or 183 | (index < 0) or 184 | (index > GetFilesCount) then 185 | result:= ft_Unknown 186 | else 187 | result:=TDFFile(BundleFiles.Items[Index]).FileType; 188 | end; 189 | 190 | procedure TPAKManager.Log(Text: string); 191 | begin 192 | if assigned(fOnDebug) then fOnDebug(Text); 193 | end; 194 | 195 | procedure TPAKManager.ParseFiles; 196 | var 197 | Version: integer; 198 | begin 199 | if fBigEndian then //All LE reading auto converted to BE 200 | Log('Detected as : big endian'); 201 | 202 | 203 | //Read header 204 | fBundle.Position := 4; 205 | Version := fBundle.ReadByte; //not dword 206 | 207 | if Version = 2 then 208 | ReadV2Bundle //Costume quest 209 | else 210 | if Version = 5 then 211 | ReadV5Bundle 212 | else 213 | if Version = 6 then 214 | ReadV6Bundle 215 | else 216 | begin 217 | Log('WARNING: Unknown DFPF version: ' + inttostr(Version)); 218 | if (Version > 5) and (Version < 10) then 219 | ReadV6Bundle; //Give it a try - probably wont work but... 220 | end; 221 | 222 | end; 223 | 224 | procedure TPAKManager.ReadV2Bundle; 225 | var 226 | FileObject: TDFFile; 227 | 228 | {NameDirSize} NumFiles: integer; 229 | 230 | FileExtensionOffset, NameDirOffset, {JunkDataOffset,} FileRecordsOffset: uint64; 231 | 232 | {Marker1, Marker2, FooterOffset1, FooterOffset2, Unknown, BlankBytes1, BlankBytes2: integer;} 233 | 234 | i, tempint, FileExtensionCount: integer; 235 | 236 | FileExtensions: TStringList; 237 | const 238 | sizeOfFileRecord: integer = 16; 239 | begin 240 | fBundle.Position := 8; 241 | 242 | FileExtensionOffset := fBundle.ReadQWord; //Or alignment? table 1 - type table pointer 243 | NameDirOffset := fBundle.ReadQWord; //fn table pointer 244 | FileExtensionCount := fBundle.ReadDWord; //number of types 245 | fBundle.Seek(4 , sofromcurrent); //NameDirSize := fBundle.ReadDWord; //Wrong! //size of fn table 246 | numFiles := fBundle.ReadDWord; //file count 247 | fBundle.Seek(4 , sofromcurrent); //Marker1 := fBundle.ReadDWordLe; //MARKER 23A1CEABh 248 | fBundle.Seek(4 , sofromcurrent); //BlankBytes1 := fBundle.ReadDWord; 249 | fBundle.Seek(4 , sofromcurrent); //BlankBytes2 := fBundle.ReadDWord; 250 | fBundle.Seek(8 , sofromcurrent); //JunkDataOffset := fBundle.ReadQWord; //start of extra bytes in .~p file? 251 | FileRecordsOffset := fBundle.ReadQWord; //p table 252 | fBundle.Seek(8 , sofromcurrent); //FooterOffset1 := fBundle.ReadQWord; 253 | fBundle.Seek(8 , sofromcurrent); //FooterOffset2 := fBundle.ReadQWord; 254 | fBundle.Seek(4 , sofromcurrent); //Unknown := fBundle.ReadDWord; 255 | fBundle.Seek(4 , sofromcurrent); //Marker2 := fBundle.ReadDWordLe; //marker again 23A1CEABh 256 | 257 | 258 | 259 | //Parse files 260 | for I := 0 to numFiles - 1 do //16 bytes 261 | begin 262 | fBundle.Position := FileRecordsOffset + (sizeOfFileRecord * i); 263 | FileObject := TDFFile.Create; 264 | 265 | FileObject.UnCompressedSize := (fBundle.ReadDWord shr 9) ; 266 | fBundle.Seek(1, soFromCurrent); 267 | FileObject.Size := (fBundle.ReadDWord shl 1) shr 10; //Size in the p file 268 | fBundle.Seek(-2, soFromCurrent); 269 | FileObject.Offset := (fBundle.ReadQWord shl 7) shr 34; //read 8 bytes not 4 because we need 30 bits starting at byte 7, bit 7 - so doing shl 7 means we only get 25 bits remaining out of a dword 270 | fBundle.Seek(-3, soFromCurrent); 271 | FileObject.NameOffset := (fBundle.ReadDWord) shr 11; 272 | fBundle.Seek(-2, soFromCurrent); 273 | FileObject.FileTypeIndex := (fBundle.ReadDWord shl 5) shr 25; 274 | fBundle.Seek(-3, soFromCurrent); 275 | FileObject.CompressionType := fBundle.ReadByte and 15; 276 | 277 | case FileObject.CompressionType of 278 | 2: FileObject.Compressed := false; 279 | 4: FileObject.Compressed := true; 280 | else Log('Unknown compression type! ' + inttostr(FileObject.CompressionType)); 281 | end; 282 | 283 | 284 | //Get filename from filenames table 285 | fBundle.Position := NameDirOffset + FileObject.NameOffset; 286 | FileObject.FileName := PChar(fBundle.ReadString(255)); 287 | 288 | 289 | BundleFiles.Add(FileObject); 290 | 291 | {Log(''); 292 | Log(inttostr(i+1)); 293 | Log(FileObject.FileName); 294 | Log('Size when decompressed ' + inttostr(FileObject.UnCompressedSize)); 295 | Log('Name offset ' + inttostr(FileObject.NameOffset)); 296 | Log('Size ' + inttostr(FileObject.Size)); 297 | Log('Offset ' + inttostr(FileObject.Offset)); 298 | Log('Filetype index ' + inttostr(FileObject.FileTypeIndex)); 299 | Log('Comp type ' + inttostr(FileObject.CompressionType));} 300 | end; 301 | 302 | 303 | //Parse the 'other data' File Extension table 304 | FileExtensions := TStringList.Create; 305 | try 306 | fBundle.Position := FileExtensionOffset; 307 | for I := 0 to FileExtensionCount - 1 do 308 | begin 309 | TempInt := fBundle.ReadDWord; 310 | FileExtensions.Add(Trim(fBundle.ReadString( TempInt))); 311 | fBundle.Seek(12, soFromCurrent); //12 unknown bytes 312 | //Log(FileExtensions[i]); 313 | end; 314 | 315 | 316 | //Match filetype index and populate FileType 317 | for I := 0 to BundleFiles.Count -1 do 318 | begin 319 | //Add file extension 320 | TDFFile(BundleFiles[i]).FileExtension := 321 | Trim(FileExtensions[TDFFile(BundleFiles[i]).FileTypeIndex]); 322 | 323 | //Add file type 324 | TDFFile(BundleFiles[i]).FileType := GetFileTypeFromFileExtension( 325 | TDFFile(BundleFiles[i]).FileExtension, 326 | Uppercase(ExtractFileExt(TDFFile(BundleFiles[i]).Filename)) ); 327 | 328 | if TDFFile(BundleFiles[i]).FileType = ft_Unknown then 329 | Log('Unknown file type ' + TDFFile(BundleFiles[i]).FileExtension); 330 | end; 331 | 332 | finally 333 | FileExtensions.Free; 334 | end; 335 | 336 | if (Assigned(FOnDoneLoading)) then 337 | FOnDoneLoading(numFiles); 338 | 339 | 340 | end; 341 | 342 | procedure TPAKManager.ReadV5Bundle; 343 | var 344 | FileObject: TDFFile; 345 | 346 | {NameDirSize} NumFiles: integer; 347 | 348 | FileExtensionOffset, NameDirOffset, {JunkDataOffset,} FileRecordsOffset: uint64; 349 | 350 | {Marker1, Marker2, FooterOffset1, FooterOffset2, Unknown, BlankBytes1, BlankBytes2: integer;} 351 | 352 | i, tempint, FileExtensionCount: integer; 353 | 354 | FileExtensions: TStringList; 355 | const 356 | sizeOfFileRecord: integer = 16; 357 | begin 358 | { 359 | Think this is the structure 360 | Header 361 | always? 88 blank bytes 362 | Other data offset that looks like this 363 | ( 364 | uint32 nameLength; 365 | char name[nameLength]; 366 | uint32 unknown[3]; 367 | ) 368 | File records 369 | Name directory 370 | Blank bytes on end - but sometimes not - eg rgb_stuff 371 | } 372 | 373 | 374 | //Read header 375 | fBundle.Position := 8; 376 | 377 | FileExtensionOffset := fBundle.ReadQWord; //Or alignment? table 1 - type table pointer 378 | NameDirOffset := fBundle.ReadQWord; //fn table pointer 379 | FileExtensionCount := fBundle.ReadDWord; //number of types 380 | fBundle.Seek(4 , sofromcurrent); //NameDirSize := fBundle.ReadDWord; //Wrong! //size of fn table 381 | numFiles := fBundle.ReadDWord; //file count 382 | fBundle.Seek(4 , sofromcurrent); //Marker1 := fBundle.ReadDWordLe; //MARKER 23A1CEABh 383 | fBundle.Seek(4 , sofromcurrent); //BlankBytes1 := fBundle.ReadDWord; 384 | fBundle.Seek(4 , sofromcurrent); //BlankBytes2 := fBundle.ReadDWord; 385 | fBundle.Seek(8 , sofromcurrent); //JunkDataOffset := fBundle.ReadQWord; //start of extra bytes in .~p file? 386 | FileRecordsOffset := fBundle.ReadQWord; //p table 387 | fBundle.Seek(8 , sofromcurrent); //FooterOffset1 := fBundle.ReadQWord; 388 | fBundle.Seek(8 , sofromcurrent); //FooterOffset2 := fBundle.ReadQWord; 389 | fBundle.Seek(4 , sofromcurrent); //Unknown := fBundle.ReadDWord; 390 | fBundle.Seek(4 , sofromcurrent); //Marker2 := fBundle.ReadDWordLe; //marker again 23A1CEABh 391 | 392 | {Log(' Version ' + inttostr(Version ) ); 393 | Log(' FileExtensionOffset ' + inttostr( FileExtensionOffset) ); 394 | Log(' NameDirOffset ' + inttostr(NameDirOffset ) ); 395 | Log(' FileExtension count ' + inttostr( FileExtensionCount) ); 396 | Log(' NameDirSize ' + inttostr(NameDirSize ) ); 397 | Log(' numFiles ' + inttostr(numFiles ) ); 398 | Log(' Marker 1 ' + inttostr(Marker1 ) ); 399 | Log(' BlankBytes1 ' + inttostr(BlankBytes1 ) ); 400 | Log(' BlankBytes2 ' + inttostr(BlankBytes2 ) ); 401 | Log(' Junk data offset ' + inttostr(JunkDataOffset ) ); 402 | Log(' FileRecordsOffset ' + inttostr(FileRecordsOffset ) ); 403 | Log(' Footer offset 1 ' + inttostr(FooterOffset1 ) ); 404 | Log(' Footer offset 2 ' + inttostr(FooterOffset2 ) ); 405 | Log(' Unknown ' + inttostr( Unknown) ); 406 | Log(' Marker 2 ' + inttostr( Marker2) ); 407 | Log('');} 408 | 409 | 410 | //Parse files 411 | for I := 0 to numFiles - 1 do //16 bytes 412 | begin 413 | fBundle.Position := FileRecordsOffset + (sizeOfFileRecord * i); 414 | FileObject := TDFFile.Create; 415 | 416 | FileObject.UnCompressedSize := (fBundle.ReadDWord shr 8) ; //fBundle.ReadTriByte; //when decompressed 417 | fBundle.Seek(-1, sofromcurrent); 418 | FileObject.NameOffset := (fBundle.ReadDWord)shr 11; //(fbundle.ReadTriByte shr 11); specs wrong - from byte 3 not 4 419 | fBundle.Seek(1, sofromcurrent); 420 | FileObject.Offset := fBundle.ReadDWord shr 3; 421 | fBundle.Seek(-1, soFromCurrent); 422 | FileObject.Size := (fBundle.ReadDWord shl 5) shr 9; //Size in the p file 423 | fBundle.Seek(-1, soFromCurrent); 424 | FileObject.FileTypeIndex := (fBundle.ReadDWord shl 4) shr 24; 425 | FileObject.FileTypeIndex := FileObject.FileTypeIndex shr 1; //normalise it 426 | fBundle.Seek(-3, soFromCurrent); 427 | FileObject.CompressionType := fBundle.ReadByte and 15; //Unsure about anding with 15. This field of more use in xbox games where compression will sometimes need XBDecompress. Until we encounter that - just compare compressed vs decompressed sizes when dumping. 428 | 429 | case FileObject.CompressionType of 430 | 4: FileObject.Compressed := false; 431 | 8: FileObject.Compressed := true; //ZLIB 432 | 12: FileObject.Compressed := true; //XMemCompress Xbox 433 | else Log('Unknown compression type! ' + inttostr(FileObject.CompressionType)); 434 | end; 435 | 436 | //Get filename from filenames table 437 | fBundle.Position := NameDirOffset + FileObject.NameOffset; 438 | FileObject.FileName := PChar(fBundle.ReadString(255)); 439 | 440 | 441 | BundleFiles.Add(FileObject); 442 | 443 | {Log(''); 444 | Log(inttostr(i+1)); 445 | Log(FileObject.FileName); 446 | Log('Size when decompressed ' + inttostr(FileObject.UnCompressedSize)); 447 | Log('Name offset ' + inttostr(FileObject.NameOffset)); 448 | Log('Size ' + inttostr(FileObject.Size)); 449 | Log('Offset ' + inttostr(FileObject.Offset)); 450 | Log('Filetype index ' + inttostr(FileObject.FileTypeIndex)); 451 | Log('Comp type ' + inttostr(FileObject.CompressionType));} 452 | end; 453 | 454 | 455 | //Parse the 'other data' File Extension table 456 | FileExtensions := TStringList.Create; 457 | try 458 | fBundle.Position := FileExtensionOffset; 459 | for I := 0 to FileExtensionCount - 1 do 460 | begin 461 | TempInt := fBundle.ReadDWord; 462 | FileExtensions.Add(Trim(fBundle.ReadString( TempInt))); 463 | fBundle.Seek(12, soFromCurrent); //12 unknown bytes 464 | //Log(FileExtensions[i]); 465 | end; 466 | 467 | 468 | //Match filetype index and populate FileType 469 | for I := 0 to BundleFiles.Count -1 do 470 | begin 471 | //Add file extension 472 | TDFFile(BundleFiles[i]).FileExtension := 473 | Trim(FileExtensions[TDFFile(BundleFiles[i]).FileTypeIndex]); 474 | 475 | //Add file type 476 | TDFFile(BundleFiles[i]).FileType := GetFileTypeFromFileExtension( 477 | TDFFile(BundleFiles[i]).FileExtension, 478 | Uppercase(ExtractFileExt(TDFFile(BundleFiles[i]).Filename)) ); 479 | 480 | if TDFFile(BundleFiles[i]).FileType = ft_Unknown then 481 | Log('Unknown file type ' + TDFFile(BundleFiles[i]).FileExtension); 482 | 483 | //Now add file extensions to the files 484 | //TCaveFile(BundleFiles[i]).FileName := TCaveFile(BundleFiles[i]).FileName + '.' + TCaveFile(BundleFiles[i]).FileExtension; 485 | end; 486 | 487 | finally 488 | FileExtensions.Free; 489 | end; 490 | 491 | if (Assigned(FOnDoneLoading)) then 492 | FOnDoneLoading(numFiles); 493 | end; 494 | 495 | procedure TPAKManager.ReadV6Bundle; 496 | var 497 | FileObject: TDFFile; 498 | 499 | NameDirSize, NumFiles: integer; 500 | 501 | FileExtensionOffset, NameDirOffset, JunkDataOffset, FileRecordsOffset: uint64; 502 | 503 | Marker1, Marker2, FooterOffset1, FooterOffset2, Unknown, BlankBytes1, BlankBytes2: integer; 504 | 505 | i, tempint, FileExtensionCount, fileflags: integer; 506 | 507 | FileExtensions: TStringList; 508 | const 509 | sizeOfFileRecord: integer = 16; 510 | begin 511 | //Read header 512 | fBundle.Position := 8; 513 | 514 | 515 | FileExtensionOffset := fBundle.ReadQWord; 516 | NameDirOffset := fBundle.ReadQWord; 517 | FileExtensionCount := fBundle.ReadDWord; 518 | NameDirSize := fBundle.ReadDWord; 519 | numFiles := fBundle.ReadDWord; 520 | {Marker1 := fBundle.ReadDWordLe; //MARKER 23A1CEABh 521 | BlankBytes1 := fBundle.ReadDWord; 522 | BlankBytes2 := fBundle.ReadDWord; 523 | JunkDataOffset := fBundle.ReadQWord;} //start of extra bytes in .~p file? 524 | fBundle.Seek(20, soFromCurrent); 525 | FileRecordsOffset := fBundle.ReadQWord; //p table 526 | {FooterOffset1 := fBundle.ReadQWord; 527 | FooterOffset2 := fBundle.ReadQWord; 528 | Unknown := fBundle.ReadDWord; 529 | Marker2 := fBundle.ReadDWordLe;} //marker again 23A1CEABh 530 | 531 | 532 | 533 | {Log(' FileExtensionOffset ' + inttostr( FileExtensionOffset) ); 534 | Log(' NameDirOffset ' + inttostr(NameDirOffset ) ); 535 | Log(' FileExtension count ' + inttostr( FileExtensionCount) ); 536 | Log(' NameDirSize? ' + inttostr(NameDirSize ) ); 537 | Log(' numFiles ' + inttostr(numFiles ) ); 538 | Log(' Marker 1 ' + inttostr(Marker1 ) ); 539 | Log(' BlankBytes1 ' + inttostr(BlankBytes1 ) ); 540 | Log(' BlankBytes2 ' + inttostr(BlankBytes2 ) ); 541 | Log(' Junk data offset ' + inttostr(JunkDataOffset ) ); 542 | Log(' FileRecordsOffset ' + inttostr(FileRecordsOffset ) ); 543 | Log(' Footer offset 1 ' + inttostr(FooterOffset1 ) ); 544 | Log(' Footer offset 2 ' + inttostr(FooterOffset2 ) ); 545 | Log(' Unknown ' + inttostr( Unknown) ); 546 | Log(' Marker 2 ' + inttostr( Marker2) ); 547 | Log(''); } 548 | 549 | 550 | //Parse files 551 | //Data stored in bitfields that arent aligned to bytes 552 | for I := 0 to numFiles - 1 do 553 | begin 554 | //Read dwords and shift around rather than read tribyte because with tribyte bits can fall off the end 555 | 556 | fBundle.Position := FileRecordsOffset + (sizeOfFileRecord * i); 557 | FileObject := TDFFile.Create; 558 | 559 | FileObject.UnCompressedSize := (fBundle.ReadDWord shr 5); //Size when decompressed - 3 bytes + 5 bits of next byte 560 | 561 | fBundle.Seek(-2, soFromCurrent); 562 | FileObject.NameOffset := (fBundle.ReadDWord shl 13) shr 13; //Last 3 bits of byte 4 and next 2 bytes 563 | 564 | fBundle.Seek(2, soFromCurrent); //Unknown 2 bytes 565 | 566 | FileObject.Offset := fBundle.ReadDWord shr 1; 567 | 568 | fBundle.Seek(-1, soFromCurrent); 569 | FileObject.Size := (fBundle.ReadDWord shl 7) shr 7; //Raw size in the p file - uses last bit of byte 11 + next 3 bytes 570 | 571 | FileObject.FileTypeIndex := fBundle.ReadByte shr 2; //Index in filetype table - first 6 bits 572 | 573 | fBundle.Seek(-1, soFromCurrent); 574 | FileObject.CompressionType := Byte(fBundle.ReadByte shl 6) shr 6; //Last 2 bits - have to cast to byte here https://stackoverflow.com/questions/21362455/what-is-the-behaviour-of-shl-and-shr-for-non-register-sized-operands 575 | 576 | 577 | //Get filename from filenames table 578 | fBundle.Position := NameDirOffset + FileObject.NameOffset; 579 | FileObject.FileName := PChar(fBundle.ReadString(255)); 580 | 581 | case FileObject.CompressionType of 582 | 1: FileObject.Compressed := false; 583 | 2: FileObject.Compressed := true; 584 | else Log('Unknown compression type! ' + inttostr(FileObject.CompressionType) + ' ' + FileObject.FileName); 585 | end; 586 | 587 | 588 | BundleFiles.Add(FileObject); 589 | 590 | {Log(''); 591 | Log(inttostr(i+1)); 592 | Log('Start offset in file of record info: ' + inttostr(FileRecordsOffset + (sizeOfFileRecord * i))); 593 | Log(FileObject.FileName); 594 | Log('Size when decompressed ' + inttostr(FileObject.UnCompressedSize)); 595 | Log('Name offset ' + inttostr(FileObject.NameOffset)); 596 | Log('File flags: ' + inttostr(fileflags)); 597 | Log('Size ' + inttostr(FileObject.Size)); 598 | Log('Offset ' + inttostr(FileObject.Offset)); 599 | Log('Filetype index ' + inttostr(FileObject.FileTypeIndex)); 600 | Log('Comp type ' + inttostr(FileObject.CompressionType)); 601 | Log('');} 602 | end; 603 | 604 | 605 | //Parse the 'other data' File Extension table 606 | FileExtensions := TStringList.Create; 607 | try 608 | fBundle.Position := FileExtensionOffset; 609 | for I := 0 to FileExtensionCount - 1 do 610 | begin 611 | TempInt := fBundle.ReadDWord; 612 | FileExtensions.Add(Trim(fBundle.ReadString( TempInt))); 613 | fBundle.Seek(12, soFromCurrent); //12 unknown bytes 614 | //Log(FileExtensions[i]); 615 | end; 616 | 617 | 618 | //Match filetype index and populate FileType 619 | for I := 0 to BundleFiles.Count -1 do 620 | begin 621 | //Add file extension 622 | TDFFile(BundleFiles[i]).FileExtension := 623 | Trim(FileExtensions[TDFFile(BundleFiles[i]).FileTypeIndex]); 624 | 625 | //Add file type 626 | TDFFile(BundleFiles[i]).FileType := GetFileTypeFromFileExtension( 627 | TDFFile(BundleFiles[i]).FileExtension, 628 | Uppercase(ExtractFileExt(TDFFile(BundleFiles[i]).Filename)) ); 629 | 630 | if TDFFile(BundleFiles[i]).FileType = ft_Unknown then 631 | Log('Unknown file type ' + TDFFile(BundleFiles[i]).FileExtension); 632 | end; 633 | 634 | finally 635 | FileExtensions.Free; 636 | end; 637 | 638 | if (Assigned(FOnDoneLoading)) then 639 | FOnDoneLoading(numFiles); 640 | end; 641 | 642 | procedure TPAKManager.SaveFile(FileNo: integer; DestDir, FileName: string); 643 | var 644 | SaveFile: TFileStream; 645 | begin 646 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 647 | begin 648 | Log(strErrFileSize); 649 | exit; 650 | end; 651 | 652 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 653 | begin 654 | Log(strErrFileNo); 655 | exit; 656 | end; 657 | 658 | 659 | SaveFile:=tfilestream.Create(IncludeTrailingPathDelimiter(DestDir) + 660 | FileName, fmOpenWrite or fmCreate); 661 | try 662 | SaveFileToStream(FileNo,SaveFile); 663 | finally 664 | SaveFile.Free; 665 | end; 666 | 667 | end; 668 | 669 | procedure TPAKManager.SaveFiles(DestDir: string); 670 | var 671 | i: integer; 672 | SaveFile: TFileStream; 673 | begin 674 | for I := 0 to BundleFiles.Count - 1 do 675 | begin 676 | ForceDirectories(extractfilepath(IncludeTrailingPathDelimiter(DestDir) + 677 | ExtractPartialPath( TDFFile(BundleFiles.Items[i]).FileName))); 678 | 679 | SaveFile:=TFileStream.Create(IncludeTrailingPathDelimiter(DestDir) + 680 | TDFFile(BundleFiles.Items[i]).FileName , fmOpenWrite or fmCreate); 681 | try 682 | SaveFileToStream(i, SaveFile); 683 | finally 684 | SaveFile.free; 685 | if Assigned(FOnProgress) then FOnProgress(GetFilesCount -1, i); 686 | Application.Processmessages; 687 | end; 688 | end; 689 | 690 | end; 691 | 692 | procedure TPAKManager.SaveFileToStream(FileNo: integer; DestStream: TStream); 693 | var 694 | Ext: string; 695 | TempStream: TMemoryStream; 696 | begin 697 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 698 | begin 699 | Log(strErrFileSize); 700 | exit; 701 | end; 702 | 703 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 704 | begin 705 | Log(strErrFileNo); 706 | exit; 707 | end; 708 | 709 | Ext:=Uppercase(ExtractFileExt(TDFFile(BundleFiles.Items[FileNo]).FileName)); 710 | 711 | fDataBundle.Seek(TDFFile(BundleFiles.Items[FileNo]).Offset, sofrombeginning); 712 | 713 | //if TDFFile(BundleFiles.Items[FileNo]).UncompressedSize <> TDFFile(BundleFiles.Items[FileNo]).Size then 714 | if TDFFile(BundleFiles.Items[FileNo]).Compressed then 715 | begin 716 | TempStream := tmemorystream.Create; 717 | try 718 | TempStream.CopyFrom(fDataBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 719 | //tempstream.SaveToFile('c:\users\ben\desktop\testfile'); 720 | Tempstream.Position := 0; 721 | 722 | //Comp type 12 is Xbox XMemDecompress. Others are ZLib 723 | if TDFFile(BundleFiles.Items[FileNo]).CompressionType = 12 then 724 | DecompressXCompress(TempStream, DestStream, TDFFile(BundleFiles.Items[FileNo]).Size, TDFFile(BundleFiles.Items[FileNo]).UnCompressedSize) 725 | else 726 | DecompressZLib(TempStream, TDFFile(BundleFiles.Items[FileNo]).UnCompressedSize, DestStream); 727 | finally 728 | TempStream.Free; 729 | end 730 | end 731 | else 732 | DestStream.CopyFrom(fDataBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 733 | 734 | DestStream.Position:=0; 735 | end; 736 | 737 | end. 738 | -------------------------------------------------------------------------------- /uDFExplorer_PCKManager.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | unit uDFExplorer_PCKManager; 15 | 16 | interface 17 | 18 | uses 19 | classes, sysutils, Contnrs, forms, 20 | uDFExplorer_BaseBundleManager, uFileReader, uMemReader, uDFExplorer_Types, 21 | uDFExplorer_Funcs, uZlib; 22 | 23 | type 24 | TPCKManager = class (TBundleManager) 25 | private 26 | fBigEndian: boolean; 27 | protected 28 | fBundle: TExplorerFileStream; 29 | fBundleFileName: string; 30 | function DetectBundle: boolean; override; 31 | function GetFilesCount: integer; override; 32 | function GetFileName(Index: integer): string; override; 33 | function GetFileSize(Index: integer): integer; override; 34 | function GetFileOffset(Index: integer): int64; override; 35 | function GetFileType(Index: integer): TFiletype; override; 36 | function GetFileExtension(Index: integer): string; override; 37 | procedure Log(Text: string); override; 38 | procedure ParsePCKBundle; 39 | public 40 | BundleFiles: TObjectList; 41 | constructor Create(ResourceFile: string); override; 42 | destructor Destroy; override; 43 | procedure ParseFiles; override; 44 | procedure SaveFile(FileNo: integer; DestDir, FileName: string); override; 45 | procedure SaveFileToStream(FileNo: integer; DestStream: TStream); override; 46 | procedure SaveFiles(DestDir: string); override; 47 | property Count: integer read GetFilesCount; 48 | property FileName[Index: integer]: string read GetFileName; 49 | property FileSize[Index: integer]: integer read GetFileSize; 50 | property FileOffset[Index: integer]: int64 read GetFileOffset; 51 | property FileType[Index: integer]: TFileType read GetFileType; 52 | property FileExtension[Index: integer]: string read GetFileExtension; 53 | property BigEndian: boolean read fBigEndian; 54 | end; 55 | 56 | const 57 | strErrInvalidFile: string = 'Not a valid bundle'; 58 | 59 | implementation 60 | 61 | { TBundleManager } 62 | 63 | 64 | constructor TPCKManager.Create(ResourceFile: string); 65 | begin 66 | try 67 | fBundle:=TExplorerFileStream.Create(ResourceFile); 68 | except on E: EInvalidFile do 69 | raise; 70 | end; 71 | 72 | fBundleFileName:=ExtractFileName(ResourceFile); 73 | BundleFiles:=TObjectList.Create(true); 74 | 75 | if DetectBundle = false then 76 | raise EInvalidFile.Create( strErrInvalidFile ); 77 | end; 78 | 79 | destructor TPCKManager.Destroy; 80 | begin 81 | if BundleFiles <> nil then 82 | begin 83 | BundleFiles.Free; 84 | BundleFiles:=nil; 85 | end; 86 | 87 | if fBundle <> nil then 88 | fBundle.free; 89 | 90 | inherited; 91 | end; 92 | 93 | function TPCKManager.DetectBundle: boolean; 94 | var 95 | BlockHeader: integer; 96 | begin 97 | Result := false; 98 | BlockHeader := fBundle.ReadDWord; 99 | 100 | if BlockHeader = 67305985 then //0x01020304 101 | begin 102 | Result := true; 103 | fBundle.BigEndian := false; 104 | fBigEndian := false; 105 | end 106 | end; 107 | 108 | function TPCKManager.GetFileExtension(Index: integer): string; 109 | begin 110 | if (not assigned(BundleFiles)) or 111 | (index < 0) or 112 | (index > GetFilesCount) then 113 | result:= '' 114 | else 115 | result:=TDFFile(BundleFiles.Items[Index]).FileExtension; 116 | end; 117 | 118 | function TPCKManager.GetFileName(Index: integer): string; 119 | begin 120 | if (not assigned(BundleFiles)) or 121 | (index < 0) or 122 | (index > GetFilesCount) then 123 | result:= '' 124 | else 125 | result:=TDFFile(BundleFiles.Items[Index]).FileName; 126 | end; 127 | 128 | function TPCKManager.GetFileOffset(Index: integer): int64; 129 | begin 130 | if (not assigned(BundleFiles)) or 131 | (index < 0) or 132 | (index > GetFilesCount) then 133 | result:=0 134 | else 135 | result:=TDFFile(BundleFiles.Items[Index]).offset; 136 | end; 137 | 138 | function TPCKManager.GetFilesCount: integer; 139 | begin 140 | if BundleFiles <> nil then 141 | result:=BundleFiles.Count 142 | else 143 | result:=0; 144 | end; 145 | 146 | function TPCKManager.GetFileSize(Index: integer): integer; 147 | begin 148 | if (not assigned(BundleFiles)) or 149 | (index < 0) or 150 | (index > GetFilesCount) then 151 | result:=-1 152 | else 153 | result:=TDFFile(BundleFiles.Items[Index]).size; 154 | end; 155 | 156 | function TPCKManager.GetFileType(Index: integer): TFileType; 157 | begin 158 | if (not assigned(BundleFiles)) or 159 | (index < 0) or 160 | (index > GetFilesCount) then 161 | result:= ft_Unknown 162 | else 163 | result:=TDFFile(BundleFiles.Items[Index]).FileType; 164 | end; 165 | 166 | procedure TPCKManager.Log(Text: string); 167 | begin 168 | if assigned(fOnDebug) then fOnDebug(Text); 169 | end; 170 | 171 | procedure TPCKManager.ParseFiles; 172 | begin 173 | if fBigEndian then //All LE reading auto converted to BE 174 | Log('Detected as : big endian'); 175 | 176 | 177 | ParsePCKBundle; 178 | end; 179 | 180 | { 181 | PCK File Format: 182 | File data with headers 183 | File records 184 | Footer - with file info 185 | 186 | Footer at end of file 187 | The last 22 bytes of the file 188 | 189 | 4 'PACK' 190 | 4 0? 191 | 2 num files 192 | 2 num files again? 193 | 4 size of file records section 194 | 4 offset of file records section 195 | 2 0? 196 | 197 | File data for each file 198 | 199 | local file header signature 4 bytes ( 0x04034b50 ) 200 | version needed to extract 2 bytes 201 | general purpose bit flag 2 bytes 202 | compression method 2 bytes 203 | last mod file time 2 bytes 204 | last mod file date 2 bytes 205 | crc-32 4 bytes 206 | compressed size 4 bytes 207 | uncompressed size 4 bytes 208 | file name length 2 bytes 209 | extra field length 2 bytes 210 | file name ( variable size ) 211 | extra field ( variable size ) 212 | compressed bytes ( variable size ) 213 | 214 | 215 | File records section 216 | 217 | 4 0x04030102 218 | 2 version needed to extract 219 | 2 version needed to extract (again?) 220 | 2 general purpose bit flag 221 | 2 compression method 222 | 2 last mod file time 223 | 2 last mod file date 224 | 4 CRC32 225 | 4 compressed size 226 | 4 uncompressed size 227 | 4 filename length 228 | 10 unknown 229 | 4 offset of file data - 1st byte of this dword is xorval for some filenames 230 | x filename 231 | } 232 | 233 | 234 | procedure TPCKManager.ParsePCKBundle; 235 | var 236 | NumFiles, FileRecordsOffset, CompressedSize, UnCompressedSize, FilenameLength, 237 | ExtraFieldLength, FileDataOffset, I, J, OldPosition: integer; 238 | XORval: byte; 239 | FileName, FileExt: string; 240 | FileType: TFileType; 241 | FileObject: TDFFile; 242 | const 243 | FileDataHeader: integer = 67305985; //0x01020304 244 | FileRecordsHeader: integer = 33620740; //0x04030102 245 | begin 246 | {********************************Footer section********************************} 247 | fBundle.Position := fBundle.Size - 22; 248 | 249 | if fBundle.ReadBlockName <> 'PACK' then 250 | begin 251 | Log('Footer BLOCK not found!'); 252 | raise EInvalidFile.Create( strErrInvalidFile ); 253 | end; 254 | 255 | fBundle.Seek(4, soFromCurrent); //4 0 bytes 256 | NumFiles := fBundle.ReadWord; 257 | fBundle.Seek(6, soFromCurrent); //numfiles again and size of file records 258 | FileRecordsOffset := fBundle.ReadDWord; 259 | 260 | 261 | {*****************************File Records section*****************************} 262 | fBundle.Seek(FileRecordsOffset, soFromBeginning); 263 | for I := 0 to NumFiles - 1 do 264 | begin 265 | if fBundle.ReadDWord <> FileRecordsHeader then 266 | begin 267 | Log('File records header expected but not found!'); 268 | raise EInvalidFile.Create( strErrInvalidFile ); 269 | end; 270 | 271 | fBundle.Seek(16, soFromCurrent); //Info that we dont need to dump files 272 | CompressedSize := fBundle.ReadDWord; 273 | UnCompressedSize := fBundle.ReadDWord; 274 | 275 | if CompressedSize <> UncompressedSize then 276 | Log('Compressed and uncompressed size differ - check compression. File no ' + 277 | inttostr(i) + ' at offset ' + inttostr(fBundle.Position - 22)); 278 | 279 | FilenameLength := fBundle.ReadDWord; 280 | fBundle.Seek(10, soFromCurrent); //Unknown 281 | FileDataOffset := fBundle.ReadDWord; 282 | 283 | //Filename is xor'ed by first byte of the offset dword 284 | XORVal := FileDataOffset AND $FF; //get first byte of the dword; 285 | 286 | if XORVal > 128 then //Values under 128 are just xor'ed by 128 287 | else 288 | XORVal := 128; 289 | 290 | //Decode the filename 291 | FileName := PChar(fBundle.ReadString(FilenameLength)); 292 | for J := 1 to Length(FileName) do 293 | begin 294 | FileName[J] := Chr( ord(FileName[J]) xor XORVal); 295 | end; 296 | 297 | 298 | //Correct the file extension 299 | FileExt := ExtractFileExt(FileName); //Dont want the . on the file extension 300 | if (length(FileExt)>0) and (FileExt[1]='.') then 301 | delete(FileExt,1,1); 302 | 303 | 304 | //Correct the file type 305 | FileType := GetFileTypeFromFileExtension( FileExt, Uppercase(ExtractFileExt(Filename))); 306 | if (FileType = ft_Unknown) and (ExtractFileExt(FileName) <> '') then 307 | Log('Unknown file type ' + FileExt); 308 | 309 | 310 | //Parse the file data to get the actual offset of the data - the size of the header for each file is variable 311 | OldPosition := fBundle.Position; 312 | fBundle.Position := FileDataOffset; 313 | if fBundle.ReadDWord <> FileDataHeader then 314 | begin 315 | Log('File data header expected but not found!'); 316 | raise EInvalidFile.Create( strErrInvalidFile ); 317 | end; 318 | fBundle.Seek(22, soFromCurrent); 319 | FilenameLength := fBundle.ReadWord; 320 | ExtraFieldLength := fBundle.ReadWord; 321 | fBundle.Seek(FilenameLength, soFromCurrent); 322 | fBundle.Seek(ExtraFieldLength, soFromCurrent); 323 | FileDataOffset := fBundle.Position; //Correct the offset 324 | fBundle.Position := OldPosition; 325 | 326 | 327 | //Add a new FileObject 328 | FileObject := TDFFile.Create; 329 | FileObject.UncompressedSize := UncompressedSize; 330 | FileObject.Offset := FileDataOffset; 331 | FileObject.Size := CompressedSize; 332 | FileObject.Compressed := CompressedSize <> UncompressedSize; 333 | FileObject.FileExtension := FileExt; 334 | FileObject.FileType := FileType; 335 | FileObject.FileName := FileName; 336 | FileObject.FileTypeIndex := -1; 337 | FileObject.CompressionType := 0; 338 | FileObject.NameOffset := -1; 339 | 340 | BundleFiles.Add(FileObject); 341 | end; 342 | 343 | if (Assigned(FOnDoneLoading)) then 344 | FOnDoneLoading(NumFiles); 345 | end; 346 | 347 | procedure TPCKManager.SaveFile(FileNo: integer; DestDir, FileName: string); 348 | var 349 | SaveFile: TFileStream; 350 | begin 351 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 352 | begin 353 | Log(strErrFileSize); 354 | exit; 355 | end; 356 | 357 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 358 | begin 359 | Log(strErrFileNo); 360 | exit; 361 | end; 362 | 363 | SaveFile:=tfilestream.Create(IncludeTrailingPathDelimiter(DestDir) + FileName, 364 | fmOpenWrite or fmCreate); 365 | try 366 | SaveFileToStream(FileNo,SaveFile); 367 | finally 368 | SaveFile.Free; 369 | end; 370 | 371 | end; 372 | 373 | procedure TPCKManager.SaveFiles(DestDir: string); 374 | var 375 | i: integer; 376 | SaveFile: TFileStream; 377 | begin 378 | for I := 0 to BundleFiles.Count - 1 do 379 | begin 380 | ForceDirectories(extractfilepath(IncludeTrailingPathDelimiter(DestDir) + 381 | ExtractPartialPath( TDFFile(BundleFiles.Items[i]).FileName))); 382 | 383 | SaveFile:=TFileStream.Create(IncludeTrailingPathDelimiter(DestDir) + 384 | TDFFile(BundleFiles.Items[i]).FileName , fmOpenWrite or fmCreate); 385 | try 386 | SaveFileToStream(i, SaveFile); 387 | finally 388 | SaveFile.free; 389 | if Assigned(FOnProgress) then FOnProgress(GetFilesCount -1, i); 390 | Application.Processmessages; 391 | end; 392 | end; 393 | 394 | end; 395 | 396 | procedure TPCKManager.SaveFileToStream(FileNo: integer; DestStream: TStream); 397 | var 398 | Ext: string; 399 | TempStream: TMemoryStream; 400 | begin 401 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 402 | begin 403 | Log(strErrFileSize); 404 | exit; 405 | end; 406 | 407 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 408 | begin 409 | Log(strErrFileNo); 410 | exit; 411 | end; 412 | 413 | Ext:=Uppercase(ExtractFileExt(TDFFile(BundleFiles.Items[FileNo]).FileName)); 414 | 415 | fBundle.Seek(TDFFile(BundleFiles.Items[FileNo]).Offset, sofrombeginning); 416 | 417 | //TEMPORARY HACK FOR DECOMPRESSING TEX TEXTURES************************************************************************* 418 | {if TDFFile(BundleFiles.Items[FileNo]).FileExtension = 'tex' then 419 | begin 420 | TDFFile(BundleFiles.Items[FileNo]).Compressed := true; 421 | fBundle.Seek(16, soFromCurrent); 422 | TDFFile(BundleFiles.Items[FileNo]).UncompressedSize := fBundle.ReadDWord; 423 | fBundle.Seek(12, soFromCurrent); 424 | end;} 425 | //*********************************************************************************************************************** 426 | 427 | 428 | //if TDFFile(BundleFiles.Items[FileNo]).UncompressedSize <> TDFFile(BundleFiles.Items[FileNo]).Size then 429 | if TDFFile(BundleFiles.Items[FileNo]).Compressed then 430 | begin 431 | TempStream := tmemorystream.Create; 432 | try 433 | TempStream.CopyFrom(fBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 434 | //tempstream.SaveToFile('c:\users\ben\desktop\testfile'); 435 | Tempstream.Position := 0; 436 | DecompressZLib(TempStream, TDFFile(BundleFiles.Items[FileNo]).UnCompressedSize, 437 | DestStream); 438 | finally 439 | TempStream.Free; 440 | end 441 | end 442 | else 443 | DestStream.CopyFrom(fBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 444 | 445 | DestStream.Position:=0; 446 | end; 447 | 448 | end. 449 | -------------------------------------------------------------------------------- /uDFExplorer_PKGManager.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | { 15 | PKG files from Psychonauts (all pc releases) 16 | } 17 | 18 | 19 | unit uDFExplorer_PKGManager; 20 | 21 | interface 22 | 23 | uses 24 | classes, sysutils, Contnrs, forms, 25 | uDFExplorer_BaseBundleManager, uFileReader, uMemReader, uDFExplorer_Types, 26 | uDFExplorer_Funcs, uZlib; 27 | 28 | type 29 | TPKGManager = class (TBundleManager) 30 | private 31 | fBigEndian: boolean; 32 | protected 33 | fBundle: TExplorerFileStream; 34 | fBundleFileName: string; 35 | function DetectBundle: boolean; override; 36 | function GetFilesCount: integer; override; 37 | function GetFileName(Index: integer): string; override; 38 | function GetFileSize(Index: integer): integer; override; 39 | function GetFileOffset(Index: integer): int64; override; 40 | function GetFileType(Index: integer): TFiletype; override; 41 | function GetFileExtension(Index: integer): string; override; 42 | procedure Log(Text: string); override; 43 | procedure ParsePKGBundle; 44 | public 45 | BundleFiles: TObjectList; 46 | constructor Create(ResourceFile: string); override; 47 | destructor Destroy; override; 48 | procedure ParseFiles; override; 49 | procedure SaveFile(FileNo: integer; DestDir, FileName: string); override; 50 | procedure SaveFileToStream(FileNo: integer; DestStream: TStream); override; 51 | procedure SaveFiles(DestDir: string); override; 52 | property Count: integer read GetFilesCount; 53 | property FileName[Index: integer]: string read GetFileName; 54 | property FileSize[Index: integer]: integer read GetFileSize; 55 | property FileOffset[Index: integer]: int64 read GetFileOffset; 56 | property FileType[Index: integer]: TFileType read GetFileType; 57 | property FileExtension[Index: integer]: string read GetFileExtension; 58 | property BigEndian: boolean read fBigEndian; 59 | end; 60 | 61 | const 62 | strErrInvalidFile: string = 'Not a valid bundle'; 63 | 64 | implementation 65 | 66 | { TBundleManager } 67 | 68 | 69 | constructor TPKGManager.Create(ResourceFile: string); 70 | begin 71 | try 72 | fBundle:=TExplorerFileStream.Create(ResourceFile); 73 | except on E: EInvalidFile do 74 | raise; 75 | end; 76 | 77 | fBundleFileName:=ExtractFileName(ResourceFile); 78 | BundleFiles:=TObjectList.Create(true); 79 | 80 | if DetectBundle = false then 81 | raise EInvalidFile.Create( strErrInvalidFile ); 82 | end; 83 | 84 | destructor TPKGManager.Destroy; 85 | begin 86 | if BundleFiles <> nil then 87 | begin 88 | BundleFiles.Free; 89 | BundleFiles:=nil; 90 | end; 91 | 92 | if fBundle <> nil then 93 | fBundle.free; 94 | 95 | inherited; 96 | end; 97 | 98 | function TPKGManager.DetectBundle: boolean; 99 | var 100 | BlockHeader: integer; 101 | begin 102 | Result := false; 103 | BlockHeader := fBundle.ReadDWord; 104 | 105 | if BlockHeader = 1196118106 then //ZPKG 106 | begin 107 | Result := true; 108 | fBundle.BigEndian := false; 109 | fBigEndian := false; 110 | end 111 | end; 112 | 113 | function TPKGManager.GetFileExtension(Index: integer): string; 114 | begin 115 | if (not assigned(BundleFiles)) or 116 | (index < 0) or 117 | (index > GetFilesCount) then 118 | result:= '' 119 | else 120 | result:=TDFFile(BundleFiles.Items[Index]).FileExtension; 121 | end; 122 | 123 | function TPKGManager.GetFileName(Index: integer): string; 124 | begin 125 | if (not assigned(BundleFiles)) or 126 | (index < 0) or 127 | (index > GetFilesCount) then 128 | result:= '' 129 | else 130 | result:=TDFFile(BundleFiles.Items[Index]).FileName; 131 | end; 132 | 133 | function TPKGManager.GetFileOffset(Index: integer): int64; 134 | begin 135 | if (not assigned(BundleFiles)) or 136 | (index < 0) or 137 | (index > GetFilesCount) then 138 | result:=0 139 | else 140 | result:=TDFFile(BundleFiles.Items[Index]).offset; 141 | end; 142 | 143 | function TPKGManager.GetFilesCount: integer; 144 | begin 145 | if BundleFiles <> nil then 146 | result:=BundleFiles.Count 147 | else 148 | result:=0; 149 | end; 150 | 151 | function TPKGManager.GetFileSize(Index: integer): integer; 152 | begin 153 | if (not assigned(BundleFiles)) or 154 | (index < 0) or 155 | (index > GetFilesCount) then 156 | result:=-1 157 | else 158 | result:=TDFFile(BundleFiles.Items[Index]).size; 159 | end; 160 | 161 | function TPKGManager.GetFileType(Index: integer): TFileType; 162 | begin 163 | if (not assigned(BundleFiles)) or 164 | (index < 0) or 165 | (index > GetFilesCount) then 166 | result:= ft_Unknown 167 | else 168 | result:=TDFFile(BundleFiles.Items[Index]).FileType; 169 | end; 170 | 171 | procedure TPKGManager.Log(Text: string); 172 | begin 173 | if assigned(fOnDebug) then fOnDebug(Text); 174 | end; 175 | 176 | procedure TPKGManager.ParseFiles; 177 | begin 178 | if fBigEndian then //All LE reading auto converted to BE 179 | Log('Detected as : big endian'); 180 | 181 | 182 | ParsePKGBundle; 183 | end; 184 | 185 | procedure TPKGManager.ParsePKGBundle; 186 | var 187 | NumFiles, NumDirs, FolderDirOffset, NameDirOffset, FileExtDirOffset, 188 | FileExtensionOffset, FilenameOffset, i, j, OldPosition, ReturnPos1, 189 | ReturnPos2, StartIndex, EndIndex, ThisID: integer; 190 | FileObject: TDFFile; 191 | CurrName: string; 192 | Letter: char; 193 | ReturnNames, PathNames: TStringList; 194 | ReturnIDs, PathIDsStart, PathIDsEnd: array of integer; 195 | begin 196 | fBundle.Position:=0; 197 | if fBundle.ReadBlockName <> 'ZPKG' then 198 | begin 199 | raise EInvalidFile.Create( strErrInvalidFile ); 200 | end; 201 | 202 | //Read Header 203 | fBundle.seek(4, sofromcurrent); //version 204 | fBundle.seek(4, sofromcurrent); //first file offset 205 | NumFiles := fBundle.readdword; //number of files 206 | FolderDirOffset := fBundle.readdword; //offset of folder directory 207 | NumDirs := fBundle.readdword; //number of records in folder directory 208 | NameDirOffset := fBundle.readdword; //name dir offset 209 | FileExtDirOffset := fBundle.readdword; //file extension dir offset 210 | 211 | //Parse File Records 212 | fBundle.Position := 512; 213 | 214 | for I := 0 to NumFiles -1 do 215 | begin 216 | fBundle.seek(1, sofromcurrent); //null 217 | FileExtensionOffset := fBundle.ReadWord; //offset in file ext dir 218 | fBundle.seek(1, sofromcurrent); //null 219 | FilenameOffset := fBundle.ReadDWord; //offset in filename dir 220 | 221 | FileObject := TDFFile.Create; 222 | FileObject.Compressed := false; 223 | FileObject.CompressionType := 0; 224 | FileObject.FileTypeIndex := -1; 225 | 226 | //Now get filename and filetype 227 | OldPosition := fBundle.Position; 228 | fBundle.Position := FileExtensionOffset + FileExtDirOffset; 229 | FileObject.FileExtension := Trim(PChar(fBundle.ReadString(5))); 230 | //Need to add the . in 231 | if FileObject.FileExtension <> '' then 232 | FileObject.FileExtension := '.' + FileObject.FileExtension; 233 | 234 | 235 | fBundle.Position := FilenameOffset + NameDirOffset; 236 | FileObject.FileName := PChar(fBundle.ReadString(100)) + FileObject.FileExtension; 237 | 238 | //Correct the file type 239 | FileObject.FileType := GetFileTypeFromFileExtension( FileObject.FileExtension); 240 | if (FileObject.FileType = ft_Unknown) and (FileObject.FileExtension <> '') then 241 | Log('Unknown file type ' + FileObject.FileExtension); 242 | 243 | fBundle.Position := OldPosition; 244 | FileObject.Offset := fBundle.ReadDWord; 245 | FileObject.Size := fBundle.ReadDWord; 246 | FileObject.UncompressedSize := FileObject.Size; //Not compressed 247 | 248 | BundleFiles.Add(FileObject); 249 | end; 250 | 251 | //Now parse the folder Dir. Its 12 bytes per record 252 | //Algorithm by Watto https://github.com/bgbennyboy/DoubleFine-Explorer/issues/4 253 | fBundle.Position := FolderDirOffset; 254 | SetLength(ReturnIDs, NumDirs); 255 | CurrName := ''; 256 | 257 | ReturnNames := TStringList.Create; 258 | PathNames := TStringList.Create; 259 | try 260 | for i := 0 to NumDirs -1 do 261 | begin 262 | Letter := chr(fBundle.ReadByte); //One letter of a dir string 263 | fBundle.Seek(1, soFromCurrent); //Null byte after the letter 264 | 265 | ReturnPos1 := fBundle.ReadWord; 266 | if ReturnPos1 <> 0 then 267 | begin 268 | ReturnNames.Add(CurrName); 269 | ReturnIDs[ ReturnNames.Count -1 ] := returnPos1; 270 | end; 271 | 272 | ReturnPos2 := fBundle.ReadWord; 273 | if ReturnPos2 <> 0 then 274 | begin 275 | ReturnNames.Add(CurrName); 276 | ReturnIDs[ ReturnNames.Count -1 ] := returnPos2; 277 | end; 278 | 279 | CurrName := CurrName + Letter; //CurrName contains the dir string we are building 280 | fBundle.Seek(2, soFromCurrent); //2 bytes - Character ID (incremental from 1) 281 | StartIndex := fBundle.ReadWord; //2 bytes - First File ID in this Folder 282 | EndIndex := fBundle.ReadWord; //2 bytes- Last File ID in this Folder 283 | 284 | if (StartIndex <> 0) and (EndIndex <> 0) then 285 | begin 286 | PathNames.Add(CurrName); 287 | SetLength(PathIDsStart, PathNames.Count); //messy way of doing this - todo have array of records instead 288 | SetLength(PathIDsEnd, PathNames.Count); 289 | PathIDsStart[PathNames.Count-1] := StartIndex; 290 | PathIDsEnd[PathNames.Count-1] := EndIndex; 291 | end; 292 | 293 | //Process the returns now 294 | ThisID := i + 1; 295 | for j := 0 to ReturnNames.Count -1 do 296 | begin 297 | if ReturnIDs[j] = thisID then //Found one 298 | begin 299 | currName := ReturnNames.Strings[j]; 300 | 301 | //Shuffle the return arrays to remove this entry 302 | if j <> ReturnNames.Count -1 then 303 | begin 304 | returnIDs[j] := returnIDs[ ReturnNames.Count -1]; 305 | returnNames[j] := returnNames[ ReturnNames.Count -1]; 306 | end; 307 | end; 308 | end; 309 | end; 310 | 311 | //Sanitise the directory strings 312 | for i := 0 to pathNames.Count -1 do 313 | begin 314 | PathNames[i] := IncludeTrailingPathDelimiter(PathNames[i]); 315 | PathNames[i] := StringReplace(PathNames[i], '/', '\', [rfReplaceAll]) 316 | end; 317 | 318 | //Match the dir string to each file 319 | for i := 0 to PathNames.Count -1 do 320 | begin 321 | //Get each folder string, then loop through the path IDs associated with it and append the dir to each name 322 | for j := PathIDsStart[i]-1 to PathIDsEnd[i]-1 do //-1 because pathids start from 1 and our list starts from 0 323 | TDFFile(BundleFiles[j]).FileName := pathNames[i] + TDFFile(BundleFiles[j]).FileName; 324 | end; 325 | 326 | finally 327 | ReturnNames.Free; 328 | PathNames.Free; 329 | end; 330 | 331 | if (Assigned(FOnDoneLoading)) then 332 | FOnDoneLoading(NumFiles); 333 | end; 334 | 335 | procedure TPKGManager.SaveFile(FileNo: integer; DestDir, FileName: string); 336 | var 337 | SaveFile: TFileStream; 338 | begin 339 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 340 | begin 341 | Log(strErrFileSize + FileName); 342 | exit; 343 | end; 344 | 345 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 346 | begin 347 | Log(strErrFileNo); 348 | exit; 349 | end; 350 | 351 | SaveFile:=tfilestream.Create(IncludeTrailingPathDelimiter(DestDir) + FileName, 352 | fmOpenWrite or fmCreate); 353 | try 354 | SaveFileToStream(FileNo,SaveFile); 355 | finally 356 | SaveFile.Free; 357 | end; 358 | 359 | end; 360 | 361 | procedure TPKGManager.SaveFiles(DestDir: string); 362 | var 363 | i: integer; 364 | SaveFile: TFileStream; 365 | begin 366 | for I := 0 to BundleFiles.Count - 1 do 367 | begin 368 | ForceDirectories(extractfilepath(IncludeTrailingPathDelimiter(DestDir) + 369 | ExtractPartialPath( TDFFile(BundleFiles.Items[i]).FileName))); 370 | SaveFile:=TFileStream.Create(IncludeTrailingPathDelimiter(DestDir) + 371 | TDFFile(BundleFiles.Items[i]).FileName , fmOpenWrite or fmCreate); 372 | try 373 | SaveFileToStream(i, SaveFile); 374 | finally 375 | SaveFile.free; 376 | if Assigned(FOnProgress) then FOnProgress(GetFilesCount -1, i); 377 | Application.Processmessages; 378 | end; 379 | end; 380 | 381 | end; 382 | 383 | procedure TPKGManager.SaveFileToStream(FileNo: integer; DestStream: TStream); 384 | var 385 | Ext: string; 386 | TempStream: TMemoryStream; 387 | begin 388 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 389 | begin 390 | Log(strErrFileSize + TDFFile(BundleFiles.Items[FileNo]).FileName); 391 | exit; 392 | end; 393 | 394 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 395 | begin 396 | Log(strErrFileNo); 397 | exit; 398 | end; 399 | 400 | Ext:=Uppercase(ExtractFileExt(TDFFile(BundleFiles.Items[FileNo]).FileName)); 401 | 402 | fBundle.Position := TDFFile(BundleFiles.Items[FileNo]).Offset; 403 | 404 | 405 | if TDFFile(BundleFiles.Items[FileNo]).Compressed then 406 | begin 407 | TempStream := tmemorystream.Create; 408 | try 409 | TempStream.CopyFrom(fBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 410 | //tempstream.SaveToFile('c:\users\ben\desktop\testfile'); 411 | Tempstream.Position := 0; 412 | DecompressZLib(TempStream, TDFFile(BundleFiles.Items[FileNo]).UnCompressedSize, 413 | DestStream); 414 | finally 415 | TempStream.Free; 416 | end 417 | end 418 | else 419 | DestStream.CopyFrom(fBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 420 | 421 | DestStream.Position:=0; 422 | end; 423 | 424 | end. 425 | -------------------------------------------------------------------------------- /uDFExplorer_PPAKManager.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | { 15 | Psychonauts Pc Level pack files *.ppf 16 | File format seems to have changed in the newer steam and gog releases. 17 | UNFINISHED 18 | } 19 | 20 | unit uDFExplorer_PPAKManager; 21 | 22 | interface 23 | 24 | uses 25 | classes, sysutils, Contnrs, forms, math, system.IOUtils, 26 | uDFExplorer_BaseBundleManager, uFileReader, uMemReader, uDFExplorer_Types, 27 | uDFExplorer_Funcs, uZlib, 28 | RTTI; 29 | 30 | type 31 | TPPAKManager = class (TBundleManager) 32 | private 33 | fBigEndian: boolean; 34 | protected 35 | fBundle: TExplorerFileStream; 36 | fBundleFileName: string; 37 | function DetectBundle: boolean; override; 38 | function GetFilesCount: integer; override; 39 | function GetFileName(Index: integer): string; override; 40 | function GetFileSize(Index: integer): integer; override; 41 | function GetFileOffset(Index: integer): int64; override; 42 | function GetFileType(Index: integer): TFiletype; override; 43 | function GetFileExtension(Index: integer): string; override; 44 | function GetPsychoDDS(Index: integer): TPsychonautsDDS; 45 | //function OLDCalculateMipmapSize(Mipmaps, TextureID, TextureSize: integer): integer; 46 | //function OLDCalculateMainTextureSize(TextureID, Width, Height: integer): integer; 47 | function CalculateIndividualTextureSize(TextureFormat: TDDSTextureFormat; Width, Height: integer): integer; 48 | function CalculateFullTextureSize(Mipmaps, Width, Height: integer; TextureFormat: TDDSTextureFormat): integer; 49 | procedure Log(Text: string); override; 50 | //procedure ParsePPAK_OLD_ATTEMPT; 51 | procedure ParsePPAK; 52 | public 53 | BundleFiles: TObjectList; 54 | constructor Create(ResourceFile: string); override; 55 | destructor Destroy; override; 56 | procedure ParseFiles; override; 57 | procedure SaveFile(FileNo: integer; DestDir, FileName: string); override; 58 | procedure SaveFileToStream(FileNo: integer; DestStream: TStream); override; 59 | procedure SaveFiles(DestDir: string); override; 60 | property Count: integer read GetFilesCount; 61 | property FileName[Index: integer]: string read GetFileName; 62 | property FileSize[Index: integer]: integer read GetFileSize; 63 | property FileOffset[Index: integer]: int64 read GetFileOffset; 64 | property FileType[Index: integer]: TFileType read GetFileType; 65 | property FileExtension[Index: integer]: string read GetFileExtension; 66 | property PsychoDDS [Index: integer]: TPsychonautsDDS read GetPsychoDDS; 67 | property BigEndian: boolean read fBigEndian; 68 | end; 69 | 70 | const 71 | strErrInvalidFile: string = 'Not a valid bundle'; 72 | 73 | implementation 74 | 75 | { TBundleManager } 76 | 77 | constructor TPPAKManager.Create(ResourceFile: string); 78 | begin 79 | try 80 | fBundle:=TExplorerFileStream.Create(ResourceFile); 81 | except on E: EInvalidFile do 82 | raise; 83 | end; 84 | 85 | fBundleFileName:=ExtractFileName(ResourceFile); 86 | BundleFiles:=TObjectList.Create(true); 87 | 88 | if DetectBundle = false then 89 | raise EInvalidFile.Create( strErrInvalidFile ); 90 | end; 91 | 92 | destructor TPPAKManager.Destroy; 93 | begin 94 | if BundleFiles <> nil then 95 | begin 96 | BundleFiles.Free; 97 | BundleFiles:=nil; 98 | end; 99 | 100 | if fBundle <> nil then 101 | fBundle.free; 102 | 103 | inherited; 104 | end; 105 | 106 | function TPPAKManager.DetectBundle: boolean; 107 | var 108 | BlockHeader: integer; 109 | begin 110 | Result := false; 111 | BlockHeader := fBundle.ReadDWord; 112 | 113 | if BlockHeader = 1262571600 then //PPAK 114 | begin 115 | Result := true; 116 | fBundle.BigEndian := false; 117 | fBigEndian := false; 118 | end 119 | end; 120 | 121 | function TPPAKManager.GetFileExtension(Index: integer): string; 122 | begin 123 | if (not assigned(BundleFiles)) or 124 | (index < 0) or 125 | (index > GetFilesCount) then 126 | result:= '' 127 | else 128 | result:=TDFFile(BundleFiles.Items[Index]).FileExtension; 129 | end; 130 | 131 | function TPPAKManager.GetFileName(Index: integer): string; 132 | begin 133 | if (not assigned(BundleFiles)) or 134 | (index < 0) or 135 | (index > GetFilesCount) then 136 | result:= '' 137 | else 138 | result:=TDFFile(BundleFiles.Items[Index]).FileName; 139 | end; 140 | 141 | function TPPAKManager.GetFileOffset(Index: integer): int64; 142 | begin 143 | if (not assigned(BundleFiles)) or 144 | (index < 0) or 145 | (index > GetFilesCount) then 146 | result:=0 147 | else 148 | result:=TDFFile(BundleFiles.Items[Index]).offset; 149 | end; 150 | 151 | function TPPAKManager.GetFilesCount: integer; 152 | begin 153 | if BundleFiles <> nil then 154 | result:=BundleFiles.Count 155 | else 156 | result:=0; 157 | end; 158 | 159 | function TPPAKManager.GetFileSize(Index: integer): integer; 160 | begin 161 | if (not assigned(BundleFiles)) or 162 | (index < 0) or 163 | (index > GetFilesCount) then 164 | result:=-1 165 | else 166 | result:=TDFFile(BundleFiles.Items[Index]).size; 167 | end; 168 | 169 | function TPPAKManager.GetFileType(Index: integer): TFileType; 170 | begin 171 | if (not assigned(BundleFiles)) or 172 | (index < 0) or 173 | (index > GetFilesCount) then 174 | result:= ft_Unknown 175 | else 176 | result:=TDFFile(BundleFiles.Items[Index]).FileType; 177 | end; 178 | 179 | function TPPAKManager.GetPsychoDDS(Index: integer): TPsychonautsDDS; 180 | begin 181 | if (not assigned(BundleFiles)) or 182 | (index < 0) or 183 | (index > GetFilesCount) then 184 | result:= nil 185 | else 186 | result:=TDFFile(BundleFiles.Items[Index]).PsychonautsDDS; 187 | end; 188 | 189 | procedure TPPAKManager.Log(Text: string); 190 | begin 191 | if assigned(fOnDebug) then fOnDebug(Text); 192 | end; 193 | 194 | procedure TPPAKManager.ParseFiles; 195 | begin 196 | if fBigEndian then //All LE reading auto converted to BE 197 | Log('Detected as : big endian'); 198 | 199 | ParsePPAK; 200 | end; 201 | 202 | //New format RE'ed by John Peel https://github.com/JohnPeel/ppf/wiki/PPF-File-Format 203 | procedure TPPAKManager.ParsePPAK; 204 | var 205 | BundleVersion, LanguageID, LanguageSize, TextureCount, i, j, TextureSize, 206 | Path_ptr, Anim_ptr, PathLength, AnimFrameCount, CalculatedTextureSize, 207 | TextureWidth, TextureHeight, TextureNumMipmaps, Has_Palette, ResourceCount, 208 | TempWidth, TempHeight, ScriptsVersion, TextureDataStart: integer; 209 | TextureFormat: TDDSTextureFormat; 210 | TextureType: TPsychoTextureType; 211 | FileObject: TDFFile; 212 | LanguageHeader: word; 213 | begin 214 | //Initialise these here to stop compiler warnings. Todo refactor this procedure. 215 | TextureType := TPsychoTextureType(0); 216 | TextureFormat := TDDSTextureFormat(0); 217 | TextureWidth := 0; 218 | TextureHeight := 0; 219 | TextureNumMipmaps := 0; 220 | TextureDataStart := 0; 221 | 222 | fBundle.Position:= 0; 223 | 224 | if fBundle.ReadBlockName <> 'PPAK' then 225 | begin 226 | raise EInvalidFile.Create( strErrInvalidFile ); 227 | end; 228 | 229 | //Version section marker 0xFDFD 230 | if fBundle.ReadWord() = $FDFD then 231 | BundleVersion := fBundle.ReadWord() //1 232 | else 233 | fBundle.Seek(-2, soFromCurrent); //If identifier not there this whole section doesnt exist 234 | 235 | {Language section marker 0xFFFF 236 | If languages block is present then there's often multiple language blocks each with multiple textures eg in BBA1.ppf. 237 | No counter for how many language blocks there are so just keep repeating until we dont get a 0xFFFF language header - meaning we are then onto the MPAK section.} 238 | LanguageHeader := 0; 239 | repeat 240 | LanguageHeader := fBundle.ReadWord(); 241 | if LanguageHeader = $FFFF then 242 | begin 243 | LanguageID := fBundle.ReadWord(); 244 | LanguageSize := fBundle.ReadDWord(); 245 | end 246 | else 247 | fBundle.Seek(-2, soFromCurrent); //If identifier not there this whole section doesnt exist 248 | 249 | TextureCount := fBundle.ReadWord(); 250 | 251 | //Log(IntToStr(TextureCount)); 252 | //Log('Bundle pos before textures' + inttostr(fBundle.Position)); 253 | 254 | for i := 0 to TextureCount -1 do 255 | begin 256 | FileObject := TDFFile.Create; 257 | FileObject.Compressed := false; 258 | FileObject.CompressionType := 0; 259 | FileObject.FileTypeIndex := -1; 260 | FileObject.FileExtension := ''; 261 | FileObject.Offset := fBundle.Position; 262 | FileObject.FileType := ft_HeaderlessPsychoDDSImage; 263 | 264 | if BundleVersion = 1 then 265 | begin 266 | if fBundle.ReadDWord <> $31545820 then //' XT1' identifier 267 | raise EInvalidFile.Create( 'XT1 header expected but not found!' ); 268 | 269 | TextureSize := fBundle.ReadDWord; 270 | FileObject.Size := TextureSize; 271 | end 272 | else 273 | Log('WARNING bundle version 0 so texturesize + fileobjectsize potentially not set!*****************'); 274 | 275 | fBundle.Seek(12, soFromCurrent); // element_id, texture_handle, palette_handle 276 | Path_ptr := fBundle.ReadDWord; 277 | Anim_ptr := fBundle.ReadDWord; 278 | fBundle.Seek(20, soFromCurrent); // density, visual_importance, memory_importance, unknown0, flags 279 | 280 | //If there's a path 281 | if Path_ptr <> 0 then 282 | begin 283 | PathLength := fBundle.ReadWord; 284 | FileObject.FileName := PChar(fBundle.ReadString(PathLength)); //Null terminated 285 | FileObject.FileExtension := ExtractFileExt( FileObject.FileName ); 286 | end; 287 | 288 | //If its an animation 289 | if Anim_ptr <> 0 then 290 | begin 291 | AnimFrameCount := fBundle.ReadDWord; 292 | fBundle.Seek(24, soFromCurrent); //Rest of anim info 293 | end 294 | else 295 | AnimFrameCount := 1; 296 | 297 | if AnimFrameCount = 0 then 298 | begin 299 | Log('Anim frame count is 0!'); 300 | raise EInvalidFile.Create( strErrInvalidFile ); 301 | exit; 302 | end; 303 | 304 | //Multiple animation frames means we have to parse them all. First file in common.ppf for example 305 | for j := 0 to AnimFrameCount -1 do 306 | begin 307 | //Now at texture structure 308 | fBundle.Seek(4, soFromCurrent); //element_id 309 | TextureFormat := TDDSTextureFormat(fBundle.ReadDWord); 310 | TextureType := TPsychoTextureType(fBundle.ReadDWord); 311 | fBundle.Seek(4, soFromCurrent); //Flags 312 | TextureWidth := fBundle.ReadDWord; 313 | TextureHeight := fBundle.ReadDWord; 314 | TextureNumMipmaps := fBundle.ReadDWord; 315 | fBundle.Seek(16, soFromCurrent); 316 | 317 | 318 | if TextureFormat = PAL8 then 319 | begin 320 | Has_Palette := fBundle.ReadWord; 321 | if Has_Palette <> 0 then 322 | fBundle.Seek(4, soFromCurrent); //Seek past the palette 323 | end; 324 | 325 | 326 | 327 | //Store current pos for where texture data actually begins 328 | if j=0 then 329 | TextureDataStart := fBundle.Position - FileObject.Offset; 330 | 331 | //Correct for mipmaps being 0 when...they arent. 332 | if TextureNumMipmaps = 0 then 333 | begin 334 | //Log('Mipmaps=0 so correcting...'); 335 | TempWidth := TextureWidth; 336 | TempHeight := TextureHeight; 337 | while (TempWidth > 0) and (TempHeight > 0) do 338 | begin 339 | TempWidth := TempWidth shr 1; 340 | TempHeight := TempHeight shr 1; 341 | TextureNumMipmaps := TextureNumMipmaps + 1; 342 | end; 343 | end; 344 | 345 | //Log(FileObject.FileName + ' TextureSize=' + IntToStr(TextureSize) + ' Width=' + IntToStr(TextureWidth) + ' Height=' + IntToStr(TextureHeight) + ' HasPalette=' + inttostr(Has_Palette) + ' AnimFrameCount=' + IntToStr(AnimFrameCount) + ' TextureType=' + TRttiEnumerationType.GetName(TextureType) + ' TextureFormat=' + TRttiEnumerationType.GetName(TextureFormat) + ' TextureNumMipmaps=' + inttostr(TextureNumMipmaps)); 346 | 347 | CalculatedTextureSize := CalculateFullTextureSize(TextureNumMipmaps, TextureWidth, TextureHeight, TextureFormat); 348 | 349 | if TextureType = Cubemap then 350 | CalculatedTextureSize := CalculatedTextureSize * 6; 351 | 352 | //Log('Offset before seek past texture=' + inttostr(fBUndle.Position) + ' CalculatedTextureSize=' + inttostr(CalculatedTextureSize )); 353 | 354 | fBundle.Seek(CalculatedTextureSize, soFromCurrent); //Seek past the texture 355 | end; 356 | 357 | //Now texture data 358 | FileObject.PsychonautsDDS := TPsychonautsDDS.Create; 359 | FileObject.PsychonautsDDS.TextureType := TextureFormat; 360 | FileObject.PsychonautsDDS.Width := TextureWidth; 361 | FileObject.PsychonautsDDS.Height := TextureHeight; 362 | FileObject.PsychonautsDDS.Mipmaps := TextureNumMipmaps; 363 | FileObject.PsychonautsDDS.DataOffset := TextureDataStart; //fBundle.Position - FileObject.Offset; 364 | FileObject.PsychonautsDDS.MainTextureSize := CalculateIndividualTextureSize(TextureFormat, TextureWidth, TextureHeight); 365 | FileObject.PsychonautsDDS.IsCubemap := (TextureType = Cubemap); //TODO swap for enum TPsychoTextureType and just store that 366 | 367 | //Log('Offset=' + inttostr(fBUndle.Position) + ' CalculatedTextureSize=' + inttostr(CalculatedTextureSize )); 368 | 369 | BundleFiles.Add(FileObject); 370 | end; 371 | until LanguageHeader <> $FFFF; 372 | 373 | //MPAK section 374 | if fBundle.ReadBlockName <> 'MPAK' then 375 | begin 376 | Log('MPAK header not found!'); 377 | exit; 378 | end; 379 | 380 | ResourceCount := fBundle.ReadWord; 381 | //Log('NUM MPAK Files ' + inttostr(numfiles)); 382 | for I := 0 to ResourceCount -1 do 383 | begin 384 | FileObject := TDFFile.Create; 385 | FileObject.Compressed := false; 386 | FileObject.CompressionType := 0; 387 | FileObject.FileTypeIndex := -1; 388 | FileObject.FileExtension := ''; 389 | FileObject.FileType := ft_other; 390 | 391 | PathLength := fBundle.ReadWord; 392 | FileObject.FileName := PChar(fBundle.ReadString(PathLength)); //Null terminated 393 | FileObject.FileExtension := ExtractFileExt( FileObject.FileName ); 394 | fBundle.Seek(2, sofromcurrent); //?? 395 | 396 | FileObject.Size := fBundle.ReadDWord; 397 | FileObject.Offset := fBundle.Position; 398 | fBundle.Seek(FileObject.Size, sofromcurrent); 399 | 400 | BundleFiles.Add(FileObject); 401 | end; 402 | 403 | 404 | //ScriptsVersion section 405 | if fBundle.ReadWord() = $FCFC then 406 | ScriptsVersion := fBundle.ReadWord //Version = 1 407 | else 408 | fBundle.Seek(-2, soFromCurrent); //If identifier not there this whole section doesnt exist 409 | 410 | //Global section 411 | ResourceCount := fBundle.ReadWord; 412 | for i := 0 to ResourceCount -1 do 413 | begin 414 | FileObject := TDFFile.Create; 415 | FileObject.Compressed := false; 416 | FileObject.CompressionType := 0; 417 | FileObject.FileTypeIndex := -1; 418 | FileObject.FileExtension := ''; 419 | FileObject.FileType := ft_other; 420 | 421 | 422 | PathLength := fBundle.ReadWord; 423 | FileObject.FileName := PChar(fBundle.ReadString(PathLength)); //Null terminated 424 | FileObject.FileExtension := '.lua'; //ExtractFileExt( FileObject.FileName ); 425 | FileObject.Size := fBundle.ReadDWord; 426 | FileObject.Offset := fBundle.Position; 427 | fBundle.Seek(FileObject.Size, sofromcurrent); 428 | BundleFiles.Add(FileObject); 429 | end; 430 | 431 | //Scripts section 432 | ResourceCount := fBundle.ReadWord; 433 | for i := 0 to ResourceCount -1 do 434 | begin 435 | FileObject := TDFFile.Create; 436 | FileObject.Compressed := false; 437 | FileObject.CompressionType := 0; 438 | FileObject.FileTypeIndex := -1; 439 | FileObject.FileExtension := ''; 440 | FileObject.FileType := ft_other; 441 | 442 | if ScriptsVersion = 1 then //This path section only exists if ScriptsVersion is 1 443 | begin 444 | PathLength := fBundle.ReadWord; 445 | FileObject.FileName := PChar(fBundle.ReadString(PathLength)); //Null terminated 446 | end 447 | else 448 | FileObject.FileName := 'UnnamedScript' + inttostr(i); 449 | 450 | FileObject.FileExtension := '.lua'; 451 | FileObject.Size := fBundle.ReadDWord; 452 | FileObject.Offset := fBundle.Position; 453 | fBundle.Seek(FileObject.Size, sofromcurrent); 454 | BundleFiles.Add(FileObject); 455 | end; 456 | 457 | //Level section 458 | FileObject := TDFFile.Create; 459 | FileObject.Compressed := false; 460 | FileObject.CompressionType := 0; 461 | FileObject.FileTypeIndex := -1; 462 | FileObject.FileType := ft_Other; 463 | 464 | FileObject.FileName := TPath.GetFileNameWithoutExtension (fBundleFileName) + ' level file.plb'; 465 | FileObject.FileExtension := '.plb'; 466 | FileObject.Size := fBundle.Size - fBundle.Position; 467 | FileObject.Offset := fBundle.Position; 468 | BundleFiles.Add(FileObject); 469 | 470 | if (Assigned(FOnDoneLoading)) then 471 | FOnDoneLoading(BundleFiles.Count); 472 | end; 473 | 474 | 475 | function TPPAKManager.CalculateIndividualTextureSize( 476 | TextureFormat: TDDSTextureFormat; Width, Height: integer): integer; 477 | begin 478 | result := 0; 479 | 480 | case TextureFormat of 481 | A8R8G8B8, V16U16: result := (Width * Height) * 4; 482 | R8G8B8: result := (Width * Height) * 3; 483 | A4R4G4B4, A1R5G5B5, X1R5G5B5, R5G6B5, V8U8: result := (Width * Height) * 2; 484 | L8,A8, AL8, PAL8: result := (Width * Height); 485 | DXT1: result := Max(1, ((Width + 3) div 4)) * Max(1, ((Height + 3) div 4)) * 8; 486 | DXT3, DXT5: result := Max(1, ((Width + 3) div 4)) * Max(1, ((Height + 3) div 4)) * 16; 487 | else 488 | Log('Texture size not calculated for ' + TRttiEnumerationType.GetName(TextureFormat)); 489 | end 490 | end; 491 | 492 | function TPPAKManager.CalculateFullTextureSize(Mipmaps, Width, Height: integer; 493 | TextureFormat: TDDSTextureFormat): integer; 494 | var 495 | i: Integer; 496 | begin 497 | result := 0; 498 | 499 | for i := 0 to Mipmaps -1 do 500 | begin 501 | result := result + CalculateIndividualTextureSize(TextureFormat, Width, Height); 502 | Width := Width shr 1; 503 | Height := Height shr 1; 504 | end; 505 | 506 | end; 507 | 508 | procedure TPPAKManager.SaveFile(FileNo: integer; DestDir, FileName: string); 509 | var 510 | SaveFile: TFileStream; 511 | begin 512 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 513 | begin 514 | Log(strErrFileSize); 515 | exit; 516 | end; 517 | 518 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 519 | begin 520 | Log(strErrFileNo); 521 | exit; 522 | end; 523 | 524 | SaveFile:=tfilestream.Create(IncludeTrailingPathDelimiter(DestDir) + FileName, 525 | fmOpenWrite or fmCreate); 526 | try 527 | SaveFileToStream(FileNo,SaveFile); 528 | finally 529 | SaveFile.Free; 530 | end; 531 | 532 | end; 533 | 534 | procedure TPPAKManager.SaveFiles(DestDir: string); 535 | var 536 | i: integer; 537 | SaveFile: TFileStream; 538 | begin 539 | for I := 0 to BundleFiles.Count - 1 do 540 | begin 541 | ForceDirectories(extractfilepath(IncludeTrailingPathDelimiter(DestDir) + 542 | ExtractPartialPath( TDFFile(BundleFiles.Items[i]).FileName))); 543 | 544 | SaveFile:=TFileStream.Create(IncludeTrailingPathDelimiter(DestDir) + 545 | TDFFile(BundleFiles.Items[i]).FileName , fmOpenWrite or fmCreate); 546 | try 547 | SaveFileToStream(i, SaveFile); 548 | finally 549 | SaveFile.free; 550 | if Assigned(FOnProgress) then FOnProgress(GetFilesCount -1, i); 551 | Application.Processmessages; 552 | end; 553 | end; 554 | 555 | end; 556 | 557 | procedure TPPAKManager.SaveFileToStream(FileNo: integer; DestStream: TStream); 558 | var 559 | Ext: string; 560 | TempStream: TMemoryStream; 561 | begin 562 | if TDFFile(BundleFiles.Items[FileNo]).Size <= 0 then 563 | begin 564 | Log(strErrFileSize); 565 | exit; 566 | end; 567 | 568 | if (FileNo < 0) or (FileNo > BundleFiles.Count) then 569 | begin 570 | Log(strErrFileNo); 571 | exit; 572 | end; 573 | 574 | Ext:=Uppercase(ExtractFileExt(TDFFile(BundleFiles.Items[FileNo]).FileName)); 575 | 576 | fBundle.Seek(TDFFile(BundleFiles.Items[FileNo]).Offset, sofrombeginning); 577 | 578 | 579 | if TDFFile(BundleFiles.Items[FileNo]).Compressed then 580 | begin 581 | TempStream := tmemorystream.Create; 582 | try 583 | TempStream.CopyFrom(fBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 584 | //tempstream.SaveToFile('c:\users\ben\desktop\testfile'); 585 | Tempstream.Position := 0; 586 | DecompressZLib(TempStream, TDFFile(BundleFiles.Items[FileNo]).UnCompressedSize, 587 | DestStream); 588 | finally 589 | TempStream.Free; 590 | end 591 | end 592 | else 593 | DestStream.CopyFrom(fBundle, TDFFile(BundleFiles.Items[FileNo]).Size); 594 | 595 | DestStream.Position:=0; 596 | end; 597 | 598 | end. 599 | -------------------------------------------------------------------------------- /uDFExplorer_Types.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | unit uDFExplorer_Types; 15 | 16 | interface 17 | 18 | uses 19 | SysUtils; 20 | 21 | type 22 | TProgressEvent = procedure(ProgressMax: integer; ProgressPos: integer) of object; 23 | TDebugEvent = procedure(DebugText: string) of object; 24 | TOnDoneLoading = procedure(FileNamesCount: integer) of object; 25 | EInvalidFile = class (exception); 26 | 27 | TFileType = ( 28 | ft_GenericImage, 29 | ft_DDSImage, 30 | ft_HeaderlessDDSImage, 31 | ft_HeaderlessPsychoDDSImage, 32 | ft_HeaderlessDOTTDDSImage, 33 | ft_DOTTFontImage, 34 | ft_DOTTXMLCostumeWithImage, 35 | ft_FTHeaderlessCHNKImage, 36 | ft_Text, 37 | ft_CSVText, 38 | ft_DelimitedText, 39 | ft_Audio, 40 | ft_FSBFile, 41 | ft_IMCAudio, 42 | ft_LUA, 43 | ft_Other, 44 | ft_Unknown 45 | ); 46 | 47 | TDDSTextureFormat = ( 48 | A8R8G8B8 = 0, 49 | R8G8B8, 50 | A4R4G4B4, 51 | A1R5G5B5, 52 | X1R5G5B5, 53 | R5G6B5, 54 | A8, 55 | L8, 56 | AL8, 57 | DXT1, 58 | DXT3, 59 | DXT5, 60 | V8U8, 61 | V16U16, 62 | PAL8 63 | ); 64 | 65 | TPsychoTextureType = ( 66 | Bitmap = 0, 67 | Cubemap, 68 | VolumeMap, 69 | DepthBuffer 70 | ); 71 | 72 | TPsychonautsDDS = class 73 | TextureType: TDDSTextureFormat; 74 | Width: integer; 75 | Height: integer; 76 | Mipmaps: integer; 77 | DataOffset: integer; 78 | MainTextureSize: integer; 79 | IsCubemap: boolean; 80 | end; 81 | 82 | TDFFile = class 83 | FileName: string; 84 | UncompressedSize: integer; 85 | NameOffset: integer; 86 | Offset: int64; //Make it 64 bit int - DOTT has big uint offsets and then DOTT has 64 bit offsets 87 | Size: integer; 88 | FileTypeIndex: integer; 89 | CompressionType: integer; 90 | Compressed: boolean; 91 | FileExtension: string; 92 | FileType: TFileType; 93 | PsychonautsDDS: TPsychonautsDDS; 94 | end; 95 | 96 | TDDSType = ( 97 | DDS_NORMAL, 98 | DDS_HEADERLESS, 99 | DDS_HEADERLESS_PSYCHONAUTS, 100 | DDS_HEADERLESS_DOTT, 101 | DDS_HEADERLESS_DOTT_COSTUME, 102 | DDS_HEADERLESS_FT_CHNK 103 | ); 104 | 105 | TFSBCodec = ( 106 | FMOD_SOUND_FORMAT_NONE, //* Unitialized / unknown. */ 107 | FMOD_SOUND_FORMAT_PCM8, //* 8bit integer PCM data. */ 108 | FMOD_SOUND_FORMAT_PCM16, //* 16bit integer PCM data. */ 109 | FMOD_SOUND_FORMAT_PCM24, //* 24bit integer PCM data. */ 110 | FMOD_SOUND_FORMAT_PCM32, //* 32bit integer PCM data. */ 111 | FMOD_SOUND_FORMAT_PCMFLOAT, //* 32bit floating point PCM data. */ 112 | FMOD_SOUND_FORMAT_GCADPCM, //* Compressed Nintendo 3DS/Wii DSP data. */ 113 | FMOD_SOUND_FORMAT_IMAADPCM, //* Compressed IMA ADPCM data. */ 114 | FMOD_SOUND_FORMAT_VAG, //* Compressed PlayStation Portable ADPCM data. */ 115 | FMOD_SOUND_FORMAT_HEVAG, //* Compressed PSVita ADPCM data. */ 116 | FMOD_SOUND_FORMAT_XMA, //* Compressed Xbox360 XMA data. */ 117 | FMOD_SOUND_FORMAT_MPEG, //* Compressed MPEG layer 2 or 3 data. */ 118 | FMOD_SOUND_FORMAT_CELT, //* Compressed CELT data. */ 119 | FMOD_SOUND_FORMAT_AT9, //* Compressed PSVita ATRAC9 data. */ 120 | FMOD_SOUND_FORMAT_XWMA, //* Compressed Xbox360 xWMA data. */ 121 | FMOD_SOUND_FORMAT_VORBIS //* Compressed Vorbis data. */ 122 | ); 123 | 124 | TFSBFile = class 125 | FileName: string; 126 | Size: integer; 127 | Offset: integer; 128 | FileType: TFileType; 129 | FileExtension: string; 130 | Codec: TFSBCodec; 131 | Channels: integer; 132 | Bits: integer; 133 | Freq: integer; 134 | end; 135 | 136 | implementation 137 | 138 | end. 139 | -------------------------------------------------------------------------------- /uFileReader.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | unit uFileReader; 15 | 16 | interface 17 | 18 | uses 19 | Classes, SysUtils; 20 | 21 | type 22 | TExplorerFileStream = class (TFileStream) 23 | 24 | private 25 | fBigEndian: boolean; 26 | function Swap8 (const i: uInt64): uInt64; register; 27 | procedure setBigEndian(const Value: boolean); 28 | public 29 | function ReadByte: byte; inline; 30 | function ReadWord: word; inline; 31 | function ReadWordBE: word; inline; 32 | function ReadDWord: longword; inline; 33 | function ReadDWordLE: longword; inline; 34 | function ReadDWordBE: longword; inline; 35 | function ReadQWord: uint64; inline; 36 | function ReadQWordBE: uint64; inline; 37 | function ReadTriByte: longword; inline; 38 | function ReadTriByteBE: longword; inline; 39 | function ReadBlockName: string; inline; 40 | function ReadString(Length: integer): string; inline; 41 | function ReadStringAlt(Length: integer): string; inline; 42 | constructor Create(FileName: string); 43 | destructor Destroy; override; 44 | property BigEndian: boolean read fBigEndian write setBigEndian; 45 | 46 | end; 47 | 48 | implementation 49 | 50 | function TExplorerFileStream.ReadByte: byte; 51 | begin 52 | Read(result,1); 53 | end; 54 | 55 | function TExplorerFileStream.ReadWord: word; 56 | begin 57 | if fBigEndian then 58 | result :=ReadWordBE 59 | else 60 | Read(result,2); 61 | end; 62 | 63 | function TExplorerFileStream.ReadWordBE: word; 64 | begin 65 | result:=ReadByte shl 8 66 | +ReadByte; 67 | end; 68 | 69 | function TExplorerFileStream.ReadDWord: longword; 70 | begin 71 | if fBigEndian then 72 | result :=ReadDWordBE 73 | else 74 | Read(result,4); 75 | end; 76 | 77 | function TExplorerFileStream.ReadDWordBE: longword; 78 | begin 79 | result:=ReadByte shl 24 80 | +ReadByte shl 16 81 | +ReadByte shl 8 82 | +ReadByte; 83 | end; 84 | 85 | function TExplorerFileStream.ReadDWordLE: longword; 86 | begin 87 | Read(result,4); 88 | end; 89 | 90 | function TExplorerFileStream.ReadQWord: uint64; 91 | begin 92 | if fBigEndian then 93 | result := ReadQWordBE 94 | else 95 | Read(result,8); 96 | end; 97 | 98 | function TExplorerFileStream.Swap8(const i: uInt64): uInt64; 99 | asm 100 | mov edx, dword [i] 101 | bswap edx 102 | mov eax, dword [i+4] 103 | bswap eax 104 | end; 105 | 106 | function TExplorerFileStream.ReadQWordBE: uint64; 107 | var 108 | i: uint64; 109 | begin 110 | read(i, 8); 111 | result:= Swap8(i); 112 | end; 113 | 114 | function TExplorerFileStream.ReadBlockName: string; 115 | begin 116 | result:=chr(ReadByte)+chr(ReadByte)+chr(ReadByte)+chr(ReadByte); 117 | end; 118 | 119 | function TExplorerFileStream.ReadString(Length: integer): string; 120 | var 121 | n: longword; 122 | begin 123 | SetLength(result,length); 124 | for n:=1 to length do 125 | begin 126 | result[n]:=Chr(ReadByte); 127 | end; 128 | end; 129 | 130 | function TExplorerFileStream.ReadStringAlt(Length: integer): string; 131 | var //Replaces #0 chars with character 132 | n: longword; 133 | Rchar: char; 134 | begin 135 | SetLength(result,length); 136 | for n:=0 to length -1 do 137 | begin 138 | RChar:=Chr(ReadByte); 139 | if RChar=#0 then 140 | result[n]:='x' 141 | else 142 | result[n]:=rchar; 143 | end; 144 | end; 145 | 146 | function TExplorerFileStream.ReadTriByte: longword; 147 | begin 148 | if fBigEndian then 149 | result :=ReadTriByteBE 150 | else 151 | Read(result,3); 152 | end; 153 | 154 | function TExplorerFileStream.ReadTriByteBE: longword; 155 | begin 156 | result:=ReadByte shl 16 157 | +ReadByte shl 8 158 | +ReadByte; 159 | end; 160 | 161 | procedure TExplorerFileStream.setBigEndian(const Value: boolean); 162 | begin 163 | fBigEndian := Value; 164 | end; 165 | 166 | constructor TExplorerFileStream.Create(FileName: string); 167 | begin 168 | inherited Create(Filename, fmopenread OR fmShareDenyNone); 169 | fBigEndian := false; 170 | end; 171 | 172 | destructor TExplorerFileStream.Destroy; 173 | begin 174 | inherited; 175 | end; 176 | 177 | end. 178 | -------------------------------------------------------------------------------- /uMPEGHeaderCheck.pas: -------------------------------------------------------------------------------- 1 | //Taken from MP3FileUtils by Daniel Gaussmann http://www.gausi.de/ 2 | // 3 | 4 | unit uMPEGHeaderCheck; 5 | 6 | interface 7 | type 8 | TMpegHeader = record 9 | version: byte; 10 | layer: byte; 11 | protection: boolean; 12 | bitrate: LongInt; 13 | samplerate: LongInt; 14 | channelmode: byte; 15 | extension: byte; 16 | copyright: boolean; 17 | original: boolean; 18 | emphasis: byte; 19 | padding: boolean; 20 | framelength: word; 21 | valid: boolean; 22 | end; 23 | 24 | TBuffer = Array of byte; 25 | 26 | const 27 | MPEG_BIT_RATES : array[1..3] of array[1..3] of array[0..15] of word = 28 | { Version 1, Layer I } 29 | (((0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0), 30 | { Version 1, Layer II } 31 | (0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,0), 32 | { Version 1, Layer III } 33 | (0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,0)), 34 | { Version 2, Layer I } 35 | ((0,32,48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,0), 36 | { Version 2, Layer II } 37 | (0, 8,16,24, 32, 40, 48, 56, 64, 80, 96, 112,128,144,160,0), 38 | { Version 2, Layer III } 39 | (0, 8,16,24, 32, 40, 48, 56, 64, 80, 96, 112,128,144,160,0)), 40 | { Version 2.5, Layer I } 41 | ((0,32,48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,0), 42 | { Version 2.5, Layer II } 43 | (0, 8,16,24, 32, 40, 48, 56, 64, 80, 96, 112,128,144,160,0), 44 | { Version 2.5, Layer III } 45 | (0, 8,16,24, 32, 40, 48, 56, 64, 80, 96, 112,128,144,160,0))); 46 | 47 | sample_rates: array[1..3] of array [0..3] of word= 48 | ((44100,48000,32000,0), 49 | (22050,24000,16000,0), 50 | (11025,12000,8000,0)); 51 | 52 | function GetFramelength(version:byte;layer:byte;bitrate:longint;Samplerate:longint;padding:boolean):integer; 53 | function GetValidatedHeader(aBuffer: TBuffer; position: integer): TMpegheader; 54 | 55 | implementation 56 | 57 | function GetFramelength(version:byte;layer:byte;bitrate:longint;Samplerate:longint;padding:boolean):integer; 58 | begin 59 | if samplerate=0 then result := -2 60 | else 61 | if Layer=1 then 62 | result := trunc(12*bitrate*1000 / samplerate+Integer(padding)*4) 63 | else 64 | if Version = 1 then 65 | result := 144 * bitrate * 1000 DIV samplerate + integer(padding) 66 | else 67 | result := 72 * bitrate * 1000 DIV samplerate + integer(padding) 68 | 69 | end; 70 | 71 | function GetValidatedHeader(aBuffer: TBuffer; position: integer): TMpegheader; 72 | var bitrateindex, versionindex: byte; 73 | samplerateindex:byte; 74 | tmpLength: Integer; 75 | begin 76 | // a mpeg-header starts with 11 (eleven) bits 77 | if (abuffer[position]<>$FF) OR (abuffer[position+1]<$E0) 78 | then 79 | begin 80 | result.valid := False; 81 | exit; 82 | end; 83 | 84 | //Byte 1 and 2: AAAAAAAA AAABBCCD 85 | //A=1 (11 Sync bytes) at the beginning 86 | //B: version, normally BB=11 (=MPEG1, Layer3), but some others are allowed 87 | //C: Layer, for layer III is CC=01 88 | //D: Protection BIT. If set, the header is followed by a 16bit CRC 89 | Versionindex := (abuffer[position+1] shr 3) and 3; 90 | case versionindex of 91 | 0: result.version := 3; //version 2.5 actually - but I need an array-index. ;-) 92 | 1: result.version := 0; //Reserved 93 | 2: result.version := 2; 94 | 3: result.version := 1; 95 | end; 96 | result.Layer := 4-((abuffer[position+1] shr 1) and 3); 97 | result.protection := (abuffer[position+1] AND 1)=0; 98 | 99 | // ---> 100 | // bugfix by terryk from delphi-forum.de 101 | if (Result.version = 0) or (Result.Layer = 4) then 102 | begin 103 | Result.valid := False; 104 | Exit; 105 | end; 106 | // <--- 107 | 108 | // Byte 3: EEEEFFGH 109 | // E: Bitrate-index 110 | // F: Samplerate-index 111 | // G: Padding bit 112 | // H: Private bit 113 | bitrateindex := (abuffer[position+2] shr 4) AND $F; 114 | result.bitrate := MPEG_BIT_RATES[result.version][result.layer][bitrateindex]; 115 | if bitrateindex=$F then 116 | begin 117 | result.valid := false; // Bad Value ! 118 | exit; 119 | end; 120 | samplerateindex := (abuffer[position+2] shr 2) AND $3; 121 | result.samplerate := sample_rates[result.version][samplerateindex]; 122 | result.padding := ((abuffer[position+2] shr 1) AND $1) = 1; 123 | 124 | // Byte 4: IIJJKLMM 125 | // I: Channel mode 126 | // J: Mode extension (for Joint Stereo) 127 | // K: copyright 128 | // L: original 129 | // M: Emphasis =0 in most cases 130 | result.channelmode := ((abuffer[position+3] shr 6) AND 3); 131 | result.extension := ((abuffer[position+3] shr 4) AND 3); 132 | result.copyright := ((abuffer[position+3] shr 3) AND 1)=1; 133 | result.original := ((abuffer[position+3] shr 2) AND 1)=1; 134 | result.emphasis := (abuffer[position+3] AND 3); 135 | 136 | // "For Layer II there are some combinations of bitrate and mode which are not allowed." 137 | if result.layer=2 then 138 | if ((result.bitrate=32) AND (result.channelmode<>3)) 139 | OR ((result.bitrate=48) AND (result.channelmode<>3)) 140 | OR ((result.bitrate=56) AND (result.channelmode<>3)) 141 | //OR ((result.bitrate=80) AND (result.channelmode<>3)) //Brutal legend has this! 142 | OR ((result.bitrate=224) AND (result.channelmode=3)) 143 | OR ((result.bitrate=256) AND (result.channelmode=3)) 144 | OR ((result.bitrate=320) AND (result.channelmode=3)) 145 | OR ((result.bitrate=384) AND (result.channelmode=3)) 146 | then begin 147 | result.valid := false; 148 | exit; 149 | end; 150 | 151 | // calculate framelength 152 | tmpLength := GetFramelength(result.version, result.layer, 153 | result.bitrate, 154 | result.Samplerate, 155 | result.padding); 156 | 157 | if tmpLength > 0 then 158 | begin 159 | result.valid := True; 160 | result.framelength := Word(tmpLength); 161 | end else 162 | begin 163 | result.valid := false; 164 | result.framelength := high(word); 165 | end; 166 | 167 | end; 168 | end. 169 | -------------------------------------------------------------------------------- /uMemReader.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | unit uMemReader; 15 | 16 | interface 17 | 18 | uses 19 | Classes, SysUtils; 20 | 21 | type 22 | TExplorerMemoryStream = class (TMemoryStream) 23 | 24 | private 25 | 26 | public 27 | function ReadByte: byte; 28 | function ReadWord: word; 29 | function ReadWordBE: word; 30 | function ReadDWord: longword; 31 | function ReadDWordBE: longword; 32 | function ReadBlockName: string; 33 | function ReadBuffer(var Buffer; Count: longint): longint; 34 | function ReadString(Length: integer): string; inline; 35 | function ReadAnsiString(Length: integer): ansistring; inline; 36 | function ReadStringAlt(Length: integer): string; inline; 37 | function FindFileHeader(Header: string; StartSearchAt, EndSearchAt: cardinal): longint; 38 | constructor Create; 39 | destructor Destroy; override; 40 | 41 | end; 42 | 43 | implementation 44 | 45 | function TExplorerMemoryStream.ReadByte: byte; 46 | begin 47 | Read(result,1); 48 | end; 49 | 50 | function TExplorerMemoryStream.ReadWord: word; 51 | begin 52 | Read(result,2); 53 | end; 54 | 55 | function TExplorerMemoryStream.ReadWordBE: word; 56 | begin 57 | result:=ReadByte shl 8 58 | +ReadByte; 59 | end; 60 | 61 | function TExplorerMemoryStream.ReadDWord: longword; 62 | begin 63 | Read(result,4); 64 | end; 65 | 66 | 67 | function TExplorerMemoryStream.ReadDWordBE: longword; 68 | begin 69 | result:=ReadByte shl 24 70 | +ReadByte shl 16 71 | +ReadByte shl 8 72 | +ReadByte; 73 | end; 74 | 75 | function TExplorerMemoryStream.ReadBlockName: string; 76 | begin 77 | result:=chr(ReadByte)+chr(ReadByte)+chr(ReadByte)+chr(ReadByte); 78 | end; 79 | 80 | function TExplorerMemoryStream.ReadBuffer(var Buffer; Count:longint): longint; 81 | begin 82 | result:=Read(Buffer,Count); 83 | end; 84 | 85 | function TExplorerMemoryStream.ReadString(Length: integer): string; 86 | var 87 | n: longword; 88 | begin 89 | SetLength(result,length); 90 | for n:=1 to length do 91 | begin 92 | result[n]:=Chr(ReadByte); 93 | end; 94 | end; 95 | 96 | function TExplorerMemoryStream.ReadAnsiString(Length: integer): ansistring; 97 | var 98 | n: longword; 99 | begin 100 | SetLength(result,length); 101 | for n:=1 to length do 102 | begin 103 | result[n]:=Ansichar(Chr(ReadByte)); 104 | end; 105 | 106 | end; 107 | 108 | function TExplorerMemoryStream.ReadStringAlt(Length: integer): string; 109 | var //Replaces #0 chars 110 | n: longword; 111 | Rchar: char; 112 | begin 113 | SetLength(result,length); 114 | for n:=0 to length -1 do 115 | begin 116 | RChar:=Chr(ReadByte); 117 | if RChar=#0 then 118 | result[n]:='x' 119 | else 120 | result[n]:=rchar; 121 | end; 122 | end; 123 | 124 | constructor TExplorerMemoryStream.Create; 125 | begin 126 | inherited Create; 127 | end; 128 | 129 | destructor TExplorerMemoryStream.Destroy; 130 | begin 131 | inherited; 132 | end; 133 | 134 | function TExplorerMemoryStream.FindFileHeader(Header: string; StartSearchAt, 135 | EndSearchAt: cardinal): longint; 136 | var 137 | HeaderLength, Index: integer; 138 | begin 139 | Result:=-1; 140 | Index:=1; 141 | if EndSearchAt > self.Size then 142 | EndSearchAt:=self.Size; 143 | 144 | HeaderLength:=Length(Header); 145 | if HeaderLength <= 0 then exit; 146 | 147 | 148 | Self.Position:=StartSearchAt; 149 | while Self.Position < EndSearchAt do 150 | begin 151 | if Chr(ReadByte) <> Header[Index] then 152 | begin 153 | if Index > 1 then 154 | Self.Position := Self.Position -1; 155 | 156 | Index:=1; 157 | continue; 158 | end; 159 | 160 | inc(Index); 161 | if index > HeaderLength then 162 | begin 163 | Result:=Self.Position - HeaderLength; 164 | exit; 165 | end; 166 | end; 167 | 168 | 169 | end; 170 | 171 | end. 172 | -------------------------------------------------------------------------------- /uVimaDecode.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgbennyboy/DoubleFine-Explorer/b06e04366e304e65d7c8ae8f8fe24e62a7c9910d/uVimaDecode.pas -------------------------------------------------------------------------------- /uWaveWriter.pas: -------------------------------------------------------------------------------- 1 | { 2 | TWaveStream class 3 | (c) 2005 Benjamin Haisch 4 | Writes stuff to wave files without ACM etc 5 | } 6 | 7 | unit uWaveWriter; 8 | 9 | interface 10 | 11 | uses 12 | Classes, SysUtils; 13 | 14 | const 15 | WAVE_FORMAT_PCM = $0001; 16 | 17 | type 18 | TWaveHeader = record 19 | riff, 20 | totallen, 21 | wave, 22 | fmt, 23 | wavelen: cardinal; 24 | wFormatTag, 25 | wChannels: word; 26 | dwSamplesPerSec, 27 | dwAvgBytesPerSec: cardinal; 28 | wBlockAlign, 29 | wBitsPerSample: word; 30 | data, 31 | datalen: cardinal; 32 | end; 33 | 34 | type 35 | TWaveStream = class(TStream) 36 | private 37 | FDest: TStream; 38 | FStartOfs: integer; 39 | FWaveHeader: TWaveHeader; 40 | public 41 | constructor Create(Dest: TStream; Channels, Bits, Freq: integer); 42 | destructor Destroy; override; 43 | 44 | procedure WriteBuffer(Buf: Pointer; Size: longint); 45 | function Write(const Buffer; Count: Longint): Longint; override; 46 | function Read(var Buffer; Count: Longint): Longint; override; 47 | 48 | function CopyFrom(Source: TStream; Count: Int64): Int64; 49 | 50 | end; 51 | 52 | implementation 53 | 54 | function MakeID(Str: ansistring): cardinal; 55 | begin 56 | Move(Str[1], Result, 4); 57 | end; 58 | 59 | constructor TWaveStream.Create(Dest: TStream; Channels, Bits, Freq: integer); 60 | begin 61 | FDest:= Dest; 62 | FStartOfs:= FDest.Position; 63 | 64 | with FWaveHeader do begin 65 | riff:= MakeID('RIFF'); 66 | totallen:= SizeOf(TWaveHeader) - 8; 67 | wave:= MakeID('WAVE'); 68 | fmt:= MakeID('fmt '); 69 | wavelen:= 16; 70 | wFormatTag:= WAVE_FORMAT_PCM; 71 | wChannels:= Channels; 72 | dwSamplesPerSec:= Freq; 73 | wBlockAlign:= Channels * (Bits div 8); 74 | dwAvgBytesPerSec:= wBlockAlign * Freq; 75 | wBitsPerSample:= Bits; 76 | data:= MakeID('data'); 77 | datalen:= 0; 78 | end; 79 | 80 | FDest.Write(FWaveHeader, SizeOf(TWaveHeader)); 81 | 82 | end; 83 | 84 | destructor TWaveStream.Destroy; 85 | var 86 | SavePos: Cardinal; 87 | begin 88 | SavePos := FDest.Position; 89 | FDest.Seek(FStartOfs, soFromBeginning); 90 | FDest.Write(FWaveHeader, SizeOf(TWaveHeader)); 91 | FDest.Position := SavePos; 92 | end; 93 | 94 | procedure TWaveStream.WriteBuffer(Buf: Pointer; Size: longint); 95 | begin 96 | FDest.Write(Buf^, Size); 97 | Inc(FWaveHeader.DataLen, Size); 98 | Inc(FWaveHeader.TotalLen, Size); 99 | end; 100 | 101 | function TWaveStream.Write(const Buffer; Count: Longint): Longint; 102 | begin 103 | WriteBuffer(@Buffer, Count); 104 | Result := Count; 105 | end; 106 | 107 | function TWaveStream.Read(var Buffer; Count: Longint): Longint; 108 | begin 109 | Result := 0; 110 | end; 111 | 112 | function TWaveStream.CopyFrom(Source: TStream; Count: Int64): Int64; 113 | const 114 | MaxBufSize = $F000; 115 | var 116 | BufSize, N: Integer; 117 | Buffer: PChar; 118 | begin 119 | Result := Count; 120 | if Count > MaxBufSize then BufSize := MaxBufSize else BufSize := Count; 121 | GetMem(Buffer, BufSize); 122 | try 123 | while Count <> 0 do 124 | begin 125 | if Count > BufSize then N := BufSize else N := Count; 126 | Source.ReadBuffer(Buffer^, N); 127 | WriteBuffer(Buffer, N); 128 | Dec(Count, N); 129 | end; 130 | finally 131 | FreeMem(Buffer, BufSize); 132 | end; 133 | end; 134 | 135 | end. 136 | -------------------------------------------------------------------------------- /uXCompress.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | 9 | //Adapted from https://github.com/indirivacua/RAGE-Console-Texture-Editor/blob/master/Compression.LZX.pas 10 | 11 | unit uXCompress; 12 | 13 | interface 14 | 15 | uses 16 | Classes, Windows, Sysutils, 17 | MemoryModule; 18 | 19 | type 20 | EXCompressDecompressionError = class (exception); 21 | LZXBlockSize = record 22 | UnCompressedSize, 23 | CompressedSize: WORD; 24 | end; 25 | 26 | XMEMCODEC_TYPE = ( XMEMCODEC_DEFAULT = 0, XMEMCODEC_LZX = 1 ); 27 | XMEMCODEC_PARAMETERS_LZX = packed record 28 | Flags: Integer; 29 | WindowSize: Integer; 30 | CompressionPartitionSize: Integer; 31 | end; 32 | 33 | var 34 | DllLib : PMemoryModule; 35 | XMemCreateDecompressionContext: function(CodecType: XMEMCODEC_TYPE; pCodecParams: Pointer; Flags: Integer; pContext: PInteger): HRESULT; stdcall; 36 | XMemDestroyDecompressionContext: procedure (Context: Integer); stdcall; 37 | XMemResetDecompressionContext: function(Context: Integer): HRESULT; stdcall; 38 | XMemDecompressStream: function(Context: Integer; pDestination: Pointer; pDestSize: PInteger; pSource: Pointer; pSrcSize: PInteger): HRESULT; stdcall; 39 | 40 | 41 | XDecompress: function(inData: PAnsiChar; inlen: integer; outdata: PAnsiChar; outlen: integer): integer; cdecl; 42 | LZXinit: function (window: integer): Integer; cdecl; 43 | procedure XCompress_Load_DLL; 44 | procedure DecompressXCompress_Old(Source: TStream; Dest: TStream; UncompressedSize: integer); 45 | procedure DecompressXCompress(Source: TStream; Dest: TStream; CompressedSize, UncompressedSize: integer); 46 | 47 | 48 | 49 | const 50 | XMEMCOMPRESS_STREAM = $00000001; 51 | 52 | implementation 53 | 54 | procedure XCompress_Load_DLL; 55 | var 56 | ResStream: TResourceStream; 57 | begin 58 | if DllLib <> nil then 59 | exit; //already loaded 60 | 61 | //Load it from resource into memory 62 | ResStream := TResourceStream.Create(hInstance, 'xcompressdll', RT_RCDATA); 63 | try 64 | ResStream.Position := 0; 65 | if MemoryLoadLibrary(ResStream.Memory, DLLLib) < 0 then 66 | begin 67 | raise Exception.Create('XCompress dll load failed!'); 68 | exit; 69 | end; 70 | finally 71 | ResStream.Free; 72 | end; 73 | 74 | 75 | 76 | {XDecompress := MemoryGetProcAddress(DllLib, PAnsiChar('LZXdecompress')); 77 | if not Assigned (XDecompress) then 78 | begin 79 | raise Exception.Create('Couldnt find decompression function in DLL'); 80 | end; 81 | 82 | LZXinit := MemoryGetProcAddress(DllLib, PAnsiChar('LZXinit')); 83 | if not Assigned (LZXinit) then 84 | begin 85 | raise Exception.Create('Couldnt find LZXinit function in DLL'); 86 | end;} 87 | 88 | XMemCreateDecompressionContext := MemoryGetProcAddress(DllLib, PAnsiChar('XMemCreateDecompressionContext')); 89 | if not Assigned (XMemCreateDecompressionContext) then 90 | begin 91 | raise Exception.Create('Couldnt find XMemCreateDecompressionContext function in DLL'); 92 | end; 93 | 94 | XMemDestroyDecompressionContext := MemoryGetProcAddress(DllLib, PAnsiChar('XMemDestroyDecompressionContext')); 95 | if not Assigned (XMemDestroyDecompressionContext) then 96 | begin 97 | raise Exception.Create('Couldnt find XMemDestroyDecompressionContext function in DLL'); 98 | end; 99 | 100 | XMemResetDecompressionContext := MemoryGetProcAddress(DllLib, PAnsiChar('XMemResetDecompressionContext')); 101 | if not Assigned (XMemResetDecompressionContext) then 102 | begin 103 | raise Exception.Create('Couldnt find XMemResetDecompressionContext function in DLL'); 104 | end; 105 | 106 | XMemDecompressStream := MemoryGetProcAddress(DllLib, PAnsiChar('XMemDecompressStream')); 107 | if not Assigned (XMemDecompressStream) then 108 | begin 109 | raise Exception.Create('Couldnt find XMemDecompressStream function in DLL'); 110 | end; 111 | 112 | end; 113 | 114 | function ReadBlockSize(Stream: TStream): LZXBlockSize; 115 | var 116 | b0, b1, b2, b3, b4: Byte; 117 | begin 118 | Stream.Read(b0,1); 119 | if b0 = $FF then 120 | begin 121 | Stream.Read(b1,1); 122 | Stream.Read(b2,1); 123 | Stream.Read(b3,1); 124 | Stream.Read(b4,1); 125 | Result.UnCompressedSize:= b2 or b1 shl 8; //(b1 shl 8)+b2; 126 | Result.CompressedSize:= b4 or b3 shl 8; //(b3 shl 8)+b4; 127 | end 128 | else 129 | begin 130 | Stream.Read(b1,1); 131 | Result.UnCompressedSize:= $8000; 132 | Result.CompressedSize:= b1 or b0 shl 8; //(b0 shl 8)+b1; 133 | end; 134 | end; 135 | 136 | procedure DecompressXCompress_Old(Source: TStream; Dest: TStream; UncompressedSize: integer); 137 | var 138 | //OutResult : integer; 139 | Identifier: byte; 140 | BlockSize: LZXBlockSize; 141 | pDataIn, pDataOut: PAnsiChar; 142 | begin 143 | LZXinit(17); 144 | {Source.Read(Identifier, 1); 145 | if Identifier <> $FF then raise EXCompressDecompressionError.Create('Decompression Failed not XMemCompress compressed data'); //Always first byte FF? 146 | Source.Seek(-1, soFromCurrent);} 147 | 148 | while (Dest.Size <> UncompressedSize) do 149 | begin 150 | BlockSize:= ReadBlockSize(Source); 151 | GetMem(pDataIn, BlockSize.CompressedSize); 152 | try 153 | GetMem(pDataOut, BlockSize.UnCompressedSize); 154 | try 155 | Source.ReadBuffer(pDataIn^,BlockSize.CompressedSize); 156 | {OutResult :=} XDecompress(pDataIn, BlockSize.CompressedSize, pDataOut, BlockSize.UnCompressedSize); 157 | {if OutResult <> 0 then //Unsure about this, it seems to return 0 if uncompressed ok 158 | begin 159 | raise EXCompressDecompressionError.Create('XMemCompress Decompression Failed. Result was ' + inttostr(OutResult)); 160 | end;} 161 | Dest.WriteBuffer(pDataOut^,BlockSize.UnCompressedSize); 162 | finally 163 | FreeMem(pDataOut, BlockSize.UnCompressedSize); 164 | end; 165 | finally 166 | FreeMem(pDataIn, BlockSize.CompressedSize); 167 | end; 168 | end; 169 | Dest.Position := 0; 170 | end; 171 | 172 | procedure DecompressXCompress(Source: TStream; Dest: TStream; CompressedSize, UncompressedSize: integer); 173 | var 174 | Contex: Integer; 175 | Result: HRESULT; 176 | codec_parameters: XMEMCODEC_PARAMETERS_LZX; 177 | pSource, pDest: Pointer; 178 | dwInSize, dwOutSize: DWORD; 179 | tmp: integer; 180 | begin 181 | Contex:=0; 182 | Result:= XMemCreateDecompressionContext(XMEMCODEC_LZX, @codec_parameters, XMEMCOMPRESS_STREAM, @Contex); 183 | if Succeeded(Result) then 184 | begin 185 | Result:= XMemResetDecompressionContext(Contex); 186 | dwInSize := CompressedSize; 187 | dwOutSize := UncompressedSize; 188 | GetMem(pSource, dwInSize); 189 | ZeroMemory(pSource, dwInSize); 190 | Source.Read(pSource^, dwInSize); 191 | pDest:= GetMemory(dwOutSize); 192 | ZeroMemory(pDest, dwOutSize); 193 | tmp:=dwOutSize; 194 | Result:= XMemDecompressStream(Contex, pDest, @dwOutSize, pSource, @dwInSize); 195 | if Succeeded(Result) then 196 | begin 197 | dwOutSize:=tmp; 198 | Dest.Write(pDest^, dwOutSize); 199 | FreeMemory(pDest); 200 | FreeMemory(pSource); 201 | end; 202 | XMemDestroyDecompressionContext(Contex); 203 | Dest.Position := 0; 204 | end; 205 | end; 206 | 207 | Initialization 208 | DllLib := nil; 209 | finalization 210 | if DllLib <> nil then 211 | MemoryFreeLibrary(DllLib); 212 | 213 | end. 214 | -------------------------------------------------------------------------------- /uXboxAdpcmDecoder.pas: -------------------------------------------------------------------------------- 1 | { 2 | TXboxAdpcmDecoder class 3 | (c) 2005 Benjamin Haisch 4 | 5 | Revision 2 with stereo support 6 | 7 | } 8 | 9 | unit uXboxAdpcmDecoder; 10 | 11 | interface 12 | 13 | uses 14 | Classes, SysUtils; 15 | 16 | type 17 | TAdpcmState = packed record 18 | Index, StepSize: Integer; 19 | Predictor: SmallInt; 20 | end; 21 | TXboxAdpcmDecoder = class 22 | private 23 | FChannels, 24 | FBlockSize: Word; 25 | FAdpcmState: array[0..1] of TAdpcmState; 26 | function DecodeSample(Code: Byte; var State: TAdpcmState): Integer; 27 | public 28 | constructor Create(AChannels: Word); 29 | destructor Destroy; override; 30 | 31 | procedure Decode(ASource, ADest: TStream; SourcePos, SourceSize: integer); 32 | 33 | end; 34 | 35 | implementation 36 | 37 | const 38 | StepTable: array[0..88] of Integer = ( 39 | $7, $8, $9, $0A, $0B, $0C, $0D, $0E, $10, $11, $13, $15, $17, $19, $1C, 40 | $1F, $22, $25, $29, $2D, $32, $37, $3C, $42, $49, $50, $58, $61, $6B, 41 | $76, $82, $8F, $9D, $0AD, $0BE, $0D1, $0E6, $0FD, $117, $133, $151, 42 | $173, $198, $1C1, $1EE, $220, $256, $292, $2D4, $31C, $36C, $3C3, 43 | $424, $48E, $502, $583, $610, $6AB, $756, $812, $8E0, $9C3, $0ABD, 44 | $0BD0, $0CFF, $0E4C, $0FBA, $114C, $1307, $14EE, $1706, $1954, $1BDC, 45 | $1EA5, $21B6, $2515, $28CA, $2CDF, $315B, $364B, $3BB9, $41B2, $4844, 46 | $4F7E, $5771, $602F, $69CE, $7462, $7FFF 47 | ); 48 | 49 | IndexTable: array[0..15] of Integer =( 50 | -1, -1, -1, -1, 2, 4, 6, 8, 51 | -1, -1, -1, -1, 2, 4, 6, 8 52 | ); 53 | 54 | constructor TXboxAdpcmDecoder.Create(AChannels: Word); 55 | begin 56 | case AChannels of 57 | 1: FBlockSize := $20; 58 | 2: FBlockSize := $40; 59 | end; 60 | FChannels := AChannels; 61 | end; 62 | 63 | destructor TXboxAdpcmDecoder.Destroy; 64 | begin 65 | end; 66 | 67 | procedure TXboxAdpcmDecoder.Decode(ASource, ADest: TStream; SourcePos, SourceSize: integer); 68 | var 69 | i, j: Integer; 70 | Channel: Byte; 71 | CodeBuf: Cardinal; 72 | Buffers: array[0..1] of array[0..7] of SmallInt; 73 | 74 | procedure PrepareAdpcmState(var AdpcmState: TAdpcmState); 75 | begin 76 | AdpcmState.Predictor := 0; 77 | AdpcmState.Index := 0; 78 | ASource.Read(AdpcmState.Predictor, 2); 79 | ASource.Read(AdpcmState.Index, 1); 80 | ASource.Seek(1, soFromCurrent); 81 | ADest.Write(AdpcmState.Predictor, 2); 82 | AdpcmState.StepSize := StepTable[AdpcmState.Index]; 83 | end; 84 | 85 | begin 86 | while ASource.Position < SourcePos + SourceSize do begin 87 | // read the adpcm header 88 | PrepareAdpcmState(FAdpcmState[0]); 89 | if FChannels = 2 then 90 | PrepareAdpcmState(FAdpcmState[1]); 91 | // decode the stuff 92 | for i := 0 to 7 do begin 93 | // decode channel 1 data 94 | Channel := 0; 95 | ASource.Read(CodeBuf, 4); 96 | for j := 0 to 7 do begin 97 | Buffers[Channel,j] := DecodeSample(CodeBuf and $0F, FAdpcmState[Channel]); 98 | CodeBuf := CodeBuf shr 4; 99 | end; 100 | // decode channel 2 data if available 101 | if FChannels = 2 then begin 102 | Channel := 1; 103 | ASource.Read(CodeBuf, 4); 104 | for j := 0 to 7 do begin 105 | Buffers[Channel,j] := DecodeSample(CodeBuf and $0F, FAdpcmState[Channel]); 106 | CodeBuf := CodeBuf shr 4; 107 | end; 108 | end; 109 | // write the decoded samples 110 | for j := 0 to 7 do begin 111 | ADest.Write(Buffers[0,j], 2); 112 | if FChannels = 2 then 113 | ADest.Write(Buffers[1,j], 2); 114 | end; 115 | end; 116 | end; 117 | end; 118 | 119 | function TXboxAdpcmDecoder.DecodeSample(Code: Byte; var State: TAdpcmState): Integer; 120 | var 121 | Delta: Integer; 122 | begin 123 | // get the delta value 124 | Delta := 0; 125 | if Code and 4 = 4 then Inc(Delta, State.StepSize); 126 | if Code and 2 = 2 then Inc(Delta, State.StepSize shr 1); 127 | if Code and 1 = 1 then Inc(Delta, State.StepSize shr 2); 128 | Inc(Delta, State.StepSize shr 3); 129 | // sign bit set? 130 | if Code and 8 = 8 then Delta := -Delta; 131 | Result := State.Predictor + Delta; 132 | // clip the sample 133 | if Result > High(SmallInt) then 134 | Result := High(SmallInt) 135 | else if Result < Low(SmallInt) then 136 | Result := Low(SmallInt); 137 | Inc(State.Index, IndexTable[Code]); 138 | // clip the index 139 | if State.Index < 0 then State.Index := 0 140 | else if State.Index > 88 then State.Index := 88; 141 | // get the new stepsize 142 | State.StepSize := StepTable[State.Index]; 143 | State.Predictor := Result; 144 | end; 145 | 146 | end. 147 | 148 | -------------------------------------------------------------------------------- /uZlib.pas: -------------------------------------------------------------------------------- 1 | { 2 | ****************************************************** 3 | DoubleFine Explorer 4 | By Bennyboy 5 | Http://quickandeasysoftware.net 6 | ****************************************************** 7 | } 8 | { 9 | This Source Code Form is subject to the terms of the Mozilla Public 10 | License, v. 2.0. If a copy of the MPL was not distributed with this 11 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 12 | } 13 | 14 | unit uZlib; 15 | 16 | interface 17 | 18 | uses 19 | Classes, ZlibEx; 20 | 21 | 22 | procedure DecompressZlib(Source: TStream; UnCompressedSize: integer; 23 | Dest: TStream); 24 | 25 | implementation 26 | 27 | function Private_DecompressZLib(Source: TStream; UnCompressedSize, WindowBits: integer; 28 | Dest: TStream): boolean; 29 | var 30 | DeCompressionStream: TZDecompressionStream; 31 | begin 32 | Result := false; 33 | 34 | DeCompressionStream := TZDecompressionStream.Create(Source, WindowBits); 35 | try 36 | try 37 | Dest.CopyFrom(DeCompressionStream, UnCompressedSize); 38 | Result := true; 39 | except on E: EZDecompressionError do 40 | end; 41 | finally 42 | DeCompressionStream.Free; 43 | end; 44 | end; 45 | 46 | procedure DecompressZlib(Source: TStream; UnCompressedSize: integer; 47 | Dest: TStream); 48 | var 49 | SourcePos: integer; 50 | begin 51 | SourcePos := Source.Position; 52 | if Private_DecompressZLib(Source, UncompressedSize, 0, Dest) = false then 53 | begin 54 | Source.Position := SourcePos; 55 | if Private_DecompressZLib(Source, UncompressedSize, -15, Dest) = false then 56 | raise EZDecompressionError.Create('Decompression error!'); 57 | end; 58 | 59 | end; 60 | 61 | 62 | {procedure DecompressZlib(Source: TStream; UnCompressedSize: integer; 63 | Dest: TStream); 64 | var 65 | DeCompressionStream: TZDecompressionStream; 66 | begin 67 | DecompressionStream:=TZDecompressionStream.Create(Source); 68 | try 69 | Dest.CopyFrom(DecompressionStream, UnCompressedSize); 70 | finally 71 | DecompressionStream.Free; 72 | end; 73 | end;} 74 | end. 75 | --------------------------------------------------------------------------------