]> git.localhorst.tv Git - blank.git/blob - src/io/WorldSave.cpp
load block types from data file
[blank.git] / src / io / WorldSave.cpp
1 #include "WorldSave.hpp"
2
3 #include "filesystem.hpp"
4
5 #include <cctype>
6 #include <fstream>
7 #include <iostream>
8 #include <limits>
9 #include <stdexcept>
10 #include <zlib.h>
11
12 using namespace std;
13
14
15 namespace blank {
16
17 WorldSave::WorldSave(const string &path)
18 : root_path(path)
19 , conf_path(path + "world.conf")
20 , chunk_path(path + "chunks/%d/%d/%d.gz")
21 , chunk_bufsiz(chunk_path.length() + 3 * std::numeric_limits<int>::digits10)
22 , chunk_buf(new char[chunk_bufsiz]) {
23
24 }
25
26
27 bool WorldSave::Exists() const noexcept {
28         return is_dir(root_path) && is_file(conf_path);
29 }
30
31
32 void WorldSave::Read(World::Config &conf) const {
33         cout << "reading world save" << endl;
34
35         ifstream in(conf_path);
36         if (!in) {
37                 throw runtime_error("failed to open world config");
38         }
39
40         constexpr char spaces[] = "\n\r\t ";
41
42         string line;
43         while (getline(in, line)) {
44                 if (line.empty() || line[0] == '#') continue;
45                 auto equals_pos = line.find_first_of('=');
46
47                 auto name_begin = line.find_first_not_of(spaces, 0, sizeof(spaces));
48                 auto name_end = equals_pos - 1;
49                 while (name_end > name_begin && isspace(line[name_end])) {
50                         --name_end;
51                 }
52
53                 auto value_begin = line.find_first_not_of(spaces, equals_pos + 1, sizeof(spaces));
54                 auto value_end = line.length() - 1;
55                 while (value_end > value_begin && isspace(line[value_end])) {
56                         --value_end;
57                 }
58
59                 string name(line, name_begin, name_end - name_begin + 1);
60                 string value(line, value_begin, value_end - value_begin + 1);
61
62                 if (name == "seed") {
63                         conf.gen.seed = stoul(value);
64                 } else {
65                         throw runtime_error("unknown world option: " + name);
66                 }
67         }
68         if (in.bad()) {
69                 throw runtime_error("IO error reading world config");
70         }
71 }
72
73 void WorldSave::Write(const World::Config &conf) const {
74         cout << "writing world save" << endl;
75
76         if (!make_dirs(root_path)) {
77                 throw runtime_error("failed to create world save directory");
78         }
79
80         ofstream out(conf_path);
81         out << "seed = " << conf.gen.seed << endl;
82         out.close();
83
84         if (!out) {
85                 throw runtime_error("failed to write world config");
86         }
87 }
88
89
90 bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept {
91         return is_file(ChunkPath(pos));
92 }
93
94 void WorldSave::Read(Chunk &chunk) const {
95         const char *path = ChunkPath(chunk.Position());
96         gzFile file = gzopen(path, "r");
97         if (!file) {
98                 throw runtime_error("failed to open chunk file");
99         }
100         if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) {
101                 throw runtime_error("failed to read chunk from file");
102         }
103         if (gzclose(file) != Z_OK) {
104                 throw runtime_error("failed to read chunk file");
105         }
106         chunk.InvalidateModel();
107         chunk.ClearSave();
108 }
109
110 void WorldSave::Write(Chunk &chunk) const {
111         const char *path = ChunkPath(chunk.Position());
112         gzFile file = gzopen(path, "w");
113         if (!file) {
114                 // check if it's because of a missing path component
115                 if (errno != ENOENT) {
116                         // nope, fatal
117                         throw runtime_error(strerror(errno));
118                 }
119                 string dir_path(path);
120                 dir_path.erase(dir_path.find_last_of("\\/"));
121                 if (!make_dirs(dir_path)) {
122                         throw runtime_error("failed to create dir for chunk file");
123                 }
124                 file = gzopen(path, "w");
125                 if (!file) {
126                         throw runtime_error("failed to open chunk file");
127                 }
128         }
129         if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) {
130                 gzclose(file); // if this fails, it can't be helped
131                 throw runtime_error("failed to write chunk to file");
132         }
133         if (gzclose(file) != Z_OK) {
134                 throw runtime_error("failed to write chunk file");
135         }
136         chunk.ClearSave();
137 }
138
139
140 const char *WorldSave::ChunkPath(const Chunk::Pos &pos) const {
141         snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z);
142         return chunk_buf.get();
143 }
144
145 }