Last active
March 10, 2016 19:04
-
-
Save hastinbe/19737b955629123a6e5e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Terraria.IO.WorldFile do | |
@moduledoc false | |
import Terraria.BinaryUtils | |
use Bitwise, only_operators: true | |
alias Terraria.IO.FileData | |
alias Terraria.IO.FileMetadata | |
alias Terraria.IO.WorldFileData | |
require IEx | |
@doc """ | |
Load a Terraria world file. | |
""" | |
def load_world(path, io_output \\ &IO.puts/1) do | |
world_fd = %WorldFileData{} | |
world_fd = %{world_fd | filedata: %FileData{path: path}} | |
io_output.("Loading world #{world_fd.filedata.path}") | |
case File.read(path) do | |
{:ok, binary} -> | |
try do | |
<< version :: int32, data :: binary >> = binary | |
loader = if version > 87, do: &load_world_v2/3, else: &load_world_v1/3 | |
{:ok, metadata} = loader.(data, version, path) | |
io_output.("#{to_string(metadata.filetype)} (ver #{version}, rev #{metadata.revision})") | |
if version < 141 do | |
{:ok, file_info} = :file.read_file_info(path) | |
ctime = file_info |> elem(6) | |
end | |
after | |
File.close(binary) | |
end | |
{:error, reason} -> | |
io_output.("Couldn't read #{path}: #{:file.format_error(reason)}") | |
end | |
end | |
@doc """ | |
Load a Terraria version 1 world file. | |
""" | |
@spec load_world_v1(Binary.t, Integer.t, String.t) :: {:ok, FileMetadata.t} | {:error, String.t} | |
def load_world_v1(data, version, path) do | |
{:error, "Not implemented"} | |
end | |
@doc """ | |
Load a Terraria version 2 world file. | |
""" | |
@spec load_world_v2(Binary.t, Integer.t, String.t) :: {:ok, FileMetadata.t} | {:error, String.t} | |
def load_world_v2(data, version, path) do | |
{:ok, metadata, rest} = parse_metadata(data) | |
if metadata.filetype != :world, do: {:error, "Not a world file"} | |
{:ok, {tile_frame_important, section_offsets}, rest} = parse_section_header(rest) | |
{:ok, _, rest} = parse_world_header(rest, version, path) | |
#IO.inspect section_offsets | |
#IO.inspect tile_frame_important | |
rest | |
|> load_tile_data | |
|> load_chest_data | |
|> load_sign_data | |
|> load_npc_data | |
|> load_mob_data version | |
#|> load_footer | |
{:ok, metadata} | |
end | |
@doc """ | |
Reads Re-Logic file metadata. | |
""" | |
@spec parse_metadata(Binary.t) :: {:ok, FileMetadata.t, Binary.t} | |
def parse_metadata(data) do | |
<< "relogic", | |
filetype :: int8, | |
revision :: uint32, | |
favorite :: int64, | |
rest :: binary >> = data | |
filetype = filetype |> Terraria.IO.FileType.filetype | |
is_favorite = ((favorite &&& 1) == 1) | |
metadata = %FileMetadata{ | |
filetype: filetype, | |
revision: revision, | |
favorite: is_favorite | |
} | |
{:ok, metadata, rest} | |
end | |
# | |
defp parse_section_header(data) do | |
<< num_offsets :: int16, | |
rest :: binary >> = data | |
{section_offsets, rest} = rest |> parse_section_offset([], num_offsets) | |
<< num_flags :: int16, | |
rest :: binary >> = rest | |
{tile_frame_important, rest} = read_bitarray(rest, [], 0, 128, num_flags) | |
IEx.pry | |
{:ok, {tile_frame_important, section_offsets}, rest} | |
end | |
# Reads the offsets for each section of the world file. They are not | |
# used currently because the entire file is read into memory. | |
defp parse_section_offset(data, offsets, n) when n < 1 do | |
{Enum.reverse(offsets), data} | |
end | |
defp parse_section_offset(data, offsets, n) do | |
<< offset :: int32, | |
rest :: binary >> = data | |
offsets = [ offset | offsets ] | |
parse_section_offset(rest, offsets, n - 1) | |
end | |
# | |
defp read_bitarray(data, bitmap, input, bitmask, n) when n < 1 do | |
{Enum.reverse(bitmap), data} | |
end | |
defp read_bitarray(data, bitmap, input, bitmask, n) do | |
if bitmask == 128 do | |
<< input :: int8, | |
data :: binary >> = data | |
bitmask = 1 | |
else | |
bitmask = bitmask <<< 1 | |
end | |
bitmap = [ ((input &&& bitmask) == bitmask) | bitmap ] | |
read_bitarray(data, bitmap, input, bitmask, n - 1) | |
end | |
# Read all the information about the world, apart from the | |
# world's assets. | |
defp parse_world_header(data, version, path) do | |
<< length :: int8, | |
world_name :: binary(length), | |
world_id :: int32, | |
world_left :: int32, | |
world_right :: int32, | |
world_top :: int32, | |
world_bottom :: int32, | |
world_size_x :: int32, | |
world_size_y :: int32, | |
rest :: binary >> = data | |
if version >= 112 do | |
<< expertmode :: uint8, | |
rest :: binary >> = rest | |
is_expertmode = ((expertmode &&& 1) == 1) | |
else | |
is_expertmode = false | |
end | |
if version >= 141 do | |
# A 64-bit signed integer that encodes the Kind property in a 2-bit field and the Ticks property in a 62-bit field | |
# Created: 8/29/2015 7:57:31 PM | |
# erlang format {{2015, 10, 15}, {17, 12, 0}} | |
# -8587607106338212178 = ticks | |
# 4611686018427387904 = if ticks > @ticks_ceiling - @ticks_per_day, do: ticks = ticks - @ticks_ceiling | |
# 2015-08-29 7:57:31 = 635764750510000000 | |
# | |
<< world_ctime :: int64, | |
rest :: binary >> = rest | |
else | |
{:ok, file_info} = :file.read_file_info(path) | |
world_ctime = file_info |> elem(6) | |
end | |
<< moon_type :: int8, | |
tree_x0 :: int32, | |
tree_x1 :: int32, | |
tree_x2 :: int32, | |
tree_style0 :: int32, | |
tree_style1 :: int32, | |
tree_style2 :: int32, | |
tree_style3 :: int32, | |
cave_back_x0 :: int32, | |
cave_back_x1 :: int32, | |
cave_back_x2 :: int32, | |
cave_back_style0 :: int32, | |
cave_back_style1 :: int32, | |
cave_back_style2 :: int32, | |
cave_back_style3 :: int32, | |
ice_back_style :: int32, | |
jungle_back_style :: int32, | |
hell_back_style :: int32, | |
spawn_x :: int32, | |
spawn_y :: int32, | |
ground_level :: float64, | |
rock_level :: float64, | |
time :: float64, | |
is_day_time :: bool, | |
moon_phase :: int32, | |
is_blood_moon :: bool, | |
is_eclipse :: bool, | |
dungeon_x :: int32, | |
dungeon_y :: int32, | |
is_crimson :: bool, | |
downed_boss1 :: bool, | |
downed_boss2 :: bool, | |
downed_boss3 :: bool, | |
downed_queenbee :: bool, | |
downed_mechboss1 :: bool, | |
downed_mechboss2 :: bool, | |
downed_mechboss3 :: bool, | |
downed_mechbossany :: bool, | |
downed_plantboss :: bool, | |
downed_golemboss :: bool, | |
rest :: binary >> = rest | |
if version >= 147 do | |
<< downed_slimekingboss :: bool, | |
rest :: binary >> = rest | |
end | |
<< saved_goblin :: bool, | |
saved_wizard :: bool, | |
saved_mech :: bool, | |
downed_goblins :: bool, | |
downed_clown :: bool, | |
downed_frost :: bool, | |
downed_pirates :: bool, | |
shadow_orb_smashed :: bool, | |
spawn_meteor :: int8, # byte | |
shadow_orb_count :: int8, # byte | |
altar_count :: int32, | |
hard_mode :: int8, # byte | |
invasion_delay :: int32, | |
invasion_size :: int32, | |
invasion_type :: int32, | |
invasion_x :: float64, | |
rest :: binary >> = rest | |
if version >= 147 do | |
<< slime_rain_time :: float64, | |
sundial_cooldown :: int8, # byte | |
rest :: binary >> = rest | |
end | |
<< temp_raining :: bool, | |
temp_rain_time :: int32, | |
temp_max_rain :: float32, | |
ore_tier1 :: int32, | |
ore_tier2 :: int32, | |
ore_tier3 :: int32, | |
bg_tree :: int8, # byte | |
bg_corruption :: int8, # byte | |
bg_jungle :: int8, # byte | |
bg_snow :: int8, # byte | |
bg_hallow :: int8, # byte | |
bg_crimson :: int8, # byte | |
bg_desert :: int8, # byte | |
bg_ocean :: int8, # byte | |
cloud_bg_active :: float32, | |
num_clouds :: int16, | |
wind_speed_set :: float32, | |
rest :: binary >> = rest | |
if version >= 95 do | |
<< num_anglers :: int32, | |
rest :: binary >> = rest | |
read_anglers = fn | |
n when n > 0 -> | |
anglers = for angler <- 1..n do | |
<< length :: int8, | |
angler :: binary(length), | |
rest :: binary >> = rest | |
angler | |
end | |
n -> [] | |
end | |
anglers = read_anglers.(num_anglers) | |
IO.puts "num_anglers=#{num_anglers}" | |
IO.inspect anglers | |
end | |
if version >= 99 do | |
<< saved_angler :: bool, | |
rest :: binary >> = rest | |
end | |
if version >= 101 do | |
<< angler_quest :: int32, | |
rest :: binary >> = rest | |
end | |
if version >= 104 do | |
<< saved_stylist :: bool, | |
saved_tax_collector :: bool, | |
invasion_size_start :: int32, | |
cultist_delay :: int32, | |
num_mobs :: int16, | |
rest :: binary >> = rest | |
read_killed_mobs = fn | |
n when n > 0 -> | |
mobs = for mob <- 1..n do | |
<< mob :: int32, | |
rest :: binary >> = rest | |
mob | |
end | |
n -> [] | |
end | |
killed_mobs = read_killed_mobs.(num_mobs) | |
<< fast_forward_time :: bool, | |
downed_fishron :: bool, | |
downed_martians :: bool, | |
downed_lunatic_cultist :: bool, | |
downed_moonlord :: bool, | |
downed_halloween_king :: bool, | |
downed_halloween_tree :: bool, | |
downed_christmas_queen :: bool, | |
downed_celestial_solar :: bool, | |
downed_celestial_vortex :: bool, | |
downed_celestial_nebula :: bool, | |
downed_celestial_stardust :: bool, | |
celestial_solar_active :: bool, | |
celestial_vortex_active :: bool, | |
celestial_nebula_active :: bool, | |
celestial_stardust_active :: bool, | |
apocalypse :: bool, | |
rest :: binary >> = rest | |
end | |
{:ok, nil, data} | |
end | |
defp load_tile_data(data), do: data | |
defp load_chest_data(data), do: data | |
defp load_sign_data(data), do: data | |
defp load_npc_data(data), do: data | |
defp load_mob_data(data, version) when version >= 140, do: data | |
defp load_mob_data(data, version), do: data | |
defp load_footer(data) do | |
<< valid :: bool, | |
length :: int8, | |
world_name :: binary(length), | |
world_id :: int32, | |
_ :: binary >> = data | |
true | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment