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