mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-07-04 13:47:04 -04:00
DBPF reader by Tristan Dobrowolski
This commit is contained in:
parent
41be518322
commit
55f23b2a4e
4 changed files with 447 additions and 0 deletions
17
library/formats/dbpf/LICENSE.md
Normal file
17
library/formats/dbpf/LICENSE.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
Copyright (C) 2021 Tristan Dobrowolski <TristanCDobrowolski@gmail.com>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
104
library/formats/dbpf/README.md
Normal file
104
library/formats/dbpf/README.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
# dbpf_reader
|
||||
|
||||
A compact DBPF (Database Packed File) reader library. This library provides a simple interface in C for reading DBPF files. It was originally created for reading of .package files used in The Sims 4, but it can be easily extended for support of other DBPF versions. The format which is currently fully supported is DBPF 2.1 (index version 0.3).
|
||||
|
||||
## DBPF - Database Packed File
|
||||
|
||||
DBPF is a proprietary archive format used in many games developed by Maxis Studios, including The Sims series, SimCity series and Spore.
|
||||
Further reading on DBPF format:
|
||||
|
||||
- [SC4D Encyclopaedia - DBPF](https://wiki.sc4devotion.com/index.php?title=DBPF)
|
||||
- [Sims Wiki - Sims 3: DBPF](http://simswiki.info/Sims_3:DBPF)
|
||||
- [Mod The Sims - Database Packed File](https://modthesims.info/wiki.php?title=DBPF)
|
||||
|
||||
### Code example
|
||||
|
||||
Below you can find a simple code example for extracting thumbnail files from a .package file (The Sims 4).
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include "dbpf_reader.h" /* for dbpf_reader library interface */
|
||||
#include "zlib.h" /* for decompression of files */
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
/* Structure containing single dbpf archive information. */
|
||||
dbpf_archive dbpf;
|
||||
|
||||
/* Read the archive from example.package file. A buffer will be dynamically
|
||||
allocated for the archive content. */
|
||||
dbpf_ret retVal = dbpf_init(&dbpf, "example.package");
|
||||
|
||||
if (DBPF_E_OK != retVal)
|
||||
{
|
||||
/* Write error information to stdout. No resources were dynamically
|
||||
allocated. */
|
||||
dbpf_print_ret(retVal);
|
||||
return 1;
|
||||
}
|
||||
/* Pointer to a singe index entry. Notice the 2_x prefix indicating that this type
|
||||
is only applicable for DBPF version 2.x. Index entries have various layouts
|
||||
depending on DBPF version. */
|
||||
dbpf_2_x_index_entry *entry;
|
||||
|
||||
char thumbnailFileName[256];
|
||||
|
||||
for (uint32_t entryNum = 0u; entryNum < dbpf.header->indexEntryCount; entryNum++)
|
||||
{
|
||||
retVal = dbpf_2_x_get_index_entry(&dbpf, &entry, entryNum);
|
||||
if (DBPF_E_OK != retVal)
|
||||
{
|
||||
/* Write error information to stdout */
|
||||
dbpf_print_ret(retVal);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DBPF_INDEX_ENTRY_TYPE_THUMB != entry->type)
|
||||
{
|
||||
/* Skip this entry as we are only intereseted in extracting
|
||||
thumbnails. */
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t ucompSize = entry->memSize;
|
||||
uint32_t compSize = entry->fileSize & (~0x80000000);
|
||||
|
||||
/* Allocate buffer for decompressed thumbnail file */
|
||||
uint8_t *resourceBuffer = malloc(sizeof(uint8_t) * ucompSize);
|
||||
|
||||
if (!resourceBuffer)
|
||||
{
|
||||
printf("Failed to allocate memory\n");
|
||||
dbpf_free(&dbpf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Decompress the thumbnail file using zlib uncompress */
|
||||
int res = uncompress((Bytef *)resourceBuffer,
|
||||
&ucompSize,
|
||||
(Bytef *)(dbpf.data + entry->offset),
|
||||
compSize);
|
||||
if (Z_OK != res) {
|
||||
printf("Decompression for entry %lu failed\n", entryNum);
|
||||
free(resourceBuffer);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Use high DWORD of instance ID in hex as filename */
|
||||
sprintf(thumbnailFileName, "0x%08x.jpg", entry->instanceHigh);
|
||||
FILE *fp = fopen(thumbnailFileName, "wb");
|
||||
if (fp)
|
||||
{
|
||||
(void) fwrite(resourceBuffer, 1u, ucompSize, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
free(resourceBuffer);
|
||||
}
|
||||
/* Free allocated resources */
|
||||
dbpf_free(&dbpf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
218
library/formats/dbpf/dbpf_reader.c
Normal file
218
library/formats/dbpf/dbpf_reader.c
Normal file
|
@ -0,0 +1,218 @@
|
|||
/* ===== include files =============================== */
|
||||
|
||||
#include "dbpf_reader.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* ===== internal constants and macros =============== */
|
||||
|
||||
const char *const dbpf_return_value_strings[DBPF_RET_VAL_NUMBER] =
|
||||
{
|
||||
"Success",
|
||||
"General failure",
|
||||
"File input/outpu operation failed",
|
||||
"Invalid file format",
|
||||
"Invalid archive or index version",
|
||||
"Argument out of range"
|
||||
};
|
||||
|
||||
/* ===== internal functions declaration ============== */
|
||||
|
||||
static void dbpf_reset(dbpf_archive *dbpf);
|
||||
|
||||
/* ===== external functions definition =============== */
|
||||
|
||||
dbpf_ret dbpf_init(dbpf_archive *dbpf, const char * path)
|
||||
{
|
||||
dbpf_ret retVal = DBPF_E_NOT_OK;
|
||||
|
||||
if (DBPF_NULL != dbpf && DBPF_NULL != path)
|
||||
{
|
||||
FILE *fp = DBPF_NULL;
|
||||
|
||||
dbpf->path = path;
|
||||
fp = fopen(dbpf->path, "rb");
|
||||
|
||||
if (DBPF_NULL != fp) {
|
||||
/* get file size*/
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
dbpf->dataSize = ftell(fp);
|
||||
rewind(fp);
|
||||
|
||||
dbpf->data = malloc(sizeof(uint8_t) * dbpf->dataSize);
|
||||
|
||||
if (DBPF_NULL != dbpf->data) {
|
||||
uint32_t readDataSize = fread(dbpf->data, sizeof(uint8_t), dbpf->dataSize, fp);
|
||||
if (readDataSize == dbpf->dataSize) {
|
||||
if (sizeof(dbpf_header) <= dbpf->dataSize &&
|
||||
'D' == dbpf->data[0] &&
|
||||
'B' == dbpf->data[1] &&
|
||||
'P' == dbpf->data[2] &&
|
||||
'F' == dbpf->data[3]
|
||||
)
|
||||
{
|
||||
dbpf->header = (dbpf_header *)dbpf->data;
|
||||
retVal = DBPF_E_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = DBPF_E_FILE_INVALID_FORMAT;
|
||||
free(dbpf->data);
|
||||
dbpf_reset(dbpf);
|
||||
}
|
||||
}
|
||||
else {
|
||||
free(dbpf->data);
|
||||
dbpf_reset(dbpf);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
else {
|
||||
retVal = DBPF_E_FILE_IO_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void dbpf_free(dbpf_archive *dbpf)
|
||||
{
|
||||
if (DBPF_NULL != dbpf)
|
||||
{
|
||||
if (DBPF_NULL != dbpf->data)
|
||||
{
|
||||
free(dbpf->data);
|
||||
}
|
||||
dbpf_reset(dbpf);
|
||||
}
|
||||
}
|
||||
|
||||
dbpf_bool dbpf_is_initialized(dbpf_archive *dbpf)
|
||||
{
|
||||
return (
|
||||
DBPF_NULL != dbpf &&
|
||||
DBPF_NULL != dbpf->data &&
|
||||
0 < dbpf->dataSize &&
|
||||
DBPF_NULL != dbpf->header &&
|
||||
DBPF_NULL != dbpf->path
|
||||
);
|
||||
}
|
||||
|
||||
dbpf_ret dbpf_print_info(dbpf_archive *dbpf) {
|
||||
dbpf_ret retVal = DBPF_E_NOT_OK;
|
||||
|
||||
if (dbpf_is_initialized(dbpf))
|
||||
{
|
||||
retVal = DBPF_E_OK;
|
||||
|
||||
printf("File:\t\t\t\t%s\n", dbpf->path);
|
||||
printf("DBPF version:\t\t\t%d.%d\n", dbpf->header->majorVersion, dbpf->header->minorVersion);
|
||||
printf("Size:\t\t\t\t%d\n", dbpf->dataSize);
|
||||
printf("Index version:\t\t\t%d.%d\n", dbpf->header->indexMajorVersion, dbpf->header->indexMinorVersion);
|
||||
printf("Index entry count:\t\t%d\n", dbpf->header->indexEntryCount);
|
||||
printf("Index size:\t\t\t%d\n", dbpf->header->indexSize);
|
||||
printf("Index entry size:\t\t%d\n", dbpf->header->indexSize / dbpf->header->indexEntryCount);
|
||||
printf("Index offset:\t\t\t%d\n", dbpf->header->indexOffset);
|
||||
printf("Index first entry offset:\t%d\n", dbpf->header->indexFirstEntryOffset);
|
||||
printf("Index type:\t\t\t%d\n", *((uint32_t*)(dbpf->data + dbpf->header->indexOffset)));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void dbpf_print_ret(dbpf_ret retVal)
|
||||
{
|
||||
printf("DBPF status: ");
|
||||
if (DBPF_RET_VAL_NUMBER > retVal)
|
||||
{
|
||||
printf("%s", dbpf_return_value_strings[retVal]);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Invalid return value");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
/* dbpf_2_x functions */
|
||||
|
||||
dbpf_bool dbpf_2_x_is_version_valid(dbpf_archive *dbpf)
|
||||
{
|
||||
dbpf_bool isVersionValid = DBPF_FLASE;
|
||||
|
||||
if (dbpf_is_initialized(dbpf))
|
||||
{
|
||||
isVersionValid = (
|
||||
DBPF_2_X_VERSION_MAJOR == dbpf->header->majorVersion &&
|
||||
DBPF_2_X_VERSION_MINOR == dbpf->header->minorVersion &&
|
||||
DBPF_2_X_INDEX_VERSION_MAJOR == dbpf->header->indexMajorVersion &&
|
||||
DBPF_2_X_INDEX_VERSION_MINOR == dbpf->header->indexMinorVersion
|
||||
);
|
||||
}
|
||||
|
||||
return isVersionValid;
|
||||
}
|
||||
|
||||
dbpf_ret dbpf_2_x_get_index_entry(dbpf_archive *dbpf, dbpf_2_x_index_entry **entry, uint32_t entryNumber)
|
||||
{
|
||||
dbpf_ret retVal = DBPF_E_NOT_OK;
|
||||
|
||||
if (dbpf_is_initialized(dbpf) && DBPF_NULL != entry) {
|
||||
if (dbpf_2_x_is_version_valid(dbpf))
|
||||
{
|
||||
if (entryNumber < dbpf->header->indexEntryCount)
|
||||
{
|
||||
retVal = DBPF_E_OK;
|
||||
|
||||
/* entry address := data address + index offset + size of index type + size of index entry * requested entry number */
|
||||
*entry = (dbpf_2_x_index_entry *)(dbpf->data + dbpf->header->indexOffset + sizeof(uint32_t) + sizeof(dbpf_2_x_index_entry) * entryNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = DBPF_E_OUT_OF_RANGE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = DBPF_E_INVALID_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
dbpf_ret dbpf_2_x_print_index_entry(dbpf_2_x_index_entry *entry)
|
||||
{
|
||||
dbpf_ret retVal = DBPF_E_NOT_OK;
|
||||
|
||||
if (DBPF_NULL != entry) {
|
||||
retVal = DBPF_E_OK;
|
||||
|
||||
printf("Type:\t\t0x%08x\n", entry->type);
|
||||
printf("Group:\t\t0x%08x\n", entry->group);
|
||||
printf("Instance high:\t0x%08x\n", entry->instanceHigh);
|
||||
printf("Instance low:\t0x%08x\n", entry->instanceLow);
|
||||
printf("Offset:\t\t0x%08x\n", entry->offset);
|
||||
printf("File size:\t0x%08x\n", entry->fileSize);
|
||||
printf("Mem size:\t0x%08x\n", entry->memSize);
|
||||
printf("Compressed:\t0x%04x\n", entry->compressed);
|
||||
printf("Unknown:\t0x%04x\n", entry->unknown);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/* ===== internal functions definition =============== */
|
||||
|
||||
static void dbpf_reset(dbpf_archive *dbpf)
|
||||
{
|
||||
if (DBPF_NULL != dbpf) {
|
||||
dbpf->data = DBPF_NULL;
|
||||
dbpf->dataSize = 0;
|
||||
dbpf->header = DBPF_NULL;
|
||||
dbpf->path = DBPF_NULL;
|
||||
}
|
||||
}
|
||||
|
108
library/formats/dbpf/dbpf_reader.h
Normal file
108
library/formats/dbpf/dbpf_reader.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
|
||||
/* ===== include files =============================== */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* ===== external constants and macros =============== */
|
||||
|
||||
#ifdef DBPFREADER_EXPORTS
|
||||
#define DBPFREADER_API __declspec(dllexport)
|
||||
#else
|
||||
#define DBPFREADER_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
/* Return values */
|
||||
#define DBPF_E_OK 0u
|
||||
#define DBPF_E_NOT_OK 1u
|
||||
#define DBPF_E_FILE_IO_FAILED 2u
|
||||
#define DBPF_E_FILE_INVALID_FORMAT 3u
|
||||
#define DBPF_E_INVALID_VERSION 4u
|
||||
#define DBPF_E_OUT_OF_RANGE 5u
|
||||
#define DBPF_RET_VAL_NUMBER 6u
|
||||
|
||||
/* dbpf_bool values */
|
||||
#define DBPF_FLASE 0u
|
||||
#define DBPF_TRUE 1u
|
||||
|
||||
/* NULL pointer */
|
||||
#define DBPF_NULL ((void *)0)
|
||||
|
||||
/* Index entries types */
|
||||
#define DBPF_INDEX_ENTRY_TYPE_THUMB 0x3c1af1f2u
|
||||
|
||||
/* DBPF_2_X versions */
|
||||
#define DBPF_2_X_VERSION_MAJOR 2u
|
||||
#define DBPF_2_X_VERSION_MINOR 1u
|
||||
#define DBPF_2_X_INDEX_VERSION_MAJOR 0u
|
||||
#define DBPF_2_X_INDEX_VERSION_MINOR 3u
|
||||
|
||||
/* ===== external types ============================== */
|
||||
|
||||
typedef uint8_t dbpf_ret;
|
||||
typedef uint8_t dbpf_bool;
|
||||
|
||||
#pragma pack(1)
|
||||
typedef struct {
|
||||
uint8_t magic[4]; /* "DBPF" */
|
||||
uint32_t majorVersion;
|
||||
uint32_t minorVersion;
|
||||
uint32_t unknown1; /* unused */
|
||||
uint32_t unknown2; /* unused */
|
||||
uint32_t unknown3; /* should be zero in DBPF 2.0 */
|
||||
uint32_t dateCreated; /* Unix time stamp */
|
||||
uint32_t dateModified; /* Unix time stamp */
|
||||
uint32_t indexMajorVersion;
|
||||
uint32_t indexEntryCount;
|
||||
uint32_t indexFirstEntryOffset;
|
||||
uint32_t indexSize;
|
||||
uint32_t holeEntryCount;
|
||||
uint32_t holeOffset;
|
||||
uint32_t holeSize;
|
||||
uint32_t indexMinorVersion;
|
||||
uint32_t indexOffset;
|
||||
uint32_t unknown4;
|
||||
uint8_t reserver[24];
|
||||
} dbpf_header;
|
||||
|
||||
typedef struct {
|
||||
uint32_t type;
|
||||
uint32_t group;
|
||||
uint32_t instanceHigh;
|
||||
uint32_t instanceLow;
|
||||
uint32_t offset;
|
||||
uint32_t fileSize; /* size of compressed data */
|
||||
uint32_t memSize; /* size of uncompressed data */
|
||||
uint16_t compressed;
|
||||
uint16_t unknown;
|
||||
} dbpf_2_x_index_entry;
|
||||
#pragma pack(8) /* back to default */
|
||||
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
dbpf_header *header;
|
||||
const char *path;
|
||||
int32_t dataSize;
|
||||
} dbpf_archive;
|
||||
|
||||
/* ===== external functions declaration ============== */
|
||||
|
||||
DBPFREADER_API dbpf_ret dbpf_init(dbpf_archive *dbpf, const char *path);
|
||||
|
||||
DBPFREADER_API void dbpf_free(dbpf_archive *dbpf);
|
||||
|
||||
DBPFREADER_API dbpf_bool dbpf_is_initialized(dbpf_archive *dbpf);
|
||||
|
||||
DBPFREADER_API dbpf_ret dbpf_print_info(dbpf_archive *dbpf);
|
||||
|
||||
DBPFREADER_API void dbpf_print_ret(dbpf_ret retVal);
|
||||
|
||||
/* dbpf_2_x functions*/
|
||||
|
||||
DBPFREADER_API dbpf_bool dbpf_2_x_is_version_valid(dbpf_archive *dbpf);
|
||||
|
||||
DBPFREADER_API dbpf_ret dbpf_2_x_get_index_entry(dbpf_archive *dbpf,
|
||||
dbpf_2_x_index_entry **entry,
|
||||
uint32_t entryNumber);
|
||||
|
||||
DBPFREADER_API dbpf_ret dbpf_2_x_print_index_entry(dbpf_2_x_index_entry *entry);
|
Loading…
Add table
Add a link
Reference in a new issue