From 74b06d022b0b61538590e8c30ec20830ebdd3c5c Mon Sep 17 00:00:00 2001 From: RElesgoe Date: Sat, 28 Apr 2018 17:23:10 -0700 Subject: [PATCH] Overhaul versioncheck system (#340) * Overhaul versioncheck system - Removed version_exeinfo_match, skip_versioncheck, and version_exeinfo_maxdiff from bnetd.conf - Modified configuration file to use JSON - Correctly finds the appropriate versioncheck entries - Add versioncheck.md to docs/ * Update JSON for Modern C++ from 3.0.1 to 3.1.2 --- conf/CMakeLists.txt | 4 +- conf/bnetd.conf.in | 30 +- conf/bnetd.conf.win32 | 2 +- conf/versioncheck.conf.in | 2447 ---------------- conf/versioncheck.json.in | 1855 ++++++++++++ docs/versioncheck.md | 16 + src/bnetd/anongame.cpp | 14 +- src/bnetd/command.cpp | 2 +- src/bnetd/connection.cpp | 20 +- src/bnetd/connection.h | 6 +- src/bnetd/handle_bnet.cpp | 692 +++-- src/bnetd/luainterface.cpp | 3 - src/bnetd/main.cpp | 8 +- src/bnetd/prefs.cpp | 79 - src/bnetd/prefs.h | 3 - src/bnetd/server.cpp | 6 +- src/bnetd/versioncheck.cpp | 815 ++---- src/bnetd/versioncheck.h | 125 +- src/common/CMakeLists.txt | 2 +- src/common/hash_tuple.hpp | 67 + src/common/setup_before.h | 2 +- src/json/json.hpp | 5482 ++++++++++++++++++++++++++---------- 22 files changed, 6590 insertions(+), 5090 deletions(-) delete mode 100644 conf/versioncheck.conf.in create mode 100644 conf/versioncheck.json.in create mode 100644 docs/versioncheck.md create mode 100644 src/common/hash_tuple.hpp diff --git a/conf/CMakeLists.txt b/conf/CMakeLists.txt index dc3ff9d..0d90d85 100644 --- a/conf/CMakeLists.txt +++ b/conf/CMakeLists.txt @@ -4,7 +4,7 @@ set(OUTPUT_CONFS ad.json anongame_infos.conf address_translation.conf bnissue.txt bnmaps.conf bnxpcalc.conf bnxplevel.conf channel.conf command_groups.conf realm.conf sql_DB_layout.conf supportfile.conf topics.conf - tournament.conf versioncheck.conf icons.conf) + tournament.conf versioncheck.json icons.conf) foreach(CONF ${OUTPUT_CONFS}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${CONF}.in ${CMAKE_CURRENT_BINARY_DIR}/${CONF} @ONLY) endforeach(CONF) @@ -20,7 +20,7 @@ if(WITH_BNETD) bnetd_default_user.plain bnissue.txt bnmaps.conf bnxpcalc.conf bnxplevel.conf channel.conf command_groups.conf realm.conf sql_DB_layout.conf supportfile.conf topics.conf - tournament.conf versioncheck.conf icons.conf) + tournament.conf versioncheck.json icons.conf) # special treatment for non .in files install(FILES bnetd_default_user.cdb DESTINATION ${SYSCONFDIR}) diff --git a/conf/bnetd.conf.in b/conf/bnetd.conf.in index 0615ce5..26d94d8 100644 --- a/conf/bnetd.conf.in +++ b/conf/bnetd.conf.in @@ -85,7 +85,7 @@ mpqfile = "${SYSCONFDIR}/autoupdate.conf" logfile = "${LOCALSTATEDIR}/bnetd.log" realmfile = "${SYSCONFDIR}/realm.conf" maildir = "${LOCALSTATEDIR}/bnmail" -versioncheck_file = "${SYSCONFDIR}/versioncheck.conf" +versioncheck_file = "${SYSCONFDIR}/versioncheck.json" mapsfile = "${SYSCONFDIR}/bnmaps.conf" xplevelfile = "${SYSCONFDIR}/bnxplevel.conf" xpcalcfile = "${SYSCONFDIR}/bnxpcalc.conf" @@ -209,19 +209,6 @@ star_iconfile = "icons_STAR.bni" # Example: allowed_clients = war3,w3xp allowed_clients = all -# If this option is enabled, the verification step is skipped if possible. -# This only works with clients < 109. It is useful because you no longer -# need any of the IX86AUTH?.MPQ and PMACAUTH?.MPQ files. Note that it will -# also skip over all the autoupdate checks effectively disabling it. -# -# If you disable this you must have one or more of the MPQ files. Otherwise -# clients will hang when they first connect because they are attempting to -# download them. The versioncheck can only be skipped for clients older -# than 109. Starting with version 109 the clients will always do version -# checking since they do not function properly if the server does not -# request it. -skip_versioncheck = true - # If you enable the version checks but want to allow clients that don't pass # the checksum test then enable this. allow_bad_version = true @@ -232,19 +219,6 @@ allow_bad_version = true # is a good idea. allow_unknown_version = true -# This defines how the exeinfo field in the versioncheck file is being -# checked. You can choose between no match at all [none] (default), -# exact match [exact], exact case-sensitive match [exactcase], dumb wildcard -# match [wildcard], and parsed value comparison [parse]. -# NOTE: [parse] needs the mktime() function and might therefore not work on -# every system. -version_exeinfo_match = none - -# If you have choosen [parse] above, this is the tolerance with which -# the time can differ. The value must be given in seconds. If it's 0 this -# check is disabled. -version_exeinfo_maxdiff = 0 - # # ############################################################################## @@ -448,7 +422,7 @@ max_friends = 20 # Set track=0 to disable tracking. Any other number will set number # of seconds between sending tracking packets. This is ON by default. #track = 0 -track = 60 +track = 0 # Tracking server(s) # Use a comma delimited list of hostnames with optional UDP port numbers diff --git a/conf/bnetd.conf.win32 b/conf/bnetd.conf.win32 index a2a0cc0..d0ae4fa 100644 --- a/conf/bnetd.conf.win32 +++ b/conf/bnetd.conf.win32 @@ -68,7 +68,7 @@ transfile = conf\address_translation.conf mpqfile = conf\autoupdate.conf logfile = var\bnetd.log realmfile = conf\realm.conf -versioncheck_file = conf\versioncheck.conf +versioncheck_file = conf\versioncheck.json mapsfile = conf\bnmaps.conf xplevelfile = conf\bnxplevel.conf xpcalcfile = conf\bnxpcalc.conf diff --git a/conf/versioncheck.conf.in b/conf/versioncheck.conf.in deleted file mode 100644 index 8907d9d..0000000 --- a/conf/versioncheck.conf.in +++ /dev/null @@ -1,2447 +0,0 @@ -############################################################################## -# versioncheck.conf - Version Check Configuration # -#----------------------------------------------------------------------------# -# # -# This file is used to verify that clients are the same version they claim # -# to be and to tag certain versions for autoupgrades (see autoupdate.conf). # -# # -# Every combination of archtag/clienttag/version/equation/mpqfile needs to # -# be listed seperately. Each combination has a unique checksum. # -# # -# If the exeinfo field is "NULL" then the string comparison will be disabled # -# for that entry. If the checksum field is 0 then the checksum verification # -# is disabled for that entry. If the versionid is 0 then the verification # -# of versionid is disabled for that entry The versiontag is optional. # -# It is used to identify a particular version even when the version number # -# is identical to another (e.g. 109c and 109d are both version 1.0.9.0). # -# These tags can be used in the autoupdate file allowing it to know the # -# difference because of the different checksums. # -# # -# Multiple matching: # -# exeinfo = NULL will match all client exeinfos # -# exeinfo = "war3.exe ??/??/?? ??:??:?? 1568211" will match war3.exe with # -# filesize 1568211 but won't care about the file's date/time # -# versionid = 0x0 will match all client versionids # -# gameversion = 0.0.0.0 will match all client's versions # -# checksum = 0x0 will match all client checksums # -# # -# Note: if you use multiple equations/mpqfiles be sure to put entries with # -# corresponding checksum for each archtag/clienttag # -# # -# versioncheck entries work like a firewall chains, bnetd will match the # -# first entry, then is safe to use entries in descending order, so if we # -# found a badversion, it will be tagged with the tag of entry with the lower # -# version. Last line can be used to match all version unmatched so far # -# # -# Do not include "/" in any of the filenames. # -# # -# The version number can be in two forms. If it does not contain a period # -# then 123 is assumed to mean 1.2.3.0. Otherwise it may contain up to three # -# periods. If fewer than four parts are present, the latter ones will be # -# assumed to be zero. For example, 1.8 would be interpreted as 1.8.0.0. # -# Sections will overflow if they are greater than 255. # -# # -# Typically Blizzard will add a 1 in the fourth position to signify an # -# expansion pack and 10 to signify "b" versions. So Brood War 1.08b would # -# have a version of 1.0.8.11. # -# # -# ---------------------------equation--------------------------- \ # -# ---mpqfile---- \ # -# archtag \ # -# clienttag \ # -# ----------------exeinfo----------------- \ # -# versionid- \ # -# --verstr-- \ # -# -checksum- \ # -# -versiontag- # -# # -############################################################################## - -##### Intel (IX86) ########################################################### - -# -=-=-=-=-=-=- Latest Versions -=-=-=-=-=-=- No Update Required -=-=-=-=-=-=- - -# Warcraft III - TFT (Expansion) 1.28.5. -"A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - W3XP \ - "Warcraft III.exe 07/07/17 20:15:59 562152" \ - 0x0000001c \ - 1.28.5.0 \ - 0x60743fc9 \ - W3XP_1285 - -# Warcraft III - ROC 1.28.5. -"A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - W3XP \ - "Warcraft III.exe 07/07/17 20:15:59 562152" \ - 0x0000001c \ - 1.28.5.0 \ - 0x60743fc9 \ - WAR3_1285 - -# Diablo II - LoD (Expansion) 1.14d -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - ver-IX86-1.mpq \ - IX86 \ - D2XP \ - "Game.exe 05/31/16 19:02:24 3618792" \ - 0x0000000e \ - 1.14.3.0 \ - 0x43f51a83 \ - D2XP_114D - -# Diablo II 1.14d -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - ver-IX86-1.mpq \ - IX86 \ - D2DV \ - "Game.exe 05/31/16 19:02:32 3614696" \ - 0x0000000e \ - 1.14.3.0 \ - 0x6b6a21b5 \ - D2DV_114D - -# Starcraft - Broodwar (Expansion) 1.16.1 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "StarCraft.exe 01/09/09 22:57:43 1220608" \ - 0x000000d3 \ - 1.16.1.1 \ - 0x0a85372d \ - SEXP_1161 - -# Starcraft - Broodwar (Expansion) 1.16.1 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "StarCraft.exe 01/09/09 22:57:43 1220608" \ - 0x000000d3 \ - 1.16.1.1 \ - 0x0a85372d \ - STAR_1161 - -# Starcraft (Standalone) 1.16.1 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "StarCraft.exe 01/08/09 05:23:44 1220608" \ - 0x000000d3 \ - 1.16.1.0 \ - 0x02623d85 \ - STAR_1161 - -# Starcraft - Broodwar (Expansion) 1.16.1 - (Broodwar mode) - MAC - "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "XMACver1.mpq" \ - XMAC \ - SEXP \ - "Starcraft (Carbon) 100/50/58 100:50:46 1402780" \ - 0x000000d3 \ - 0x010a6425 \ - 0xd6802c9e \ - SEXP_1161 - -# -=-=-=-=-=- Older Versions -=-=-=-=-=-=- Update Required -=-=-=-=-=- - -# =================== WarCraft 3 Expansion Entries ================== - -# Warcraft III - TFT (Expansion) 1.28.4. -"A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - W3XP \ - "Warcraft III.exe 06/21/17 16:24:03 561128" \ - 0x0000001c \ - 1.28.4.184 \ - 0xce7bb7ee \ - W3XP_1284 - -# Warcraft III - TFT (Expansion) 1.28.2. -"A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - W3XP \ - "War3.exe 05/20/17 13:25:29 558568" \ - 0x0000001c \ - 1.28.2.227 \ - 0x2dc96bf5 \ - W3XP_1282 - -# Warcraft III - TFT (Expansion) 1.28.1. -"A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - W3XP \ - "War3.exe 04/28/17 19:10:24 558568" \ - 0x0000001c \ - 1.28.1.197 \ - 0x1fbde0fd \ - W3XP_1281 - -# Warcraft III - TFT (Expansion) 1.28. -"A=4159479319 C=1286227868 B=2524979557 4 A=A+S B=B^C C=C+A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - W3XP \ - "War3.exe 04/17/17 12:06:57 536040" \ - 0x0000001c \ - 1.28.0.37 \ - 0xe00c3096 \ - W3XP_128 - -# Warcraft III - TFT (Expansion) 1.27b -"B=454282227 C=2370009462 A=2264812340 4 A=A^S B=B-C C=C-A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - W3XP \ - "War3.exe 12/09/16 06:05:09 515048" \ - 0x0000001b \ - 1.27.1.173 \ - 0x4026b5b0 \ - W3XP_127B - -# Warcraft III - TFT (Expansion) 1.27a -# Note: this version check is sent in both FT and ROC mode -"B=454282227 C=2370009462 A=2264812340 4 A=A^S B=B-C C=C-A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - W3XP \ - "War3.exe 08/05/16 03:15:27 514536" \ - 0x0000001b \ - 1.27.0.16 \ - 0x960947fd \ - W3XP_127A - -# Warcraft III - TFT (Expansion) 1.26a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 03/18/11 20:03:55 471040" \ - 0x0000001a \ - 1.26.0.1 \ - 0xf2e7cec2 \ - W3XP_126A - -# Warcraft III - TFT (Expansion) 1.25b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 02/12/11 03:32:48 471040" \ - 0x00000019 \ - 1.25.1.253 \ - 0x41a8664c \ - W3XP_125B - -# Warcraft III - TFT (Expansion) 1.24e -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 02/26/10 20:41:08 471040" \ - 0x00000018 \ - 1.24.4.243 \ - 0xde4443c5 \ - W3XP_124E - -# Warcraft III - TFT (Expansion) 1.24d -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 01/13/10 00:35:02 471040" \ - 0x00000018 \ - 1.24.3.240 \ - 0x2a7b4a96 \ - W3XP_124D - -# Warcraft III - TFT (Expansion) 1.24c -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 10/21/09 19:14:23 471040" \ - 0x00000018 \ - 1.24.2.234 \ - 0x307c4598 \ - W3XP_124C - -# Warcraft III - TFT (Expansion) 1.24b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 08/07/09 19:20:53 471040" \ - 0x00000018 \ - 1.24.1.230 \ - 0xcaa59e30 \ - W3XP_124B - -# Warcraft III - TFT (Expansion) 1.24a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 07/23/09 20:15:12 471040" \ - 0x00000018 \ - 1.24.0.228 \ - 0xf3c22f0b \ - W3XP_124A - -# Warcraft III - TFT (Expansion) 1.23a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 03/12/09 23:23:40 471040" \ - 0x00000017 \ - 1.23.0.208 \ - 0xfad9bc05 \ - W3XP_123A - -# Warcraft III - TFT (Expansion) 1.22a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 06/27/08 00:01:35 471040" \ - 0x00000016 \ - 1.22.0.184 \ - 0x909998db \ - W3XP_122A - -# Warcraft III - TFT (Expansion) 1.21b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 07/19/07 18:41:12 409660" \ - 0x00000015 \ - 1.21.1.156 \ - 0x1b735294 \ - W3XP_121B - -# Warcraft III - TFT (Expansion) 1.21a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 12/28/06 20:35:21 1572307" \ - 0x00000015 \ - 1.21.0.119 \ - 0xab35b5f1 \ - W3XP_121A - -# Warcraft III - TFT (Expansion) 1.20e -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 06/13/06 06:13:05 1572307" \ - 0x00000014 \ - 1.20.4.186 \ - 0x8771b225 \ - W3XP_120E - -# Warcraft III - TFT (Expansion) 1.20d -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "War3.exe 04/06/06 00:19:58 1572307" \ - 0x00000014 \ - 1.20.3.182 \ - 0xbbd3e672 \ - W3XP_120D - -# Warcraft III - TFT (Expansion) 1.20c -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "War3.exe 01/05/06 02:50:54 1572307" \ - 0x00000014 \ - 1.20.2.177 \ - 0x97c8e59b \ - W3XP_120C - -# Warcraft III - TFT (Expansion) 1.20b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "War3.exe 12/07/05 23:59:26 1572307" \ - 0x00000014 \ - 1.0.20.168 \ - 0x57e714c1 \ - W3XP_120B - -# Warcraft III - TFT (Expansion) 1.20a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "War3.exe 09/23/05 03:16:24 1572307" \ - 0x00000014 \ - 1.0.20.160 \ - 0xdfe00da5 \ - W3XP_120A - -# Warcraft III - TFT (Expansion) 1.19b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 09/20/05 04:03:24 1572307" \ - 0x00000013 \ - 1.0.19.158 \ - 0x9c73744d \ - W3XP_119B - -# Warcraft III - TFT (Expansion) 1.19a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 09/07/05 18:58:53 1572307" \ - 0x00000013 \ - 1.0.19.153 \ - 0x892fa500 \ - W3XP_119A - -# Warcraft III - TFT (Expansion) 1.18a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 02/18/05 04:38:43 1572307" \ - 0x00000012 \ - 1.0.18.142 \ - 0x087fb830 \ - W3XP_118A - -# Warcraft III - TFT (Expansion) 1.17a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 09/15/04 22:42:18 1568211" \ - 0x00000011 \ - 1.0.17.100 \ - 0xd1d89b0e \ - W3XP_117A - -# Warcraft III - TFT (Expansion) 1.16a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "War3.exe 06/28/04 23:37:02 1568211" \ - 0x00000010 \ - 1.0.16.38 \ - 0xcc882269 \ - W3XP_116A - -# Warcraft III - TFT (Expansion) 1.15a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 05/04/04 01:18:12 1568211" \ - 0x0000000f \ - 1.0.15.156 \ - 0x39b3974b \ - W3XP_115A - -# Warcraft III - TFT (Expansion) 1.14b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "War3.exe 01/09/04 06:11:44 1568211" \ - 0x0000000e \ - 1.0.14.152 \ - 0x36fd6e73 \ - W3XP_114B - -# Warcraft III - TFT (Expansion) 1.14a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "War3.exe 01/06/04 03:44:50 1568211" \ - 0x0000000e \ - 1.0.14.151 \ - 0xa842edef \ - W3XP_114A - -# Warcraft III - TFT (Expansion) 1.13b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "War3.exe 12/18/03 00:53:06 1568211" \ - 0x0000000d \ - 1.0.13.149 \ - 0x0bb5600d \ - W3XP_113B - -# Warcraft III - TFT (Expansion) 1.13a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 12/10/03 23:42:46 2150400" \ - 0x0000000d \ - 1.0.13.149 \ - 0xaf284a18 \ - W3XP_113A - -# Warcraft III - TFT (Expansion) 1.12a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 07/22/03 22:43:15 1568211" \ - 0x0000000c \ - 1.0.12.148 \ - 0xa9c022bd \ - W3XP_112A - -# Warcraft III - TFT (Expansion) 1.11a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 07/01/03 18:22:52 1568211" \ - 0x0000000b \ - 1.0.11.147 \ - 0x5fce4f04 \ - W3XP_111A - -# Warcraft III - TFT (Expansion) 1.10a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - "war3.exe 06/17/03 16:37:46 1568211" \ - 0x0000000a \ - 1.0.10.146 \ - 0x75d1c16f \ - W3XP_110A - -# Warcraft III - TFT (Expansion) 1.07a -# Note: this version check is sent in both FT and ROC mode -# exeinfo is NULL here because it's the date of install -# but that's ok because we have the checksum -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W3XP \ - NULL \ - 0x00000007 \ - 1.0.7.142 \ - 0xde80b55b \ - W3XP_107A - -# ====================== WarCraft 3 Entries ======================== - -# Warcraft III - ROC 1.28.4. -"A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - W3XP \ - "Warcraft III.exe 06/21/17 16:24:03 561128" \ - 0x0000001c \ - 1.28.4.184 \ - 0xce7bb7ee \ - W3XP_1284 - -# Warcraft III - ROC 1.28.2. -"A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 05/20/17 13:25:29 558568" \ - 0x0000001c \ - 1.28.2.227 \ - 0x2dc96bf5 \ - WAR3_1282 - -# Warcraft III - ROC 1.28.1. -"C=2765988015 A=1961381685 B=3183936986 4 A=A-S B=B+C C=C-A A=A^B" \ - ver-IX86-1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 04/17/17 12:06:57 536040" \ - 0x0000001c \ - 1.28.1.197 \ - 0x1fbde0fd \ - WAR3_1281 - -# Warcraft III - ROC 1.28 -"C=1210345628 B=2907417192 A=3489626739 4 A=A+S B=B+C C=C+A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 04/17/17 12:06:57 536040" \ - 0x0000001c \ - 1.28.0.37 \ - 0xe00c3096 \ - WAR3_128 - -# Warcraft III - ROC 1.27b -"B=454282227 C=2370009462 A=2264812340 4 A=A^S B=B-C C=C-A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 12/09/16 06:05:09 515048" \ - 0x0000001b \ - 1.27.1.173 \ - 0x4026b5b0 \ - WAR3_127B - -# Warcraft III - ROC 1.27a -# Note: this version check is sent in both FT and ROC mode -"B=454282227 C=2370009462 A=2264812340 4 A=A^S B=B-C C=C-A A=A+B" \ - ver-IX86-1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 08/05/16 03:15:27 514536" \ - 0x0000001b \ - 1.27.0.16 \ - 0x960947fd \ - WAR3_127A - -# Warcraft III - ROC 1.26a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 03/18/11 20:03:55 471040" \ - 0x0000001a \ - 1.26.0.1 \ - 0xf2e7cec2 \ - WAR3_126A - -# Warcraft III - ROC 1.25b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 02/12/11 03:32:48 471040" \ - 0x00000019 \ - 1.25.1.253 \ - 0x41a8664c \ - W3XP_125B - -# Warcraft III - ROC 1.24e -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 02/26/10 20:41:08 471040" \ - 0x00000018 \ - 1.24.4.243 \ - 0xde4443c5 \ - W3XP_124E - -# Warcraft III - ROC 1.24d -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 01/13/10 00:35:02 471040" \ - 0x00000018 \ - 1.24.3.240 \ - 0x2a7b4a96 \ - WAR3_124D - -# Warcraft III - ROC 1.24c -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 10/21/09 19:14:23 471040" \ - 0x00000018 \ - 1.24.2.234 \ - 0x307c4598 \ - WAR3_124C - -# Warcraft III - ROC 1.24b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 08/07/09 19:20:53 471040" \ - 0x00000018 \ - 1.24.1.230 \ - 0xcaa59e30 \ - WAR3_124B - -# Warcraft III - ROC 1.24a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 07/23/09 20:15:12 471040" \ - 0x00000018 \ - 1.24.0.228 \ - 0xf3c22f0b \ - WAR3_124A - -# Warcraft III - ROC 1.23a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 03/12/09 23:23:40 471040" \ - 0x00000017 \ - 1.23.0.208 \ - 0xfad9bc05 \ - WAR3_123A - -# Warcraft III - ROC 1.22a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 06/27/08 00:01:35 471040" \ - 0x00000016 \ - 1.22.0.184 \ - 0x909998db \ - WAR3_122A - -# Warcraft III - ROC 1.21b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 07/19/07 18:41:12 409660" \ - 0x00000015 \ - 1.21.1.156 \ - 0x1b735294 \ - WAR3_121B - -# Warcraft III - ROC 1.21a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 12/28/06 20:35:21 1572307" \ - 0x00000015 \ - 1.21.0.119 \ - 0xab35b5f1 \ - WAR3_121A - -# Warcraft III - ROC 1.20e -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 06/13/06 06:13:05 1572307" \ - 0x00000014 \ - 1.20.4.186 \ - 0x8771b225 \ - WAR3_120E - -# Warcraft III - ROC 1.20d -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 04/06/06 00:19:58 1572307" \ - 0x00000014 \ - 1.20.3.182 \ - 0xbbd3e672 \ - WAR3_120D - -# Warcraft III - ROC 1.20c -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 01/05/06 02:50:54 1572307" \ - 0x00000014 \ - 1.20.2.177 \ - 0x97c8e59b \ - WAR3_120C - -# Warcraft III - ROC 1.20b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 12/07/05 23:59:26 1572307" \ - 0x00000014 \ - 1.0.20.168 \ - 0x57e714c1 \ - WAR3_120B - -# Warcraft III - ROC 1.20a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 09/23/05 03:16:24 1572307" \ - 0x00000014 \ - 1.0.20.160 \ - 0xdfe00da5 \ - WAR3_120A - -# Warcraft III - ROC 1.19b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 09/20/05 04:03:24 1572307" \ - 0x00000013 \ - 1.0.19.158 \ - 0x9c73744d \ - WAR3_119B - -# Warcraft III - ROC 1.19a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 09/07/05 18:58:53 1572307" \ - 0x00000013 \ - 1.0.19.153 \ - 0x892fa500 \ - WAR3_119A - -# Warcraft III - ROC 1.18a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 02/18/05 04:38:43 1572307" \ - 0x00000012 \ - 1.0.18.142 \ - 0x087fb830 \ - WAR3_118A - -# Warcraft III - ROC 1.17a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 09/15/04 22:42:18 1568211" \ - 0x00000011 \ - 1.0.17.100 \ - 0xd1d89b0e \ - WAR3_117A - -# Warcraft III - ROC 1.16a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 06/28/04 23:37:02 1568211" \ - 0x00000010 \ - 1.0.16.38 \ - 0xcc882269 \ - WAR3_116A - -# Warcraft III - ROC 1.15a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 05/04/04 01:18:12 1568211" \ - 0x0000000f \ - 1.0.15.156 \ - 0x39b3974b \ - WAR3_115A - -# Warcraft III - ROC 1.14b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 01/09/04 06:11:44 1568211" \ - 0x0000000e \ - 1.0.14.152 \ - 0x36fd6e73 \ - WAR3_114B - -# Warcraft III - ROC 1.14a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 01/06/04 03:44:50 1568211" \ - 0x0000000e \ - 1.0.14.151 \ - 0xa842edef \ - WAR3_114A - -# Warcraft III - ROC 1.13b -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "War3.exe 12/18/03 00:53:06 1568211" \ - 0x0000000d \ - 1.0.13.149 \ - 0x0bb5600d \ - WAR3_113B - -# Warcraft III - ROC 1.13a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 12/10/03 23:42:46 2150400" \ - 0x0000000d \ - 1.0.13.149 \ - 0xaf284a18 \ - WAR3_113A - -# Warcraft III - ROC 1.12a -# Note: this version check is sent in both FT and ROC mode -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 07/22/03 22:43:15 1568211" \ - 0x0000000c \ - 1.0.12.148 \ - 0xa9c022bd \ - WAR3_112A - -# Warcraft III - RoC 1.11a -#"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 07/01/03 18:22:52 1568211" \ - 0x0000000b \ - 1.0.11.147 \ - 0x5fce4f04 \ - WAR3_111A - -# Warcraft III - RoC 1.10a -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 06/17/03 16:37:46 1568211" \ - 0x0000000a \ - 1.0.10.146 \ - 0x75d1c16f \ - WAR3_110A - -# Warcraft III - RoC 1.07 (Used for TFT 1.07a in RoC Mode) -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - NULL \ - 0x00000007 \ - 1.0.7.142 \ - 0xde80b55b \ - WAR3_107A - -# WarCraft III - RoC 1.06a -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 05/23/03 19:50:08 1461715" \ - 0x00000006 \ - 1.0.6.0 \ - 0x302dca19 \ - WAR3_106A - -# WarCraft III - RoC 1.05a -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 01/30/03 03:08:10 1170899" \ - 0x00000005 \ - 1.0.5.0 \ - 0xbe284ba9 \ - WAR3_105A - -# WarCraft III - RoC 1.04c -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 01/16/03 02:44:48 1166807" \ - 0x00000004 \ - 1.0.4.1 \ - 0x3f06d9fa \ - WAR3_104C - -# WarCraft III - RoC 1.04b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 10/31/02 17:29:22 1068499" \ - 0x00000004 \ - 1.0.3.0 \ - 0x56f4cc37 \ - WAR3_104B - -# WarCraft III - RoC 1.04a -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 10/29/02 23:26:17 713495" \ - 0x00000004 \ - 1.0.3.0 \ - 0x0e529b2d \ - WAR3_104A - -# Warcraft III - RoC 1.03a -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 10/08/02 20:54:49 713495" \ - 0x00000003 \ - 1.0.3.0 \ - 0xae4ac7fa \ - WAR3_103A - -# Warcraft III - RoC 1.02a -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 09/06/02 02:41:02 713495" \ - 0x00000002 \ - 1.0.1.0 \ - 0xa67d9a0e \ - WAR3_102A - -# Warcraft III - RoC 1.02 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 08/12/02 18:21:15 713495" \ - 0x00000002 \ - 1.0.1.0 \ - 0x03d8be54 \ - WAR3_102 - -# Warcraft III - RoC 1.01b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 07/10/02 18:59:10 713495" \ - 0x00000001 \ - 1.0.1.1 \ - 0x062ef7dc \ - WAR3_101B - -# Warcraft III - RoC 1.01 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - "war3.exe 06/28/02 18:41:18 713495" \ - 0x00000001 \ - 1.0.1.0 \ - 0x62c2e1a2 \ - WAR3_101 - -# Warcraft III - RoC 1.00 -# exeinfo is NULL here because it's the date of install -# but that's ok because we have the checksum -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - WAR3 \ - NULL \ - 0x00000000 \ - 1.0.0.0 \ - 0xf891f129 \ - WAR3_100 - -# ====================== Diablo II LOD entries =========================== - -# Diablo II - LoD (Expansion) 1.14c -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - ver-IX86-1.mpq \ - IX86 \ - D2XP \ - "Game.exe 05/06/16 18:15:44 3586024" \ - 0x0000000e \ - 1.14.2.0 \ - 0x6123b0f2 \ - D2XP_114C - -# Diablo II - LoD (Expansion) 1.14b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - ver-IX86-1.mpq \ - IX86 \ - D2XP \ - "Game.exe 03/31/16 01:09:01 3590120" \ - 0x0000000e \ - 1.14.1.0 \ - 0x1373c295 \ - D2XP_114B - -# Diablo II - LoD (Expansion) 1.14a -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - ver-IX86-1.mpq \ - IX86 \ - D2XP \ - "Game.exe 02/29/16 17:34:46 3590120" \ - 0x0000000e \ - 1.14.0.0 \ - 0xbcb5b3bb \ - D2XP_114A - -# Diablo II - LoD (Expansion) 1.13c -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2XP \ - "Game.exe 03/09/10 04:10:51 61440" \ - 0x0000000d \ - 1.0.13.0 \ - 0x7686beca \ - D2XP_113C - -# Diablo II - LoD (Expansion) 1.12a - -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2XP \ - "Game.exe 05/28/08 01:44:02 61440" \ - 0x0000000c \ - 1.0.12.0 \ - 0x3d421510 \ - D2XP_112A - -# Diablo II - LoD (Expansion) 1.11b - -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2XP \ - "Game.exe 08/17/05 01:12:38 2129920" \ - 0x0000000b \ - 1.0.11.0 \ - 0xbfc36199 \ - D2XP_111B - -# Diablo II - LoD (Expansion) 1.11 - -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2XP \ - "Game.exe 07/26/05 23:22:13 2129920" \ - 0x0000000b \ - 1.0.11.0 \ - 0xfb0c59b2 \ - D2XP_111 - -# Diablo II - LoD (Expansion) 1.10 - -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2XP \ - "Game.exe 10/13/03 08:35:30 1198857" \ - 0x0000000a \ - 1.0.10.0 \ - 0x210126ec \ - D2XP_110 - -# Diablo II - LoD (Expansion) 1.09d - -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2XP \ - "Game.exe 11/30/01 04:00:17 448675" \ - 9 \ - 1.0.9.0 \ - 0xfa39efbb \ - D2XP_109D - -# Diablo II - LoD (Expansion) 1.09b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2XP \ - "Game.exe 08/31/01 22:40:56 428163" \ - 9 \ - 1.0.9.0 \ - 0x1436c138 \ - D2XP_109B - -# Diablo II - LoD (Expansion) 1.09 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2XP \ - "Game.exe 08/16/01 23:05:03 428163" \ - 9 \ - 1.0.9.0 \ - 0x15abc72d \ - D2XP_109 - -# Diablo II - LoD (Expansion) 1.08 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2XP \ - "Game.exe 06/19/01 02:24:32 428163" \ - 8 \ - 1.0.8.0 \ - 0x3a8664ff \ - D2XP_108 - -# Diablo II - LoD (Expansion) 1.07 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2XP \ - "Game.exe 01/23/02 06:39:26 424067" \ - 7 \ - 1.0.7.0 \ - 0x4f3079e6 \ - D2XP_107 - -# ==================== Diablo 2 (original) entries ===================== - -# Diablo II 1.14c -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 05/06/16 18:15:49 3581928" \ - 0x0000000e \ - 1.14.2.0 \ - 0x7d54f650 \ - D2DV_114C - -# Diablo II 1.14b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 06/13/16 02:02:21 3586024" \ - 0x0000000e \ - 1.14.1.0 \ - 0x77f3db54 \ - D2DV_114B - -# Diablo II 1.14a -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - ver-IX86-1.mpq \ - IX86 \ - D2DV \ - "Game.exe 02/29/16 17:34:50 3586024" \ - 0x0000000e \ - 1.14.0.0 \ - 0xd9e9368a \ - D2DV_114A - -# Diablo II 1.13c -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 03/09/10 04:11:00 57344" \ - 0x0000000d \ - 1.0.13.0 \ - 0xfc04b9f6 \ - D2DV_113C - -# Diablo II 1.12a - -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 07/05/08 17:19:40 57344" \ - 0x0000000c \ - 1.0.12.0 \ - 0x243ac143 \ - D2DV_112A - -# Diablo II 1.11b - -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 08/17/05 01:11:43 2125824" \ - 0x0000000b \ - 1.0.11.0 \ - 0x602f8323 \ - D2DV_111B - -# Diablo II 1.11 - -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 27/06/05 23:21:18 2125824" \ - 0x0000000b \ - 1.0.11.0 \ - 0xbdf86a85 \ - D2DV_111 - -# Diablo II 1.10 - -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 10/13/03 08:34:46 1194761" \ - 0x0000000a \ - 1.0.10.0 \ - 0x3132dda6 \ - D2DV_110 - -# Diablo II 1.09d - -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 11/30/01 03:59:45 444579" \ - 9 \ - 1.0.9.0 \ - 0x41e223a3 \ - D2DV_109D - -# Diablo II 1.09b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 08/31/01 22:40:35 424067" \ - 9 \ - 1.0.9.0 \ - 0xb9cb2cb1 \ - D2DV_109B - -# Diablo II 1.09 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 08/16/01 23:04:40 424067" \ - 9 \ - 1.0.9.0 \ - 0xc43f1764 \ - D2DV_109 - -# Diablo II 1.08 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 06/19/01 02:24:04 424067" \ - 8 \ - 1.0.8.0 \ - 0x9e488d3d \ - D2DV_108 - -# Diablo II 1.06b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 05/16/01 18:55:11 419971" \ - 6 \ - 1.0.6.0 \ - 0xb2fb38b7 \ - D2DV_106B - -# Diablo II 1.06 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 04/19/01 02:05:33 419971" \ - 6 \ - 1.0.6.0 \ - 0xaa348e13 \ - D2DV_106 - -# Diablo II 1.05b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 02/01/01 21:21:16 399491" \ - 5 \ - 1.0.5.1 \ - 0x36869f98 \ - D2DV_105B - -# Diablo II 1.05 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 01/27/01 01:16:00 399491" \ - 5 \ - 1.0.5.0 \ - 0xd6dec09b \ - D2DV_105 - -# Diablo II 1.04c -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 12/23/00 18:28:04 387107" \ - 4 \ - 1.0.4.2 \ - 0xc6d40aaf \ - D2DV_104B - -# Diablo II 1.04b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 12/22/00 18:08:34 403587" \ - 4 \ - 1.0.4.1 \ - 0x3882960e \ - D2DV_104B - -# Diablo II 1.03 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 08/01/00 00:34:00 346243" \ - 3 \ - 1.0.3.0 \ - 0x0120ed90 \ - D2DV_103 - -# Diablo II 1.01 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 06/26/00 22:31:00 346243" \ - 1 \ - 1.0.0.1 \ - 0x5aef7e66 \ - D2DV_101 - -# Diablo II 1.00 (installation) -# 1.00 have 0 versionid used as a wildchar in versioncheck -# but we don't care because we have exeinfo and checksum -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - D2DV \ - "Game.exe 01/01/97 02:05:09 309379" \ - 0 \ - 1.0.0.1 \ - 0xac5e46cb \ - D2DV_100 - -# ==================== StarCraft Broodwar entries ============================ - -# Starcraft - Broodwar (Expansion) 1.16 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "StarCraft.exe 11/06/08 01:10:59 1220608" \ - 0x000000d3 \ - 1.16.0.1 \ - 0xd206c475 \ - SEXP_116 - -# Starcraft - Broodwar (Expansion) 1.16 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "StarCraft.exe 11/06/08 01:10:59 1220608" \ - 0x000000d3 \ - 1.16.0.1 \ - 0xd206c475 \ - STAR_116 - -# Starcraft - Broodwar (Expansion) 1.15.3 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "StarCraft.exe 07/25/08 23:34:58 1220608" \ - 0x000000d1 \ - 1.15.3.1 \ - 0x2abff892 \ - SEXP_1153 - -# Starcraft - Broodwar (Expansion) 1.15.3 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "StarCraft.exe 07/25/08 23:34:58 1220608" \ - 0x000000d1 \ - 1.15.3.1 \ - 0x2abff892 \ - STAR_1153 - -# Starcraft - Broodwar (Expansion) 1.15.2 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "StarCraft.exe 01/10/08 20:23:42 1220608" \ - 0x000000d1 \ - 1.15.2.1 \ - 0x8fbdf18d \ - SEXP_1152 - -# Starcraft - Broodwar (Expansion) 1.15.2 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "StarCraft.exe 01/10/08 20:23:42 1220608" \ - 0x000000d1 \ - 1.15.2.1 \ - 0x8fbdf18d \ - STAR_1152 - -# Starcraft - Broodwar (Expansion) 1.15.1 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 07/19/07 02:30:05 1220608" \ - 0x000000d1 \ - 1.15.1.0 \ - 0x99e92077 \ - SEXP_1151 - -# Starcraft - Broodwar (Expansion) 1.15.1 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 07/19/07 02:30:05 1220608" \ - 0x000000d1 \ - 1.15.1.0 \ - 0x99e92077 \ - STAR_1151 - -# Starcraft - Broodwar (Expansion) 1.15 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 05/08/07 19:13:26 1220608" \ - 0x000000d1 \ - 1.15.0.1 \ - 0x1eec4ebc \ - SEXP_115 - -# Starcraft - Broodwar (Expansion) 1.15 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 05/08/07 19:13:26 1220608" \ - 0x000000d1 \ - 1.15.0.1 \ - 0x1eec4ebc \ - STAR_115 - -# Starcraft Broodwar (Expansion) 1.14 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 05/10/06 00:13:58 1216512" \ - 0x000000cf \ - 1.14.0.1 \ - 0xf5280a9e \ - SEXP_114 - -# Starcraft - Broodwar (Expansion) 1.14 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 05/10/06 00:13:58 1216512" \ - 0x000000cf \ - 1.14.0.1 \ - 0xf5280a9e \ - STAR_114 - -# Starcraft - Broodwar (Expansion) 1.13f - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 01/13/06 05:42:48 1146939" \ - 0x000000cd \ - 1.1.3.11 \ - 0x5c5e4509 \ - SEXP_113F - -# Starcraft - Broodwar (Expansion) 1.13f - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 01/13/06 05:42:48 1146939" \ - 0x000000cd \ - 1.1.3.11 \ - 0x5c5e4509 \ - STAR_113F - -# Starcraft - Broodwar (Expansion) 1.13e - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 09/07/05 19:06:42 1093632" \ - 0x000000cd \ - 1.1.3.9 \ - 0xdee278b8 \ - SEXP_113E - -# Starcraft - Broodwar (Expansion) 1.13e - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 09/07/05 19:06:42 1093632" \ - 0x000000cd \ - 1.1.3.9 \ - 0xdee278b8 \ - STAR_113E - -# Starcraft - Broodwar (Expansion) 1.13d - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 08/25/05 22:23:54 1093632" \ - 0x000000cd \ - 1.1.3.7 \ - 0x5b3cbe07 \ - SEXP_113D - -# Starcraft - Broodwar (Expansion) 1.13d - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 08/25/05 22:23:54 1093632" \ - 0x000000cd \ - 1.1.3.7 \ - 0x5b3cbe07 \ - SEXP_113D_STAR - -# Starcraft - Broodwar (Expansion) 1.13c - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 08/18/05 23:42:08 1093632" \ - 0x000000cd \ - 1.1.3.5 \ - 0xbd7aca6e \ - SEXP_113C - -# Starcraft - Broodwar (Expansion) 1.13c - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 08/18/05 23:42:08 1093632" \ - 0x000000cd \ - 1.1.3.5 \ - 0xbd7aca6e \ - SEXP_113C_STAR - -# Starcraft - Broodwar (Expansion) 1.13b - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 08/10/05 22:19:41 1093632" \ - 0x000000cd \ - 1.1.3.3 \ - 0x02cfab16 \ - SEXP_113B - -# Starcraft - Broodwar (Expansion) 1.13b - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 08/10/05 22:19:41 1093632" \ - 0x000000cd \ - 1.1.3.3 \ - 0x02cfab16 \ - SEXP_113B_STAR - -# Starcraft - Broodwar (Expansion) 1.13 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 06/24/05 17:19:30 1093632" \ - 0x000000cd \ - 1.1.3.1 \ - 0x02bb0696 \ - SEXP_113 - -# Starcraft - Broodwar (Expansion) 1.13 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 06/24/05 17:19:30 1093632" \ - 0x000000cd \ - 1.1.3.1 \ - 0x02bb0696 \ - SEXP_113_STAR - -# Starcraft - Broodwar (Expansion) 1.12b - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 02/18/05 02:14:10 1093632" \ - 0x000000cb \ - 1.1.2.3 \ - 0x0eea30bc \ - SEXP_112B - -# Starcraft - Broodwar (Expansion) 1.12b - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 02/10/05 02:14:10 1093632" \ - 0x000000cb \ - 1.1.2.3 \ - 0x0eea30bc \ - SEXP_112B_STAR - -# Starcraft - Broodwar (Expansion) 1.12 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 02/10/05 05:36:14 1093632" \ - 0x000000cb \ - 1.1.2.1 \ - 0x796786fe \ - SEXP_112 - -# Starcraft - Broodwar (Expansion) 1.12 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 02/10/05 05:36:14 1093632" \ - 0x000000cb \ - 1.1.2.1 \ - 0x796786fe \ - SEXP_112_STAR - -# Starcraft - Broodwar (Expansion) 1.11b - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 05/26/04 00:46:00 1048576" \ - 0x000000c9 \ - 1.1.1.3 \ - 0xd1ef9ca4 \ - SEXP_111B - -# Starcraft - Broodwar (Expansion) 1.11b - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 05/26/04 00:46:00 1048576" \ - 0x000000c9 \ - 1.1.1.3 \ - 0xd1ef9ca4 \ - SEXP_111B_STAR - -# Starcraft - Broodwar (Expansion) 1.11 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 04/27/04 05:12:02 1048576" \ - 0x000000c9 \ - 1.1.1.1 \ - 0x706c67e6 \ - SEXP_111 - -# Starcraft - Broodwar (Expansion) 1.11 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 04/27/04 05:12:02 1048576" \ - 0x000000c9 \ - 1.1.1.1 \ - 0x706c67e6 \ - SEXP_111_STAR - -# Starcraft - Broodwar (Expansion) 1.10 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 03/28/03 04:21:58 1064960" \ - 0x000000c7 \ - 1.1.0.1 \ - 0xe20a2ca1 \ - SEXP_110 - -# Starcraft - Broodwar (Expansion) 1.10 - (Starcraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 03/28/03 04:21:58 1064960" \ - 0x000000c7 \ - 1.1.0.1 \ - 0xe20a2ca1 \ - SEXP_110_STAR - -# Starcraft - Broodwar (Expansion) 1.09b - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 02/21/02 20:54:04 1083392" \ - 0x000000c5 \ - 1.0.9.3 \ - 0xf76df1ed \ - SEXP_109B - -# Starcraft - Broodwar (Expansion) 1.09b - (Starcraft mode) -#"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 02/21/02 20:54:04 1083392" \ - 0x000000c5 \ - 1.0.9.3 \ - 0xf76df1ed \ - SEXP_109B_STAR - -# Starcraft - Broodwar (Expansion) 1.09 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 02/05/02 07:17:26 1083392" \ - 0x000000c5 \ - 1.0.9.1 \ - 0x9333e69f \ - SEXP_109 - -# Starcraft - Broodwar (Expansion) 1.09 - (StarCraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 02/05/02 07:17:26 1083392" \ - 0x000000c5 \ - 1.0.9.1 \ - 0x9333e69f \ - SEXP_109_STAR - -# Starcraft - Broodwar (Expansion) 1.08b - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 05/19/01 02:28:22 1082880" \ - 0x000000c3 \ - 1.0.8.11 \ - 0x95b346ec \ - SEXP_108B - -# Starcraft - Broodwar (Expansion) 1.08b - (StarCraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 05/19/01 02:28:22 1082880" \ - 0x000000c3 \ - 1.0.8.11 \ - 0x95b346ec \ - SEXP_108B_STAR - -# Starcraft - Broodwar (Expansion) 1.08 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 05/17/01 03:51:08 1082880" \ - 0x000000c3 \ - 1.0.8.1 \ - 0x5063884e \ - SEXP_108 - -# Starcraft - Broodwar (Expansion) 1.08 - (StarCraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 05/17/01 03:51:08 1082880" \ - 0x000000c3 \ - 1.0.8.1 \ - 0x5063884e \ - SEXP_108_STAR - -# Starcraft - Broodwar (Expansion) 1.07 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 10/30/99 03:54:30 1043456" \ - 0x000000c1 \ - 1.0.7.1 \ - 0xacf13dce \ - SEXP_107 - -# Starcraft - Broodwar (Expansion) 1.07 - (StarCraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 10/30/99 03:54:30 1043456" \ - 0x000000c1 \ - 1.0.7.1 \ - 0xacf13dce \ - SEXP_107_STAR - -# Starcraft - Broodwar (Expansion) 1.06 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 09/28/99 19:41:34 1042432" \ - 0x000000bd \ - 1.0.6.1 \ - 0x1acce912 \ - SEXP_106 - -# Starcraft - Broodwar (Expansion) 1.06 - (StarCraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 09/28/99 19:41:34 1042432" \ - 0x000000bd \ - 1.0.6.1 \ - 0x1acce912 \ - SEXP_106_STAR - -# Starcraft - Broodwar (Expansion) 1.05 - (Broodwar mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - SEXP \ - "starcraft.exe 03/08/99 22:37:32 1042432" \ - 0x000000bd \ - 1.0.5.1 \ - 0x9a2d64e6 \ - SEXP_105 - -# Starcraft - Broodwar (Expansion) 1.05 - (StarCraft mode) -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 03/08/99 22:37:32 1042432" \ - 0x000000bd \ - 1.0.5.1 \ - 0x9a2d64e6 \ - SEXP_105_STAR - -# Starcraft - Broodwar (Expansion) 1.04 - (Broodwar mode) -# exeinfo is NULL here because it's the date of install -# but that's ok because we have the checksum -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - SEXP \ - NULL \ - 0x000000bb \ - 1.0.4.1 \ - 0xfd581427 \ - SEXP_104 - -# Starcraft - Broodwar (Expansion) 1.04 - (StarCraft mode) -# exeinfo is NULL here because it's the date of install -# but that's ok because we have the checksum -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - STAR \ - NULL \ - 0x000000bb \ - 1.0.4.1 \ - 0xfd581427 \ - SEXP_104_STAR - -# ==================== StarCraft (original) entries ========================== - -# Starcraft (Standalone) 1.16 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "StarCraft.exe 11/06/08 01:14:14 1220608" \ - 0x000000d3 \ - 1.16.0.0 \ - 0xcab35ce0 \ - STAR_116 - -# Starcraft (Standalone) 1.15.3 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "StarCraft.exe 07/25/08 23:36:39 1220608" \ - 0x000000d1 \ - 1.15.3.0 \ - 0xc9fc7927 \ - STAR_1153 - -# Starcraft (Standalone) 1.15.2 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "StarCraft.exe 01/08/08 23:45:36 1220608" \ - 0x000000d1 \ - 1.15.2.0 \ - 0x91c0f907 \ - STAR_1152 - -# Starcraft (Standalone) 1.15.1 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 07/19/07 02:30:53 1220608" \ - 0x000000d1 \ - 1.15.1.0 \ - 0x34702730 \ - STAR_1151 - -# Starcraft (Standalone) 1.15 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 05/08/07 19:14:08 1220608" \ - 0x000000d1 \ - 1.15.0.0 \ - 0xd892b992 \ - STAR_115 - -# Starcraft (Standalone) 1.14 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 05/19/06 00:05:06 1216512" \ - 0x000000cf \ - 1.14.0.0 \ - 0x1f109657 \ - STAR_114 - -# Starcraft (Standalone) 1.13f -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 01/13/06 05:46:36 1146939" \ - 0x000000cd \ - 1.1.3.10 \ - 0x0e3d3732 \ - STAR_113F - -# Starcraft (Standalone) 1.13e -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 09/07/05 19:31:20 1093632" \ - 0x000000cd \ - 1.1.3.8 \ - 0xa21f8b5c \ - STAR_113E - -# Starcraft (Standalone) 1.13d -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 08/25/05 22:36:16 1093632" \ - 0x000000cd \ - 1.1.3.6 \ - 0xdef3af2b \ - STAR_113D - -# Starcraft (Standalone) 1.13c -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 08/18/05 23:52:34 1093632" \ - 0x000000cd \ - 1.1.3.4 \ - 0xe888a7ce \ - STAR_113C - -# Starcraft (Standalone) 1.13b -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 08/10/05 22:24:39 1093632" \ - 0x000000cd \ - 1.1.3.2 \ - 0x934ce3ac \ - STAR_113B - -# Starcraft (Standalone) 1.13 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 06/24/05 17:26:11 1093632" \ - 0x000000cd \ - 1.1.3.0 \ - 0x3462622c \ - STAR_113 - -# Starcraft (Standalone) 1.12b -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 02/18/05 02:18:55 1093632" \ - 0x000000cb \ - 1.1.2.2 \ - 0x90abd67f \ - STAR_112B - -# Starcraft (Standalone) 1.12 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 02/10/05 05:37:38 1093632" \ - 0x000000cb \ - 1.1.2.0 \ - 0x2132ae0b \ - STAR_112 - -# Starcraft 1.11b -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 05/26/04 01:25:14 1048576" \ - 0x000000c9 \ - 1.1.1.2 \ - 0x7e9ce2f1 \ - STAR_111B - -# Starcraft 1.11 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 04/27/04 05:46:02 1048576" \ - 0x000000c9 \ - 1.1.1.1 \ - 0xff88c010 \ - STAR_111 - -# Starcraft 1.10 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 03/27/03 04:30:39 1064960" \ - 0x000000c7 \ - 1.1.0.0 \ - 0x8515bf6a \ - STAR_110 - -# Starcraft 1.09b -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 02/21/02 21:48:41 1083392" \ - 0x000000c5 \ - 1.0.9.2 \ - 0x96d28df0 \ - STAR_109B - -# Starcraft 1.09 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 02/05/02 07:37:20 1083392" \ - 0x000000c5 \ - 1.0.9.0 \ - 0x251098be \ - STAR_109 - -# Starcraft 1.08b -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 05/19/01 02:01:50 1082880" \ - 0x000000c3 \ - 1.0.8.10 \ - 0x7859c173 \ - STAR_108B - -# Starcraft 1.08 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 05/17/01 04:12:40 1082880" \ - 0x000000c3 \ - 1.0.8.0 \ - 0x7b58b906 \ - STAR_108 - -# Starcraft 1.07 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 10/30/99 02:20:12 1043456" \ - 0x000000c1 \ - 1.0.7.0 \ - 0x79126108 \ - STAR_107 - -# Starcraft 1.06 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 09/25/99 22:40:10 1042432" \ - 0x000000bd \ - 1.0.6.0 \ - 0xfea28485 \ - STAR_106 - -# Starcraft 1.05 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 03/08/99 22:41:50 1042432" \ - 0x000000bd \ - 1.0.5.0 \ - 0xc243123c \ - STAR_105 - -# # Starcraft 1.04 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 12/16/98 22:24:18 1041408" \ - 0x000000bb \ - 1.0.4.0 \ - 0x2779bdc8 \ - STAR_104 - -# Starcraft 1.03 -"A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B" \ - "IX86ver1.mpq" \ - IX86 \ - STAR \ - "starcraft.exe 10/09/98 23:18:22 987648" \ - 0x000000a9 \ - 1.0.3.0 \ - 0x1309eaad \ - STAR_103 - -# ==================== Diablo (original) entries ======================= - -# Diablo 109b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - DRTL \ - "Diablo.exe 05/18/01 23:10:57 757760" \ - 0x0000002a \ - 1.0.9.2 \ - 0x23135c73 \ - DRTL_109B - -# Diablo 109 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - DRTL \ - "Diablo.exe 05/12/01 00:53:17 757760" \ - 0x0000002a \ - 1.0.9.1 \ - 0x0dcb0513 \ - DRTL_109 - -# Diablo 108 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - DRTL \ - "Diablo.exe 05/24/00 01:16:01 764928" \ - 0x00000028 \ - 1.0.8.1 \ - 0x09eb1213 \ - DRTL_108 - -# =========================== WARCRAFT2 BNE =========================== - -# Warcraft II BNE 2.02b -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - W2BN \ - "Warcraft II BNE.exe 05/21/01 21:52:22 712704" \ - 0x0000004f \ - 2.0.2.1 \ - 0xff0d4c4a \ - W2BN_202B - -# Warcraft II BNE 2.02a -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - "ver-IX86-1.mpq" \ - IX86 \ - W2BN \ - "Warcraft II BNE.exe 05/15/01 20:53:19 712704" \ - 0x0000004f \ - 2.0.2.0 \ - 0xb52bad87 \ - W2BN_202A - -# Warcraft II BNE v2.01 -"A=2383634235 B=711286254 C=3710735432 4 A=A+S B=B^C C=C+A A=A^B" \ - "IX86ver4.mpq" \ - IX86 \ - W2BN \ - "Warcraft II BNE.exe ??/??/?? ??:??:?? 704512" \ - 0x0000004b \ - 2.0.0.154 \ - 0x0 \ - W2BN_201 - -# Warcraft II BNE v2.00 -"A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B" \ - IX86ver1.mpq \ - IX86 \ - W2BN \ - NULL \ - 0x0000004b \ - 2.0.0.154 \ - 0x7133f5ed \ - W2BN_200 diff --git a/conf/versioncheck.json.in b/conf/versioncheck.json.in new file mode 100644 index 0000000..62b4f2e --- /dev/null +++ b/conf/versioncheck.json.in @@ -0,0 +1,1855 @@ +{ + "WAR3": { + "IX86": { + "0x1c": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B", + "entries": [ + { + "title": "Warcraft III - ROC 1.28.2.", + "version": "1.28.2.227", + "hash": "0x2dc96bf5", + "fileMetadata": "War3.exe 05/20/17 13:25:29 558568", + "versionTag": "WAR3_1282" + }, + { + "title": "Warcraft III - ROC 1.28.1.", + "version": "1.28.1.197", + "hash": "0x1fbde0fd", + "fileMetadata": "War3.exe 04/17/17 12:06:57 536040", + "versionTag": "WAR3_1281" + }, + { + "title": "Warcraft III - ROC 1.28", + "version": "1.28.0.37", + "hash": "0xe00c3096", + "fileMetadata": "War3.exe 04/17/17 12:06:57 536040", + "versionTag": "WAR3_128" + } + ] + }, + "0x1b": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "B=454282227 C=2370009462 A=2264812340 4 A=A^S B=B-C C=C-A A=A+B", + "entries": [ + { + "title": "Warcraft III - ROC 1.27b", + "version": "1.27.1.173", + "hash": "0x4026b5b0", + "fileMetadata": "War3.exe 12/09/16 06:05:09 515048", + "versionTag": "WAR3_127B" + }, + { + "title": "Warcraft III - ROC 1.27a", + "version": "1.27.0.16", + "hash": "0x960947fd", + "fileMetadata": "War3.exe 08/05/16 03:15:27 514536", + "versionTag": "WAR3_127A" + } + ] + }, + "0x1a": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.26a", + "version": "1.26.0.1", + "hash": "0xf2e7cec2", + "fileMetadata": "war3.exe 03/18/11 20:03:55 471040", + "versionTag": "WAR3_126A" + } + ] + }, + "0x19": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.25b", + "version": "1.25.1.253", + "hash": "0x41a8664c", + "fileMetadata": "war3.exe 02/12/11 03:32:48 471040", + "versionTag": "W3XP_125B" + } + ] + }, + "0x18": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.24e", + "version": "1.24.4.243", + "hash": "0xde4443c5", + "fileMetadata": "war3.exe 02/26/10 20:41:08 471040", + "versionTag": "W3XP_124E" + }, + { + "title": "Warcraft III - ROC 1.24d", + "version": "1.24.3.240", + "hash": "0x2a7b4a96", + "fileMetadata": "war3.exe 01/13/10 00:35:02 471040", + "versionTag": "WAR3_124D" + }, + { + "title": "Warcraft III - ROC 1.24c", + "version": "1.24.2.234", + "hash": "0x307c4598", + "fileMetadata": "war3.exe 10/21/09 19:14:23 471040", + "versionTag": "WAR3_124C" + }, + { + "title": "Warcraft III - ROC 1.24b", + "version": "1.24.1.230", + "hash": "0xcaa59e30", + "fileMetadata": "war3.exe 08/07/09 19:20:53 471040", + "versionTag": "WAR3_124B" + }, + { + "title": "Warcraft III - ROC 1.24a", + "version": "1.24.0.228", + "hash": "0xf3c22f0b", + "fileMetadata": "war3.exe 07/23/09 20:15:12 471040", + "versionTag": "WAR3_124A" + } + ] + }, + "0x17": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.23a", + "version": "1.23.0.208", + "hash": "0xfad9bc05", + "fileMetadata": "war3.exe 03/12/09 23:23:40 471040", + "versionTag": "WAR3_123A" + } + ] + }, + "0x16": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.22a", + "version": "1.22.0.184", + "hash": "0x909998db", + "fileMetadata": "war3.exe 06/27/08 00:01:35 471040", + "versionTag": "WAR3_122A" + } + ] + }, + "0x15": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.21b", + "version": "1.21.1.156", + "hash": "0x1b735294", + "fileMetadata": "war3.exe 07/19/07 18:41:12 409660", + "versionTag": "WAR3_121B" + }, + { + "title": "Warcraft III - ROC 1.21a", + "version": "1.21.0.119", + "hash": "0xab35b5f1", + "fileMetadata": "war3.exe 12/28/06 20:35:21 1572307", + "versionTag": "WAR3_121A" + } + ] + }, + "0x14": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.20e", + "version": "1.20.4.186", + "hash": "0x8771b225", + "fileMetadata": "war3.exe 06/13/06 06:13:05 1572307", + "versionTag": "WAR3_120E" + }, + { + "title": "Warcraft III - ROC 1.20d", + "version": "1.20.3.182", + "hash": "0xbbd3e672", + "fileMetadata": "War3.exe 04/06/06 00:19:58 1572307", + "versionTag": "WAR3_120D" + }, + { + "title": "Warcraft III - ROC 1.20c", + "version": "1.20.2.177", + "hash": "0x97c8e59b", + "fileMetadata": "War3.exe 01/05/06 02:50:54 1572307", + "versionTag": "WAR3_120C" + }, + { + "title": "Warcraft III - ROC 1.20b", + "version": "1.0.20.168", + "hash": "0x57e714c1", + "fileMetadata": "War3.exe 12/07/05 23:59:26 1572307", + "versionTag": "WAR3_120B" + }, + { + "title": "Warcraft III - ROC 1.20a", + "version": "1.0.20.160", + "hash": "0xdfe00da5", + "fileMetadata": "War3.exe 09/23/05 03:16:24 1572307", + "versionTag": "WAR3_120A" + } + ] + }, + "0x13": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.19b", + "version": "1.0.19.158", + "hash": "0x9c73744d", + "fileMetadata": "war3.exe 09/20/05 04:03:24 1572307", + "versionTag": "WAR3_119B" + }, + { + "title": "Warcraft III - ROC 1.19a", + "version": "1.0.19.153", + "hash": "0x892fa500", + "fileMetadata": "war3.exe 09/07/05 18:58:53 1572307", + "versionTag": "WAR3_119A" + } + ] + }, + "0x12": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.18a", + "version": "1.0.18.142", + "hash": "0x087fb830", + "fileMetadata": "war3.exe 02/18/05 04:38:43 1572307", + "versionTag": "WAR3_118A" + } + ] + }, + "0x11": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.17a", + "version": "1.0.17.100", + "hash": "0xd1d89b0e", + "fileMetadata": "war3.exe 09/15/04 22:42:18 1568211", + "versionTag": "WAR3_117A" + } + ] + }, + "0x10": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.16a", + "version": "1.0.16.38", + "hash": "0xcc882269", + "fileMetadata": "War3.exe 06/28/04 23:37:02 1568211", + "versionTag": "WAR3_116A" + } + ] + }, + "0x0f": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.15a", + "version": "1.0.15.156", + "hash": "0x39b3974b", + "fileMetadata": "war3.exe 05/04/04 01:18:12 1568211", + "versionTag": "WAR3_115A" + } + ] + }, + "0x0e": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.14b", + "version": "1.0.14.152", + "hash": "0x36fd6e73", + "fileMetadata": "War3.exe 01/09/04 06:11:44 1568211", + "versionTag": "WAR3_114B" + }, + { + "title": "Warcraft III - ROC 1.14a", + "version": "1.0.14.151", + "hash": "0xa842edef", + "fileMetadata": "War3.exe 01/06/04 03:44:50 1568211", + "versionTag": "WAR3_114A" + } + ] + }, + "0x0d": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.13b", + "version": "1.0.13.149", + "hash": "0x0bb5600d", + "fileMetadata": "War3.exe 12/18/03 00:53:06 1568211", + "versionTag": "WAR3_113B" + } + ] + }, + "0x0c": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - ROC 1.12a", + "version": "1.0.12.148", + "hash": "0xa9c022bd", + "fileMetadata": "war3.exe 07/22/03 22:43:15 1568211", + "versionTag": "WAR3_112A" + } + ] + }, + "0x0a": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - RoC 1.10a", + "version": "1.0.10.146", + "hash": "0x75d1c16f", + "fileMetadata": "war3.exe 06/17/03 16:37:46 1568211", + "versionTag": "WAR3_110A" + } + ] + }, + "0x07": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - RoC 1.07 (Used for TFT 1.07a in RoC Mode)", + "version": "1.0.7.142", + "hash": "0xde80b55b", + "fileMetadata": "NULL", + "versionTag": "WAR3_107A" + } + ] + }, + "0x06": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "WarCraft III - RoC 1.06a", + "version": "1.0.6.0", + "hash": "0x302dca19", + "fileMetadata": "war3.exe 05/23/03 19:50:08 1461715", + "versionTag": "WAR3_106A" + } + ] + }, + "0x05": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "WarCraft III - RoC 1.05a", + "version": "1.0.5.0", + "hash": "0xbe284ba9", + "fileMetadata": "war3.exe 01/30/03 03:08:10 1170899", + "versionTag": "WAR3_105A" + } + ] + }, + "0x04": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "WarCraft III - RoC 1.04c", + "version": "1.0.4.1", + "hash": "0x3f06d9fa", + "fileMetadata": "war3.exe 01/16/03 02:44:48 1166807", + "versionTag": "WAR3_104C" + }, + { + "title": "WarCraft III - RoC 1.04b", + "version": "1.0.3.0", + "hash": "0x56f4cc37", + "fileMetadata": "war3.exe 10/31/02 17:29:22 1068499", + "versionTag": "WAR3_104B" + } + ] + }, + "0x03": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - RoC 1.03a", + "version": "1.0.3.0", + "hash": "0xae4ac7fa", + "fileMetadata": "war3.exe 10/08/02 20:54:49 713495", + "versionTag": "WAR3_103A" + } + ] + }, + "0x02": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - RoC 1.02a", + "version": "1.0.1.0", + "hash": "0xa67d9a0e", + "fileMetadata": "war3.exe 09/06/02 02:41:02 713495", + "versionTag": "WAR3_102A" + } + ] + }, + "0x01": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - RoC 1.01b", + "version": "1.0.1.1", + "hash": "0x062ef7dc", + "fileMetadata": "war3.exe 07/10/02 18:59:10 713495", + "versionTag": "WAR3_101B" + }, + { + "title": "Warcraft III - RoC 1.01", + "version": "1.0.1.0", + "hash": "0x62c2e1a2", + "fileMetadata": "war3.exe 06/28/02 18:41:18 713495", + "versionTag": "WAR3_101" + } + ] + }, + "0x00": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - RoC 1.00", + "version": "1.0.0.0", + "hash": "0xf891f129", + "fileMetadata": "NULL", + "versionTag": "WAR3_100" + } + ] + } + } + }, + "W3XP": { + "IX86": { + "0x1c": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.28.5.", + "version": "1.28.5.0", + "hash": "0x60743fc9", + "fileMetadata": "Warcraft III.exe 07/07/17 20:15:59 562152", + "versionTag": "W3XP_1285" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.28.4.", + "version": "1.28.4.184", + "hash": "0xce7bb7ee", + "fileMetadata": "Warcraft III.exe 06/21/17 16:24:03 561128", + "versionTag": "W3XP_1284" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.28.2.", + "version": "1.28.2.227", + "hash": "0x2dc96bf5", + "fileMetadata": "War3.exe 05/20/17 13:25:29 558568", + "versionTag": "W3XP_1282" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.28.1.", + "version": "1.28.1.197", + "hash": "0x1fbde0fd", + "fileMetadata": "War3.exe 04/28/17 19:10:24 558568", + "versionTag": "W3XP_1281" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.28.", + "version": "1.28.0.37", + "hash": "0xe00c3096", + "fileMetadata": "War3.exe 04/17/17 12:06:57 536040", + "versionTag": "W3XP_128" + } + ] + }, + "0x1b": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "B=454282227 C=2370009462 A=2264812340 4 A=A^S B=B-C C=C-A A=A+B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.27b", + "version": "1.27.1.173", + "hash": "0x4026b5b0", + "fileMetadata": "War3.exe 12/09/16 06:05:09 515048", + "versionTag": "W3XP_127B" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.27a", + "version": "1.27.0.16", + "hash": "0x960947fd", + "fileMetadata": "War3.exe 08/05/16 03:15:27 514536", + "versionTag": "W3XP_127A" + } + ] + }, + "0x1a": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.26a", + "version": "1.26.0.1", + "hash": "0xf2e7cec2", + "fileMetadata": "war3.exe 03/18/11 20:03:55 471040", + "versionTag": "W3XP_126A" + } + ] + }, + "0x19": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.25b", + "version": "1.25.1.253", + "hash": "0x41a8664c", + "fileMetadata": "war3.exe 02/12/11 03:32:48 471040", + "versionTag": "W3XP_125B" + } + ] + }, + "0x18": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.24e", + "version": "1.24.4.243", + "hash": "0xde4443c5", + "fileMetadata": "war3.exe 02/26/10 20:41:08 471040", + "versionTag": "W3XP_124E" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.24d", + "version": "1.24.3.240", + "hash": "0x2a7b4a96", + "fileMetadata": "war3.exe 01/13/10 00:35:02 471040", + "versionTag": "W3XP_124D" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.24c", + "version": "1.24.2.234", + "hash": "0x307c4598", + "fileMetadata": "war3.exe 10/21/09 19:14:23 471040", + "versionTag": "W3XP_124C" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.24b", + "version": "1.24.1.230", + "hash": "0xcaa59e30", + "fileMetadata": "war3.exe 08/07/09 19:20:53 471040", + "versionTag": "W3XP_124B" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.24a", + "version": "1.24.0.228", + "hash": "0xf3c22f0b", + "fileMetadata": "war3.exe 07/23/09 20:15:12 471040", + "versionTag": "W3XP_124A" + } + ] + }, + "0x17": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.23a", + "version": "1.23.0.208", + "hash": "0xfad9bc05", + "fileMetadata": "war3.exe 03/12/09 23:23:40 471040", + "versionTag": "W3XP_123A" + } + ] + }, + "0x16": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.22a", + "version": "1.22.0.184", + "hash": "0x909998db", + "fileMetadata": "war3.exe 06/27/08 00:01:35 471040", + "versionTag": "W3XP_122A" + } + ] + }, + "0x15": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.21b", + "version": "1.21.1.156", + "hash": "0x1b735294", + "fileMetadata": "war3.exe 07/19/07 18:41:12 409660", + "versionTag": "W3XP_121B" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.21a", + "version": "1.21.0.119", + "hash": "0xab35b5f1", + "fileMetadata": "war3.exe 12/28/06 20:35:21 1572307", + "versionTag": "W3XP_121A" + } + ] + }, + "0x14": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.20e", + "version": "1.20.4.186", + "hash": "0x8771b225", + "fileMetadata": "war3.exe 06/13/06 06:13:05 1572307", + "versionTag": "W3XP_120E" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.20d", + "version": "1.20.3.182", + "hash": "0xbbd3e672", + "fileMetadata": "War3.exe 04/06/06 00:19:58 1572307", + "versionTag": "W3XP_120D" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.20c", + "version": "1.20.2.177", + "hash": "0x97c8e59b", + "fileMetadata": "War3.exe 01/05/06 02:50:54 1572307", + "versionTag": "W3XP_120C" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.20b", + "version": "1.0.20.168", + "hash": "0x57e714c1", + "fileMetadata": "War3.exe 12/07/05 23:59:26 1572307", + "versionTag": "W3XP_120B" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.20a", + "version": "1.0.20.160", + "hash": "0xdfe00da5", + "fileMetadata": "War3.exe 09/23/05 03:16:24 1572307", + "versionTag": "W3XP_120A" + } + ] + }, + "0x13": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.19b", + "version": "1.0.19.158", + "hash": "0x9c73744d", + "fileMetadata": "war3.exe 09/20/05 04:03:24 1572307", + "versionTag": "W3XP_119B" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.19a", + "version": "1.0.19.153", + "hash": "0x892fa500", + "fileMetadata": "war3.exe 09/07/05 18:58:53 1572307", + "versionTag": "W3XP_119A" + } + ] + }, + "0x12": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.18a", + "version": "1.0.18.142", + "hash": "0x087fb830", + "fileMetadata": "war3.exe 02/18/05 04:38:43 1572307", + "versionTag": "W3XP_118A" + } + ] + }, + "0x11": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.17a", + "version": "1.0.17.100", + "hash": "0xd1d89b0e", + "fileMetadata": "war3.exe 09/15/04 22:42:18 1568211", + "versionTag": "W3XP_117A" + } + ] + }, + "0x10": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.16a", + "version": "1.0.16.38", + "hash": "0xcc882269", + "fileMetadata": "War3.exe 06/28/04 23:37:02 1568211", + "versionTag": "W3XP_116A" + } + ] + }, + "0x0f": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.15a", + "version": "1.0.15.156", + "hash": "0x39b3974b", + "fileMetadata": "war3.exe 05/04/04 01:18:12 1568211", + "versionTag": "W3XP_115A" + } + ] + }, + "0x0e": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.14b", + "version": "1.0.14.152", + "hash": "0x36fd6e73", + "fileMetadata": "War3.exe 01/09/04 06:11:44 1568211", + "versionTag": "W3XP_114B" + }, + { + "title": "Warcraft III - TFT (Expansion) 1.14a", + "version": "1.0.14.151", + "hash": "0xa842edef", + "fileMetadata": "War3.exe 01/06/04 03:44:50 1568211", + "versionTag": "W3XP_114A" + } + ] + }, + "0x0d": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.13b", + "version": "1.0.13.149", + "hash": "0x0bb5600d", + "fileMetadata": "War3.exe 12/18/03 00:53:06 1568211", + "versionTag": "W3XP_113B" + } + ] + }, + "0x0c": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.12a", + "version": "1.0.12.148", + "hash": "0xa9c022bd", + "fileMetadata": "war3.exe 07/22/03 22:43:15 1568211", + "versionTag": "W3XP_112A" + } + ] + }, + "0x0b": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.11a", + "version": "1.0.11.147", + "hash": "0x5fce4f04", + "fileMetadata": "war3.exe 07/01/03 18:22:52 1568211", + "versionTag": "W3XP_111A" + } + ] + }, + "0x0a": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.10a", + "version": "1.0.10.146", + "hash": "0x75d1c16f", + "fileMetadata": "war3.exe 06/17/03 16:37:46 1568211", + "versionTag": "W3XP_110A" + } + ] + }, + "0x07": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft III - TFT (Expansion) 1.07a", + "version": "1.0.7.142", + "hash": "0xde80b55b", + "fileMetadata": "NULL", + "versionTag": "W3XP_107A" + } + ] + } + } + }, + "W2BN": { + "IX86": { + "0x4f": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Warcraft II BNE 2.02b", + "version": "2.0.2.1", + "hash": "0xff0d4c4a", + "fileMetadata": "Warcraft II BNE.exe 05/21/01 21:52:22 712704", + "versionTag": "W2BN_202B" + }, + { + "title": "Warcraft II BNE 2.02a", + "version": "2.0.2.0", + "hash": "0xb52bad87", + "fileMetadata": "Warcraft II BNE.exe 05/15/01 20:53:19 712704", + "versionTag": "W2BN_202A" + } + ] + }, + "0x4b": { + "checkRevisionFile": "IX86ver4.mpq", + "equation": "A=2383634235 B=711286254 C=3710735432 4 A=A+S B=B^C C=C+A A=A^B", + "entries": [ + { + "title": "Warcraft II BNE v2.01", + "version": "2.0.0.154", + "hash": "0x0", + "fileMetadata": "Warcraft II BNE.exe ??/??/?? ??:??:?? 704512", + "versionTag": "W2BN_201" + } + ] + } + } + }, + "STAR": { + "IX86": { + "0xd3": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.16.1 - (Starcraft mode)", + "version": "1.16.1.1", + "hash": "0x0a85372d", + "fileMetadata": "StarCraft.exe 01/09/09 22:57:43 1220608", + "versionTag": "STAR_1161" + }, + { + "title": "Starcraft (Standalone) 1.16.1", + "version": "1.16.1.0", + "hash": "0x02623d85", + "fileMetadata": "StarCraft.exe 01/08/09 05:23:44 1220608", + "versionTag": "STAR_1161" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.16 - (Starcraft mode)", + "version": "1.16.0.1", + "hash": "0xd206c475", + "fileMetadata": "StarCraft.exe 11/06/08 01:10:59 1220608", + "versionTag": "STAR_116" + }, + { + "title": "Starcraft (Standalone) 1.16", + "version": "1.16.0.0", + "hash": "0xcab35ce0", + "fileMetadata": "StarCraft.exe 11/06/08 01:14:14 1220608", + "versionTag": "STAR_116" + } + ] + }, + "0xd1": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.15.3 - (Starcraft mode)", + "version": "1.15.3.1", + "hash": "0x2abff892", + "fileMetadata": "StarCraft.exe 07/25/08 23:34:58 1220608", + "versionTag": "STAR_1153" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.15.2 - (Starcraft mode)", + "version": "1.15.2.1", + "hash": "0x8fbdf18d", + "fileMetadata": "StarCraft.exe 01/10/08 20:23:42 1220608", + "versionTag": "STAR_1152" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.15.1 - (Starcraft mode)", + "version": "1.15.1.0", + "hash": "0x99e92077", + "fileMetadata": "starcraft.exe 07/19/07 02:30:05 1220608", + "versionTag": "STAR_1151" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.15 - (Starcraft mode)", + "version": "1.15.0.1", + "hash": "0x1eec4ebc", + "fileMetadata": "starcraft.exe 05/08/07 19:13:26 1220608", + "versionTag": "STAR_115" + }, + { + "title": "Starcraft (Standalone) 1.15.3", + "version": "1.15.3.0", + "hash": "0xc9fc7927", + "fileMetadata": "StarCraft.exe 07/25/08 23:36:39 1220608", + "versionTag": "STAR_1153" + }, + { + "title": "Starcraft (Standalone) 1.15.2", + "version": "1.15.2.0", + "hash": "0x91c0f907", + "fileMetadata": "StarCraft.exe 01/08/08 23:45:36 1220608", + "versionTag": "STAR_1152" + }, + { + "title": "Starcraft (Standalone) 1.15", + "version": "1.15.0.0", + "hash": "0xd892b992", + "fileMetadata": "starcraft.exe 05/08/07 19:14:08 1220608", + "versionTag": "STAR_115" + } + ] + }, + "0xcf": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.14 - (Starcraft mode)", + "version": "1.14.0.1", + "hash": "0xf5280a9e", + "fileMetadata": "starcraft.exe 05/10/06 00:13:58 1216512", + "versionTag": "STAR_114" + }, + { + "title": "Starcraft (Standalone) 1.14", + "version": "1.14.0.0", + "hash": "0x1f109657", + "fileMetadata": "starcraft.exe 05/19/06 00:05:06 1216512", + "versionTag": "STAR_114" + } + ] + }, + "0xcd": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.13f - (Starcraft mode)", + "version": "1.1.3.11", + "hash": "0x5c5e4509", + "fileMetadata": "starcraft.exe 01/13/06 05:42:48 1146939", + "versionTag": "STAR_113F" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.13e - (Starcraft mode)", + "version": "1.1.3.9", + "hash": "0xdee278b8", + "fileMetadata": "starcraft.exe 09/07/05 19:06:42 1093632", + "versionTag": "STAR_113E" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.13d - (Starcraft mode)", + "version": "1.1.3.7", + "hash": "0x5b3cbe07", + "fileMetadata": "starcraft.exe 08/25/05 22:23:54 1093632", + "versionTag": "SEXP_113D_STAR" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.13c - (Starcraft mode)", + "version": "1.1.3.5", + "hash": "0xbd7aca6e", + "fileMetadata": "starcraft.exe 08/18/05 23:42:08 1093632", + "versionTag": "SEXP_113C_STAR" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.13b - (Starcraft mode)", + "version": "1.1.3.3", + "hash": "0x02cfab16", + "fileMetadata": "starcraft.exe 08/10/05 22:19:41 1093632", + "versionTag": "SEXP_113B_STAR" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.13 - (Starcraft mode)", + "version": "1.1.3.1", + "hash": "0x02bb0696", + "fileMetadata": "starcraft.exe 06/24/05 17:19:30 1093632", + "versionTag": "SEXP_113_STAR" + }, + { + "title": "Starcraft (Standalone) 1.13f", + "version": "1.1.3.10", + "hash": "0x0e3d3732", + "fileMetadata": "starcraft.exe 01/13/06 05:46:36 1146939", + "versionTag": "STAR_113F" + }, + { + "title": "Starcraft (Standalone) 1.13e", + "version": "1.1.3.8", + "hash": "0xa21f8b5c", + "fileMetadata": "starcraft.exe 09/07/05 19:31:20 1093632", + "versionTag": "STAR_113E" + }, + { + "title": "Starcraft (Standalone) 1.13d", + "version": "1.1.3.6", + "hash": "0xdef3af2b", + "fileMetadata": "starcraft.exe 08/25/05 22:36:16 1093632", + "versionTag": "STAR_113D" + }, + { + "title": "Starcraft (Standalone) 1.13c", + "version": "1.1.3.4", + "hash": "0xe888a7ce", + "fileMetadata": "starcraft.exe 08/18/05 23:52:34 1093632", + "versionTag": "STAR_113C" + }, + { + "title": "Starcraft (Standalone) 1.13b", + "version": "1.1.3.2", + "hash": "0x934ce3ac", + "fileMetadata": "starcraft.exe 08/10/05 22:24:39 1093632", + "versionTag": "STAR_113B" + }, + { + "title": "Starcraft (Standalone) 1.13", + "version": "1.1.3.0", + "hash": "0x3462622c", + "fileMetadata": "starcraft.exe 06/24/05 17:26:11 1093632", + "versionTag": "STAR_113" + } + ] + }, + "0xcb": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.12b - (Starcraft mode)", + "version": "1.1.2.3", + "hash": "0x0eea30bc", + "fileMetadata": "starcraft.exe 02/10/05 02:14:10 1093632", + "versionTag": "SEXP_112B_STAR" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.12 - (Starcraft mode)", + "version": "1.1.2.1", + "hash": "0x796786fe", + "fileMetadata": "starcraft.exe 02/10/05 05:36:14 1093632", + "versionTag": "SEXP_112_STAR" + }, + { + "title": "Starcraft (Standalone) 1.12b", + "version": "1.1.2.2", + "hash": "0x90abd67f", + "fileMetadata": "starcraft.exe 02/18/05 02:18:55 1093632", + "versionTag": "STAR_112B" + }, + { + "title": "Starcraft (Standalone) 1.12", + "version": "1.1.2.0", + "hash": "0x2132ae0b", + "fileMetadata": "starcraft.exe 02/10/05 05:37:38 1093632", + "versionTag": "STAR_112" + } + ] + }, + "0xc9": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.11b - (Starcraft mode)", + "version": "1.1.1.3", + "hash": "0xd1ef9ca4", + "fileMetadata": "starcraft.exe 05/26/04 00:46:00 1048576", + "versionTag": "SEXP_111B_STAR" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.11 - (Starcraft mode)", + "version": "1.1.1.1", + "hash": "0x706c67e6", + "fileMetadata": "starcraft.exe 04/27/04 05:12:02 1048576", + "versionTag": "SEXP_111_STAR" + }, + { + "title": "Starcraft 1.11b", + "version": "1.1.1.2", + "hash": "0x7e9ce2f1", + "fileMetadata": "starcraft.exe 05/26/04 01:25:14 1048576", + "versionTag": "STAR_111B" + } + ] + }, + "0xc7": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.10 - (Starcraft mode)", + "version": "1.1.0.1", + "hash": "0xe20a2ca1", + "fileMetadata": "starcraft.exe 03/28/03 04:21:58 1064960", + "versionTag": "SEXP_110_STAR" + }, + { + "title": "Starcraft 1.10", + "version": "1.1.0.0", + "hash": "0x8515bf6a", + "fileMetadata": "starcraft.exe 03/27/03 04:30:39 1064960", + "versionTag": "STAR_110" + } + ] + }, + "0xc5": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.09 - (StarCraft mode)", + "version": "1.0.9.1", + "hash": "0x9333e69f", + "fileMetadata": "starcraft.exe 02/05/02 07:17:26 1083392", + "versionTag": "SEXP_109_STAR" + }, + { + "title": "Starcraft 1.09b", + "version": "1.0.9.2", + "hash": "0x96d28df0", + "fileMetadata": "starcraft.exe 02/21/02 21:48:41 1083392", + "versionTag": "STAR_109B" + }, + { + "title": "Starcraft 1.09", + "version": "1.0.9.0", + "hash": "0x251098be", + "fileMetadata": "starcraft.exe 02/05/02 07:37:20 1083392", + "versionTag": "STAR_109" + } + ] + }, + "0xc3": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.08b - (StarCraft mode)", + "version": "1.0.8.11", + "hash": "0x95b346ec", + "fileMetadata": "starcraft.exe 05/19/01 02:28:22 1082880", + "versionTag": "SEXP_108B_STAR" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.08 - (StarCraft mode)", + "version": "1.0.8.1", + "hash": "0x5063884e", + "fileMetadata": "starcraft.exe 05/17/01 03:51:08 1082880", + "versionTag": "SEXP_108_STAR" + }, + { + "title": "Starcraft 1.08b", + "version": "1.0.8.10", + "hash": "0x7859c173", + "fileMetadata": "starcraft.exe 05/19/01 02:01:50 1082880", + "versionTag": "STAR_108B" + }, + { + "title": "Starcraft 1.08", + "version": "1.0.8.0", + "hash": "0x7b58b906", + "fileMetadata": "starcraft.exe 05/17/01 04:12:40 1082880", + "versionTag": "STAR_108" + } + ] + }, + "0xc1": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.07 - (StarCraft mode)", + "version": "1.0.7.1", + "hash": "0xacf13dce", + "fileMetadata": "starcraft.exe 10/30/99 03:54:30 1043456", + "versionTag": "SEXP_107_STAR" + }, + { + "title": "Starcraft 1.07", + "version": "1.0.7.0", + "hash": "0x79126108", + "fileMetadata": "starcraft.exe 10/30/99 02:20:12 1043456", + "versionTag": "STAR_107" + } + ] + }, + "0xbd": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.06 - (StarCraft mode)", + "version": "1.0.6.1", + "hash": "0x1acce912", + "fileMetadata": "starcraft.exe 09/28/99 19:41:34 1042432", + "versionTag": "SEXP_106_STAR" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.05 - (StarCraft mode)", + "version": "1.0.5.1", + "hash": "0x9a2d64e6", + "fileMetadata": "starcraft.exe 03/08/99 22:37:32 1042432", + "versionTag": "SEXP_105_STAR" + }, + { + "title": "Starcraft 1.06", + "version": "1.0.6.0", + "hash": "0xfea28485", + "fileMetadata": "starcraft.exe 09/25/99 22:40:10 1042432", + "versionTag": "STAR_106" + }, + { + "title": "Starcraft 1.05", + "version": "1.0.5.0", + "hash": "0xc243123c", + "fileMetadata": "starcraft.exe 03/08/99 22:41:50 1042432", + "versionTag": "STAR_105" + } + ] + }, + "0xbb": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.04 - (StarCraft mode)", + "version": "1.0.4.1", + "hash": "0xfd581427", + "fileMetadata": "NULL", + "versionTag": "SEXP_104_STAR" + }, + { + "title": "# Starcraft 1.04", + "version": "1.0.4.0", + "hash": "0x2779bdc8", + "fileMetadata": "starcraft.exe 12/16/98 22:24:18 1041408", + "versionTag": "STAR_104" + } + ] + }, + "0xa9": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft 1.03", + "version": "1.0.3.0", + "hash": "0x1309eaad", + "fileMetadata": "starcraft.exe 10/09/98 23:18:22 987648", + "versionTag": "STAR_103" + } + ] + } + } + }, + "SEXP": { + "IX86": { + "0xd3": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.16.1 - (Broodwar mode)", + "version": "1.16.1.1", + "hash": "0x0a85372d", + "fileMetadata": "StarCraft.exe 01/09/09 22:57:43 1220608", + "versionTag": "SEXP_1161" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.16 - (Broodwar mode)", + "version": "1.16.0.1", + "hash": "0xd206c475", + "fileMetadata": "StarCraft.exe 11/06/08 01:10:59 1220608", + "versionTag": "SEXP_116" + } + ] + }, + "0xd1": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.15.3 - (Broodwar mode)", + "version": "1.15.3.1", + "hash": "0x2abff892", + "fileMetadata": "StarCraft.exe 07/25/08 23:34:58 1220608", + "versionTag": "SEXP_1153" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.15.2 - (Broodwar mode)", + "version": "1.15.2.1", + "hash": "0x8fbdf18d", + "fileMetadata": "StarCraft.exe 01/10/08 20:23:42 1220608", + "versionTag": "SEXP_1152" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.15.1 - (Broodwar mode)", + "version": "1.15.1.0", + "hash": "0x99e92077", + "fileMetadata": "starcraft.exe 07/19/07 02:30:05 1220608", + "versionTag": "SEXP_1151" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.15 - (Broodwar mode)", + "version": "1.15.0.1", + "hash": "0x1eec4ebc", + "fileMetadata": "starcraft.exe 05/08/07 19:13:26 1220608", + "versionTag": "SEXP_115" + } + ] + }, + "0xcf": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft Broodwar (Expansion) 1.14 - (Broodwar mode)", + "version": "1.14.0.1", + "hash": "0xf5280a9e", + "fileMetadata": "starcraft.exe 05/10/06 00:13:58 1216512", + "versionTag": "SEXP_114" + } + ] + }, + "0xcd": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.13f - (Broodwar mode)", + "version": "1.1.3.11", + "hash": "0x5c5e4509", + "fileMetadata": "starcraft.exe 01/13/06 05:42:48 1146939", + "versionTag": "SEXP_113F" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.13e - (Broodwar mode)", + "version": "1.1.3.9", + "hash": "0xdee278b8", + "fileMetadata": "starcraft.exe 09/07/05 19:06:42 1093632", + "versionTag": "SEXP_113E" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.13d - (Broodwar mode)", + "version": "1.1.3.7", + "hash": "0x5b3cbe07", + "fileMetadata": "starcraft.exe 08/25/05 22:23:54 1093632", + "versionTag": "SEXP_113D" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.13c - (Broodwar mode)", + "version": "1.1.3.5", + "hash": "0xbd7aca6e", + "fileMetadata": "starcraft.exe 08/18/05 23:42:08 1093632", + "versionTag": "SEXP_113C" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.13b - (Broodwar mode)", + "version": "1.1.3.3", + "hash": "0x02cfab16", + "fileMetadata": "starcraft.exe 08/10/05 22:19:41 1093632", + "versionTag": "SEXP_113B" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.13 - (Broodwar mode)", + "version": "1.1.3.1", + "hash": "0x02bb0696", + "fileMetadata": "starcraft.exe 06/24/05 17:19:30 1093632", + "versionTag": "SEXP_113" + } + ] + }, + "0xcb": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.12b - (Broodwar mode)", + "version": "1.1.2.3", + "hash": "0x0eea30bc", + "fileMetadata": "starcraft.exe 02/18/05 02:14:10 1093632", + "versionTag": "SEXP_112B" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.12 - (Broodwar mode)", + "version": "1.1.2.1", + "hash": "0x796786fe", + "fileMetadata": "starcraft.exe 02/10/05 05:36:14 1093632", + "versionTag": "SEXP_112" + } + ] + }, + "0xc9": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.11b - (Broodwar mode)", + "version": "1.1.1.3", + "hash": "0xd1ef9ca4", + "fileMetadata": "starcraft.exe 05/26/04 00:46:00 1048576", + "versionTag": "SEXP_111B" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.11 - (Broodwar mode)", + "version": "1.1.1.1", + "hash": "0x706c67e6", + "fileMetadata": "starcraft.exe 04/27/04 05:12:02 1048576", + "versionTag": "SEXP_111" + } + ] + }, + "0xc7": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.10 - (Broodwar mode)", + "version": "1.1.0.1", + "hash": "0xe20a2ca1", + "fileMetadata": "starcraft.exe 03/28/03 04:21:58 1064960", + "versionTag": "SEXP_110" + } + ] + }, + "0xc5": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.09b - (Broodwar mode)", + "version": "1.0.9.3", + "hash": "0xf76df1ed", + "fileMetadata": "starcraft.exe 02/21/02 20:54:04 1083392", + "versionTag": "SEXP_109B" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.09 - (Broodwar mode)", + "version": "1.0.9.1", + "hash": "0x9333e69f", + "fileMetadata": "starcraft.exe 02/05/02 07:17:26 1083392", + "versionTag": "SEXP_109" + } + ] + }, + "0xc3": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.08b - (Broodwar mode)", + "version": "1.0.8.11", + "hash": "0x95b346ec", + "fileMetadata": "starcraft.exe 05/19/01 02:28:22 1082880", + "versionTag": "SEXP_108B" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.08 - (Broodwar mode)", + "version": "1.0.8.1", + "hash": "0x5063884e", + "fileMetadata": "starcraft.exe 05/17/01 03:51:08 1082880", + "versionTag": "SEXP_108" + } + ] + }, + "0xc1": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.07 - (Broodwar mode)", + "version": "1.0.7.1", + "hash": "0xacf13dce", + "fileMetadata": "starcraft.exe 10/30/99 03:54:30 1043456", + "versionTag": "SEXP_107" + } + ] + }, + "0xbd": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.06 - (Broodwar mode)", + "version": "1.0.6.1", + "hash": "0x1acce912", + "fileMetadata": "starcraft.exe 09/28/99 19:41:34 1042432", + "versionTag": "SEXP_106" + }, + { + "title": "Starcraft - Broodwar (Expansion) 1.05 - (Broodwar mode)", + "version": "1.0.5.1", + "hash": "0x9a2d64e6", + "fileMetadata": "starcraft.exe 03/08/99 22:37:32 1042432", + "versionTag": "SEXP_105" + } + ] + }, + "0xbb": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=2521522835 B=3428392135 C=218673704 4 A=A^S B=B-C C=C+A A=A-B", + "entries": [ + { + "title": "Starcraft - Broodwar (Expansion) 1.04 - (Broodwar mode)", + "version": "1.0.4.1", + "hash": "0xfd581427", + "fileMetadata": "NULL", + "versionTag": "SEXP_104" + } + ] + } + } + }, + "DRTL": { + "IX86": { + "0x2a": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo 109b", + "version": "1.0.9.2", + "hash": "0x23135c73", + "fileMetadata": "Diablo.exe 05/18/01 23:10:57 757760", + "versionTag": "DRTL_109B" + }, + { + "title": "Diablo 109", + "version": "1.0.9.1", + "hash": "0x0dcb0513", + "fileMetadata": "Diablo.exe 05/12/01 00:53:17 757760", + "versionTag": "DRTL_109" + } + ] + }, + "0x28": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo 108", + "version": "1.0.8.1", + "hash": "0x09eb1213", + "fileMetadata": "Diablo.exe 05/24/00 01:16:01 764928", + "versionTag": "DRTL_108" + } + ] + } + } + }, + "D2XP": { + "IX86": { + "0x0e": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II - LoD (Expansion) 1.14d", + "version": "1.14.3.0", + "hash": "0x43f51a83", + "fileMetadata": "Game.exe 05/31/16 19:02:24 3618792", + "versionTag": "D2XP_114D" + }, + { + "title": "Diablo II - LoD (Expansion) 1.14c", + "version": "1.14.2.0", + "hash": "0x6123b0f2", + "fileMetadata": "Game.exe 05/06/16 18:15:44 3586024", + "versionTag": "D2XP_114C" + }, + { + "title": "Diablo II - LoD (Expansion) 1.14b", + "version": "1.14.1.0", + "hash": "0x1373c295", + "fileMetadata": "Game.exe 03/31/16 01:09:01 3590120", + "versionTag": "D2XP_114B" + }, + { + "title": "Diablo II - LoD (Expansion) 1.14a", + "version": "1.14.0.0", + "hash": "0xbcb5b3bb", + "fileMetadata": "Game.exe 02/29/16 17:34:46 3590120", + "versionTag": "D2XP_114A" + } + ] + }, + "0x0d": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II - LoD (Expansion) 1.13c", + "version": "1.0.13.0", + "hash": "0x7686beca", + "fileMetadata": "Game.exe 03/09/10 04:10:51 61440", + "versionTag": "D2XP_113C" + } + ] + }, + "0x09": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II - LoD (Expansion) 1.09b", + "version": "1.0.9.0", + "hash": "0x1436c138", + "fileMetadata": "Game.exe 08/31/01 22:40:56 428163", + "versionTag": "D2XP_109B" + } + ] + }, + "0x08": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II - LoD (Expansion) 1.08", + "version": "1.0.8.0", + "hash": "0x3a8664ff", + "fileMetadata": "Game.exe 06/19/01 02:24:32 428163", + "versionTag": "D2XP_108" + } + ] + }, + "0x07": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II - LoD (Expansion) 1.07", + "version": "1.0.7.0", + "hash": "0x4f3079e6", + "fileMetadata": "Game.exe 01/23/02 06:39:26 424067", + "versionTag": "D2XP_107" + } + ] + } + } + }, + "D2DV": { + "IX86": { + "0x0e": { + "checkRevisionFile": "ver-IX86-1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II 1.14d", + "version": "1.14.3.0", + "hash": "0x6b6a21b5", + "fileMetadata": "Game.exe 05/31/16 19:02:32 3614696", + "versionTag": "D2DV_114D" + }, + { + "title": "Diablo II 1.14c", + "version": "1.14.2.0", + "hash": "0x7d54f650", + "fileMetadata": "Game.exe 05/06/16 18:15:49 3581928", + "versionTag": "D2DV_114C" + }, + { + "title": "Diablo II 1.14b", + "version": "1.14.1.0", + "hash": "0x77f3db54", + "fileMetadata": "Game.exe 06/13/16 02:02:21 3586024", + "versionTag": "D2DV_114B" + }, + { + "title": "Diablo II 1.14a", + "version": "1.14.0.0", + "hash": "0xd9e9368a", + "fileMetadata": "Game.exe 02/29/16 17:34:50 3586024", + "versionTag": "D2DV_114A" + } + ] + }, + "0x0d": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II 1.13c", + "version": "1.0.13.0", + "hash": "0xfc04b9f6", + "fileMetadata": "Game.exe 03/09/10 04:11:00 57344", + "versionTag": "D2DV_113C" + } + ] + }, + "0x09": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II 1.09b", + "version": "1.0.9.0", + "hash": "0xb9cb2cb1", + "fileMetadata": "Game.exe 08/31/01 22:40:35 424067", + "versionTag": "D2DV_109B" + } + ] + }, + "0x08": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II 1.08", + "version": "1.0.8.0", + "hash": "0x9e488d3d", + "fileMetadata": "Game.exe 06/19/01 02:24:04 424067", + "versionTag": "D2DV_108" + } + ] + }, + "0x06": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II 1.06b", + "version": "1.0.6.0", + "hash": "0xb2fb38b7", + "fileMetadata": "Game.exe 05/16/01 18:55:11 419971", + "versionTag": "D2DV_106B" + } + ] + }, + "0x05": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II 1.05b", + "version": "1.0.5.1", + "hash": "0x36869f98", + "fileMetadata": "Game.exe 02/01/01 21:21:16 399491", + "versionTag": "D2DV_105B" + }, + { + "title": "Diablo II 1.05", + "version": "1.0.5.0", + "hash": "0xd6dec09b", + "fileMetadata": "Game.exe 01/27/01 01:16:00 399491", + "versionTag": "D2DV_105" + } + ] + }, + "0x04": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II 1.04c", + "version": "1.0.4.2", + "hash": "0xc6d40aaf", + "fileMetadata": "Game.exe 12/23/00 18:28:04 387107", + "versionTag": "D2DV_104B" + }, + { + "title": "Diablo II 1.04b", + "version": "1.0.4.1", + "hash": "0x3882960e", + "fileMetadata": "Game.exe 12/22/00 18:08:34 403587", + "versionTag": "D2DV_104B" + } + ] + }, + "0x03": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II 1.03", + "version": "1.0.3.0", + "hash": "0x0120ed90", + "fileMetadata": "Game.exe 08/01/00 00:34:00 346243", + "versionTag": "D2DV_103" + } + ] + }, + "0x01": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II 1.01", + "version": "1.0.0.1", + "hash": "0x5aef7e66", + "fileMetadata": "Game.exe 06/26/00 22:31:00 346243", + "versionTag": "D2DV_101" + } + ] + }, + "0x00": { + "checkRevisionFile": "IX86ver1.mpq", + "equation": "A=3845581634 B=880823580 C=1363937103 4 A=A-S B=B-C C=C-A A=A-B", + "entries": [ + { + "title": "Diablo II 1.00 (installation)", + "version": "1.0.0.1", + "hash": "0xac5e46cb", + "fileMetadata": "Game.exe 01/01/97 02:05:09 309379", + "versionTag": "D2DV_100" + } + ] + } + } + } +} \ No newline at end of file diff --git a/docs/versioncheck.md b/docs/versioncheck.md new file mode 100644 index 0000000..e94dc4a --- /dev/null +++ b/docs/versioncheck.md @@ -0,0 +1,16 @@ +# VersionCheck + +### Configuration File +The configuration file is in standard JSON format and consists of arrays for each Battle.net game client whose names are a four-letter string. + +Within the game client arrays are arrays for platform whose names are also a four-letter string (i.e. `IX86`, `PMAC`, `XMAC`). + +Within the platform arrays are arrays for the game client's version ID, traditionally known as a "version byte". The version ID is traditionally written in hex format, as indicated with a preceding "0x" or "0X", but can also be in decimal format. The version ID array consists of two pairs, `checkRevisionFile` and `equation`, and an array, `entries`. + +The `entries` array consists of five pairs: `title`, `version`, `hash`, `fileMetadata`, `versionTag`. +- `title`: This is to assist the reader of the configuration file, it does not affect the entry in any way. +- `version`: The version number returned by CheckRevision which is obtained from the [VERSIONINFO](https://msdn.microsoft.com/en-us/library/aa381058) resource of the game file. See sample implementation: https://github.com/pvpgn/CheckRevision +- `hash`: The hash returned by CheckRevision which uses up to three files from the game to produce the hash. See sample implementation: https://github.com/pvpgn/CheckRevision +- `fileMetadata`: The string returned by CheckRevision which consists of the game's filename, last modified date, last modified time, and filesize, all separated by one space (e.g. `war3.exe 08/16/09 19:21:59 471040`). See sample implementation: https://github.com/pvpgn/CheckRevision + - Note: This pair is currently unused by PvPGN, but may be used in the future. +- `versionTag`: An arbitrary string that must be unique from all other version tags. It is traditionally in the form of the four-letter game string, followed by an underscore and the version (e.g. `WAR3_1282` is used for WarCraft 3: Reign of Chaos 1.28.2). \ No newline at end of file diff --git a/src/bnetd/anongame.cpp b/src/bnetd/anongame.cpp index f0d5536..609f531 100644 --- a/src/bnetd/anongame.cpp +++ b/src/bnetd/anongame.cpp @@ -72,7 +72,6 @@ namespace pvpgn /**********************************************************************************/ static t_connection *_connlist_find_connection_by_uid(int uid); - static char const *_conn_get_versiontag(t_connection * c); static int _anongame_gametype_to_queue(int type, int gametype); static int _anongame_level_by_queue(t_connection * c, int queue); @@ -95,11 +94,6 @@ namespace pvpgn return connlist_find_connection_by_account(accountlist_find_account_by_uid(uid)); } - static char const *_conn_get_versiontag(t_connection * c) - { - return versioncheck_get_versiontag(conn_get_versioncheck(c)); - } - /**********/ static char const *_anongame_queue_to_string(int queue) @@ -541,7 +535,7 @@ namespace pvpgn md = (t_matchdata*)xmalloc(sizeof(t_matchdata)); md->c = c; md->map_prefs = map_prefs; - md->versiontag = _conn_get_versiontag(c); + md->versiontag = conn_get_versioncheck(c) ? conn_get_versioncheck(c)->get_version_tag().c_str() : nullptr; list_append_data(matchlists[queue][level], md); @@ -834,7 +828,11 @@ namespace pvpgn LIST_TRAVERSE(matchlists[queue][level + delta], curr) { md = (t_matchdata*)elem_get_data(curr); - if (md->versiontag && _conn_get_versiontag(c) && !std::strcmp(md->versiontag, _conn_get_versiontag(c)) && (cur_prefs & md->map_prefs)) { + if (md->versiontag + && conn_get_versioncheck(c) + && !std::strcmp(md->versiontag, conn_get_versioncheck(c)->get_version_tag().c_str()) + && (cur_prefs & md->map_prefs)) + { /* set maxlevel and minlevel to keep all players within 6 levels */ maxlevel = (level + delta + diff < maxlevel) ? level + delta + diff : maxlevel; minlevel = (level + delta - diff > minlevel) ? level + delta - diff : minlevel; diff --git a/src/bnetd/command.cpp b/src/bnetd/command.cpp index 1a9bd01..f273dac 100644 --- a/src/bnetd/command.cpp +++ b/src/bnetd/command.cpp @@ -4627,7 +4627,7 @@ namespace pvpgn // disallow get/set value for password hash and username (hash can be cracked easily, account name should be permanent) - if (strcasecmp(key, "bnet\\acct\\passhash1") == 0 || strcasecmp(key, "bnet\\acct\\username") == 0 || strcasecmp(key, "bnet\\username") == 0) + if (strcasecmp(key, "bnet\\acct\\passhash1") == 0 || strcasecmp(key, "bnet\\acct\\username") == 0 || strcasecmp(key, "bnet\\acct\\verifier") == 0 || strcasecmp(key, "bnet\\acct\\salt") == 0 || strcasecmp(key, "bnet\\username") == 0) { message_send_text(c, message_type_info, c, localize(c, "Access denied due to security reasons.")); return -1; diff --git a/src/bnetd/connection.cpp b/src/bnetd/connection.cpp index f1fe95d..7fde5cb 100644 --- a/src/bnetd/connection.cpp +++ b/src/bnetd/connection.cpp @@ -402,7 +402,7 @@ namespace pvpgn temp->protocol.client.clientexe = NULL; temp->protocol.client.owner = NULL; temp->protocol.client.cdkey = NULL; - temp->protocol.client.versioncheck = NULL; + temp->protocol.client.versioncheck = nullptr; temp->protocol.account = NULL; temp->protocol.chat.channel = NULL; temp->protocol.chat.last_message = now; @@ -617,9 +617,6 @@ namespace pvpgn if (c->protocol.account) watchlist->dispatch(c->protocol.account, NULL, c->protocol.client.clienttag, Watch::ET_logout); - if (c->protocol.client.versioncheck) - versioncheck_destroy((t_versioncheck*)c->protocol.client.versioncheck); /* avoid warning */ - if (c->protocol.chat.lastsender) xfree((void *)c->protocol.chat.lastsender); /* avoid warning */ @@ -3199,34 +3196,29 @@ namespace pvpgn } - extern t_versioncheck * conn_get_versioncheck(t_connection * c) + const VersionCheck *conn_get_versioncheck(t_connection *c) { if (!c) { eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); - return NULL; + return nullptr; } return c->protocol.client.versioncheck; } - extern int conn_set_versioncheck(t_connection * c, t_versioncheck * versioncheck) + bool conn_set_versioncheck(t_connection *c, const VersionCheck* versioncheck) { if (!c) { eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); - return -1; - } - if (!versioncheck) - { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL versioncheck"); - return -1; + return false; } c->protocol.client.versioncheck = versioncheck; - return 0; + return true; } extern int conn_get_echoback(t_connection * c) diff --git a/src/bnetd/connection.h b/src/bnetd/connection.h index 396a9d1..0fbbf35 100644 --- a/src/bnetd/connection.h +++ b/src/bnetd/connection.h @@ -150,7 +150,7 @@ namespace pvpgn char const * clientexe; char const * owner; char const * cdkey; - t_versioncheck * versioncheck; /* equation and MPQ file used to validate game checksum */ + const VersionCheck *versioncheck; } client; /* client program specific data */ struct { t_queue * outqueue; /* packets waiting to be sent */ @@ -382,8 +382,8 @@ namespace pvpgn extern int conn_quota_exceeded(t_connection * c, char const * message); extern int conn_set_lastsender(t_connection * c, char const * sender); extern char const * conn_get_lastsender(t_connection const * c); - extern t_versioncheck * conn_get_versioncheck(t_connection * c); - extern int conn_set_versioncheck(t_connection * c, t_versioncheck * versioncheck); + const VersionCheck *conn_get_versioncheck(t_connection *c); + bool conn_set_versioncheck(t_connection *c, const VersionCheck* versioncheck); extern int conn_get_echoback(t_connection * c); extern void conn_set_echoback(t_connection * c, int echoback); extern int conn_set_ircline(t_connection * c, char const * line); diff --git a/src/bnetd/handle_bnet.cpp b/src/bnetd/handle_bnet.cpp index 3b802fc..a92732b 100644 --- a/src/bnetd/handle_bnet.cpp +++ b/src/bnetd/handle_bnet.cpp @@ -29,7 +29,9 @@ #include #include #include +#include #include +#include #include "compat/strcasecmp.h" #include "compat/strncasecmp.h" @@ -570,15 +572,12 @@ namespace pvpgn packet_del_ref(rpacket); } - if ((rpacket = packet_create(packet_class_bnet))) { - t_versioncheck *vc; - - eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selecting version check", conn_get_socket(c)); - vc = versioncheck_create(conn_get_archtag(c), conn_get_clienttag(c)); - conn_set_versioncheck(c, vc); + if ((rpacket = packet_create(packet_class_bnet))) + { packet_set_size(rpacket, sizeof(t_server_authreq_109)); packet_set_type(rpacket, SERVER_AUTHREQ_109); + // Logon type if ((conn_get_clienttag(c) == CLIENTTAG_WARCRAFT3_UINT)) bn_int_set(&rpacket->u.server_authreq_109.logontype, SERVER_AUTHREQ_109_LOGONTYPE_W3); else if ((conn_get_clienttag(c) == CLIENTTAG_WAR3XP_UINT)) @@ -586,18 +585,29 @@ namespace pvpgn else bn_int_set(&rpacket->u.server_authreq_109.logontype, SERVER_AUTHREQ_109_LOGONTYPE); + + // Session bn_int_set(&rpacket->u.server_authreq_109.sessionkey, conn_get_sessionkey(c)); bn_int_set(&rpacket->u.server_authreq_109.sessionnum, conn_get_sessionnum(c)); - file_to_mod_time(c, versioncheck_get_mpqfile(vc), &rpacket->u.server_authreq_109.timestamp); - packet_append_string(rpacket, versioncheck_get_mpqfile(vc)); - packet_append_string(rpacket, versioncheck_get_eqn(vc)); - eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selected \"{}\" \"{}\"", conn_get_socket(c), versioncheck_get_mpqfile(vc), versioncheck_get_eqn(vc)); + + + // CheckRevision + std::tuple checkrevision = select_checkrevision(bn_int_get(packet->u.client_countryinfo_109.archtag), bn_int_get(packet->u.client_countryinfo_109.clienttag), bn_int_get(packet->u.client_countryinfo_109.versionid)); + file_to_mod_time(c, std::get<0>(checkrevision).c_str(), &rpacket->u.server_authreq_109.timestamp); // Checkrevision file timestamp + packet_append_string(rpacket, std::get<0>(checkrevision).c_str()); // CheckRevision filename + packet_append_string(rpacket, std::get<1>(checkrevision).c_str()); // CheckRevision equation + eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selected \"{}\" \"{}\"", conn_get_socket(c), std::get<0>(checkrevision), std::get<1>(checkrevision)); + + + // WarCraft 3 Server Signature if ((conn_get_clienttag(c) == CLIENTTAG_WARCRAFT3_UINT) - || (conn_get_clienttag(c) == CLIENTTAG_WAR3XP_UINT)) { + || (conn_get_clienttag(c) == CLIENTTAG_WAR3XP_UINT)) + { char padding[128]; std::memset(padding, 0, 128); packet_append_data(rpacket, padding, 128); } + conn_push_outqueue(c, rpacket); packet_del_ref(rpacket); } @@ -633,35 +643,22 @@ namespace pvpgn conn_set_archtag(c, bn_int_get(packet->u.client_progident.archtag)); conn_set_clienttag(c, bn_int_get(packet->u.client_progident.clienttag)); - if (prefs_get_skip_versioncheck()) { - eventlog(eventlog_level_debug, __FUNCTION__, "[{}] attempting to skip version check by sending early authreply", conn_get_socket(c)); - /* skip over SERVER_AUTHREQ1 and CLIENT_AUTHREQ1 */ - if ((rpacket = packet_create(packet_class_bnet))) { - packet_set_size(rpacket, sizeof(t_server_authreply1)); - packet_set_type(rpacket, SERVER_AUTHREPLY1); - bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_OK); - packet_append_string(rpacket, ""); - packet_append_string(rpacket, ""); /* FIXME: what's the second string for? */ - conn_push_outqueue(c, rpacket); - packet_del_ref(rpacket); - } - } - else { - t_versioncheck *vc; + if ((rpacket = packet_create(packet_class_bnet))) + { + packet_set_size(rpacket, sizeof(t_server_authreq1)); + packet_set_type(rpacket, SERVER_AUTHREQ1); - eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selecting version check", conn_get_socket(c)); - vc = versioncheck_create(conn_get_archtag(c), conn_get_clienttag(c)); - conn_set_versioncheck(c, vc); - if ((rpacket = packet_create(packet_class_bnet))) { - packet_set_size(rpacket, sizeof(t_server_authreq1)); - packet_set_type(rpacket, SERVER_AUTHREQ1); - file_to_mod_time(c, versioncheck_get_mpqfile(vc), &rpacket->u.server_authreq1.timestamp); - packet_append_string(rpacket, versioncheck_get_mpqfile(vc)); - packet_append_string(rpacket, versioncheck_get_eqn(vc)); - eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selected \"{}\" \"{}\"", conn_get_socket(c), versioncheck_get_mpqfile(vc), versioncheck_get_eqn(vc)); - conn_push_outqueue(c, rpacket); - packet_del_ref(rpacket); - } + + // CheckRevision + std::tuple checkrevision = select_checkrevision(bn_int_get(packet->u.client_progident.archtag), bn_int_get(packet->u.client_progident.clienttag), bn_int_get(packet->u.client_progident.versionid)); + file_to_mod_time(c, std::get<0>(checkrevision).c_str(), &rpacket->u.server_authreq_109.timestamp); // Checkrevision file timestamp + packet_append_string(rpacket, std::get<0>(checkrevision).c_str()); // CheckRevision filename + packet_append_string(rpacket, std::get<1>(checkrevision).c_str()); // CheckRevision equation + eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selected \"{}\" \"{}\"", conn_get_socket(c), std::get<0>(checkrevision), std::get<1>(checkrevision)); + + + conn_push_outqueue(c, rpacket); + packet_del_ref(rpacket); } return 0; @@ -1000,102 +997,126 @@ namespace pvpgn static int _client_authreq1(t_connection * c, t_packet const *const packet) { - t_packet *rpacket; - - if (packet_get_size(packet) < sizeof(t_client_authreq1)) { + if (packet_get_size(packet) < sizeof(t_client_authreq1)) + { eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ1 packet (expected {} bytes, got {})", conn_get_socket(c), sizeof(t_client_authreq1), packet_get_size(packet)); return -1; } + auto send_failed_packet = [](t_connection *c) { - char verstr[16]; - char const *exeinfo; - char const *versiontag; - int failed; - - failed = 0; - if (bn_int_get(packet->u.client_authreq1.archtag) != conn_get_archtag(c)) - failed = 1; - if (bn_int_get(packet->u.client_authreq1.clienttag) != conn_get_clienttag(c)) - failed = 1; - - if (!(exeinfo = packet_get_str_const(packet, sizeof(t_client_authreq1), MAX_EXEINFO_STR))) { - eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ1 (missing or too long exeinfo)", conn_get_socket(c)); - exeinfo = "badexe"; - failed = 1; - } - conn_set_versionid(c, bn_int_get(packet->u.client_authreq1.versionid)); - conn_set_checksum(c, bn_int_get(packet->u.client_authreq1.checksum)); - conn_set_gameversion(c, bn_int_get(packet->u.client_authreq1.gameversion)); - std::strcpy(verstr, vernum_to_verstr(bn_int_get(packet->u.client_authreq1.gameversion))); - conn_set_clientver(c, verstr); - conn_set_clientexe(c, exeinfo); - - eventlog(eventlog_level_info, __FUNCTION__, "[{}] CLIENT_AUTHREQ1 archtag=0x{:08x} clienttag=0x{:08x} verstr={} exeinfo=\"{}\" versionid=0x{:08x} gameversion=0x{:08x} checksum=0x{:08x}", conn_get_socket(c), bn_int_get(packet->u.client_authreq1.archtag), bn_int_get(packet->u.client_authreq1.clienttag), verstr, exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c)); - - - if ((rpacket = packet_create(packet_class_bnet))) { + t_packet *rpacket = packet_create(packet_class_bnet); + if (rpacket) + { packet_set_size(rpacket, sizeof(t_server_authreply1)); packet_set_type(rpacket, SERVER_AUTHREPLY1); + conn_set_state(c, conn_state_untrusted); + bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_BADVERSION); + packet_append_string(rpacket, ""); + packet_append_string(rpacket, ""); // undocumented extra null terminator - if (!conn_get_versioncheck(c) && prefs_get_skip_versioncheck()) - eventlog(eventlog_level_info, __FUNCTION__, "[{}] skip versioncheck enabled and client did not request validation", conn_get_socket(c)); - else - switch (versioncheck_validate(conn_get_versioncheck(c), conn_get_archtag(c), conn_get_clienttag(c), exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c))) { - case -1: /* failed test... client has been modified */ - if (!prefs_get_allow_bad_version()) { - eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed test (marking untrusted)", conn_get_socket(c)); - failed = 1; - } - else - eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed test, allowing anyway", conn_get_socket(c)); - break; - case 0: /* not listed in table... can't tell if client has been modified */ - if (!prefs_get_allow_unknown_version()) { - eventlog(eventlog_level_info, __FUNCTION__, "[{}] unable to test client (marking untrusted)", conn_get_socket(c)); - failed = 1; - } - else - eventlog(eventlog_level_info, __FUNCTION__, "[{}] unable to test client, allowing anyway", conn_get_socket(c)); - break; - /* 1 == test passed... client seems to be ok */ - } - - versiontag = versioncheck_get_versiontag(conn_get_versioncheck(c)); - - eventlog(eventlog_level_info, __FUNCTION__, "[{}] client matches versiontag \"{}\"", conn_get_socket(c), versiontag); - - if (failed) { - conn_set_state(c, conn_state_untrusted); - bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_BADVERSION); - packet_append_string(rpacket, ""); - } - else { - char *mpqfilename; - - mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), versiontag, NULL); - - /* Only handle updates when there is an update file available. */ - if (mpqfilename != NULL) { - eventlog(eventlog_level_info, __FUNCTION__, "[{}] an upgrade for version {} is available \"{}\"", conn_get_socket(c), versioncheck_get_versiontag(conn_get_versioncheck(c)), mpqfilename); - bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_UPDATE); - packet_append_string(rpacket, mpqfilename); - } - else { - eventlog(eventlog_level_info, __FUNCTION__, "[{}] no upgrade for {} is available", conn_get_socket(c), versioncheck_get_versiontag(conn_get_versioncheck(c))); - bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_OK); - packet_append_string(rpacket, ""); - } - - if (mpqfilename) - xfree((void *)mpqfilename); - } - - packet_append_string(rpacket, ""); /* FIXME: what's the second string for? */ conn_push_outqueue(c, rpacket); + packet_del_ref(rpacket); } + }; + + // The following if statements are sanity checks + // The client should have already sent this information in a previous packet and is resending it again in this packet + if (bn_int_get(packet->u.client_authreq1.archtag) != conn_get_archtag(c)) + { + send_failed_packet(c); + return 0; + } + + if (bn_int_get(packet->u.client_authreq1.clienttag) != conn_get_clienttag(c)) + { + send_failed_packet(c); + return 0; + } + + if (bn_int_get(packet->u.client_authreq1.versionid) != conn_get_versionid(c)) + { + send_failed_packet(c); + return 0; + } + + + const char *exeinfo = packet_get_str_const(packet, sizeof(t_client_authreq1), MAX_EXEINFO_STR); + if (exeinfo) + { + conn_set_clientexe(c, exeinfo); + } + else + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ1 (missing or too long exeinfo)", conn_get_socket(c)); + send_failed_packet(c); + return 0; + } + + std::string version_string = vernum_to_verstr(bn_int_get(packet->u.client_authreq1.gameversion)); + conn_set_clientver(c, version_string.c_str()); + eventlog(eventlog_level_info, __FUNCTION__, "[{}] CLIENT_AUTHREQ1 archtag=0x{:08x} clienttag=0x{:08x} verstr={} exeinfo=\"{}\" versionid=0x{:08x} gameversion=0x{:08x} checksum=0x{:08x}", conn_get_socket(c), bn_int_get(packet->u.client_authreq1.archtag), bn_int_get(packet->u.client_authreq1.clienttag), version_string, exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c)); + + conn_set_versionid(c, bn_int_get(packet->u.client_authreq1.versionid)); + conn_set_checksum(c, bn_int_get(packet->u.client_authreq1.checksum)); + conn_set_gameversion(c, bn_int_get(packet->u.client_authreq1.gameversion)); + + + const VersionCheck* vc = select_versioncheck(conn_get_archtag(c), conn_get_clienttag(c), conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c)); + conn_set_versioncheck(c, vc); + if (vc) + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] client matches versiontag \"{}\"", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag()); + } + else + { + if (prefs_get_allow_unknown_version()) + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] skipping versioncheck because allow_unknown_version is true", conn_get_socket(c)); + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed versioncheck", conn_get_socket(c)); + send_failed_packet(c); + return 0; + } + } + + + t_packet *rpacket = packet_create(packet_class_bnet); + if (rpacket) + { + packet_set_size(rpacket, sizeof(t_server_authreply1)); + packet_set_type(rpacket, SERVER_AUTHREPLY1); + + char *mpqfilename = nullptr; + if (vc) + { + mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), vc->get_version_tag().c_str(), nullptr); + } + + // Only handle updates when there is an update file available. + if (mpqfilename) + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] an upgrade for version {} is available \"{}\"", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag(), mpqfilename); + bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_UPDATE); + packet_append_string(rpacket, mpqfilename); + xfree(static_cast(mpqfilename)); + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] no upgrade is available", conn_get_socket(c)); + } + + bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_OK); + packet_append_string(rpacket, ""); + packet_append_string(rpacket, ""); // FIXME: what's the second string for? + + conn_push_outqueue(c, rpacket); + + packet_del_ref(rpacket); } return 0; @@ -1103,108 +1124,117 @@ namespace pvpgn static int _client_authreq109(t_connection * c, t_packet const *const packet) { - t_packet *rpacket; - - if (packet_get_size(packet) < sizeof(t_client_authreq_109)) { + if (packet_get_size(packet) < sizeof(t_client_authreq_109)) + { eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 packet (expected {} bytes, got {})", conn_get_socket(c), sizeof(t_client_authreq_109), packet_get_size(packet)); return 0; } + auto send_failed_packet = [](t_connection *c) { - char verstr[16]; - char const *exeinfo; - char const *versiontag; - int failed; - char const *owner; - unsigned int count; - unsigned int pos; - - failed = 0; - count = bn_int_get(packet->u.client_authreq_109.cdkey_number); - pos = sizeof(t_client_authreq_109)+(count * sizeof(t_cdkey_info)); - - if (!(exeinfo = packet_get_str_const(packet, pos, MAX_EXEINFO_STR))) { - eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 (missing or too long exeinfo)", conn_get_socket(c)); - exeinfo = "badexe"; - failed = 1; - } - conn_set_clientexe(c, exeinfo); - pos += std::strlen(exeinfo) + 1; - - if (!(owner = packet_get_str_const(packet, pos, MAX_OWNER_STR))) { - eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 (missing or too long owner)", conn_get_socket(c)); - owner = ""; /* maybe owner was missing, use empty string */ - } - conn_set_owner(c, owner); - - conn_set_checksum(c, bn_int_get(packet->u.client_authreq_109.checksum)); - conn_set_gameversion(c, bn_int_get(packet->u.client_authreq_109.gameversion)); - std::strcpy(verstr, vernum_to_verstr(bn_int_get(packet->u.client_authreq_109.gameversion))); - conn_set_clientver(c, verstr); - conn_set_clientexe(c, exeinfo); - - eventlog(eventlog_level_info, __FUNCTION__, "[{}] CLIENT_AUTHREQ_109 ticks=0x{:08x}, verstr={} exeinfo=\"{}\" versionid=0x{:08x} gameversion=0x{:08x} checksum=0x{:08x}", conn_get_socket(c), bn_int_get(packet->u.client_authreq_109.ticks), verstr, exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c)); - - if ((rpacket = packet_create(packet_class_bnet))) { + t_packet *rpacket = packet_create(packet_class_bnet); + if (rpacket) + { packet_set_size(rpacket, sizeof(t_server_authreply_109)); packet_set_type(rpacket, SERVER_AUTHREPLY_109); + conn_set_state(c, conn_state_untrusted); - - if (!conn_get_versioncheck(c) && prefs_get_skip_versioncheck()) - eventlog(eventlog_level_info, __FUNCTION__, "[{}] skip versioncheck enabled and client did not request validation", conn_get_socket(c)); - else - switch (versioncheck_validate(conn_get_versioncheck(c), conn_get_archtag(c), conn_get_clienttag(c), exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c))) { - case -1: /* failed test... client has been modified */ - if (!prefs_get_allow_bad_version()) { - eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed test (closing connection)", conn_get_socket(c)); - failed = 1; - } - else - eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed test, allowing anyway", conn_get_socket(c)); - break; - case 0: /* not listed in table... can't tell if client has been modified */ - if (!prefs_get_allow_unknown_version()) { - eventlog(eventlog_level_info, __FUNCTION__, "[{}] unable to test client (closing connection)", conn_get_socket(c)); - failed = 1; - } - else - eventlog(eventlog_level_info, __FUNCTION__, "[{}] unable to test client, allowing anyway", conn_get_socket(c)); - break; - /* 1 == test passed... client seems to be ok */ - } - - versiontag = versioncheck_get_versiontag(conn_get_versioncheck(c)); - - eventlog(eventlog_level_info, __FUNCTION__, "[{}] client matches versiontag \"{}\"", conn_get_socket(c), versiontag); - - if (failed) { - conn_set_state(c, conn_state_untrusted); - bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_BADVERSION); - packet_append_string(rpacket, ""); - } - else { - char *mpqfilename; - - mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), versiontag, NULL); - - /* Only handle updates when there is an update file available. */ - if (mpqfilename != NULL) { - eventlog(eventlog_level_info, __FUNCTION__, "[{}] an upgrade for {} is available \"{}\"", conn_get_socket(c), versiontag, mpqfilename); - bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_UPDATE); - packet_append_string(rpacket, mpqfilename); - } - else { - eventlog(eventlog_level_info, __FUNCTION__, "[{}] no upgrade for {} is available", conn_get_socket(c), versiontag); - bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_OK); - packet_append_string(rpacket, ""); - } - if (mpqfilename) - xfree((void *)mpqfilename); - } + bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_BADVERSION); + packet_append_string(rpacket, '\0'); conn_push_outqueue(c, rpacket); + packet_del_ref(rpacket); } + }; + + + std::uint32_t count = bn_int_get(packet->u.client_authreq_109.cdkey_number); + std::size_t position = sizeof(t_client_authreq_109) + (count * sizeof(t_cdkey_info)); + + const char *const exeinfo = packet_get_str_const(packet, position, MAX_EXEINFO_STR); + if (exeinfo) + { + conn_set_clientexe(c, exeinfo); + } + else + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 (missing or too long exeinfo)", conn_get_socket(c)); + send_failed_packet(c); + return 0; + } + + position += std::strlen(exeinfo) + 1; + + const char *const owner = packet_get_str_const(packet, position, MAX_OWNER_STR); + if (owner) + { + conn_set_owner(c, owner); + } + else + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 (missing or too long owner)", conn_get_socket(c)); + conn_set_owner(c, ""); + } + + conn_set_checksum(c, bn_int_get(packet->u.client_authreq_109.checksum)); + conn_set_gameversion(c, bn_int_get(packet->u.client_authreq_109.gameversion)); + std::string version = vernum_to_verstr(bn_int_get(packet->u.client_authreq_109.gameversion)); + conn_set_clientver(c, version.c_str()); + + eventlog(eventlog_level_info, __FUNCTION__, "[{}] CLIENT_AUTHREQ_109 ticks=0x{:08x}, verstr={} exeinfo=\"{}\" versionid=0x{:08x} gameversion=0x{:08x} checksum=0x{:08x}", conn_get_socket(c), bn_int_get(packet->u.client_authreq_109.ticks), version, exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c)); + + t_packet *rpacket; + if ((rpacket = packet_create(packet_class_bnet))) + { + packet_set_size(rpacket, sizeof(t_server_authreply_109)); + packet_set_type(rpacket, SERVER_AUTHREPLY_109); + + + const VersionCheck* vc = select_versioncheck(conn_get_archtag(c), conn_get_clienttag(c), conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c)); + conn_set_versioncheck(c, vc); + if (vc) + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] client matches versiontag \"{}\"", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag()); + } + else + { + if (prefs_get_allow_unknown_version()) + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] skipping versioncheck because allow_unknown_version is true", conn_get_socket(c)); + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed versioncheck", conn_get_socket(c)); + send_failed_packet(c); + return 0; + } + } + + + char *mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), conn_get_versioncheck(c)->get_version_tag().c_str(), NULL); + if (vc) + { + // Only handle updates when there is an update file available. + if (mpqfilename) + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] an upgrade for {} is available \"{}\"", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag(), mpqfilename); + bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_UPDATE); + packet_append_string(rpacket, mpqfilename); + + xfree(static_cast(mpqfilename)); + } + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] no upgrade is available", conn_get_socket(c)); + } + + bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_OK); + packet_append_string(rpacket, ""); + + conn_push_outqueue(c, rpacket); + packet_del_ref(rpacket); } return 0; @@ -2427,97 +2457,163 @@ namespace pvpgn static int _client_atfriendscreen(t_connection * c, t_packet const *const packet) { - char const *myusername; - char const *fname; - t_connection *dest_c; - unsigned char f_cnt = 0; - t_account *account; - t_packet *rpacket; - char const *vt; - char const *nvt; - t_friend *fr; - t_list *flist; - t_elem *curr; - t_channel *mychannel, *chan; - int publicchan = 1; + eventlog(eventlog_level_debug, __FUNCTION__, "[{}] got CLIENT_ARRANGEDTEAM_FRIENDSCREEN packet", conn_get_socket(c)); - eventlog(eventlog_level_info, __FUNCTION__, "[{}] got CLIENT_ARRANGEDTEAM_FRIENDSCREEN packet", conn_get_socket(c)); - - myusername = conn_get_username(c); - eventlog(eventlog_level_trace, "handle_bnet", "[{}] AT - Got Username %s", conn_get_socket(c), myusername); - - if (!(rpacket = packet_create(packet_class_bnet))) { - eventlog(eventlog_level_error, "handle_bnet", "[{}] AT - can't create friendscreen server packet", conn_get_socket(c)); + const char *my_username = conn_get_username(c); + if (!my_username) + { return -1; } + t_channel *my_channel = conn_get_channel(c); + int my_channel_is_public = 1; + if (my_channel) + { + my_channel_is_public = channel_get_flags(my_channel) & channel_flags_public; + } + + + t_packet *rpacket = packet_create(packet_class_bnet); + if (!rpacket) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] could not create friendscreen server packet", conn_get_socket(c)); + return -1; + } packet_set_size(rpacket, sizeof(t_server_arrangedteam_friendscreen)); packet_set_type(rpacket, SERVER_ARRANGEDTEAM_FRIENDSCREEN); - mychannel = conn_get_channel(c); - if ((mychannel)) - publicchan = channel_get_flags(mychannel) & channel_flags_public; + std::uint8_t available_players = 0; - vt = versioncheck_get_versiontag(conn_get_versioncheck(c)); - flist = account_get_friends(conn_get_account(c)); - LIST_TRAVERSE(flist, curr) { - if (!(fr = (t_friend*)elem_get_data(curr))) { + // begin search for mutual and available friends + t_list *my_friend_list = account_get_friends(conn_get_account(c)); + t_elem *entry; + LIST_TRAVERSE(my_friend_list, entry) + { + t_friend *_friend = static_cast(elem_get_data(entry)); + if (!_friend) + { eventlog(eventlog_level_error, __FUNCTION__, "found NULL entry in list"); continue; } - account = friend_get_account(fr); - if (!(dest_c = connlist_find_connection_by_account(account))) - continue; // if user is offline, then continue to next friend - nvt = versioncheck_get_versiontag(conn_get_versioncheck(dest_c)); - if (vt && nvt && std::strcmp(vt, nvt)) - continue; /* friend is using another game/version */ + if (available_players == std::numeric_limits::max()) + { + eventlog(eventlog_level_info, __FUNCTION__, "Reached maximum amount of available players to send in packet"); + break; + } - if (friend_get_mutual(fr)) { - if (conn_get_dndstr(dest_c)) - continue; // user is dnd - if (conn_get_awaystr(dest_c)) - continue; // user is away - if (conn_get_game(dest_c)) - continue; // user is some game - if (!(chan = conn_get_channel(dest_c))) + // skip non-mutual friends + if (friend_get_mutual(_friend) == FRIEND_NOTMUTUAL) + { + continue; + } + + t_account *friend_account = friend_get_account(_friend); + t_connection *friend_connection = connlist_find_connection_by_account(friend_account); + // if user is offline, then continue to next friend + if (!friend_connection) + { + continue; + } + + const std::string my_version_tag = conn_get_versioncheck(c) ? conn_get_versioncheck(c)->get_version_tag() : ""; + const std::string friend_version_tag = conn_get_versioncheck(friend_connection) ? conn_get_versioncheck(friend_connection)->get_version_tag() : ""; + // friend is using another game or is on a different version + if (my_version_tag != friend_version_tag) + { + continue; + } + + // friend has Do Not Disturb mode enabled + if (conn_get_dndstr(friend_connection)) + { + continue; + } + + // friend is away + if (conn_get_awaystr(friend_connection)) + { + continue; + } + + // friend is not in a channel + t_channel *friend_channel = conn_get_channel(friend_connection); + if (!friend_channel) + { + continue; + } + + // // don't list YET if in same private channel + if (!my_channel_is_public && (friend_channel == my_channel)) + { + continue; + } + + const char *friend_name = account_get_name(friend_account); + eventlog(eventlog_level_trace, __FUNCTION__, "Friend {} is available for an AT Game.", friend_name); + available_players += 1; + packet_append_string(rpacket, friend_name); + } + + + // now list matching users in same private chan + if (!my_channel_is_public) + { + for (t_connection *user_connection = channel_get_first(my_channel); user_connection; user_connection = channel_get_next()) + { + if (available_players == std::numeric_limits::max()) + { + eventlog(eventlog_level_info, __FUNCTION__, "Reached maximum amount of available players to send in packet"); + break; + } + + // skip self + if (user_connection == c) + { continue; - if (!publicchan && (chan == mychannel)) - continue; // don't list YET if in same private channel + } - fname = account_get_name(account); - eventlog(eventlog_level_trace, "handle_bnet", "AT - Friend: {} is available for a AT Game.", fname); - f_cnt++; - packet_append_string(rpacket, fname); + const std::string my_version_tag = conn_get_versioncheck(c) ? conn_get_versioncheck(c)->get_version_tag() : ""; + const std::string user_version_tag = conn_get_versioncheck(user_connection) ? conn_get_versioncheck(user_connection)->get_version_tag() : ""; + // user is using another game or is on a different version + if (my_version_tag != user_version_tag) + { + continue; + } + + // user is dnd + if (conn_get_dndstr(user_connection)) + { + continue; + } + + // user is away + if (conn_get_awaystr(user_connection)) + { + continue; + } + + const char *username = account_get_name(conn_get_account(user_connection)); + if (!username) + { + continue; + } + + eventlog(eventlog_level_trace, __FUNCTION__, "user {} in private channel {} is available for an AT Game.", username, channel_get_name(my_channel)); + available_players += 1; + packet_append_string(rpacket, username); } } - if (!publicchan) { // now list matching users in same private chan - for (dest_c = channel_get_first(mychannel); dest_c; dest_c = channel_get_next()) { - if (dest_c == c) - continue; // don'tlist yourself - nvt = versioncheck_get_versiontag(conn_get_versioncheck(dest_c)); - if (vt && nvt && std::strcmp(vt, nvt)) - continue; /* user is using another game/version */ - if (conn_get_dndstr(dest_c)) - continue; // user is dnd - if (conn_get_awaystr(dest_c)) - continue; // user is away - if (!(conn_get_account(dest_c))) - continue; - fname = account_get_name(conn_get_account(dest_c)); - eventlog(eventlog_level_trace, "handle_bnet", "AT - user in private channel: {} is available for a AT Game.", fname); - f_cnt++; - packet_append_string(rpacket, fname); - } + if (available_players == 0) + { + eventlog(eventlog_level_info, __FUNCTION__, "No available players"); } - if (!f_cnt) - eventlog(eventlog_level_info, "handle_bnet", "AT - no friends available for AT game."); - bn_byte_set(&rpacket->u.server_arrangedteam_friendscreen.f_count, f_cnt); + bn_byte_set(&rpacket->u.server_arrangedteam_friendscreen.f_count, available_players); + conn_push_outqueue(c, rpacket); - packet_del_ref(rpacket); return 0; @@ -3653,12 +3749,12 @@ namespace pvpgn } if (conn_get_versioncheck(cbdata->c) && conn_get_versioncheck(game_get_owner(game)) && - versioncheck_get_versiontag(conn_get_versioncheck(cbdata->c)) && - versioncheck_get_versiontag(conn_get_versioncheck(game_get_owner(game))) && - std::strcmp(versioncheck_get_versiontag(conn_get_versioncheck(cbdata->c)), versioncheck_get_versiontag(conn_get_versioncheck(game_get_owner(game)))) != 0) { + conn_get_versioncheck(cbdata->c)->get_version_tag() != conn_get_versioncheck(game_get_owner(game))->get_version_tag()) + { eventlog(eventlog_level_debug, __FUNCTION__, "[{}] not listing because game is wrong versiontag", conn_get_socket(cbdata->c)); return 0; } + bn_short_set(&glgame.gametype, gtype_to_bngtype(game_get_type(game))); bn_short_set(&glgame.unknown1, SERVER_GAMELISTREPLY_GAME_UNKNOWN1); bn_short_set(&glgame.unknown3, SERVER_GAMELISTREPLY_GAME_UNKNOWN3); @@ -4716,33 +4812,23 @@ namespace pvpgn static int _client_changeclient(t_connection * c, t_packet const *const packet) { - t_versioncheck *vc; - - if (packet_get_size(packet) < sizeof(t_client_changeclient)) { + if (packet_get_size(packet) < sizeof(t_client_changeclient)) + { eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad CLIENT_CHANGECLIENT packet (expected {} bytes, got {})", conn_get_socket(c), sizeof(t_client_changeclient), packet_get_size(packet)); return -1; } - if (tag_check_in_list(bn_int_get(packet->u.client_changeclient.clienttag), prefs_get_allowed_clients())) { + if (conn_get_clienttag(c) != CLIENTTAG_WAR3XP_UINT + || bn_int_get(packet->u.client_changeclient.clienttag) != CLIENTTAG_WARCRAFT3_UINT) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] invalid attempt to change client from {X} to {X}", conn_get_socket(c), conn_get_clienttag(c), bn_int_get(packet->u.client_changeclient.clienttag)); conn_set_state(c, conn_state_destroy); - return 0; + return -1; } conn_set_clienttag(c, bn_int_get(packet->u.client_changeclient.clienttag)); - vc = conn_get_versioncheck(c); - versioncheck_set_versiontag(vc, clienttag_uint_to_str(conn_get_clienttag(c))); - - if (vc && versioncheck_get_versiontag(vc)) { - switch (versioncheck_validate(vc, conn_get_archtag(c), conn_get_clienttag(c), conn_get_clientexe(c), conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c))) { - case -1: /* failed test... client has been modified */ - case 0: /* not listed in table... can't tell if client has been modified */ - eventlog(eventlog_level_error, __FUNCTION__, "[{}] error revalidating, allowing anyway", conn_get_socket(c)); - break; - } - - eventlog(eventlog_level_info, __FUNCTION__, "[{}] client versiontag set to \"{}\"", conn_get_socket(c), versioncheck_get_versiontag(vc)); - } + eventlog(eventlog_level_info, __FUNCTION__, "[{}] changed client to {X}", conn_get_socket(c), bn_int_get(packet->u.client_changeclient.clienttag)); return 0; } diff --git a/src/bnetd/luainterface.cpp b/src/bnetd/luainterface.cpp index 7aac911..91adf5c 100644 --- a/src/bnetd/luainterface.cpp +++ b/src/bnetd/luainterface.cpp @@ -213,11 +213,8 @@ namespace pvpgn config.update("star_iconfile", prefs_get_star_iconfile()); config.update("tosfile", prefs_get_tosfile()); config.update("allowed_clients", prefs_get_allowed_clients()); - config.update("skip_versioncheck", prefs_get_skip_versioncheck()); config.update("allow_bad_version", prefs_get_allow_bad_version()); config.update("allow_unknown_version", prefs_get_allow_unknown_version()); - config.update("version_exeinfo_match", prefs_get_version_exeinfo_match()); - config.update("version_exeinfo_maxdiff", prefs_get_version_exeinfo_maxdiff()); config.update("usersync", prefs_get_user_sync_timer()); config.update("userflush", prefs_get_user_flush_timer()); config.update("userflush_connected", prefs_get_user_flush_connected()); diff --git a/src/bnetd/main.cpp b/src/bnetd/main.cpp index 323226e..86508a9 100644 --- a/src/bnetd/main.cpp +++ b/src/bnetd/main.cpp @@ -380,8 +380,12 @@ int pre_server_startup(void) if (autoupdate_load(prefs_get_mpqfile()) < 0) eventlog(eventlog_level_error, __FUNCTION__, "could not load autoupdate list"); - if (versioncheck_load(prefs_get_versioncheck_file()) < 0) + + if (!load_versioncheck_conf(prefs_get_versioncheck_file())) + { eventlog(eventlog_level_error, __FUNCTION__, "could not load versioncheck list"); + } + if (news_load(prefs_get_newsfile()) < 0) eventlog(eventlog_level_error, __FUNCTION__, "could not load news list"); watchlist.reset(new WatchComponent()); @@ -449,7 +453,7 @@ void post_server_shutdown(int status) attrlayer_cleanup(); watchlist.reset(); news_unload(); - versioncheck_unload(); + unload_versioncheck_conf(); autoupdate_unload(); ipbanlist_save(prefs_get_ipbanfile()); ipbanlist_destroy(); diff --git a/src/bnetd/prefs.cpp b/src/bnetd/prefs.cpp index 3f6fe86..f054e8d 100644 --- a/src/bnetd/prefs.cpp +++ b/src/bnetd/prefs.cpp @@ -118,7 +118,6 @@ namespace pvpgn char const * maildir; char const * log_notice; unsigned int savebyname; - unsigned int skip_versioncheck; unsigned int allow_bad_version; unsigned int allow_unknown_version; char const * versioncheck_file; @@ -494,10 +493,6 @@ namespace pvpgn static const char *conf_get_savebyname(void); static int conf_setdef_savebyname(void); - static int conf_set_skip_versioncheck(const char *valstr); - static const char *conf_get_skip_versioncheck(void); - static int conf_setdef_skip_versioncheck(void); - static int conf_set_allow_bad_version(const char *valstr); static const char *conf_get_allow_bad_version(void); static int conf_setdef_allow_bad_version(void); @@ -530,14 +525,6 @@ namespace pvpgn static const char *conf_get_ipban_check_int(void); static int conf_setdef_ipban_check_int(void); - static int conf_set_version_exeinfo_match(const char *valstr); - static const char *conf_get_version_exeinfo_match(void); - static int conf_setdef_version_exeinfo_match(void); - - static int conf_set_version_exeinfo_maxdiff(const char *valstr); - static const char *conf_get_version_exeinfo_maxdiff(void); - static int conf_setdef_version_exeinfo_maxdiff(void); - static int conf_set_max_concurrent_logins(const char *valstr); static const char *conf_get_max_concurrent_logins(void); static int conf_setdef_max_concurrent_logins(void); @@ -816,7 +803,6 @@ namespace pvpgn { "maildir", conf_set_maildir, conf_get_maildir, conf_setdef_maildir }, { "log_notice", conf_set_log_notice, conf_get_log_notice, conf_setdef_log_notice }, { "savebyname", conf_set_savebyname, conf_get_savebyname, conf_setdef_savebyname }, - { "skip_versioncheck", conf_set_skip_versioncheck, conf_get_skip_versioncheck, conf_setdef_skip_versioncheck }, { "allow_bad_version", conf_set_allow_bad_version, conf_get_allow_bad_version, conf_setdef_allow_bad_version }, { "allow_unknown_version", conf_set_allow_unknown_version, conf_get_allow_unknown_version, conf_setdef_allow_unknown_version }, { "versioncheck_file", conf_set_versioncheck_file, conf_get_versioncheck_file, conf_setdef_versioncheck_file }, @@ -825,8 +811,6 @@ namespace pvpgn { "hashtable_size", conf_set_hashtable_size, conf_get_hashtable_size, conf_setdef_hashtable_size }, { "telnetaddrs", conf_set_telnetaddrs, conf_get_telnetaddrs, conf_setdef_telnetaddrs }, { "ipban_check_int", conf_set_ipban_check_int, conf_get_ipban_check_int, conf_setdef_ipban_check_int }, - { "version_exeinfo_match", conf_set_version_exeinfo_match, conf_get_version_exeinfo_match, conf_setdef_version_exeinfo_match }, - { "version_exeinfo_maxdiff", conf_set_version_exeinfo_maxdiff, conf_get_version_exeinfo_maxdiff, conf_setdef_version_exeinfo_maxdiff }, { "max_concurrent_logins", conf_set_max_concurrent_logins, conf_get_max_concurrent_logins, conf_setdef_max_concurrent_logins }, { "mapsfile", conf_set_mapsfile, conf_get_mapsfile, conf_setdef_mapsfile }, { "xplevelfile", conf_set_xplevelfile, conf_get_xplevelfile, conf_setdef_xplevelfile }, @@ -2564,27 +2548,6 @@ namespace pvpgn } - extern unsigned int prefs_get_skip_versioncheck(void) - { - return prefs_runtime_config.skip_versioncheck; - } - - static int conf_set_skip_versioncheck(const char *valstr) - { - return conf_set_bool(&prefs_runtime_config.skip_versioncheck, valstr, 0); - } - - static int conf_setdef_skip_versioncheck(void) - { - return conf_set_bool(&prefs_runtime_config.skip_versioncheck, NULL, 0); - } - - static const char* conf_get_skip_versioncheck(void) - { - return conf_get_bool(prefs_runtime_config.skip_versioncheck); - } - - extern unsigned int prefs_get_allow_bad_version(void) { return prefs_runtime_config.allow_bad_version; @@ -2753,48 +2716,6 @@ namespace pvpgn } - extern char const * prefs_get_version_exeinfo_match(void) - { - return prefs_runtime_config.version_exeinfo_match; - } - - static int conf_set_version_exeinfo_match(const char *valstr) - { - return conf_set_str(&prefs_runtime_config.version_exeinfo_match, valstr, NULL); - } - - static int conf_setdef_version_exeinfo_match(void) - { - return conf_set_str(&prefs_runtime_config.version_exeinfo_match, NULL, BNETD_EXEINFO_MATCH); - } - - static const char* conf_get_version_exeinfo_match(void) - { - return prefs_runtime_config.version_exeinfo_match; - } - - - extern unsigned int prefs_get_version_exeinfo_maxdiff(void) - { - return prefs_runtime_config.version_exeinfo_maxdiff; - } - - static int conf_set_version_exeinfo_maxdiff(const char *valstr) - { - return conf_set_int(&prefs_runtime_config.version_exeinfo_maxdiff, valstr, 0); - } - - static int conf_setdef_version_exeinfo_maxdiff(void) - { - return conf_set_int(&prefs_runtime_config.version_exeinfo_maxdiff, NULL, PVPGN_VERSION_TIMEDIV); - } - - static const char* conf_get_version_exeinfo_maxdiff(void) - { - return conf_get_int(prefs_runtime_config.version_exeinfo_maxdiff); - } - - extern unsigned int prefs_get_max_concurrent_logins(void) { return prefs_runtime_config.max_concurrent_logins; diff --git a/src/bnetd/prefs.h b/src/bnetd/prefs.h index 3abe0f1..07545f8 100644 --- a/src/bnetd/prefs.h +++ b/src/bnetd/prefs.h @@ -113,7 +113,6 @@ namespace pvpgn extern char const * prefs_get_maildir(void); extern char const * prefs_get_log_notice(void); extern unsigned int prefs_get_savebyname(void); - extern unsigned int prefs_get_skip_versioncheck(void); extern unsigned int prefs_get_allow_bad_version(void); extern unsigned int prefs_get_allow_unknown_version(void); extern char const * prefs_get_versioncheck_file(void); @@ -122,8 +121,6 @@ namespace pvpgn extern unsigned int prefs_get_hashtable_size(void); extern char const * prefs_get_telnet_addrs(void); extern unsigned int prefs_get_ipban_check_int(void); - extern char const * prefs_get_version_exeinfo_match(void); - extern unsigned int prefs_get_version_exeinfo_maxdiff(void); extern unsigned int prefs_get_max_concurrent_logins(void); diff --git a/src/bnetd/server.cpp b/src/bnetd/server.cpp index 97ac856..8b88fcf 100644 --- a/src/bnetd/server.cpp +++ b/src/bnetd/server.cpp @@ -1415,9 +1415,11 @@ namespace pvpgn if (do_restart == restart_mode_all || do_restart == restart_mode_versioncheck) { - versioncheck_unload(); - if (versioncheck_load(prefs_get_versioncheck_file()) < 0) + unload_versioncheck_conf(); + if (!load_versioncheck_conf(prefs_get_versioncheck_file())) + { eventlog(eventlog_level_error, __FUNCTION__, "could not load versioncheck list"); + } } if (do_restart == restart_mode_all || do_restart == restart_mode_ipbans) diff --git a/src/bnetd/versioncheck.cpp b/src/bnetd/versioncheck.cpp index 799cb06..1e37ef9 100644 --- a/src/bnetd/versioncheck.cpp +++ b/src/bnetd/versioncheck.cpp @@ -16,646 +16,263 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "common/setup_before.h" -#define VERSIONCHECK_INTERNAL_ACCESS #include "versioncheck.h" -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "compat/strcasecmp.h" -#include "common/list.h" -#include "common/xalloc.h" #include "common/eventlog.h" -#include "common/util.h" + #include "common/field_sizes.h" -#include "common/token.h" +#include "common/format.h" +#include "common/hash_tuple.hpp" #include "common/proginfo.h" -#include "common/xstring.h" +#include "compat/strcasecmp.h" +#include "common/token.h" +#include "common/util.h" + + +#include "json/json.hpp" #include "prefs.h" #include "common/setup_after.h" +using json = nlohmann::json; + + namespace pvpgn { namespace bnetd { + std::unordered_map, VersionCheck, hash_tuple::hash>> vc_entries; + std::unordered_map, std::tuple, hash_tuple::hash>> cr_entries; - static t_list * versioninfo_head = NULL; - static t_versioncheck dummyvc = { "A=42 B=42 C=42 4 A=A^S B=B^B C=C^C A=A^S", "IX86ver1.mpq", "NoVC" }; + bool versioncheck_conf_is_loaded = false; - static int versioncheck_compare_exeinfo(t_parsed_exeinfo * pattern, t_parsed_exeinfo * match); + // bool validate_file_metadata_format(const std::string& metadata); - extern t_versioncheck * versioncheck_create(t_tag archtag, t_tag clienttag) + + bool load_versioncheck_conf(const std::string& filename) { - t_elem const * curr; - t_versioninfo * vi; - t_versioncheck * vc; - char archtag_str[5]; - char clienttag_str[5]; - - LIST_TRAVERSE_CONST(versioninfo_head, curr) + if (versioncheck_conf_is_loaded) { - if (!(vi = (t_versioninfo*)elem_get_data(curr))) /* should not happen */ + eventlog(eventlog_level_error, __FUNCTION__, "Could not load {}, a versioncheck configuration file is already loaded", filename); + return false; + } + + auto t0 = std::chrono::steady_clock::now(); + + std::ifstream file_stream(filename, std::ios::in); + if (!file_stream.is_open()) + { + eventlog(eventlog_level_error, __FUNCTION__, "Could not open file \"{}\" for reading", filename); + return false; + } + + json jconf; + try + { + file_stream >> jconf; + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "Could not parse {}: {}", filename, e.what()); + return false; + } + + vc_entries.reserve(185); + + for (auto jclient : jconf.items()) + { + t_tag client = tag_str_to_uint(jclient.key().c_str()); + if (!tag_check_client(client)) { - eventlog(eventlog_level_error, __FUNCTION__, "version list contains NULL item"); + eventlog(eventlog_level_error, __FUNCTION__, "Invalid client: {}", jclient.key()); continue; } - eventlog(eventlog_level_debug, __FUNCTION__, "version check entry archtag={}, clienttag={}", - tag_uint_to_str(archtag_str, vi->archtag), - tag_uint_to_str(clienttag_str, vi->clienttag)); - - if (vi->archtag != archtag) - continue; - if (vi->clienttag != clienttag) - continue; - - /* FIXME: randomize the selection if more than one match */ - vc = (t_versioncheck*)xmalloc(sizeof(t_versioncheck)); - vc->eqn = xstrdup(vi->eqn); - vc->mpqfile = xstrdup(vi->mpqfile); - vc->versiontag = xstrdup(tag_uint_to_str(clienttag_str, clienttag)); - - return vc; - } - - /* - * No entries in the file that match, return the dummy because we have to send - * some equation and auth mpq to the client. The client is not going to pass the - * validation later unless skip_versioncheck or allow_unknown_version is enabled. - */ - return &dummyvc; - } - - - extern int versioncheck_destroy(t_versioncheck * vc) - { - if (!vc) - { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc"); - return -1; - } - - if (vc == &dummyvc) - return 0; - - xfree((void *)vc->versiontag); - xfree((void *)vc->mpqfile); - xfree((void *)vc->eqn); - xfree(vc); - - return 0; - } - - extern int versioncheck_set_versiontag(t_versioncheck * vc, char const * versiontag) - { - if (!vc) { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc"); - return -1; - } - if (!versiontag) { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL versiontag"); - return -1; - } - - if (vc->versiontag != NULL) xfree((void *)vc->versiontag); - vc->versiontag = xstrdup(versiontag); - return 0; - } - - - extern char const * versioncheck_get_versiontag(t_versioncheck const * vc) - { - if (!vc) { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc"); - return NULL; - } - - return vc->versiontag; - } - - - extern char const * versioncheck_get_mpqfile(t_versioncheck const * vc) - { - if (!vc) - { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc"); - return NULL; - } - - return vc->mpqfile; - } - - - extern char const * versioncheck_get_eqn(t_versioncheck const * vc) - { - if (!vc) - { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc"); - return NULL; - } - - return vc->eqn; - } - - t_parsed_exeinfo * parse_exeinfo(char const * exeinfo) - { - t_parsed_exeinfo * parsed_exeinfo; - - if (!exeinfo) { - return NULL; - } - - parsed_exeinfo = (t_parsed_exeinfo*)xmalloc(sizeof(t_parsed_exeinfo)); - parsed_exeinfo->exe = xstrdup(exeinfo); - parsed_exeinfo->time = 0; - parsed_exeinfo->size = 0; - - if (std::strcmp(prefs_get_version_exeinfo_match(), "parse") == 0) - { - struct std::tm t1; - char *exe; - char mask[MAX_EXEINFO_STR + 1]; - char * marker; - unsigned size; - char time_invalid = 0; - - if ((exeinfo[0] == '\0') || //happens when using war3-noCD and having deleted war3.org - (std::strcmp(exeinfo, "badexe") == 0)) //happens when AUTHREQ had no owner/exeinfo entry + for (auto jarchitecture : jclient.value().items()) { - xfree((void *)parsed_exeinfo->exe); - xfree((void *)parsed_exeinfo); - eventlog(eventlog_level_error, __FUNCTION__, "found empty exeinfo"); - return NULL; - } - - std::memset(&t1, 0, sizeof(t1)); - t1.tm_isdst = -1; - - exeinfo = strreverse((char *)exeinfo); - if (!(marker = std::strchr((char *)exeinfo, ' '))) - { - xfree((void *)parsed_exeinfo->exe); - xfree((void *)parsed_exeinfo); - return NULL; - } - for (; marker[0] == ' '; marker++); - - if (!(marker = std::strchr(marker, ' '))) - { - xfree((void *)parsed_exeinfo->exe); - xfree((void *)parsed_exeinfo); - return NULL; - } - for (; marker[0] == ' '; marker++); - - if (!(marker = std::strchr(marker, ' '))) - { - xfree((void *)parsed_exeinfo->exe); - xfree((void *)parsed_exeinfo); - return NULL; - } - for (; marker[0] == ' '; marker++); - marker--; - marker[0] = '\0'; - marker++; - - exe = xstrdup(marker); - xfree((void *)parsed_exeinfo->exe); - parsed_exeinfo->exe = strreverse((char *)exe); - - exeinfo = strreverse((char *)exeinfo); - - std::sprintf(mask, "%%02u/%%02u/%%u %%02u:%%02u:%%02u %%u"); - - if (std::sscanf(exeinfo, mask, &t1.tm_mon, &t1.tm_mday, &t1.tm_year, &t1.tm_hour, &t1.tm_min, &t1.tm_sec, &size) != 7) { - if (std::sscanf(exeinfo, "%*s %*s %u", &size) != 1) + t_tag architecture = tag_str_to_uint(jarchitecture.key().c_str()); + if (!tag_check_arch(architecture)) { - - eventlog(eventlog_level_warn, __FUNCTION__, "parser error while parsing pattern \"{}\"", exeinfo); - xfree((void *)parsed_exeinfo->exe); - xfree((void *)parsed_exeinfo); - return NULL; /* neq */ - } - time_invalid = 1; - } - - /* Now we have a Y2K problem :) Thanks for using a 2 digit decimal years, Blizzard. */ - /* 00-79 -> 2000-2079 - * * 80-99 -> 1980-1999 - * * 100+ unchanged */ - if (t1.tm_year < 80) - t1.tm_year = t1.tm_year + 100; - - if (time_invalid) - parsed_exeinfo->time = static_cast(-1); - else - parsed_exeinfo->time = std::mktime(&t1); - parsed_exeinfo->size = size; - } - - return parsed_exeinfo; - } - - - /* This implements some dumb kind of pattern matching. Any '?' - * signs in the pattern are treated as "don't care" signs. This - * means that it doesn't matter what's on this place in the match. - */ - //static int versioncheck_compare_exeinfo(char const * pattern, char const * match) - static int versioncheck_compare_exeinfo(t_parsed_exeinfo * pattern, t_parsed_exeinfo * match) - { - assert(pattern); - assert(match); - - if (!strcasecmp(prefs_get_version_exeinfo_match(), "none")) - return 0; /* ignore exeinfo */ - - if (std::strlen(pattern->exe) != std::strlen(match->exe)) - return 1; /* neq */ - - if (std::strcmp(prefs_get_version_exeinfo_match(), "exact") == 0) { - return strcasecmp(pattern->exe, match->exe); - } - else if (std::strcmp(prefs_get_version_exeinfo_match(), "exactcase") == 0) { - return std::strcmp(pattern->exe, match->exe); - } - else if (std::strcmp(prefs_get_version_exeinfo_match(), "wildcard") == 0) { - unsigned int i; - size_t pattern_exelen = std::strlen(pattern->exe); - for (i = 0; i < pattern_exelen; i++) - if ((pattern->exe[i] != '?') && /* out "don't care" sign */ - (safe_toupper(pattern->exe[i]) != safe_toupper(match->exe[i]))) - return 1; /* neq */ - return 0; /* ok */ - } - else if (std::strcmp(prefs_get_version_exeinfo_match(), "parse") == 0) { - - if (strcasecmp(pattern->exe, match->exe) != 0) - { - eventlog(eventlog_level_trace, __FUNCTION__, "filename differs"); - return 1; /* neq */ - } - if (pattern->size != match->size) - { - eventlog(eventlog_level_trace, __FUNCTION__, "size differs"); - return 1; /* neq */ - } - if ((pattern->time != -1) && prefs_get_version_exeinfo_maxdiff() && (abs(pattern->time - match->time) > (signed)prefs_get_version_exeinfo_maxdiff())) - { - eventlog(eventlog_level_trace, __FUNCTION__, "time differs by {}", abs(pattern->time - match->time)); - return 1; - } - return 0; /* ok */ - } - else { - eventlog(eventlog_level_error, __FUNCTION__, "unknown version exeinfo match method \"{}\"", prefs_get_version_exeinfo_match()); - return -1; /* neq/fail */ - } - } - - void free_parsed_exeinfo(t_parsed_exeinfo * parsed_exeinfo) - { - if (parsed_exeinfo) - { - if (parsed_exeinfo->exe) - xfree((void *)parsed_exeinfo->exe); - xfree((void *)parsed_exeinfo); - } - } - - extern int versioncheck_validate(t_versioncheck * vc, t_tag archtag, t_tag clienttag, char const * exeinfo, unsigned long versionid, unsigned long gameversion, unsigned long checksum) - { - t_elem const * curr; - t_versioninfo * vi; - int badexe, badcs; - t_parsed_exeinfo * parsed_exeinfo; - - if (!vc) - { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc"); - return -1; - } - - badexe = badcs = 0; - parsed_exeinfo = parse_exeinfo(exeinfo); - LIST_TRAVERSE_CONST(versioninfo_head, curr) - { - if (!(vi = (t_versioninfo*)elem_get_data(curr))) /* should not happen */ - { - eventlog(eventlog_level_error, __FUNCTION__, "version list contains NULL item"); - continue; - } - - if (vi->archtag != archtag) - continue; - if (vi->clienttag != clienttag) - continue; - if (std::strcmp(vi->eqn, vc->eqn) != 0) - continue; - if (std::strcmp(vi->mpqfile, vc->mpqfile) != 0) - continue; - - if (vi->versionid && vi->versionid != versionid) - continue; - - if (vi->gameversion && vi->gameversion != gameversion) - continue; - - - if ((!(parsed_exeinfo)) || (vi->parsed_exeinfo && (versioncheck_compare_exeinfo(vi->parsed_exeinfo, parsed_exeinfo) != 0))) - { - /* - * Found an entry matching but the exeinfo doesn't match. - * We need to rember this because if no other matching versions are found - * we will return badversion. - */ - badexe = 1; - } - else - badexe = 0; - - if (vi->checksum && vi->checksum != checksum) - { - /* - * Found an entry matching but the checksum doesn't match. - * We need to rember this because if no other matching versions are found - * we will return badversion. - */ - badcs = 1; - } - else - badcs = 0; - - if (vc->versiontag) - xfree((void *)vc->versiontag); - vc->versiontag = xstrdup(vi->versiontag); - - if (badexe || badcs) - continue; - - /* Ok, version and checksum matches or exeinfo/checksum are disabled - * anyway we have found a complete match */ - eventlog(eventlog_level_info, __FUNCTION__, "got a matching entry: {}", vc->versiontag); - free_parsed_exeinfo(parsed_exeinfo); - return 1; - } - - if (badcs) /* A match was found but the checksum was different */ - { - eventlog(eventlog_level_info, __FUNCTION__, "bad checksum, closest match is: {}", vc->versiontag); - free_parsed_exeinfo(parsed_exeinfo); - return -1; - } - if (badexe) /* A match was found but the exeinfo string was different */ - { - eventlog(eventlog_level_info, __FUNCTION__, "bad exeinfo, closest match is: {}", vc->versiontag); - free_parsed_exeinfo(parsed_exeinfo); - return -1; - } - - /* No match in list */ - eventlog(eventlog_level_info, __FUNCTION__, "no match in list, setting to: {}", vc->versiontag); - free_parsed_exeinfo(parsed_exeinfo); - return 0; - } - - extern int versioncheck_load(char const * filename) - { - std::FILE * fp; - unsigned int line; - unsigned int pos; - char * buff; - char * temp; - char const * eqn; - char const * mpqfile; - char const * archtag; - char const * clienttag; - char const * exeinfo; - char const * versionid; - char const * gameversion; - char const * checksum; - char const * versiontag; - t_versioninfo * vi; - - if (!filename) - { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL filename"); - return -1; - } - - if (!(versioninfo_head = list_create())) - { - eventlog(eventlog_level_error, __FUNCTION__, "could create list"); - return -1; - } - if (!(fp = std::fopen(filename, "r"))) - { - eventlog(eventlog_level_error, __FUNCTION__, "could not open file \"{}\" for reading (std::fopen: {})", filename, std::strerror(errno)); - list_destroy(versioninfo_head); - versioninfo_head = NULL; - return -1; - } - - line = 1; - for (; (buff = file_get_line(fp)); line++) - { - for (pos = 0; buff[pos] == '\t' || buff[pos] == ' '; pos++); - if (buff[pos] == '\0' || buff[pos] == '#') - { - continue; - } - if ((temp = std::strrchr(buff, '#'))) - { - unsigned int len; - unsigned int endpos; - - *temp = '\0'; - len = std::strlen(buff) + 1; - for (endpos = len - 1; buff[endpos] == '\t' || buff[endpos] == ' '; endpos--); - buff[endpos + 1] = '\0'; - } - - if (!(eqn = next_token(buff, &pos))) - { - eventlog(eventlog_level_error, __FUNCTION__, "missing eqn near line {} of file \"{}\"", line, filename); - continue; - } - line++; - if (!(mpqfile = next_token(buff, &pos))) - { - eventlog(eventlog_level_error, __FUNCTION__, "missing mpqfile near line {} of file \"{}\"", line, filename); - continue; - } - line++; - if (!(archtag = next_token(buff, &pos))) - { - eventlog(eventlog_level_error, __FUNCTION__, "missing archtag near line {} of file \"{}\"", line, filename); - continue; - } - line++; - if (!(clienttag = next_token(buff, &pos))) - { - eventlog(eventlog_level_error, __FUNCTION__, "missing clienttag near line {} of file \"{}\"", line, filename); - continue; - } - line++; - if (!(exeinfo = next_token(buff, &pos))) - { - eventlog(eventlog_level_error, __FUNCTION__, "missing exeinfo near line {} of file \"{}\"", line, filename); - continue; - } - line++; - if (!(versionid = next_token(buff, &pos))) - { - eventlog(eventlog_level_error, __FUNCTION__, "missing versionid near line {} of file \"{}\"", line, filename); - continue; - } - line++; - if (!(gameversion = next_token(buff, &pos))) - { - eventlog(eventlog_level_error, __FUNCTION__, "missing gameversion near line {} of file \"{}\"", line, filename); - continue; - } - line++; - if (!(checksum = next_token(buff, &pos))) - { - eventlog(eventlog_level_error, __FUNCTION__, "missing checksum near line {} of file \"{}\"", line, filename); - continue; - } - line++; - if (!(versiontag = next_token(buff, &pos))) - { - versiontag = NULL; - } - - vi = (t_versioninfo*)xmalloc(sizeof(t_versioninfo)); - vi->eqn = xstrdup(eqn); - vi->mpqfile = xstrdup(mpqfile); - if (std::strlen(archtag) != 4) - { - eventlog(eventlog_level_error, __FUNCTION__, "invalid arch tag on line {} of file \"{}\"", line, filename); - xfree((void *)vi->mpqfile); /* avoid warning */ - xfree((void *)vi->eqn); /* avoid warning */ - xfree(vi); - continue; - } - if (!tag_check_arch((vi->archtag = tag_str_to_uint(archtag)))) - { - eventlog(eventlog_level_error, __FUNCTION__, "got unknown archtag \"{}\"", archtag); - xfree((void *)vi->mpqfile); /* avoid warning */ - xfree((void *)vi->eqn); /* avoid warning */ - xfree(vi); - continue; - } - if (std::strlen(clienttag) != 4) - { - eventlog(eventlog_level_error, __FUNCTION__, "invalid client tag on line {} of file \"{}\"", line, filename); - xfree((void *)vi->mpqfile); /* avoid warning */ - xfree((void *)vi->eqn); /* avoid warning */ - xfree(vi); - continue; - } - if (!tag_check_client((vi->clienttag = tag_str_to_uint(clienttag)))) - { - eventlog(eventlog_level_error, __FUNCTION__, "got unknown clienttag\"{}\"", clienttag); - xfree((void *)vi->mpqfile); /* avoid warning */ - xfree((void *)vi->eqn); /* avoid warning */ - xfree(vi); - continue; - } - if (std::strcmp(exeinfo, "NULL") == 0) - vi->parsed_exeinfo = NULL; - else - { - if (!(vi->parsed_exeinfo = parse_exeinfo(exeinfo))) - { - eventlog(eventlog_level_error, __FUNCTION__, "encountered an error while parsing exeinfo"); - xfree((void *)vi->mpqfile); /* avoid warning */ - xfree((void *)vi->eqn); /* avoid warning */ - xfree(vi); - continue; - } - } - - vi->versionid = std::strtoul(versionid, NULL, 0); - if (verstr_to_vernum(gameversion, &vi->gameversion) < 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "malformed version on line {} of file \"{}\"", line, filename); - xfree((void *)vi->parsed_exeinfo); /* avoid warning */ - xfree((void *)vi->mpqfile); /* avoid warning */ - xfree((void *)vi->eqn); /* avoid warning */ - xfree(vi); - continue; - } - - vi->checksum = std::strtoul(checksum, NULL, 0); - if (versiontag) - vi->versiontag = xstrdup(versiontag); - else - vi->versiontag = NULL; - - - list_append_data(versioninfo_head, vi); - } - - file_get_line(NULL); // clear file_get_line buffer - if (std::fclose(fp) < 0) - eventlog(eventlog_level_error, __FUNCTION__, "could not close versioncheck file \"{}\" after reading (std::fclose: {})", filename, std::strerror(errno)); - - return 0; - } - - - extern int versioncheck_unload(void) - { - t_elem * curr; - t_versioninfo * vi; - - if (versioninfo_head) - { - LIST_TRAVERSE(versioninfo_head, curr) - { - if (!(vi = (t_versioninfo*)elem_get_data(curr))) /* should not happen */ - { - eventlog(eventlog_level_error, __FUNCTION__, "version list contains NULL item"); + eventlog(eventlog_level_error, __FUNCTION__, "Invalid architecture: {}", jarchitecture.key()); continue; } - if (list_remove_elem(versioninfo_head, &curr) < 0) - eventlog(eventlog_level_error, __FUNCTION__, "could not remove item from list"); - - if (vi->parsed_exeinfo) + for (auto jversion_id : jarchitecture.value().items()) { - if (vi->parsed_exeinfo->exe) - xfree((void *)vi->parsed_exeinfo->exe); - xfree((void *)vi->parsed_exeinfo); /* avoid warning */ - } - xfree((void *)vi->mpqfile); /* avoid warning */ - xfree((void *)vi->eqn); /* avoid warning */ - if (vi->versiontag) - xfree((void *)vi->versiontag); /* avoid warning */ - xfree(vi); - } + std::uint32_t version_id; + try + { + version_id = std::stoul(jversion_id.key(), nullptr, 0); + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "Invalid version id \"{}\": {}", jversion_id.key(), e.what()); + continue; + } - if (list_destroy(versioninfo_head) < 0) - return -1; - versioninfo_head = NULL; + // FIXME: do error checking for these + std::string checkrevision_filename = jversion_id.value()["checkRevisionFile"].get(); + std::string checkrevision_equation = jversion_id.value()["equation"].get(); + + cr_entries.emplace( + std::piecewise_construct, + std::forward_as_tuple(architecture, client, version_id), + std::forward_as_tuple(checkrevision_filename, checkrevision_equation) + ); + + for (auto jentry : jversion_id.value()["entries"]) + { + try + { + VersionCheck entry(jentry["title"].get(), + version_id, + jentry["version"].get(), + jentry["hash"].get(), + architecture, + client, + jentry["versionTag"].get() + ); + + vc_entries.insert({ std::make_tuple(entry.m_version_id, entry.m_game_version, entry.m_architecture, entry.m_client), entry }); + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "Failed to load versioncheck entry: {}", e.what()); + continue; + } + } + } + } } - return 0; + auto t1 = std::chrono::steady_clock::now(); + + eventlog(eventlog_level_info, __FUNCTION__, "Successfully loaded {} versioncheck entries in {} microseconds", vc_entries.size(), std::chrono::duration_cast(t1 - t0).count()); + + versioncheck_conf_is_loaded = true; + + return true; } + void unload_versioncheck_conf() + { + if (versioncheck_conf_is_loaded) + { + vc_entries.clear(); + + cr_entries.clear(); + + versioncheck_conf_is_loaded = false; + + eventlog(eventlog_level_info, __FUNCTION__, "Successfully unloaded all version check entries"); + } + } + + std::tuple select_checkrevision(t_tag architecture, t_tag client, std::uint32_t version_id) + { + static const std::tuple default_checkrevision { "ver-IX86-1.mpq", "A=42 B=42 C=42 4 A=A^S B=B^B C=C^C A=A^S" }; + + auto key = cr_entries.find(std::make_tuple(architecture, client, version_id)); + if (key != cr_entries.end()) + { + return key->second; + } + + eventlog(eventlog_level_debug, __FUNCTION__, "Could not find corresponding CheckRevision entry, returning default CheckRevision"); + + return default_checkrevision; + } + + // could use C++17 std::optional here + const VersionCheck* select_versioncheck(t_tag architecture, t_tag client, std::uint32_t version_id, + std::uint32_t checkrevision_version, std::uint32_t checkrevision_checksum) + { + auto it = vc_entries.find(std::make_tuple(version_id, checkrevision_version, architecture, client)); + if (it == vc_entries.end()) + { + return nullptr; + } + + if (it->second.m_checksum != checkrevision_checksum + && !prefs_get_allow_bad_version()) + { + return nullptr; + } + + return &(it->second); + } + + VersionCheck::VersionCheck(const std::string& title, std::uint32_t version_id, const std::string& game_version, + const std::string& checksum, t_tag architecture, t_tag client, const std::string& version_tag) + : m_version_id(version_id), m_architecture(architecture), m_client(client) + { + if (verstr_to_vernum(game_version.c_str(), reinterpret_cast(&this->m_game_version)) < 0) + { + throw std::runtime_error("Invalid version \"" + game_version + "\" in entry \"" + title + "\""); + } + + this->m_checksum = std::stoul(checksum, nullptr, 0); + + // FIXME: check for uniqueness + this->m_version_tag = version_tag; + } + + std::string VersionCheck::get_version_tag() const + { + return this->m_version_tag; + } + + /* + // This function only validates the format of the metadata string + // There is no need to validate the information in the string because validating the checksum and file version is sufficient + bool validate_file_metadata_format(const std::string& metadata) + { + if (metadata.empty()) + { + return false; + } + + try + { + std::smatch tokens; + if (std::regex_match(metadata, tokens, std::regex{ R"(([[:print:]]+\.exe) (\d\d/\d\d/\d\d \d\d:\d\d:\d\d) (\d+))" }) == false) + { + eventlog(eventlog_level_error, __FUNCTION__, "got invalid file metadata \"{}\"", metadata); + return false; + } + } + catch (const std::regex_error& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "{} (code {})", e.what(), e.code()); + return false; + } + + return true; + } + */ + } } diff --git a/src/bnetd/versioncheck.h b/src/bnetd/versioncheck.h index e1bef94..eb71792 100644 --- a/src/bnetd/versioncheck.h +++ b/src/bnetd/versioncheck.h @@ -16,86 +16,81 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef INCLUDED_VERSIONCHECK_TYPES -#define INCLUDED_VERSIONCHECK_TYPES +#ifndef PVPGN_BNETD_VERSIONCHECK_H +#define PVPGN_BNETD_VERSIONCHECK_H +#include #include +#include +#include #include "common/tag.h" -namespace pvpgn -{ - - namespace bnetd - { - -#ifdef VERSIONCHECK_INTERNAL_ACCESS - typedef struct - { - char const * exe; - std::time_t time; - int size; - } t_parsed_exeinfo; -#endif - -#ifdef VERSIONCHECK_INTERNAL_ACCESS - typedef struct - { - char const * eqn; - char const * mpqfile; - t_tag archtag; - t_tag clienttag; - char const * versiontag; - t_parsed_exeinfo * parsed_exeinfo; - unsigned long versionid; - unsigned long gameversion; - unsigned long checksum; - } t_versioninfo; -#endif - - typedef struct s_versioncheck -#ifdef VERSIONCHECK_INTERNAL_ACCESS - { - char const * eqn; - char const * mpqfile; - char const * versiontag; - } -#endif - t_versioncheck; - - } - -} - -#endif - -#ifndef JUST_NEED_TYPES -#ifndef INCLUDED_VERSIONCHECK_PROTOS -#define INCLUDED_VERSIONCHECK_PROTOS namespace pvpgn { namespace bnetd { + class VersionCheck; - extern t_versioncheck * versioncheck_create(t_tag archtag, t_tag clienttag); - extern int versioncheck_destroy(t_versioncheck * vc); - extern char const * versioncheck_get_mpqfile(t_versioncheck const * vc); - extern char const * versioncheck_get_eqn(t_versioncheck const * vc); - extern int versioncheck_validate(t_versioncheck * vc, t_tag archtag, t_tag clienttag, char const * exeinfo, unsigned long versionid, unsigned long gameversion, unsigned long checksum); - extern int versioncheck_load(char const * filename); - extern int versioncheck_unload(void); + // filename, equation + std::tuple select_checkrevision(t_tag architecture, t_tag client, std::uint32_t version_id); - extern char const * versioncheck_get_versiontag(t_versioncheck const * vc); - extern int versioncheck_set_versiontag(t_versioncheck * vc, char const * versiontag); + const VersionCheck* select_versioncheck(t_tag architecture, t_tag client, std::uint32_t version_id, + std::uint32_t checkrevision_version, std::uint32_t checkrevision_checksum); - } -} + /*******************************************************************************/ + // Conf + /*******************************************************************************/ + bool load_versioncheck_conf(const std::string& filename); + void unload_versioncheck_conf(); -#endif -#endif + + class VersionCheck + { + public: + /*******************************************************************************/ + // Getters + /*******************************************************************************/ + std::string get_version_tag() const; + + /*******************************************************************************/ + // Deconstructors + /*******************************************************************************/ + ~VersionCheck() = default; + private: + /*******************************************************************************/ + // Friend functions + /*******************************************************************************/ + friend bool load_versioncheck_conf(const std::string& filename); + friend const VersionCheck* select_versioncheck(t_tag architecture, t_tag client, std::uint32_t version_id, + std::uint32_t checkrevision_version, std::uint32_t checkrevision_checksum); + + + /*******************************************************************************/ + // Constructors + /*******************************************************************************/ + VersionCheck(const std::string& title, std::uint32_t version_id, const std::string& game_version, + const std::string& checksum, t_tag architecture, t_tag client, const std::string& version_tag); + + /*******************************************************************************/ + // Member variables + /*******************************************************************************/ + std::uint32_t m_version_id; // AKA "Version Byte" + std::uint32_t m_game_version; // Windows file version + std::uint32_t m_checksum; + t_tag m_architecture; + t_tag m_client; + std::string m_version_tag; + }; + + } // namespace bnetd + +} // namespace pvpgn + +#endif //PVPGN_BNETD_VERSIONCHECK_H \ No newline at end of file diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 6ed6edd..1a90f1a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -10,7 +10,7 @@ set(COMMON_SOURCES fdwatch_poll.h fdwatch_select.cpp fdwatch_select.h fdwbackend.cpp fdwbackend.h field_sizes.h file_protocol.h flags.h give_up_root_privileges.cpp give_up_root_privileges.h hashtable.cpp - hashtable.h hexdump.cpp hexdump.h init_protocol.h introtate.h + hashtable.h hash_tuple.hpp hexdump.cpp hexdump.h init_protocol.h introtate.h irc_protocol.h list.cpp list.h lstr.h make_unique.hpp network.cpp network.h packet.cpp packet.h proginfo.cpp proginfo.h queue.cpp queue.h rcm.cpp rcm.h rlimit.cpp rlimit.h scoped_array.h scoped_ptr.h setup_after.h diff --git a/src/common/hash_tuple.hpp b/src/common/hash_tuple.hpp new file mode 100644 index 0000000..2c5bb71 --- /dev/null +++ b/src/common/hash_tuple.hpp @@ -0,0 +1,67 @@ +#ifndef INCLUDED_PVPGN_HASH_TUPLE_H +#define INCLUDED_PVPGN_HASH_TUPLE_H + +#include +#include +#include + +// https://stackoverflow.com/questions/7110301/generic-hash-for-tuples-in-unordered-map-unordered-set +namespace pvpgn +{ + namespace hash_tuple + { + template + struct hash + { + std::size_t operator()(TT const& tt) const + { + return std::hash()(tt); + } + }; + + namespace + { + template + inline void hash_combine(std::size_t& seed, T const& v) + { + seed ^= hash_tuple::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + } + + namespace + { + // Recursive template code derived from Matthieu M. + template ::value - 1> + struct HashValueImpl + { + static void apply(std::size_t& seed, Tuple const& tuple) + { + HashValueImpl::apply(seed, tuple); + hash_combine(seed, std::get(tuple)); + } + }; + + template + struct HashValueImpl + { + static void apply(std::size_t& seed, Tuple const& tuple) + { + hash_combine(seed, std::get<0>(tuple)); + } + }; + } + + template + struct hash> + { + std::size_t operator()(std::tuple const& tt) const + { + std::size_t seed = 0; + HashValueImpl >::apply(seed, tt); + return seed; + } + }; + } +} + +#endif \ No newline at end of file diff --git a/src/common/setup_before.h b/src/common/setup_before.h index b84c9c5..a7bb72a 100644 --- a/src/common/setup_before.h +++ b/src/common/setup_before.h @@ -240,7 +240,7 @@ const unsigned BNETD_HASHTABLE_SIZE = 61; const int BNETD_REALM_PORT = 6113; /* where D2CS listens */ const char * const BNETD_TELNET_ADDRS = ""; /* this means none */ const int BNETD_TELNET_PORT = 23; /* used if port not specified */ -const char * const BNETD_EXEINFO_MATCH = "exact"; +const char * const BNETD_EXEINFO_MATCH = "true"; const unsigned PVPGN_VERSION_TIMEDIV = 0; /* no timediff check by default */ const int PVPGN_CACHE_MEMLIMIT = 5000000; /* bytes */ const char * const PVPGN_DEFAULT_SYMB = "-_[]"; diff --git a/src/json/json.hpp b/src/json/json.hpp index 5b0b0ea..6b6655a 100644 --- a/src/json/json.hpp +++ b/src/json/json.hpp @@ -1,11 +1,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 3.0.1 +| | |__ | | | | | | version 3.1.2 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -29,34 +29,94 @@ SOFTWARE. #ifndef NLOHMANN_JSON_HPP #define NLOHMANN_JSON_HPP -#include // all_of, copy, fill, find, for_each, generate_n, none_of, remove, reverse, transform -#include // array +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 1 +#define NLOHMANN_JSON_VERSION_PATCH 2 + +#include // all_of, find, for_each #include // assert #include // and, not, or -#include // lconv, localeconv -#include // isfinite, labs, ldexp, signbit #include // nullptr_t, ptrdiff_t, size_t -#include // int64_t, uint64_t -#include // abort, strtod, strtof, strtold, strtoul, strtoll, strtoull -#include // memcpy, strlen -#include // forward_list -#include // function, hash, less +#include // hash, less #include // initializer_list -#include // hex -#include // istream, ostream -#include // advance, begin, back_inserter, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator -#include // numeric_limits -#include // locale -#include // map -#include // addressof, allocator, allocator_traits, unique_ptr +#include // istream, ostream +#include // iterator_traits, random_access_iterator_tag #include // accumulate -#include // stringstream -#include // getline, stoi, string, to_string -#include // add_pointer, conditional, decay, enable_if, false_type, integral_constant, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_default_constructible, is_enum, is_floating_point, is_integral, is_nothrow_move_assignable, is_nothrow_move_constructible, is_pointer, is_reference, is_same, is_scalar, is_signed, remove_const, remove_cv, remove_pointer, remove_reference, true_type, underlying_type -#include // declval, forward, make_pair, move, pair, swap -#include // valarray +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string #include // vector +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + // exclude unsupported compilers #if defined(__clang__) #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 @@ -90,7 +150,7 @@ SOFTWARE. #endif // allow to disable exceptions -#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && not defined(JSON_NOEXCEPTION) +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception #define JSON_TRY try #define JSON_CATCH(exception) catch(exception) @@ -100,6 +160,20 @@ SOFTWARE. #define JSON_CATCH(exception) if(false) #endif +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER +#endif + // manual branch prediction #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) @@ -117,27 +191,6 @@ SOFTWARE. #define JSON_HAS_CPP_14 #endif -/*! -@brief namespace for Niels Lohmann -@see https://github.com/nlohmann -@since version 1.0.0 -*/ -namespace nlohmann -{ -template -struct adl_serializer; - -// forward declaration of basic_json (required to split the class) -template class ObjectType = std::map, - template class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator, - template class JSONSerializer = adl_serializer> -class basic_json; - // Ugly macros to avoid uglier copy-paste when specializing basic_json. They // may be removed in the future once the class is split. @@ -154,17 +207,300 @@ class basic_json; NumberIntegerType, NumberUnsignedType, NumberFloatType, \ AllocatorType, JSONSerializer> - /*! -@brief unnamed namespace with internal helper functions +@brief Helper to determine whether there's a key_type for T. -This namespace collects some functions that could not be defined inside the -@ref basic_json class. +This helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template struct has_##type { \ + private: \ + template \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral()))>::value; \ + } + +// #include + + +#include // not +#include // size_t +#include // numeric_limits +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // declval + +// #include + +// #include + + +namespace nlohmann +{ +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. @since version 2.1.0 */ namespace detail { +///////////// +// helpers // +///////////// + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +/* +Implementation of two C++17 constructs: conjunction, negation. This is needed +to avoid evaluating all the traits in a condition + +For example: not std::is_same::value and has_value_type::value +will not compile when T = void (on MSVC at least). Whereas +conjunction>, has_value_type>::value will +stop evaluating if negation<...>::value == false + +Please note that those constructs must be used with caution, since symbols can +become very long quickly (which can slow down compilation and cause MSVC +internal compiler errors). Only use it when you have to (see example ahead). +*/ +template struct conjunction : std::true_type {}; +template struct conjunction : B1 {}; +template +struct conjunction : std::conditional, B1>::type {}; + +template struct negation : std::integral_constant {}; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +//////////////////////// +// has_/is_ functions // +//////////////////////// + +// source: https://stackoverflow.com/a/37193089/4116453 + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl +{ + static constexpr auto value = + std::is_constructible::value and + std::is_constructible::value; +}; + +template +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction>, + has_mapped_type, + has_key_type>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template +struct is_basic_json_nested_type +{ + static auto constexpr value = std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value; +}; + +template +struct is_compatible_array_type +{ + static auto constexpr value = + conjunction>, + negation>, + negation>, + negation>, + has_value_type, + has_iterator>::value; +}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type +{ + static constexpr auto value = + is_compatible_integer_type_impl < + std::is_integral::value and + not std::is_same::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json +{ + private: + // also check the return type of from_json + template::from_json( + std::declval(), std::declval()))>::value>> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t::from_json(std::declval()))>::value >> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +template +struct has_to_json +{ + private: + template::to_json( + std::declval(), std::declval()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +template +struct is_compatible_complete_type +{ + static constexpr bool value = + not std::is_base_of::value and + not is_basic_json::value and + not is_basic_json_nested_type::value and + has_to_json::value; +}; + +template +struct is_compatible_type + : conjunction, + is_compatible_complete_type> +{ +}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} +} + +// #include + + +#include // exception +#include // runtime_error +#include // to_string + +namespace nlohmann +{ +namespace detail +{ //////////////// // exceptions // //////////////// @@ -419,6 +755,8 @@ json.exception.out_of_range.403 | key 'foo' not found | The provided key was not json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON only supports integers numbers up to 9223372036854775807. | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | @liveexample{The following code shows how an `out_of_range` exception can be caught.,out_of_range} @@ -481,9 +819,21 @@ class other_error : public exception private: other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; +} +} + +// #include +#include // array +#include // and +#include // size_t +#include // uint8_t +namespace nlohmann +{ +namespace detail +{ /////////////////////////// // JSON type enumeration // /////////////////////////// @@ -512,7 +862,7 @@ value with the default value for a given type @since version 1.0.0 */ -enum class value_t : uint8_t +enum class value_t : std::uint8_t { null, ///< null value object, ///< object (unordered set of name/value pairs) @@ -537,7 +887,7 @@ Returns an ordering that is similar to Python: */ inline bool operator<(const value_t lhs, const value_t rhs) noexcept { - static constexpr std::array order = {{ + static constexpr std::array order = {{ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */ } @@ -547,80 +897,356 @@ inline bool operator<(const value_t lhs, const value_t rhs) noexcept const auto r_index = static_cast(rhs); return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index]; } +} +} + +// #include -///////////// -// helpers // -///////////// +#include // transform +#include // array +#include // and, not +#include // forward_list +#include // inserter, front_inserter, end +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // pair, declval +#include // valarray -template struct is_basic_json : std::false_type {}; +// #include -NLOHMANN_BASIC_JSON_TPL_DECLARATION -struct is_basic_json : std::true_type {}; +// #include -// alias templates to reduce boilerplate -template -using enable_if_t = typename std::enable_if::type; +// #include -template -using uncvref_t = typename std::remove_cv::type>::type; +// #include -// implementation of C++14 index_sequence and affiliates -// source: https://stackoverflow.com/a/32223343 -template -struct index_sequence + +namespace nlohmann { - using type = index_sequence; - using value_type = std::size_t; - static constexpr std::size_t size() noexcept +namespace detail +{ +// overloads for basic_json template parameters +template::value and + not std::is_same::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) { - return sizeof...(Ints); + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_UNLIKELY(not j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_UNLIKELY(not j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + arr = *j.template get_ptr(); +} + +// forward_list doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get(); + }); +} + +// valarray doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::valarray& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.resize(j.size()); + std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); +} + +template +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) +{ + using std::end; + + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval()), + void()) +{ + using std::end; + + arr.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +void from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type::value and + not std::is_same::value and + std::is_constructible < + BasicJsonType, typename CompatibleArrayType::value_type >::value, + int > = 0 > +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + std::string(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<2> {}); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); + } + + auto inner_object = j.template get_ptr(); + using value_type = typename CompatibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(obj, obj.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get()); + }); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value, + int> = 0> +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, std::pair& p) +{ + p = {j.at(0).template get(), j.at(1).template get()}; +} + +template +void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence) +{ + t = std::make_tuple(j.at(Idx).template get::type>()...); +} + +template +void from_json(const BasicJsonType& j, std::tuple& t) +{ + from_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct from_json_fn +{ + private: + template + auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template + void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); } }; +} -template -struct merge_and_renumber; +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace +{ +constexpr const auto& from_json = detail::static_const::value; +} +} -template -struct merge_and_renumber, index_sequence> - : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; - -template -struct make_index_sequence - : merge_and_renumber < typename make_index_sequence < N / 2 >::type, - typename make_index_sequence < N - N / 2 >::type > {}; - -template<> struct make_index_sequence<0> : index_sequence<> {}; -template<> struct make_index_sequence<1> : index_sequence<0> {}; - -template -using index_sequence_for = make_index_sequence; - -/* -Implementation of two C++17 constructs: conjunction, negation. This is needed -to avoid evaluating all the traits in a condition - -For example: not std::is_same::value and has_value_type::value -will not compile when T = void (on MSVC at least). Whereas -conjunction>, has_value_type>::value will -stop evaluating if negation<...>::value == false - -Please note that those constructs must be used with caution, since symbols can -become very long quickly (which can slow down compilation and cause MSVC -internal compiler errors). Only use it when you have to (see example ahead). -*/ -template struct conjunction : std::true_type {}; -template struct conjunction : B1 {}; -template -struct conjunction : std::conditional, B1>::type {}; - -template struct negation : std::integral_constant {}; - -// dispatch utility (taken from ranges-v3) -template struct priority_tag : priority_tag < N - 1 > {}; -template<> struct priority_tag<0> {}; +// #include +#include // or, and, not +#include // begin, end +#include // tuple, get +#include // is_same, is_constructible, is_floating_point, is_enum, underlying_type +#include // move, forward, declval, pair +#include // valarray +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ ////////////////// // constructors // ////////////////// @@ -783,159 +1409,6 @@ struct external_constructor } }; - -//////////////////////// -// has_/is_ functions // -//////////////////////// - -/*! -@brief Helper to determine whether there's a key_type for T. - -This helper is used to tell associative containers apart from other containers -such as sequence containers. For instance, `std::map` passes the test as it -contains a `mapped_type`, whereas `std::vector` fails the test. - -@sa http://stackoverflow.com/a/7728728/266378 -@since version 1.0.0, overworked in version 2.0.6 -*/ -#define NLOHMANN_JSON_HAS_HELPER(type) \ - template struct has_##type { \ - private: \ - template \ - static int detect(U &&); \ - static void detect(...); \ - public: \ - static constexpr bool value = \ - std::is_integral()))>::value; \ - } - -NLOHMANN_JSON_HAS_HELPER(mapped_type); -NLOHMANN_JSON_HAS_HELPER(key_type); -NLOHMANN_JSON_HAS_HELPER(value_type); -NLOHMANN_JSON_HAS_HELPER(iterator); - -#undef NLOHMANN_JSON_HAS_HELPER - - -template -struct is_compatible_object_type_impl : std::false_type {}; - -template -struct is_compatible_object_type_impl -{ - static constexpr auto value = - std::is_constructible::value and - std::is_constructible::value; -}; - -template -struct is_compatible_object_type -{ - static auto constexpr value = is_compatible_object_type_impl < - conjunction>, - has_mapped_type, - has_key_type>::value, - typename BasicJsonType::object_t, CompatibleObjectType >::value; -}; - -template -struct is_basic_json_nested_type -{ - static auto constexpr value = std::is_same::value or - std::is_same::value or - std::is_same::value or - std::is_same::value; -}; - -template -struct is_compatible_array_type -{ - static auto constexpr value = - conjunction>, - negation>, - negation>, - negation>, - has_value_type, - has_iterator>::value; -}; - -template -struct is_compatible_integer_type_impl : std::false_type {}; - -template -struct is_compatible_integer_type_impl -{ - // is there an assert somewhere on overflows? - using RealLimits = std::numeric_limits; - using CompatibleLimits = std::numeric_limits; - - static constexpr auto value = - std::is_constructible::value and - CompatibleLimits::is_integer and - RealLimits::is_signed == CompatibleLimits::is_signed; -}; - -template -struct is_compatible_integer_type -{ - static constexpr auto value = - is_compatible_integer_type_impl < - std::is_integral::value and - not std::is_same::value, - RealIntegerType, CompatibleNumberIntegerType >::value; -}; - - -// trait checking if JSONSerializer::from_json(json const&, udt&) exists -template -struct has_from_json -{ - private: - // also check the return type of from_json - template::from_json( - std::declval(), std::declval()))>::value>> - static int detect(U&&); - static void detect(...); - - public: - static constexpr bool value = std::is_integral>()))>::value; -}; - -// This trait checks if JSONSerializer::from_json(json const&) exists -// this overload is used for non-default-constructible user-defined-types -template -struct has_non_default_from_json -{ - private: - template::from_json(std::declval()))>::value>> - static int detect(U&&); - static void detect(...); - - public: - static constexpr bool value = std::is_integral>()))>::value; -}; - -// This trait checks if BasicJsonType::json_serializer::to_json exists -template -struct has_to_json -{ - private: - template::to_json( - std::declval(), std::declval()))> - static int detect(U&&); - static void detect(...); - - public: - static constexpr bool value = std::is_integral>()))>::value; -}; - - ///////////// // to_json // ///////////// @@ -1055,261 +1528,6 @@ void to_json(BasicJsonType& j, const std::tuple& t) to_json_tuple_impl(j, t, index_sequence_for {}); } -/////////////// -// from_json // -/////////////// - -// overloads for basic_json template parameters -template::value and - not std::is_same::value, - int> = 0> -void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) -{ - switch (static_cast(j)) - { - case value_t::number_unsigned: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_integer: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_float: - { - val = static_cast(*j.template get_ptr()); - break; - } - - default: - JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); - } -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) -{ - if (JSON_UNLIKELY(not j.is_boolean())) - { - JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); - } - b = *j.template get_ptr(); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) -{ - if (JSON_UNLIKELY(not j.is_string())) - { - JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); - } - s = *j.template get_ptr(); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) -{ - get_arithmetic_value(j, val); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) -{ - get_arithmetic_value(j, val); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) -{ - get_arithmetic_value(j, val); -} - -template::value, int> = 0> -void from_json(const BasicJsonType& j, EnumType& e) -{ - typename std::underlying_type::type val; - get_arithmetic_value(j, val); - e = static_cast(val); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - arr = *j.template get_ptr(); -} - -// forward_list doesn't have an insert method -template::value, int> = 0> -void from_json(const BasicJsonType& j, std::forward_list& l) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - std::transform(j.rbegin(), j.rend(), - std::front_inserter(l), [](const BasicJsonType & i) - { - return i.template get(); - }); -} - -// valarray doesn't have an insert method -template::value, int> = 0> -void from_json(const BasicJsonType& j, std::valarray& l) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - l.resize(j.size()); - std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); -} - -template -void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) -{ - using std::end; - - std::transform(j.begin(), j.end(), - std::inserter(arr, end(arr)), [](const BasicJsonType & i) - { - // get() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get(); - }); -} - -template -auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) --> decltype( - arr.reserve(std::declval()), - void()) -{ - using std::end; - - arr.reserve(j.size()); - std::transform(j.begin(), j.end(), - std::inserter(arr, end(arr)), [](const BasicJsonType & i) - { - // get() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get(); - }); -} - -template -void from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) -{ - for (std::size_t i = 0; i < N; ++i) - { - arr[i] = j.at(i).template get(); - } -} - -template::value and - std::is_convertible::value and - not std::is_same::value, int> = 0> -void from_json(const BasicJsonType& j, CompatibleArrayType& arr) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - - from_json_array_impl(j, arr, priority_tag<2> {}); -} - -template::value, int> = 0> -void from_json(const BasicJsonType& j, CompatibleObjectType& obj) -{ - if (JSON_UNLIKELY(not j.is_object())) - { - JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); - } - - auto inner_object = j.template get_ptr(); - using value_type = typename CompatibleObjectType::value_type; - std::transform( - inner_object->begin(), inner_object->end(), - std::inserter(obj, obj.begin()), - [](typename BasicJsonType::object_t::value_type const & p) - { - return value_type(p.first, p.second.template get()); - }); -} - -// overload for arithmetic types, not chosen for basic_json template arguments -// (BooleanType, etc..); note: Is it really necessary to provide explicit -// overloads for boolean_t etc. in case of a custom BooleanType which is not -// an arithmetic type? -template::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value, - int> = 0> -void from_json(const BasicJsonType& j, ArithmeticType& val) -{ - switch (static_cast(j)) - { - case value_t::number_unsigned: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_integer: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_float: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::boolean: - { - val = static_cast(*j.template get_ptr()); - break; - } - - default: - JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); - } -} - -template -void from_json(const BasicJsonType& j, std::pair& p) -{ - p = {j.at(0).template get(), j.at(1).template get()}; -} - -template -void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence) -{ - t = std::make_tuple(j.at(Idx).template get::type>()...); -} - -template -void from_json(const BasicJsonType& j, std::tuple& t) -{ - from_json_tuple_impl(j, t, index_sequence_for {}); -} - struct to_json_fn { private: @@ -1342,50 +1560,39 @@ struct to_json_fn return call(j, std::forward(val), priority_tag<1> {}); } }; +} -struct from_json_fn +/// namespace to hold default `to_json` function +namespace { - private: - template - auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const - noexcept(noexcept(from_json(j, val))) - -> decltype(from_json(j, val), void()) - { - return from_json(j, val); - } +constexpr const auto& to_json = detail::static_const::value; +} +} - template - void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept - { - static_assert(sizeof(BasicJsonType) == 0, - "could not find from_json() method in T's namespace"); -#ifdef _MSC_VER - // MSVC does not show a stacktrace for the above assert - using decayed = uncvref_t; - static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, - "forcing MSVC stacktrace to show which T we're talking about."); -#endif - } +// #include - public: - template - void operator()(const BasicJsonType& j, T& val) const - noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) - { - return call(j, val, priority_tag<1> {}); - } -}; -// taken from ranges-v3 -template -struct static_const +#include // min +#include // array +#include // assert +#include // size_t +#include // strlen +#include // streamsize, streamoff, streampos +#include // istream +#include // begin, end, iterator_traits, random_access_iterator_tag, distance, next +#include // shared_ptr, make_shared, addressof +#include // accumulate +#include // string, char_traits +#include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer +#include // pair, declval + +// #include + + +namespace nlohmann +{ +namespace detail { - static constexpr T value{}; -}; - -template -constexpr T static_const::value; - //////////////////// // input adapters // //////////////////// @@ -1624,10 +1831,34 @@ class input_adapter /// the actual adapter input_adapter_t ia = nullptr; }; +} +} -////////////////////// -// lexer and parser // -////////////////////// +// #include + + +#include // localeconv +#include // size_t +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // hex, uppercase +#include // setw, setfill +#include // stringstream +#include // char_traits, string +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////// +// lexer // +/////////// /*! @brief lexical analysis @@ -1640,6 +1871,7 @@ class lexer using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; public: /// token types for the parser @@ -1819,9 +2051,10 @@ class lexer @brief scan a string literal This function scans a string according to Sect. 7 of RFC 7159. While - scanning, bytes are escaped and copied into buffer yytext. Then the function - returns successfully, yytext is *not* null-terminated (as it may contain \0 - bytes), and yytext.size() is the number of bytes in the string. + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. @return token_type::value_string if string could be successfully scanned, token_type::parse_error otherwise @@ -1831,7 +2064,7 @@ class lexer */ token_type scan_string() { - // reset yytext (ignore opening quote) + // reset token_buffer (ignore opening quote) reset(); // we entered the function by reading an open quote @@ -2303,7 +2536,7 @@ class lexer contains cycles, but any cycle can be left when EOF is read. Therefore, the function is guaranteed to terminate. - During scanning, the read bytes are stored in yytext. This string is + During scanning, the read bytes are stored in token_buffer. This string is then converted to a signed integer, an unsigned integer, or a floating-point number. @@ -2317,7 +2550,7 @@ class lexer */ token_type scan_number() { - // reset yytext to store the number's bytes + // reset token_buffer to store the number's bytes reset(); // the type of the parsed number; initially set to unsigned; will be @@ -2601,10 +2834,10 @@ scan_number_done: // try to parse integers first and fall back to floats if (number_type == token_type::value_unsigned) { - const auto x = std::strtoull(yytext.data(), &endptr, 10); + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); // we checked the number format before - assert(endptr == yytext.data() + yytext.size()); + assert(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { @@ -2617,10 +2850,10 @@ scan_number_done: } else if (number_type == token_type::value_integer) { - const auto x = std::strtoll(yytext.data(), &endptr, 10); + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); // we checked the number format before - assert(endptr == yytext.data() + yytext.size()); + assert(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { @@ -2634,10 +2867,10 @@ scan_number_done: // this code is reached if we parse a floating-point number or if an // integer conversion above failed - strtof(value_float, yytext.data(), &endptr); + strtof(value_float, token_buffer.data(), &endptr); // we checked the number format before - assert(endptr == yytext.data() + yytext.size()); + assert(endptr == token_buffer.data() + token_buffer.size()); return token_type::value_float; } @@ -2666,10 +2899,10 @@ scan_number_done: // input management ///////////////////// - /// reset yytext; current character is beginning of token + /// reset token_buffer; current character is beginning of token void reset() noexcept { - yytext.clear(); + token_buffer.clear(); token_string.clear(); token_string.push_back(std::char_traits::to_char_type(current)); } @@ -2707,10 +2940,10 @@ scan_number_done: } } - /// add a character to yytext + /// add a character to token_buffer void add(int c) { - yytext.push_back(std::char_traits::to_char_type(c)); + token_buffer.push_back(std::char_traits::to_char_type(c)); } public: @@ -2737,9 +2970,9 @@ scan_number_done: } /// return current string value (implicitly resets the token; useful only once) - std::string move_string() + string_t&& move_string() { - return std::move(yytext); + return std::move(token_buffer); } ///////////////////// @@ -2867,7 +3100,7 @@ scan_number_done: std::vector token_string {}; /// buffer for variable-length tokens (numbers, strings) - std::string yytext {}; + string_t token_buffer {}; /// a description of occurred lexer errors const char* error_message = ""; @@ -2880,6 +3113,37 @@ scan_number_done: /// the decimal point const char decimal_point_char = '.'; }; +} +} + +// #include + + +#include // assert +#include // isfinite +#include // uint8_t +#include // function +#include // string +#include // move + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////// +// parser // +//////////// /*! @brief syntax analysis @@ -2892,6 +3156,7 @@ class parser using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; using lexer_t = lexer; using token_type = typename lexer_t::token_type; @@ -3035,7 +3300,7 @@ class parser } // parse values - std::string key; + string_t key; BasicJsonType value; while (true) { @@ -3263,6 +3528,7 @@ class parser if (keep and callback and not callback(depth, parse_event_t::value, result)) { + result.m_value.destroy(result.m_type); result.m_type = value_t::discarded; } } @@ -3445,12 +3711,20 @@ class parser /// whether to throw exceptions in case of errors const bool allow_exceptions = true; }; +} +} -/////////////// -// iterators // -/////////////// +// #include -/*! + +#include // ptrdiff_t +#include // numeric_limits + +namespace nlohmann +{ +namespace detail +{ +/* @brief an iterator for primitive JSON types This class models an iterator for primitive JSON types (boolean, number, @@ -3461,9 +3735,15 @@ end_value (`1`) models past the end. */ class primitive_iterator_t { - public: + private: using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: constexpr difference_type get_value() const noexcept { return m_it; @@ -3503,10 +3783,10 @@ class primitive_iterator_t return lhs.m_it < rhs.m_it; } - primitive_iterator_t operator+(difference_type i) + primitive_iterator_t operator+(difference_type n) noexcept { auto result = *this; - result += i; + result += n; return result; } @@ -3515,57 +3795,57 @@ class primitive_iterator_t return lhs.m_it - rhs.m_it; } - friend std::ostream& operator<<(std::ostream& os, primitive_iterator_t it) - { - return os << it.m_it; - } - - primitive_iterator_t& operator++() + primitive_iterator_t& operator++() noexcept { ++m_it; return *this; } - primitive_iterator_t const operator++(int) + primitive_iterator_t const operator++(int) noexcept { auto result = *this; m_it++; return result; } - primitive_iterator_t& operator--() + primitive_iterator_t& operator--() noexcept { --m_it; return *this; } - primitive_iterator_t const operator--(int) + primitive_iterator_t const operator--(int) noexcept { auto result = *this; m_it--; return result; } - primitive_iterator_t& operator+=(difference_type n) + primitive_iterator_t& operator+=(difference_type n) noexcept { m_it += n; return *this; } - primitive_iterator_t& operator-=(difference_type n) + primitive_iterator_t& operator-=(difference_type n) noexcept { m_it -= n; return *this; } - - private: - static constexpr difference_type begin_value = 0; - static constexpr difference_type end_value = begin_value + 1; - - /// iterator as signed integer type - difference_type m_it = (std::numeric_limits::min)(); }; +} +} +// #include + + +// #include + + +namespace nlohmann +{ +namespace detail +{ /*! @brief an iterator value @@ -3581,7 +3861,34 @@ template struct internal_iterator /// generic iterator for all other types primitive_iterator_t primitive_iterator {}; }; +} +} +// #include + + +#include // not +#include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include // conditional, is_const, remove_const + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// forward declare, to be able to friend it later on template class iteration_proxy; /*! @@ -4174,10 +4481,25 @@ class iter_impl /// associated JSON instance pointer m_object = nullptr; /// the actual iterator of the associated instance - internal_iterator::type> m_it = {}; + internal_iterator::type> m_it; }; +} +} -/// proxy class for the iterator_wrapper functions +// #include + + +#include // size_t +#include // string, to_string + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/// proxy class for the items() function template class iteration_proxy { private: @@ -4247,7 +4569,7 @@ template class iteration_proxy public: /// construct iteration proxy from a container - explicit iteration_proxy(typename IteratorType::reference cont) + explicit iteration_proxy(typename IteratorType::reference cont) noexcept : container(cont) {} /// return iterator begin (needed for range-based for) @@ -4262,6 +4584,23 @@ template class iteration_proxy return iteration_proxy_internal(container.end()); } }; +} +} + +// #include + + +#include // ptrdiff_t +#include // reverse_iterator +#include // declval + +namespace nlohmann +{ +namespace detail +{ +////////////////////// +// reverse_iterator // +////////////////////// /*! @brief a template for a reverse iterator class @@ -4366,11 +4705,25 @@ class json_reverse_iterator : public std::reverse_iterator return it.operator * (); } }; +} +} -///////////////////// -// output adapters // -///////////////////// +// #include + +#include // copy +#include // size_t +#include // streamsize +#include // back_inserter +#include // shared_ptr, make_shared +#include // basic_ostream +#include // basic_string +#include // vector + +namespace nlohmann +{ +namespace detail +{ /// abstract output adapter interface template struct output_adapter_protocol { @@ -4426,11 +4779,11 @@ class output_stream_adapter : public output_adapter_protocol }; /// output adapter for basic_string -template +template> class output_string_adapter : public output_adapter_protocol { public: - explicit output_string_adapter(std::basic_string& s) : str(s) {} + explicit output_string_adapter(StringType& s) : str(s) {} void write_character(CharType c) override { @@ -4443,10 +4796,10 @@ class output_string_adapter : public output_adapter_protocol } private: - std::basic_string& str; + StringType& str; }; -template +template> class output_adapter { public: @@ -4456,8 +4809,8 @@ class output_adapter output_adapter(std::basic_ostream& s) : oa(std::make_shared>(s)) {} - output_adapter(std::basic_string& s) - : oa(std::make_shared>(s)) {} + output_adapter(StringType& s) + : oa(std::make_shared>(s)) {} operator output_adapter_t() { @@ -4467,10 +4820,43 @@ class output_adapter private: output_adapter_t oa = nullptr; }; +} +} -////////////////////////////// -// binary reader and writer // -////////////////////////////// +// #include + + +#include // generate_n +#include // array +#include // assert +#include // ldexp +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // setw, setfill +#include // hex +#include // back_inserter +#include // numeric_limits +#include // stringstream +#include // char_traits, string +#include // make_pair, move + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary reader // +/////////////////// /*! @brief deserialization of CBOR and MessagePack values @@ -4480,6 +4866,7 @@ class binary_reader { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using string_t = typename BasicJsonType::string_t; public: /*! @@ -4508,7 +4895,7 @@ class binary_reader if (strict) { get(); - check_eof(true); + expect_eof(); } return res; } @@ -4529,7 +4916,28 @@ class binary_reader if (strict) { get(); - check_eof(true); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from UBJSON input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from UBJSON input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_ubjson(const bool strict) + { + const auto res = parse_ubjson_internal(); + if (strict) + { + get_ignore_noop(); + expect_eof(); } return res; } @@ -4628,7 +5036,6 @@ class binary_reader case 0x38: // Negative integer (one-byte uint8_t follows) { - // must be uint8_t ! return static_cast(-1) - get_number(); } @@ -4819,9 +5226,9 @@ class binary_reader case 0xF9: // Half-Precision Float (two-byte IEEE 754) { const int byte1 = get(); - check_eof(); + unexpect_eof(); const int byte2 = get(); - check_eof(); + unexpect_eof(); // code from RFC 7049, Appendix D, Figure 3: // As half-precision floating-point numbers were only added @@ -5195,6 +5602,16 @@ class binary_reader } } + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + /*! @brief get next character from the input @@ -5210,6 +5627,20 @@ class binary_reader return (current = ia->get_character()); } + /*! + @return character read from the input after ignoring all 'N' entries + */ + int get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + /* @brief read a number from the input @@ -5230,7 +5661,7 @@ class binary_reader for (std::size_t i = 0; i < sizeof(NumberType); ++i) { get(); - check_eof(); + unexpect_eof(); // reverse byte order prior to conversion if necessary if (is_little_endian) @@ -5255,7 +5686,7 @@ class binary_reader @param[in] len number of bytes to read @note We can not reserve @a len bytes for the result, because @a len - may be too large. Usually, @ref check_eof() detects the end of + may be too large. Usually, @ref unexpect_eof() detects the end of the input before we run out of string memory. @return string created by reading @a len bytes @@ -5263,13 +5694,13 @@ class binary_reader @throw parse_error.110 if input has less than @a len bytes */ template - std::string get_string(const NumberType len) + string_t get_string(const NumberType len) { - std::string result; + string_t result; std::generate_n(std::back_inserter(result), len, [this]() { get(); - check_eof(); + unexpect_eof(); return static_cast(current); }); return result; @@ -5287,9 +5718,9 @@ class binary_reader @throw parse_error.110 if input ended @throw parse_error.113 if an unexpected byte is read */ - std::string get_cbor_string() + string_t get_cbor_string() { - check_eof(); + unexpect_eof(); switch (current) { @@ -5344,11 +5775,10 @@ class binary_reader case 0x7F: // UTF-8 string (indefinite length) { - std::string result; + string_t result; while (get() != 0xFF) { - check_eof(); - result.push_back(static_cast(current)); + result.append(get_cbor_string()); } return result; } @@ -5400,9 +5830,9 @@ class binary_reader @throw parse_error.110 if input ended @throw parse_error.113 if an unexpected byte is read */ - std::string get_msgpack_string() + string_t get_msgpack_string() { - check_eof(); + unexpect_eof(); switch (current) { @@ -5496,25 +5926,265 @@ class binary_reader } /*! - @brief check if input ended + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return string + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read */ - void check_eof(const bool expect_eof = false) const + string_t get_ubjson_string(const bool get_char = true) { - if (expect_eof) + if (get_char) { - if (JSON_UNLIKELY(current != std::char_traits::eof())) + get(); // TODO: may we ignore N here? + } + + unexpect_eof(); + + switch (current) + { + case 'U': + return get_string(get_number()); + case 'i': + return get_string(get_number()); + case 'I': + return get_string(get_number()); + case 'l': + return get_string(get_number()); + case 'L': + return get_string(get_number()); + default: + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a UBJSON string; last byte: 0x" + ss.str())); + } + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @return pair of the size and the type + */ + std::pair get_ubjson_size_type() + { + std::size_t sz = string_t::npos; + int tc = 0; + + get_ignore_noop(); + + if (current == '$') + { + tc = get(); // must not ignore 'N', because 'N' maybe the type + unexpect_eof(); + + get_ignore_noop(); + if (current != '#') { - JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "expected '#' after UBJSON type information; last byte: 0x" + ss.str())); + } + sz = parse_ubjson_internal(); + } + else if (current == '#') + { + sz = parse_ubjson_internal(); + } + + return std::make_pair(sz, tc); + } + + BasicJsonType get_ubjson_value(const int prefix) + { + switch (prefix) + { + case std::char_traits::eof(): // EOF + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 'T': // true + return true; + case 'F': // false + return false; + + case 'Z': // null + return nullptr; + + case 'U': + return get_number(); + case 'i': + return get_number(); + case 'I': + return get_number(); + case 'l': + return get_number(); + case 'L': + return get_number(); + case 'd': + return get_number(); + case 'D': + return get_number(); + + case 'C': // char + { + get(); + unexpect_eof(); + if (JSON_UNLIKELY(current > 127)) + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + ss.str())); + } + return string_t(1, static_cast(current)); + } + + case 'S': // string + return get_ubjson_string(); + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading UBJSON; last byte: 0x" + ss.str())); + } + } + + BasicJsonType get_ubjson_array() + { + BasicJsonType result = value_t::array; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive array size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this, size_and_type]() + { + return get_ubjson_value(size_and_type.second); + }); + } + } + else + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this]() + { + return parse_ubjson_internal(); + }); } } else { - if (JSON_UNLIKELY(current == std::char_traits::eof())) + while (current != ']') { - JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + result.push_back(parse_ubjson_internal(false)); + get_ignore_noop(); } } + + return result; + } + + BasicJsonType get_ubjson_object() + { + BasicJsonType result = value_t::object; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive object size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this, size_and_type]() + { + auto key = get_ubjson_string(); + auto val = get_ubjson_value(size_and_type.second); + return std::make_pair(std::move(key), std::move(val)); + }); + } + else + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this]() + { + auto key = get_ubjson_string(); + auto val = parse_ubjson_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + } + } + else + { + while (current != '}') + { + auto key = get_ubjson_string(false); + result[std::move(key)] = parse_ubjson_internal(); + get_ignore_noop(); + } + } + + return result; + } + + /*! + @brief throw if end of input is not reached + @throw parse_error.110 if input not ended + */ + void expect_eof() const + { + if (JSON_UNLIKELY(current != std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); + } + } + + /*! + @briefthrow if end of input is reached + @throw parse_error.110 if input ended + */ + void unexpect_eof() const + { + if (JSON_UNLIKELY(current == std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + } } private: @@ -5530,6 +6200,30 @@ class binary_reader /// whether we can assume little endianess const bool is_little_endian = little_endianess(); }; +} +} + +// #include + + +#include // reverse +#include // array +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // numeric_limits + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary writer // +/////////////////// /*! @brief serialization to CBOR and MessagePack values @@ -5678,23 +6372,23 @@ class binary_writer { write_number(static_cast(0x60 + N)); } - else if (N <= 0xFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x78)); write_number(static_cast(N)); } - else if (N <= 0xFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x79)); write_number(static_cast(N)); } - else if (N <= 0xFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x7A)); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xFFFFFFFFFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x7B)); write_number(static_cast(N)); @@ -5716,23 +6410,23 @@ class binary_writer { write_number(static_cast(0x80 + N)); } - else if (N <= 0xFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x98)); write_number(static_cast(N)); } - else if (N <= 0xFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x99)); write_number(static_cast(N)); } - else if (N <= 0xFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x9A)); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xFFFFFFFFFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x9B)); write_number(static_cast(N)); @@ -5755,23 +6449,23 @@ class binary_writer { write_number(static_cast(0xA0 + N)); } - else if (N <= 0xFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0xB8)); write_number(static_cast(N)); } - else if (N <= 0xFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0xB9)); write_number(static_cast(N)); } - else if (N <= 0xFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0xBA)); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xFFFFFFFFFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0xBB)); write_number(static_cast(N)); @@ -5939,19 +6633,19 @@ class binary_writer // fixstr write_number(static_cast(0xA0 | N)); } - else if (N <= 255) + else if (N <= (std::numeric_limits::max)()) { // str 8 oa->write_character(static_cast(0xD9)); write_number(static_cast(N)); } - else if (N <= 65535) + else if (N <= (std::numeric_limits::max)()) { // str 16 oa->write_character(static_cast(0xDA)); write_number(static_cast(N)); } - else if (N <= 4294967295) + else if (N <= (std::numeric_limits::max)()) { // str 32 oa->write_character(static_cast(0xDB)); @@ -5974,13 +6668,13 @@ class binary_writer // fixarray write_number(static_cast(0x90 | N)); } - else if (N <= 0xFFFF) + else if (N <= (std::numeric_limits::max)()) { // array 16 oa->write_character(static_cast(0xDC)); write_number(static_cast(N)); } - else if (N <= 0xFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { // array 32 oa->write_character(static_cast(0xDD)); @@ -6004,13 +6698,13 @@ class binary_writer // fixmap write_number(static_cast(0x80 | (N & 0xF))); } - else if (N <= 65535) + else if (N <= (std::numeric_limits::max)()) { // map 16 oa->write_character(static_cast(0xDE)); write_number(static_cast(N)); } - else if (N <= 4294967295) + else if (N <= (std::numeric_limits::max)()) { // map 32 oa->write_character(static_cast(0xDF)); @@ -6031,6 +6725,165 @@ class binary_writer } } + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + */ + void write_ubjson(const BasicJsonType& j, const bool use_count, + const bool use_type, const bool add_prefix = true) + { + switch (j.type()) + { + case value_t::null: + { + if (add_prefix) + { + oa->write_character(static_cast('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + oa->write_character(j.m_value.boolean + ? static_cast('T') + : static_cast('F')); + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + break; + } + + case value_t::string: + { + if (add_prefix) + { + oa->write_character(static_cast('S')); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(static_cast('[')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.array->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.array->size(), true); + } + + for (const auto& el : *j.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast(']')); + } + + break; + } + + case value_t::object: + { + if (add_prefix) + { + oa->write_character(static_cast('{')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.object->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.object->size(), true); + } + + for (const auto& el : *j.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size(), true); + oa->write_characters( + reinterpret_cast(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast('}')); + } + + break; + } + + default: + break; + } + } + private: /* @brief write a number to output input @@ -6039,10 +6892,11 @@ class binary_writer @tparam NumberType the type of the number @note This function needs to respect the system's endianess, because bytes - in CBOR and MessagePack are stored in network order (big endian) and - therefore need reordering on little endian systems. + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. */ - template void write_number(NumberType n) + template + void write_number(const NumberType n) { // step 1: write number to array of length NumberType std::array vec; @@ -6058,6 +6912,210 @@ class binary_writer oa->write_characters(vec.data(), sizeof(NumberType)); } + // UBJSON: write number (floating point) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (add_prefix) + { + oa->write_character(static_cast('D')); // float64 + } + write_number(n); + } + + // UBJSON: write number (unsigned integer) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + } + + // UBJSON: write number (signed integer) + template::value and + not std::is_floating_point::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if (static_cast((std::numeric_limits::min)()) <= n and n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + // LCOV_EXCL_START + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + // LCOV_EXCL_STOP + } + + /*! + @brief determine the type prefix of container values + + @note This function does not need to be 100% accurate when it comes to + integer limits. In case a number exceeds the limits of int64_t, + this will be detected by a later call to function + write_number_with_ubjson_prefix. Therefore, we return 'L' for any + value that does not fit the previous limits. + */ + char ubjson_prefix(const BasicJsonType& j) const noexcept + { + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_float: + return 'D'; + + case value_t::string: + return 'S'; + + case value_t::array: + return '['; + + case value_t::object: + return '{'; + + default: // discarded values + return 'N'; + } + } + private: /// whether we can assume little endianess const bool is_little_endian = binary_reader::little_endianess(); @@ -6065,7 +7123,1140 @@ class binary_writer /// the output output_adapter_t oa = nullptr; }; +} +} +// #include + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // assert +#include // and, or +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // setfill +#include // next +#include // numeric_limits +#include // string +#include // stringstream +#include // is_same + +// #include + +// #include + + +#include // assert +#include // or, and, not +#include // signbit, isfinite +#include // intN_t, uintN_t +#include // memcpy, memmove + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + uint64_t f; + int e; + + constexpr diyfp() noexcept : f(0), e(0) {} + constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + assert(x.e == y.e); + assert(x.f >= y.f); + + return diyfp(x.f - y.f, x.e); + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const uint64_t u_lo = x.f & 0xFFFFFFFF; + const uint64_t u_hi = x.f >> 32; + const uint64_t v_lo = y.f & 0xFFFFFFFF; + const uint64_t v_hi = y.f >> 32; + + const uint64_t p0 = u_lo * v_lo; + const uint64_t p1 = u_lo * v_hi; + const uint64_t p2 = u_hi * v_lo; + const uint64_t p3 = u_hi * v_hi; + + const uint64_t p0_hi = p0 >> 32; + const uint64_t p1_lo = p1 & 0xFFFFFFFF; + const uint64_t p1_hi = p1 >> 32; + const uint64_t p2_lo = p2 & 0xFFFFFFFF; + const uint64_t p2_hi = p2 >> 32; + + uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += uint64_t{1} << (64 - 32 - 1); // round, ties up + + const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32); + + return diyfp(h, x.e + y.e + 64); + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + assert(x.f != 0); + + while ((x.f >> 63) == 0) + { + x.f <<= 1; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + assert(delta >= 0); + assert(((x.f << delta) >> delta) == x.f); + + return diyfp(x.f << delta, target_exponent); + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + assert(std::isfinite(value)); + assert(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type; + + const uint64_t bits = reinterpret_bits(value); + const uint64_t E = bits >> (kPrecision - 1); + const uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = (E == 0); + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = (F == 0 and E > 1); + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersSize = 79; + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr cached_power kCachedPowers[] = + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + assert(e >= -1500); + assert(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + (f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + assert(index >= 0); + assert(index < kCachedPowersSize); + static_cast(kCachedPowersSize); // Fix warning. + + const cached_power cached = kCachedPowers[index]; + assert(kAlpha <= cached.e + e + 64); + assert(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const uint32_t n, uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + else if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + else if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + else if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + else if (n >= 100000) + { + pow10 = 100000; + return 6; + } + else if (n >= 10000) + { + pow10 = 10000; + return 5; + } + else if (n >= 1000) + { + pow10 = 1000; + return 4; + } + else if (n >= 100) + { + pow10 = 100; + return 3; + } + else if (n >= 10) + { + pow10 = 10; + return 2; + } + else + { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta, + uint64_t rest, uint64_t ten_k) +{ + assert(len >= 1); + assert(dist <= delta); + assert(rest <= delta); + assert(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + and delta - rest >= ten_k + and (rest + ten_k < dist or dist - rest > rest + ten_k - dist)) + { + assert(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + assert(M_plus.e >= kAlpha); + assert(M_plus.e <= kGamma); + + uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + + uint32_t p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + assert(p1 > 0); + + uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const uint64_t rest = (uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const uint64_t ten_n = uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + assert(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + assert(p2 <= UINT64_MAX / 10); + p2 *= 10; + const uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + assert(m_plus.e == m_minus.e); + assert(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + assert(std::isfinite(value)); + assert(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (http://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +inline char* append_exponent(char* buf, int e) +{ + assert(e > -1000); + assert(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + uint32_t k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + assert(min_exp < 0); + assert(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n and n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n - k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (n + 2); + } + + if (0 < n and n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + assert(k > n); + + std::memmove(buf + (n + 1), buf + n, static_cast(k - n)); + buf[n] = '.'; + return buf + (k + 1); + } + + if (min_exp < n and n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + -n), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2 + (-n) + k); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k - 1)); + buf[1] = '.'; + buf += 1 + k; + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +char* to_chars(char* first, char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + assert(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + + assert(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + assert(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + assert(last - first >= kMaxExp + 2); + assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + assert(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ /////////////////// // serialization // /////////////////// @@ -6077,6 +8268,9 @@ class serializer using number_float_t = typename BasicJsonType::number_float_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + public: /*! @param[in] s output stream to serialize to @@ -6086,7 +8280,8 @@ class serializer : o(std::move(s)), loc(std::localeconv()), thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), - indent_char(ichar), indent_string(512, indent_char) {} + indent_char(ichar), indent_string(512, indent_char) + {} // delete because of pointer members serializer(const serializer&) = delete; @@ -6302,171 +8497,6 @@ class serializer } private: - /*! - @brief returns the number of expected bytes following in UTF-8 string - - @param[in] u the first byte of a UTF-8 string - @return the number of expected bytes following - */ - static constexpr std::size_t bytes_following(const uint8_t u) - { - return ((u <= 127) ? 0 - : ((192 <= u and u <= 223) ? 1 - : ((224 <= u and u <= 239) ? 2 - : ((240 <= u and u <= 247) ? 3 : std::string::npos)))); - } - - /*! - @brief calculates the extra space to escape a JSON string - - @param[in] s the string to escape - @param[in] ensure_ascii whether to escape non-ASCII characters with - \uXXXX sequences - @return the number of characters required to escape string @a s - - @complexity Linear in the length of string @a s. - */ - static std::size_t extra_space(const string_t& s, - const bool ensure_ascii) noexcept - { - std::size_t res = 0; - - for (std::size_t i = 0; i < s.size(); ++i) - { - switch (s[i]) - { - // control characters that can be escaped with a backslash - case '"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - { - // from c (1 byte) to \x (2 bytes) - res += 1; - break; - } - - // control characters that need \uxxxx escaping - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x0B: - case 0x0E: - case 0x0F: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - case 0x18: - case 0x19: - case 0x1A: - case 0x1B: - case 0x1C: - case 0x1D: - case 0x1E: - case 0x1F: - { - // from c (1 byte) to \uxxxx (6 bytes) - res += 5; - break; - } - - default: - { - if (ensure_ascii and (s[i] & 0x80 or s[i] == 0x7F)) - { - const auto bytes = bytes_following(static_cast(s[i])); - // invalid characters will be detected by throw_if_invalid_utf8 - assert (bytes != std::string::npos); - - if (bytes == 3) - { - // codepoints that need 4 bytes (i.e., 3 additional - // bytes) in UTF-8 need a surrogate pair when \u - // escaping is used: from 4 bytes to \uxxxx\uxxxx - // (12 bytes) - res += (12 - bytes - 1); - } - else - { - // from x bytes to \uxxxx (6 bytes) - res += (6 - bytes - 1); - } - - // skip the additional bytes - i += bytes; - } - break; - } - } - } - - return res; - } - - static void escape_codepoint(int codepoint, string_t& result, std::size_t& pos) - { - // expecting a proper codepoint - assert(0x00 <= codepoint and codepoint <= 0x10FFFF); - - // the last written character was the backslash before the 'u' - assert(result[pos] == '\\'); - - // write the 'u' - result[++pos] = 'u'; - - // convert a number 0..15 to its hex representation (0..f) - static const std::array hexify = - { - { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - } - }; - - if (codepoint < 0x10000) - { - // codepoints U+0000..U+FFFF can be represented as \uxxxx. - result[++pos] = hexify[(codepoint >> 12) & 0x0F]; - result[++pos] = hexify[(codepoint >> 8) & 0x0F]; - result[++pos] = hexify[(codepoint >> 4) & 0x0F]; - result[++pos] = hexify[codepoint & 0x0F]; - } - else - { - // codepoints U+10000..U+10FFFF need a surrogate pair to be - // represented as \uxxxx\uxxxx. - // http://www.unicode.org/faq/utf_bom.html#utf16-4 - codepoint -= 0x10000; - const int high_surrogate = 0xD800 | ((codepoint >> 10) & 0x3FF); - const int low_surrogate = 0xDC00 | (codepoint & 0x3FF); - result[++pos] = hexify[(high_surrogate >> 12) & 0x0F]; - result[++pos] = hexify[(high_surrogate >> 8) & 0x0F]; - result[++pos] = hexify[(high_surrogate >> 4) & 0x0F]; - result[++pos] = hexify[high_surrogate & 0x0F]; - ++pos; // backslash is already in output - result[++pos] = 'u'; - result[++pos] = hexify[(low_surrogate >> 12) & 0x0F]; - result[++pos] = hexify[(low_surrogate >> 8) & 0x0F]; - result[++pos] = hexify[(low_surrogate >> 4) & 0x0F]; - result[++pos] = hexify[low_surrogate & 0x0F]; - } - - ++pos; - } - /*! @brief dump escaped string @@ -6481,145 +8511,146 @@ class serializer @complexity Linear in the length of string @a s. */ - void dump_escaped(const string_t& s, const bool ensure_ascii) const + void dump_escaped(const string_t& s, const bool ensure_ascii) { - throw_if_invalid_utf8(s); - - const auto space = extra_space(s, ensure_ascii); - if (space == 0) - { - o->write_characters(s.c_str(), s.size()); - return; - } - - // create a result string of necessary size - string_t result(s.size() + space, '\\'); - std::size_t pos = 0; + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer for (std::size_t i = 0; i < s.size(); ++i) { - switch (s[i]) + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) { - case '"': // quotation mark (0x22) + case UTF8_ACCEPT: // decode found a new code point { - result[pos + 1] = '"'; - pos += 2; - break; - } - - case '\\': // reverse solidus (0x5C) - { - // nothing to change - pos += 2; - break; - } - - case '\b': // backspace (0x08) - { - result[pos + 1] = 'b'; - pos += 2; - break; - } - - case '\f': // formfeed (0x0C) - { - result[pos + 1] = 'f'; - pos += 2; - break; - } - - case '\n': // newline (0x0A) - { - result[pos + 1] = 'n'; - pos += 2; - break; - } - - case '\r': // carriage return (0x0D) - { - result[pos + 1] = 'r'; - pos += 2; - break; - } - - case '\t': // horizontal tab (0x09) - { - result[pos + 1] = 't'; - pos += 2; - break; - } - - default: - { - // escape control characters (0x00..0x1F) or, if - // ensure_ascii parameter is used, non-ASCII characters - if ((0x00 <= s[i] and s[i] <= 0x1F) or - (ensure_ascii and (s[i] & 0x80 or s[i] == 0x7F))) + switch (codepoint) { - const auto bytes = bytes_following(static_cast(s[i])); - // invalid characters will be detected by throw_if_invalid_utf8 - assert (bytes != std::string::npos); - - // check that the additional bytes are present - assert(i + bytes < s.size()); - - // to use \uxxxx escaping, we first need to calculate - // the codepoint from the UTF-8 bytes - int codepoint = 0; - - // bytes is unsigned type: - assert(bytes <= 3); - switch (bytes) + case 0x08: // backspace { - case 0: - { - codepoint = s[i] & 0xFF; - break; - } - - case 1: - { - codepoint = ((s[i] & 0x3F) << 6) - + (s[i + 1] & 0x7F); - break; - } - - case 2: - { - codepoint = ((s[i] & 0x1F) << 12) - + ((s[i + 1] & 0x7F) << 6) - + (s[i + 2] & 0x7F); - break; - } - - case 3: - { - codepoint = ((s[i] & 0xF) << 18) - + ((s[i + 1] & 0x7F) << 12) - + ((s[i + 2] & 0x7F) << 6) - + (s[i + 3] & 0x7F); - break; - } - - default: - break; // LCOV_EXCL_LINE + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; } - escape_codepoint(codepoint, result, pos); - i += bytes; + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0 + (codepoint >> 10)), + static_cast(0xDC00 + (codepoint & 0x3FF))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } } - else + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) { - // all other characters are added as-is - result[pos++] = s[i]; + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str())); + } + + default: // decode found yet incomplete multi-byte code point + { + if (not ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; } break; } } } - assert(pos == result.size()); - o->write_characters(result.c_str(), result.size()); + if (JSON_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str())); + } } /*! @@ -6679,14 +8710,36 @@ class serializer void dump_float(number_float_t x) { // NaN / inf - if (not std::isfinite(x) or std::isnan(x)) + if (not std::isfinite(x)) { o->write_characters("null", 4); return; } - // get number of digits for a text -> float -> text round-trip - static constexpr auto d = std::numeric_limits::digits10; + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or + (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; // the actual conversion std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); @@ -6744,15 +8797,16 @@ class serializer followed. @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) @param[in] byte next byte to decode + @return new state - @note The function has been edited: a std::array is used and the code - point is not calculated. + @note The function has been edited: a std::array is used. @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */ - static void decode(uint8_t& state, const uint8_t byte) + static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept { static const std::array utf8d = { @@ -6775,42 +8829,13 @@ class serializer }; const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast(0xff >> type) & (byte); + state = utf8d[256u + state * 16u + type]; - } - - /*! - @brief throw an exception if a string is not UTF-8 encoded - - @param[in] str UTF-8 string to check - @throw type_error.316 if passed string is not UTF-8 encoded - - @since version 3.0.0 - */ - static void throw_if_invalid_utf8(const std::string& str) - { - // start with state 0 (= accept) - uint8_t state = 0; - - for (size_t i = 0; i < str.size(); ++i) - { - const auto byte = static_cast(str[i]); - decode(state, byte); - if (state == 1) - { - // state 1 means reject - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(byte); - JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str())); - } - } - - if (state != 0) - { - // we finish reading, but do not accept: string was incomplete - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(static_cast(str.back())); - JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str())); - } + return state; } private: @@ -6827,13 +8852,27 @@ class serializer /// the locale's decimal point character const char decimal_point = '\0'; + /// string buffer + std::array string_buffer{{}}; + /// the indentation character const char indent_char; - /// the indentation string string_t indent_string; }; +} +} +// #include + + +#include +#include + +namespace nlohmann +{ +namespace detail +{ template class json_ref { @@ -6886,74 +8925,30 @@ class json_ref value_type* value_ref = nullptr; const bool is_rvalue; }; - -} // namespace detail - -/// namespace to hold default `to_json` / `from_json` functions -namespace -{ -constexpr const auto& to_json = detail::static_const::value; -constexpr const auto& from_json = detail::static_const::value; +} } +// #include -/*! -@brief default JSONSerializer template argument -This serializer ignores the template arguments and uses ADL -([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) -for serialization. -*/ -template -struct adl_serializer +#include // assert +#include // accumulate +#include // string +#include // vector + +// #include + +// #include + +// #include + + +namespace nlohmann { - /*! - @brief convert a JSON value to any value type - - This function is usually called by the `get()` function of the - @ref basic_json class (either explicit or via conversion operators). - - @param[in] j JSON value to read from - @param[in,out] val value to write to - */ - template - static void from_json(BasicJsonType&& j, ValueType& val) noexcept( - noexcept(::nlohmann::from_json(std::forward(j), val))) - { - ::nlohmann::from_json(std::forward(j), val); - } - - /*! - @brief convert any value type to a JSON value - - This function is usually called by the constructors of the @ref basic_json - class. - - @param[in,out] j JSON value to write to - @param[in] val value to read from - */ - template - static void to_json(BasicJsonType& j, ValueType&& val) noexcept( - noexcept(::nlohmann::to_json(j, std::forward(val)))) - { - ::nlohmann::to_json(j, std::forward(val)); - } -}; - -/*! -@brief JSON Pointer - -A JSON pointer defines a string syntax for identifying a specific value -within a JSON document. It can be used with functions `at` and -`operator[]`. Furthermore, JSON pointers are the base for JSON patches. - -@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) - -@since version 2.0.0 -*/ +template class json_pointer { - /// allow basic_json to access private members + // allow basic_json to access private members NLOHMANN_BASIC_JSON_TPL_DECLARATION friend class basic_json; @@ -6967,19 +8962,21 @@ class json_pointer @param[in] s string representing the JSON pointer; if omitted, the empty string is assumed which references the whole JSON value - @throw parse_error.107 if the given JSON pointer @a s is nonempty and - does not begin with a slash (`/`); see example below + @throw parse_error.107 if the given JSON pointer @a s is nonempty and does + not begin with a slash (`/`); see example below - @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s - is not followed by `0` (representing `~`) or `1` (representing `/`); - see example below + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is + not followed by `0` (representing `~`) or `1` (representing `/`); see + example below - @liveexample{The example shows the construction several valid JSON - pointers as well as the exceptional behavior.,json_pointer} + @liveexample{The example shows the construction several valid JSON pointers + as well as the exceptional behavior.,json_pointer} @since version 2.0.0 */ - explicit json_pointer(const std::string& s = "") : reference_tokens(split(s)) {} + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} /*! @brief return a string representation of the JSON pointer @@ -7021,7 +9018,7 @@ class json_pointer */ static int array_index(const std::string& s) { - size_t processed_chars = 0; + std::size_t processed_chars = 0; const int res = std::stoi(s, &processed_chars); // check if the string was completely read @@ -7076,8 +9073,66 @@ class json_pointer @throw parse_error.109 if array index is not a number @throw type_error.313 if value cannot be unflattened */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - NLOHMANN_BASIC_JSON_TPL& get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const; + BasicJsonType& get_and_create(BasicJsonType& j) const + { + using size_type = typename BasicJsonType::size_type; + auto result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + JSON_TRY + { + result = &result->operator[](static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } + } + + return *result; + } /*! @brief return a reference to the pointed to value @@ -7098,8 +9153,75 @@ class json_pointer @throw parse_error.109 if an array index was not a number @throw out_of_range.404 if the JSON pointer can not be resolved */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - NLOHMANN_BASIC_JSON_TPL& get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const; + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->m_type == detail::value_t::null) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const char x) + { + return (x >= '0' and x <= '9'); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums or reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } /*! @throw parse_error.106 if an array index begins with '0' @@ -7107,8 +9229,57 @@ class json_pointer @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - NLOHMANN_BASIC_JSON_TPL& get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const; + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } /*! @brief return a const reference to the pointed to value @@ -7123,8 +9294,58 @@ class json_pointer @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - const NLOHMANN_BASIC_JSON_TPL& get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const; + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // use unchecked array access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } /*! @throw parse_error.106 if an array index begins with '0' @@ -7132,8 +9353,57 @@ class json_pointer @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - const NLOHMANN_BASIC_JSON_TPL& get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const; + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } /*! @brief split the string input to reference tokens @@ -7252,10 +9522,57 @@ class json_pointer @note Empty objects or arrays are flattened to `null`. */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION static void flatten(const std::string& reference_string, - const NLOHMANN_BASIC_JSON_TPL& value, - NLOHMANN_BASIC_JSON_TPL& result); + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.m_type) + { + case detail::value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (std::size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } /*! @param[in] value flattened JSON @@ -7267,19 +9584,108 @@ class json_pointer @throw type_error.315 if object values are not primitive @throw type_error.313 if value cannot be unflattened */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - static NLOHMANN_BASIC_JSON_TPL - unflatten(const NLOHMANN_BASIC_JSON_TPL& value); + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_UNLIKELY(not value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (JSON_UNLIKELY(not element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } friend bool operator==(json_pointer const& lhs, - json_pointer const& rhs) noexcept; + json_pointer const& rhs) noexcept + { + return (lhs.reference_tokens == rhs.reference_tokens); + } friend bool operator!=(json_pointer const& lhs, - json_pointer const& rhs) noexcept; + json_pointer const& rhs) noexcept + { + return not (lhs == rhs); + } /// the reference tokens std::vector reference_tokens; }; +} + +// #include + + +#include + +// #include + +// #include + + +namespace nlohmann +{ +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static void to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + { + ::nlohmann::to_json(j, std::forward(val)); + } +}; +} + + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ /*! @brief a class to store JSON values @@ -7367,7 +9773,7 @@ class basic_json { private: template friend struct detail::external_constructor; - friend ::nlohmann::json_pointer; + friend ::nlohmann::json_pointer; friend ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; template @@ -7404,7 +9810,7 @@ class basic_json public: using value_t = detail::value_t; /// @copydoc nlohmann::json_pointer - using json_pointer = ::nlohmann::json_pointer; + using json_pointer = ::nlohmann::json_pointer; template using json_serializer = JSONSerializer; /// helper type for initializer lists of basic_json values @@ -7517,10 +9923,13 @@ class basic_json result["copyright"] = "(C) 2013-2017 Niels Lohmann"; result["name"] = "JSON for Modern C++"; result["url"] = "https://github.com/nlohmann/json"; - result["version"] = - { - {"string", "3.0.1"}, {"major", 3}, {"minor", 0}, {"patch", 1} - }; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; #ifdef _WIN32 result["platform"] = "win32"; @@ -7622,10 +10031,10 @@ class basic_json - When all names are unique, objects will be interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings. - - When the names within an object are not unique, later stored name/value - pairs overwrite previously stored name/value pairs, leaving the used - names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will - be treated as equal and both stored as `{"key": 1}`. + - When the names within an object are not unique, it is unspecified which + one of the values for a given key will be chosen. For instance, + `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or + `{"key": 2}`. - Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see @ref dump) in this order. For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored @@ -8139,7 +10548,7 @@ class basic_json object = nullptr; // silence warning, see #821 if (JSON_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.0.1")); // LCOV_EXCL_LINE + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.2")); // LCOV_EXCL_LINE } break; } @@ -8182,7 +10591,7 @@ class basic_json array = create(std::move(value)); } - void destroy(value_t t) + void destroy(value_t t) noexcept { switch (t) { @@ -8227,7 +10636,7 @@ class basic_json value is changed, because the invariant expresses a relationship between @a m_type and @a m_value. */ - void assert_invariant() const + void assert_invariant() const noexcept { assert(m_type != value_t::object or m_value.object != nullptr); assert(m_type != value_t::array or m_value.array != nullptr); @@ -8409,6 +10818,7 @@ class basic_json - @a CompatibleType is not derived from `std::istream`, - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move constructors), + - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments) - @a CompatibleType is not a @ref basic_json nested type (e.g., @ref json_pointer, @ref iterator, etc ...) - @ref @ref json_serializer has a @@ -8432,20 +10842,90 @@ class basic_json @since version 2.1.0 */ - template, - detail::enable_if_t::value and - not std::is_same::value and - not detail::is_basic_json_nested_type< - basic_json_t, U>::value and - detail::has_to_json::value, - int> = 0> - basic_json(CompatibleType && val) noexcept(noexcept(JSONSerializer::to_json( - std::declval(), std::forward(val)))) + template , + detail::enable_if_t< + detail::is_compatible_type::value, int> = 0> + basic_json(CompatibleType && val) noexcept(noexcept( + JSONSerializer::to_json(std::declval(), + std::forward(val)))) { JSONSerializer::to_json(*this, std::forward(val)); assert_invariant(); } + /*! + @brief create a JSON value from an existing one + + This is a constructor for existing @ref basic_json types. + It does not hijack copy/move constructors, since the parameter has different + template arguments than the current ones. + + The constructor tries to convert the internal @ref m_value of the parameter. + + @tparam BasicJsonType a type such that: + - @a BasicJsonType is a @ref basic_json type. + - @a BasicJsonType has different template arguments than @ref basic_json_t. + + @param[in] val the @ref basic_json value to be converted. + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @since version 3.1.2 + */ + template ::value and not std::is_same::value, int> = 0> + basic_json(const BasicJsonType& val) + { + using other_boolean_t = typename BasicJsonType::boolean_t; + using other_number_float_t = typename BasicJsonType::number_float_t; + using other_number_integer_t = typename BasicJsonType::number_integer_t; + using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using other_string_t = typename BasicJsonType::string_t; + using other_object_t = typename BasicJsonType::object_t; + using other_array_t = typename BasicJsonType::array_t; + + switch (val.type()) + { + case value_t::boolean: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_float: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_integer: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_unsigned: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::string: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::object: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::array: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_type = value_t::discarded; + break; + } + assert_invariant(); + } + /*! @brief create a container (array or object) from an initializer list @@ -9021,7 +11501,7 @@ class basic_json @since version 1.0.0 */ - ~basic_json() + ~basic_json() noexcept { assert_invariant(); m_value.destroy(m_type); @@ -9078,7 +11558,7 @@ class basic_json const bool ensure_ascii = false) const { string_t result; - serializer s(detail::output_adapter(result), indent_char); + serializer s(detail::output_adapter(result), indent_char); if (indent >= 0) { @@ -9618,6 +12098,29 @@ class basic_json return *this; } + /*! + @brief get special-case overload + + This overloads converts the current @ref basic_json in a different + @ref basic_json type + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this, converted into @tparam BasicJsonType + + @complexity Depending on the implementation of the called `from_json()` + method. + + @since version 3.1.2 + */ + template::value and + detail::is_basic_json::value, int> = 0> + BasicJsonType get() const + { + return *this; + } + /*! @brief get a value (explicit) @@ -9659,7 +12162,7 @@ class basic_json */ template, detail::enable_if_t < - not std::is_same::value and + not detail::is_basic_json::value and detail::has_from_json::value and not detail::has_non_default_from_json::value, int> = 0> @@ -9925,7 +12428,8 @@ class basic_json template < typename ValueType, typename std::enable_if < not std::is_pointer::value and not std::is_same>::value and - not std::is_same::value + not std::is_same::value and + not detail::is_basic_json::value #ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 and not std::is_same>::value #endif @@ -11361,18 +13865,89 @@ class basic_json @note The name of this function is not yet final and may change in the future. + + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use @ref items() instead; + that is, replace `json::iterator_wrapper(j)` with `j.items()`. */ - static iteration_proxy iterator_wrapper(reference ref) + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(reference ref) noexcept { - return iteration_proxy(ref); + return ref.items(); } /*! @copydoc iterator_wrapper(reference) */ - static iteration_proxy iterator_wrapper(const_reference ref) + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(const_reference ref) noexcept { - return iteration_proxy(ref); + return ref.items(); + } + + /*! + @brief helper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without `items()` function: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without `items()` function: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with `items()` function: + + @code{cpp} + for (auto it : j_object.items()) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). For primitive types (e.g., numbers), + `key()` returns an empty string. + + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the function is used.,items} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 3.x.x. + */ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); + } + + /*! + @copydoc items() + */ + iteration_proxy items() const noexcept + { + return iteration_proxy(*this); } /// @} @@ -12312,7 +14887,7 @@ class basic_json // passed iterators must belong to objects if (JSON_UNLIKELY(not first.m_object->is_object() - or not first.m_object->is_object())) + or not last.m_object->is_object())) { JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); } @@ -12943,8 +15518,8 @@ class basic_json /*! @brief serialize to stream - @deprecated This stream operator is deprecated and will be removed in a - future version of the library. Please use + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use @ref operator<<(std::ostream&, const basic_json&) instead; that is, replace calls like `j >> o;` with `o << j;`. @since version 1.0.0; deprecated since version 3.0.0 @@ -13129,8 +15704,8 @@ class basic_json /*! @brief deserialize from stream - @deprecated This stream operator is deprecated and will be removed in a - future version of the library. Please use + @deprecated This stream operator is deprecated and will be removed in + version 4.0.0 of the library. Please use @ref operator>>(std::istream&, basic_json&) instead; that is, replace calls like `j << i;` with `i >> j;`. @since version 1.0.0; deprecated since version 3.0.0 @@ -13331,9 +15906,11 @@ class basic_json vector in CBOR format.,to_cbor} @sa http://cbor.io - @sa @ref from_cbor(const std::vector&, const size_t) for the + @sa @ref from_cbor(detail::input_adapter, const bool strict) for the analogous deserialization @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format @since version 2.0.9 */ @@ -13429,6 +16006,8 @@ class basic_json @sa @ref from_msgpack(const std::vector&, const size_t) for the analogous deserialization @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format @since version 2.0.9 */ @@ -13449,6 +16028,107 @@ class basic_json binary_writer(o).write_msgpack(j); } + /*! + @brief create a UBJSON serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the UBJSON + (Universal Binary JSON) serialization format. UBJSON aims to be more compact + than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + UBJSON types according to the UBJSON specification: + + JSON value type | value/range | UBJSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | `Z` + boolean | `true` | true | `T` + boolean | `false` | false | `F` + number_integer | -9223372036854775808..-2147483649 | int64 | `L` + number_integer | -2147483648..-32769 | int32 | `l` + number_integer | -32768..-129 | int16 | `I` + number_integer | -128..127 | int8 | `i` + number_integer | 128..255 | uint8 | `U` + number_integer | 256..32767 | int16 | `I` + number_integer | 32768..2147483647 | int32 | `l` + number_integer | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 0..127 | int8 | `i` + number_unsigned | 128..255 | uint8 | `U` + number_unsigned | 256..32767 | int16 | `I` + number_unsigned | 32768..2147483647 | int32 | `l` + number_unsigned | 2147483648..9223372036854775807 | int64 | `L` + number_float | *any value* | float64 | `D` + string | *with shortest length indicator* | string | `S` + array | *see notes on optimized format* | array | `[` + object | *see notes on optimized format* | map | `{` + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a UBJSON value. + + @note The following values can **not** be converted to a UBJSON value: + - strings with more than 9223372036854775807 bytes (theoretical) + - unsigned integer numbers above 9223372036854775807 + + @note The following markers are not used in the conversion: + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + + @note Any UBJSON output created @ref to_ubjson can be successfully parsed + by @ref from_ubjson. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The optimized formats for containers are supported: Parameter + @a use_size adds size information to the beginning of a container and + removes the closing marker. Parameter @a use_type further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The @a use_type + parameter must only be used together with @a use_size = true. Note + that @a use_size = true alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + + @param[in] j JSON value to serialize + @param[in] use_size whether to add size annotations to container types + @param[in] use_type whether to add type annotations to container types + (must be combined with @a use_size = true) + @return UBJSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in UBJSON format.,to_ubjson} + + @sa http://ubjson.org + @sa @ref from_ubjson(detail::input_adapter, const bool strict) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + + @since version 3.1.0 + */ + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + /*! @brief create a JSON value from an input in CBOR format @@ -13535,6 +16215,8 @@ class basic_json @sa @ref to_cbor(const basic_json&) for the analogous serialization @sa @ref from_msgpack(detail::input_adapter, const bool) for the related MessagePack format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format @since version 2.0.9; parameter @a start_index since 2.1.1; changed to consume input adapters, removed start_index parameter, and added @@ -13622,6 +16304,8 @@ class basic_json @sa @ref to_msgpack(const basic_json&) for the analogous serialization @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format @since version 2.0.9; parameter @a start_index since 2.1.1; changed to consume input adapters, removed start_index parameter, and added @@ -13643,6 +16327,72 @@ class basic_json return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_msgpack(strict); } + /*! + @brief create a JSON value from an input in UBJSON format + + Deserializes a given input @a i to a JSON value using the UBJSON (Universal + Binary JSON) serialization format. + + The library maps UBJSON types to JSON value types as follows: + + UBJSON type | JSON value type | marker + ----------- | --------------------------------------- | ------ + no-op | *no value, next value is read* | `N` + null | `null` | `Z` + false | `false` | `F` + true | `true` | `T` + float32 | number_float | `d` + float64 | number_float | `D` + uint8 | number_unsigned | `U` + int8 | number_integer | `i` + int16 | number_integer | `I` + int32 | number_integer | `l` + int64 | number_integer | `L` + string | string | `S` + char | string | `C` + array | array (optimized values are supported) | `[` + object | object (optimized values are supported) | `{` + + @note The mapping is **complete** in the sense that any UBJSON value can + be converted to a JSON value. + + @param[in] i an input in UBJSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if a parse error occurs + @throw parse_error.113 if a string could not be parsed successfully + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + UBJSON format to a JSON value.,from_ubjson} + + @sa http://ubjson.org + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_msgpack(detail::input_adapter, const bool) for the related + MessagePack format + + @since version 3.1.0 + */ + static basic_json from_ubjson(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_ubjson(strict); + } + + template::value, int> = 0> + static basic_json from_ubjson(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_ubjson(strict); + } + /// @} ////////////////////////// @@ -14066,7 +16816,7 @@ class basic_json // wrapper to get a value for an operation const auto get_value = [&val](const std::string & op, const std::string & member, - bool string_type) -> basic_json& + bool string_type) -> basic_json & { // find value auto it = val.m_value.object->find(member); @@ -14216,6 +16966,7 @@ class basic_json diff for two JSON values.,diff} @sa @ref patch -- apply a JSON patch + @sa @ref merge_patch -- apply a JSON Merge Patch @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) @@ -14347,418 +17098,86 @@ class basic_json } /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /*! + @brief applies a JSON Merge Patch + + The merge patch format is primarily intended for use with the HTTP PATCH + method as a means of describing a set of modifications to a target + resource's content. This function applies a merge patch to the current + JSON value. + + The function implements the following algorithm from Section 2 of + [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): + + ``` + define MergePatch(Target, Patch): + if Patch is an Object: + if Target is not an Object: + Target = {} // Ignore the contents and set it to an empty Object + for each Name/Value pair in Patch: + if Value is null: + if Name exists in Target: + remove the Name/Value pair from Target + else: + Target[Name] = MergePatch(Target[Name], Value) + return Target + else: + return Patch + ``` + + Thereby, `Target` is the current object; that is, the patch is applied to + the current value. + + @param[in] patch the patch to apply + + @complexity Linear in the lengths of @a patch. + + @liveexample{The following code shows how a JSON Merge Patch is applied to + a JSON document.,merge_patch} + + @sa @ref patch -- apply a JSON patch + @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) + + @since version 3.0.0 + */ + void merge_patch(const basic_json& patch) + { + if (patch.is_object()) + { + if (not is_object()) + { + *this = object(); + } + for (auto it = patch.begin(); it != patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = patch; + } + } + + /// @} }; - -///////////// -// presets // -///////////// - -/*! -@brief default JSON class - -This type is the default specialization of the @ref basic_json class which -uses the standard template types. - -@since version 1.0.0 -*/ -using json = basic_json<>; - -////////////////// -// json_pointer // -////////////////// - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - auto result = &j; - - // in case no reference tokens exist, return a reference to the JSON value - // j which will be overwritten by a primitive value - for (const auto& reference_token : reference_tokens) - { - switch (result->m_type) - { - case detail::value_t::null: - { - if (reference_token == "0") - { - // start a new array if reference token is 0 - result = &result->operator[](0); - } - else - { - // start a new object otherwise - result = &result->operator[](reference_token); - } - break; - } - - case detail::value_t::object: - { - // create an entry in the object - result = &result->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - // create an entry in the array - JSON_TRY - { - result = &result->operator[](static_cast(array_index(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - /* - The following code is only reached if there exists a reference - token _and_ the current value is primitive. In this case, we have - an error situation, because primitive values may only occur as - single value; that is, with an empty list of reference tokens. - */ - default: - JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); - } - } - - return *result; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - // convert null values to arrays or objects before continuing - if (ptr->m_type == detail::value_t::null) - { - // check if reference token is a number - const bool nums = - std::all_of(reference_token.begin(), reference_token.end(), - [](const char x) - { - return (x >= '0' and x <= '9'); - }); - - // change value to array for numbers or "-" or to object otherwise - *ptr = (nums or reference_token == "-") - ? detail::value_t::array - : detail::value_t::object; - } - - switch (ptr->m_type) - { - case detail::value_t::object: - { - // use unchecked object access - ptr = &ptr->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - if (reference_token == "-") - { - // explicitly treat "-" as index beyond the end - ptr = &ptr->operator[](ptr->m_value.array->size()); - } - else - { - // convert array index to number; unchecked access - JSON_TRY - { - ptr = &ptr->operator[]( - static_cast(array_index(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case detail::value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - // note: at performs range check - JSON_TRY - { - ptr = &ptr->at(static_cast(array_index(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -const NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case detail::value_t::object: - { - // use unchecked object access - ptr = &ptr->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_UNLIKELY(reference_token == "-")) - { - // "-" cannot be used for const access - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - // use unchecked array access - JSON_TRY - { - ptr = &ptr->operator[]( - static_cast(array_index(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -const NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case detail::value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - // note: at performs range check - JSON_TRY - { - ptr = &ptr->at(static_cast(array_index(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -void json_pointer::flatten(const std::string& reference_string, - const NLOHMANN_BASIC_JSON_TPL& value, - NLOHMANN_BASIC_JSON_TPL& result) -{ - switch (value.m_type) - { - case detail::value_t::array: - { - if (value.m_value.array->empty()) - { - // flatten empty array as null - result[reference_string] = nullptr; - } - else - { - // iterate array and use index as reference string - for (std::size_t i = 0; i < value.m_value.array->size(); ++i) - { - flatten(reference_string + "/" + std::to_string(i), - value.m_value.array->operator[](i), result); - } - } - break; - } - - case detail::value_t::object: - { - if (value.m_value.object->empty()) - { - // flatten empty object as null - result[reference_string] = nullptr; - } - else - { - // iterate object and use keys as reference string - for (const auto& element : *value.m_value.object) - { - flatten(reference_string + "/" + escape(element.first), element.second, result); - } - } - break; - } - - default: - { - // add primitive value with its reference string - result[reference_string] = value; - break; - } - } -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL -json_pointer::unflatten(const NLOHMANN_BASIC_JSON_TPL& value) -{ - if (JSON_UNLIKELY(not value.is_object())) - { - JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); - } - - NLOHMANN_BASIC_JSON_TPL result; - - // iterate the JSON object values - for (const auto& element : *value.m_value.object) - { - if (JSON_UNLIKELY(not element.second.is_primitive())) - { - JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); - } - - // assign value to reference pointed to by JSON pointer; Note that if - // the JSON pointer is "" (i.e., points to the whole value), function - // get_and_create returns a reference to result itself. An assignment - // will then create a primitive value. - json_pointer(element.first).get_and_create(result) = element.second; - } - - return result; -} - -inline bool operator==(json_pointer const& lhs, json_pointer const& rhs) noexcept -{ - return (lhs.reference_tokens == rhs.reference_tokens); -} - -inline bool operator!=(json_pointer const& lhs, json_pointer const& rhs) noexcept -{ - return not (lhs == rhs); -} } // namespace nlohmann - /////////////////////// // nonmember support // /////////////////////// @@ -14853,6 +17272,9 @@ inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std return nlohmann::json::json_pointer(std::string(s, n)); } +// #include + + // restore GCC/clang diagnostic settings #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop @@ -14868,7 +17290,11 @@ inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std #undef JSON_LIKELY #undef JSON_UNLIKELY #undef JSON_DEPRECATED +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 #undef NLOHMANN_BASIC_JSON_TPL_DECLARATION #undef NLOHMANN_BASIC_JSON_TPL +#undef NLOHMANN_JSON_HAS_HELPER + #endif