Skip to content

Instantly share code, notes, and snippets.

@Hornwitser
Last active October 29, 2024 14:06
Show Gist options
  • Save Hornwitser/f291638024e7e3c0271b1f3a4723e05a to your computer and use it in GitHub Desktop.
Save Hornwitser/f291638024e7e3c0271b1f3a4723e05a to your computer and use it in GitHub Desktop.

Revisions

  1. Hornwitser revised this gist May 18, 2021. 1 changed file with 286 additions and 529 deletions.
    815 changes: 286 additions & 529 deletions exchange_string_decoder.js
    Original file line number Diff line number Diff line change
    @@ -2,177 +2,167 @@ const zlib = require("zlib");
    const util = require("util");


    function read_version(pos, buf) {
    let major = buf.readUInt16LE(pos);
    pos += 2;
    let minor = buf.readUInt16LE(pos);
    pos += 2;
    let patch = buf.readUInt16LE(pos);
    pos += 2;
    let developer = buf.readUInt16LE(pos);
    pos += 2;
    return [pos, [major, minor, patch, developer]];
    class Parser {
    constructor(buf) {
    this.pos = 0;
    this.buf = buf;
    this.last_position = { x: 0, y: 0 };
    }
    }

    function read_bool(pos, buf) {
    let value = buf.readUint8(pos) !== 0;
    pos += 1;
    return [pos, value];
    function read_bool(parser) {
    let value = read_uint8(parser) !== 0;
    return value;
    }

    function read_int32(pos, buf) {
    let value = buf.readInt32LE(pos);
    pos += 4;
    function read_uint8(parser) {
    let value = parser.buf.readUInt8(parser.pos);
    parser.pos += 1;
    return value;
    }

    return [pos, value];
    function read_int16(parser) {
    let value = parser.buf.readInt16LE(parser.pos);
    parser.pos += 2;
    return value;
    }

    function read_uint32(pos, buf) {
    let value = buf.readUInt32LE(pos);
    pos += 4;
    function read_uint16(parser) {
    let value = parser.buf.readUInt16LE(parser.pos);
    parser.pos += 2;
    return value;
    }

    function read_int32(parser) {
    let value = parser.buf.readInt32LE(parser.pos);
    parser.pos += 4;
    return value;
    }

    return [pos, value];
    function read_uint32(parser) {
    let value = parser.buf.readUInt32LE(parser.pos);
    parser.pos += 4;
    return value;
    }

    function read_uint32so(pos, buf) {
    let value = buf.readUInt8(pos);
    pos += 1;
    function read_uint32so(parser) {
    let value = read_uint8(parser);
    if (value === 0xff) {
    let value = buf.readUInt32LE(pos);
    pos += 4;
    return read_uint32(parser);
    }

    return [pos, value];
    return value;
    }

    function read_optional(pos, buf, read) {
    let load = buf.readUint8(pos) !== 0;
    pos += 1;
    if (!load) {
    return [pos, null];
    }
    return read(pos, buf);
    function read_float(parser) {
    let value = parser.buf.readFloatLE(parser.pos);
    parser.pos += 4;
    return value;
    }

    function read_double(pos, buf) {
    let value = buf.readDoubleLE(pos);
    pos += 8;
    return [pos, value];
    function read_double(parser) {
    let value = parser.buf.readDoubleLE(parser.pos);
    parser.pos += 8;
    return value;
    }

    function read_string(pos, buf) {
    let size;
    ([pos, size] = read_uint32so(pos, buf));
    let data = buf.slice(pos, pos + size).toString("utf-8");
    return [pos + size, data];
    function read_string(parser) {
    let size = read_uint32so(parser);
    let data = parser.buf.slice(parser.pos, parser.pos + size).toString("utf-8");
    parser.pos += size;
    return data;
    }

    function read_optional(parser, read_value) {
    let load = read_uint8(parser) !== 0;
    if (!load) {
    return null;
    }
    return read_value(parser);
    }

    function read_dict(pos, buf, read_key, read_value) {
    let size;
    ([pos, size] = read_uint32so(pos, buf));
    function read_array(parser, read_item) {
    let size = read_uint32so(parser);

    let mapping = new Map();
    let array = [];
    for (let i = 0; i < size; i++) {
    let key, value;
    ([pos, key] = read_key(pos, buf));
    ([pos, value] = read_value(pos, buf));
    mapping.set(key, value);
    let item = read_item(parser);
    array.push(item);
    }

    return [pos, mapping];
    return array;
    }

    function read_array(pos, buf, read_item) {
    let size;
    ([pos, size] = read_uint32so(pos, buf));
    function read_dict(parser, read_key, read_value) {
    let size = read_uint32so(parser);

    let array = [];
    let mapping = new Map();
    for (let i = 0; i < size; i++) {
    let item;
    ([pos, item] = read_item(pos, buf));
    array.push(item);
    let key = read_key(parser);
    let value = read_value(parser);
    mapping.set(key, value);
    }

    return [pos, array];
    return mapping;
    }

    function read_frequency_size_richness(pos, buf) {
    let frequency = buf.readFloatLE(pos);
    pos += 4;
    let size = buf.readFloatLE(pos);
    pos += 4;
    let richness = buf.readFloatLE(pos);
    pos += 4;
    return [pos, { frequency, size, richness }];
    function read_version(parser) {
    let major = read_uint16(parser);
    let minor = read_uint16(parser);
    let patch = read_uint16(parser);
    let developer = read_uint16(parser);
    return [major, minor, patch, developer];
    }

    function read_autoplace_setting(pos, buf) {
    let treat_missing_as_default = buf.readUInt8(pos) !== 0;
    pos += 1;
    let settings;
    ([pos, settings] = read_dict(pos, buf, read_string, read_frequency_size_richness));
    function read_frequency_size_richness(parser) {
    return {
    frequency: read_float(parser),
    size: read_float(parser),
    richness: read_float(parser),
    }
    }

    return [pos, {
    treat_missing_as_default,
    settings: map_to_object(settings),
    }];
    function read_autoplace_setting(parser) {
    return {
    treat_missing_as_default: read_bool(parser),
    settings: map_to_object(read_dict(parser, read_string, read_frequency_size_richness)),
    };
    }

    function read_map_position(pos, buf, state) {
    function read_map_position(parser) {
    let x, y;
    let x_diff = buf.readInt16LE(pos) / 256;
    pos += 2;
    let x_diff = read_int16(parser) / 256;
    if (x_diff === 0x7fff / 256) {
    x = buf.readInt32LE(pos) / 256;
    pos += 4;
    y = buf.readInt32LE(pos) / 256;
    pos += 4;
    x = read_int32(parser) / 256;
    y = read_int32(parser) / 256;
    } else {
    let y_diff = buf.readInt16LE(pos) / 256;
    pos += 2;
    x = state.last_position.x + x_diff;
    y = state.last_position.y + y_diff;
    let y_diff = read_int16(parser) / 256;
    x = parser.last_position.x + x_diff;
    y = parser.last_position.y + y_diff;
    }
    state.last_position.x = x;
    state.last_position.x = y;
    return [pos, { x, y }]
    }

    function read_bounding_box(pos, buf, state) {
    let left_top;
    ([pos, left_top] = read_map_position(pos, buf, state));
    let right_bottom;
    ([pos, right_bottom] = read_map_position(pos, buf, state));
    let orientation;

    let x = buf.readInt16LE(pos);
    pos += 2;
    let y = buf.readInt16LE(pos);
    pos += 2;

    return [pos, {
    left_top,
    right_bottom,
    orientation: { x, y },
    }];
    }

    function read_cliff_settings(pos, buf) {
    let name;
    ([pos, name] = read_string(pos, buf));
    let elevation_0 = buf.readFloatLE(pos);
    pos += 4;
    let elevation_interval = buf.readFloatLE(pos);
    pos += 4;
    let richness = buf.readFloatLE(pos);
    pos += 4;

    return [pos, {
    name,
    elevation_0,
    elevation_interval,
    richness,
    }];
    parser.last_position.x = x;
    parser.last_position.x = y;
    return { x, y };
    }

    function read_bounding_box(parser) {
    return {
    left_top: read_map_position(parser),
    right_bottom: read_map_position(parser),
    orientation: {
    x: read_int16(parser),
    y: read_int16(parser)
    },
    };
    }

    function read_cliff_settings(parser) {
    return {
    name: read_string(parser),
    elevation_0: read_float(parser),
    elevation_interval: read_float(parser),
    richness: read_float(parser),
    };
    }

    function map_to_object(map) {
    @@ -183,391 +173,164 @@ function map_to_object(map) {
    return obj;
    }

    function read_map_gen_settings(pos, buf) {
    const state = {
    last_position: { x: 0, y: 0 },
    function read_map_gen_settings(parser) {
    return {
    terrain_segmentation: read_float(parser),
    water: read_float(parser),
    autoplace_controls: map_to_object(read_dict(parser, read_string, read_frequency_size_richness)),
    autoplace_settings: map_to_object(read_dict(parser, read_string, read_autoplace_setting)),
    default_enable_all_autoplace_controls: read_bool(parser),
    seed: read_uint32(parser),
    width: read_uint32(parser),
    height: read_uint32(parser),
    area_to_generate_at_start: read_bounding_box(parser),
    starting_area: read_float(parser),
    peaceful_mode: read_bool(parser),
    starting_points: read_array(parser, read_map_position),
    property_expression_names: map_to_object(read_dict(parser, read_string, read_string)),
    cliff_settings: read_cliff_settings(parser),
    };
    }

    let terrain_segmentation = buf.readFloatLE(pos);
    pos += 4;
    let water = buf.readFloatLE(pos);
    pos += 4;
    let autoplace_controls;
    ([pos, autoplace_controls] = read_dict(pos, buf, read_string, read_frequency_size_richness));
    let autoplace_settings;
    ([pos, autoplace_settings] = read_dict(pos, buf, read_string, read_autoplace_setting));
    let default_enable_all_autoplace_controls = buf.readUInt8(pos) !== 0;
    pos += 1;
    let seed = buf.readUInt32LE(pos);
    pos += 4;
    let width = buf.readUInt32LE(pos);
    pos += 4;
    let height = buf.readUInt32LE(pos);
    pos += 4;
    let area_to_generate_at_start;
    ([pos, area_to_generate_at_start] = read_bounding_box(pos, buf, state));
    let starting_area = buf.readFloatLE(pos);
    pos += 4;
    let peaceful_mode = buf.readUInt8(pos) !== 0;
    pos += 1;
    let starting_points;
    ([pos, starting_points] = read_array(pos, buf, (pos, buf) => read_map_position(pos, buf, state)));
    let property_expression_names;
    ([pos, property_expression_names] = read_dict(pos, buf, read_string, read_string));
    let cliff_settings;
    ([pos, cliff_settings] = read_cliff_settings(pos, buf));

    return [pos, {
    terrain_segmentation,
    water,
    autoplace_controls: map_to_object(autoplace_controls),
    autoplace_settings: map_to_object(autoplace_settings),
    default_enable_all_autoplace_controls,
    seed,
    width,
    height,
    area_to_generate_at_start,
    starting_area,
    peaceful_mode,
    starting_points,
    property_expression_names: map_to_object(property_expression_names),
    cliff_settings,
    }];
    }

    function read_pollution(pos, buf) {
    let enabled;
    ([pos, enabled] = read_optional(pos, buf, read_bool));
    let diffusion_ratio;
    ([pos, diffusion_ratio] = read_optional(pos, buf, read_double));
    let min_to_diffuse;
    ([pos, min_to_diffuse] = read_optional(pos, buf, read_double));
    let ageing;
    ([pos, ageing] = read_optional(pos, buf, read_double));
    let expected_max_per_chunk;
    ([pos, expected_max_per_chunk] = read_optional(pos, buf, read_double));
    let min_to_show_per_chunk;
    ([pos, min_to_show_per_chunk] = read_optional(pos, buf, read_double));
    let min_pollution_to_damage_trees;
    ([pos, min_pollution_to_damage_trees] = read_optional(pos, buf, read_double));
    let pollution_with_max_forest_damage;
    ([pos, pollution_with_max_forest_damage] = read_optional(pos, buf, read_double));
    let pollution_per_tree_damage;
    ([pos, pollution_per_tree_damage] = read_optional(pos, buf, read_double));
    let pollution_restored_per_tree_damage;
    ([pos, pollution_restored_per_tree_damage] = read_optional(pos, buf, read_double));
    let max_pollution_to_restore_trees;
    ([pos, max_pollution_to_restore_trees] = read_optional(pos, buf, read_double));
    let enemy_attack_pollution_consumption_modifier;
    ([pos, enemy_attack_pollution_consumption_modifier] = read_optional(pos, buf, read_double));

    return [pos, {
    enabled,
    diffusion_ratio,
    min_to_diffuse,
    ageing,
    expected_max_per_chunk,
    min_to_show_per_chunk,
    min_pollution_to_damage_trees,
    pollution_with_max_forest_damage,
    pollution_per_tree_damage,
    pollution_restored_per_tree_damage,
    max_pollution_to_restore_trees,
    enemy_attack_pollution_consumption_modifier,
    }];
    }

    function read_real_steering(pos, buf) {
    let radius;
    ([pos, radius] = read_optional(pos, buf, read_double));
    let separation_factor;
    ([pos, separation_factor] = read_optional(pos, buf, read_double));
    let separation_force;
    ([pos, separation_force] = read_optional(pos, buf, read_double));
    let force_unit_fuzzy_goto_behavior;
    ([pos, force_unit_fuzzy_goto_behavior] = read_optional(pos, buf, read_bool));

    return [pos, {
    radius,
    separation_factor,
    separation_force,
    force_unit_fuzzy_goto_behavior,
    }];

    }

    function read_steering(pos, buf) {
    let default_;
    ([pos, default_] = read_real_steering(pos, buf));
    let moving
    ([pos, moving] = read_real_steering(pos, buf));

    return [pos, {
    default: default_,
    moving,
    }];
    }

    function read_enemy_evolution(pos, buf) {
    function read_pollution(parser) {
    let enabled;
    ([pos, enabled] = read_optional(pos, buf, read_bool));
    let time_factor;
    ([pos, time_factor] = read_optional(pos, buf, read_double));
    let destroy_factor;
    ([pos, destroy_factor] = read_optional(pos, buf, read_double));
    let pollution_factor;
    ([pos, pollution_factor] = read_optional(pos, buf, read_double));

    return [pos, {
    enabled,
    time_factor,
    destroy_factor,
    pollution_factor,
    }];
    }

    function read_enemy_expansion(pos, buf) {
    let enabled;
    ([pos, enabled] = read_optional(pos, buf, read_bool));
    let max_expansion_distance;
    ([pos, max_expansion_distance] = read_optional(pos, buf, read_uint32));
    let friendly_base_influence_radius;
    ([pos, friendly_base_influence_radius] = read_optional(pos, buf, read_uint32));
    let enemy_building_influence_radius;
    ([pos, enemy_building_influence_radius] = read_optional(pos, buf, read_uint32));
    let building_coefficient;
    ([pos, building_coefficient] = read_optional(pos, buf, read_double));
    let other_base_coefficient;
    ([pos, other_base_coefficient] = read_optional(pos, buf, read_double));
    let neighbouring_chunk_coefficient;
    ([pos, neighbouring_chunk_coefficient] = read_optional(pos, buf, read_double));
    let neighbouring_base_chunk_coefficient;
    ([pos, neighbouring_base_chunk_coefficient] = read_optional(pos, buf, read_double));
    let max_colliding_tiles_coefficient;
    ([pos, max_colliding_tiles_coefficient] = read_optional(pos, buf, read_double));
    let settler_group_min_size;
    ([pos, settler_group_min_size] = read_optional(pos, buf, read_uint32));
    let settler_group_max_size;
    ([pos, settler_group_max_size] = read_optional(pos, buf, read_uint32));
    let min_expansion_cooldown;
    ([pos, min_expansion_cooldown] = read_optional(pos, buf, read_uint32));
    let max_expansion_cooldown;
    ([pos, max_expansion_cooldown] = read_optional(pos, buf, read_uint32));

    return [pos, {
    enabled,
    max_expansion_distance,
    friendly_base_influence_radius,
    enemy_building_influence_radius,
    building_coefficient,
    other_base_coefficient,
    neighbouring_chunk_coefficient,
    neighbouring_base_chunk_coefficient,
    max_colliding_tiles_coefficient,
    settler_group_min_size,
    settler_group_max_size,
    min_expansion_cooldown,
    max_expansion_cooldown,
    }];
    }

    function read_unit_group(pos, buf) {
    let min_group_gathering_time;
    ([pos, min_group_gathering_time] = read_optional(pos, buf, read_uint32));
    let max_group_gathering_time;
    ([pos, max_group_gathering_time] = read_optional(pos, buf, read_uint32));
    let max_wait_time_for_late_members;
    ([pos, max_wait_time_for_late_members] = read_optional(pos, buf, read_uint32));
    let max_group_radius;
    ([pos, max_group_radius] = read_optional(pos, buf, read_double));
    let min_group_radius;
    ([pos, min_group_radius] = read_optional(pos, buf, read_double));
    let max_member_speedup_when_behind;
    ([pos, max_member_speedup_when_behind] = read_optional(pos, buf, read_double));
    let max_member_slowdown_when_ahead;
    ([pos, max_member_slowdown_when_ahead] = read_optional(pos, buf, read_double));
    let max_group_slowdown_factor;
    ([pos, max_group_slowdown_factor] = read_optional(pos, buf, read_double));
    let max_group_member_fallback_factor;
    ([pos, max_group_member_fallback_factor] = read_optional(pos, buf, read_double));
    let member_disown_distance;
    ([pos, member_disown_distance] = read_optional(pos, buf, read_double));
    let tick_tolerance_when_member_arrives;
    ([pos, tick_tolerance_when_member_arrives] = read_optional(pos, buf, read_uint32));
    let max_gathering_unit_groups;
    ([pos, max_gathering_unit_groups] = read_optional(pos, buf, read_uint32));
    let max_unit_group_size;
    ([pos, max_unit_group_size] = read_optional(pos, buf, read_uint32));

    return [pos, {
    min_group_gathering_time,
    max_group_gathering_time,
    max_wait_time_for_late_members,
    max_group_radius,
    min_group_radius,
    max_member_speedup_when_behind,
    max_member_slowdown_when_ahead,
    max_group_slowdown_factor,
    max_group_member_fallback_factor,
    member_disown_distance,
    tick_tolerance_when_member_arrives,
    max_gathering_unit_groups,
    max_unit_group_size,
    }];
    }

    function read_path_finder(pos, buf) {
    let fwd2bwd_ratio;
    ([pos, fwd2bwd_ratio] = read_optional(pos, buf, read_int32));
    let goal_pressure_ratio;
    ([pos, goal_pressure_ratio] = read_optional(pos, buf, read_double));
    let use_path_cache;
    ([pos, use_path_cache] = read_optional(pos, buf, read_bool));
    let max_steps_worked_per_tick;
    ([pos, max_steps_worked_per_tick] = read_optional(pos, buf, read_double));
    let max_work_done_per_tick;
    ([pos, max_work_done_per_tick] = read_optional(pos, buf, read_uint32));
    let short_cache_size;
    ([pos, short_cache_size] = read_optional(pos, buf, read_uint32));
    let long_cache_size;
    ([pos, long_cache_size] = read_optional(pos, buf, read_uint32));
    let short_cache_min_cacheable_distance;
    ([pos, short_cache_min_cacheable_distance] = read_optional(pos, buf, read_double));
    let short_cache_min_algo_steps_to_cache;
    ([pos, short_cache_min_algo_steps_to_cache] = read_optional(pos, buf, read_uint32));
    let long_cache_min_cacheable_distance;
    ([pos, long_cache_min_cacheable_distance] = read_optional(pos, buf, read_double));
    let cache_max_connect_to_cache_steps_multiplier;
    ([pos, cache_max_connect_to_cache_steps_multiplier] = read_optional(pos, buf, read_uint32));
    let cache_accept_path_start_distance_ratio;
    ([pos, cache_accept_path_start_distance_ratio] = read_optional(pos, buf, read_double));
    let cache_accept_path_end_distance_ratio;
    ([pos, cache_accept_path_end_distance_ratio] = read_optional(pos, buf, read_double));
    let negative_cache_accept_path_start_distance_ratio;
    ([pos, negative_cache_accept_path_start_distance_ratio] = read_optional(pos, buf, read_double));
    let negative_cache_accept_path_end_distance_ratio;
    ([pos, negative_cache_accept_path_end_distance_ratio] = read_optional(pos, buf, read_double));
    let cache_path_start_distance_rating_multiplier;
    ([pos, cache_path_start_distance_rating_multiplier] = read_optional(pos, buf, read_double));
    let cache_path_end_distance_rating_multiplier;
    ([pos, cache_path_end_distance_rating_multiplier] = read_optional(pos, buf, read_double));
    let stale_enemy_with_same_destination_collision_penalty;
    ([pos, stale_enemy_with_same_destination_collision_penalty] = read_optional(pos, buf, read_double));
    let ignore_moving_enemy_collision_distance;
    ([pos, ignore_moving_enemy_collision_distance] = read_optional(pos, buf, read_double));
    let enemy_with_different_destination_collision_penalty;
    ([pos, enemy_with_different_destination_collision_penalty] = read_optional(pos, buf, read_double));
    let general_entity_collision_penalty;
    ([pos, general_entity_collision_penalty] = read_optional(pos, buf, read_double));
    let general_entity_subsequent_collision_penalty;
    ([pos, general_entity_subsequent_collision_penalty] = read_optional(pos, buf, read_double));
    let extended_collision_penalty;
    ([pos, extended_collision_penalty] = read_optional(pos, buf, read_double));
    let max_clients_to_accept_any_new_request;
    ([pos, max_clients_to_accept_any_new_request] = read_optional(pos, buf, read_uint32));
    let max_clients_to_accept_short_new_request;
    ([pos, max_clients_to_accept_short_new_request] = read_optional(pos, buf, read_uint32));
    let direct_distance_to_consider_short_request;
    ([pos, direct_distance_to_consider_short_request] = read_optional(pos, buf, read_uint32));
    let short_request_max_steps;
    ([pos, short_request_max_steps] = read_optional(pos, buf, read_uint32));
    let short_request_ratio;
    ([pos, short_request_ratio] = read_optional(pos, buf, read_double));
    let min_steps_to_check_path_find_termination;
    ([pos, min_steps_to_check_path_find_termination] = read_optional(pos, buf, read_uint32));
    let start_to_goal_cost_multiplier_to_terminate_path_find;
    ([pos, start_to_goal_cost_multiplier_to_terminate_path_find] = read_optional(pos, buf, read_double));
    let overload_levels;
    ([pos, overload_levels] = read_optional(pos, buf, (p, b) => read_array(p, b, read_uint32)));
    let overload_multipliers;
    ([pos, overload_multipliers] = read_optional(pos, buf, (p, b) => read_array(p, b, read_double)));
    let negative_path_cache_delay_interval;
    ([pos, negative_path_cache_delay_interval] = read_optional(pos, buf, read_uint32));

    return [pos, {
    fwd2bwd_ratio,
    goal_pressure_ratio,
    use_path_cache,
    max_steps_worked_per_tick,
    max_work_done_per_tick,
    short_cache_size,
    long_cache_size,
    short_cache_min_cacheable_distance,
    short_cache_min_algo_steps_to_cache,
    long_cache_min_cacheable_distance,
    cache_max_connect_to_cache_steps_multiplier,
    cache_accept_path_start_distance_ratio,
    cache_accept_path_end_distance_ratio,
    negative_cache_accept_path_start_distance_ratio,
    negative_cache_accept_path_end_distance_ratio,
    cache_path_start_distance_rating_multiplier,
    cache_path_end_distance_rating_multiplier,
    stale_enemy_with_same_destination_collision_penalty,
    ignore_moving_enemy_collision_distance,
    enemy_with_different_destination_collision_penalty,
    general_entity_collision_penalty,
    general_entity_subsequent_collision_penalty,
    extended_collision_penalty,
    max_clients_to_accept_any_new_request,
    max_clients_to_accept_short_new_request,
    direct_distance_to_consider_short_request,
    short_request_max_steps,
    short_request_ratio,
    min_steps_to_check_path_find_termination,
    start_to_goal_cost_multiplier_to_terminate_path_find,
    overload_levels,
    overload_multipliers,
    negative_path_cache_delay_interval,
    }];
    }

    function read_difficulty_settings(pos, buf) {
    let recipe_difficulty = buf.readUInt8(pos);
    pos += 1;
    let technology_difficulty = buf.readUInt8(pos);
    pos += 1;
    let technology_price_multiplier = buf.readDoubleLE(pos);
    pos += 8;
    let research_queue_setting = buf.readUInt8(pos);
    pos += 1;

    return [pos, {
    recipe_difficulty,
    technology_difficulty,
    technology_price_multiplier,
    research_queue_setting: ["always", "after-victory", "never"][research_queue_setting],
    }];
    }

    function read_map_settings(pos, buf) {
    let pollution;
    ([pos, pollution] = read_pollution(pos, buf));
    let steering;
    ([pos, steering] = read_steering(pos, buf));
    let enemy_evolution;
    ([pos, enemy_evolution] = read_enemy_evolution(pos, buf));
    let enemy_expansion;
    ([pos, enemy_expansion] = read_enemy_expansion(pos, buf));
    let unit_group;
    ([pos, unit_group] = read_unit_group(pos, buf));
    let path_finder;
    ([pos, path_finder] = read_path_finder(pos, buf));
    let max_failed_behavior_count = buf.readUInt32LE(pos);
    pos += 4;
    let difficulty_settings;
    ([pos, difficulty_settings] = read_difficulty_settings(pos, buf));


    return [pos, {
    pollution,
    steering,
    enemy_evolution,
    enemy_expansion,
    unit_group,
    path_finder,
    max_failed_behavior_count,
    difficulty_settings,
    }];

    return {
    enabled: read_optional(parser, read_bool),
    diffusion_ratio: read_optional(parser, read_double),
    min_to_diffuse: read_optional(parser, read_double),
    ageing: read_optional(parser, read_double),
    expected_max_per_chunk: read_optional(parser, read_double),
    min_to_show_per_chunk: read_optional(parser, read_double),
    min_pollution_to_damage_trees: read_optional(parser, read_double),
    pollution_with_max_forest_damage: read_optional(parser, read_double),
    pollution_per_tree_damage: read_optional(parser, read_double),
    pollution_restored_per_tree_damage: read_optional(parser, read_double),
    max_pollution_to_restore_trees: read_optional(parser, read_double),
    enemy_attack_pollution_consumption_modifier: read_optional(parser, read_double),
    };
    }

    function read_real_steering(parser) {
    return {
    radius: read_optional(parser, read_double),
    separation_factor: read_optional(parser, read_double),
    separation_force: read_optional(parser, read_double),
    force_unit_fuzzy_goto_behavior: read_optional(parser, read_bool),
    };

    }

    function read_steering(parser) {
    return {
    default: read_real_steering(parser),
    moving: read_real_steering(parser),
    };
    }

    function read_enemy_evolution(parser) {
    return {
    enabled: read_optional(parser, read_bool),
    time_factor: read_optional(parser, read_double),
    destroy_factor: read_optional(parser, read_double),
    pollution_factor: read_optional(parser, read_double),
    };
    }

    function read_enemy_expansion(parser) {
    return {
    enabled: read_optional(parser, read_bool),
    max_expansion_distance: read_optional(parser, read_uint32),
    friendly_base_influence_radius: read_optional(parser, read_uint32),
    enemy_building_influence_radius: read_optional(parser, read_uint32),
    building_coefficient: read_optional(parser, read_double),
    other_base_coefficient: read_optional(parser, read_double),
    neighbouring_chunk_coefficient: read_optional(parser, read_double),
    neighbouring_base_chunk_coefficient: read_optional(parser, read_double),
    max_colliding_tiles_coefficient: read_optional(parser, read_double),
    settler_group_min_size: read_optional(parser, read_uint32),
    settler_group_max_size: read_optional(parser, read_uint32),
    min_expansion_cooldown: read_optional(parser, read_uint32),
    max_expansion_cooldown: read_optional(parser, read_uint32),
    };
    }

    function read_unit_group(parser) {
    return {
    min_group_gathering_time: read_optional(parser, read_uint32),
    max_group_gathering_time: read_optional(parser, read_uint32),
    max_wait_time_for_late_members: read_optional(parser, read_uint32),
    max_group_radius: read_optional(parser, read_double),
    min_group_radius: read_optional(parser, read_double),
    max_member_speedup_when_behind: read_optional(parser, read_double),
    max_member_slowdown_when_ahead: read_optional(parser, read_double),
    max_group_slowdown_factor: read_optional(parser, read_double),
    max_group_member_fallback_factor: read_optional(parser, read_double),
    member_disown_distance: read_optional(parser, read_double),
    tick_tolerance_when_member_arrives: read_optional(parser, read_uint32),
    max_gathering_unit_groups: read_optional(parser, read_uint32),
    max_unit_group_size: read_optional(parser, read_uint32),
    };
    }

    function read_path_finder(parser) {
    return {
    fwd2bwd_ratio: read_optional(parser, read_int32),
    goal_pressure_ratio: read_optional(parser, read_double),
    use_path_cache: read_optional(parser, read_bool),
    max_steps_worked_per_tick: read_optional(parser, read_double),
    max_work_done_per_tick: read_optional(parser, read_uint32),
    short_cache_size: read_optional(parser, read_uint32),
    long_cache_size: read_optional(parser, read_uint32),
    short_cache_min_cacheable_distance: read_optional(parser, read_double),
    short_cache_min_algo_steps_to_cache: read_optional(parser, read_uint32),
    long_cache_min_cacheable_distance: read_optional(parser, read_double),
    cache_max_connect_to_cache_steps_multiplier: read_optional(parser, read_uint32),
    cache_accept_path_start_distance_ratio: read_optional(parser, read_double),
    cache_accept_path_end_distance_ratio: read_optional(parser, read_double),
    negative_cache_accept_path_start_distance_ratio: read_optional(parser, read_double),
    negative_cache_accept_path_end_distance_ratio: read_optional(parser, read_double),
    cache_path_start_distance_rating_multiplier: read_optional(parser, read_double),
    cache_path_end_distance_rating_multiplier: read_optional(parser, read_double),
    stale_enemy_with_same_destination_collision_penalty: read_optional(parser, read_double),
    ignore_moving_enemy_collision_distance: read_optional(parser, read_double),
    enemy_with_different_destination_collision_penalty: read_optional(parser, read_double),
    general_entity_collision_penalty: read_optional(parser, read_double),
    general_entity_subsequent_collision_penalty: read_optional(parser, read_double),
    extended_collision_penalty: read_optional(parser, read_double),
    max_clients_to_accept_any_new_request: read_optional(parser, read_uint32),
    max_clients_to_accept_short_new_request: read_optional(parser, read_uint32),
    direct_distance_to_consider_short_request: read_optional(parser, read_uint32),
    short_request_max_steps: read_optional(parser, read_uint32),
    short_request_ratio: read_optional(parser, read_double),
    min_steps_to_check_path_find_termination: read_optional(parser, read_uint32),
    start_to_goal_cost_multiplier_to_terminate_path_find: read_optional(parser, read_double),
    overload_levels: read_optional(parser, (p) => read_array(p, read_uint32)),
    overload_multipliers: read_optional(parser, (p) => read_array(p, read_double)),
    negative_path_cache_delay_interval: read_optional(parser, read_uint32),
    };
    }

    function read_difficulty_settings(parser) {
    return {
    recipe_difficulty: read_uint8(parser),
    technology_difficulty: read_uint8(parser),
    technology_price_multiplier: read_double(parser),
    research_queue_setting: ["always", "after-victory", "never"][read_uint8(parser)],
    };
    }

    function read_map_settings(parser) {
    return {
    pollution: read_pollution(parser),
    steering: read_steering(parser),
    enemy_evolution: read_enemy_evolution(parser),
    enemy_expansion: read_enemy_expansion(parser),
    unit_group: read_unit_group(parser),
    path_finder: read_path_finder(parser),
    max_failed_behavior_count: read_uint32(parser),
    difficulty_settings: read_difficulty_settings(parser),
    };
    }

    function decode(s) {
    @@ -579,27 +342,21 @@ function decode(s) {
    let buf = Buffer.from(s.slice(3, -3), "base64");
    buf = zlib.inflateSync(buf);

    let pos = 0;
    let version;
    ([pos, version] = read_version(pos, buf));
    pos += 1; // Discard unknown flag.
    let map_gen_settings;
    ([pos, map_gen_settings] = read_map_gen_settings(pos, buf));
    let map_settings;
    ([pos, map_settings] = read_map_settings(pos, buf));
    let checksum = buf.readUInt32LE(pos);
    pos += 4;

    if (pos != buf.length) {
    let parser = new Parser(buf);

    let data = {
    version: read_version(parser),
    unknown: read_uint8(parser),
    map_gen_settings: read_map_gen_settings(parser),
    map_settings: read_map_settings(parser),
    checksum: read_uint32(parser),
    };

    if (parser.pos != buf.length) {
    return "data after end";
    }

    return {
    version,
    map_gen_settings,
    map_settings,
    checksum,
    }
    return data;
    }

    let test = `
  2. Hornwitser revised this gist May 18, 2021. 1 changed file with 19 additions and 19 deletions.
    38 changes: 19 additions & 19 deletions exchange_string_decoder.js
    Original file line number Diff line number Diff line change
    @@ -110,11 +110,11 @@ function read_autoplace_setting(pos, buf) {
    let treat_missing_as_default = buf.readUInt8(pos) !== 0;
    pos += 1;
    let settings;
    ([pos, settings] = read_frequency_size_richness(pos, buf));
    ([pos, settings] = read_dict(pos, buf, read_string, read_frequency_size_richness));

    return [pos, {
    treat_missing_as_default,
    settings,
    settings: map_to_object(settings),
    }];
    }

    @@ -238,8 +238,8 @@ function read_map_gen_settings(pos, buf) {
    function read_pollution(pos, buf) {
    let enabled;
    ([pos, enabled] = read_optional(pos, buf, read_bool));
    let diffision_ratio;
    ([pos, diffision_ratio] = read_optional(pos, buf, read_double));
    let diffusion_ratio;
    ([pos, diffusion_ratio] = read_optional(pos, buf, read_double));
    let min_to_diffuse;
    ([pos, min_to_diffuse] = read_optional(pos, buf, read_double));
    let ageing;
    @@ -263,7 +263,7 @@ function read_pollution(pos, buf) {

    return [pos, {
    enabled,
    diffision_ratio,
    diffusion_ratio,
    min_to_diffuse,
    ageing,
    expected_max_per_chunk,
    @@ -284,14 +284,14 @@ function read_real_steering(pos, buf) {
    ([pos, separation_factor] = read_optional(pos, buf, read_double));
    let separation_force;
    ([pos, separation_force] = read_optional(pos, buf, read_double));
    let force_unit_fuzzy_goto_behaviour;
    ([pos, force_unit_fuzzy_goto_behaviour] = read_optional(pos, buf, read_bool));
    let force_unit_fuzzy_goto_behavior;
    ([pos, force_unit_fuzzy_goto_behavior] = read_optional(pos, buf, read_bool));

    return [pos, {
    radius,
    separation_factor,
    separation_force,
    force_unit_fuzzy_goto_behaviour,
    force_unit_fuzzy_goto_behavior,
    }];

    }
    @@ -355,6 +355,7 @@ function read_enemy_expansion(pos, buf) {
    ([pos, max_expansion_cooldown] = read_optional(pos, buf, read_uint32));

    return [pos, {
    enabled,
    max_expansion_distance,
    friendly_base_influence_radius,
    enemy_building_influence_radius,
    @@ -478,8 +479,8 @@ function read_path_finder(pos, buf) {
    ([pos, start_to_goal_cost_multiplier_to_terminate_path_find] = read_optional(pos, buf, read_double));
    let overload_levels;
    ([pos, overload_levels] = read_optional(pos, buf, (p, b) => read_array(p, b, read_uint32)));
    let oveload_multipliers;
    ([pos, oveload_multipliers] = read_optional(pos, buf, (p, b) => read_array(p, b, read_double)));
    let overload_multipliers;
    ([pos, overload_multipliers] = read_optional(pos, buf, (p, b) => read_array(p, b, read_double)));
    let negative_path_cache_delay_interval;
    ([pos, negative_path_cache_delay_interval] = read_optional(pos, buf, read_uint32));

    @@ -515,12 +516,12 @@ function read_path_finder(pos, buf) {
    min_steps_to_check_path_find_termination,
    start_to_goal_cost_multiplier_to_terminate_path_find,
    overload_levels,
    oveload_multipliers,
    overload_multipliers,
    negative_path_cache_delay_interval,
    }];
    }

    function read_difficulty(pos, buf) {
    function read_difficulty_settings(pos, buf) {
    let recipe_difficulty = buf.readUInt8(pos);
    pos += 1;
    let technology_difficulty = buf.readUInt8(pos);
    @@ -553,8 +554,8 @@ function read_map_settings(pos, buf) {
    ([pos, path_finder] = read_path_finder(pos, buf));
    let max_failed_behavior_count = buf.readUInt32LE(pos);
    pos += 4;
    let difficulty;
    ([pos, difficulty] = read_difficulty(pos, buf));
    let difficulty_settings;
    ([pos, difficulty_settings] = read_difficulty_settings(pos, buf));


    return [pos, {
    @@ -565,18 +566,17 @@ function read_map_settings(pos, buf) {
    unit_group,
    path_finder,
    max_failed_behavior_count,
    difficulty,
    difficulty_settings,
    }];
    }

    function decode(s) {
    s = s.trim();
    if (!/>>>[0-9a-zA-Z\/+\n]+<<</.test(s)) {
    s = s.replace(/[ \t\n\r]+/g, "");
    if (!/>>>[0-9a-zA-Z\/+]+={0,3}<<</.test(s)) {
    return "Not a map exchange string";
    }

    s = s.slice(3, -3).replace(/\n/g, "");
    let buf = Buffer.from(s, "base64");
    let buf = Buffer.from(s.slice(3, -3), "base64");
    buf = zlib.inflateSync(buf);

    let pos = 0;
  3. Hornwitser created this gist May 17, 2021.
    620 changes: 620 additions & 0 deletions exchange_string_decoder.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,620 @@
    const zlib = require("zlib");
    const util = require("util");


    function read_version(pos, buf) {
    let major = buf.readUInt16LE(pos);
    pos += 2;
    let minor = buf.readUInt16LE(pos);
    pos += 2;
    let patch = buf.readUInt16LE(pos);
    pos += 2;
    let developer = buf.readUInt16LE(pos);
    pos += 2;
    return [pos, [major, minor, patch, developer]];
    }

    function read_bool(pos, buf) {
    let value = buf.readUint8(pos) !== 0;
    pos += 1;
    return [pos, value];
    }

    function read_int32(pos, buf) {
    let value = buf.readInt32LE(pos);
    pos += 4;

    return [pos, value];
    }

    function read_uint32(pos, buf) {
    let value = buf.readUInt32LE(pos);
    pos += 4;

    return [pos, value];
    }

    function read_uint32so(pos, buf) {
    let value = buf.readUInt8(pos);
    pos += 1;
    if (value === 0xff) {
    let value = buf.readUInt32LE(pos);
    pos += 4;
    }

    return [pos, value];
    }

    function read_optional(pos, buf, read) {
    let load = buf.readUint8(pos) !== 0;
    pos += 1;
    if (!load) {
    return [pos, null];
    }
    return read(pos, buf);
    }

    function read_double(pos, buf) {
    let value = buf.readDoubleLE(pos);
    pos += 8;
    return [pos, value];
    }

    function read_string(pos, buf) {
    let size;
    ([pos, size] = read_uint32so(pos, buf));
    let data = buf.slice(pos, pos + size).toString("utf-8");
    return [pos + size, data];
    }

    function read_dict(pos, buf, read_key, read_value) {
    let size;
    ([pos, size] = read_uint32so(pos, buf));

    let mapping = new Map();
    for (let i = 0; i < size; i++) {
    let key, value;
    ([pos, key] = read_key(pos, buf));
    ([pos, value] = read_value(pos, buf));
    mapping.set(key, value);
    }

    return [pos, mapping];
    }

    function read_array(pos, buf, read_item) {
    let size;
    ([pos, size] = read_uint32so(pos, buf));

    let array = [];
    for (let i = 0; i < size; i++) {
    let item;
    ([pos, item] = read_item(pos, buf));
    array.push(item);
    }

    return [pos, array];
    }

    function read_frequency_size_richness(pos, buf) {
    let frequency = buf.readFloatLE(pos);
    pos += 4;
    let size = buf.readFloatLE(pos);
    pos += 4;
    let richness = buf.readFloatLE(pos);
    pos += 4;
    return [pos, { frequency, size, richness }];
    }

    function read_autoplace_setting(pos, buf) {
    let treat_missing_as_default = buf.readUInt8(pos) !== 0;
    pos += 1;
    let settings;
    ([pos, settings] = read_frequency_size_richness(pos, buf));

    return [pos, {
    treat_missing_as_default,
    settings,
    }];
    }

    function read_map_position(pos, buf, state) {
    let x, y;
    let x_diff = buf.readInt16LE(pos) / 256;
    pos += 2;
    if (x_diff === 0x7fff / 256) {
    x = buf.readInt32LE(pos) / 256;
    pos += 4;
    y = buf.readInt32LE(pos) / 256;
    pos += 4;
    } else {
    let y_diff = buf.readInt16LE(pos) / 256;
    pos += 2;
    x = state.last_position.x + x_diff;
    y = state.last_position.y + y_diff;
    }
    state.last_position.x = x;
    state.last_position.x = y;
    return [pos, { x, y }]
    }

    function read_bounding_box(pos, buf, state) {
    let left_top;
    ([pos, left_top] = read_map_position(pos, buf, state));
    let right_bottom;
    ([pos, right_bottom] = read_map_position(pos, buf, state));
    let orientation;

    let x = buf.readInt16LE(pos);
    pos += 2;
    let y = buf.readInt16LE(pos);
    pos += 2;

    return [pos, {
    left_top,
    right_bottom,
    orientation: { x, y },
    }];
    }

    function read_cliff_settings(pos, buf) {
    let name;
    ([pos, name] = read_string(pos, buf));
    let elevation_0 = buf.readFloatLE(pos);
    pos += 4;
    let elevation_interval = buf.readFloatLE(pos);
    pos += 4;
    let richness = buf.readFloatLE(pos);
    pos += 4;

    return [pos, {
    name,
    elevation_0,
    elevation_interval,
    richness,
    }];
    }

    function map_to_object(map) {
    let obj = {};
    for (let [key, value] of map) {
    obj[key] = value;
    }
    return obj;
    }

    function read_map_gen_settings(pos, buf) {
    const state = {
    last_position: { x: 0, y: 0 },
    };

    let terrain_segmentation = buf.readFloatLE(pos);
    pos += 4;
    let water = buf.readFloatLE(pos);
    pos += 4;
    let autoplace_controls;
    ([pos, autoplace_controls] = read_dict(pos, buf, read_string, read_frequency_size_richness));
    let autoplace_settings;
    ([pos, autoplace_settings] = read_dict(pos, buf, read_string, read_autoplace_setting));
    let default_enable_all_autoplace_controls = buf.readUInt8(pos) !== 0;
    pos += 1;
    let seed = buf.readUInt32LE(pos);
    pos += 4;
    let width = buf.readUInt32LE(pos);
    pos += 4;
    let height = buf.readUInt32LE(pos);
    pos += 4;
    let area_to_generate_at_start;
    ([pos, area_to_generate_at_start] = read_bounding_box(pos, buf, state));
    let starting_area = buf.readFloatLE(pos);
    pos += 4;
    let peaceful_mode = buf.readUInt8(pos) !== 0;
    pos += 1;
    let starting_points;
    ([pos, starting_points] = read_array(pos, buf, (pos, buf) => read_map_position(pos, buf, state)));
    let property_expression_names;
    ([pos, property_expression_names] = read_dict(pos, buf, read_string, read_string));
    let cliff_settings;
    ([pos, cliff_settings] = read_cliff_settings(pos, buf));

    return [pos, {
    terrain_segmentation,
    water,
    autoplace_controls: map_to_object(autoplace_controls),
    autoplace_settings: map_to_object(autoplace_settings),
    default_enable_all_autoplace_controls,
    seed,
    width,
    height,
    area_to_generate_at_start,
    starting_area,
    peaceful_mode,
    starting_points,
    property_expression_names: map_to_object(property_expression_names),
    cliff_settings,
    }];
    }

    function read_pollution(pos, buf) {
    let enabled;
    ([pos, enabled] = read_optional(pos, buf, read_bool));
    let diffision_ratio;
    ([pos, diffision_ratio] = read_optional(pos, buf, read_double));
    let min_to_diffuse;
    ([pos, min_to_diffuse] = read_optional(pos, buf, read_double));
    let ageing;
    ([pos, ageing] = read_optional(pos, buf, read_double));
    let expected_max_per_chunk;
    ([pos, expected_max_per_chunk] = read_optional(pos, buf, read_double));
    let min_to_show_per_chunk;
    ([pos, min_to_show_per_chunk] = read_optional(pos, buf, read_double));
    let min_pollution_to_damage_trees;
    ([pos, min_pollution_to_damage_trees] = read_optional(pos, buf, read_double));
    let pollution_with_max_forest_damage;
    ([pos, pollution_with_max_forest_damage] = read_optional(pos, buf, read_double));
    let pollution_per_tree_damage;
    ([pos, pollution_per_tree_damage] = read_optional(pos, buf, read_double));
    let pollution_restored_per_tree_damage;
    ([pos, pollution_restored_per_tree_damage] = read_optional(pos, buf, read_double));
    let max_pollution_to_restore_trees;
    ([pos, max_pollution_to_restore_trees] = read_optional(pos, buf, read_double));
    let enemy_attack_pollution_consumption_modifier;
    ([pos, enemy_attack_pollution_consumption_modifier] = read_optional(pos, buf, read_double));

    return [pos, {
    enabled,
    diffision_ratio,
    min_to_diffuse,
    ageing,
    expected_max_per_chunk,
    min_to_show_per_chunk,
    min_pollution_to_damage_trees,
    pollution_with_max_forest_damage,
    pollution_per_tree_damage,
    pollution_restored_per_tree_damage,
    max_pollution_to_restore_trees,
    enemy_attack_pollution_consumption_modifier,
    }];
    }

    function read_real_steering(pos, buf) {
    let radius;
    ([pos, radius] = read_optional(pos, buf, read_double));
    let separation_factor;
    ([pos, separation_factor] = read_optional(pos, buf, read_double));
    let separation_force;
    ([pos, separation_force] = read_optional(pos, buf, read_double));
    let force_unit_fuzzy_goto_behaviour;
    ([pos, force_unit_fuzzy_goto_behaviour] = read_optional(pos, buf, read_bool));

    return [pos, {
    radius,
    separation_factor,
    separation_force,
    force_unit_fuzzy_goto_behaviour,
    }];

    }

    function read_steering(pos, buf) {
    let default_;
    ([pos, default_] = read_real_steering(pos, buf));
    let moving
    ([pos, moving] = read_real_steering(pos, buf));

    return [pos, {
    default: default_,
    moving,
    }];
    }

    function read_enemy_evolution(pos, buf) {
    let enabled;
    ([pos, enabled] = read_optional(pos, buf, read_bool));
    let time_factor;
    ([pos, time_factor] = read_optional(pos, buf, read_double));
    let destroy_factor;
    ([pos, destroy_factor] = read_optional(pos, buf, read_double));
    let pollution_factor;
    ([pos, pollution_factor] = read_optional(pos, buf, read_double));

    return [pos, {
    enabled,
    time_factor,
    destroy_factor,
    pollution_factor,
    }];
    }

    function read_enemy_expansion(pos, buf) {
    let enabled;
    ([pos, enabled] = read_optional(pos, buf, read_bool));
    let max_expansion_distance;
    ([pos, max_expansion_distance] = read_optional(pos, buf, read_uint32));
    let friendly_base_influence_radius;
    ([pos, friendly_base_influence_radius] = read_optional(pos, buf, read_uint32));
    let enemy_building_influence_radius;
    ([pos, enemy_building_influence_radius] = read_optional(pos, buf, read_uint32));
    let building_coefficient;
    ([pos, building_coefficient] = read_optional(pos, buf, read_double));
    let other_base_coefficient;
    ([pos, other_base_coefficient] = read_optional(pos, buf, read_double));
    let neighbouring_chunk_coefficient;
    ([pos, neighbouring_chunk_coefficient] = read_optional(pos, buf, read_double));
    let neighbouring_base_chunk_coefficient;
    ([pos, neighbouring_base_chunk_coefficient] = read_optional(pos, buf, read_double));
    let max_colliding_tiles_coefficient;
    ([pos, max_colliding_tiles_coefficient] = read_optional(pos, buf, read_double));
    let settler_group_min_size;
    ([pos, settler_group_min_size] = read_optional(pos, buf, read_uint32));
    let settler_group_max_size;
    ([pos, settler_group_max_size] = read_optional(pos, buf, read_uint32));
    let min_expansion_cooldown;
    ([pos, min_expansion_cooldown] = read_optional(pos, buf, read_uint32));
    let max_expansion_cooldown;
    ([pos, max_expansion_cooldown] = read_optional(pos, buf, read_uint32));

    return [pos, {
    max_expansion_distance,
    friendly_base_influence_radius,
    enemy_building_influence_radius,
    building_coefficient,
    other_base_coefficient,
    neighbouring_chunk_coefficient,
    neighbouring_base_chunk_coefficient,
    max_colliding_tiles_coefficient,
    settler_group_min_size,
    settler_group_max_size,
    min_expansion_cooldown,
    max_expansion_cooldown,
    }];
    }

    function read_unit_group(pos, buf) {
    let min_group_gathering_time;
    ([pos, min_group_gathering_time] = read_optional(pos, buf, read_uint32));
    let max_group_gathering_time;
    ([pos, max_group_gathering_time] = read_optional(pos, buf, read_uint32));
    let max_wait_time_for_late_members;
    ([pos, max_wait_time_for_late_members] = read_optional(pos, buf, read_uint32));
    let max_group_radius;
    ([pos, max_group_radius] = read_optional(pos, buf, read_double));
    let min_group_radius;
    ([pos, min_group_radius] = read_optional(pos, buf, read_double));
    let max_member_speedup_when_behind;
    ([pos, max_member_speedup_when_behind] = read_optional(pos, buf, read_double));
    let max_member_slowdown_when_ahead;
    ([pos, max_member_slowdown_when_ahead] = read_optional(pos, buf, read_double));
    let max_group_slowdown_factor;
    ([pos, max_group_slowdown_factor] = read_optional(pos, buf, read_double));
    let max_group_member_fallback_factor;
    ([pos, max_group_member_fallback_factor] = read_optional(pos, buf, read_double));
    let member_disown_distance;
    ([pos, member_disown_distance] = read_optional(pos, buf, read_double));
    let tick_tolerance_when_member_arrives;
    ([pos, tick_tolerance_when_member_arrives] = read_optional(pos, buf, read_uint32));
    let max_gathering_unit_groups;
    ([pos, max_gathering_unit_groups] = read_optional(pos, buf, read_uint32));
    let max_unit_group_size;
    ([pos, max_unit_group_size] = read_optional(pos, buf, read_uint32));

    return [pos, {
    min_group_gathering_time,
    max_group_gathering_time,
    max_wait_time_for_late_members,
    max_group_radius,
    min_group_radius,
    max_member_speedup_when_behind,
    max_member_slowdown_when_ahead,
    max_group_slowdown_factor,
    max_group_member_fallback_factor,
    member_disown_distance,
    tick_tolerance_when_member_arrives,
    max_gathering_unit_groups,
    max_unit_group_size,
    }];
    }

    function read_path_finder(pos, buf) {
    let fwd2bwd_ratio;
    ([pos, fwd2bwd_ratio] = read_optional(pos, buf, read_int32));
    let goal_pressure_ratio;
    ([pos, goal_pressure_ratio] = read_optional(pos, buf, read_double));
    let use_path_cache;
    ([pos, use_path_cache] = read_optional(pos, buf, read_bool));
    let max_steps_worked_per_tick;
    ([pos, max_steps_worked_per_tick] = read_optional(pos, buf, read_double));
    let max_work_done_per_tick;
    ([pos, max_work_done_per_tick] = read_optional(pos, buf, read_uint32));
    let short_cache_size;
    ([pos, short_cache_size] = read_optional(pos, buf, read_uint32));
    let long_cache_size;
    ([pos, long_cache_size] = read_optional(pos, buf, read_uint32));
    let short_cache_min_cacheable_distance;
    ([pos, short_cache_min_cacheable_distance] = read_optional(pos, buf, read_double));
    let short_cache_min_algo_steps_to_cache;
    ([pos, short_cache_min_algo_steps_to_cache] = read_optional(pos, buf, read_uint32));
    let long_cache_min_cacheable_distance;
    ([pos, long_cache_min_cacheable_distance] = read_optional(pos, buf, read_double));
    let cache_max_connect_to_cache_steps_multiplier;
    ([pos, cache_max_connect_to_cache_steps_multiplier] = read_optional(pos, buf, read_uint32));
    let cache_accept_path_start_distance_ratio;
    ([pos, cache_accept_path_start_distance_ratio] = read_optional(pos, buf, read_double));
    let cache_accept_path_end_distance_ratio;
    ([pos, cache_accept_path_end_distance_ratio] = read_optional(pos, buf, read_double));
    let negative_cache_accept_path_start_distance_ratio;
    ([pos, negative_cache_accept_path_start_distance_ratio] = read_optional(pos, buf, read_double));
    let negative_cache_accept_path_end_distance_ratio;
    ([pos, negative_cache_accept_path_end_distance_ratio] = read_optional(pos, buf, read_double));
    let cache_path_start_distance_rating_multiplier;
    ([pos, cache_path_start_distance_rating_multiplier] = read_optional(pos, buf, read_double));
    let cache_path_end_distance_rating_multiplier;
    ([pos, cache_path_end_distance_rating_multiplier] = read_optional(pos, buf, read_double));
    let stale_enemy_with_same_destination_collision_penalty;
    ([pos, stale_enemy_with_same_destination_collision_penalty] = read_optional(pos, buf, read_double));
    let ignore_moving_enemy_collision_distance;
    ([pos, ignore_moving_enemy_collision_distance] = read_optional(pos, buf, read_double));
    let enemy_with_different_destination_collision_penalty;
    ([pos, enemy_with_different_destination_collision_penalty] = read_optional(pos, buf, read_double));
    let general_entity_collision_penalty;
    ([pos, general_entity_collision_penalty] = read_optional(pos, buf, read_double));
    let general_entity_subsequent_collision_penalty;
    ([pos, general_entity_subsequent_collision_penalty] = read_optional(pos, buf, read_double));
    let extended_collision_penalty;
    ([pos, extended_collision_penalty] = read_optional(pos, buf, read_double));
    let max_clients_to_accept_any_new_request;
    ([pos, max_clients_to_accept_any_new_request] = read_optional(pos, buf, read_uint32));
    let max_clients_to_accept_short_new_request;
    ([pos, max_clients_to_accept_short_new_request] = read_optional(pos, buf, read_uint32));
    let direct_distance_to_consider_short_request;
    ([pos, direct_distance_to_consider_short_request] = read_optional(pos, buf, read_uint32));
    let short_request_max_steps;
    ([pos, short_request_max_steps] = read_optional(pos, buf, read_uint32));
    let short_request_ratio;
    ([pos, short_request_ratio] = read_optional(pos, buf, read_double));
    let min_steps_to_check_path_find_termination;
    ([pos, min_steps_to_check_path_find_termination] = read_optional(pos, buf, read_uint32));
    let start_to_goal_cost_multiplier_to_terminate_path_find;
    ([pos, start_to_goal_cost_multiplier_to_terminate_path_find] = read_optional(pos, buf, read_double));
    let overload_levels;
    ([pos, overload_levels] = read_optional(pos, buf, (p, b) => read_array(p, b, read_uint32)));
    let oveload_multipliers;
    ([pos, oveload_multipliers] = read_optional(pos, buf, (p, b) => read_array(p, b, read_double)));
    let negative_path_cache_delay_interval;
    ([pos, negative_path_cache_delay_interval] = read_optional(pos, buf, read_uint32));

    return [pos, {
    fwd2bwd_ratio,
    goal_pressure_ratio,
    use_path_cache,
    max_steps_worked_per_tick,
    max_work_done_per_tick,
    short_cache_size,
    long_cache_size,
    short_cache_min_cacheable_distance,
    short_cache_min_algo_steps_to_cache,
    long_cache_min_cacheable_distance,
    cache_max_connect_to_cache_steps_multiplier,
    cache_accept_path_start_distance_ratio,
    cache_accept_path_end_distance_ratio,
    negative_cache_accept_path_start_distance_ratio,
    negative_cache_accept_path_end_distance_ratio,
    cache_path_start_distance_rating_multiplier,
    cache_path_end_distance_rating_multiplier,
    stale_enemy_with_same_destination_collision_penalty,
    ignore_moving_enemy_collision_distance,
    enemy_with_different_destination_collision_penalty,
    general_entity_collision_penalty,
    general_entity_subsequent_collision_penalty,
    extended_collision_penalty,
    max_clients_to_accept_any_new_request,
    max_clients_to_accept_short_new_request,
    direct_distance_to_consider_short_request,
    short_request_max_steps,
    short_request_ratio,
    min_steps_to_check_path_find_termination,
    start_to_goal_cost_multiplier_to_terminate_path_find,
    overload_levels,
    oveload_multipliers,
    negative_path_cache_delay_interval,
    }];
    }

    function read_difficulty(pos, buf) {
    let recipe_difficulty = buf.readUInt8(pos);
    pos += 1;
    let technology_difficulty = buf.readUInt8(pos);
    pos += 1;
    let technology_price_multiplier = buf.readDoubleLE(pos);
    pos += 8;
    let research_queue_setting = buf.readUInt8(pos);
    pos += 1;

    return [pos, {
    recipe_difficulty,
    technology_difficulty,
    technology_price_multiplier,
    research_queue_setting: ["always", "after-victory", "never"][research_queue_setting],
    }];
    }

    function read_map_settings(pos, buf) {
    let pollution;
    ([pos, pollution] = read_pollution(pos, buf));
    let steering;
    ([pos, steering] = read_steering(pos, buf));
    let enemy_evolution;
    ([pos, enemy_evolution] = read_enemy_evolution(pos, buf));
    let enemy_expansion;
    ([pos, enemy_expansion] = read_enemy_expansion(pos, buf));
    let unit_group;
    ([pos, unit_group] = read_unit_group(pos, buf));
    let path_finder;
    ([pos, path_finder] = read_path_finder(pos, buf));
    let max_failed_behavior_count = buf.readUInt32LE(pos);
    pos += 4;
    let difficulty;
    ([pos, difficulty] = read_difficulty(pos, buf));


    return [pos, {
    pollution,
    steering,
    enemy_evolution,
    enemy_expansion,
    unit_group,
    path_finder,
    max_failed_behavior_count,
    difficulty,
    }];
    }

    function decode(s) {
    s = s.trim();
    if (!/>>>[0-9a-zA-Z\/+\n]+<<</.test(s)) {
    return "Not a map exchange string";
    }

    s = s.slice(3, -3).replace(/\n/g, "");
    let buf = Buffer.from(s, "base64");
    buf = zlib.inflateSync(buf);

    let pos = 0;
    let version;
    ([pos, version] = read_version(pos, buf));
    pos += 1; // Discard unknown flag.
    let map_gen_settings;
    ([pos, map_gen_settings] = read_map_gen_settings(pos, buf));
    let map_settings;
    ([pos, map_settings] = read_map_settings(pos, buf));
    let checksum = buf.readUInt32LE(pos);
    pos += 4;

    if (pos != buf.length) {
    return "data after end";
    }

    return {
    version,
    map_gen_settings,
    map_settings,
    checksum,
    }
    }

    let test = `
    >>>eNp1Uz2IE1EQnsklmosoKVKcoGfkUhzChhCtgmSfNmJhIZhScLN5
    0YXNbm5/wDsLU1xxhSDINdpo64k2YmEXsPFAQbSyOzkLBcE7PeQKIb7
    Zt2+zxNzAzM77ZuabmfdYBITTIEV/trHRzGdN17ABBrrSgun2+9zTXI
    9TkoJnTS/scM217BTKCtzhvWWtbfiUzACGUShvea4jGYYJQ84PXCdOi
    5HA49wXY0SjEHIk9AzHCnuydkC4rMeXv65vDlbngXR0F8qjEanwtgQj
    KeAgYkGBxZKdM10n8Fxb83kQWM7NhhHebrQtw5/VatV6jWRxWkrX40s
    hd8zlRi+0A6tvW9zL16tRQe3kZEXPtfwg9PgEs3Zg3lT6WvVsJDnTtr
    pdgPKFVqt1kVZCxDulF5e+rKzrKBerstjZj5FhWyGXY+fBc3ZQKKtCc
    E45OzrK7r9TjmwaiBZxVp6NHRlcpSDi7q3ttVf7e038+3T345X2DR2v
    fS0u+We+0+xH6W0yiXn0kOS1WgUU55Yehz7r+P4dyQ8dZ6higczOega
    wdvUQYPGYOD65J0z5BKjRmoqmxLAbyR+1ybZyPumTe1QYnifyeTJvye
    QgoRSToXTZfYbslIoeH6eI+jqkZ+iMN9xUbd+k+k8MUvnvIdJ7TCAVN
    uUZCtSwk5hvM8k04j4/HFYn9phFdwmUtScweZI/o6SS3yLDkvhE9x4T
    LbLM2k9Y+AcYCOn/<<<`;
    console.log(util.inspect(decode(test), { depth: 20, colors: true }));