principia/src/pkgman.cc
ROllerozxa 689163b24e Extract some pkginfo methods out of header
Removes the dependency on progress.hh in pkgman.hh
2026-06-05 16:52:26 +02:00

1519 lines
42 KiB
C++

#include "pkgman.hh"
#include <sys/stat.h>
#include <time.h>
#include <dirent.h>
#include "progress.hh"
#include "zlib.h"
#include "misc.hh"
#ifdef TMS_BACKEND_WINDOWS
#include <windows.h>
#endif
// for lvledit command line tool
#ifdef _NO_TMS
#include <stdlib.h>
#include <stdio.h>
#include <cinttypes>
#define tms_infof(...)
#define tms_warnf(...)
#define tms_fatalf(...)
#define tms_errorf(...)
static const char *tms_storage_path() { return "/tmp/"; }
static const char *tms_storage_cache_path() { return "/tmp/"; }
#else
#include <tms/bindings/cpp/cpp.hh>
#endif
static char _level_path[4][1024];
static char _state_path[1024];
static char _cache_path[4][1024];
static char _cache_state_path[1024];
static char _pkg_path[4][1024];
static const char *_dir_names[] = {
"local", "db", "main", "sys"
};
lvlbuf tmpbuf;
lvlinfo tmplvl;
void lvlinfo::create(int type, uint64_t seed, uint32_t version) {
this->local_id = 0;
this->save_id = 0;
this->version = (version ? version : LEVEL_VERSION);
this->type = type;
this->name[0] = '\0';
this->name_len = 0;
if (this->type == LCAT_PARTIAL) {
return;
}
this->community_id = 0;
this->autosave_id = 0;
this->revision = 0;
this->parent_id = 0;
this->parent_revision = 0;
this->pause_on_finish = true;
this->show_score = (type == LCAT_ADVENTURE);
this->descr_len = 0;
this->visibility = LEVEL_VISIBLE;
static const int random_bgs[] = {
0,1,2,4,8,9,10,11,12,13,14
};
static const int random_bgs_adventure[] = {
0,1,2,4,5,8,9,10,11,12,13,14
};
if (type == LCAT_ADVENTURE) {
this->bg = random_bgs_adventure[rand()%(sizeof(random_bgs_adventure)/sizeof(int))];
} else {
this->bg = random_bgs[rand()%(sizeof(random_bgs)/sizeof(int))];
}
this->bg_color = 0;
this->descr = 0;
this->size_x[0] = 40;
this->size_x[1] = 40;
this->size_y[0] = 4;
this->size_y[1] = 10;
this->gravity_x = 0;
this->gravity_y = -20.f;
this->flags = 0;
this->min_x = 0;
this->max_x = 0;
this->min_y = 0;
this->max_y = 0;
this->final_score = 0;
memset(this->icon, 0, 128*128);
this->sandbox_cam_x = 0.f;
this->sandbox_cam_y = 0.f;
this->sandbox_cam_zoom = 12.f;
this->velocity_iterations = 10;
this->position_iterations = 10;
this->seed = 0;
this->adventure_id = 0xffffffff;
this->prismatic_tolerance = 0.0125f;
this->pivot_tolerance = 0.0125f;
this->linear_damping = 0.1f;
this->angular_damping = 0.2f;
this->joint_friction = 0.3f;
if (type == LCAT_ADVENTURE) {
this->dead_enemy_absorb_time = 60.0f;
} else {
this->dead_enemy_absorb_time = 10.0f;
}
this->time_before_player_can_respawn = 1.5f;
this->compression_length = 0;
this->flags |= LVL_ALLOW_RESPAWN_WITHOUT_CHECKPOINT;
if (type == LCAT_ADVENTURE) {
this->flags |= LVL_ABSORB_DEAD_ENEMIES;
this->flags |= LVL_ENABLE_INTERACTIVE_DESTRUCTION;
this->flags |= LVL_DEAD_CREATURE_DESTRUCTION;
if (seed) {
this->flags |= LVL_CHUNKED_LEVEL_LOADING;
this->flags |= LVL_ALLOW_QUICKSAVING;
this->show_score = false;
this->seed = seed;
this->size_y[0] = 500;
this->bg = 5;
}
}
this->num_groups = 0;
this->num_entities = 0;
this->num_connections = 0;
this->num_cables = 0;
this->num_chunks = 0;
this->state_size = 0;
this->num_gentypes = 0;
}
void lvlinfo::sanity_check() {
if (this->version < LEVEL_VERSION_1_5) {
// remove flags added in 1.5
this->flags &= ~(
LVL_CHUNKED_LEVEL_LOADING |
LVL_DISABLE_CAVEVIEW |
LVL_DISABLE_ROCKET_TRIGGER_EXPLOSIVES |
LVL_STORE_SCORE_ON_GAME_OVER |
LVL_ALLOW_HIGH_SCORE_SUBMISSIONS |
LVL_LOWER_SCORE_IS_BETTER |
LVL_DISABLE_ENDSCREENS |
LVL_ALLOW_QUICKSAVING |
LVL_DEAD_CREATURE_DESTRUCTION
);
// add flags added in 1.5 that must be enabled by default
this->flags |= (
LVL_ALLOW_RESPAWN_WITHOUT_CHECKPOINT
);
this->dead_enemy_absorb_time = 10.0f;
this->time_before_player_can_respawn = 1.5f;
this->linear_damping = 0.0f;
this->angular_damping = 0.0f;
this->joint_friction = 0.0f;
this->seed = 0;
this->adventure_id = 0xffffffff;
this->compression_length = 0;
}
}
int lvlinfo::get_size() const {
return sizeof(uint8_t) // version
+ sizeof(uint8_t) // type
+ sizeof(uint32_t) // community_id
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(uint32_t) : 0) // autosave_id
+ sizeof(uint32_t) // revision
+ sizeof(uint32_t) // parent_id
+ sizeof(uint8_t) // name_len
+ sizeof(uint16_t) // descr_len
+ sizeof(uint8_t) // allow_derivatives
+ (this->version >= 3 ? sizeof(uint8_t) : 0) // visibility
+ (this->version >= 7 ? sizeof(uint32_t) : 0) // parent_revision
+ (this->version >= 7 ? sizeof(uint8_t) : 0) // pause_on_finish
+ (this->version >= 7 ? sizeof(uint8_t) : 0) // show_score
+ sizeof(uint8_t) // bg
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(uint32_t) : 0) // bg color, stored as uint32_t
+ sizeof(uint16_t) // size_x
+ sizeof(uint16_t) // size_y
+ (this->version>=12 ? sizeof(uint16_t) : 0) // size_x 2
+ (this->version>=12 ? sizeof(uint16_t) : 0) // size_y 2
+ sizeof(uint8_t) // velocity_iterations
+ sizeof(uint8_t) // position_iterations
+ sizeof(uint32_t) // final_score
+ sizeof(float) // sandbox_cam_x
+ sizeof(float) // sandbox_cam_y
+ sizeof(float) // sandbox_cam_zoom
+ (this->version >= 3 ? sizeof(float) : 0) // gravity_x
+ (this->version >= 3 ? sizeof(float) : 0) // gravity_y
+ (this->version >= 13 ? sizeof(float)*4 : 0) // bounds
+ (this->version >= 9 ? sizeof(uint64_t) : 0) // flags
+ (this->version >= LEVEL_VERSION_1_4 ? sizeof(float) : 0) // prismatic_tolerance
+ (this->version >= LEVEL_VERSION_1_4 ? sizeof(float) : 0) // pivot_tolerance
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(uint64_t) : 0) // seed
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(uint32_t) : 0) // adventure_id
// num groups, entities, connections, cables, chunks, state_size, num_gentypes
+ (this->version >= LEVEL_VERSION_1_5 ? 7 * sizeof(uint32_t) : 4 * sizeof(uint16_t))
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(float) : 0) // linear damping
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(float) : 0) // angular damping
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(float) : 0) // joint friction
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(float) : 0) // dead enemy absorb time
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(float) : 0) // time before player can respawn
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(uint64_t) : 0) // compression buffer length
+ this->name_len
+ (this->version >= 6 ? 128*128 : 0)
+ this->descr_len;
}
void lvlinfo::write(lvlbuf *lb) {
this->sanity_check();
lb->ensure(this->get_size());
lb->w_uint8(this->version);
lb->w_uint8(this->type);
if (this->type != LCAT_PARTIAL) {
lb->w_uint32(this->community_id);
if (this->version >= LEVEL_VERSION_1_5) {
lb->w_uint32(this->autosave_id);
}
lb->w_uint32(this->revision);
lb->w_uint32(this->parent_id);
}
lb->w_uint8(this->name_len);
if (this->type != LCAT_PARTIAL) {
lb->w_uint16(this->descr_len);
lb->w_uint8(1);
if (this->version >= 3) {
lb->w_uint8(this->visibility);
}
if (this->version >= 7) {
lb->w_uint32(this->parent_revision);
lb->w_uint8(this->pause_on_finish);
lb->w_uint8(this->show_score);
}
lb->w_uint8(this->bg);
if (this->version >= LEVEL_VERSION_1_5) {
lb->w_uint32(this->bg_color);
}
if (this->version >= 12) {
lb->w_uint16(this->size_x[0]);
lb->w_uint16(this->size_x[1]);
lb->w_uint16(this->size_y[0]);
lb->w_uint16(this->size_y[1]);
} else {
lb->w_uint16(this->size_x[0]+this->size_x[1]);
lb->w_uint16(this->size_y[0]+this->size_y[1]);
}
lb->w_uint8(this->velocity_iterations);
lb->w_uint8(this->position_iterations);
lb->w_uint32(this->final_score);
lb->w_float(this->sandbox_cam_x);
lb->w_float(this->sandbox_cam_y);
lb->w_float(this->sandbox_cam_zoom);
if (this->version >= 3) {
lb->w_float(this->gravity_x);
lb->w_float(this->gravity_y);
}
}
if (this->version >= 13) {
lb->w_float(this->min_x);
lb->w_float(this->max_x);
lb->w_float(this->min_y);
lb->w_float(this->max_y);
}
if (this->type != LCAT_PARTIAL) {
if (this->version >= 9) {
lb->w_uint64(this->flags);
}
if (this->version >= LEVEL_VERSION_1_4) {
lb->w_float(this->prismatic_tolerance);
lb->w_float(this->pivot_tolerance);
}
if (this->version >= LEVEL_VERSION_1_5) {
lb->w_uint64(this->seed);
lb->w_uint32(this->adventure_id);
lb->w_float(this->linear_damping);
lb->w_float(this->angular_damping);
lb->w_float(this->joint_friction);
lb->w_float(this->dead_enemy_absorb_time);
lb->w_float(this->time_before_player_can_respawn);
lb->w_uint64(this->compression_length);
}
}
if (this->name_len) {
lb->w_buf(this->name, this->name_len);
}
if (this->type != LCAT_PARTIAL) {
if (this->version >= 6) {
int nn = 0;
int n_max = 0;
for (int y=0; y<128*128; y++) {
if (icon[y] != 0) nn ++;
if (icon[y] > n_max) n_max = icon[y];
}
lb->w_buf((const char *)&this->icon, 128*128);
}
if (this->descr_len)
lb->w_buf(this->descr, this->descr_len);
}
if (this->version >= LEVEL_VERSION_1_5) {
lb->w_uint32(this->num_groups);
lb->w_uint32(this->num_entities);
lb->w_uint32(this->num_connections);
lb->w_uint32(this->num_cables);
lb->w_uint32(this->num_chunks);
lb->w_uint32(this->state_size);
lb->w_uint32(this->num_gentypes);
} else {
lb->w_uint16((uint16_t)this->num_groups);
lb->w_uint16((uint16_t)this->num_entities);
lb->w_uint16((uint16_t)this->num_connections);
lb->w_uint16((uint16_t)this->num_cables);
}
}
bool lvlinfo::read(lvlbuf *lb, bool skip_description) {
this->version = lb->r_uint8();
if (this->version > LEVEL_VERSION) {
return false;
}
this->type = lb->r_uint8();
if (this->type != LCAT_PARTIAL) {
this->community_id = lb->r_uint32();
if (this->version >= LEVEL_VERSION_1_5) {
this->autosave_id = lb->r_uint32();
}
this->revision = lb->r_uint32();
this->parent_id = lb->r_uint32();
}
this->name_len = lb->r_uint8();
if (this->type != LCAT_PARTIAL) {
this->descr_len = lb->r_uint16();
// XXX: Get rid of this value in next level version (allow_derivatives)
(void)lb->r_uint8();
if (this->version >= 3)
this->visibility = lb->r_uint8();
else
this->visibility = LEVEL_VISIBLE;
if (this->version >= 7) {
this->parent_revision = lb->r_uint32();
this->pause_on_finish = (bool)lb->r_uint8();
this->show_score = (bool)lb->r_uint8();
} else {
this->parent_revision = 0;
this->pause_on_finish = true;
this->show_score = 1;
}
this->bg = lb->r_uint8();
if (this->version >= LEVEL_VERSION_1_5) {
this->bg_color = lb->r_uint32();
}
if (this->version >= 12) {
this->size_x[0] = lb->r_uint16();
this->size_x[1] = lb->r_uint16();
this->size_y[0] = lb->r_uint16();
this->size_y[1] = lb->r_uint16();
} else {
this->size_x[0] = lb->r_uint16();
this->size_y[0] = lb->r_uint16();
this->size_x[1] = this->size_x[0];
this->size_y[1] = this->size_y[0];
}
this->velocity_iterations = lb->r_uint8();
this->position_iterations = lb->r_uint8();
this->final_score = lb->r_uint32();
this->sandbox_cam_x = lb->r_float();
this->sandbox_cam_y = lb->r_float();
this->sandbox_cam_zoom = lb->r_float();
if (this->version >= 3) {
this->gravity_x = lb->r_float();
this->gravity_y = lb->r_float();
} else {
this->gravity_x = 0.f;
this->gravity_y = -20.f;
}
}
if (this->version >= 13) {
min_x = lb->r_float();
max_x = lb->r_float();
min_y = lb->r_float();
max_y = lb->r_float();
} else {
min_x = 0.f;
max_x = 0.f;
min_y = 0.f;
max_y = 0.f;
}
if (this->type != LCAT_PARTIAL) {
if (this->version >= 9) {
this->flags = lb->r_uint64();
if (this->version < LEVEL_VERSION_1_5) {
this->flags |= LVL_ALLOW_RESPAWN_WITHOUT_CHECKPOINT;
}
} else {
this->flags = 0;
}
if (this->version >= 26) {
this->prismatic_tolerance = lb->r_float();
this->pivot_tolerance = lb->r_float();
} else {
this->prismatic_tolerance = 0.f;
this->pivot_tolerance = 0.f;
}
if (this->version >= LEVEL_VERSION_1_5) {
this->seed = lb->r_uint64();
this->adventure_id = lb->r_uint32();
this->linear_damping = lb->r_float();
this->angular_damping = lb->r_float();
this->joint_friction = lb->r_float();
this->dead_enemy_absorb_time = lb->r_float();
this->time_before_player_can_respawn = lb->r_float();
this->compression_length = lb->r_uint64();
} else {
this->seed = 0;
this->adventure_id = 0xffffffff;
this->linear_damping = 0.f;
this->angular_damping = 0.f;
this->joint_friction = 0.f;
this->dead_enemy_absorb_time = 10.f;
this->time_before_player_can_respawn = 0.f;
this->compression_length = 0;
}
}
if (this->name_len) {
lb->r_buf(this->name, this->name_len);
}
if (this->type != LCAT_PARTIAL) {
if (this->version >= 6) {
//tms_infof(" reading icon, pt: %d, end: %d", lb->rp, lb->size);
lb->r_buf((char *)&this->icon, 128*128);
} else
memset(this->icon, 0, 128*128);
if (this->descr_len && !skip_description) {
this->descr = (char*)realloc(this->descr, this->descr_len+1);
lb->r_buf(this->descr, this->descr_len);
this->descr[this->descr_len] = '\0';
}
}
if (this->version >= LEVEL_VERSION_1_5) {
this->num_groups = lb->r_uint32();
this->num_entities = lb->r_uint32();
this->num_connections = lb->r_uint32();
this->num_cables = lb->r_uint32();
this->num_chunks = lb->r_uint32();
this->state_size = lb->r_uint32();
this->num_gentypes = lb->r_uint32();
} else {
this->num_groups = lb->r_uint16();
this->num_entities = lb->r_uint16();
this->num_connections = lb->r_uint16();
this->num_cables = lb->r_uint16();
this->num_chunks = 0;
this->state_size = 0;
this->num_gentypes = 0;
}
this->sanity_check();
return true;
}
void lvlinfo::print() const {
printf("Level headers:\n");
printf("Level version: %u\t(%s)\n",
this->version, level_version_string(this->version));
printf("Level type: %u\t(%s)\n",
this->type, level_type_string(this->type));
if (this->type != LCAT_PARTIAL) {
printf("Community ID: %u\n",
this->community_id);
if (this->version >= LEVEL_VERSION_1_5) {
printf("Autosave ID: %u\n",
this->autosave_id);
}
printf("Revision: %u\n",
this->revision);
printf("Parent ID: %u\n",
this->parent_id);
}
printf("Name len: %u\n",
this->name_len);
if (this->type != LCAT_PARTIAL) {
printf("Description len: %u\n",
this->descr_len);
printf("Visibility: %s\n",
level_visibility_string(this->visibility));
printf("Visibility: %u\n",
this->parent_revision);
printf("Pause on finish: %s\n",
this->pause_on_finish ? "yes" : "no");
printf("Show score: %s\n",
this->show_score ? "yes" : "no");
printf("Background ID: %u\n",
this->bg);
if (this->version >= LEVEL_VERSION_1_5) {
printf("Background color: %u\n",
this->bg_color);
}
printf("Border left: %u\n",
this->size_x[0]);
printf("Border right: %u\n",
this->size_x[1]);
printf("Border down: %u\n",
this->size_y[0]);
printf("Border up: %u\n",
this->size_y[1]);
printf("Vel iterations: %u\n",
this->velocity_iterations);
printf("Pos iterations: %u\n",
this->position_iterations);
printf("Final score: %u\n",
this->final_score);
printf("Sandbox cam X: %f\n",
this->sandbox_cam_x);
printf("Sandbox cam Y: %f\n",
this->sandbox_cam_y);
printf("Sandbox cam Z: %f\n",
this->sandbox_cam_zoom);
if (this->version >= 3) {
printf("Gravity X: %.6f\n",
this->gravity_x);
printf("Gravity Y: %.6f\n",
this->gravity_y);
}
}
printf("Min/Max X: %.2f/%.2f\n",
this->min_x,
this->max_x);
printf("Min/Max Y: %.2f/%.2f\n",
this->min_y,
this->max_y);
if (this->type != LCAT_PARTIAL) {
printf("Flags: %" PRIu64 "\n",
this->flags);
printf("Prismatic tolerance: %.2f\n",
this->prismatic_tolerance);
printf("Pivot tolerance: %.2f\n",
this->pivot_tolerance);
printf("Seed: 0x%016" PRIx64 "\n",
this->seed);
printf("Adventure ID: 0x%08" PRIx32 "\n",
this->adventure_id);
printf("Linear damping: %.2f\n",
this->linear_damping);
printf("Angular damping: %.2f\n",
this->angular_damping);
printf("Joint friction: %.2f\n",
this->joint_friction);
printf("Enemy absorb time: %.2f\n",
this->dead_enemy_absorb_time);
printf("Player respawn time: %.2f\n",
this->time_before_player_can_respawn);
}
if (this->name_len) {
printf("Name: (%u) '%s'\n",
(unsigned)strlen(this->name), this->name);
}
if (this->type != LCAT_PARTIAL) {
if (this->descr_len) {
printf("Description: (%u) '%s'\n",
(unsigned)strlen(this->descr), this->descr);
}
}
printf("Num groups: %u\n",
this->num_groups);
printf("Num entities: %u\n",
this->num_entities);
printf("Num connections: %u\n",
this->num_connections);
printf("Num chunks: %u\n",
this->num_chunks);
printf("Num gentypes: %u\n",
this->num_gentypes);
printf("State size: %u\n",
this->state_size);
printf("Num cables: %u\n",
this->num_cables);
}
// pkginfo
uint32_t pkginfo::get_next_level(uint32_t id) {
if (this->return_on_finish) {
if (this->first_is_menu)
return this->levels[0];
else
return 0; // return to pkg screen
}
// find the level in the package, return the next level or 0 if it was the last level
for (int x=0; x<this->num_levels-1; x++) {
if (this->levels[x] == id)
return this->levels[x+1];
}
return 0;
}
bool pkginfo::is_level_locked(uint8_t index) {
uint8_t i_unlocked = 0;
int x = 0;
if (index >= this->num_levels) return true;
if (this->unlock_count == 0) return false;
if (this->first_is_menu) {
x = 1;
i_unlocked++;
}
for (; x<index; ++x) {
lvl_progress *p = progress::get_level_progress(this->type, this->levels[x]);
if (p->completed)
i_unlocked++;
}
if ((index - i_unlocked) < this->unlock_count)
return false;
return true;
}
bool pkginfo::add_level(uint32_t id) {
// make sure it isnt already added
for (int x=0; x<this->num_levels; x++)
if (this->levels[x] == id) return false;
this->levels = (uint32_t*)realloc(this->levels, sizeof(uint32_t)*(this->num_levels+1));
if (!this->levels) exit(1);
this->levels[this->num_levels] = id;
this->num_levels ++;
return true;
}
bool pkginfo::open(int type, uint32_t id) {
if (type < 0 || type >= 4) {
tms_errorf("invalid level type");
return false;
}
char path[1024];
char *storage = (char*)pkgman::get_pkg_path(type);
snprintf(path, 1023, "%s/%d.ppkg", storage, id);
this->type = type;
this->id = id;
if (this->levels) free(this->levels);
this->levels = 0;
this->num_levels = 0;
this->name[0] = '\0';
this->name[255] = '\0';
FILE_IN_ASSET(type == LEVEL_MAIN);
FILE *fp = _fopen(path, "rb");
if (!fp) {
tms_errorf("could not open: %s", path);
return false;
}
_fread(&this->version, 1, 1, fp);
if (this->version >= 2)
_fread(&this->community_id, 1, sizeof(uint32_t), fp);
else
this->community_id = 0;
_fread(this->name, 1, 255, fp);
if (this->version >= 3)
_fread(&this->unlock_count, 1, sizeof(uint8_t), fp);
if (this->version >= 1) {
_fread(&this->first_is_menu, 1, sizeof(uint8_t), fp);
_fread(&this->return_on_finish, 1, sizeof(uint8_t), fp);
}
_fread(&this->num_levels, 1, sizeof(uint8_t), fp);
if (this->num_levels)
this->levels = (uint32_t*)malloc(this->num_levels * sizeof(uint32_t));
for (int x=0; x<this->num_levels; x++) {
_fread(&this->levels[x], 1, sizeof(uint32_t), fp);
}
_fclose(fp);
return true;
}
bool pkginfo::save() {
if (this->type >= 3) {
tms_errorf("invalid level type");
return false;
}
char path[1024];
char *storage = (char*)pkgman::get_pkg_path(this->type);
snprintf(path, 1023, "%s/%d.ppkg", storage, this->id);
return save_to_path(path);
}
bool pkginfo::save_to_path(const char *path) {
FILE *fp = fopen(path, "wb");
if (!fp) {
tms_errorf("could not open: %s", path);
return false;
}
// always update the version to the latest on save
this->version = PKG_VERSION;
fwrite(&this->version, 1, 1, fp);
fwrite(&this->community_id, 1, sizeof(uint32_t), fp);
fwrite(this->name, 1, 255, fp);
fwrite(&this->unlock_count, 1, sizeof(uint8_t), fp);
fwrite(&this->first_is_menu, 1, sizeof(uint8_t), fp);
fwrite(&this->return_on_finish, 1, sizeof(uint8_t), fp);
fwrite(&this->num_levels, 1, sizeof(uint8_t), fp);
for (int x=0; x<this->num_levels; x++) {
fwrite(&this->levels[x], 1, sizeof(uint32_t), fp);
}
fclose(fp);
return true;
}
// pkgman
static uint32_t get_next_id(const char *storage, const char *ext) {
char path[1024];
struct stat s;
// Check IDs up to 2 billion. Ought to be enough for everyone.
for (uint32_t x = 1; x < 2e9; x++) {
snprintf(path, 1023, "%s/%d.%s", storage, x, ext);
int i = stat(path, &s);
if (i == -1)
return x;
}
return 0;
}
uint32_t pkgman::get_next_pkg_id() {
return get_next_id(pkgman::get_pkg_path(LEVEL_LOCAL), "ppkg");
}
uint32_t pkgman::get_next_level_id() {
return get_next_id(pkgman::get_level_path(LEVEL_LOCAL), "plvl");
}
uint32_t pkgman::get_next_object_id() {
return get_next_id(pkgman::get_level_path(LEVEL_LOCAL), "pobj");
}
const char *pkgman::get_pkg_path(int type) {
if (type < 0 || type >= 4) {
tms_errorf("invalid level type");
return "";
}
if (!_pkg_path[type][0]) {
if (type == LEVEL_MAIN)
// main levels are stored internally in data/
snprintf(_pkg_path[type], 1023, "data/pkg");
else if (type == LEVEL_DB)
snprintf(_pkg_path[type], 1023, "%s/pkg/db", tms_storage_cache_path());
else
snprintf(_pkg_path[type], 1023, "%s/packages", tms_storage_path());
}
return _pkg_path[type];
}
const char *pkgman::get_level_ext(int level_type) {
if (level_type == LEVEL_PARTIAL) return "pobj";
else if (level_type >= LEVEL_LOCAL_STATE) return "psav";
else return "plvl";
}
const char *pkgman::get_level_path(int level_type) {
if (level_type == LEVEL_PARTIAL) level_type = LEVEL_LOCAL;
if (level_type >= LEVEL_LOCAL_STATE) {
if (!_state_path[0])
snprintf(_state_path, 1023, "%s/saves", tms_storage_path());
return _state_path;
}
if (level_type < 0 || level_type >= 4) {
tms_errorf("invalid level type");
return "";
}
if (!_level_path[level_type][0]) {
if (level_type == LEVEL_MAIN)
// main levels are stored internally in data/
snprintf(_level_path[level_type], 1023, "data/lvl");
else if (level_type == LEVEL_SYS)
snprintf(_level_path[level_type], 1023, "%s/cache/local", tms_storage_cache_path());
else if (level_type == LEVEL_DB)
snprintf(_level_path[level_type], 1023, "%s/lvl/db", tms_storage_cache_path());
else
snprintf(_level_path[level_type], 1023, "%s/levels", tms_storage_path());
}
return _level_path[level_type];
}
const char *pkgman::get_state_prefix(int level_type) {
if (level_type >= LEVEL_LOCAL_STATE)
level_type -= LEVEL_LOCAL_STATE;
if (level_type == LEVEL_LOCAL)
return "local";
else if (level_type == LEVEL_DB)
return "db";
tms_errorf("Unknown state prefix for level type %d", level_type);
return "unknown";
}
const char *pkgman::get_cache_path(int level_type) {
if (level_type == LEVEL_PARTIAL) level_type = LEVEL_LOCAL;
if (level_type >= LEVEL_LOCAL_STATE) {
if (!_cache_state_path[0]) {
snprintf(_cache_state_path, 1023,
"%s/cache/sav",
tms_storage_cache_path());
}
return _cache_state_path;
}
if (level_type < 0 || level_type >= 4) {
tms_errorf("invalid level type");
return "";
}
if (!_cache_path[level_type][0]) {
snprintf(_cache_path[level_type], 1023,
"%s/cache/%s",
tms_storage_cache_path(),
_dir_names[level_type]);
}
return _cache_path[level_type];
}
void pkgman::get_level_full_path(int level_type, uint32_t id, uint32_t save_id, char *output) {
if (level_type >= LEVEL_LOCAL_STATE) {
snprintf(output, 1023, "%s/%s.%d.%u.%s",
pkgman::get_level_path(level_type),
pkgman::get_state_prefix(level_type),
id,
save_id,
pkgman::get_level_ext(level_type));
} else {
snprintf(output, 1023, "%s/%d.%s",
pkgman::get_level_path(level_type),
id,
pkgman::get_level_ext(level_type));
}
}
void pkgman::get_cache_full_path(int level_type, uint32_t id, uint32_t save_id, char *output) {
if (level_type >= LEVEL_LOCAL_STATE) {
snprintf(output, 1023, "%s/%s.%u.%u.pcache",
pkgman::get_cache_path(level_type),
pkgman::get_state_prefix(level_type),
id,
save_id);
} else {
snprintf(output, 1023, "%s/%d.pcache",
pkgman::get_cache_path(level_type),
id);
}
}
bool pkgman::get_level_name(int level_type, uint32_t id, uint32_t save_id, char *output) {
uint8_t o_version;
pkgman::get_level_data(level_type, id, save_id, output, &o_version);
return true;
}
bool pkgman::get_level_data(int level_type, uint32_t id, uint32_t save_id, char *o_name, uint8_t *o_version) {
char filename[1024];
pkgman::get_level_full_path(level_type, id, save_id, filename);
FILE *fp = fopen(filename, "rb");
if (!fp) {
tms_warnf("unable to open file for lid %u", id);
return false;
}
tmpbuf.reset();
tmpbuf.ensure(sizeof(lvlinfo));
fread(tmpbuf.buf, 1, sizeof(lvlinfo), fp);
tmpbuf.size = sizeof(lvlinfo);
tmplvl.read(&tmpbuf, true);
if (tmplvl.name_len == 0) {
strcpy(o_name, "<no name>");
} else {
memcpy(o_name, tmplvl.name, tmplvl.name_len*sizeof(char));
o_name[tmplvl.name_len] = '\0';
}
*o_version = tmplvl.version;
fclose(fp);
return true;
}
pkginfo *pkgman::get_pkgs(int type) {
if (type < 0 || type >= 4)
return 0;
const char *path = pkgman::get_pkg_path(type);
DIR *dir;
struct dirent *ent;
pkginfo *first = 0, *curr = 0;
if ((dir = opendir(path))) {
while ((ent = readdir(dir))) {
int len = strlen(ent->d_name);
if (len > 5 && memcmp(&ent->d_name[len-5], ".ppkg", 5) == 0) {
uint32_t pkg_id = atoi(ent->d_name);
if (pkg_id != 0) {
pkginfo *ff = new pkginfo();
if (ff->open(type, pkg_id)) {
if (curr) curr->next = ff;
if (!first) first = ff;
curr = ff;
} else {
tms_errorf("could not open pkg %d", pkg_id);
delete ff;
}
}
}
}
closedir(dir);
}
return first;
}
lvlfile *pkgman::get_levels(int level_type) {
bool state = false;
int orig_level_type = level_type;
if (level_type >= LEVEL_LOCAL_STATE) {
level_type -= LEVEL_LOCAL_STATE;
state = true;
}
if (level_type < 0 || level_type >= 5) {
tms_warnf("unknown level type");
return 0;
}
char ext[6];
snprintf(ext, 6, ".%s", pkgman::get_level_ext(orig_level_type));
const char *path = pkgman::get_level_path(orig_level_type);
char tmp[1024];
DIR *dir;
struct dirent *ent;
lvlfile *first = 0, *last = 0;
dir = opendir(path);
if (!dir) {
tms_errorf("could not open directory %s", path);
return first;
}
while ((ent = readdir(dir))) {
int len = strlen(ent->d_name);
if (!(len > 5 && memcmp(&ent->d_name[len-5], ext, 5) == 0))
continue;
uint32_t level_id = 0;
uint32_t save_id = 0;
uint8_t state_level_type = LEVEL_LOCAL_STATE;
if (state) {
// State format: {0:s:type}.{1:d:id}.{2:d:save_id}.{3:s:ext}
if (ent->d_name[0] == 'l')
state_level_type = LEVEL_LOCAL_STATE;
else if (ent->d_name[0] == 'd')
state_level_type = LEVEL_DB_STATE;
char *level_id_c = strchr(ent->d_name, '.');
level_id = atoi(level_id_c+1);
save_id = atoi(strchr(level_id_c+1, '.')+1);
} else {
// Regular level format: {0:d:id}.{1:s:save_id}
level_id = atoi(ent->d_name);
save_id = atoi(strchr(ent->d_name, '.')+1);
}
snprintf(tmp, 1023, "%s/%s", path, ent->d_name);
time_t mtime = get_mtime(tmp);
char date[21];
strftime(date, 20, "%Y-%m-%d %H:%M:%S", gmtime((time_t*)&(mtime)));
if (!(level_id != 0 || state))
continue;
lvlfile *ff = new lvlfile((state ? state_level_type : level_type), level_id);
strcpy(ff->modified_date, date);
ff->mtime = mtime;
ff->save_id = save_id;
/**
* For state searches, we do not care about the original level type.
* The original level type is merely there to notify us that it's states
* we are looking for.
* The level type of the current state is discerned via the filename
* using the first "dotted" argument (level or db).
**/
if (!pkgman::get_level_data((state ? state_level_type : orig_level_type), level_id, save_id, ff->name, &ff->version)) {
tms_warnf("Unable to get level name for lid %u", level_id);
delete ff;
continue;
}
if (!first) {
first = ff;
} else {
// loop through and insert the lvlfile as soon as the date is newer
lvlfile *l = first, *prev = 0;
while (l) {
if (strcmp(ff->modified_date, l->modified_date) > 0) {
break;
}
prev = l;
l = l->next;
}
if (!prev) {
ff->next = first;
first = ff;
} else {
ff->next = prev->next;
prev->next = ff;
}
}
}
closedir(dir);
return first;
}
uint32_t pkgman::get_latest_level_id(int level_type) {
lvlfile *level = pkgman::get_levels(LEVEL_LOCAL);
uint32_t latest_id = 0;
time_t latest_mtime = 0;
while (level) {
if (level->mtime > latest_mtime) {
latest_id = level->id;
latest_mtime = level->mtime;
}
lvlfile *next = level->next;
delete level;
level = next;
}
return latest_id;
}
extern "C" struct lvlfile* pkgman_get_levels(int level_type) {
return pkgman::get_levels(level_type);
}
// lvledit
bool lvledit::open(int lvl_type, uint32_t lvl_id) {
char filename[1024];
const char *ext = pkgman::get_level_ext(lvl_type);
const char *path = pkgman::get_level_path(lvl_type);
if (lvl_id == 0)
snprintf(filename, 1024, "%s/.autosave", path);
else
snprintf(filename, 1024, "%s/%d.%s", path, lvl_id, ext);
this->lvl_type = 0;
this->lvl_id = 0;
if (!open_from_path(filename)) {
tms_errorf("could not open level file '%s'", filename);
return false;
}
this->lvl_type = lvl_type;
this->lvl_id = lvl_id;
return true;
}
bool lvledit::save() {
char filename[1024];
const char *ext = pkgman::get_level_ext(lvl_type);
const char *path = pkgman::get_level_path(lvl_type);
snprintf(filename, 1024, "%s/%d.%s", path, this->lvl_id, ext);
return save_to_path(filename);
}
bool lvledit::open_from_path(const char *path) {
FILE *fp = fopen(path, "rb");
if (!fp) {
tms_errorf("could not open file '%s'", path);
return false;
}
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (size > 2*1024*1024) {
tms_fatalf("file too big");
}
this->lb.reset();
this->lb.size = 0;
this->lb.ensure((int)size);
fread(this->lb.buf, 1, size, fp);
fclose(fp);
this->lb.size = size;
this->lvl.read(&this->lb);
this->header_size = this->lvl.get_size();
return true;
}
bool lvledit::save_to_path(const char *path) {
if (this->lvl.get_size() != this->header_size) {
// new header size does not match old header size,
// we need to perform a memmove on the object data
int diff = this->lvl.get_size() - this->header_size;
if (diff > 0)
this->lb.ensure(diff);
char *header_end = (char*)this->lb.buf + this->header_size;
memmove(header_end + diff, header_end, this->lb.size - this->header_size);
this->header_size += diff;
this->lb.size += diff;
}
int saved_size = this->lb.size;
this->lb.size = 0;
this->lvl.write(&this->lb);
this->lb.size = saved_size;
FILE *fp = fopen(path, "wb");
if (!fp) {
tms_errorf("could not open file '%s' for writing", path);
return false;
}
fwrite(this->lb.buf, 1, this->lb.size, fp);
fclose(fp);
return true;
}
void lvledit::print_gids() {
// IMPORTANT: keep this in sync with of::read()
this->lb.rp = this->lvl.get_size();
uint32_t num_groups = this->lvl.num_groups;
uint32_t num_entities = this->lvl.num_entities;
// skip groups
#if 0
this->lb.rp +=
num_groups *
(
sizeof(uint32_t) // id
+sizeof(float) // pos.x
+sizeof(float) // pos.y
+sizeof(float) // angle
+(version >= LEVEL_VERSION_1_5 ? sizeof(uint32_t) : 0)
);
#endif
for (uint32_t x = 0; x < num_groups; ++x) {
lb.r_uint32(); // id
lb.rp += sizeof(float) // pos.x
+ sizeof(float) // pos.y
+ sizeof(float);// angle
if (this->lvl.version >= LEVEL_VERSION_1_5) {
uint32_t state_size = lb.r_uint32();
lb.rp += state_size;
}
}
for (uint32_t x = 0; x < num_entities; ++x) {
uint8_t np;
uint8_t nc = 0;
uint32_t state_size = 0;
uint8_t g_id = lb.r_uint8(); // g_id
lb.r_uint32(); // id
lb.r_uint16(); // group_id half
lb.r_uint16(); // group id half
printf("%s%u",((x>0)?"\n":""), g_id); // g_id
np = lb.r_uint8(); // num properties
if (this->lvl.version >= LEVEL_VERSION_1_5) {
nc = lb.r_uint8(); // num chunks
state_size = lb.r_uint32(); // state size
}
(void)lb.r_float(); // pos.x
(void)lb.r_float(); // pos.y
(void)lb.r_float(); // angle
(void)lb.r_uint8(); // layer
if (this->lvl.version >= LEVEL_VERSION_1_5) {
lb.r_uint64(); // state flags
// this includes moveable and axisrot
for (int x=0; x<nc; x++) {
lb.r_int32(); // chunk x
lb.r_int32(); // chunk y
}
// skip state buffer
lb.rp += state_size;
} else {
(void)lb.r_uint8(); // axisrot
if (this->lvl.version >= 10) {
(void)lb.r_uint8(); // moveable
}
}
for (int x=0; x<np; x++) {
uint8_t type = lb.r_uint8();
switch (type) {
case P_INT8:
lb.r_uint8();
break;
case P_INT:
case P_ID:
lb.r_uint32();
break;
case P_FLT:
lb.r_float();
break;
case P_STR:
if (this->lvl.version >= LEVEL_VERSION_1_5) {
uint32_t len = lb.r_uint32();
lb.rp += len;
} else {
uint16_t len = lb.r_uint16();
lb.rp += len;
}
break;
default: fprintf(stderr, "invalid object property %d", type); exit(1);
}
}
}
}
// lvlbuf
void lvlbuf::ensure(uint64_t s) {
uint64_t ns = this->size + s;
if (ns > this->cap) {
uint64_t old_cap = this->cap;
if (this->sparse_resize) {
// round up to nearest power of two
this->cap = ns;
this->cap --;
this->cap |= this->cap >> 1;
this->cap |= this->cap >> 2;
this->cap |= this->cap >> 4;
this->cap |= this->cap >> 8;
this->cap |= this->cap >> 16;
this->cap++;
} else {
this->cap = ((ns > this->cap+4096) ? ns+4096 : this->cap+4096);
}
this->buf = static_cast<uint8_t*>(realloc(this->buf, this->cap));
}
}
void lvlbuf::zcompress(const lvlinfo &level, unsigned char **dest, uint64_t *dest_len) const {
uint64_t header_size = level.get_size();
*dest_len = compressBound(this->size-header_size);
*dest = (unsigned char*)malloc(*dest_len);
int ret = compress(*dest, (uLong*)dest_len, this->buf+header_size, this->size-header_size);
// TODO: Better error-handling
switch (ret) {
case Z_OK:
tms_infof("zcompress success");
break;
case Z_MEM_ERROR:
tms_errorf("no mem");
break;
case Z_BUF_ERROR:
tms_errorf("not enough room in buffer");
break;
case Z_STREAM_ERROR:
tms_errorf("z stream error");
break;
default:
tms_infof("unknown ret: %d", ret);
break;
}
tms_infof("Compression status: Old: %" PRIu64 ". New: %" PRIu64 ". Total: %.2f", this->size-header_size, *dest_len, (float)(float)*dest_len/((this->size-header_size)));
}
void lvlbuf::zuncompress(const lvlinfo &level) {
uint64_t header_size = level.get_size();
uLong dest_len = level.compression_length;
unsigned char *dest = (unsigned char*)malloc(dest_len);
int ret = uncompress(dest, &dest_len, this->buf+header_size, this->size-header_size);
// TODO: Better error-handling
switch (ret) {
case Z_OK:
tms_infof("zuncompress success");
break;
case Z_MEM_ERROR:
tms_errorf("no mem");
break;
case Z_BUF_ERROR:
tms_errorf("not enough room in buffer");
break;
case Z_STREAM_ERROR:
tms_errorf("z stream error");
break;
default:
tms_infof("unknown ret: %d", ret);
break;
}
tms_infof("Uncompression status: Old: %" PRIu64 ". New: %u. Total: %.2f", this->size-header_size, (unsigned)dest_len, (float)(float)dest_len/((this->size-header_size)));
this->size = header_size;
this->ensure(dest_len);
this->size = header_size + dest_len;
memcpy(this->buf+header_size, dest, dest_len);
free(dest);
}