mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4a929f620 | ||
|
|
f5ba58f27a | ||
|
|
9bd179167c | ||
|
|
9e88b16d71 | ||
|
|
15a4f4275b | ||
|
|
466f12b22e | ||
|
|
26622f3596 | ||
|
|
629d1f2b7b | ||
|
|
1453a3e294 | ||
|
|
b5c0fbdf86 | ||
|
|
948558c64c | ||
|
|
6412d14a28 | ||
|
|
6835d10590 | ||
|
|
20ad8d8778 | ||
|
|
6782a21185 | ||
|
|
d12dbca951 | ||
|
|
823858f275 | ||
|
|
b93591f5d7 | ||
|
|
a91e40e3cc | ||
|
|
7da7d75f30 | ||
|
|
72b4680ff5 | ||
|
|
99a762b649 | ||
|
|
1afe925cbf | ||
|
|
95c07dba9f | ||
|
|
0fc042ef84 | ||
|
|
29df63dd99 | ||
|
|
6f2693edf7 | ||
|
|
e51004a3d1 | ||
|
|
9aa2ae4bbc | ||
|
|
968534314d | ||
|
|
a38f3436f8 | ||
|
|
15c22ccdfc | ||
|
|
f263075abf | ||
|
|
c95f91c2ff | ||
|
|
db86e53af2 | ||
|
|
349af8fbb8 |
@@ -614,6 +614,10 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
|
||||
target_sources(mapper-ce PUBLIC
|
||||
${FALLOUT_ENGINE_SOURCES}
|
||||
${FALLOUT_PLATFORM_SOURCES}
|
||||
"src/proto_txt.cc"
|
||||
"src/proto_txt.h"
|
||||
"src/mapper/map_edge_setup.cc"
|
||||
"src/mapper/map_edge_setup.h"
|
||||
"src/mapper/map_func.cc"
|
||||
"src/mapper/map_func.h"
|
||||
"src/mapper/mapper.cc"
|
||||
@@ -626,6 +630,8 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
|
||||
"src/mapper/mp_scrpt.h"
|
||||
"src/mapper/mp_targt.cc"
|
||||
"src/mapper/mp_targt.h"
|
||||
"src/mapper/mp_utils.cc"
|
||||
"src/mapper/mp_utils.h"
|
||||
"src/mapper/mp_text.cc"
|
||||
"src/mapper/mp_text.h"
|
||||
)
|
||||
|
||||
+13
-5
@@ -76,7 +76,7 @@ For example:
|
||||
|
||||
## `ce-frm2png` CLI Tool
|
||||
|
||||
There is also a small FRM conversion tool in this repo for exporting Fallout `.frm` art to `.png`.
|
||||
There is also a small FRM conversion tool in this repo for converting Fallout art between `.frm` and `.png`.
|
||||
|
||||
From the repo root, build it with your normal CMake workflow, targeting `ce-frm2png`:
|
||||
|
||||
@@ -89,18 +89,26 @@ Basic usage:
|
||||
1. `./<BUILD_DIR>/ce-frm2png <input.frm> [output.png]`
|
||||
2. `./<BUILD_DIR>/ce-frm2png <input.frm> [output.png] --palette <path-to-color.pal>`
|
||||
3. `./<BUILD_DIR>/ce-frm2png <input.frm> [output.png] --frame <index> --direction <index>`
|
||||
4. `./<BUILD_DIR>/ce-frm2png - [output.png|-] --palette <path-to-color.pal>`
|
||||
4. `./<BUILD_DIR>/ce-frm2png <input.png> [output.frm] --palette <path-to-color.pal>`
|
||||
5. `./<BUILD_DIR>/ce-frm2png <input.png> [output.frm] --fps 10 --action-frame 0 --x-offset 0 --y-offset 0`
|
||||
6. `./<BUILD_DIR>/ce-frm2png <input.png> <output.png> --palette <path-to-color.pal>`
|
||||
7. `./<BUILD_DIR>/ce-frm2png - [output.png|-] --from-frm --palette <path-to-color.pal>`
|
||||
8. `./<BUILD_DIR>/ce-frm2png - [output.frm|-] --from-png --palette <path-to-color.pal>`
|
||||
|
||||
`-` can be used to take input from stdin (e.g. from `ce-dat-tool cat` or write to stdout).
|
||||
`-` can be used to take input from stdin (e.g. from `ce-dat-tool cat`) or write to stdout. When reading from stdin, pass `--from-frm` or `--from-png`. Output defaults to PNG for FRM input and FRM for PNG input when output is omitted or `-`; use `--to-png` for PNG output to stdout from PNG input.
|
||||
|
||||
If `--palette` isn't specified, it will pick up `color.pal` in the .frm's directory or cwd.
|
||||
Output file extensions are validated against the selected conversion. For example, PNG-to-FRM output must use `.frm`, and PNG output must use `.png`.
|
||||
|
||||
If `--palette` isn't specified, it will pick up `color.pal` in the input file's directory or cwd.
|
||||
|
||||
Notes:
|
||||
|
||||
- The tool expects a raw `color.pal` file. It does not read palettes directly from `master.dat`, so extract `color.pal` first if needed.
|
||||
- A typical extraction flow is: `ce-dat-tool master.dat extract --lower . 'color.pal'`
|
||||
- By default palette index `0` becomes transparent in the output PNG. Pass `--opaque` to keep it opaque.
|
||||
- The current implementation writes RGBA PNGs. It does not preserve the original Fallout palette indices as a palette-indexed PNG.
|
||||
- PNG output is always palette-indexed and preserves Fallout palette indices when possible.
|
||||
- For `png -> frm`, transparent pixels (alpha below 128) map to palette index `0` by default. Pass `--opaque` to quantize every pixel instead.
|
||||
- The current `png -> frm` implementation writes a single-frame FRM and points all six direction slots at the same frame payload.
|
||||
|
||||
## Updating SDL
|
||||
|
||||
|
||||
@@ -153,6 +153,7 @@ In time this stuff will receive in-game interface, right now you have to do it m
|
||||
* Auto open doors
|
||||
* 44.1 kHz stereo sound/music supported, in .ogg and .wav format as well as legacy .acm
|
||||
* Last used save slot is remembered
|
||||
* Item/Corpse/Container/Critter highlighting (configure using [mods/sfall-mods.ini](https://github.com/sfall-team/sfall/blob/master/artifacts/config_files/sfall-mods.ini))
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -162,6 +163,14 @@ Integrating Sfall goodies is the top priority. Quality of life updates are OK to
|
||||
|
||||
For current sfall compatibility status and the remaining work needed to close gaps, see [SFALL_COMPATIBILITY.md](SFALL_COMPATIBILITY.md).
|
||||
|
||||
## For modders
|
||||
|
||||
* Robust (though not 100%) Sfall opcode and hook support
|
||||
* .ogg/.wav support for map music
|
||||
* .png support for static assets (must be 8bit indexed)
|
||||
* .zip support for .dat archives
|
||||
* Working BIS mapper
|
||||
|
||||
## License
|
||||
|
||||
The source code is this repository is available under the [Sustainable Use License](LICENSE.md).
|
||||
|
||||
@@ -73,7 +73,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
|
||||
| Arrays / Array functions | create_array<br>temp_array<br>fix_array<br>get/set_array<br>resize_array<br>free_array<br>scan_array<br>len_array<br>save/load_array<br>array_key<br>arrayexpr | âś… | - |
|
||||
| Perks and traits / NPC perks | set_fake_perk_npc<br>set_fake_trait_npc<br>set_selectable_perk_npc<br>has_fake_perk_npc<br>has_fake_trait_npc | not implemented | - |
|
||||
| Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | âś… except available_global_script_types | - |
|
||||
| Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | implemented: get_attack_type, get/set_bodypart_hit_modifier, combat_data, get/set_critter_current_ap, get/set_combat_free_move | - |
|
||||
| Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | âś… except block_combat, force_aimed_shots, disable_aimed_shots, get_last_target, get_last_attacker, set_spray_settings | - |
|
||||
| Car | set_car_current_town<br>car_gas_amount<br>set_car_intface_art | implemented: all except set_car_intface_art | - |
|
||||
| Interface / Windows and images | art_frame_data<br>interface_art_draw<br>interface_print<br>draw_image<br>draw_image_scaled<br>get_window_under_mouse<br>create_win<br>get_window_attribute<br>message_box<br>set_window_flag<br>win_fill_color<br>interface_overlay<br>dialog_message<br>get_text_width<br>hide_window<br>show_window<br>create_message_window | implemented: only message_box, get_text_width, show_window, create_message_window | - |
|
||||
| Interface / Outline | outlined_object<br>get_outline<br>set_outline | âś… | - |
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
+12
-2
@@ -119,15 +119,25 @@ numbers_in_dialogue=0
|
||||
;Set to 2 to also skip the splash screen
|
||||
skip_opening_movies=0
|
||||
splash_screen_size=1
|
||||
;Set to 1 to scale movies to fit the screen while preserving aspect ratio.
|
||||
;Set to 0 to keep movies at their native size, centered on screen.
|
||||
movie_aspect_fit=1
|
||||
;Maximum number of columns shown in the main inventory and loot/steal windows (valid range: 1..2)
|
||||
;2-column Loot/steal window requires minimum screen width of 673px
|
||||
inventory_columns=2
|
||||
|
||||
;Set to 0 to keep the main menu art and controls at native size.
|
||||
;Set to 1 to scale only the main menu background image to fit the screen while maintaining aspect ratio.
|
||||
;Set to 2 to also scale the main menu button panel, red buttons, and button labels with the menu art.
|
||||
main_menu_scale_mode=1
|
||||
|
||||
|
||||
[qol]
|
||||
; Automatically open doors that are unlocked and has no script attached.
|
||||
auto_open_doors=0
|
||||
; Overrides distance at which walk animation is used when using an object.
|
||||
use_walk_distance=5
|
||||
; Overrides distance at which walk animation is used when using an object. 0 disables walking
|
||||
; This is the number of empty tiles between the player and the target
|
||||
use_walk_distance=3
|
||||
; Allow opening party member inventory from the long-press world menu.
|
||||
party_trade_from_menu=1
|
||||
; Allow switching to companions' inventories in loot screens (and barter, in the future)
|
||||
|
||||
@@ -60,12 +60,14 @@ procedure start begin
|
||||
|
||||
display_msg("Testing combat/inventory/object helpers...");
|
||||
|
||||
call assertEquals("metarule attack_is_aimed", metarule_exist("attack_is_aimed"), 1);
|
||||
call assertEquals("metarule get_combat_free_move", metarule_exist("get_combat_free_move"), 1);
|
||||
call assertEquals("metarule set_combat_free_move", metarule_exist("set_combat_free_move"), 1);
|
||||
call assertEquals("metarule item_weight", metarule_exist("item_weight"), 1);
|
||||
call assertEquals("metarule obj_is_openable", metarule_exist("obj_is_openable"), 1);
|
||||
call assertEquals("opcode get_critter_current_ap", opcode_exists(OP_GET_CRITTER_CURRENT_AP), 1);
|
||||
call assertEquals("opcode set_critter_current_ap", opcode_exists(OP_SET_CRITTER_CURRENT_AP), 1);
|
||||
call assertEquals("attack_is_aimed defaults to false", attack_is_aimed, 0);
|
||||
|
||||
testItem := create_object_sid(TEST_ITEM_PID, 0, 0, -1);
|
||||
call assertEquals("item_weight matches proto weight for test item", item_weight(testItem), obj_weight(testItem));
|
||||
|
||||
@@ -1043,6 +1043,8 @@ int _action_climb_ladder(Object* critter, Object* ladder)
|
||||
animationRequestOptions |= ANIMATION_REQUEST_NO_STAND;
|
||||
reg_anim_begin(animationRequestOptions);
|
||||
|
||||
// note: distance is checked to ladder tile, but movement is to SE tile, so the walk/run choice
|
||||
// could be slightly off
|
||||
int tile = tileGetTileInDirection(ladder->tile, ROTATION_SE, 1);
|
||||
if (actionPoints != -1 || objectWithinWalkDistance(critter, ladder)) {
|
||||
animationRegisterMoveToTile(critter, tile, ladder->elevation, actionPoints, 0);
|
||||
|
||||
+196
-9
@@ -1,6 +1,9 @@
|
||||
#include "art.h"
|
||||
|
||||
#include <lodepng.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -134,6 +137,7 @@ static int* gArtCritterFidShoudRunData;
|
||||
|
||||
static std::unordered_map<std::string, std::shared_ptr<NamedCacheEntry>> gNamedArtCache;
|
||||
constexpr int kNamedCacheMaxBytes = 32 * 1024 * 1024; // 32MB soft limit
|
||||
constexpr size_t kMaxNamedPngPixels = 16 * 1024 * 1024;
|
||||
static unsigned int gNamedArtCacheMruCounter = 0;
|
||||
static int gNamedArtCacheCurrentBytes = 0;
|
||||
|
||||
@@ -363,6 +367,12 @@ void artToggleObjectTypeHidden(int objectType)
|
||||
}
|
||||
}
|
||||
|
||||
// art_total
|
||||
int art_total(int objectType)
|
||||
{
|
||||
return objectType >= 0 && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].fileNamesLength : 0;
|
||||
}
|
||||
|
||||
// 0x418F7C
|
||||
int artGetFidgetCount(int headFid)
|
||||
{
|
||||
@@ -1116,7 +1126,7 @@ static int artReadFrameData(unsigned char* data, File* stream, int count, int* p
|
||||
// 0x419E1C
|
||||
static int artReadHeader(Art* art, File* stream)
|
||||
{
|
||||
if (fileReadInt32(stream, &(art->field_0)) == -1) return -1;
|
||||
if (fileReadInt32(stream, &(art->version)) == -1) return -1;
|
||||
if (fileReadInt16(stream, &(art->framesPerSecond)) == -1) return -1;
|
||||
if (fileReadInt16(stream, &(art->actionFrame)) == -1) return -1;
|
||||
if (fileReadInt16(stream, &(art->frameCount)) == -1) return -1;
|
||||
@@ -1133,13 +1143,166 @@ static int artReadHeader(Art* art, File* stream)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: Original function was slightly different, but never used. Basically
|
||||
// it's a memory allocating variant of `artRead` (which reads data into given
|
||||
// buffer). This function is useful to load custom `frm` files since `Art` now
|
||||
// needs more memory then it's on-disk size (due to memory padding).
|
||||
//
|
||||
// 0x419EC0
|
||||
Art* artLoad(const char* path)
|
||||
static bool artPathHasExtension(const char* path, const char* extension)
|
||||
{
|
||||
size_t pathLength = strlen(path);
|
||||
size_t extensionLength = strlen(extension);
|
||||
if (pathLength < extensionLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return compat_stricmp(path + pathLength - extensionLength, extension) == 0;
|
||||
}
|
||||
|
||||
static bool artReadFile(const char* path, std::vector<unsigned char>& data)
|
||||
{
|
||||
int size = 0;
|
||||
if (dbGetFileSize(path, &size) != 0 || size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data.resize(size);
|
||||
return dbGetFileContents(path, data.data()) == 0;
|
||||
}
|
||||
|
||||
static bool artUnpackIndexedPngPixels(const std::vector<unsigned char>& indexedData, unsigned width, unsigned height, unsigned bitdepth, unsigned char* output)
|
||||
{
|
||||
if (bitdepth != 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
|
||||
if (indexedData.size() < pixelCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(output, indexedData.data(), pixelCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool artValidateIndexedPngHeader(const char* path, unsigned width, unsigned height, const LodePNGColorMode& color)
|
||||
{
|
||||
if (color.colortype != LCT_PALETTE) {
|
||||
debugPrint("ART: PNG is not palette-indexed: %s\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (color.bitdepth != 8) {
|
||||
debugPrint("ART: indexed PNG bit depth must be 8: %s\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0 || width > SHRT_MAX || height > SHRT_MAX) {
|
||||
debugPrint("ART: invalid indexed PNG dimensions for %s: %ux%u\n", path, width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
|
||||
if (pixelCount > kMaxNamedPngPixels || pixelCount > INT_MAX) {
|
||||
debugPrint("ART: indexed PNG is too large: %s\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool artIndexedPngHasSupportedTransparency(const LodePNGColorMode& color)
|
||||
{
|
||||
if (!lodepng_has_palette_alpha(&color)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < color.palettesize; index++) {
|
||||
unsigned char alpha = color.palette[index * 4 + 3];
|
||||
if (index == 0) {
|
||||
// palette index 0 is allowed to be either fully transparent or fully opaque
|
||||
if (alpha != 0 && alpha != 255) {
|
||||
return false;
|
||||
}
|
||||
} else if (alpha != 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Art* artLoadIndexedPng(const char* path)
|
||||
{
|
||||
std::vector<unsigned char> encoded;
|
||||
if (!artReadFile(path, encoded)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
lodepng::State state;
|
||||
state.decoder.color_convert = 0;
|
||||
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
unsigned error = lodepng_inspect(&width, &height, &state, encoded.data(), encoded.size());
|
||||
if (error != 0) {
|
||||
debugPrint("ART: failed to inspect indexed PNG %s: %s\n", path, lodepng_error_text(error));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!artValidateIndexedPngHeader(path, width, height, state.info_png.color)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> indexedData;
|
||||
error = lodepng::decode(indexedData, width, height, state, encoded);
|
||||
if (error != 0) {
|
||||
debugPrint("ART: failed to decode indexed PNG %s: %s\n", path, lodepng_error_text(error));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!artIndexedPngHasSupportedTransparency(state.info_png.color)) {
|
||||
debugPrint("ART: indexed PNG transparency is unsupported, reserve palette index 0 instead: %s\n", path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
|
||||
|
||||
Art header = {};
|
||||
header.version = 4;
|
||||
header.framesPerSecond = 10;
|
||||
header.actionFrame = 0;
|
||||
header.frameCount = 1;
|
||||
header.dataSize = sizeof(ArtFrame) + static_cast<int>(pixelCount);
|
||||
|
||||
int currentPadding = paddingForSize(sizeof(Art));
|
||||
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
|
||||
header.dataOffsets[rotation] = 0;
|
||||
header.padding[rotation] = currentPadding;
|
||||
}
|
||||
|
||||
int dataSize = artGetDataSize(&header);
|
||||
unsigned char* data = reinterpret_cast<unsigned char*>(internal_malloc(dataSize));
|
||||
if (data == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
memset(data, 0, dataSize);
|
||||
Art* art = reinterpret_cast<Art*>(data);
|
||||
*art = header;
|
||||
|
||||
ArtFrame* frame = reinterpret_cast<ArtFrame*>(data + sizeof(Art) + art->padding[0]);
|
||||
frame->width = static_cast<short>(width);
|
||||
frame->height = static_cast<short>(height);
|
||||
frame->size = static_cast<int>(pixelCount);
|
||||
frame->x = 0;
|
||||
frame->y = 0;
|
||||
|
||||
if (!artUnpackIndexedPngPixels(indexedData, width, height, state.info_png.color.bitdepth, reinterpret_cast<unsigned char*>(frame) + sizeof(ArtFrame))) {
|
||||
debugPrint("ART: failed to read indexed PNG pixels: %s\n", path);
|
||||
internal_free(data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return art;
|
||||
}
|
||||
|
||||
static Art* artLoadFrm(const char* path)
|
||||
{
|
||||
File* stream = fileOpen(path, "rb");
|
||||
if (stream == nullptr) {
|
||||
@@ -1167,6 +1330,30 @@ Art* artLoad(const char* path)
|
||||
return reinterpret_cast<Art*>(data);
|
||||
}
|
||||
|
||||
// NOTE: Original function was slightly different, but never used. Basically
|
||||
// it's a memory allocating variant of `artRead` (which reads data into given
|
||||
// buffer). This function is useful to load custom `frm` files since `Art` now
|
||||
// needs more memory then it's on-disk size (due to memory padding).
|
||||
//
|
||||
// 0x419EC0
|
||||
Art* artLoad(const char* path)
|
||||
{
|
||||
if (path == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (artPathHasExtension(path, ".png")) {
|
||||
return artLoadIndexedPng(path);
|
||||
}
|
||||
|
||||
Art* art = artLoadFrm(path);
|
||||
if (art != nullptr) {
|
||||
return art;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Art* artLoadLocalized(const char* path)
|
||||
{
|
||||
const char* localizedPath;
|
||||
@@ -1239,7 +1426,7 @@ int artWriteFrameData(unsigned char* data, File* stream, int count)
|
||||
// 0x41A138
|
||||
int artWriteHeader(Art* art, File* stream)
|
||||
{
|
||||
if (fileWriteInt32(stream, art->field_0) == -1) return -1;
|
||||
if (fileWriteInt32(stream, art->version) == -1) return -1;
|
||||
if (fileWriteInt16(stream, art->framesPerSecond) == -1) return -1;
|
||||
if (fileWriteInt16(stream, art->actionFrame) == -1) return -1;
|
||||
if (fileWriteInt16(stream, art->frameCount) == -1) return -1;
|
||||
|
||||
@@ -69,7 +69,7 @@ typedef enum Background {
|
||||
} Background;
|
||||
|
||||
typedef struct Art {
|
||||
int field_0;
|
||||
int version;
|
||||
short framesPerSecond;
|
||||
short actionFrame;
|
||||
short frameCount;
|
||||
@@ -123,6 +123,7 @@ void artExit();
|
||||
char* artGetObjectTypeName(int objectType);
|
||||
int artIsObjectTypeHidden(int objectType);
|
||||
void artToggleObjectTypeHidden(int objectType);
|
||||
int art_total(int objectType);
|
||||
int artGetFidgetCount(int headFid);
|
||||
void artRender(int fid, unsigned char* dest, int width, int height, int pitch);
|
||||
int art_list_str(int fid, char* name);
|
||||
|
||||
+49
-2
@@ -3,6 +3,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "actions.h"
|
||||
#include "animation.h"
|
||||
@@ -27,7 +28,6 @@
|
||||
#include "proto.h"
|
||||
#include "proto_instance.h"
|
||||
#include "random.h"
|
||||
#include "scripts.h"
|
||||
#include "settings.h"
|
||||
#include "sfall_script_hooks.h"
|
||||
#include "skill.h"
|
||||
@@ -51,6 +51,7 @@ static constexpr int kChemUseAnytimeChance = 75;
|
||||
static constexpr int kChemUseAlwaysChance = 100;
|
||||
|
||||
static constexpr int kRandomDrugPickingArraySize = 3;
|
||||
static std::unordered_set<Object*> burstDisabledCritters;
|
||||
|
||||
typedef struct AiMessageRange {
|
||||
int start;
|
||||
@@ -533,6 +534,7 @@ err:
|
||||
// 0x4279F8
|
||||
void aiReset()
|
||||
{
|
||||
burstDisabledCritters.clear();
|
||||
}
|
||||
|
||||
// 0x4279FC
|
||||
@@ -615,7 +617,14 @@ int aiSave(File* stream)
|
||||
// 0x427BC8
|
||||
static int aiPacketRead(File* stream, AiPacket* ai)
|
||||
{
|
||||
if (fileReadInt32(stream, &(ai->packet_num)) == -1) return -1;
|
||||
int packetNum;
|
||||
if (fileReadInt32(stream, &packetNum) == -1) return -1;
|
||||
|
||||
// Consume the serialized packet number to preserve save compatibility,
|
||||
// but do not overwrite the value in AiPacket to avoid corrupted data on save loads.
|
||||
// This might happen if saves disagree on packet_num and packets are loaded from AI.TXT only on game init.
|
||||
(void)packetNum;
|
||||
|
||||
if (fileReadInt32(stream, &(ai->max_dist)) == -1) return -1;
|
||||
if (fileReadInt32(stream, &(ai->min_to_hit)) == -1) return -1;
|
||||
if (fileReadInt32(stream, &(ai->min_hp)) == -1) return -1;
|
||||
@@ -889,6 +898,37 @@ int aiSetChemUse(Object* critter, int chemUse)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool aiIsBurstDisabled(Object* critter)
|
||||
{
|
||||
if (critter == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return burstDisabledCritters.find(critter) != burstDisabledCritters.end();
|
||||
}
|
||||
|
||||
void aiSetBurstDisabled(Object* critter, bool disable)
|
||||
{
|
||||
if (critter == nullptr || FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER || critter == gDude) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (disable) {
|
||||
burstDisabledCritters.insert(critter);
|
||||
} else {
|
||||
burstDisabledCritters.erase(critter);
|
||||
}
|
||||
}
|
||||
|
||||
void aiRemoveBurstDisabled(Object* critter)
|
||||
{
|
||||
if (critter == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
burstDisabledCritters.erase(critter);
|
||||
}
|
||||
|
||||
// 0x428340
|
||||
int aiGetDisposition(Object* obj)
|
||||
{
|
||||
@@ -2286,6 +2326,13 @@ static int _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defender)
|
||||
return HIT_MODE_RIGHT_WEAPON_PRIMARY;
|
||||
}
|
||||
|
||||
if (aiIsBurstDisabled(attacker)) {
|
||||
int secondaryAnimation = weaponGetAnimationForHitMode(weapon, HIT_MODE_RIGHT_WEAPON_SECONDARY);
|
||||
if (secondaryAnimation == ANIM_FIRE_BURST || secondaryAnimation == ANIM_FIRE_CONTINUOUS) {
|
||||
return HIT_MODE_RIGHT_WEAPON_PRIMARY;
|
||||
}
|
||||
}
|
||||
|
||||
bool useSecondaryMode = false;
|
||||
|
||||
AiPacket* ai = aiGetPacket(attacker);
|
||||
|
||||
@@ -45,6 +45,9 @@ int aiSetBestWeapon(Object* critter, int bestWeapon);
|
||||
int aiSetDistance(Object* critter, int distance);
|
||||
int aiSetAttackWho(Object* critter, int attackWho);
|
||||
int aiSetChemUse(Object* critter, int chemUse);
|
||||
bool aiIsBurstDisabled(Object* critter);
|
||||
void aiSetBurstDisabled(Object* critter, bool disable);
|
||||
void aiRemoveBurstDisabled(Object* critter);
|
||||
int aiGetDisposition(Object* obj);
|
||||
int aiSetDisposition(Object* obj, int a2);
|
||||
int _caiSetupTeamCombat(Object* attackerTeam, Object* defenderTeam);
|
||||
|
||||
@@ -144,6 +144,30 @@ File* fileOpen(const char* filename, const char* mode)
|
||||
return xfileOpen(filename, mode);
|
||||
}
|
||||
|
||||
File* fileOpenDirect(const char* filename, const char* mode)
|
||||
{
|
||||
return xfileOpenDirect(filename, mode);
|
||||
}
|
||||
|
||||
const char* buildDataPath(const char* root, const char* relative)
|
||||
{
|
||||
static char path[COMPAT_MAX_PATH];
|
||||
|
||||
size_t rootLen = strlen(root);
|
||||
bool rootHasSeparator = rootLen > 0 && (root[rootLen - 1] == '\\' || root[rootLen - 1] == '/');
|
||||
snprintf(path, sizeof(path), "%s%s%s", root, rootHasSeparator ? "" : "\\", relative);
|
||||
|
||||
char dir[COMPAT_MAX_PATH];
|
||||
snprintf(dir, sizeof(dir), "%s", path);
|
||||
char* separator = strrchr(dir, '\\');
|
||||
if (separator != nullptr) {
|
||||
*separator = '\0';
|
||||
compat_mkdir_recursive(dir);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// 0x4C5ED0 db_fprintf
|
||||
int filePrintFormatted(File* stream, const char* format, ...)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ int dbGetFileSize(const char* filePath, int* sizePtr);
|
||||
int dbGetFileContents(const char* filePath, void* ptr);
|
||||
int fileClose(File* stream);
|
||||
File* fileOpen(const char* filename, const char* mode);
|
||||
File* fileOpenDirect(const char* filename, const char* mode);
|
||||
int filePrintFormatted(File* stream, const char* format, ...);
|
||||
int fileReadChar(File* stream);
|
||||
char* fileReadString(char* str, size_t size, File* stream);
|
||||
@@ -111,6 +112,10 @@ void fileNameListFree(char*** fileNames, int unused);
|
||||
int fileGetSize(File* stream);
|
||||
void fileSetReadProgressHandler(FileReadProgressHandler* handler, int size);
|
||||
|
||||
// Joins [root] and [relative] into a save path and creates its directory.
|
||||
// Returns a pointer to a static buffer.
|
||||
const char* buildDataPath(const char* root, const char* relative);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* DB_H */
|
||||
|
||||
+301
-19
@@ -38,15 +38,26 @@ namespace fallout {
|
||||
#define DFILE_HAS_COMPRESSED_UNGETC (0x10)
|
||||
|
||||
static int dbaseFindEntryByFilePath(const void* file, const void* entryName);
|
||||
static int dbaseFindEntryByFilePathForSort(const void* a, const void* b);
|
||||
static bool dbaseZipIsEocd(const unsigned char* p);
|
||||
|
||||
enum class ZipSignature : int {
|
||||
NOT_ZIP = 0,
|
||||
ZIP = 1,
|
||||
EMPTY = 2
|
||||
};
|
||||
static ZipSignature dbaseCheckZipSignature(FILE* stream);
|
||||
static bool dfileDecompressInit(z_streamp stream, DBaseFormat format, unsigned char* buffer);
|
||||
static bool dbaseParseZip(DBase* dbase, FILE* stream, int& errorFlags);
|
||||
static DFile* dfileOpenInternal(DBase* dbase, const char* filename, const char* mode, DFile* dfile);
|
||||
static int dfileReadCharInternal(DFile* stream);
|
||||
static bool dfileReadCompressed(DFile* stream, void* ptr, size_t size);
|
||||
static void dfileUngetCompressed(DFile* stream, int ch);
|
||||
|
||||
// Reads .DAT file contents.
|
||||
// Reads .DAT or .ZIP file contents.
|
||||
//
|
||||
// 0x4E4F58 dbase_open
|
||||
DBase* dbaseOpen(const char* filePath)
|
||||
DBase* dbaseOpen(const char* filePath, int* errorFlags)
|
||||
{
|
||||
assert(filePath); // "filename", "dfile.c", 74
|
||||
|
||||
@@ -55,16 +66,46 @@ DBase* dbaseOpen(const char* filePath)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ZipSignature zipStatus = dbaseCheckZipSignature(stream);
|
||||
if (zipStatus == ZipSignature::EMPTY) {
|
||||
if (errorFlags) {
|
||||
*errorFlags = DBASE_ERROR_EMPTY;
|
||||
}
|
||||
fclose(stream);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DBase* dbase = (DBase*)malloc(sizeof(*dbase));
|
||||
if (dbase == nullptr) {
|
||||
fclose(stream);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
memset(dbase, 0, sizeof(*dbase));
|
||||
|
||||
// Get file size, and reposition stream to read footer, which contains two
|
||||
// 32-bits ints.
|
||||
if (zipStatus == ZipSignature::ZIP) {
|
||||
dbase->format = DBaseFormat::ZIP;
|
||||
dbase->dataOffset = 0;
|
||||
dbase->path = compat_strdup(filePath);
|
||||
|
||||
int zipErrorFlags = 0;
|
||||
if (!dbaseParseZip(dbase, stream, zipErrorFlags)
|
||||
|| zipErrorFlags != 0) {
|
||||
if (errorFlags) {
|
||||
*errorFlags = zipErrorFlags;
|
||||
}
|
||||
dbaseClose(dbase);
|
||||
fclose(stream);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
fclose(stream);
|
||||
return dbase;
|
||||
}
|
||||
|
||||
dbase->format = DBaseFormat::DAT;
|
||||
|
||||
// DAT format: parse footer.
|
||||
// Reposition stream to read footer, which contains two 32-bit ints.
|
||||
int fileSize = getFileSize(stream);
|
||||
if (fseek(stream, fileSize - sizeof(int) * 2, SEEK_SET) != 0) {
|
||||
goto err;
|
||||
@@ -569,13 +610,7 @@ int dfileSeek(DFile* stream, long offset, int origin)
|
||||
return 1;
|
||||
}
|
||||
|
||||
stream->decompressionStream->zalloc = Z_NULL;
|
||||
stream->decompressionStream->zfree = Z_NULL;
|
||||
stream->decompressionStream->opaque = Z_NULL;
|
||||
stream->decompressionStream->next_in = stream->decompressionBuffer;
|
||||
stream->decompressionStream->avail_in = 0;
|
||||
|
||||
if (inflateInit(stream->decompressionStream) != Z_OK) {
|
||||
if (!dfileDecompressInit(stream->decompressionStream, stream->dbase->format, stream->decompressionBuffer)) {
|
||||
stream->flags |= DFILE_ERROR;
|
||||
return 1;
|
||||
}
|
||||
@@ -636,6 +671,259 @@ static int dbaseFindEntryByFilePath(const void* file, const void* entryName)
|
||||
return compat_stricmp(filePath, entry->path);
|
||||
}
|
||||
|
||||
// qsort comparison callback for sorting ZIP entries ascending by path.
|
||||
static int dbaseFindEntryByFilePathForSort(const void* a, const void* b)
|
||||
{
|
||||
const auto* ea = static_cast<const DBaseEntry*>(a);
|
||||
const auto* eb = static_cast<const DBaseEntry*>(b);
|
||||
|
||||
return compat_stricmp(ea->path, eb->path);
|
||||
}
|
||||
|
||||
static bool dfileDecompressInit(z_streamp stream, DBaseFormat format, unsigned char* buffer)
|
||||
{
|
||||
stream->zalloc = Z_NULL;
|
||||
stream->zfree = Z_NULL;
|
||||
stream->opaque = Z_NULL;
|
||||
stream->next_in = buffer;
|
||||
stream->avail_in = 0;
|
||||
|
||||
int inflateResult = format == DBaseFormat::ZIP
|
||||
? inflateInit2(stream, -15)
|
||||
: inflateInit(stream);
|
||||
|
||||
return inflateResult == Z_OK;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool dbaseReadValue(FILE* stream, T* value)
|
||||
{
|
||||
return fread(value, sizeof(T), 1, stream) == 1;
|
||||
}
|
||||
|
||||
static ZipSignature dbaseCheckZipSignature(FILE* stream)
|
||||
{
|
||||
unsigned char magic[4];
|
||||
if (fread(magic, 1, 4, stream) != 4 || magic[0] != 0x50 || magic[1] != 0x4B) {
|
||||
return ZipSignature::NOT_ZIP;
|
||||
}
|
||||
if (magic[2] == 0x03 && magic[3] == 0x04) {
|
||||
return ZipSignature::ZIP;
|
||||
}
|
||||
if (magic[2] == 0x05 && magic[3] == 0x06) {
|
||||
return ZipSignature::EMPTY;
|
||||
}
|
||||
return ZipSignature::NOT_ZIP;
|
||||
}
|
||||
|
||||
static bool dbaseZipIsEocd(const unsigned char* p)
|
||||
{
|
||||
return p[0] == 0x50 && p[1] == 0x4b && p[2] == 0x05 && p[3] == 0x06;
|
||||
}
|
||||
|
||||
// Parses a ZIP archive's central directory and builds the DBaseEntry array.
|
||||
static bool dbaseParseZip(DBase* dbase, FILE* stream, int& errorFlags)
|
||||
{
|
||||
constexpr unsigned int kCentralDirSignature = 0x02014b50;
|
||||
constexpr int kMaxEocdSearch = 65557;
|
||||
|
||||
int fileSize = getFileSize(stream);
|
||||
if (fileSize < 22) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try the common case first: no comment, EOCD is the last 22 bytes.
|
||||
unsigned char tail[22];
|
||||
if (fseek(stream, -22, SEEK_END) != 0
|
||||
|| fread(tail, sizeof(tail), 1, stream) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int eocdOffset = fileSize - 22;
|
||||
bool found = dbaseZipIsEocd(tail);
|
||||
|
||||
// ZIP allows a variable-length comment between the EOCD and end of file.
|
||||
// Search backwards through the last 65557 bytes if not at the expected spot.
|
||||
if (!found) {
|
||||
int searchStart = std::max(0, fileSize - kMaxEocdSearch);
|
||||
int searchSize = fileSize - searchStart;
|
||||
|
||||
if (fseek(stream, searchStart, SEEK_SET) != 0) {
|
||||
return false;
|
||||
}
|
||||
auto* buf = static_cast<unsigned char*>(malloc(searchSize));
|
||||
if (!buf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fread(buf, searchSize, 1, stream) != 1) {
|
||||
free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = searchSize - 22; i >= 0; i--) {
|
||||
if (dbaseZipIsEocd(buf + i)) {
|
||||
eocdOffset = searchStart + i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read EOCD: skip signature (4) + disk number (2) + disk with CD (2) + entries on this disk (2).
|
||||
if (fseek(stream, eocdOffset + 10, SEEK_SET) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned short totalEntries;
|
||||
unsigned int centralDirSize, centralDirOffset;
|
||||
if (!dbaseReadValue(stream, &totalEntries)
|
||||
|| !dbaseReadValue(stream, ¢ralDirSize)
|
||||
|| !dbaseReadValue(stream, ¢ralDirOffset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (totalEntries == 0xFFFF || centralDirSize == 0xFFFFFFFF || centralDirOffset == 0xFFFFFFFF) {
|
||||
errorFlags |= DBASE_ERROR_ZIP64;
|
||||
return false;
|
||||
}
|
||||
|
||||
// There is no point loading empty ZIP since DBase is read-only.
|
||||
if (totalEntries == 0) {
|
||||
errorFlags |= DBASE_ERROR_EMPTY;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If any entry is skipped, entries will contain unused elements at the end. But this is acceptable because DBaseEntry is small and both qsort and bsearch use entriesLength.
|
||||
dbase->entries = static_cast<DBaseEntry*>(malloc(sizeof(*dbase->entries) * totalEntries));
|
||||
if (!dbase->entries) {
|
||||
return false;
|
||||
}
|
||||
memset(dbase->entries, 0, sizeof(*dbase->entries) * totalEntries);
|
||||
|
||||
// Walk central directory entries.
|
||||
if (fseek(stream, static_cast<long>(centralDirOffset), SEEK_SET) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dbase->entriesLength = 0;
|
||||
|
||||
for (unsigned short entryIndex = 0; entryIndex < totalEntries; entryIndex++) {
|
||||
unsigned int signature;
|
||||
unsigned short flags, method;
|
||||
int compressedSize, uncompressedSize, localHeaderOffset;
|
||||
unsigned short fileNameLength, extraFieldLength, fileCommentLength, diskNumberStart;
|
||||
if (!dbaseReadValue(stream, &signature)
|
||||
|| signature != kCentralDirSignature
|
||||
// version made by, version needed to extract
|
||||
|| fseek(stream, 4, SEEK_CUR) != 0
|
||||
|| !dbaseReadValue(stream, &flags)
|
||||
|| !dbaseReadValue(stream, &method)
|
||||
// mod time, mod date, crc32
|
||||
|| fseek(stream, 8, SEEK_CUR) != 0
|
||||
|| !dbaseReadValue(stream, &compressedSize)
|
||||
|| !dbaseReadValue(stream, &uncompressedSize)
|
||||
|| !dbaseReadValue(stream, &fileNameLength)
|
||||
|| !dbaseReadValue(stream, &extraFieldLength)
|
||||
|| !dbaseReadValue(stream, &fileCommentLength)
|
||||
|| !dbaseReadValue(stream, &diskNumberStart)
|
||||
// internal attrs, external attrs
|
||||
|| fseek(stream, 6, SEEK_CUR) != 0
|
||||
|| !dbaseReadValue(stream, &localHeaderOffset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flags & 0x01) {
|
||||
errorFlags |= DBASE_ERROR_ENCRYPTED;
|
||||
}
|
||||
if (flags & 0x08) {
|
||||
errorFlags |= DBASE_ERROR_DESCRIPTORS;
|
||||
}
|
||||
if (diskNumberStart != 0) {
|
||||
errorFlags |= DBASE_ERROR_MULTI_DISK;
|
||||
}
|
||||
// Skip files larger than 2GB since our DBaseEntry uses signed int
|
||||
if (compressedSize < 0 || uncompressedSize < 0 || localHeaderOffset < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read and normalize filename.
|
||||
auto fileName = static_cast<char*>(malloc(fileNameLength + 1));
|
||||
if (fileName == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (fread(fileName, fileNameLength, 1, stream) != 1
|
||||
// Skip extra field and file comment.
|
||||
|| fseek(stream, extraFieldLength + fileCommentLength, SEEK_CUR) != 0) {
|
||||
free(fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
fileName[fileNameLength] = '\0';
|
||||
for (unsigned short j = 0; j < fileNameLength; j++) {
|
||||
if (fileName[j] == '/') {
|
||||
fileName[j] = '\\';
|
||||
}
|
||||
}
|
||||
// Skip directory entries (trailing slash).
|
||||
if (fileNameLength > 0 && fileName[fileNameLength - 1] == '\\') {
|
||||
free(fileName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map compression method: 0=stored, 8=deflated.
|
||||
unsigned char compressed;
|
||||
if (method == 0) {
|
||||
compressed = 0;
|
||||
} else if (method == 8) {
|
||||
compressed = 1;
|
||||
} else {
|
||||
errorFlags |= DBASE_ERROR_UNSUPPORTED_METHOD;
|
||||
free(fileName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read local file header to get actual data offset.
|
||||
long savedPos = ftell(stream);
|
||||
unsigned short localFileNameLength;
|
||||
unsigned short localExtraFieldLength;
|
||||
if (fseek(stream, localHeaderOffset + 26, SEEK_SET) != 0
|
||||
|| !dbaseReadValue(stream, &localFileNameLength)
|
||||
|| !dbaseReadValue(stream, &localExtraFieldLength)
|
||||
|| fseek(stream, savedPos, SEEK_SET) != 0) {
|
||||
free(fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute absolute offset to file data (past local file header).
|
||||
int dataOffset = localHeaderOffset + 30 + localFileNameLength + localExtraFieldLength;
|
||||
if (dataOffset < 0) {
|
||||
free(fileName);
|
||||
continue;
|
||||
}
|
||||
|
||||
DBaseEntry* entry = &dbase->entries[dbase->entriesLength];
|
||||
entry->path = fileName;
|
||||
entry->compressed = compressed;
|
||||
entry->uncompressedSize = uncompressedSize;
|
||||
entry->dataSize = compressedSize;
|
||||
entry->dataOffset = dataOffset;
|
||||
|
||||
dbase->entriesLength++;
|
||||
}
|
||||
|
||||
if (dbase->entriesLength > 1) {
|
||||
qsort(dbase->entries, dbase->entriesLength, sizeof(*dbase->entries), dbaseFindEntryByFilePathForSort);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x4E5D9C dfile_fopen_helper
|
||||
static DFile* dfileOpenInternal(DBase* dbase, const char* filePath, const char* mode, DFile* dfile)
|
||||
{
|
||||
@@ -703,13 +991,7 @@ static DFile* dfileOpenInternal(DBase* dbase, const char* filePath, const char*
|
||||
}
|
||||
}
|
||||
|
||||
dfile->decompressionStream->zalloc = Z_NULL;
|
||||
dfile->decompressionStream->zfree = Z_NULL;
|
||||
dfile->decompressionStream->opaque = Z_NULL;
|
||||
dfile->decompressionStream->next_in = dfile->decompressionBuffer;
|
||||
dfile->decompressionStream->avail_in = 0;
|
||||
|
||||
if (inflateInit(dfile->decompressionStream) != Z_OK) {
|
||||
if (!dfileDecompressInit(dfile->decompressionStream, dbase->format, dfile->decompressionBuffer)) {
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
|
||||
+21
-3
@@ -9,18 +9,35 @@
|
||||
|
||||
namespace fallout {
|
||||
|
||||
enum class DBaseFormat : int {
|
||||
DAT = 0,
|
||||
ZIP = 1,
|
||||
};
|
||||
|
||||
enum DBaseErrorFlags : int {
|
||||
DBASE_ERROR_EMPTY = 1 << 1,
|
||||
DBASE_ERROR_DESCRIPTORS = 1 << 2,
|
||||
DBASE_ERROR_ZIP64 = 1 << 3,
|
||||
DBASE_ERROR_ENCRYPTED = 1 << 4,
|
||||
DBASE_ERROR_MULTI_DISK = 1 << 5,
|
||||
DBASE_ERROR_UNSUPPORTED_METHOD = 1 << 6,
|
||||
};
|
||||
|
||||
typedef struct DBase DBase;
|
||||
typedef struct DBaseEntry DBaseEntry;
|
||||
typedef struct DFile DFile;
|
||||
|
||||
// A representation of .DAT file.
|
||||
// A representation of .DAT or .ZIP file.
|
||||
typedef struct DBase {
|
||||
// The path of .DAT file that this structure represents.
|
||||
// The path of archive file that this structure represents.
|
||||
char* path;
|
||||
|
||||
// The offset to the beginning of data section of .DAT file.
|
||||
int dataOffset;
|
||||
|
||||
// The format of this archive.
|
||||
DBaseFormat format;
|
||||
|
||||
// The number of entries.
|
||||
int entriesLength;
|
||||
|
||||
@@ -110,7 +127,8 @@ typedef struct DFileFindData {
|
||||
int index;
|
||||
} DFileFindData;
|
||||
|
||||
DBase* dbaseOpen(const char* filename);
|
||||
// Reads DAT or ZIP file header and table of entries.
|
||||
DBase* dbaseOpen(const char* filename, int* errorFlags = nullptr);
|
||||
bool dbaseClose(DBase* dbase);
|
||||
bool dbaseFindFirstEntry(DBase* dbase, DFileFindData* findFileData, const char* pattern);
|
||||
bool dbaseFindNextEntry(DBase* dbase, DFileFindData* findFileData);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user