-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
32 changed files
with
1,709 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Pbo.json | ||
|
||
You can put the `pbo.json` file into the PBO content root directory when you pack a PBO via the PBO Manager. The PBO Manager reads the PBO headers and the compression rules from the `pbo.json`. | ||
|
||
An example `pbo.json`: | ||
|
||
```json | ||
{ | ||
"headers": [{ | ||
"name": "prefix", | ||
"value": "my-addon" | ||
}, { | ||
"name": "version", | ||
"value": "1.0.0" | ||
}, { | ||
"name": "product", | ||
"value": "my-mod" | ||
}], | ||
"compress": { | ||
"include": ["\.txt$", "\.sqf$", "\.ext"], | ||
"exclude": ["^description.ext$"] | ||
} | ||
} | ||
``` | ||
|
||
## Pbo.json fields | ||
|
||
### headers | ||
|
||
The collection of the headers the PBO file should have. | ||
|
||
Type: `array` | ||
|
||
Required: `no` | ||
|
||
Default: `[]` | ||
|
||
#### headers[].name | ||
|
||
The name of the header. | ||
|
||
Type: `string` | ||
|
||
Required: `yes` | ||
|
||
#### headers[].value | ||
|
||
The value of the header. | ||
|
||
Type: `string` | ||
|
||
Required: `no` | ||
|
||
Default: `""` | ||
|
||
### compress | ||
|
||
The rules of compressing the contents of the PBO file. | ||
|
||
#### compress.include | ||
|
||
Which files to compress. | ||
|
||
Type: `array` of [regular expressions](https://en.wikipedia.org/wiki/Regular_expression). (The regular expressions will run against the **relative** file names with **forward slashes** as separators, e.g. `scripts/script1.sqf`) | ||
|
||
Required: `no` | ||
|
||
#### compress.exclude | ||
|
||
Which of the **included** files to exclude from compression. Use this to _"include all then exclude a couple"_ scenario. | ||
|
||
Type: `array` of [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) | ||
|
||
Required: `no` | ||
|
||
|
||
## Regular expression debugging | ||
|
||
There are lots of the services for regular expression debugging, such as https://regex101.com | ||
|
||
## Regular expression examples | ||
|
||
| What it will match | Example files | RegEx | | ||
| ------------------------------------------------- | ------------------------------------------------------------------ | ---------------------- | | ||
| All the `.sqf` files. | `my-script.sqf` or `Scripts/script.sqf` | `\.sqf$` | | ||
| All the `.sqf` files in the `Scripts` root foler. | `Scripts/Client/Alpha/fn_run.sqf` | `^scripts\/.+\.sqf$` | | ||
| All the `.sqf` files in the `Alpha` subfolders. | `Scripts/Client/Alpha/initClient.sqf` or `Scripts/Server/Alpha/initServer.sqf` | `\/.+\/Alpha\/.+\.sqf$` | | ||
| The `decription.ext` file in the root. | `description.ext` | `^description.ext$` | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Prefix files | ||
|
||
Since the times of Operation Flashpoint, they used so-called [prefix files](https://community.bistudio.com/wiki/PBOPREFIX) to provide the packaging tools the information regarding the headers a packed PBO should have. | ||
|
||
To use a prefix file, you create a text file with the name i.e. `$pboprefix$` (or `pboprefix.txt`) and some text. And when the PBO Manager packs the folder, the produced `PBO` will have the header with the name `prefix` and the text from the file. | ||
|
||
The PBO Manager supports the following prefix files (names are case-insensitive): | ||
|
||
| File name | Alternative file name | Resulting PBO header | | ||
| -------------- | --------------------- | -------------------- | | ||
| \$pboprefix\$ | pboprefix.txt | prefix | | ||
| \$pboproduct\$ | pboproduct.txt | product | | ||
| \$pboversion\$ | pboversion.txt | version | | ||
|
||
However, there is a more efficient way to control file headers: [pbo.json](pbo_json.md). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
#pragma once | ||
|
||
#include "exception.h" | ||
#include "domain/binarysource.h" | ||
#include "io/bs/pbobinarysource.h" | ||
#include "io/bs/fsrawbinarysource.h" | ||
#include "io/bs/fslzhbinarysource.h" | ||
|
||
namespace pboman3::model { | ||
using namespace domain; | ||
using namespace io; | ||
|
||
inline void ChangeBinarySourceCompressionMode(QSharedPointer<BinarySource>& bs, bool compress) { | ||
if (dynamic_cast<PboBinarySource*>(bs.get())) { | ||
throw InvalidOperationException("Can't query compression status"); | ||
} | ||
|
||
if (compress) { | ||
if (dynamic_cast<FsRawBinarySource*>(bs.get())) { | ||
bs = QSharedPointer<BinarySource>(new FsLzhBinarySource(bs->path())); | ||
bs->open(); | ||
} | ||
} else { | ||
if (dynamic_cast<FsLzhBinarySource*>(bs.get())) { | ||
bs = QSharedPointer<BinarySource>(new FsRawBinarySource(bs->path())); | ||
bs->open(); | ||
} | ||
} | ||
} | ||
|
||
inline bool IsCompressed(const QSharedPointer<BinarySource>& bs) { | ||
if (const auto pboBs = dynamic_cast<PboBinarySource*>(bs.get())) { | ||
return pboBs->isCompressed(); | ||
} | ||
throw InvalidOperationException("Can't query compression status"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
#include "model/task/extractconfiguration.h" | ||
|
||
#include <QTemporaryDir> | ||
#include <QTemporaryFile> | ||
#include <gtest/gtest.h> | ||
#include "domain/pbodocument.h" | ||
#include "io/bs/pbobinarysource.h" | ||
|
||
namespace pboman3::model::task { | ||
using namespace domain; | ||
|
||
TEST(ExtractConfigurationTest, Extract_Takes_Headers) { | ||
const QList headers{ | ||
QSharedPointer<DocumentHeader>(new DocumentHeader("n1", "v1")), | ||
QSharedPointer<DocumentHeader>(new DocumentHeader("n2", "v2")), | ||
QSharedPointer<DocumentHeader>(new DocumentHeader("n3", "")), | ||
}; | ||
const PboDocument document("file.pbo", headers, QByteArray(1, 20)); | ||
|
||
const PackOptions options = ExtractConfiguration::extractFrom(document); | ||
|
||
ASSERT_EQ(options.headers.count(), 3); | ||
ASSERT_EQ(options.headers.at(0).name, "n1"); | ||
ASSERT_EQ(options.headers.at(0).value, "v1"); | ||
ASSERT_EQ(options.headers.at(1).name, "n2"); | ||
ASSERT_EQ(options.headers.at(1).value, "v2"); | ||
ASSERT_EQ(options.headers.at(2).name, "n3"); | ||
ASSERT_EQ(options.headers.at(2).value, ""); | ||
} | ||
|
||
struct ExtractConfigurationExtParam { | ||
QString fileWithExt; | ||
}; | ||
|
||
class ExtractConfigurationExtensionTest : public testing::TestWithParam<ExtractConfigurationExtParam> { | ||
}; | ||
|
||
TEST_P(ExtractConfigurationExtensionTest, Extract_Picks_Extension_Compression) { | ||
const PboDocument document("file.pbo"); | ||
document.root()->createHierarchy(PboPath("f1.p3d")); | ||
document.root()->createHierarchy(PboPath("f2.paa")); | ||
document.root()->createHierarchy(PboPath("snd/f3.ogg")); | ||
document.root()->createHierarchy(PboPath(GetParam().fileWithExt)); | ||
|
||
const PackOptions options = ExtractConfiguration::extractFrom(document); | ||
|
||
ASSERT_EQ(options.compress.include.count(), 1); | ||
ASSERT_EQ(options.compress.include.at(0), "\\." + GetFileExtension(GetParam().fileWithExt).toLower() + "$"); | ||
} | ||
|
||
INSTANTIATE_TEST_SUITE_P(ExtractConfigurationTest, ExtractConfigurationExtensionTest, testing::Values( | ||
ExtractConfigurationExtParam{"file1.sqf"}, | ||
ExtractConfigurationExtParam{"folder1/file1.sqf"}, | ||
ExtractConfigurationExtParam{"file1.sqs"}, | ||
ExtractConfigurationExtParam{"file1.txt"}, | ||
ExtractConfigurationExtParam{"file1.Xml"}, | ||
ExtractConfigurationExtParam{"file1.cSv"} | ||
)); | ||
|
||
struct ExtractConfigurationFileParam { | ||
QString fileName; | ||
bool compressed; | ||
}; | ||
|
||
class ExtractConfigurationFileTest : public testing::TestWithParam<ExtractConfigurationFileParam> { | ||
}; | ||
|
||
TEST_P(ExtractConfigurationFileTest, Extract_Picks_File_Compression) { | ||
QTemporaryFile file; | ||
file.open(); | ||
file.close(); | ||
|
||
const PboDocument document("file.pbo"); | ||
PboNode* node = document.root()->createHierarchy(PboPath(GetParam().fileName)); | ||
node->binarySource = QSharedPointer<BinarySource>( | ||
new io::PboBinarySource(file.fileName(), io::PboDataInfo{10, 10, 0, 0, GetParam().compressed})); | ||
|
||
const PackOptions options = ExtractConfiguration::extractFrom(document); | ||
|
||
if (GetParam().compressed) { | ||
ASSERT_EQ(options.compress.include.count(), 1); | ||
ASSERT_EQ(options.compress.include.at(0), "^" + GetParam().fileName.toLower() + "$"); | ||
} else { | ||
ASSERT_EQ(options.compress.include.count(), 0); | ||
} | ||
} | ||
|
||
INSTANTIATE_TEST_SUITE_P(ExtractConfigurationTest, ExtractConfigurationFileTest, testing::Values( | ||
ExtractConfigurationFileParam{"mission.sQm", true}, | ||
ExtractConfigurationFileParam{"mission.sqm", false}, | ||
ExtractConfigurationFileParam{"descriptIon.ext", true}, | ||
ExtractConfigurationFileParam{"description.ext", false} | ||
)); | ||
|
||
TEST(ExtractConfigurationTest, Extract_Picks_Multiple_Compression_Rules) { | ||
QTemporaryFile file; | ||
file.open(); | ||
file.close(); | ||
|
||
const PboDocument document("file.pbo"); | ||
PboNode* node = document.root()->createHierarchy(PboPath("mission.sqm")); | ||
node->binarySource = QSharedPointer<BinarySource>( | ||
new io::PboBinarySource(file.fileName(), io::PboDataInfo{10, 10, 0, 0, true})); | ||
|
||
document.root()->createHierarchy(PboPath("script.sqf")); | ||
|
||
const PackOptions options = ExtractConfiguration::extractFrom(document); | ||
|
||
ASSERT_EQ(options.compress.include.count(), 2); | ||
ASSERT_EQ(options.compress.include.at(0), "\\.sqf$"); | ||
ASSERT_EQ(options.compress.include.at(1), "^mission.sqm$"); | ||
} | ||
|
||
TEST(ExtractConfigurationTest, SaveTo_Writes_To_Directory) { | ||
QTemporaryDir t; | ||
const QDir target(t.path()); | ||
|
||
PackOptions options; | ||
options.headers = QList{PackHeader("h1", "v1"), PackHeader("h2", "v2")}; | ||
options.compress.include = QList<QString>{"i1", "i2"}; | ||
options.compress.exclude = QList<QString>{"e1", "e2"}; | ||
|
||
ExtractConfiguration::saveTo(options, target); | ||
|
||
QFile config(target.filePath("pbo.json")); | ||
ASSERT_TRUE(config.exists()); | ||
|
||
ASSERT_TRUE(config.open(QIODeviceBase::ReadOnly)); | ||
|
||
const QByteArray bytes = config.readAll(); | ||
ASSERT_EQ(QString(bytes), QString("{\n \"compress\": {\n \"exclude\": [\n \"e1\",\n \"e2\"\n ],\n \"include\": [\n \"i1\",\n \"i2\"\n ]\n },\n \"headers\": [\n {\n \"name\": \"h1\",\n \"value\": \"v1\"\n },\n {\n \"name\": \"h2\",\n \"value\": \"v2\"\n }\n ]\n}\n")); | ||
} | ||
|
||
TEST(ExtractConfigurationTest, SaveTo_Picks_Non_Conflict_Name) { | ||
QTemporaryDir t; | ||
const QDir target(t.path()); | ||
|
||
//two placeholder files | ||
QFile f1(target.filePath("pbo.json")); | ||
f1.open(QIODeviceBase::NewOnly); | ||
f1.close(); | ||
QFile f2(target.filePath("pbo-1.json")); | ||
f2.open(QIODeviceBase::NewOnly); | ||
f2.close(); | ||
|
||
const PackOptions options; | ||
ExtractConfiguration::saveTo(options, target); | ||
|
||
//the resulting file prevented conflicts | ||
QFile config(target.filePath("pbo-2.json")); | ||
ASSERT_TRUE(config.exists()); | ||
|
||
ASSERT_TRUE(config.open(QIODeviceBase::ReadOnly)); | ||
|
||
const QByteArray bytes = config.readAll(); | ||
ASSERT_EQ(QString(bytes), QString("{\n \"compress\": {\n \"exclude\": [\n ],\n \"include\": [\n ]\n },\n \"headers\": [\n ]\n}\n")); | ||
} | ||
} |
Oops, something went wrong.