bool GraphicManager::loadOTFI(const FileName& filename, wxString& error, wxArrayString& warnings) { otfi_found = wxFileExists(filename.GetFullPath()); if(otfi_found) { std::string path = std::string(filename.GetFullPath().mb_str()); OTMLDocumentPtr doc = OTMLDocument::parse(path); if(doc->size() == 0 || !doc->hasChildAt("DatSpr")) { error += "'DatSpr' tag not found"; return false; } OTMLNodePtr node = doc->get("DatSpr"); is_extended = node->valueAt<bool>("extended"); has_transparency = node->valueAt<bool>("transparency"); has_frame_durations = node->valueAt<bool>("frame-durations"); has_frame_groups = node->valueAt<bool>("frame-groups"); } else { is_extended = false; has_transparency = false; has_frame_durations = false; has_frame_groups = false; } return true; }
bool IOMapOTMM::saveMap(Map& map, const FileName& identifier, bool showdialog) { DiskNodeFileWriteHandle f(std::string(identifier.GetFullPath().mb_str(wxConvUTF8))); if(f.isOk() == false){ error(wxT("Can not open file %s for writing"), (const char*)identifier.GetFullPath().mb_str(wxConvUTF8)); return false; } if(showdialog) gui.CreateLoadBar(wxT("Saving OTMM map...")); bool ret = saveMap(map, f, identifier, showdialog); if(showdialog) gui.DestroyLoadBar(); return ret; }
bool CreatureDatabase::saveToXML(const FileName& filename) { pugi::xml_document doc; pugi::xml_node decl = doc.prepend_child(pugi::node_declaration); decl.append_attribute("version") = "1.0"; pugi::xml_node creatureNodes = doc.append_child("creatures"); for (const auto& creatureEntry : creature_map) { CreatureType* creatureType = creatureEntry.second; if (!creatureType->standard) { pugi::xml_node creatureNode = creatureNodes.append_child("creature"); creatureNode.append_attribute("name") = creatureType->name.c_str(); creatureNode.append_attribute("type") = creatureType->isNpc ? "npc" : "monster"; const Outfit& outfit = creatureType->outfit; creatureNode.append_attribute("looktype") = outfit.lookType; creatureNode.append_attribute("lookitem") = outfit.lookItem; creatureNode.append_attribute("lookaddon") = outfit.lookAddon; creatureNode.append_attribute("lookhead") = outfit.lookHead; creatureNode.append_attribute("lookbody") = outfit.lookBody; creatureNode.append_attribute("looklegs") = outfit.lookLegs; creatureNode.append_attribute("lookfeet") = outfit.lookFeet; } } return doc.save_file(filename.GetFullPath().mb_str(), "\t", pugi::format_default, pugi::encoding_utf8); }
bool CreatureDatabase::loadFromXML(const FileName& filename, bool standard, wxString& error, wxArrayString& warnings) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(filename.GetFullPath().mb_str()); if (!result) { error = wxT("Couldn't open file \"") + filename.GetFullName() + wxT("\", invalid format?"); return false; } pugi::xml_node node = doc.child("creatures"); if (!node) { error = wxT("Invalid file signature, this file is not a valid creatures file."); return false; } for (pugi::xml_node creatureNode = node.first_child(); creatureNode; creatureNode = creatureNode.next_sibling()) { if (as_lower_str(creatureNode.name()) != "creature") { continue; } CreatureType* creatureType = CreatureType::loadFromXML(creatureNode, warnings); if (creatureType) { creatureType->standard = standard; if ((*this)[creatureType->name]) { warnings.push_back(wxT("Duplicate creature type name \"") + wxstr(creatureType->name) + wxT("\"! Discarding...")); delete creatureType; } else { creature_map[as_lower_str(creatureType->name)] = creatureType; } } } return true; }
ClientVersionID IOMapOTMM::getVersionInfo(const FileName& filename) { wxString wpath = filename.GetFullPath(); DiskNodeFileReadHandle f((const char*)wpath.mb_str(wxConvUTF8)); if(f.isOk() == false) { return CLIENT_VERSION_NONE; } BinaryNode* root = f.getRootNode(); if(!root) {return CLIENT_VERSION_NONE;} root->skip(1); // Skip the type byte uint16_t u16; uint32_t u32; if(!root->getU32(u32) || u32 != 1) { // Version return CLIENT_VERSION_NONE; } root->getU16(u16); root->getU16(u16); root->getU32(u32); if(root->getU32(u32)) { // OTB minor version return ClientVersionID(u32); } return CLIENT_VERSION_NONE; }
bool IOMapOTMM::loadMap(Map& map, const FileName& identifier, bool showdialog) { if(showdialog) gui.CreateLoadBar(wxT("Loading OTMM map...")); DiskNodeFileReadHandle f(nstr(identifier.GetFullPath())); if(f.isOk() == false) { error(wxT("Couldn't open file for reading\nThe error reported was: ") + wxstr(f.getErrorMessage())); return false; } bool ret = loadMap(map, f, identifier, showdialog); if(showdialog) gui.DestroyLoadBar(); return ret; }
bool Materials::loadMaterials(const FileName& identifier, wxString& error, wxArrayString& warnings) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(identifier.GetFullPath().mb_str()); if(!result) { warnings.push_back("Could not open " + identifier.GetFullName() + " (file not found or syntax error)"); return false; } pugi::xml_node node = doc.child("materials"); if(!node) { warnings.push_back(identifier.GetFullName() + ": Invalid rootheader."); return false; } unserializeMaterials(identifier, node, error, warnings); return true; }
bool ItemDatabase::loadFromGameXml(const FileName& identifier, wxString& error, wxArrayString& warnings) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(identifier.GetFullPath().mb_str()); if(!result) { error = wxT("Could not load items.xml (Syntax error?)"); return false; } pugi::xml_node node = doc.child("items"); if(!node) { error = wxT("items.xml, invalid root node."); return false; } for(pugi::xml_node itemNode = node.first_child(); itemNode; itemNode = itemNode.next_sibling()) { if(as_lower_str(itemNode.name()) != "item") { continue; } int32_t fromId = pugi::cast<int32_t>(itemNode.attribute("fromid").value()); int32_t toId = pugi::cast<int32_t>(itemNode.attribute("toid").value()); if(pugi::xml_attribute attribute = itemNode.attribute("id")) { fromId = toId = pugi::cast<int32_t>(attribute.value()); } if(fromId == 0 || toId == 0) { error = wxT("Could not read item id from item node."); return false; } for(int32_t id = fromId; id <= toId; ++id) { if(!loadItemFromGameXml(itemNode, id)) { return false; } } } return true; }
bool Materials::loadMaterials(const FileName& identifier, wxString& error, wxArrayString& warnings) { xmlDocPtr doc = xmlParseFile(identifier.GetFullPath().mb_str()); if(doc) { xmlNodePtr root = xmlDocGetRootElement(doc); if(xmlStrcmp(root->name,(const xmlChar*)"materials") != 0) { xmlFreeDoc(doc); error = wxT("materials.xml: Invalid root header"); return false; } unserializeMaterials(identifier, root, error, warnings); } else { error = wxT("Couldn't open materials.xml (file not found or syntax error)"); return false; } return true; }
bool GraphicManager::loadSpriteData(const FileName& datafile, wxString& error, wxArrayString& warnings) { FileReadHandle fh(nstr(datafile.GetFullPath())); if(!fh.isOk()) { error = "Failed to open file for reading"; return false; } #define safe_get(func, ...) do {\ if(!fh.get##func(__VA_ARGS__)) {\ error = wxstr(fh.getErrorMessage()); \ return false; \ } \ } while(false) uint32_t sprSignature; safe_get(U32, sprSignature); uint32_t total_pics = 0; if(is_extended) { safe_get(U32, total_pics); } else { uint16_t u16 = 0; safe_get(U16, u16); total_pics = u16; } if(!g_settings.getInteger(Config::USE_MEMCACHED_SPRITES)) { spritefile = nstr(datafile.GetFullPath()); unloaded = false; return true; } std::vector<uint32_t> sprite_indexes; for(uint32_t i = 0; i < total_pics; ++i) { uint32_t index; safe_get(U32, index); sprite_indexes.push_back(index); } // Now read individual sprites int id = 1; for(std::vector<uint32_t>::iterator sprite_iter = sprite_indexes.begin(); sprite_iter != sprite_indexes.end(); ++sprite_iter, ++id) { uint32_t index = *sprite_iter + 3; fh.seek(index); uint16_t size; safe_get(U16, size); ImageMap::iterator it = image_space.find(id); if(it != image_space.end()) { GameSprite::NormalImage* spr = dynamic_cast<GameSprite::NormalImage*>(it->second); if(spr && size > 0) { if(spr->size > 0) { wxString ss; ss << "items.spr: Duplicate GameSprite id " << id; warnings.push_back(ss); fh.seekRelative(size); } else { spr->id = id; spr->size = size; spr->dump = newd uint8_t[size]; if(!fh.getRAW(spr->dump, size)) { error = wxstr(fh.getErrorMessage()); \ return false; } } } } else { fh.seekRelative(size); } } #undef safe_get unloaded = false; return true; }
bool GraphicManager::loadSpriteMetadata(const FileName& datafile, wxString& error, wxArrayString& warnings) { // items.otb has most of the info we need. This only loads the GameSprite metadata FileReadHandle file(nstr(datafile.GetFullPath())); if(!file.isOk()) { error += "Failed to open " + datafile.GetFullPath() + " for reading\nThe error reported was:" + wxstr(file.getErrorMessage()); return false; } uint16_t effect_count, distance_count; uint32_t datSignature; file.getU32(datSignature); //get max id file.getU16(item_count); file.getU16(creature_count); file.getU16(effect_count); file.getU16(distance_count); uint32_t minclientID = 100; // tibia.dat start with id 100 // We don't load distance/effects, if we would, just add effect_count & distance_count here uint32_t maxclientID = item_count + creature_count; dat_format = client_version->getDatFormatForSignature(datSignature); if(!otfi_found) { is_extended = dat_format >= DAT_FORMAT_96; has_frame_durations = dat_format >= DAT_FORMAT_1050; has_frame_groups = dat_format >= DAT_FORMAT_1057; } uint16_t id = minclientID; // loop through all ItemDatabase until we reach the end of file while(id <= maxclientID) { GameSprite* sType = newd GameSprite(); sprite_space[id] = sType; sType->id = id; // Load the sprite flags if(!loadSpriteMetadataFlags(file, sType, error, warnings)) { wxString msg; msg << "Failed to load flags for sprite " << sType->id; warnings.push_back(msg); } // Reads the group count uint8_t group_count = 1; if(has_frame_groups && id > item_count) { file.getU8(group_count); } for(uint32_t k = 0; k < group_count; ++k) { // Skipping the group type if(has_frame_groups && id > item_count) { file.skip(1); } // Size and GameSprite data file.getByte(sType->width); file.getByte(sType->height); // Skipping the exact size if((sType->width > 1) || (sType->height > 1)){ file.skip(1); } file.getU8(sType->layers); // Number of blendframes (some sprites consist of several merged sprites) file.getU8(sType->pattern_x); file.getU8(sType->pattern_y); if(dat_format <= DAT_FORMAT_74) sType->pattern_z = 1; else file.getU8(sType->pattern_z); file.getU8(sType->frames); // Length of animation if(sType->frames > 1) { uint8_t async = 0; int loop_count = 0; int8_t start_frame = 0; if(has_frame_durations) { file.getByte(async); file.get32(loop_count); file.getSByte(start_frame); } sType->animator = newd Animator(sType->frames, start_frame, loop_count, async == 1); if(has_frame_durations) { for(int i = 0; i < sType->frames; i++) { uint32_t min; uint32_t max; file.getU32(min); file.getU32(max); FrameDuration* frame_duration = sType->animator->getFrameDuration(i); frame_duration->setValues(int(min), int(max)); } sType->animator->reset(); } } sType->numsprites = (int)sType->width * (int)sType->height * (int)sType->layers * (int)sType->pattern_x * (int)sType->pattern_y * sType->pattern_z * (int)sType->frames; // Read the sprite ids for(uint32_t i = 0; i < sType->numsprites; ++i) { uint32_t sprite_id; if(is_extended) { file.getU32(sprite_id); } else { uint16_t u16 = 0; file.getU16(u16); sprite_id = u16; } if(image_space[sprite_id] == nullptr) { GameSprite::NormalImage* img = newd GameSprite::NormalImage(); img->id = sprite_id; image_space[sprite_id] = img; } sType->spriteList.push_back(static_cast<GameSprite::NormalImage*>(image_space[sprite_id])); } } ++id; } return true; }
bool CreatureDatabase::importXMLFromOT(const FileName& filename, wxString& error, wxArrayString& warnings) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(filename.GetFullPath().mb_str()); if (!result) { error = wxT("Couldn't open file \"") + filename.GetFullName() + wxT("\", invalid format?"); return false; } pugi::xml_node node; if ((node = doc.child("monsters"))) { for (pugi::xml_node monsterNode = node.first_child(); monsterNode; monsterNode = monsterNode.next_sibling()) { if (as_lower_str(monsterNode.name()) != "monster") { continue; } pugi::xml_attribute attribute; if (!(attribute = monsterNode.attribute("file"))) { continue; } FileName monsterFile(filename); monsterFile.SetFullName(wxString(attribute.as_string(), wxConvUTF8)); pugi::xml_document monsterDoc; pugi::xml_parse_result monsterResult = monsterDoc.load_file(monsterFile.GetFullPath().mb_str()); if (!monsterResult) { continue; } CreatureType* creatureType = CreatureType::loadFromOTXML(monsterFile, monsterDoc, warnings); if (creatureType) { CreatureType* current = (*this)[creatureType->name]; if (current) { *current = *creatureType; delete creatureType; } else { creature_map[as_lower_str(creatureType->name)] = creatureType; Tileset* tileSet = nullptr; if (creatureType->isNpc) { tileSet = materials.tilesets["NPCs"]; } else { tileSet = materials.tilesets["Others"]; } ASSERT(tileSet != nullptr); Brush* brush = newd CreatureBrush(creatureType); brushes.addBrush(brush); TilesetCategory* tileSetCategory = tileSet->getCategory(TILESET_CREATURE); tileSetCategory->brushlist.push_back(brush); } } } } else if ((node = doc.child("monster")) || (node = doc.child("npc"))) { CreatureType* creatureType = CreatureType::loadFromOTXML(filename, doc, warnings); if (creatureType) { CreatureType* current = (*this)[creatureType->name]; if (current) { *current = *creatureType; delete creatureType; } else { creature_map[as_lower_str(creatureType->name)] = creatureType; Tileset* tileSet = nullptr; if (creatureType->isNpc) { tileSet = materials.tilesets["NPCs"]; } else { tileSet = materials.tilesets["Others"]; } ASSERT(tileSet != nullptr); Brush* brush = newd CreatureBrush(creatureType); brushes.addBrush(brush); TilesetCategory* tileSetCategory = tileSet->getCategory(TILESET_CREATURE); tileSetCategory->brushlist.push_back(brush); } } } else { error = wxT("This is not valid OT npc/monster data file."); return false; } return true; }
bool Materials::loadExtensions(FileName directoryName, wxString& error, wxArrayString& warnings) { directoryName.Mkdir(0755, wxPATH_MKDIR_FULL); // Create if it doesn't exist wxDir ext_dir(directoryName.GetPath()); if(!ext_dir.IsOpened()) { error = "Could not open extensions directory."; return false; } wxString filename; if(!ext_dir.GetFirst(&filename)) { // No extensions found return true; } StringVector clientVersions; do { FileName fn; fn.SetPath(directoryName.GetPath()); fn.SetFullName(filename); if(fn.GetExt() != "xml") { continue; } pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(fn.GetFullPath().mb_str()); if(!result) { warnings.push_back("Could not open " + filename + " (file not found or syntax error)"); continue; } pugi::xml_node extensionNode = doc.child("materialsextension"); if(!extensionNode) { warnings.push_back(filename + ": Invalid rootheader."); continue; } pugi::xml_attribute attribute; if(!(attribute = extensionNode.attribute("name"))) { warnings.push_back(filename + ": Couldn't read extension name."); continue; } const std::string& extensionName = attribute.as_string(); if(!(attribute = extensionNode.attribute("author"))) { warnings.push_back(filename + ": Couldn't read extension name."); continue; } const std::string& extensionAuthor = attribute.as_string(); if(!(attribute = extensionNode.attribute("description"))) { warnings.push_back(filename + ": Couldn't read extension name."); continue; } const std::string& extensionDescription = attribute.as_string(); if(extensionName.empty() || extensionAuthor.empty() || extensionDescription.empty()) { warnings.push_back(filename + ": Couldn't read extension attributes (name, author, description)."); continue; } std::string extensionUrl = extensionNode.attribute("url").as_string(); extensionUrl.erase(std::remove(extensionUrl.begin(), extensionUrl.end(), '\'')); std::string extensionAuthorLink = extensionNode.attribute("authorurl").as_string(); extensionAuthorLink.erase(std::remove(extensionAuthorLink.begin(), extensionAuthorLink.end(), '\'')); MaterialsExtension* materialExtension = newd MaterialsExtension(extensionName, extensionAuthor, extensionDescription); materialExtension->url = extensionUrl; materialExtension->author_url = extensionAuthorLink; if((attribute = extensionNode.attribute("client"))) { clientVersions.clear(); const std::string& extensionClientString = attribute.as_string(); size_t lastPosition = 0; size_t position = extensionClientString.find(';'); while(position != std::string::npos) { clientVersions.push_back(extensionClientString.substr(lastPosition, position - lastPosition)); lastPosition = position + 1; position = extensionClientString.find(';', lastPosition); } clientVersions.push_back(extensionClientString.substr(lastPosition)); for(const std::string& version : clientVersions) { materialExtension->addVersion(version); } std::sort(materialExtension->version_list.begin(), materialExtension->version_list.end(), VersionComparisonPredicate); auto duplicate = std::unique(materialExtension->version_list.begin(), materialExtension->version_list.end()); while(duplicate != materialExtension->version_list.end()) { materialExtension->version_list.erase(duplicate); duplicate = std::unique(materialExtension->version_list.begin(), materialExtension->version_list.end()); } } else { warnings.push_back(filename + ": Extension is not available for any version."); } extensions.push_back(materialExtension); if(materialExtension->isForVersion(g_gui.GetCurrentVersionID())) { unserializeMaterials(filename, extensionNode, error, warnings); } } while(ext_dir.GetNext(&filename)); return true; }
bool Materials::loadExtensions(FileName directoryName, wxString& error, wxArrayString& warnings) { directoryName.Mkdir(0755, wxPATH_MKDIR_FULL); // Create if it doesn't exist wxDir ext_dir(directoryName.GetPath()); if(ext_dir.IsOpened() == false) { error = wxT("Could not open extensions directory."); return false; } wxString filename; if(!ext_dir.GetFirst(&filename)) { // No extensions found return true; } do { FileName fn; fn.SetPath(directoryName.GetPath()); fn.SetFullName(filename); if(fn.GetExt() != wxT("xml")) continue; xmlDocPtr doc = xmlParseFile(fn.GetFullPath().mb_str()); if(doc) { xmlNodePtr root = xmlDocGetRootElement(doc); if(xmlStrcmp(root->name,(const xmlChar*)"materialsextension") != 0){ xmlFreeDoc(doc); warnings.push_back(filename + wxT(": Invalid rootheader.")); continue; } std::string ext_name, ext_url, ext_author, ext_author_link, ext_desc, ext_client_str; StringVector clientVersions; if( !readXMLValue(root, "name", ext_name) || !readXMLValue(root, "author", ext_author) || !readXMLValue(root, "description", ext_desc)) { warnings.push_back(filename + wxT(": Couldn't read extension attributes (name, author, description).")); continue; } readXMLValue(root, "url", ext_url); ext_url.erase(std::remove(ext_url.begin(), ext_url.end(), '\''), ext_url.end()); readXMLValue(root, "authorurl", ext_author_link); ext_author_link.erase(std::remove(ext_author_link.begin(), ext_author_link.end(), '\''), ext_author_link.end()); MaterialsExtension* me = newd MaterialsExtension(ext_name, ext_author, ext_desc); me->url = ext_url; me->author_url = ext_author_link; if(readXMLValue(root, "client", ext_client_str)) { size_t last_pos = std::numeric_limits<size_t>::max(); size_t pos; do { size_t to_pos = (last_pos == std::numeric_limits<size_t>::max()? 0 : last_pos+1); pos = ext_client_str.find(';', to_pos); if(size_t(pos) != std::string::npos) { clientVersions.push_back(ext_client_str.substr(to_pos, pos-(to_pos))); last_pos = pos; } else { clientVersions.push_back(ext_client_str.substr(to_pos)); break; } } while(true); for(StringVector::iterator iter = clientVersions.begin(); iter != clientVersions.end(); ++iter) { me->addVersion(*iter); } std::sort(me->version_list.begin(), me->version_list.end(), VersionComparisonPredicate); me->version_list.erase(std::unique(me->version_list.begin(), me->version_list.end()), me->version_list.end()); } else { warnings.push_back(filename + wxT(": Extension is not available for any version.")); } extensions.push_back(me); if(me->isForVersion(gui.GetCurrentVersionID())) { unserializeMaterials(filename, root, error, warnings); } } else { warnings.push_back(wxT("Could not open ") + filename + wxT(" (file not found or syntax error)")); continue; } } while(ext_dir.GetNext(&filename)); return true; }
bool Map::exportMinimap(FileName filename, int floor /*= 7*/, bool displaydialog) { uint8_t* pic = nullptr; try { int min_x = 0x10000, min_y = 0x10000; int max_x = 0x00000, max_y = 0x00000; if(size() == 0) return true; for(MapIterator mit = begin(); mit != end(); ++mit) { if((*mit)->get() == nullptr || (*mit)->empty()) continue; Position pos = (*mit)->getPosition(); if(pos.x < min_x) min_x = pos.x; if(pos.y < min_y) min_y = pos.y; if(pos.x > max_x) max_x = pos.x; if(pos.y > max_y) max_y = pos.y; } int minimap_width = max_x - min_x+1; int minimap_height = max_y - min_y+1; pic = newd uint8_t[minimap_width*minimap_height]; // 1 byte per pixel memset(pic, 0, minimap_width*minimap_height); int tiles_iterated = 0; for(MapIterator mit = begin(); mit != end(); ++mit) { Tile* tile = (*mit)->get(); ++tiles_iterated; if(tiles_iterated % 8192 == 0 && displaydialog) gui.SetLoadDone(int(tiles_iterated / double(tilecount) * 90.0)); if(tile->empty() || tile->getZ() != floor) continue; //std::cout << "Pixel : " << (tile->getY() - min_y) * width + (tile->getX() - min_x) << std::endl; uint32_t pixelpos = (tile->getY() - min_y) * minimap_width + (tile->getX() - min_x); uint8_t& pixel = pic[pixelpos]; for(ItemVector::const_reverse_iterator item_iter = tile->items.rbegin(); item_iter != tile->items.rend(); ++item_iter) { if((*item_iter)->getMiniMapColor()) { pixel = (*item_iter)->getMiniMapColor(); break; } } if(pixel == 0) // check ground too if(tile->hasGround()) pixel = tile->ground->getMiniMapColor(); } // Create a file for writing FileWriteHandle fh(nstr(filename.GetFullPath())); if(!fh.isOpen()) { delete[] pic; return false; } // Store the magic number fh.addRAW("BM"); // Store the file size // We need to predict how large it will be uint32_t file_size = 14 // header +40 // image data header +256*4 // color palette +((minimap_width + 3) / 4 * 4) * height; // pixels fh.addU32(file_size); // Two values reserved, must always be 0. fh.addU16(0); fh.addU16(0); // Bitmapdata offset fh.addU32(14 + 40 + 256*4); // Header size fh.addU32(40); // Header width/height fh.addU32(minimap_width); fh.addU32(minimap_height); // Color planes fh.addU16(1); // bits per pixel, OT map format is 8 fh.addU16(8); // compression type, 0 is no compression fh.addU32(0); // image size, 0 is valid if we use no compression fh.addU32(0); // horizontal/vertical resolution in pixels / meter fh.addU32(4000); fh.addU32(4000); // Number of colors fh.addU32(256); // Important colors, 0 is all fh.addU32(0); // Write the color palette for(int i = 0; i < 256; ++i) fh.addU32(uint32_t(minimap_color[i])); // Bitmap width must be divisible by four, calculate how much padding we need int padding = ((minimap_width & 3) != 0? 4-(minimap_width & 3) : 0); // Bitmap rows are saved in reverse order for(int y = minimap_height-1; y >= 0; --y) { fh.addRAW(pic + y*minimap_width, minimap_width); for(int i = 0; i < padding; ++i) { fh.addU8(0); } if(y % 100 == 0 && displaydialog) { gui.SetLoadDone(90 + int((minimap_height-y) / double(minimap_height) * 10.0)); } } delete[] pic; //fclose(file); fh.close(); } catch(...) { delete[] pic; } return true; }