Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
3
 * It is copyright by its individual contributors, as recorded in the
4
 * project's Git history.  See COPYING.txt at the top level for license
5
 * terms and a link to the Git history.
6
 */
7
/*
8
 * Load custom textures & robot data
9
 */
10
 
11
#include <memory>
12
#include <string.h>
13
#include "gr.h"
14
#include "pstypes.h"
15
#include "piggy.h"
16
#include "textures.h"
17
#include "robot.h"
18
#include "weapon.h"
19
#include "digi.h"
20
#include "hash.h"
21
#include "u_mem.h"
22
#include "custom.h"
23
#include "physfsx.h"
24
 
25
#include "compiler-range_for.h"
26
#include "partial_range.h"
27
#include <iterator>
28
#include <memory>
29
 
30
//#define D2TMAP_CONV // used for testing
31
 
32
namespace {
33
 
34
struct snd_info
35
{
36
        unsigned int length;
37
        uint8_t *data;
38
};
39
 
40
}
41
 
42
struct DiskBitmapHeader2
43
{
44
        char name[8];
45
        ubyte dflags;
46
        ubyte width;
47
        ubyte height;
48
        ubyte hi_wh;
49
        ubyte flags;
50
        ubyte avg_color;
51
        int offset;
52
} __pack__;
53
 
54
struct DiskBitmapHeader
55
{
56
        char name[8];
57
        ubyte dflags;
58
        ubyte width;
59
        ubyte height;
60
        ubyte flags;
61
        ubyte avg_color;
62
        int offset;
63
} __pack__;
64
 
65
struct DiskSoundHeader
66
{
67
        char name[8];
68
        int length;
69
        int data_length;
70
        int offset;
71
} __pack__;
72
 
73
namespace {
74
 
75
struct custom_info
76
{
77
        int offset;
78
        int repl_idx; // 0x80000000 -> sound, -1 -> n/a
79
        unsigned int flags;
80
        int width, height;
81
};
82
 
83
}
84
 
85
static std::array<grs_bitmap, MAX_BITMAP_FILES> BitmapOriginal;
86
static std::array<snd_info, MAX_SOUND_FILES> SoundOriginal;
87
 
88
static int load_pig1(PHYSFS_File *f, unsigned num_bitmaps, unsigned num_sounds, unsigned &num_custom, std::unique_ptr<custom_info[]> &ci)
89
{
90
        int data_ofs;
91
        int i;
92
        DiskBitmapHeader bmh;
93
        DiskSoundHeader sndh;
94
        char name[15];
95
 
96
        num_custom = 0;
97
 
98
        if (num_bitmaps <= MAX_BITMAP_FILES) // <v1.4 pig?
99
        {
100
                PHYSFSX_fseek(f, 8, SEEK_SET);
101
                data_ofs = 8;
102
        }
103
        else if (num_bitmaps > 0 && num_bitmaps < PHYSFS_fileLength(f)) // >=v1.4 pig?
104
        {
105
                PHYSFSX_fseek(f, num_bitmaps, SEEK_SET);
106
                data_ofs = num_bitmaps + 8;
107
                num_bitmaps = PHYSFSX_readInt(f);
108
                num_sounds = PHYSFSX_readInt(f);
109
        }
110
        else
111
                return -1; // invalid pig file
112
 
113
        if (num_bitmaps >= MAX_BITMAP_FILES ||
114
                num_sounds >= MAX_SOUND_FILES)
115
                return -1; // invalid pig file
116
        ci = std::make_unique<custom_info[]>(num_bitmaps + num_sounds);
117
        custom_info *cip = ci.get();
118
        data_ofs += num_bitmaps * sizeof(DiskBitmapHeader) + num_sounds * sizeof(DiskSoundHeader);
119
        i = num_bitmaps;
120
 
121
        while (i--)
122
        {
123
                if (PHYSFS_read(f, &bmh, sizeof(DiskBitmapHeader), 1) < 1)
124
                {
125
                        return -1;
126
                }
127
 
128
                snprintf(name, sizeof(name), "%.8s%c%d", bmh.name, (bmh.dflags & DBM_FLAG_ABM) ? '#' : 0, bmh.dflags & 63);
129
 
130
                cip->offset = bmh.offset + data_ofs;
131
                cip->repl_idx = hashtable_search(&AllBitmapsNames, name);
132
                cip->flags = bmh.flags & (BM_FLAG_TRANSPARENT | BM_FLAG_SUPER_TRANSPARENT | BM_FLAG_NO_LIGHTING | BM_FLAG_RLE);
133
                cip->width = bmh.width + ((bmh.dflags & DBM_FLAG_LARGE) ? 256 : 0);
134
                cip->height = bmh.height;
135
                cip++;
136
        }
137
 
138
        i = num_sounds;
139
 
140
        while (i--)
141
        {
142
                if (PHYSFS_read(f, &sndh, sizeof(DiskSoundHeader), 1) < 1)
143
                {
144
                        return -1;
145
                }
146
 
147
                memcpy(name, sndh.name, 8);
148
                name[8] = 0;
149
                cip->offset = sndh.offset + data_ofs;
150
                cip->repl_idx = hashtable_search(&AllDigiSndNames, name) | 0x80000000;
151
                cip->width = sndh.length;
152
                cip++;
153
        }
154
 
155
        num_custom = num_bitmaps + num_sounds;
156
 
157
        return 0;
158
}
159
 
160
static int load_pog(PHYSFS_File *f, int pog_sig, int pog_ver, unsigned &num_custom, std::unique_ptr<custom_info[]> &ci)
161
{
162
        int data_ofs;
163
        int num_bitmaps;
164
        int no_repl = 0;
165
        DiskBitmapHeader2 bmh;
166
 
167
#ifdef D2TMAP_CONV
168
        int x, j, N_d2tmap;
169
        int *d2tmap = NULL;
170
        PHYSFS_File *f2 = NULL;
171
        if ((f2 = PHYSFSX_openReadBuffered("d2tmap.bin")))
172
        {
173
                N_d2tmap = PHYSFSX_readInt(f2);
174
                if ((d2tmap = d_malloc(N_d2tmap * sizeof(d2tmap[0]))))
175
                        for (i = 0; i < N_d2tmap; i++)
176
                                d2tmap[i] = PHYSFSX_readShort(f2);
177
                PHYSFS_close(f2);
178
        }
179
#endif
180
 
181
        num_custom = 0;
182
 
183
        if (pog_sig == 0x47495050 && pog_ver == 2) /* PPIG */
184
                no_repl = 1;
185
        else if (pog_sig != 0x474f5044 || pog_ver != 1) /* DPOG */
186
                return -1; // no pig2/pog file/unknown version
187
 
188
        num_bitmaps = PHYSFSX_readInt(f);
189
        ci = std::make_unique<custom_info[]>(num_bitmaps);
190
        custom_info *cip = ci.get();
191
        data_ofs = 12 + num_bitmaps * sizeof(DiskBitmapHeader2);
192
 
193
        if (!no_repl)
194
        {
195
                for (int i = num_bitmaps; i--;)
196
                        (cip++)->repl_idx = PHYSFSX_readShort(f);
197
 
198
                cip = ci.get();
199
                data_ofs += num_bitmaps * 2;
200
        }
201
 
202
#ifdef D2TMAP_CONV
203
        if (d2tmap)
204
                for (i = 0; i < num_bitmaps; i++)
205
                {
206
                        x = cip[i].repl_idx;
207
                        cip[i].repl_idx = -1;
208
 
209
                        for (j = 0; j < N_d2tmap; j++)
210
                                if (x == d2tmap[j])
211
                                {
212
                                        cip[i].repl_idx = Textures[j % NumTextures].index;
213
                                        break;
214
                                }
215
                }
216
#endif
217
        for (int i = num_bitmaps; i--;)
218
        {
219
                if (PHYSFS_read(f, &bmh, sizeof(DiskBitmapHeader2), 1) < 1)
220
                {
221
                        return -1;
222
                }
223
 
224
                cip->offset = bmh.offset + data_ofs;
225
                cip->flags = bmh.flags & (BM_FLAG_TRANSPARENT | BM_FLAG_SUPER_TRANSPARENT | BM_FLAG_NO_LIGHTING | BM_FLAG_RLE);
226
                cip->width = bmh.width + ((bmh.hi_wh & 15) << 8);
227
                cip->height = bmh.height + ((bmh.hi_wh >> 4) << 8);
228
                cip++;
229
        }
230
 
231
        num_custom = num_bitmaps;
232
 
233
        return 0;
234
}
235
 
236
// load custom textures/sounds from pog/pig file
237
// returns 0 if ok, <0 on error
238
static int load_pigpog(const d_fname &pogname)
239
{
240
        unsigned num_custom;
241
        grs_bitmap *bmp;
242
        digi_sound *snd;
243
        uint8_t *p;
244
        auto f = PHYSFSX_openReadBuffered(pogname);
245
        int i, j, rc = -1;
246
        unsigned int x = 0;
247
 
248
        if (!f)
249
                return -1; // pog file doesn't exist
250
 
251
        i = PHYSFSX_readInt(f);
252
        x = PHYSFSX_readInt(f);
253
 
254
        std::unique_ptr<custom_info[]> ci;
255
        if (load_pog(f, i, x, num_custom, ci) && load_pig1(f, i, x, num_custom, ci))
256
        {
257
                return rc;
258
        }
259
 
260
        custom_info *cip = ci.get();
261
        i = num_custom;
262
 
263
        while (i--)
264
        {
265
                x = cip->repl_idx;
266
                if (cip->repl_idx >= 0)
267
                {
268
                        PHYSFSX_fseek( f, cip->offset, SEEK_SET );
269
 
270
                        if ( cip->flags & BM_FLAG_RLE )
271
                                j = PHYSFSX_readInt(f);
272
                        else
273
                                j = cip->width * cip->height;
274
 
275
                        if (!MALLOC(p, ubyte, j))
276
                        {
277
                                return rc;
278
                        }
279
 
280
                        bmp = &GameBitmaps[x];
281
 
282
                        if (BitmapOriginal[x].get_flag_mask(0x80)) // already customized?
283
                                gr_free_bitmap_data(*bmp);
284
                        else
285
                        {
286
                                // save original bitmap info
287
                                BitmapOriginal[x] = *bmp;
288
                                BitmapOriginal[x].add_flags(0x80);
289
                                if (GameBitmapOffset[x]) // from pig?
290
                                {
291
                                        BitmapOriginal[x].add_flags(BM_FLAG_PAGED_OUT);
292
                                        BitmapOriginal[x].bm_data = reinterpret_cast<uint8_t *>(static_cast<uintptr_t>(GameBitmapOffset[x]));
293
                                }
294
                        }
295
 
296
                        GameBitmapOffset[x] = 0; // not in pig
297
                        *bmp = {};
298
                        gr_init_bitmap(*bmp, bm_mode::linear, 0, 0, cip->width, cip->height, cip->width, p);
299
                        gr_set_bitmap_flags(*bmp, cip->flags & 255);
300
#if !DXX_USE_OGL
301
                        bmp->avg_color = cip->flags >> 8;
302
#endif
303
 
304
                        if ( cip->flags & BM_FLAG_RLE )
305
                        {
306
                                int *ip = reinterpret_cast<int *>(p);
307
                                *ip = j;
308
                                p += 4;
309
                                j -= 4;
310
                        }
311
 
312
                        if (PHYSFS_read(f, p, 1, j) < 1)
313
                        {
314
                                return rc;
315
                        }
316
 
317
                }
318
                else if ((cip->repl_idx + 1) < 0)
319
                {
320
                        PHYSFSX_fseek( f, cip->offset, SEEK_SET );
321
                        snd = &GameSounds[x & 0x7fffffff];
322
 
323
                        j = cip->width;
324
                        if (!MALLOC(p, ubyte, j))
325
                        {
326
                                return rc;
327
                        }
328
 
329
                        if (SoundOriginal[x & 0x7fffffff].length & 0x80000000)  // already customized?
330
                                d_free(snd->data);
331
                        else
332
                        {
333
#ifdef ALLEGRO
334
                                SoundOriginal[x & 0x7fffffff].length = snd->len | 0x80000000;
335
#else
336
                                SoundOriginal[x & 0x7fffffff].length = snd->length | 0x80000000;
337
#endif
338
                                SoundOriginal[x & 0x7fffffff].data = snd->data;
339
                        }
340
 
341
#ifdef ALLEGRO
342
                                snd->loop_end = snd->len = j;
343
#else
344
                                snd->length = j;
345
#endif
346
                        snd->data = p;
347
 
348
                        if (PHYSFS_read(f, p, j, 1) < 1)
349
                        {
350
                                return rc;
351
                        }
352
                }
353
                cip++;
354
        }
355
        rc = 0;
356
        return rc;
357
}
358
 
359
static int read_d2_robot_info(PHYSFS_File *fp, robot_info &ri)
360
{
361
        int j, k;
362
 
363
        ri.model_num = PHYSFSX_readInt(fp);
364
 
365
        for (j = 0; j < MAX_GUNS; j++)
366
                PHYSFSX_readVector(fp, ri.gun_points[j]);
367
        for (j = 0; j < MAX_GUNS; j++)
368
                ri.gun_submodels[j] = PHYSFSX_readByte(fp);
369
        ri.exp1_vclip_num = PHYSFSX_readShort(fp);
370
        ri.exp1_sound_num = PHYSFSX_readShort(fp);
371
        ri.exp2_vclip_num = PHYSFSX_readShort(fp);
372
        ri.exp2_sound_num = PHYSFSX_readShort(fp);
373
        const auto weapon_type = PHYSFSX_readByte(fp);
374
        ri.weapon_type = weapon_type < N_weapon_types ? static_cast<weapon_id_type>(weapon_type) : weapon_id_type::LASER_ID_L1;
375
        /*ri.weapon_type2 =*/ PHYSFSX_readByte(fp);
376
        ri.n_guns = PHYSFSX_readByte(fp);
377
        ri.contains_id = PHYSFSX_readByte(fp);
378
        ri.contains_count = PHYSFSX_readByte(fp);
379
        ri.contains_prob = PHYSFSX_readByte(fp);
380
        ri.contains_type = PHYSFSX_readByte(fp);
381
        /*ri.kamikaze =*/ PHYSFSX_readByte(fp);
382
        ri.score_value = PHYSFSX_readShort(fp);
383
        /*ri.badass =*/ PHYSFSX_readByte(fp);
384
        /*ri.energy_drain =*/ PHYSFSX_readByte(fp);
385
        ri.lighting = PHYSFSX_readFix(fp);
386
        ri.strength = PHYSFSX_readFix(fp);
387
        ri.mass = PHYSFSX_readFix(fp);
388
        ri.drag = PHYSFSX_readFix(fp);
389
        for (j = 0; j < NDL; j++)
390
                ri.field_of_view[j] = PHYSFSX_readFix(fp);
391
        for (j = 0; j < NDL; j++)
392
                ri.firing_wait[j] = PHYSFSX_readFix(fp);
393
        for (j = 0; j < NDL; j++)
394
                /*ri.firing_wait2[j] =*/ PHYSFSX_readFix(fp);
395
        for (j = 0; j < NDL; j++)
396
                ri.turn_time[j] = PHYSFSX_readFix(fp);
397
#if 0 // not used in d1, removed in d2
398
        for (j = 0; j < NDL; j++)
399
                ri.fire_power[j] = PHYSFSX_readFix(fp);
400
        for (j = 0; j < NDL; j++)
401
                ri.shield[j] = PHYSFSX_readFix(fp);
402
#endif
403
        for (j = 0; j < NDL; j++)
404
                ri.max_speed[j] = PHYSFSX_readFix(fp);
405
        for (j = 0; j < NDL; j++)
406
                ri.circle_distance[j] = PHYSFSX_readFix(fp);
407
        for (j = 0; j < NDL; j++)
408
                ri.rapidfire_count[j] = PHYSFSX_readByte(fp);
409
        for (j = 0; j < NDL; j++)
410
                ri.evade_speed[j] = PHYSFSX_readByte(fp);
411
        ri.cloak_type = PHYSFSX_readByte(fp);
412
        ri.attack_type = PHYSFSX_readByte(fp);
413
        ri.see_sound = PHYSFSX_readByte(fp);
414
        ri.attack_sound = PHYSFSX_readByte(fp);
415
        ri.claw_sound = PHYSFSX_readByte(fp);
416
        /*ri.taunt_sound =*/ PHYSFSX_readByte(fp);
417
        ri.boss_flag = PHYSFSX_readByte(fp);
418
        /*ri.companion =*/ PHYSFSX_readByte(fp);
419
        /*ri.smart_blobs =*/ PHYSFSX_readByte(fp);
420
        /*ri.energy_blobs =*/ PHYSFSX_readByte(fp);
421
        /*ri.thief =*/ PHYSFSX_readByte(fp);
422
        /*ri.pursuit =*/ PHYSFSX_readByte(fp);
423
        /*ri.lightcast =*/ PHYSFSX_readByte(fp);
424
        /*ri.death_roll =*/ PHYSFSX_readByte(fp);
425
        /*ri.flags =*/ PHYSFSX_readByte(fp);
426
        /*ri.pad[0] =*/ PHYSFSX_readByte(fp);
427
        /*ri.pad[1] =*/ PHYSFSX_readByte(fp);
428
        /*ri.pad[2] =*/ PHYSFSX_readByte(fp);
429
        /*ri.deathroll_sound =*/ PHYSFSX_readByte(fp);
430
        /*ri.glow =*/ PHYSFSX_readByte(fp);
431
        /*ri.behavior =*/ PHYSFSX_readByte(fp);
432
        /*ri.aim =*/ PHYSFSX_readByte(fp);
433
 
434
        for (j = 0; j < MAX_GUNS + 1; j++)
435
        {
436
                for (k = 0; k < N_ANIM_STATES; k++)
437
                {
438
                        ri.anim_states[j][k].n_joints = PHYSFSX_readShort(fp);
439
                        ri.anim_states[j][k].offset = PHYSFSX_readShort(fp);
440
                }
441
        }
442
        ri.always_0xabcd = PHYSFSX_readInt(fp);
443
 
444
        return 1;
445
}
446
 
447
namespace dsx {
448
 
449
static void load_hxm(const d_fname &hxmname)
450
{
451
        auto &Robot_joints = LevelSharedRobotJointState.Robot_joints;
452
        unsigned int repl_num;
453
        int i;
454
        auto f = PHYSFSX_openReadBuffered(hxmname);
455
        int n_items;
456
 
457
        if (!f)
458
                return; // hxm file doesn't exist
459
 
460
        if (PHYSFSX_readInt(f) != 0x21584d48) /* HMX! */
461
        {
462
                // invalid hxm file
463
                return;
464
        }
465
 
466
        if (PHYSFSX_readInt(f) != 1)
467
        {
468
                // unknown version
469
                return;
470
        }
471
 
472
        // read robot info
473
        if ((n_items = PHYSFSX_readInt(f)) != 0)
474
        {
475
                auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
476
                for (i = 0; i < n_items; i++)
477
                {
478
                        repl_num = PHYSFSX_readInt(f);
479
 
480
                        if (repl_num >= MAX_ROBOT_TYPES)
481
                        {
482
                                PHYSFSX_fseek(f, 480, SEEK_CUR); /* sizeof d2_robot_info */
483
                        }
484
                        else
485
                        {
486
                                if (!(read_d2_robot_info(f, Robot_info[repl_num])))
487
                                {
488
                                        return;
489
                                }
490
                        }
491
                }
492
        }
493
 
494
        // read joint positions
495
        if ((n_items = PHYSFSX_readInt(f)) != 0)
496
        {
497
                for (i = 0; i < n_items; i++)
498
                {
499
                        repl_num = PHYSFSX_readInt(f);
500
 
501
                        if (repl_num >= MAX_ROBOT_JOINTS)
502
                                PHYSFSX_fseek(f, sizeof(jointpos), SEEK_CUR);
503
                        else
504
                        {
505
                                jointpos_read(f, Robot_joints[repl_num]);
506
                        }
507
                }
508
        }
509
 
510
        // read polygon models
511
        if ((n_items = PHYSFSX_readInt(f)) != 0)
512
        {
513
                for (i = 0; i < n_items; i++)
514
                {
515
                        polymodel *pm;
516
 
517
                        repl_num = PHYSFSX_readInt(f);
518
                        if (repl_num >= MAX_POLYGON_MODELS)
519
                        {
520
                                PHYSFSX_readInt(f); // skip n_models
521
                                PHYSFSX_fseek(f, 734 - 8 + PHYSFSX_readInt(f) + 8, SEEK_CUR);
522
                        }
523
                        else
524
                        {
525
                                auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
526
                                pm = &Polygon_models[repl_num];
527
                                polymodel_read(pm, f);
528
                                const auto model_data_size = pm->model_data_size;
529
                                pm->model_data = std::make_unique<uint8_t[]>(model_data_size);
530
                                if (PHYSFS_read(f, pm->model_data, model_data_size, 1) < 1)
531
                                {
532
                                        pm->model_data.reset();
533
                                        return;
534
                                }
535
 
536
                                Dying_modelnums[repl_num] = PHYSFSX_readInt(f);
537
                                Dead_modelnums[repl_num] = PHYSFSX_readInt(f);
538
                        }
539
                }
540
        }
541
 
542
        // read object bitmaps
543
        if ((n_items = PHYSFSX_readInt(f)) != 0)
544
        {
545
                for (i = 0; i < n_items; i++)
546
                {
547
                        repl_num = PHYSFSX_readInt(f);
548
                        auto v = PHYSFSX_readShort(f);
549
                        if (repl_num < ObjBitmaps.size())
550
                                ObjBitmaps[repl_num].index = v;
551
                }
552
        }
553
}
554
 
555
}
556
 
557
// undo customized items
558
static void custom_remove()
559
{
560
        int i;
561
        auto bmo = std::begin(BitmapOriginal);
562
        auto bmp = std::begin(GameBitmaps);
563
 
564
        for (i = 0; i < MAX_BITMAP_FILES; bmo++, bmp++, i++)
565
                if (bmo->get_flag_mask(0x80))
566
                {
567
                        gr_free_bitmap_data(*bmp);
568
                        *bmp = *bmo;
569
 
570
                        if (bmo->get_flag_mask(BM_FLAG_PAGED_OUT))
571
                        {
572
                                GameBitmapOffset[i] = static_cast<int>(reinterpret_cast<uintptr_t>(bmo->bm_data));
573
                                gr_set_bitmap_flags(*bmp, BM_FLAG_PAGED_OUT);
574
                                gr_set_bitmap_data(*bmp, nullptr);
575
                        }
576
                        else
577
                        {
578
                                gr_set_bitmap_flags(*bmp, bmo->get_flag_mask(0x7f));
579
                        }
580
                        bmo->clear_flags();
581
                }
582
        for (i = 0; i < MAX_SOUND_FILES; i++)
583
                if (SoundOriginal[i].length & 0x80000000)
584
                {
585
                        d_free(GameSounds[i].data);
586
                        GameSounds[i].data = SoundOriginal[i].data;
587
#ifdef ALLEGRO
588
                        GameSounds[i].len = SoundOriginal[i].length & 0x7fffffff;
589
#else
590
                        GameSounds[i].length = SoundOriginal[i].length & 0x7fffffff;
591
#endif
592
                        SoundOriginal[i].length = 0;
593
                }
594
}
595
 
596
void load_custom_data(const d_fname &level_name)
597
{
598
        custom_remove();
599
        d_fname custom_file;
600
        using std::begin;
601
        using std::end;
602
        using std::copy;
603
        using std::next;
604
        auto bl = begin(level_name);
605
        auto bc = begin(custom_file);
606
        auto &pg1 = ".pg1";
607
        copy(bl, next(bl, custom_file.size() - sizeof(pg1)), bc);
608
        auto o = std::find(bc, next(bc, custom_file.size() - sizeof(pg1)), '.');
609
        copy(begin(pg1), end(pg1), o);
610
        load_pigpog(custom_file);
611
        auto &dtx = "dtx";
612
        ++o;
613
        copy(begin(dtx), end(dtx), o);
614
        load_pigpog(custom_file);
615
        auto &hx1 = "hx1";
616
        copy(begin(hx1), end(hx1), o);
617
        load_hxm(custom_file);
618
}
619
 
620
void custom_close()
621
{
622
        custom_remove();
623
}