diff --git a/src/dec/valkyria/odn_archive_decoder.cc b/src/dec/valkyria/odn_archive_decoder.cc index c3f84ba6c..6f050baf4 100644 --- a/src/dec/valkyria/odn_archive_decoder.cc +++ b/src/dec/valkyria/odn_archive_decoder.cc @@ -21,6 +21,7 @@ #include "enc/png/png_image_encoder.h" #include "err.h" #include "io/memory_byte_stream.h" +#include "io/xor_byte_stream.h" using namespace au; using namespace au::dec::valkyria; @@ -30,9 +31,15 @@ namespace enum class OdnVariant : u8 { Variant1, + Variant1XOR, Variant2, Variant3, }; + + struct OdnArchiveMeta final : dec::ArchiveMeta + { + OdnVariant variant; + }; } // this is pathetic but their games actually hardcode this @@ -64,8 +71,11 @@ bool OdnArchiveDecoder::is_recognized_impl(io::File &input_file) const static OdnVariant guess_odn_variant(io::BaseByteStream &input_stream) { - if (input_stream.seek(8).read(8) == "00000000"_b) + const auto likely_file1_offset = input_stream.seek(8).read(8); + if (likely_file1_offset == "00000000"_b) return OdnVariant::Variant1; + else if (likely_file1_offset == "\xc7\xc6\xc5\xc4\xc3\xc2\xc1\xc0"_b) + return OdnVariant::Variant1XOR; const auto likely_file1_prefix = input_stream.seek(0).read(4); const auto maybe_file2_prefix1 = input_stream.seek(16).read(4); @@ -99,16 +109,17 @@ static void fill_sizes( last_entry->size = input_stream.size() - last_entry->offset; } -static std::unique_ptr read_meta_v1( +static void read_meta_v1( + dec::ArchiveMeta *meta, io::BaseByteStream &input_stream) { - auto meta = std::make_unique(); + input_stream.seek(0); while (true) { auto entry = std::make_unique(); entry->path = input_stream.read(8).str(); entry->offset = hex_to_int(input_stream.read(8)); - if (entry->path.str().substr(0, 4) == "END_") + if (entry->path == "END_ffff" || entry->path == "ffffffff") break; meta->entries.push_back(std::move(entry)); } @@ -118,13 +129,12 @@ static std::unique_ptr read_meta_v1( entry->offset += input_stream.pos(); } fill_sizes(input_stream, meta->entries); - return meta; } -static std::unique_ptr read_meta_v2( +static void read_meta_v2( + dec::ArchiveMeta* meta, io::BaseByteStream &input_stream) { - auto meta = std::make_unique(); const auto data_pos = hex_to_int(input_stream.seek(8).read(8)); input_stream.seek(0); while (input_stream.pos() < data_pos) @@ -135,13 +145,12 @@ static std::unique_ptr read_meta_v2( meta->entries.push_back(std::move(entry)); } fill_sizes(input_stream, meta->entries); - return meta; } -static std::unique_ptr read_meta_v3( +static void read_meta_v3( + dec::ArchiveMeta* meta, io::BaseByteStream &input_stream) { - auto meta = std::make_unique(); const auto data_pos = hex_to_int(input_stream.seek(8).read(8)); input_stream.seek(0); while (input_stream.pos() < data_pos) @@ -153,7 +162,6 @@ static std::unique_ptr read_meta_v3( meta->entries.push_back(std::move(entry)); } fill_sizes(input_stream, meta->entries); - return meta; } static bstr decompress(const bstr &input, const size_t chunk_size) @@ -222,18 +230,26 @@ static bstr decompress_bgra(const Logger &logger, const bstr &input) std::unique_ptr OdnArchiveDecoder::read_meta_impl( const Logger &logger, io::File &input_file) const { - const auto variant = guess_odn_variant(input_file.stream); - switch (variant) + auto meta = std::make_unique(); + meta->variant = guess_odn_variant(input_file.stream); + switch (meta->variant) { case OdnVariant::Variant1: - return read_meta_v1(input_file.stream); + read_meta_v1(meta.get(), input_file.stream); + break; + case OdnVariant::Variant1XOR: + read_meta_v1(meta.get(), io::XORByteStream(input_file.stream)); + break; case OdnVariant::Variant2: - return read_meta_v2(input_file.stream); + read_meta_v2(meta.get(), input_file.stream); + break; case OdnVariant::Variant3: - return read_meta_v3(input_file.stream); + read_meta_v3(meta.get(), input_file.stream); + break; default: throw std::logic_error("Invalid archive type"); } + return meta; } std::unique_ptr OdnArchiveDecoder::read_file_impl( @@ -242,9 +258,12 @@ std::unique_ptr OdnArchiveDecoder::read_file_impl( const dec::ArchiveMeta &m, const dec::ArchiveEntry &e) const { + const auto meta = static_cast(&m); const auto entry = static_cast(&e); const auto prefix = entry->path.str().substr(0, 4); - auto data = input_file.stream.seek(entry->offset).read(entry->size); + io::XORByteStream xorstream(input_file.stream); + io::BaseByteStream& stream = meta->variant == OdnVariant::Variant1XOR ? xorstream : input_file.stream; + auto data = stream.seek(entry->offset).read(entry->size); if (prefix == "back") data = decompress_bgr(logger, data); if (prefix == "codn" || prefix == "cccc") diff --git a/src/io/xor_byte_stream.cc b/src/io/xor_byte_stream.cc new file mode 100644 index 000000000..ba69c5474 --- /dev/null +++ b/src/io/xor_byte_stream.cc @@ -0,0 +1,79 @@ +// Copyright (C) 2016 by rr- +// +// This file is part of arc_unpacker. +// +// arc_unpacker is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// arc_unpacker is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with arc_unpacker. If not, see . + +#include "io/xor_byte_stream.h" +#include "err.h" + +using namespace au; +using namespace au::io; + +XORByteStream::XORByteStream(io::BaseByteStream& parent_stream) : + parent_stream(parent_stream.clone()) +{ +} + +XORByteStream::~XORByteStream() +{ +} + +void XORByteStream::seek_impl(const uoff_t offset) +{ + parent_stream->seek(offset); +} + +void XORByteStream::read_impl(void* destination, const size_t size) +{ + u8 k = key(); + for (size_t i = 0; i < size; i++) + reinterpret_cast(destination)[i] = parent_stream->read() ^ k--; +} + +void XORByteStream::write_impl(const void* source, const size_t size) +{ + u8 k = key(); + for (size_t i = 0; i < size; i++) + parent_stream->write(reinterpret_cast(source)[i] ^ k--); +} + +uoff_t XORByteStream::pos() const +{ + return parent_stream->pos(); +} + +uoff_t XORByteStream::size() const +{ + return parent_stream->size(); +} + +void XORByteStream::resize_impl(const uoff_t new_size) +{ + throw err::NotSupportedError("Not implemented"); +} + +std::unique_ptr XORByteStream::clone() const +{ + auto ret = std::make_unique(*parent_stream); + ret->seek(pos()); + return std::move(ret); +} + +u8 XORByteStream::key() +{ + // The key starts at 0xff and decrements for each byte in the file until it wraps around. + // We can quickly determine the starting value of our key from the position using modulo. + return static_cast(0xff - pos() % 0x100); +} diff --git a/src/io/xor_byte_stream.h b/src/io/xor_byte_stream.h new file mode 100644 index 000000000..273ab629e --- /dev/null +++ b/src/io/xor_byte_stream.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 by rr- +// +// This file is part of arc_unpacker. +// +// arc_unpacker is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// arc_unpacker is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with arc_unpacker. If not, see . + +#pragma once + +#include "io/base_byte_stream.h" + +namespace au { +namespace io { + + class XORByteStream final : public BaseByteStream + { + public: + XORByteStream(io::BaseByteStream& parent_stream); + ~XORByteStream(); + + uoff_t size() const override; + uoff_t pos() const override; + std::unique_ptr clone() const override; + + protected: + void read_impl(void* destination, const size_t size) override; + void write_impl(const void* source, const size_t size) override; + void seek_impl(const uoff_t offset) override; + void resize_impl(const uoff_t new_size) override; + + private: + u8 key(); + + std::unique_ptr parent_stream; + }; + +} }