From 02faad60b00b27217fcb03599c1cde493cb1b015 Mon Sep 17 00:00:00 2001 From: LittleYang0531 Date: Thu, 26 Jan 2023 13:55:24 +0000 Subject: [PATCH] Release v1.1.1 --- README.md | 63 +++++++++++- README_en.md | 65 +++++++++++- main.cpp | 46 +++++++-- encrypt.h => modules/encrypt.h | 0 modules/export.h | 174 +++++++++++++++++++++++++++++++++ html.h => modules/html.h | 0 httpd.h => modules/httpd.h | 0 modules/import.h | 54 ++++++++++ json.h => modules/json.h | 0 mysqli.h => modules/mysqli.h | 8 +- web/index.h | 6 ++ 11 files changed, 404 insertions(+), 12 deletions(-) rename encrypt.h => modules/encrypt.h (100%) create mode 100644 modules/export.h rename html.h => modules/html.h (100%) rename httpd.h => modules/httpd.h (100%) create mode 100644 modules/import.h rename json.h => modules/json.h (100%) rename mysqli.h => modules/mysqli.h (86%) diff --git a/README.md b/README.md index 00ae209..92806b7 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,16 @@ - [Sonolus Website](https://sonolus.com/) - [Sonolus Wiki](https://wiki.sonolus.com/) +## 安装 + +我们提供对 Windows 用户的安装包,内含 MySQL v5.7.37,下载地址: [Latest Release](https://github.com/LittleYang0531/sonolus-server-cpp/releases/latest) + +安装后第一次使用需要**以管理员身份运行**启动菜单里的 `Setup Sonolus Database` 程序,之后就可以启动 `Sonolus Server for Windows` 来启动服务。 + +导入数据需要运行 `Import Sonolus Data` 程序,按照提示输入相关信息即可。 + +我们并未提供对 Linux 用户的安装包 ~~(都用 Linux 了相信或多或少还是有一点使用终端的基础了吧)~~,请自行参照下方教程进行构建。 + ## 构建 ### 下载依赖 @@ -36,10 +46,34 @@ sudo apt install g++ libjsoncpp-dev libmysqlclient-dev libssl-dev -y g++ main.cpp -o main -lpthread -lcrypto -lssl -ljsoncpp -lmysqlclient -g ``` +### 导入数据 + +以 bandori 的数据包 `bandori.bin` 为例。 + +```bash +./main import bandori.bin +``` + +我们提供各官方引擎的数据包下载: [Data Packages for v1.1.1+](https://github.com/LittleYang0531/sonolus-server-cpp/releases/tag/data) + ### 运行 ```bash -./main +./main serve +``` + +### 导出数据 + +以导出名为 `bandori` 的引擎为例 + +```bash +./main export engine bandori bandori.bin +``` + +以导出名为 `bandori-#1` 的关卡为例 + +```bash +./main export level "bandori-#1" bandori1.bin ``` ## 配置信息 @@ -100,6 +134,24 @@ g++ main.cpp -o main -lpthread -lcrypto -lssl -ljsoncpp -lmysqlclient -g - `GET /particles/{name}`: 显示名为 {name} 的粒子效果信息。 - `GET /engines/{name}`: 显示名为 {name} 的引擎信息。 + + ## 提示 ### 2023.1.21 @@ -124,6 +176,15 @@ g++ main.cpp -o main -lpthread -lcrypto -lssl -ljsoncpp -lmysqlclient -g ## 更新日志 +### v1.1.1 2023.1.26 + +1. 修复当没有条目时主页面显示 `{{html.xxx}}` 的错误。 +2. 修复当数据库中存在 NULL 数据时引起程序退出错误。 +3. 新增数据导入功能,并提供 bandori, pjsekai, llsif, taiko, voez, deemo 的数据包。 +4. 新增数据导出功能,支持引擎和关卡的导出。 +5. 修改服务器启动方式为 `./main serve`。 +6. 新增应用帮助信息。 + ### v1.1.0 2023.1.24 1. 修复 Windows 由于缺少 __int64_t 导致的编译错误。 diff --git a/README_en.md b/README_en.md index c591642..15fa579 100644 --- a/README_en.md +++ b/README_en.md @@ -13,6 +13,16 @@ Use httpd core developed by myself, which also support you to build HTTPS server - [Sonolus Website](https://sonolus.com/) - [Sonolus Wiki](https://wiki.sonolus.com/) +## Install + +We provide an installation package for Windows users, including MySQL v5.7.37. Download link: [Latest Release](https://github.com/LittleYang0531/sonolus-server-cpp/releases/latest). + +The first use after installation requires to run the `Setup Sonolus Database` program in the startup menu **as an administrator**, and then you can run the `Sonolus Server for Windows` to start the service. + +To import data, you need to run the `Import Sonolus Data` program and enter relevant information according to the prompts. + +We have not provided the installation package for Linux users. Please refer to the following tutorial to build it. + ## Building ### Install Dependencies @@ -36,12 +46,36 @@ Just enter MySQL Server and import `data.sql`. g++ main.cpp -o main -lpthread -lcrypto -lssl -ljsoncpp -lmysqlclient -g ``` +### Import Data + +Take Bandori's data package `bandori.bin` as an example. + +```bash +./main import bandori.bin +``` + +We provide data package download of all official engines: [Data Packages for v1.1.1+](https://github.com/LittleYang0531/sonolus-server-cpp/releases/tag/data) + ### Run ```bash ./main ``` +### Export Data + +Take the export engine named `bandori` as an example + +```bash +./main export engine bandori bandori.bin +``` + +Take the export level named `bandori-#1` as an example + +```bash +./main export level "bandori-#1" bandori1.bin +``` + ## Configuration - `mysql.hostname`: MySQL Server listen host. @@ -100,6 +134,24 @@ g++ main.cpp -o main -lpthread -lcrypto -lssl -ljsoncpp -lmysqlclient -g - `GET /particles/{name}`: Show information of particle named {name}. - `GET /engines/{name}`: Show information of engine named {name}. + + ## Tips ### 2023.1.21 @@ -124,13 +176,22 @@ These days, the official wiki website has added an endpoint `/sonolus/authentica ## Upload Log +### v1.1.1 2023.1.26 + +1. Fixed the error that the main page displays `{{html.xxx}}` when there are no entries. +2. Fixed the program error caused by NULL data in the database. +3. Added data import function and provided data packages of bandori, pjsekai, llsif, taiko, voez and deemo. +4. Added data export function and provided exporting engines and levels. +5. Modified the server startup mode to `./main serve` +6. Added application help information. + ### v1.1.0 2023.1.24 1. Fixed Windows version will compiled error caused by missing __int64_t. 2. Fixed the error that the feedbacks of all list apis is the same. 3. Add the recommended display function, which displays 5 works of the same author by default. -4. Add a new homepage and use native js to write it after the official project. -5. Add the detail page of each component, and use native js to write after the official project. +4. Add a new homepage and use native javascript to write it after the official project. +5. Add the detail page of each component, and use native javascript to write after the official project. 6. Add the website internationalization function. ### v1.0.2-2 2023.1.21 diff --git a/main.cpp b/main.cpp index 5b788f6..fb2a689 100755 --- a/main.cpp +++ b/main.cpp @@ -4,19 +4,32 @@ Json::Value appConfig; Json::Value i18n, i18n_raw; +int exportLevelId[] = {}; +int exportSkinId[] = {}; +int exportBackgroundId[] = {}; +int exportEffectId[] = {}; +int exportParticleId[] = {}; +int exportEngineId[] = {}; -#include"httpd.h" -#include"html.h" -#include"mysqli.h" -#include"json.h" -#include"encrypt.h" +#include"modules/import.h" #include"items/Items.h" #include"sonolus/sonolus.h" #include"api/import.h" #include"web/import.h" +#include"modules/export.h" using namespace std; -int main() { +void invalidUsage(char** argv) { + cerr << "Usage: " << argv[0] << " [command]" << endl; + cerr << "commands: " << endl; + cerr << " help: " << argv[0] << " help" << endl; + cerr << " serve: " << argv[0] << " serve" << endl; + cerr << " import: " << argv[0] << " import [file]" << endl; + cerr << " export: " << argv[0] << " export [level/engine] [name] [file]" << endl; + exit(0); +} + +int main(int argc, char** argv) { app.setopt(LOG_TARGET_TYPE, LOG_TARGET_CONSOLE); app.setopt(OPEN_DEBUG, true); @@ -41,6 +54,27 @@ int main() { exit(3); } loadDefaultVariable(); + + if (argc < 2) invalidUsage(argv); + if (string(argv[1]) == "import") { + if (argc < 3) invalidUsage(argv); + import(argv[2]); + return 0; + } + else if (string(argv[1]) == "export") { + if (argc < 5) invalidUsage(argv); + if (string(argv[2]) == "level") { + exportLevel(argv[3]); + exportData(argv[4]); + return 0; + } else if (string(argv[2]) == "engine") { + exportEngine(argv[3]); + exportData(argv[4]); + return 0; + } else invalidUsage(argv); + } else if (string(argv[1]) == "help") invalidUsage(argv); + else if (string(argv[1]) != "serve") invalidUsage(argv); + mysql = mysqli_connect(appConfig["mysql.hostname"].asString().c_str(), appConfig["mysql.username"].asString().c_str(), appConfig["mysql.password"].asString().c_str(), appConfig["mysql.database"].asString().c_str(), appConfig["mysql.port"].asInt()); app.setopt(HTTP_ENABLE_SSL, appConfig["server.enableSSL"].asBool()); diff --git a/encrypt.h b/modules/encrypt.h similarity index 100% rename from encrypt.h rename to modules/encrypt.h diff --git a/modules/export.h b/modules/export.h new file mode 100644 index 0000000..ad1afe4 --- /dev/null +++ b/modules/export.h @@ -0,0 +1,174 @@ +using namespace std; + +set fileSha; +stringstream sqlbuffer; + +int levelNum = sizeof(exportLevelId) / sizeof(int); +int skinNum = sizeof(exportSkinId) / sizeof(int); +int backgroundNum = sizeof(exportBackgroundId) / sizeof(int); +int effectNum = sizeof(exportEffectId) / sizeof(int); +int particleNum = sizeof(exportParticleId) / sizeof(int); +int engineNum = sizeof(exportEngineId) / sizeof(int); + +void exportSkin(int id) { + SkinItem skin = skinList("id = " + to_string(id)).items[0]; + sqlbuffer << "SELECT count(*) INTO @id FROM Skin; "; + sqlbuffer << "INSERT INTO Skin (id, name, version, title, subtitle, author, thumbnail, data, texture) SELECT "; + sqlbuffer << "(@id + 1), \"" << skin.name << "\", " << skin.version << ", \"" << skin.title << "\", "; + sqlbuffer << "\"" << skin.subtitle << "\", \"" << skin.author << "\", \"" << skin.thumbnail.hash << "\", "; + sqlbuffer << "\"" << skin.data.hash << "\", \"" << skin.texture.hash << "\" FROM DUAL WHERE NOT EXISTS "; + sqlbuffer << "(SELECT id FROM Skin where name = \"" << skin.name << "\"); "; + fileSha.insert(skin.thumbnail.hash); + fileSha.insert(skin.data.hash); + fileSha.insert(skin.texture.hash); +} + +void exportBackground(int id) { + BackgroundItem background = backgroundList("id = " + to_string(id)).items[0]; + sqlbuffer << "SELECT count(*) INTO @id FROM Background; "; + sqlbuffer << "INSERT INTO Background (id, name, version, title, subtitle, author, thumbnail, data, image, configuration) SELECT "; + sqlbuffer << "(@id + 1), \"" << background.name << "\", " << background.version << ", \"" << background.title << "\", "; + sqlbuffer << "\"" << background.subtitle << "\", \"" << background.author << "\", \"" << background.thumbnail.hash << "\", "; + sqlbuffer << "\"" << background.data.hash << "\", \"" << background.image.hash << "\", \"" << background.configuration.hash << "\" FROM DUAL WHERE NOT EXISTS "; + sqlbuffer << "(SELECT id FROM Background where name = \"" << background.name << "\"); "; + fileSha.insert(background.thumbnail.hash); + fileSha.insert(background.data.hash); + fileSha.insert(background.image.hash); + fileSha.insert(background.configuration.hash); +} + +void exportEffect(int id) { + EffectItem effect = effectList("id = " + to_string(id)).items[0]; + sqlbuffer << "SELECT count(*) INTO @id FROM Effect; "; + sqlbuffer << "INSERT INTO Effect (id, name, version, title, subtitle, author, thumbnail, data, audio) SELECT "; + sqlbuffer << "(@id + 1), \"" << effect.name << "\", " << effect.version << ", \"" << effect.title << "\", "; + sqlbuffer << "\"" << effect.subtitle << "\", \"" << effect.author << "\", \"" << effect.thumbnail.hash << "\", "; + sqlbuffer << "\"" << effect.data.hash << "\", \"" << effect.audio.hash << "\" FROM DUAL WHERE NOT EXISTS "; + sqlbuffer << "(SELECT id FROM Effect where name = \"" << effect.name << "\"); "; + fileSha.insert(effect.thumbnail.hash); + fileSha.insert(effect.data.hash); + fileSha.insert(effect.audio.hash); +} + +void exportParticle(int id) { + ParticleItem particle = particleList("id = " + to_string(id)).items[0]; + sqlbuffer << "SELECT count(*) INTO @id FROM Particle; "; + sqlbuffer << "INSERT INTO Particle (id, name, version, title, subtitle, author, thumbnail, data, texture) SELECT "; + sqlbuffer << "(@id + 1), \"" << particle.name << "\", " << particle.version << ", \"" << particle.title << "\", "; + sqlbuffer << "\"" << particle.subtitle << "\", \"" << particle.author << "\", \"" << particle.thumbnail.hash << "\", "; + sqlbuffer << "\"" << particle.data.hash << "\", \"" << particle.texture.hash << "\" FROM DUAL WHERE NOT EXISTS "; + sqlbuffer << "(SELECT id FROM Particle where name = \"" << particle.name << "\"); "; + fileSha.insert(particle.thumbnail.hash); + fileSha.insert(particle.data.hash); + fileSha.insert(particle.texture.hash); +} + +void exportEngine(string name) { + auto section = engineList("name = \"" + str_replace("\"", "\\\"", name) + "\""); + if (section.items.size() == 0) { + writeLog(LOG_LEVEL_ERROR, "No such engine exist."); + writeLog(LOG_LEVEL_DEBUG, "0 files were written."); + exit(3); + } + EngineItem engine = section.items[0]; + exportSkin(engine.skin.id); exportBackground(engine.background.id); + exportEffect(engine.effect.id); exportParticle(engine.particle.id); + sqlbuffer << "SELECT count(*) INTO @id FROM Engine; "; + sqlbuffer << "SELECT id INTO @skinId FROM Skin WHERE name = \"" << engine.skin.name << "\"; "; + sqlbuffer << "SELECT id INTO @backgroundId FROM Background WHERE name = \"" << engine.background.name << "\"; "; + sqlbuffer << "SELECT id INTO @effectId FROM Effect WHERE name = \"" << engine.effect.name << "\"; "; + sqlbuffer << "SELECT id INTO @particleId FROM Particle WHERE name = \"" << engine.particle.name << "\"; "; + sqlbuffer << "INSERT INTO Engine (id, name, version, title, subtitle, author, skin, background, effect, particle, thumbnail, data, configuration, rom) SELECT "; + sqlbuffer << "(@id + 1), \"" << engine.name << "\", " << engine.version << ", \"" << engine.title << "\", "; + sqlbuffer << "\"" << engine.subtitle << "\", \"" << engine.author << "\", @skinId, @backgroundId, @effectId, @particleId, "; + sqlbuffer << "\"" << engine.thumbnail.hash << "\", \"" << engine.data.hash << "\", \"" << engine.configuration.hash << "\", "; + sqlbuffer << "\"" << engine.rom.hash << "\" FROM DUAL WHERE NOT EXISTS "; + sqlbuffer << "(SELECT id FROM Engine where name = \"" << engine.name << "\"); "; + fileSha.insert(engine.thumbnail.hash); + fileSha.insert(engine.data.hash); + fileSha.insert(engine.configuration.hash); + fileSha.insert(engine.rom.hash); +} + +void exportLevel(string name) { + auto section = levelList("name = \"" + str_replace("\"", "\\\"", name) + "\""); + if (section.items.size() == 0) { + writeLog(LOG_LEVEL_ERROR, "No such level exist."); + writeLog(LOG_LEVEL_DEBUG, "0 files were written."); + exit(3); + } + LevelItem level = section.items[0]; + exportEngine(level.engine.name); + if (level.useSkin.useDefault == false) exportSkin(level.useSkin.item.id); + if (level.useBackground.useDefault == false) exportBackground(level.useBackground.item.id); + if (level.useEffect.useDefault == false) exportEffect(level.useEffect.item.id); + if (level.useParticle.useDefault == false) exportParticle(level.useParticle.item.id); + sqlbuffer << "SELECT count(*) INTO @id FROM Level; "; + string skinId = "0", backgroundId = "0", effectId = "0", particleId = "0"; + if (level.useSkin.useDefault == false) { + sqlbuffer << "SELECT id INTO @skinId FROM Skin WHERE name = \"" << level.useSkin.item.name << "\"; "; + skinId = "@skinId"; + } if (level.useBackground.useDefault == false) { + sqlbuffer << "SELECT id INTO @backgroundId FROM Background WHERE name = \"" << level.useBackground.item.name << "\"; "; + backgroundId = "@backgroundId"; + } if (level.useEffect.useDefault == false) { + sqlbuffer << "SELECT id INTO @effectId FROM Effect WHERE name = \"" << level.useEffect.item.name << "\"; "; + effectId = "@effectId"; + } if (level.useParticle.useDefault == false) { + sqlbuffer << "SELECT id INTO @particleId FROM Particle WHERE name = \"" << level.useParticle.item.name << "\"; "; + particleId = "@particleId"; + } sqlbuffer << "SELECT id INTO @engineId FROM Engine WHERE name = \"" << level.engine.name << "\"; "; + sqlbuffer << "INSERT INTO Level (id, name, version, rating, title, artists, author, engine, skin, background, effect, particle, cover, bgm, data, preview) SELECT "; + sqlbuffer << "(@id + 1), \"" << level.name << "\", " << level.version << ", " << level.rating << ", \"" << level.title << "\", "; + sqlbuffer << "\"" << level.artists << "\", \"" << level.author << "\", @engineId, " << skinId << ", " << backgroundId << ", " << effectId << ", " << particleId << ", "; + sqlbuffer << "\"" << level.cover.hash << "\", \"" << level.bgm.hash << "\", \"" << level.data.hash << "\", \"" << level.preview.hash << "\" FROM DUAL WHERE NOT EXISTS "; + sqlbuffer << "(SELECT id FROM Level where name = \"" << level.name << "\"); "; + fileSha.insert(level.cover.hash); + fileSha.insert(level.bgm.hash); + fileSha.insert(level.data.hash); + fileSha.insert(level.preview.hash); +} + +void exportData(const char* path) { + ofstream fout(path); + int fileNum = 0; + for (auto v : fileSha) if (v != "") fileNum++; + int filenum = fileNum; + vector x; for (int i = 0; i < 8; i++) x.push_back(fileNum % (1 << 8)), fileNum >>= 8; + reverse(x.begin(), x.end()); + char* ch = new char[8]; + for (int i = 0; i < 8; i++) ch[i] = x[i]; + fout.write(ch, 8); free(ch); + for (auto v : fileSha) { + if (v == "") continue; + char* sha = new char[20]; + for (int i = 0; i < 40; i += 2) { + char a = v[i], b = v[i + 1]; + int val = isdigit(a) ? a - '0' : a - 'a' + 10; + val <<= 4; + val += isdigit(b) ? b - '0' : b - 'a' + 10; + sha[i / 2] = val; + } + fout.write(sha, 20); free(sha); + + ifstream fin(("./data/" + v).c_str()); + fin.seekg(0, ios::end); + int len = fin.tellg(); int tmp = len; + vector x; for (int i = 0; i < 8; i++) x.push_back(len % (1 << 8)), len >>= 8; + reverse(x.begin(), x.end()); + char* ch = new char[8]; + for (int i = 0; i < 8; i++) ch[i] = x[i]; + fout.write(ch, 8); free(ch); + + len = tmp; + fin.seekg(0, ios::beg); + ch = new char[len]; + fin.read(ch, len); fin.close(); + fout.write(ch, len); free(ch); + writeLog(LOG_LEVEL_DEBUG, "Add file " + v + " into binary file."); + } + fout << sqlbuffer.str(); + fout.close(); + writeLog(LOG_LEVEL_INFO, "Export data successfully!"); + writeLog(LOG_LEVEL_DEBUG, to_string(filenum) + " files were written"); +} \ No newline at end of file diff --git a/html.h b/modules/html.h similarity index 100% rename from html.h rename to modules/html.h diff --git a/httpd.h b/modules/httpd.h similarity index 100% rename from httpd.h rename to modules/httpd.h diff --git a/modules/import.h b/modules/import.h new file mode 100644 index 0000000..4565784 --- /dev/null +++ b/modules/import.h @@ -0,0 +1,54 @@ +#include"httpd.h" +#include"html.h" +#include"mysqli.h" +#include"json.h" +#include"encrypt.h" + +using namespace std; + +void import(const char* path) { + ifstream fin(path); + if (!fin) { + writeLog(LOG_LEVEL_ERROR, "Couldn't open binary file!"); + writeLog(LOG_LEVEL_DEBUG, "0 files were written"); + writeLog(LOG_LEVEL_DEBUG, "0 raws were affected"); + exit(3); + } + fin.seekg(0, ios::end); + int len = fin.tellg(); + fin.seekg(0, ios::beg); + char* ch = new char[len]; + fin.read(ch, len); + int filenum = 0, pt = 0; + for (int i = 0; i < 8; i++) filenum <<= 8, filenum += (unsigned char)ch[pt++]; + for (int i = 1; i <= filenum; i++) { + int filesize = 0; + unsigned char* fileSha1 = new unsigned char[20]; + for (int j = 0; j < 20; j++) fileSha1[j] = ch[pt++]; + for (int j = 0; j < 8; j++) filesize <<= 8, filesize += (unsigned char)ch[pt++]; + char* filePointer = new char[filesize]; + for (int j = 0; j < filesize; j++) filePointer[j] = ch[pt++]; + unsigned char* fileSha1Real = sha1(filePointer, filesize); + for (int j = 0; j < 20; j++) { + if (fileSha1Real[j] != fileSha1[j]) { + writeLog(LOG_LEVEL_ERROR, "Invalid file sha1 hash."); + writeLog(LOG_LEVEL_DEBUG, to_string(i - 1) + " files were written."); + writeLog(LOG_LEVEL_DEBUG, "0 raws were affected."); + exit(3); + } + } + stringstream buffer; + for (int i = 0; i < 20; i++) + buffer << hex << setw(2) << setfill('0') << int(fileSha1[i]); + ofstream fout(("./data/" + buffer.str()).c_str()); + fout.write(filePointer, filesize); + fout.close(); free(filePointer); free(fileSha1); free(fileSha1Real); + writeLog(LOG_LEVEL_DEBUG, "Saved file " + buffer.str() + " into data."); + } + string sql = ""; + while (pt < len) sql.push_back(ch[pt++]); + int raws = mysqli_execute(mysql, sql.c_str()); + writeLog(LOG_LEVEL_INFO, "Import data successfully."); + writeLog(LOG_LEVEL_DEBUG, to_string(filenum) + " files were written."); + writeLog(LOG_LEVEL_DEBUG, to_string(raws) + " raws were affected."); +} \ No newline at end of file diff --git a/json.h b/modules/json.h similarity index 100% rename from json.h rename to modules/json.h diff --git a/mysqli.h b/modules/mysqli.h similarity index 86% rename from mysqli.h rename to modules/mysqli.h index 51f7b6d..d538176 100755 --- a/mysqli.h +++ b/modules/mysqli.h @@ -5,7 +5,7 @@ using namespace std; typedef vector > mysqld; MYSQL mysqli_connect(const char* host,const char* user,const char* passwd,const char* db,int port,string name="main-server") { MYSQL mysql,*res1; res1=mysql_init(&mysql); if (res1==NULL) cout << "Failed to initialize MYSQL structure!" << endl; - bool res2=mysql_real_connect(&mysql,host,user,passwd,db,port,nullptr,0); if (!res2) cerr << mysql_error(&mysql) << endl; + bool res2=mysql_real_connect(&mysql,host,user,passwd,db,port,nullptr,CLIENT_MULTI_STATEMENTS); if (!res2) cerr << mysql_error(&mysql) << endl; return mysql; } @@ -24,7 +24,7 @@ mysqld mysqli_query(MYSQL conn,const char* sql,string name="main-server") { mysqld res; map tmp; for (int i=0;fd=mysql_fetch_field(res2);i++) field.push_back(fd->name); while (row=mysql_fetch_row(res2)) { - for (int i=0;i levels = levelList("", 1, 5); + argList["html.levelsList"] = ""; for (int i = 0; i < levels.items.size(); i++) argList["html.levelsList"] += levels.items[i].toHTMLObject().output(); argList["html.levelsBottom"] = fetchComponentsBottom("/levels/search/0", "/levels/list").output(); argList["html.skinsTitle"] = fetchComponentsTitle("/skins/search/0", "/skins/list", "skins").output(); Section skins = skinList("", 1, 5); + argList["html.skinsList"] = ""; for (int i = 0; i < skins.items.size(); i++) argList["html.skinsList"] += skins.items[i].toHTMLObject().output(); argList["html.skinsBottom"] = fetchComponentsBottom("/skins/search/0", "/skins/list").output(); argList["html.backgroundsTitle"] = fetchComponentsTitle("/backgrounds/search/0", "/backgrounds/list", "backgrounds").output(); Section backgrounds = backgroundList("", 1, 5); + argList["html.backgroundsList"] = ""; for (int i = 0; i < backgrounds.items.size(); i++) argList["html.backgroundsList"] += backgrounds.items[i].toHTMLObject().output(); argList["html.backgroundsBottom"] = fetchComponentsBottom("/backgrounds/search/0", "/backgrounds/list").output(); argList["html.effectsTitle"] = fetchComponentsTitle("/effects/search/0", "/effect/list", "effects").output(); Section effects = effectList("", 1, 5); + argList["html.effectsList"] = ""; for (int i = 0; i < effects.items.size(); i++) argList["html.effectsList"] += effects.items[i].toHTMLObject().output(); argList["html.effectsBottom"] = fetchComponentsBottom("/effects/search/0", "/effects/list").output(); argList["html.particlesTitle"] = fetchComponentsTitle("/particles/search/0", "/particles/list", "particles").output(); Section particles = particleList("", 1, 5); + argList["html.particlesList"] = ""; for (int i = 0; i < particles.items.size(); i++) argList["html.particlesList"] += particles.items[i].toHTMLObject().output(); argList["html.particlesBottom"] = fetchComponentsBottom("/particles/search/0", "/particles/list").output();\ argList["html.enginesTitle"] = fetchComponentsTitle("/engines/search/0", "/engines/list", "engines").output(); Section engines = engineList("", 1, 5); + argList["html.enginesList"] = ""; for (int i = 0; i < engines.items.size(); i++) argList["html.enginesList"] += engines.items[i].toHTMLObject().output(); argList["html.enginesBottom"] = fetchComponentsBottom("/engines/search/0", "/engines/list").output();