Remove 6-define limit in the cache filename by storing part in a SHA-1 hash.

Note that i might have messed up somewhere my implementation of the
SHA-1 algorithm since it doesn't seem to produce exactly the same
result as sha1sum :/ ... but well it seems good enough for the task.
This commit is contained in:
Benoît Timbert 2007-08-03 23:20:16 +00:00
parent 4fbd88f19b
commit 32251b76a6
4 changed files with 320 additions and 136 deletions

View file

@ -113,6 +113,7 @@ wesnoth_SOURCES = \
replay_controller.cpp \
reports.cpp \
sdl_utils.cpp \
sha1.cpp \
show_dialog.cpp \
sound.cpp \
soundsource.cpp \
@ -397,6 +398,7 @@ noinst_HEADERS = \
replay_controller.hpp \
reports.hpp \
scoped_resource.hpp \
sha1.hpp \
sdl_utils.hpp \
show_dialog.hpp \
sound.hpp \

View file

@ -52,6 +52,7 @@
#include "serialization/binary_wml.hpp"
#include "serialization/parser.hpp"
#include "serialization/preprocessor.hpp"
#include "sha1.hpp"
#ifdef HAVE_PYTHON
#include "ai_python.hpp"
@ -1505,152 +1506,151 @@ void game_controller::read_game_cfg(const preproc_map& defines, config& cfg, boo
{
log_scope("read_game_cfg");
if(defines.size() < 6) {
bool is_valid = true;
std::stringstream str;
str << "-v" << game_config::version;
for(preproc_map::const_iterator i = defines.begin(); i != defines.end(); ++i) {
if(i->second.value != "" || i->second.arguments.empty() == false) {
is_valid = false;
break;
}
str << "-" << i->first;
bool is_valid = true;
std::stringstream str;
for(preproc_map::const_iterator i = defines.begin(); i != defines.end(); ++i) {
if(i->second.value != "" || i->second.arguments.empty() == false) {
is_valid = false;
break;
}
//std::string localename = get_locale().localename;
//str << "-lang_" << (localename.empty() ? "default" : localename);
if(is_valid) {
const std::string& cache = get_cache_dir();
if(cache != "") {
const std::string fname = cache + "/cache" + str.str();
const std::string fname_checksum = fname + ".checksum";
str << " " << i->first;
}
//std::string localename = get_locale().localename;
//str << "-lang_" << (localename.empty() ? "default" : localename);
file_tree_checksum dir_checksum;
if(is_valid) {
const std::string& cache = get_cache_dir();
if(cache != "") {
sha1_hash sha;
sha.hash(str.str()); // use a hash for a shorter display of the defines
const std::string fname = cache + "/cache-v" + game_config::version + "-" + sha.display();
const std::string fname_checksum = fname + ".checksum";
if(use_cache && !force_valid_cache_) {
try {
if(file_exists(fname_checksum)) {
config checksum_cfg;
scoped_istream stream = istream_file(fname_checksum);
read(checksum_cfg, *stream);
dir_checksum = file_tree_checksum(checksum_cfg);
}
} catch(config::error&) {
std::cerr << "cache checksum is corrupt\n";
} catch(io_exception&) {
std::cerr << "error reading cache checksum\n";
}
}
file_tree_checksum dir_checksum;
if(force_valid_cache_)
std::cerr << "skipping cache validation (forced)\n";
if(use_cache && file_exists(fname) && (force_valid_cache_ || file_create_time(fname) > data_tree_checksum().modified && dir_checksum == data_tree_checksum())) {
std::cerr << "found valid cache at '" << fname << "' using it\n";
log_scope("read cache");
try {
scoped_istream stream = istream_file(fname);
read_compressed(cfg, *stream);
return;
} catch(config::error&) {
std::cerr << "cache is corrupt. Loading from files\n";
} catch(io_exception&) {
std::cerr << "error reading cache. Loading from files\n";
}
}
std::cerr << "no valid cache found. Writing cache to '" << fname << "'\n";
preproc_map defines_map(defines);
//read the file and then write to the cache
scoped_istream stream = preprocess_file("data/", &defines_map);
//reset the parse counter before reading the game files
if (loadscreen::global_loadscreen) {
loadscreen::global_loadscreen->parser_counter = 0;
}
std::string error_log, user_error_log;
read(cfg, *stream, &error_log);
//load user campaigns
const std::string user_campaign_dir = get_user_data_dir() + "/data/campaigns/";
std::vector<std::string> user_campaigns, error_campaigns;
get_files_in_dir(user_campaign_dir,&user_campaigns,NULL,ENTIRE_FILE_PATH);
for(std::vector<std::string>::const_iterator uc = user_campaigns.begin(); uc != user_campaigns.end(); ++uc) {
static const std::string extension = ".cfg";
if(uc->size() < extension.size() || std::equal(uc->end() - extension.size(),uc->end(),extension.begin()) == false) {
continue;
}
try {
preproc_map user_defines_map(defines_map);
scoped_istream stream = preprocess_file(*uc,&user_defines_map);
std::string campaign_error_log;
config user_campaign_cfg;
read(user_campaign_cfg,*stream,&campaign_error_log);
if(campaign_error_log.empty()) {
cfg.append(user_campaign_cfg);
} else {
user_error_log += campaign_error_log;
error_campaigns.push_back(*uc);
}
} catch(config::error& err) {
std::cerr << "error reading user campaign '" << *uc << "'\n";
error_campaigns.push_back(*uc);
user_error_log += err.message + "\n";
} catch(io_exception&) {
std::cerr << "error reading user campaign '" << *uc << "'\n";
error_campaigns.push_back(*uc);
}
}
if(error_campaigns.empty() == false) {
std::stringstream msg;
msg << _("The following add-on campaign(s) had errors and could not be loaded:");
for(std::vector<std::string>::const_iterator i = error_campaigns.begin(); i != error_campaigns.end(); ++i) {
msg << "\n" << *i;
}
msg << "\n" << _("ERROR DETAILS:") << "\n" << user_error_log;
gui::show_error_message(disp(),msg.str());
}
cfg.merge_children("units");
config& hashes = cfg.add_child("multiplayer_hashes");
for(config::child_list::const_iterator ch = cfg.get_children("multiplayer").begin(); ch != cfg.get_children("multiplayer").end(); ++ch) {
hashes[(**ch)["id"]] = (*ch)->hash();
}
if(!error_log.empty()) {
gui::show_error_message(disp(),
_("Warning: Errors occurred while loading game configuration files: '") +
error_log);
} else {
try {
scoped_ostream cache = ostream_file(fname);
write_compressed(*cache, cfg);
if(use_cache && !force_valid_cache_) {
try {
if(file_exists(fname_checksum)) {
config checksum_cfg;
data_tree_checksum().write(checksum_cfg);
scoped_ostream checksum = ostream_file(fname_checksum);
write(*checksum, checksum_cfg);
} catch(io_exception&) {
std::cerr << "could not write to cache '" << fname << "'\n";
scoped_istream stream = istream_file(fname_checksum);
read(checksum_cfg, *stream);
dir_checksum = file_tree_checksum(checksum_cfg);
}
} catch(config::error&) {
std::cerr << "cache checksum is corrupt\n";
} catch(io_exception&) {
std::cerr << "error reading cache checksum\n";
}
}
if(force_valid_cache_)
std::cerr << "skipping cache validation (forced)\n";
if(use_cache && file_exists(fname) && (force_valid_cache_ || file_create_time(fname) > data_tree_checksum().modified && dir_checksum == data_tree_checksum())) {
std::cerr << "found valid cache at '" << fname << "' using it\n";
log_scope("read cache");
try {
scoped_istream stream = istream_file(fname);
read_compressed(cfg, *stream);
return;
} catch(config::error&) {
std::cerr << "cache is corrupt. Loading from files\n";
} catch(io_exception&) {
std::cerr << "error reading cache. Loading from files\n";
}
}
std::cerr << "no valid cache found. Writing cache to '" << fname << "'\n";
preproc_map defines_map(defines);
//read the file and then write to the cache
scoped_istream stream = preprocess_file("data/", &defines_map);
//reset the parse counter before reading the game files
if (loadscreen::global_loadscreen) {
loadscreen::global_loadscreen->parser_counter = 0;
}
std::string error_log, user_error_log;
read(cfg, *stream, &error_log);
//load user campaigns
const std::string user_campaign_dir = get_user_data_dir() + "/data/campaigns/";
std::vector<std::string> user_campaigns, error_campaigns;
get_files_in_dir(user_campaign_dir,&user_campaigns,NULL,ENTIRE_FILE_PATH);
for(std::vector<std::string>::const_iterator uc = user_campaigns.begin(); uc != user_campaigns.end(); ++uc) {
static const std::string extension = ".cfg";
if(uc->size() < extension.size() || std::equal(uc->end() - extension.size(),uc->end(),extension.begin()) == false) {
continue;
}
return;
try {
preproc_map user_defines_map(defines_map);
scoped_istream stream = preprocess_file(*uc,&user_defines_map);
std::string campaign_error_log;
config user_campaign_cfg;
read(user_campaign_cfg,*stream,&campaign_error_log);
if(campaign_error_log.empty()) {
cfg.append(user_campaign_cfg);
} else {
user_error_log += campaign_error_log;
error_campaigns.push_back(*uc);
}
} catch(config::error& err) {
std::cerr << "error reading user campaign '" << *uc << "'\n";
error_campaigns.push_back(*uc);
user_error_log += err.message + "\n";
} catch(io_exception&) {
std::cerr << "error reading user campaign '" << *uc << "'\n";
error_campaigns.push_back(*uc);
}
}
if(error_campaigns.empty() == false) {
std::stringstream msg;
msg << _("The following add-on campaign(s) had errors and could not be loaded:");
for(std::vector<std::string>::const_iterator i = error_campaigns.begin(); i != error_campaigns.end(); ++i) {
msg << "\n" << *i;
}
msg << "\n" << _("ERROR DETAILS:") << "\n" << user_error_log;
gui::show_error_message(disp(),msg.str());
}
cfg.merge_children("units");
config& hashes = cfg.add_child("multiplayer_hashes");
for(config::child_list::const_iterator ch = cfg.get_children("multiplayer").begin(); ch != cfg.get_children("multiplayer").end(); ++ch) {
hashes[(**ch)["id"]] = (*ch)->hash();
}
if(!error_log.empty()) {
gui::show_error_message(disp(),
_("Warning: Errors occurred while loading game configuration files: '") +
error_log);
} else {
try {
scoped_ostream cache = ostream_file(fname);
write_compressed(*cache, cfg);
config checksum_cfg;
data_tree_checksum().write(checksum_cfg);
scoped_ostream checksum = ostream_file(fname_checksum);
write(*checksum, checksum_cfg);
} catch(io_exception&) {
std::cerr << "could not write to cache '" << fname << "'\n";
}
}
return;
}
}

151
src/sha1.cpp Normal file
View file

@ -0,0 +1,151 @@
/* $Id$ */
/*
Copyright (C) 2007 by Benoit Timbert <benoit.timbert@free.fr>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/* This is supposed to be an implementation of the
Secure Hash Algorithm 1 (SHA-1)
Check RFC 3174 for details about the algorithm.
Currently this implementation might produce different result on endian and
bigendian machines, but for our current usage, we don't care :)
*/
#include "sha1.hpp"
#include <iomanip>
#include <sstream>
#define sha_rotl(n,x) ( ((x) << (n)) | ((x) >> (32-(n))) )
#define sha_ch(x,y,z) ( ((x) & (y)) | ((!(x)) & (z)) )
#define sha_parity(x,y,z) ( (x) ^ (y) ^ (z) )
#define sha_maj(x,y,z) ( ((x) & (y)) | ((x) & (z)) | ((y) & (z)) )
// display our hash
std::string sha1_hash::display() {
std::stringstream s;
s << std::hex << std::setfill('0') << std::setw(8) << H0;
s << std::hex << std::setfill('0') << std::setw(8) << H1;
s << std::hex << std::setfill('0') << std::setw(8) << H2;
s << std::hex << std::setfill('0') << std::setw(8) << H3;
s << std::hex << std::setfill('0') << std::setw(8) << H4;
return s.str();
}
// make a hash from a string
void sha1_hash::hash(const std::string& str) {
char block[64];
int bytes_left = str.size();
uint32_t ssz = bytes_left * 8; // string length in bits
// initialize the hash values
H0 = 0x67452301;
H1 = 0xefcdab89;
H2 = 0x98badcfe;
H3 = 0x10325476;
H4 = 0xc3d2e1f0;
std::stringstream iss (str, std::stringstream::in);
// cut our string in 64 bytes blocks then process it
while (bytes_left > 0) {
iss.read(block, 64);
if (bytes_left <= 64) { // if it's the last block, pad it
if (bytes_left < 64) {
block[bytes_left]=0x80; // add a 1 bit right after the end of the string
}
int i;
for (i = 63; i > bytes_left; i--) {
block[i]=0; // pad our block with zeros
}
if (bytes_left < 60) { // enough space to store the length
// put the length at the end of the block
block[60] = ssz >> 24;
block[61] = ssz >> 16;
block[62] = ssz >> 8;
block[63] = ssz;
} else { // not enough space for the zeros => we need a new block
next(block);
// new block
for (i = 0; i < 60 ; i++) {
block[i]=0; // pad our block with zeros
}
if (bytes_left == 64) {
block[0]=0x80; // add a 1 bit right after the end of the string = beginning of our new block
}
// put the length at the end of the block
block[60] = ssz >> 24;
block[61] = ssz >> 16;
block[62] = ssz >> 8;
block[63] = ssz;
}
}
next(block);
bytes_left -= 64;
}
}
// process the next 512 bits block
void sha1_hash::next(char block[64]) {
uint32_t W[80];
uint32_t A, B, C, D, E, T;
int i;
A = H0;
B = H1;
C = H2;
D = H3;
E = H4;
for (i = 0; i < 16; i++) {
W[i]= (block[4 * i] << 24) | (block[4 * i + 1] << 16) | (block[4 * i + 2] << 8) | block[4 * i + 3];
}
for (; i < 80; i++) {
W[i]=sha_rotl(1, W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]);
}
for (i = 0; i < 20; i++) {
T = sha_rotl(5,A) + sha_ch(B,C,D) + E + W[i] + 0x5a827999;
E = D;
D = C;
C = sha_rotl(30,B);
B = A;
A = T;
}
for (; i < 40; i++) {
T = sha_rotl(5,A) + sha_parity(B,C,D) + E + W[i] + 0x6ed9eba1;
E = D;
D = C;
C = sha_rotl(30,B);
B = A;
A = T;
}
for (; i < 60; i++) {
T = sha_rotl(5,A) + sha_maj(B,C,D) + E + W[i] + 0x8f1bbcdc;
E = D;
D = C;
C = sha_rotl(30,B);
B = A;
A = T;
}
for (; i < 80; i++) {
T = sha_rotl(5,A) + sha_parity(B,C,D) + E + W[i] + 0xca62c1d6;
E = D;
D = C;
C = sha_rotl(30,B);
B = A;
A = T;
}
H0 += A;
H1 += B;
H2 += C;
H3 += D;
H4 += E;
}

31
src/sha1.hpp Normal file
View file

@ -0,0 +1,31 @@
/* $Id$ */
/*
Copyright (C) 2007 by Benoit Timbert <benoit.timbert@free.fr>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef SHA1_H_INCLUDED
#define SHA1_H_INCLUDED
#include <string>
#include <stdint.h>
class sha1_hash
{
public:
void hash(const std::string& str);
std::string display();
private:
void next(char block[64]);
uint32_t H0, H1, H2, H3, H4;
};
#endif