~saiko/mcrestool

5f3969d840915c68a0db63ba64eef72530edc1df — 2xsaiko a month ago 35480b2
rust is crab
79 files changed, 624 insertions(+), 2348 deletions(-)

M CMakeLists.txt
D cmake/CMakeCargo.cmake
D cmake/CMakeDetermineRustCompiler.cmake
D cmake/CMakeRustCompiler.cmake.in
D cmake/CMakeRustInformation.cmake
D cmake/CMakeTestRustCompiler.cmake
D cmake/CargoLink.cmake
D cmake/FindRust.cmake
D logic/CMakeLists.txt
D logic/Cargo.lock
D logic/Cargo.toml
D logic/build.rs
D logic/src/datasource/dir.rs
D logic/src/datasource/ffi.rs
D logic/src/datasource/mod.rs
D logic/src/datasource/resfile.rs
D logic/src/datasource/zip.rs
D logic/src/ffi.rs
D logic/src/ffihelper.rs
D logic/src/languagetable/ffi.rs
D logic/src/languagetable/mod.rs
D logic/src/lib.rs
D logic/src/restree/ffi.rs
D logic/src/restree/mod.rs
R ui/src/{identifier.cpp => ntifier.cpp}
R ui/src/{identifier.h => ntifier.h}
A src/languagetable/languagetable.cpp
A src/languagetable/languagetable.h
R ui/src/{main.cpp => n.cpp}
R ui/src/model/{languagetablemodel.cpp => guagetablemodel.cpp}
R ui/src/model/{languagetablemodel.h => guagetablemodel.h}
R ui/src/model/{resourcetree.cpp => ourcetree.cpp}
R ui/src/model/{resourcetree.h => ourcetree.h}
R ui/src/model/{treeitem.cpp => eitem.cpp}
R ui/src/model/{treeitem.h => eitem.h}
A src/project/languagetablecontainer.cpp
R ui/src/project/{languagetablecontainer.h => guagetablecontainer.h}
R ui/src/{result.h => ult.h}
R ui/src/{table.h => le.h}
R ui/src/ui/{geneditorwindow.cpp => editorwindow.cpp}
R ui/src/ui/{geneditorwindow.h => editorwindow.h}
R ui/src/ui/{itembutton.cpp => mbutton.cpp}
R ui/src/ui/{itembutton.h => mbutton.h}
R ui/src/ui/{itemselectiondialog.ui => mselectiondialog.ui}
R ui/src/ui/{languagetablewindow.cpp => guagetablewindow.cpp}
R ui/src/ui/{languagetablewindow.h => guagetablewindow.h}
R ui/src/ui/{languagetablewindow.ui => guagetablewindow.ui}
R ui/src/ui/{mainwindow.cpp => nwindow.cpp}
R ui/src/ui/{mainwindow.h => nwindow.h}
R ui/src/ui/{mainwindow.ui => nwindow.ui}
R ui/src/ui/{recipeeditextensionwidget.cpp => ipeeditextensionwidget.cpp}
R ui/src/ui/{recipeeditextensionwidget.h => ipeeditextensionwidget.h}
R ui/src/ui/{recipeeditwindow.cpp => ipeeditwindow.cpp}
R ui/src/ui/{recipeeditwindow.h => ipeeditwindow.h}
R ui/src/ui/{recipeeditwindow.ui => ipeeditwindow.ui}
R ui/src/ui/{shapedcraftingwidget.cpp => pedcraftingwidget.cpp}
R ui/src/ui/{shapedcraftingwidget.h => pedcraftingwidget.h}
R ui/src/ui/{shapedcraftingwidget.ui => pedcraftingwidget.ui}
R ui/src/ui/{smeltingwidget.cpp => ltingwidget.cpp}
R ui/src/ui/{smeltingwidget.h => ltingwidget.h}
R ui/src/ui/{smeltingwidget.ui => ltingwidget.ui}
R ui/src/ui/{tagedit.ui => edit.ui}
R ui/src/{util.h => l.h}
A src/workspace/direntry.h
A src/workspace/fsref.cpp
A src/workspace/fsref.h
A src/workspace/workspace.cpp
A src/workspace/workspace.h
D ui/CMakeLists.txt
D ui/src/except.h
D ui/src/fs/datasource.cpp
D ui/src/fs/datasource.h
D ui/src/fs/resfile.cpp
D ui/src/fs/resfile.h
D ui/src/project/languagetablecontainer.cpp
D ui/src/project/project.h
D ui/src/project/projectsource.cpp
D ui/src/project/projectsource.h
D ui/src/util.cpp
M CMakeLists.txt => CMakeLists.txt +68 -8
@@ 1,15 1,75 @@
cmake_minimum_required(VERSION 3.0)

project(mcrestool)

set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_AUTOUIC ON)

find_package(ECM REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})

include(KDEInstallDirs NO_POLICY_SCOPE)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDECMakeSettings NO_POLICY_SCOPE)

find_package(Qt5 CONFIG REQUIRED
        Core
        Widgets)

find_package(KF5 REQUIRED
        Archive)

find_package(QuaZip5 REQUIRED)

set(mcrestool_HEADERS
        src/model/languagetablemodel.h
        src/table.h
        src/identifier.h
        src/model/resourcetree.h
        src/ui/mainwindow.h
        src/ui/languagetablewindow.h
        src/ui/itembutton.h
        src/ui/recipeeditwindow.h
        src/ui/shapedcraftingwidget.h
        src/ui/recipeeditextensionwidget.h
        src/util.h
        src/result.h
        src/ui/smeltingwidget.h
        src/model/treeitem.h
        src/project/languagetablecontainer.h
        src/ui/geneditorwindow.h
        src/workspace/workspace.h
        src/workspace/fsref.h
        src/languagetable/languagetable.h
        src/workspace/direntry.h)

set(mcrestool_SRC
        src/main.cpp
        src/model/languagetablemodel.cpp
        src/identifier.cpp
        src/model/resourcetree.cpp
        src/ui/mainwindow.cpp
        src/ui/languagetablewindow.cpp
        src/ui/itembutton.cpp
        src/ui/recipeeditwindow.cpp
        src/ui/shapedcraftingwidget.cpp
        src/ui/recipeeditextensionwidget.cpp
        src/ui/smeltingwidget.cpp
        src/model/treeitem.cpp
        src/project/languagetablecontainer.cpp
        src/ui/geneditorwindow.cpp
        src/workspace/workspace.cpp
        src/workspace/fsref.cpp
        src/languagetable/languagetable.cpp)

enable_language(Rust)
include(CMakeCargo)
add_executable(mcrestool ${mcrestool_SRC})

set(LOGIC_BUILD_DIR ${CMAKE_BINARY_DIR}/logic)
include_directories(${LOGIC_BUILD_DIR})
set_property(TARGET mcrestool PROPERTY CXX_STANDARD 20)

add_subdirectory(logic)
add_subdirectory(ui)
target_link_libraries(mcrestool
        Qt5::Core
        Qt5::Widgets
        KF5::Archive
        ${QUAZIP_LIBRARIES})

install(TARGETS mcrestool DESTINATION bin)
\ No newline at end of file

D cmake/CMakeCargo.cmake => cmake/CMakeCargo.cmake +0 -71
@@ 1,71 0,0 @@
function(cargo_build)
    cmake_parse_arguments(CARGO "" "NAME" "" ${ARGN})
    string(REPLACE "-" "_" LIB_NAME ${CARGO_NAME})

    set(CARGO_TARGET_DIR ${CMAKE_CURRENT_BINARY_DIR})

    if(WIN32)
        if(CMAKE_SIZEOF_VOID_P EQUAL 8)
            set(LIB_TARGET "x86_64-pc-windows-msvc")
        else()
            set(LIB_TARGET "i686-pc-windows-msvc")
        endif()
	elseif(ANDROID)
        if(ANDROID_SYSROOT_ABI STREQUAL "x86")
            set(LIB_TARGET "i686-linux-android")
        elseif(ANDROID_SYSROOT_ABI STREQUAL "x86_64")
            set(LIB_TARGET "x86_64-linux-android")
        elseif(ANDROID_SYSROOT_ABI STREQUAL "arm")
            set(LIB_TARGET "arm-linux-androideabi")
        elseif(ANDROID_SYSROOT_ABI STREQUAL "arm64")
            set(LIB_TARGET "aarch64-linux-android")
        endif()
    elseif(IOS)
		set(LIB_TARGET "universal")
    elseif(CMAKE_SYSTEM_NAME STREQUAL Darwin)
        set(LIB_TARGET "x86_64-apple-darwin")
	else()
        if(CMAKE_SIZEOF_VOID_P EQUAL 8)
            set(LIB_TARGET "x86_64-unknown-linux-gnu")
        else()
            set(LIB_TARGET "i686-unknown-linux-gnu")
        endif()
    endif()

    if(NOT CMAKE_BUILD_TYPE)
        set(LIB_BUILD_TYPE "debug")
    elseif(${CMAKE_BUILD_TYPE} STREQUAL "Release")
        set(LIB_BUILD_TYPE "release")
    else()
        set(LIB_BUILD_TYPE "debug")
    endif()

#    set(LIB_FILE "${CARGO_TARGET_DIR}/${LIB_TARGET}/${LIB_BUILD_TYPE}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIB_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}")
    set(LIB_FILE "${CARGO_TARGET_DIR}/${LIB_TARGET}/${LIB_BUILD_TYPE}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}")

	if(IOS)
		set(CARGO_ARGS "lipo")
	else()
    	set(CARGO_ARGS "build")
		list(APPEND CARGO_ARGS "--target" ${LIB_TARGET})
	endif()

    if(${LIB_BUILD_TYPE} STREQUAL "release")
        list(APPEND CARGO_ARGS "--release")
    endif()

    file(GLOB_RECURSE LIB_SOURCES "*.rs")

    set(CARGO_ENV_COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${CARGO_TARGET_DIR}")

    add_custom_command(
        OUTPUT ${LIB_FILE}
        COMMAND ${CARGO_ENV_COMMAND} ${CARGO_EXECUTABLE} ARGS ${CARGO_ARGS}
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        DEPENDS ${LIB_SOURCES}
        COMMENT "running cargo")
    add_custom_target(${CARGO_NAME}_target ALL DEPENDS ${LIB_FILE})
    add_library(${CARGO_NAME} STATIC IMPORTED GLOBAL)
    add_dependencies(${CARGO_NAME} ${CARGO_NAME}_target)
    set_target_properties(${CARGO_NAME} PROPERTIES IMPORTED_LOCATION ${LIB_FILE})
endfunction()
\ No newline at end of file

D cmake/CMakeDetermineRustCompiler.cmake => cmake/CMakeDetermineRustCompiler.cmake +0 -25
@@ 1,25 0,0 @@

if(NOT CMAKE_Rust_COMPILER)
	find_package(Rust)
	if(RUST_FOUND)
		set(CMAKE_Rust_COMPILER "${RUSTC_EXECUTABLE}")
		set(CMAKE_Rust_COMPILER_ID "Rust")
		set(CMAKE_Rust_COMPILER_VERSION "${RUST_VERSION}")
		set(CMAKE_Rust_PLATFORM_ID "Rust")
	endif()
endif()

message(STATUS "Cargo Home: ${CARGO_HOME}")
message(STATUS "Rust Compiler Version: ${RUSTC_VERSION}")

mark_as_advanced(CMAKE_Rust_COMPILER)

if(CMAKE_Rust_COMPILER)
	set(CMAKE_Rust_COMPILER_LOADED 1)
endif(CMAKE_Rust_COMPILER)

configure_file(${CMAKE_CURRENT_LIST_DIR}/CMakeRustCompiler.cmake.in
	${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${CMAKE_VERSION}/CMakeRustCompiler.cmake IMMEDIATE @ONLY)

set(CMAKE_Rust_COMPILER_ENV_VAR "RUSTC")


D cmake/CMakeRustCompiler.cmake.in => cmake/CMakeRustCompiler.cmake.in +0 -15
@@ 1,15 0,0 @@

set(CMAKE_Rust_COMPILER "@CMAKE_Rust_COMPILER@")
set(CMAKE_Rust_COMPILER_ID "@CMAKE_Rust_COMPILER_ID@")
set(CMAKE_Rust_COMPILER_VERSION "@CMAKE_Rust_COMPILER_VERSION@")
set(CMAKE_Rust_COMPILER_LOADED @CMAKE_Rust_COMPILER_LOADED@)
set(CMAKE_Rust_PLATFORM_ID "@CMAKE_Rust_PLATFORM_ID@")

SET(CMAKE_Rust_SOURCE_FILE_EXTENSIONS rs)
SET(CMAKE_Rust_LINKER_PREFERENCE 40)
#SET(CMAKE_Rust_OUTPUT_EXTENSION_REPLACE 1)
SET(CMAKE_STATIC_LIBRARY_PREFIX_Rust "")
SET(CMAKE_STATIC_LIBRARY_SUFFIX_Rust .a)

set(CMAKE_Rust_COMPILER_ENV_VAR "RUSTC")


D cmake/CMakeRustInformation.cmake => cmake/CMakeRustInformation.cmake +0 -106
@@ 1,106 0,0 @@

# 
# Usage: rustc [OPTIONS] INPUT
# 
# Options:
#     -h --help           Display this message
#     --cfg SPEC          Configure the compilation environment
#     -L [KIND=]PATH      Add a directory to the library search path. The
#                         optional KIND can be one of dependency, crate, native,
#                         framework or all (the default).
#     -l [KIND=]NAME      Link the generated crate(s) to the specified native
#                         library NAME. The optional KIND can be one of static,
#                         dylib, or framework. If omitted, dylib is assumed.
#     --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|metadata]
#                         Comma separated list of types of crates for the
#                         compiler to emit
#     --crate-name NAME   Specify the name of the crate being built
#     --emit [asm|llvm-bc|llvm-ir|obj|link|dep-info]
#                         Comma separated list of types of output for the
#                         compiler to emit
#     --print [crate-name|file-names|sysroot|cfg|target-list|target-cpus|target-features|relocation-models|code-models]
#                         Comma separated list of compiler information to print
#                         on stdout
#     -g                  Equivalent to -C debuginfo=2
#     -O                  Equivalent to -C opt-level=2
#     -o FILENAME         Write output to <filename>
#     --out-dir DIR       Write output to compiler-chosen filename in <dir>
#     --explain OPT       Provide a detailed explanation of an error message
#     --test              Build a test harness
#     --target TARGET     Target triple for which the code is compiled
#     -W --warn OPT       Set lint warnings
#     -A --allow OPT      Set lint allowed
#     -D --deny OPT       Set lint denied
#     -F --forbid OPT     Set lint forbidden
#     --cap-lints LEVEL   Set the most restrictive lint level. More restrictive
#                         lints are capped at this level
#     -C --codegen OPT[=VALUE]
#                         Set a codegen option
#     -V --version        Print version info and exit
#     -v --verbose        Use verbose output
# 
# Additional help:
#     -C help             Print codegen options
#     -W help             Print 'lint' options and default settings
#     -Z help             Print internal options for debugging rustc
#     --help -v           Print the full set of options rustc accepts
# 

# <TARGET> <TARGET_BASE> <OBJECT> <OBJECTS> <LINK_LIBRARIES> <FLAGS> <LINK_FLAGS> <SOURCE> <SOURCES>

include(CMakeLanguageInformation)

if(UNIX)
	set(CMAKE_Rust_OUTPUT_EXTENSION .o)
else()
	set(CMAKE_Rust_OUTPUT_EXTENSION .obj)
endif()

set(CMAKE_Rust_ECHO_ALL "echo \"TARGET: <TARGET> TARGET_BASE: <TARGET_BASE> ")
set(CMAKE_Rust_ECHO_ALL "${CMAKE_Rust_ECHO_ALL} OBJECT: <OBJECT> OBJECTS: <OBJECTS> OBJECT_DIR: <OBJECT_DIR> SOURCE: <SOURCE> SOURCES: <SOURCES> ")
set(CMAKE_Rust_ECHO_ALL "${CMAKE_Rust_ECHO_ALL} LINK_LIBRARIES: <LINK_LIBRARIES> FLAGS: <FLAGS> LINK_FLAGS: <LINK_FLAGS> \"")

if(NOT CMAKE_Rust_CREATE_SHARED_LIBRARY)
	set(CMAKE_Rust_CREATE_SHARED_LIBRARY
		"echo \"CMAKE_Rust_CREATE_SHARED_LIBRARY\""
		"${CMAKE_Rust_ECHO_ALL}"
		)
endif()

if(NOT CMAKE_Rust_CREATE_SHARED_MODULE)
	set(CMAKE_Rust_CREATE_SHARED_MODULE
		"echo \"CMAKE_Rust_CREATE_SHARED_MODULE\""
		"${CMAKE_Rust_ECHO_ALL}"
		)
endif()

if(NOT CMAKE_Rust_CREATE_STATIC_LIBRARY)
	set(CMAKE_Rust_CREATE_STATIC_LIBRARY
		"echo \"CMAKE_Rust_CREATE_STATIC_LIBRARY\""
		"${CMAKE_Rust_ECHO_ALL}"
		)
endif()

if(NOT CMAKE_Rust_COMPILE_OBJECT)
	set(CMAKE_Rust_COMPILE_OBJECT
		"echo \"CMAKE_Rust_COMPILE_OBJECT\""
		"${CMAKE_Rust_ECHO_ALL}"
		"${CMAKE_Rust_COMPILER} --emit obj <SOURCE> -o <OBJECT>")
endif()

if(NOT CMAKE_Rust_LINK_EXECUTABLE)
	set(CMAKE_Rust_LINK_EXECUTABLE
		"echo \"CMAKE_Rust_LINK_EXECUTABLE\""
		"${CMAKE_Rust_ECHO_ALL}"
		)
endif()

mark_as_advanced(
	CMAKE_Rust_FLAGS
	CMAKE_Rust_FLAGS_DEBUG
	CMAKE_Rust_FLAGS_MINSIZEREL
	CMAKE_Rust_FLAGS_RELEASE
	CMAKE_Rust_FLAGS_RELWITHDEBINFO)

set(CMAKE_Rust_INFORMATION_LOADED 1)


D cmake/CMakeTestRustCompiler.cmake => cmake/CMakeTestRustCompiler.cmake +0 -3
@@ 1,3 0,0 @@

set(CMAKE_Rust_COMPILER_WORKS 1 CACHE INTERNAL "")


D cmake/CargoLink.cmake => cmake/CargoLink.cmake +0 -64
@@ 1,64 0,0 @@

function(cargo_print)
	execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${ARGN}")
endfunction()

function(cargo_link)
	cmake_parse_arguments(CARGO_LINK "" "NAME" "TARGETS;EXCLUDE" ${ARGN})

	file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cargo-link.c" "void cargo_link() {}")
	add_library(${CARGO_LINK_NAME} "${CMAKE_CURRENT_BINARY_DIR}/cargo-link.c")
	target_link_libraries(${CARGO_LINK_NAME} ${CARGO_LINK_TARGETS})

	get_target_property(LINK_LIBRARIES ${CARGO_LINK_NAME} LINK_LIBRARIES)

	foreach(LINK_LIBRARY ${LINK_LIBRARIES})
		get_target_property(_INTERFACE_LINK_LIBRARIES ${LINK_LIBRARY} INTERFACE_LINK_LIBRARIES)
		list(APPEND LINK_LIBRARIES ${_INTERFACE_LINK_LIBRARIES})
	endforeach()
	list(REMOVE_DUPLICATES LINK_LIBRARIES)

	if(CARGO_LINK_EXCLUDE)
		list(REMOVE_ITEM LINK_LIBRARIES ${CARGO_LINK_EXCLUDE})
	endif()

	set(LINK_DIRECTORIES "")
	foreach(LINK_LIBRARY ${LINK_LIBRARIES})
		if(TARGET ${LINK_LIBRARY})
			get_target_property(_IMPORTED_CONFIGURATIONS ${LINK_LIBRARY} IMPORTED_CONFIGURATIONS)
			list(FIND _IMPORTED_CONFIGURATIONS "RELEASE" _IMPORTED_CONFIGURATION_INDEX)
			if (NOT (${_IMPORTED_CONFIGURATION_INDEX} GREATER -1))
				set(_IMPORTED_CONFIGURATION_INDEX 0)
			endif()
			list(GET _IMPORTED_CONFIGURATIONS ${_IMPORTED_CONFIGURATION_INDEX} _IMPORTED_CONFIGURATION)
			get_target_property(_IMPORTED_LOCATION ${LINK_LIBRARY} "IMPORTED_LOCATION_${_IMPORTED_CONFIGURATION}")
			get_filename_component(_IMPORTED_DIR ${_IMPORTED_LOCATION} DIRECTORY)
			get_filename_component(_IMPORTED_NAME ${_IMPORTED_LOCATION} NAME_WE)
			if(NOT WIN32)
				string(REGEX REPLACE "^lib" "" _IMPORTED_NAME ${_IMPORTED_NAME})
			endif()
			list(APPEND LINK_DIRECTORIES ${_IMPORTED_DIR})
			cargo_print("cargo:rustc-link-lib=static=${_IMPORTED_NAME}")
		else()
			if("${LINK_LIBRARY}" MATCHES "^.*/(.+)\\.framework$")
				set(FRAMEWORK_NAME ${CMAKE_MATCH_1})
				cargo_print("cargo:rustc-link-lib=framework=${FRAMEWORK_NAME}")
			elseif("${LINK_LIBRARY}" MATCHES "^(.*)/(.+)\\.so$")
				set(LIBRARY_DIR ${CMAKE_MATCH_1})
				set(LIBRARY_NAME ${CMAKE_MATCH_2})
				if(NOT WIN32)
					string(REGEX REPLACE "^lib" "" LIBRARY_NAME ${LIBRARY_NAME})
				endif()
				list(APPEND LINK_DIRECTORIES ${LIBRARY_DIR})
				cargo_print("cargo:rustc-link-lib=${LIBRARY_NAME}")
			else()
				cargo_print("cargo:rustc-link-lib=${LINK_LIBRARY}")
			endif()
		endif()
	endforeach()
	list(REMOVE_DUPLICATES LINK_DIRECTORIES)

	foreach(LINK_DIRECTORY ${LINK_DIRECTORIES})
		cargo_print("cargo:rustc-link-search=native=${LINK_DIRECTORY}")
	endforeach()
endfunction()

D cmake/FindRust.cmake => cmake/FindRust.cmake +0 -73
@@ 1,73 0,0 @@

set(_CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ${CMAKE_FIND_ROOT_PATH_MODE_PROGRAM})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)
set(_CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ${CMAKE_FIND_ROOT_PATH_MODE_INCLUDE})
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)

if(CMAKE_HOST_WIN32)
	set(USER_HOME "$ENV{USERPROFILE}")
else()
	set(USER_HOME "$ENV{HOME}")
endif()

if(NOT DEFINED CARGO_HOME)
	if("$ENV{CARGO_HOME}" STREQUAL "")
		set(CARGO_HOME "${USER_HOME}/.cargo")
	else()
		set(CARGO_HOME "$ENV{CARGO_HOME}")
	endif()
endif()

# Find cargo executable
find_program(CARGO_EXECUTABLE cargo
	HINTS "${CARGO_HOME}"
	PATH_SUFFIXES "bin")
mark_as_advanced(CARGO_EXECUTABLE)

# Find rustc executable
find_program(RUSTC_EXECUTABLE rustc
	HINTS "${CARGO_HOME}"
	PATH_SUFFIXES "bin")
mark_as_advanced(RUSTC_EXECUTABLE)

# Find rustdoc executable
find_program(RUSTDOC_EXECUTABLE rustdoc
	HINTS "${CARGO_HOME}"
	PATH_SUFFIXES "bin")
mark_as_advanced(RUSTDOC_EXECUTABLE)

# Find rust-gdb executable
find_program(RUST_GDB_EXECUTABLE rust-gdb
	HINTS "${CARGO_HOME}"
	PATH_SUFFIXES "bin")
mark_as_advanced(RUST_GDB_EXECUTABLE)

# Find rust-lldb executable
find_program(RUST_LLDB_EXECUTABLE rust-lldb
	HINTS "${CARGO_HOME}"
	PATH_SUFFIXES "bin")
mark_as_advanced(RUST_LLDB_EXECUTABLE)

# Find rustup executable
find_program(RUSTUP_EXECUTABLE rustup
	HINTS "${CARGO_HOME}"
	PATH_SUFFIXES "bin")
mark_as_advanced(RUSTUP_EXECUTABLE)

set(RUST_FOUND FALSE CACHE INTERNAL "")

if(CARGO_EXECUTABLE AND RUSTC_EXECUTABLE AND RUSTDOC_EXECUTABLE)
	set(RUST_FOUND TRUE CACHE INTERNAL "")

	set(CARGO_HOME "${CARGO_HOME}" CACHE PATH "Rust Cargo Home")

	execute_process(COMMAND ${RUSTC_EXECUTABLE} --version OUTPUT_VARIABLE RUSTC_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
	string(REGEX REPLACE "rustc ([^ ]+) .*" "\\1" RUSTC_VERSION "${RUSTC_VERSION}")
endif()

if(NOT RUST_FOUND)
	message(FATAL_ERROR "Could not find Rust!")
endif()

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ${_CMAKE_FIND_ROOT_PATH_MODE_PROGRAM})
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ${_CMAKE_FIND_ROOT_PATH_MODE_INCLUDE})

D logic/CMakeLists.txt => logic/CMakeLists.txt +0 -1
@@ 1,1 0,0 @@
cargo_build(NAME mcrestool_logic)

D logic/Cargo.lock => logic/Cargo.lock +0 -423
@@ 1,423 0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "adler32"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "bzip2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "bzip2-sys 0.1.8+1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "bzip2-sys"
version = "0.1.8+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "cbindgen"
version = "0.13.1"
source = "git+https://github.com/eqrion/cbindgen?rev=fc577f2a28898b5345ed431fb172c4ede84fa2bf#fc577f2a28898b5345ed431fb172c4ede84fa2bf"
dependencies = [
 "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
 "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
 "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
 "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "cc"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "clap"
version = "2.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
 "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
 "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "flate2"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
 "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "getrandom"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "hermit-abi"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "itoa"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "libc"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "mcrestool_logic"
version = "0.1.0"
dependencies = [
 "cbindgen 0.13.1 (git+https://github.com/eqrion/cbindgen?rev=fc577f2a28898b5345ed431fb172c4ede84fa2bf)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
 "zip 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "miniz_oxide"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "podio"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "ppv-lite86"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "proc-macro2"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "quote"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "remove_dir_all"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "ryu"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "serde"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "serde_derive"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
 "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "serde_json"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
 "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "syn"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
 "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "tempfile"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
 "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "time"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "toml"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "zip"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
 "podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
 "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]

[metadata]
"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b"
"checksum bzip2-sys 0.1.8+1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "05305b41c5034ff0e93937ac64133d109b5a2660114ec45e9760bc6816d83038"
"checksum cbindgen 0.13.1 (git+https://github.com/eqrion/cbindgen?rev=fc577f2a28898b5345ed431fb172c4ede84fa2bf)" = "<none>"
"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
"checksum flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f"
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5"
"checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
"checksum serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum zip 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6df134e83b8f0f8153a094c7b0fd79dfebe437f1d76e7715afa18ed95ebe2fd7"

D logic/Cargo.toml => logic/Cargo.toml +0 -22
@@ 1,22 0,0 @@
[package]
name = "mcrestool_logic"
version = "0.1.0"
authors = ["2xsaiko <git@dblsaiko.net>"]
edition = "2018"
build = "build.rs"

[profile.release]
lto = true

[lib]
crate-type = ["cdylib"]

[dependencies]
libc = "0.2.67"
zip = "0.5.4"
serde = "1.0.104"
serde_json = "1.0.48"

[build-dependencies]
#cbindgen = "0.13.1"
cbindgen = { git = "https://github.com/eqrion/cbindgen", rev = "fc577f2a28898b5345ed431fb172c4ede84fa2bf" }
\ No newline at end of file

D logic/build.rs => logic/build.rs +0 -42
@@ 1,42 0,0 @@
extern crate cbindgen;

use std::env;
use std::path::PathBuf;

use cbindgen::{Config, Language, EnumConfig, RenameRule};

fn main() {
    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

    let package_name = env::var("CARGO_PKG_NAME").unwrap();
    let output_file = target_dir()
        .join(format!("{}.h", package_name))
        .display()
        .to_string();

    let config = Config {
        include_guard: Some("MCRESTOOL_MCRESTOOL_LOGIC_H".to_owned()),
        language: Language::C,
        cpp_compat: true,
        enumeration: EnumConfig {
            rename_variants: Some(RenameRule::QualifiedScreamingSnakeCase),
            ..Default::default()
        },
        ..Default::default()
    };

    cbindgen::generate_with_config(&crate_dir, config)
        .unwrap()
        .write_to_file(&output_file);
}

/// Find the location of the `target/` directory. Note that this may be
/// overridden by `cmake`, so we also need to check the `CARGO_TARGET_DIR`
/// variable.
fn target_dir() -> PathBuf {
    if let Ok(target) = env::var("CARGO_TARGET_DIR") {
        PathBuf::from(target)
    } else {
        PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("target")
    }
}
\ No newline at end of file

D logic/src/datasource/dir.rs => logic/src/datasource/dir.rs +0 -70
@@ 1,70 0,0 @@
use std::{fs, io};
use std::fs::{File, OpenOptions};
use std::path::{Path, PathBuf};

use crate::datasource::normalize_path;

pub struct DirDataSource {
    dir: PathBuf,
}

impl DirDataSource {
    pub fn new(dir: impl AsRef<Path>) -> Result<Self, io::Error> {
        match fs::read_dir(&dir) {
            Err(e) => Err(e),
            Ok(_) => Ok(DirDataSource {
                dir: dir.as_ref().to_path_buf(),
            })
        }
    }

    pub fn open(&self, path: impl AsRef<Path>, opts: OpenOptions) -> Result<File, Error> {
        Ok(opts.open(self.get_full_path(path)?)?)
    }

    pub fn list_dir(&self, path: impl AsRef<Path>) -> Result<Vec<PathBuf>, Error> {
        let result = fs::read_dir(self.get_full_path(path)?)?;
        Ok(result
            .filter_map(|e| e.ok())
            .map(|e| e.path().strip_prefix(&self.dir).unwrap().to_path_buf())
            .collect())
    }

    pub fn create_dir(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        Ok(fs::create_dir(self.get_full_path(path)?)?)
    }

    pub fn create_dir_all(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        Ok(fs::create_dir_all(self.get_full_path(path)?)?)
    }

    pub fn delete_file(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        Ok(fs::remove_file(self.get_full_path(path)?)?)
    }

    pub fn delete_dir(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        Ok(fs::remove_dir(self.get_full_path(path)?)?)
    }

    pub fn delete_dir_all(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        Ok(fs::remove_dir_all(self.get_full_path(path)?)?)
    }

    fn get_full_path(&self, path: impl AsRef<Path>) -> Result<PathBuf, Error> {
        let buf = self.dir.join(normalize_path(&path).ok_or_else(|| Error::InvalidPath(path.as_ref().to_path_buf()))?.strip_prefix("/").unwrap());
        println!("{}", buf.to_str().unwrap());
        Ok(buf)
    }
}

pub enum Error {
    RootDirNotFound(io::Error),
    InvalidPath(PathBuf),
    Io(io::Error),
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self {
        Error::Io(err)
    }
}
\ No newline at end of file

D logic/src/datasource/ffi.rs => logic/src/datasource/ffi.rs +0 -152
@@ 1,152 0,0 @@
#![allow(clippy::missing_safety_doc)]

use std::{mem, slice};
use std::ffi::{CStr, CString};
use std::io::{Read, Write};
use std::os::raw::{c_char, c_void};
use std::ptr::{drop_in_place, null, null_mut};

use crate::datasource::{DataSource, OpenOptions};
use crate::datasource::dir::DirDataSource;
use crate::datasource::resfile::ResFile;
use crate::datasource::zip::ZipDataSource;
use crate::ffihelper::*;

#[no_mangle]
#[repr(u8)]
pub enum DataSourceType {
    Dir,
    Zip,
}

#[no_mangle]
pub unsafe extern "C" fn datasource_dir_create(path: *const c_char) -> *mut DataSource {
    clear_error();
    if path.is_null() { return null_mut(); }
    let raw = CStr::from_ptr(path);

    let ds = DataSource::Dir(try_ffi!(DirDataSource::new(raw.to_str().unwrap())));

    Box::into_raw(Box::new(ds))
}

#[no_mangle]
pub unsafe extern "C" fn datasource_zip_create(path: *const c_char) -> *mut DataSource {
    clear_error();
    if path.is_null() { return null_mut(); }
    let raw = CStr::from_ptr(path);

    let ds = DataSource::Zip(try_ffi!(ZipDataSource::new(raw.to_str().unwrap())));

    Box::into_raw(Box::new(ds))
}

#[no_mangle]
pub extern "C" fn datasource_type(ds: &mut DataSource) -> DataSourceType {
    match ds {
        DataSource::Dir(_) => DataSourceType::Dir,
        DataSource::Zip(_) => DataSourceType::Zip,
    }
}

#[no_mangle]
pub unsafe extern "C" fn datasource_open_file(ds: &mut DataSource, path: *const c_char, opts: OpenOptions) -> *mut ResFile {
    clear_error();
    if path.is_null() { return null_mut(); }
    let raw = CStr::from_ptr(path);

    let file = try_ffi!(ds.open(raw.to_str().unwrap(), opts));

    Box::into_raw(Box::new(file))
}

#[no_mangle]
pub unsafe extern "C" fn datasource_list_dir(ds: &mut DataSource, path: *const c_char) -> *const *const c_char {
    clear_error();
    if path.is_null() { return null_mut(); }
    let raw = CStr::from_ptr(path);

    let mut dir_list = try_ffi!(ds.list_dir(raw.to_str().unwrap())).into_iter()
        .map(|s| CString::new(&*s.file_name().unwrap().to_str().unwrap()).expect("Invalid 0-char in file name"))
        .collect::<Vec<_>>();

    dir_list.shrink_to_fit();

    let mut p_dir_list = dir_list.into_iter()
        .map(|arg| {
            let ptr = arg.as_ptr();
            mem::forget(arg);
            ptr
        })
        .collect::<Vec<_>>();

    p_dir_list.push(null());
    p_dir_list.shrink_to_fit();

    let ptr = p_dir_list.as_ptr();
    mem::forget(p_dir_list);
    ptr
}

#[no_mangle]
pub unsafe extern "C" fn dirlist_delete(dirlist: *const *const c_char) {
    if dirlist.is_null() { return; }
    let mut len = 1; // start at 1 to include the 0-ptr at the end
    let mut aptr = dirlist;
    while !aptr.is_null() {
        drop(CString::from_raw(*aptr as *mut c_char));
        aptr = aptr.add(1);
        len += 1;
    }
    drop(Vec::from_raw_parts(aptr as *mut *const i8, len, len));
}

#[no_mangle]
pub unsafe extern "C" fn datasource_delete_file(ds: &mut DataSource, path: *const c_char) -> bool {
    clear_error();
    if path.is_null() { return false; }
    let raw = CStr::from_ptr(path);

    try_ffi!(ds.delete_file(raw.to_str().unwrap()), false);

    true
}

#[no_mangle]
pub unsafe extern "C" fn datasource_delete(ds: *mut DataSource) {
    if ds.is_null() { return; }
    drop_in_place(ds);
}

#[no_mangle]
pub unsafe extern "C" fn resfile_write(data: *const c_void, len: usize, file: &mut ResFile) -> usize {
    clear_error();

    let slice = slice::from_raw_parts(data as *const u8, len);

    try_ffi!(file.write(slice), 0)
}

#[no_mangle]
pub unsafe extern "C" fn resfile_read(data: *mut c_void, len: usize, file: &mut ResFile) -> usize {
    clear_error();

    let slice = slice::from_raw_parts_mut(data as *mut u8, len);

    try_ffi!(file.read(slice), 0)
}

#[no_mangle]
pub unsafe extern "C" fn resfile_flush(file: &mut ResFile) {
    clear_error();

    if let Err(e) = file.flush() {
        set_error_from(e);
    }
}

#[no_mangle]
pub unsafe extern "C" fn resfile_close(file: *mut ResFile) {
    if file.is_null() { return; }
    drop_in_place(file);
}

D logic/src/datasource/mod.rs => logic/src/datasource/mod.rs +0 -213
@@ 1,213 0,0 @@
use std::fs;
use std::io::{Cursor, ErrorKind};
use std::path::{Component, Path, PathBuf};

use ::zip::result::ZipError;

use crate::datasource::dir::DirDataSource;
use crate::datasource::resfile::ResFile;
use crate::datasource::zip::ZipDataSource;
use crate::ffi::McrtError;
use crate::ffihelper::FfiError;

pub mod dir;
pub mod zip;
pub mod resfile;

pub mod ffi;

pub enum DataSource {
    Dir(DirDataSource),
    Zip(ZipDataSource),
}

impl DataSource {
    pub fn open(&self, path: impl AsRef<Path>, opts: OpenOptions) -> Result<ResFile, Error> {
        match self {
            DataSource::Dir(ds) => {
                Ok(ResFile::File(ds.open(path, opts.into())?))
            }
            DataSource::Zip(ds) => {
                if opts.write {
                    Err(Error::PermissionDenied)
                } else {
                    let result: Result<Vec<u8>, Error> = ds.open(path).map_err(|e| e.into())
                        .map_err(|e| match e {
                            Error::NotFound if opts.create => Error::ReadOnly,
                            x => x
                        });
                    Ok(ResFile::ZipEntry(Cursor::new(result?)))
                }
            }
        }
    }

    pub fn create_dir(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        match self {
            DataSource::Dir(ds) => Ok(ds.create_dir(path)?),
            DataSource::Zip(_) => Err(Error::ReadOnly),
        }
    }

    pub fn create_dir_all(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        match self {
            DataSource::Dir(ds) => Ok(ds.create_dir_all(path)?),
            DataSource::Zip(_) => Err(Error::ReadOnly),
        }
    }

    pub fn delete_file(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        match self {
            DataSource::Dir(ds) => Ok(ds.delete_file(path)?),
            DataSource::Zip(_) => Err(Error::ReadOnly),
        }
    }

    pub fn delete_dir(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        match self {
            DataSource::Dir(ds) => Ok(ds.delete_dir(path)?),
            DataSource::Zip(_) => Err(Error::ReadOnly),
        }
    }

    pub fn delete_dir_all(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        match self {
            DataSource::Dir(ds) => Ok(ds.delete_dir_all(path)?),
            DataSource::Zip(_) => Err(Error::ReadOnly),
        }
    }

    pub fn list_dir(&self, path: impl AsRef<Path>) -> Result<Vec<PathBuf>, Error> {
        match self {
            DataSource::Dir(ds) => Ok(ds.list_dir(path)?),
            DataSource::Zip(ds) => Ok(ds.list_dir(path)?),
        }
    }
}

pub enum Error {
    InvalidPath(PathBuf),
    NotFound,
    PermissionDenied,
    ReadOnly,
    Io,
}

impl From<dir::Error> for Error {
    fn from(err: dir::Error) -> Self {
        match err {
            dir::Error::RootDirNotFound(_) => unreachable!(),
            dir::Error::InvalidPath(p) => Error::InvalidPath(p),
            dir::Error::Io(e) => {
                match e.kind() {
                    ErrorKind::NotFound => Error::NotFound,
                    ErrorKind::PermissionDenied => Error::PermissionDenied,
                    _ => Error::Io
                }
            }
        }
    }
}

impl From<zip::Error> for Error {
    fn from(err: zip::Error) -> Self {
        match err {
            zip::Error::Zip(ZipError::FileNotFound) => Error::NotFound,
            zip::Error::Zip(ZipError::InvalidArchive(_)) |
            zip::Error::Zip(ZipError::UnsupportedArchive(_)) => Error::Io,
            zip::Error::Zip(ZipError::Io(e)) => {
                match e.kind() {
                    ErrorKind::NotFound => Error::NotFound,
                    ErrorKind::PermissionDenied => Error::PermissionDenied,
                    _ => Error::Io
                }
            }
            zip::Error::Io(_) => unreachable!(),
            zip::Error::InvalidPath(p) => Error::InvalidPath(p)
        }
    }
}

impl FfiError for Error {
    fn kind(&self) -> McrtError {
        match self {
            Error::InvalidPath(_) => McrtError::NotFound,
            Error::NotFound => McrtError::NotFound,
            Error::PermissionDenied => McrtError::PermissionDenied,
            Error::ReadOnly => McrtError::ReadOnly,
            Error::Io => McrtError::Io,
        }
    }
}

pub fn normalize_path(path: impl AsRef<Path>) -> Option<PathBuf> {
    let mut pb = PathBuf::from("/");
    for c in path.as_ref().components() {
        match c {
            Component::Prefix(_) => return None,
            Component::RootDir => {}
            Component::CurDir => {}
            Component::ParentDir => {
                pb.pop();
            }
            Component::Normal(s) => pb.push(s),
        }
    }
    Some(pb)
}

#[repr(C)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct OpenOptions {
    read: bool,
    write: bool,
    create: bool,
}

impl Default for OpenOptions {
    fn default() -> Self {
        OpenOptions::reading()
    }
}

impl OpenOptions {
    pub fn reading() -> OpenOptions {
        OpenOptions { read: true, write: false, create: false }
    }

    pub fn writing(create: bool) -> OpenOptions {
        OpenOptions { read: false, write: true, create }
    }

    pub fn read(&mut self, read: bool) -> &mut OpenOptions {
        self.read = read;
        self
    }

    pub fn write(&mut self, write: bool) -> &mut OpenOptions {
        self.write = write;
        self
    }

    pub fn create(&mut self, create: bool) -> &mut OpenOptions {
        self.create = create;
        self
    }
}

impl Into<fs::OpenOptions> for OpenOptions {
    fn into(self) -> fs::OpenOptions {
        let mut options = fs::OpenOptions::new();
        options.read(self.read);
        options.write(self.write);
        options.create(self.create);
        options
    }
}

pub struct DirEntry {
    pub is_file: bool,
    pub is_dir: bool,
    pub is_symlink: bool,
    pub name: String,
}
\ No newline at end of file

D logic/src/datasource/resfile.rs => logic/src/datasource/resfile.rs +0 -80
@@ 1,80 0,0 @@
use std::fs::File;
use std::io::{Cursor, ErrorKind, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
use std::io;

pub enum ResFile {
    File(File),
    ZipEntry(Cursor<Vec<u8>>),
}

impl Read for ResFile {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match self {
            ResFile::File(inner) => inner.read(buf),
            ResFile::ZipEntry(inner) => inner.read(buf),
        }
    }

    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
        match self {
            ResFile::File(inner) => inner.read_vectored(bufs),
            ResFile::ZipEntry(inner) => inner.read_vectored(bufs),
        }
    }

    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
        match self {
            ResFile::File(inner) => inner.read_exact(buf),
            ResFile::ZipEntry(inner) => inner.read_exact(buf),
        }
    }
}

impl Write for ResFile {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match self {
            ResFile::File(inner) => inner.write(buf),
            ResFile::ZipEntry(_) => Err(io::Error::new(ErrorKind::Other, "unsupported write")),
        }
    }

    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
        match self {
            ResFile::File(inner) => inner.write_vectored(bufs),
            ResFile::ZipEntry(_) => Err(io::Error::new(ErrorKind::Other, "unsupported write")),
        }
    }


    fn flush(&mut self) -> io::Result<()> {
        match self {
            ResFile::File(inner) => inner.flush(),
            ResFile::ZipEntry(_) => Err(io::Error::new(ErrorKind::Other, "unsupported write")),
        }
    }
}

impl Seek for ResFile {
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
        match self {
            ResFile::File(inner) => inner.seek(pos),
            ResFile::ZipEntry(inner) => inner.seek(pos),
        }
    }

    #[cfg(feature = "seek_convenience")]
    fn stream_len(&mut self) -> io::Result<u64> {
        match self {
            ResFile::File(inner) => inner.stream_len(),
            ResFile::ZipEntry(inner) => inner.stream_len(),
        }
    }

    #[cfg(feature = "seek_convenience")]
    fn stream_position(&mut self) -> io::Result<u64> {
        match self {
            ResFile::File(inner) => inner.stream_position(),
            ResFile::ZipEntry(inner) => inner.stream_position(),
        }
    }
}
\ No newline at end of file

D logic/src/datasource/zip.rs => logic/src/datasource/zip.rs +0 -90
@@ 1,90 0,0 @@
use std::cell::RefCell;
use std::fs::File;
use std::io;
use std::io::{ErrorKind, Read};
use std::path::{Path, PathBuf};

use zip::read::ZipFile;
use zip::ZipArchive;

use crate::datasource::normalize_path;
use crate::ffi::McrtError;
use crate::ffihelper::FfiError;

pub struct ZipDataSource {
    archive: RefCell<ZipArchive<File>>,
}

impl ZipDataSource {
    pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
        let file = File::open(path)?;
        let za = ZipArchive::new(file)?;
        Ok(ZipDataSource {
            archive: RefCell::new(za),
        })
    }

    pub fn open(&self, path: impl AsRef<Path>) -> Result<Vec<u8>, Error> {
        let path = ZipDataSource::resolve_path_for_archive(&path).ok_or_else(|| Error::InvalidPath(path.as_ref().to_path_buf()))?;
        let mut archive = self.archive.borrow_mut();
        let mut file = archive.by_name(&path)?;
        let mut buf = Vec::new();
        file.read_to_end(&mut buf)?;
        Ok(buf)
    }

    pub fn list_dir(&self, path: impl AsRef<Path>) -> Result<Vec<PathBuf>, Error> {
        unimplemented!()
    }

    fn resolve_path_for_archive(path: impl AsRef<Path>) -> Option<String> {
        let pb = normalize_path(path)?;
        Some(pb.strip_prefix("/").unwrap().to_str().unwrap().to_string())
    }
}

pub enum Error {
    InvalidPath(PathBuf),
    Io(io::Error),
    Zip(zip::result::ZipError),
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self {
        Error::Io(err)
    }
}

impl From<zip::result::ZipError> for Error {
    fn from(err: zip::result::ZipError) -> Self {
        Error::Zip(err)
    }
}

impl FfiError for Error {
    fn kind(&self) -> McrtError {
        use zip::result::ZipError;

        match self {
            Error::Io(e) | Error::Zip(ZipError::Io(e)) => match e.kind() {
                ErrorKind::NotFound => McrtError::NotFound,
                ErrorKind::PermissionDenied => McrtError::PermissionDenied,
                _ => McrtError::Io
            }
            Error::Zip(ZipError::UnsupportedArchive(text)) => McrtError::UnsupportedZip,
            Error::Zip(ZipError::InvalidArchive(text)) => McrtError::InvalidZip,
            Error::Zip(ZipError::FileNotFound) => McrtError::NotFound,
            _ => McrtError::Io,
        }
    }

    fn description(&self) -> &str {
        use zip::result::ZipError;

        match self {
            Error::Zip(ZipError::UnsupportedArchive(text)) => text,
            Error::Zip(ZipError::InvalidArchive(text)) => text,
            _ => self.kind().description(),
        }
    }
}
\ No newline at end of file

D logic/src/ffi.rs => logic/src/ffi.rs +0 -28
@@ 1,28 0,0 @@
use std::os::raw::c_char;
use std::ptr::null;
use std::ffi::CString;

#[no_mangle]
pub static mut MCRT_ERROR: McrtError = McrtError::None;

#[no_mangle]
pub static mut MCRT_ERROR_TEXT: *const c_char = null();

#[repr(u8)]
pub enum McrtError {
    None,
    NotFound,
    PermissionDenied,
    Io,
    UnsupportedZip,
    InvalidZip,
    ReadOnly,
    CorruptedFile,
    Nul,
}

#[no_mangle]
pub unsafe extern "C" fn mcrt_str_delete(text: *const c_char) {
    if text.is_null() { return; }
    drop(CString::from_raw(text as *mut c_char));
}
\ No newline at end of file

D logic/src/ffihelper.rs => logic/src/ffihelper.rs +0 -83
@@ 1,83 0,0 @@
use std::ffi::CString;
use std::io::ErrorKind;
use std::os::raw::c_char;
use std::ptr::null;

use crate::ffi::{MCRT_ERROR, MCRT_ERROR_TEXT, McrtError};

unsafe fn delete_error_text() {
    if !MCRT_ERROR_TEXT.is_null() {
        drop(CString::from_raw(MCRT_ERROR_TEXT as *mut c_char));
        MCRT_ERROR_TEXT = null();
    }
}

pub unsafe fn clear_error() {
    MCRT_ERROR = McrtError::None;
    delete_error_text();
}

pub unsafe fn set_error(error: McrtError, text: &str) {
    MCRT_ERROR = error;
    delete_error_text();
    MCRT_ERROR_TEXT = CString::new(text).expect("Failed to convert error text to C string").into_raw();
}

pub trait FfiError {
    fn kind(&self) -> McrtError;

    fn description(&self) -> &str {
        self.kind().description()
    }
}

impl McrtError {
    pub fn description(&self) -> &'static str {
        match self {
            McrtError::None => "",
            McrtError::NotFound => "File not found",
            McrtError::PermissionDenied => "Permission denied",
            McrtError::Io => "I/O Error",
            McrtError::UnsupportedZip => "Unsupported ZIP archive",
            McrtError::InvalidZip => "Invalid ZIP archive",
            McrtError::ReadOnly => "Filesystem is read-only",
            McrtError::CorruptedFile => "Corrupted file",
            McrtError::Nul => "0-byte found in string",
        }
    }
}

pub unsafe fn set_error_from(error: impl FfiError) {
    set_error(error.kind(), error.description());
}

macro_rules! try_ffi {
    ($e:expr) => {
        try_ffi!($e, ::std::ptr::null_mut());
    };
    ($e:expr, $rv:expr) => {
        match $e {
            ::std::result::Result::Ok(v) => v,
            ::std::result::Result::Err(e) =>  {
                $crate::ffihelper::set_error_from(e);
                return $rv;
            }
        }
    }
}

impl FfiError for std::io::Error {
    fn kind(&self) -> McrtError {
        match self.kind() {
            ErrorKind::NotFound => McrtError::NotFound,
            ErrorKind::PermissionDenied => McrtError::PermissionDenied,
            _ => McrtError::Io
        }
    }
}

impl FfiError for std::ffi::NulError {
    fn kind(&self) -> McrtError {
        McrtError::Nul
    }
}
\ No newline at end of file

D logic/src/languagetable/ffi.rs => logic/src/languagetable/ffi.rs +0 -122
@@ 1,122 0,0 @@
#![allow(clippy::missing_safety_doc)]

use std::convert::TryFrom;
use std::ffi::{CStr, CString};
use std::mem;
use std::os::raw::c_char;
use std::ptr::{drop_in_place, null, null_mut};

use crate::datasource::DataSource;
use crate::ffihelper::clear_error;
use crate::languagetable::LanguageTable;

#[no_mangle]
pub extern "C" fn languagetable_create() -> *mut LanguageTable {
    Box::into_raw(Box::new(LanguageTable::new()))
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_load_from(ds: &DataSource, dir: *const c_char) -> *mut LanguageTable {
    clear_error();
    if dir.is_null() { return null_mut(); }
    let dir = CStr::from_ptr(dir);

    let lt = try_ffi!(LanguageTable::read_from(ds, dir.to_str().unwrap()));

    Box::into_raw(Box::new(lt))
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_load_into(lt: &mut LanguageTable, ds: &DataSource, dir: *const c_char) -> bool {
    clear_error();
    if dir.is_null() { return false; }
    let dir = CStr::from_ptr(dir);

    *lt = try_ffi!(LanguageTable::read_from(ds, dir.to_str().unwrap()), false);
    true
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_write_to(lt: &LanguageTable, ds: &DataSource, dir: *const c_char) -> bool {
    clear_error();
    if dir.is_null() { return false; }
    let dir = CStr::from_ptr(dir);

    let dir = dir.to_str().unwrap();
    try_ffi!(ds.create_dir_all(dir), false);
    try_ffi!(lt.write_to(ds, dir), false);

    true
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_add_language(lt: &mut LanguageTable, lang: *const c_char) {
    if lang.is_null() { return; }
    let lang = CStr::from_ptr(lang);
    lt.add_language(lang.to_str().unwrap());
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_add_localization_key(lt: &mut LanguageTable, key: *const c_char) {
    if key.is_null() { return; }
    let key = CStr::from_ptr(key);
    lt.add_localization_key(key.to_str().unwrap());
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_insert(lt: &mut LanguageTable, lang: *const c_char, key: *const c_char, name: *const c_char) {
    if lang.is_null() || key.is_null() || name.is_null() { return; }
    let lang = CStr::from_ptr(lang);
    let key = CStr::from_ptr(key);
    let name = CStr::from_ptr(name);
    lt.insert(lang.to_str().unwrap(), key.to_str().unwrap(), name.to_str().unwrap());
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_get(lt: &mut LanguageTable, lang: *const c_char, key: *const c_char) -> *const c_char {
    clear_error();
    if lang.is_null() || key.is_null() { return null(); }
    let lang = CStr::from_ptr(lang);
    let key = CStr::from_ptr(key);
    let entry = lt.get(lang.to_str().unwrap(), key.to_str().unwrap());
    match entry {
        None => null(),
        Some(s) => try_ffi!(CString::new(s)).into_raw(),
    }
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_get_col_name(lt: &mut LanguageTable, idx: usize) -> *const c_char {
    clear_error();
    let entry = lt.column_name(idx);
    match entry {
        None => null(),
        Some(s) => try_ffi!(CString::new(s)).into_raw(),
    }
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_get_row_name(lt: &mut LanguageTable, idx: usize) -> *const c_char {
    clear_error();
    let entry = lt.row_name(idx);
    match entry {
        None => null(),
        Some(s) => try_ffi!(CString::new(s)).into_raw(),
    }
}


#[no_mangle]
pub unsafe extern "C" fn languagetable_row_count(lt: &mut LanguageTable) -> usize {
    lt.row_count()
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_col_count(lt: &mut LanguageTable) -> usize {
    lt.column_count()
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_delete(lt: *mut LanguageTable) {
    drop_in_place(lt);
}
\ No newline at end of file

D logic/src/languagetable/mod.rs => logic/src/languagetable/mod.rs +0 -121
@@ 1,121 0,0 @@
use std::collections::HashMap;
use std::path::Path;

use crate::datasource::{DataSource, OpenOptions};
use crate::datasource;
use crate::ffi::McrtError;
use crate::ffihelper::FfiError;

pub mod ffi;

pub struct LanguageTable {
    table: HashMap<String, HashMap<String, String>>,
    languages: Vec<String>,
    localization_keys: Vec<String>,
}

impl LanguageTable {
    pub fn new() -> Self {
        LanguageTable {
            table: Default::default(),
            languages: vec![],
            localization_keys: vec![],
        }
    }

    pub fn add_language(&mut self, name: &str) {
        if !self.languages.iter().any(|s| s == name) {
            self.languages.push(name.to_owned());
        }
    }

    pub fn add_localization_key(&mut self, key: &str) {
        if !self.localization_keys.iter().any(|s| s == key) {
            self.localization_keys.push(key.to_owned());
        }
    }

    pub fn insert(&mut self, lang: &str, key: &str, text: &str) -> Option<String> {
        self.add_language(lang);
        self.add_localization_key(key);
        self.table.entry(lang.to_owned()).or_default().insert(key.to_owned(), text.to_owned())
    }

    pub fn get(&self, lang: &str, key: &str) -> Option<&str> {
        Some(self.table.get(lang)?.get(key)?)
    }

    pub fn column_name(&self, idx: usize) -> Option<&str> {
        Some(self.languages.get(idx)?)
    }

    pub fn row_name(&self, idx: usize) -> Option<&str> {
        Some(self.localization_keys.get(idx)?)
    }

    fn replace_language(&mut self, lang: &str, map: HashMap<String, String>) -> Option<HashMap<String, String>> {
        self.add_language(lang);
        map.keys().for_each(|el| self.add_localization_key(el));
        self.table.insert(lang.to_owned(), map)
    }

    pub fn remove(&mut self, lang: &str, key: &str) -> Option<String> {
        self.table.get_mut(lang)?.remove(key)
    }

    pub fn row_count(&self) -> usize { self.localization_keys.len() }

    pub fn column_count(&self) -> usize { self.languages.len() }

    pub fn write_to(&self, ds: &DataSource, dir: impl AsRef<Path>) -> Result<(), Error> {
        for x in ds.list_dir(&dir)? {
            ds.delete_file(dir.as_ref().join(x))?;
        }
        for (lang, map) in self.table.iter() {
            let path = dir.as_ref().join(format!("{}.json", lang));
            let mut file = ds.open(path, OpenOptions::writing(true))?;
            serde_json::to_writer_pretty(&mut file, map)?;
        }
        Ok(())
    }

    pub fn read_from(ds: &DataSource, dir: impl AsRef<Path>) -> Result<LanguageTable, Error> {
        let mut lt = LanguageTable::new();
        for x in ds.list_dir(&dir)? {
            if x.extension().unwrap().to_str() == Some("json") {
                let file_name = x.file_name().unwrap().to_str().unwrap();
                let lang = &file_name[..file_name.len() - 5];
                let mut file = ds.open(&x, OpenOptions::reading())?;
                let map: HashMap<String, String> = serde_json::from_reader(&mut file)?;
                lt.replace_language(lang, map);
            }
        }
        Ok(lt)
    }
}

pub enum Error {
    Io(datasource::Error),
    Serde(serde_json::Error),
}

impl From<datasource::Error> for Error {
    fn from(err: datasource::Error) -> Self {
        Error::Io(err)
    }
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Error::Serde(err)
    }
}

impl FfiError for Error {
    fn kind(&self) -> McrtError {
        match self {
            Error::Io(e) => e.kind(),
            Error::Serde(_) => McrtError::CorruptedFile,
        }
    }
}
\ No newline at end of file

D logic/src/lib.rs => logic/src/lib.rs +0 -10
@@ 1,10 0,0 @@
#![allow(clippy::new_without_default)]

#[macro_use]
pub mod ffihelper;

pub mod datasource;
pub mod languagetable;
pub mod restree;

pub mod ffi;
\ No newline at end of file

D logic/src/restree/ffi.rs => logic/src/restree/ffi.rs +0 -10
@@ 1,10 0,0 @@
use std::os::raw::c_char;

use crate::restree::FileTree;
use std::ptr::null;
use std::ffi::CString;

#[no_mangle]
pub extern "C" fn filetree_get_name(rt: &FileTree) -> *const c_char {
    null()
}
\ No newline at end of file

D logic/src/restree/mod.rs => logic/src/restree/mod.rs +0 -71
@@ 1,71 0,0 @@
use std::ffi::CString;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::path::PathBuf;
use crate::datasource::DataSource;

pub mod ffi;

pub struct FileTreeRoot<'a> {
    ds: &'a DataSource,

}

pub struct FileTree {
    name: String,
    ft_path: FileTreePath,
    path: Option<PathBuf>,
    file_type: FileType,
    children: Vec<FileTree>,
}

#[repr(u8)]
pub enum FileType {
    None,
    Language,
}

#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct FileTreePath(Vec<String>);

impl Default for FileTreePath {
    fn default() -> Self { FileTreePath(vec![]) }
}

impl Display for FileTreePath {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        for x in &self.0 {
            write!(f, "/{}", x)?;
        }
        Ok(())
    }
}

impl FileTreePath {
    pub fn is_root(&self) -> bool {
        self.0.is_empty()
    }

    pub fn parent(&self) -> Option<FileTreePath> {
        if !self.is_root() {
            Some(FileTreePath(self.0[..self.0.len() - 1].to_vec()))
        } else {
            None
        }
    }

    pub fn child(&self, name: &str) -> FileTreePath {
        let mut vec = self.0.clone();
        vec.push(name.to_owned());
        FileTreePath(vec)
    }

    pub fn full_path(&self) -> String {
        format!("{}", self)
    }

    pub fn path(&self) -> Option<&str> {
        self.0.last().map(|s| &**s)
    }
}

R ui/src/identifier.cpp => src/identifier.cpp +0 -0

R ui/src/identifier.h => src/identifier.h +0 -0

A src/languagetable/languagetable.cpp => src/languagetable/languagetable.cpp +33 -0
@@ 0,0 1,33 @@
#include "languagetable.h"

void LanguageTable::insert(QString language, QString key, QString value) {

}

void LanguageTable::add_key(QString key) {

}

void LanguageTable::add_language(QString language) {

}

int LanguageTable::key_count() const {
    return 0;
}

int LanguageTable::language_count() const {
    return 0;
}

QString LanguageTable::get(const QString& language, const QString& key) const {
    return QString();
}

QString LanguageTable::get_language_at(int index) const {
    return QString();
}

QString LanguageTable::get_key_at(int index) const {
    return QString();
}

A src/languagetable/languagetable.h => src/languagetable/languagetable.h +27 -0
@@ 0,0 1,27 @@
#ifndef MCRESTOOL_LANGUAGETABLE_H
#define MCRESTOOL_LANGUAGETABLE_H

#include <QString>

class LanguageTable {

public:
    void insert(QString language, QString key, QString value);

    void add_key(QString key);

    void add_language(QString language);

    int key_count() const;

    int language_count() const;

    QString get(const QString& language, const QString& key) const;

    QString get_language_at(int index) const;

    QString get_key_at(int index) const;

};

#endif //MCRESTOOL_LANGUAGETABLE_H

R ui/src/main.cpp => src/main.cpp +0 -1
@@ 1,7 1,6 @@
#include "src/ui/mainwindow.h"
#include <QApplication>
#include <QDebug>
#include "mcrestool_logic.h"

int main(int argc, char* argv[]) {
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

R ui/src/model/languagetablemodel.cpp => src/model/languagetablemodel.cpp +14 -18
@@ 1,40 1,36 @@
#include <QDir>
#include <QUrl>
#include <QSet>
#include <QSharedPointer>
#include "languagetablemodel.h"

using std::optional;

LanguageTableModel::LanguageTableModel(LanguageTable* lt, QObject* parent) : QAbstractTableModel(parent), lt(lt) {}
LanguageTableModel::LanguageTableModel(LanguageTable lt, QObject* parent) : QAbstractTableModel(parent), lt(lt) {}

void LanguageTableModel::set_entry(QString language, QString key, QString value) {
    languagetable_insert(lt, language.toLocal8Bit(), key.toLocal8Bit(), value.toLocal8Bit());
    lt.insert(language, key, value);
    emit changed(language, key, value);
}

LanguageTableModel* LanguageTableModel::from_dir(QObject* parent, QString path) {
    QDir dir(path);
    return new LanguageTableModel(languagetable_create(), parent);
LanguageTableModel* LanguageTableModel::from_dir(FsRef path, QObject* parent) {
    return new LanguageTableModel(LanguageTable(), parent);
}

int LanguageTableModel::rowCount(const QModelIndex& parent) const {
    return languagetable_row_count(lt);
    return lt.key_count();
}

int LanguageTableModel::columnCount(const QModelIndex& parent) const {
    return languagetable_col_count(lt);
    return lt.language_count();
}

QVariant LanguageTableModel::data(const QModelIndex& index, int role) const {
    if (role == Qt::DisplayRole || role == Qt::EditRole) {
        QString column = get_column_name(index.column());
        QString row = get_row_name(index.row());
        const char* str = languagetable_get(lt, column.toLocal8Bit(), row.toLocal8Bit());
        QString content = QString(str);
        mcrt_str_delete(str);
        if (!content.isNull()) {
            return content;
        QString str = lt.get(column.toLocal8Bit(), row.toLocal8Bit());
        if (!str.isNull()) {
            return str;
        }
    }
    return QVariant();


@@ 53,11 49,11 @@ QVariant LanguageTableModel::headerData(int section, Qt::Orientation orientation
}

QString LanguageTableModel::get_column_name(int idx) const {
    return languagetable_get_col_name(lt, idx);
    return lt.get_language_at(idx);
}

QString LanguageTableModel::get_row_name(int idx) const {
    return languagetable_get_row_name(lt, idx);
    return lt.get_key_at(idx);
}

bool LanguageTableModel::setData(const QModelIndex& index, const QVariant& value, int role) {


@@ 75,16 71,16 @@ Qt::ItemFlags LanguageTableModel::flags(const QModelIndex& index) const {

void LanguageTableModel::add_locale_key(QString locale_key) {
    emit layoutAboutToBeChanged();
    languagetable_add_localization_key(lt, locale_key.toLocal8Bit());
    lt.add_key(locale_key);
    emit layoutChanged();
}

void LanguageTableModel::add_language(QString language) {
    emit layoutAboutToBeChanged();
    languagetable_add_language(lt, language.toLocal8Bit());
    lt.add_language(language);
    emit layoutChanged();
}

LanguageTable* LanguageTableModel::data() {
LanguageTable& LanguageTableModel::data() {
    return lt;
}

R ui/src/model/languagetablemodel.h => src/model/languagetablemodel.h +7 -5
@@ 1,20 1,22 @@
#ifndef MCRESTOOL_LANGUAGETABLEMODEL_H
#define MCRESTOOL_LANGUAGETABLEMODEL_H

#include "src/languagetable/languagetable.h"
#include "src/workspace/fsref.h"

#include <QObject>
#include <QMap>
#include <QAbstractTableModel>
#include "mcrestool_logic.h"

class LanguageTableModel : public QAbstractTableModel {
Q_OBJECT

public:
    explicit LanguageTableModel(LanguageTable* lt, QObject* parent = nullptr);
    explicit LanguageTableModel(LanguageTable lt, QObject* parent = nullptr);

    void set_entry(QString language, QString key, QString value);

    static LanguageTableModel* from_dir(QObject* parent, QString path);
    static LanguageTableModel* from_dir(FsRef path, QObject* parent);

    [[nodiscard]] int rowCount(const QModelIndex& parent) const override;



@@ 32,14 34,14 @@ public:

    void add_language(QString language);

    [[nodiscard]] LanguageTable* data();
    [[nodiscard]] LanguageTable& data();

signals:

    void changed(const QString& language, const QString& key, const QString& value);

private:
    LanguageTable* lt;
    LanguageTable lt;

    [[nodiscard]] QString get_column_name(int idx) const;


R ui/src/model/resourcetree.cpp => src/model/resourcetree.cpp +3 -3
@@ 1,7 1,7 @@
#include "resourcetree.h"

ResourceTree::ResourceTree(QObject* parent) :
    QAbstractItemModel(parent) {
ResourceTree::ResourceTree(QObject* parent):
        QAbstractItemModel(parent) {
    root_item = new TreeItem(QString());

    {


@@ 94,7 94,7 @@ QVariant ResourceTree::data(const QModelIndex& index, int role) const {
}

Qt::ItemFlags ResourceTree::flags(const QModelIndex& index) const {
    if (!index.isValid())return Qt::NoItemFlags;
    if (!index.isValid()) return Qt::NoItemFlags;

    return QAbstractItemModel::flags(index);
}

R ui/src/model/resourcetree.h => src/model/resourcetree.h +0 -0

R ui/src/model/treeitem.cpp => src/model/treeitem.cpp +0 -0

R ui/src/model/treeitem.h => src/model/treeitem.h +0 -0

A src/project/languagetablecontainer.cpp => src/project/languagetablecontainer.cpp +69 -0
@@ 0,0 1,69 @@
#include "languagetablecontainer.h"

LanguageTableContainer::LanguageTableContainer(
    FsRef fs_ref,
    QObject* parent
) : QObject(parent),
    fs_ref(fs_ref),
    lt(new LanguageTableModel(LanguageTable(), this)) {
    _persistent = false;
    _changed = false;
    _deleted = false;

    connect(lt, SIGNAL(changed(const QString&, const QString&, const QString&)), this, SLOT(on_changed()));
}

LanguageTableContainer::~LanguageTableContainer() {

}

LanguageTableModel* LanguageTableContainer::language_table() {
    return lt;
}

bool LanguageTableContainer::persistent() const {
    return _persistent;
}

bool LanguageTableContainer::changed() const {
    return _changed;
}

bool LanguageTableContainer::read_only() const {
    return fs_ref.read_only();
}

void LanguageTableContainer::delete_file() {
//    if (read_only()) return;
//
//    if (persistent()) {
//        QList<DirEntryW> files = src->data_source()->list_dir("/assets/" + domain + "/lang/");
//        for (const auto& entry: files) {
//            src->data_source()->delete_file(entry.name);
//        }
//    }
//    _deleted = true;
//    _persistent = false;
}

void LanguageTableContainer::save() {
//    if (read_only()) return;
//
//    languagetable_write_to(lt->data(), src->data_source()->inner(), ("/assets/" + domain + "/lang/").toLocal8Bit());
//
//    _persistent = true;
//    _changed = false;
}

void LanguageTableContainer::load() {
//    languagetable_load_into(lt->data(), src->data_source()->inner(), ("/assets/" + domain + "/lang/").toLocal8Bit());
//
//    _persistent = true;
//    _changed = false;
//    emit lt->layoutChanged();
}

void LanguageTableContainer::on_changed() {
//    _changed = true;
//    emit changed();
}

R ui/src/project/languagetablecontainer.h => src/project/languagetablecontainer.h +5 -7
@@ 1,19 1,18 @@
#ifndef MCRESTOOL_LANGUAGETABLECONTAINER_H
#define MCRESTOOL_LANGUAGETABLECONTAINER_H

#include "project.h"
#include "src/workspace/fsref.h"
#include "src/model/languagetablemodel.h"

#include <QObject>
#include "src/model/languagetablemodel.h"
#include "projectsource.h"

class LanguageTableContainer : public QObject {
Q_OBJECT

public:
    explicit LanguageTableContainer(ProjectSource* src, const QString& domain, QObject* parent = nullptr);
    explicit LanguageTableContainer(FsRef fs_ref, QObject* parent = nullptr);

    ~LanguageTableContainer() override;
    ~LanguageTableContainer();

    LanguageTableModel* language_table();



@@ 40,9 39,8 @@ signals:
    void changed();

private:
    ProjectSource* src;
    FsRef fs_ref;
    LanguageTableModel* lt;
    QString domain;

    bool _persistent;
    bool _changed;

R ui/src/result.h => src/result.h +0 -0

R ui/src/table.h => src/table.h +0 -0

R ui/src/ui/geneditorwindow.cpp => src/ui/geneditorwindow.cpp +0 -0

R ui/src/ui/geneditorwindow.h => src/ui/geneditorwindow.h +0 -0

R ui/src/ui/itembutton.cpp => src/ui/itembutton.cpp +0 -0

R ui/src/ui/itembutton.h => src/ui/itembutton.h +0 -0

R ui/src/ui/itemselectiondialog.ui => src/ui/itemselectiondialog.ui +0 -0

R ui/src/ui/languagetablewindow.cpp => src/ui/languagetablewindow.cpp +0 -2
@@ 32,12 32,10 @@ void LanguageTableWindow::add_locale_key() {

void LanguageTableWindow::save() {
    ltc->save();
    check_for_error(this);
}

void LanguageTableWindow::reload() {
    ltc->load();
    check_for_error(this);
}

LanguageTableWindow::~LanguageTableWindow() = default;

R ui/src/ui/languagetablewindow.h => src/ui/languagetablewindow.h +0 -0

R ui/src/ui/languagetablewindow.ui => src/ui/languagetablewindow.ui +0 -0

R ui/src/ui/mainwindow.cpp => src/ui/mainwindow.cpp +19 -10
@@ 11,7 11,7 @@
#include <iostream>
#include <QDebug>

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow), ws(new Workspace(this)) {
    ui->setupUi(this);

    connect(ui->action_quit, SIGNAL(triggered()), this, SLOT(quit()));


@@ 19,7 19,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
    connect(ui->action_save, SIGNAL(triggered()), this, SLOT(save()));
    connect(ui->action_save_workspace_as, SIGNAL(triggered()), this, SLOT(save_as()));
    connect(ui->action_add_res_file, SIGNAL(triggered()), this, SLOT(add_res_file()));
    connect(ui->action_add_res_folder, SIGNAL(triggered()), this, SLOT(add_res_folder()));
    connect(ui->action_add_res_folder, SIGNAL(triggered()), this, SLOT(add_res_dir()));
    connect(ui->action_about_qt, &QAction::triggered, &QApplication::aboutQt);

    connect(ui->action_resource_tree, SIGNAL(triggered(bool)), this, SLOT(show_resource_tree(bool)));


@@ 29,8 29,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi

    connect(ui->mdi_area, SIGNAL(subWindowActivated(QMdiSubWindow * )), this, SLOT(sub_window_focus_change(QMdiSubWindow * )));

    auto* ds = DataSourceW::from_dir("../../testres", this);
    auto* ltw = new LanguageTableWindow(new LanguageTableContainer(new ProjectSource(ds, "testres", this), "testmod", this), this);
    auto* ltw = new LanguageTableWindow(new LanguageTableContainer(FsRef("testres/assets/testmod/lang"), this), this);
    ltw->reload();
    ui->mdi_area->addSubWindow(ltw);



@@ 40,7 39,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
    connect(ui->action_insert_language, &QAction::triggered, ltw, &LanguageTableWindow::add_language);
    connect(ui->action_insert_translation_key, &QAction::triggered, ltw, &LanguageTableWindow::add_locale_key);

    ui->res_tree_view->setModel(new ResourceTree(this));
    connect(ui->res_tree_view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(show_restree_context_menu(QPoint)));

    // ui->res_tree_view->setModel(new ResourceTree(this));
}

void MainWindow::center() {


@@ 64,7 65,6 @@ void MainWindow::save() {
    GenEditorWindow* editorWindow = dynamic_cast<GenEditorWindow*>(window);
    if (editorWindow) {
        editorWindow->save();
        check_for_error(this);
    } else {
        qDebug() << "Failed to save because" << editorWindow << "is not a GenEditorWindow!";
    }


@@ 93,16 93,25 @@ void MainWindow::show_game_objects(bool shown) {
}

void MainWindow::add_res_file() {
    QStringList sources = QFileDialog::getOpenFileNames(this, tr("Add Resource Pack/Mod"));
    QStringList sources = QFileDialog::getOpenFileNames(this, tr("Add Resource Pack/Mod"), QString(), "Minecraft Content(*.zip *.jar);;All Files(*.*)");
}

void MainWindow::add_res_folder() {
void MainWindow::add_res_dir() {
    QString source = QFileDialog::getExistingDirectory(this, tr("Add Resource Folder"));
}

void MainWindow::sub_window_focus_change(QMdiSubWindow* window) {
    if (window)
        puts(window->widget()->objectName().toLocal8Bit());
    if (window) puts(window->widget()->objectName().toLocal8Bit());
}

void MainWindow::show_restree_context_menu(const QPoint& pt) {
    const QPoint& gPt = this->ui->res_tree_view->mapToGlobal(pt);

    QMenu menu;
    menu.addAction(tr("Add Directory"), this, SLOT(add_res_dir()));
    menu.addAction(tr("Add ZIP File"), this, SLOT(add_res_file()));

    menu.exec(gPt);
}

MainWindow::~MainWindow() = default;

R ui/src/ui/mainwindow.h => src/ui/mainwindow.h +6 -3
@@ 4,8 4,8 @@
#include <QMainWindow>
#include <QScopedPointer>
#include <QMdiSubWindow>
#include "src/model/resourcetree.h"
#include "src/model/languagetablemodel.h"
#include "src/workspace/workspace.h"

namespace Ui {
    class MainWindow;


@@ 39,7 39,7 @@ private slots:

    void add_res_file();

    void add_res_folder();
    void add_res_dir();

    void show_resource_tree(bool shown);



@@ 47,9 47,12 @@ private slots:

    void sub_window_focus_change(QMdiSubWindow* window);

    void show_restree_context_menu(const QPoint& pt);

private:
    QScopedPointer<Ui::MainWindow> ui;
    // ResourceTree rt;
    Workspace* ws;


};


R ui/src/ui/mainwindow.ui => src/ui/mainwindow.ui +45 -12
@@ 61,17 61,17 @@
    <property name="title">
     <string>&amp;File</string>
    </property>
    <addaction name="action_new"/>
    <addaction name="action_open"/>
    <addaction name="separator"/>
    <addaction name="action_save"/>
    <addaction name="action_save_all"/>
    <addaction name="action_close"/>
    <addaction name="separator"/>
    <addaction name="action_add_res_file"/>
    <addaction name="action_add_res_folder"/>
    <addaction name="separator"/>
    <addaction name="action_New_Workspace"/>
    <addaction name="action_open_workspace"/>
    <addaction name="action_save_workspace"/>
    <addaction name="action_save_workspace_as"/>
    <addaction name="action_close_workspace"/>
    <addaction name="separator"/>
    <addaction name="action_quit"/>
   </widget>


@@ 121,6 121,9 @@
    <layout class="QVBoxLayout" name="verticalLayout">
     <item>
      <widget class="QTreeView" name="res_tree_view">
       <property name="contextMenuPolicy">
        <enum>Qt::CustomContextMenu</enum>
       </property>
       <attribute name="headerVisible">
        <bool>false</bool>
       </attribute>


@@ 204,7 207,7 @@
     <normaloff>.</normaloff>.</iconset>
   </property>
   <property name="text">
    <string>Save W&amp;orkspace As…</string>
    <string>Save Workspace As…</string>
   </property>
   <property name="statusTip">
    <string>Saves the workspace settings as a new file.</string>


@@ 295,10 298,11 @@
  </action>
  <action name="action_save_all">
   <property name="icon">
    <iconset theme="document-save-all"/>
    <iconset theme="document-save-all">
     <normaloff>.</normaloff>.</iconset>
   </property>
   <property name="text">
    <string>Save All</string>
    <string>Save A&amp;ll</string>
   </property>
   <property name="statusTip">
    <string>Saves all the open editors.</string>


@@ 309,10 313,11 @@
  </action>
  <action name="action_save_workspace">
   <property name="icon">
    <iconset theme="document-save"/>
    <iconset theme="document-save">
     <normaloff>.</normaloff>.</iconset>
   </property>
   <property name="text">
    <string>Save &amp;Workspace</string>
    <string>Sa&amp;ve Workspace</string>
   </property>
   <property name="statusTip">
    <string>Saves the workspace settings.</string>


@@ 321,12 326,40 @@
    <string>Alt+Shift+S</string>
   </property>
  </action>
  <action name="action_New_Workspace">
  <action name="action_close_workspace">
   <property name="icon">
    <iconset theme="window-close">
     <normaloff>.</normaloff>.</iconset>
   </property>
   <property name="text">
    <string>Close Wor&amp;kspace</string>
   </property>
  </action>
  <action name="action_new">
   <property name="icon">
    <iconset theme="document-new">
     <normaloff>.</normaloff>.</iconset>
   </property>
   <property name="text">
    <string>&amp;New…</string>
   </property>
  </action>
  <action name="action_open_workspace">
   <property name="icon">
    <iconset theme="document-open">
     <normaloff>.</normaloff>.</iconset>
   </property>
   <property name="text">
    <string>Open &amp;Workspace…</string>
   </property>
  </action>
  <action name="action_close">
   <property name="icon">
    <iconset theme="document-new"/>
    <iconset theme="document-close">
     <normaloff>.</normaloff>.</iconset>
   </property>
   <property name="text">
    <string>&amp;New Workspace</string>
    <string>&amp;Close</string>
   </property>
  </action>
 </widget>

R ui/src/ui/recipeeditextensionwidget.cpp => src/ui/recipeeditextensionwidget.cpp +0 -0

R ui/src/ui/recipeeditextensionwidget.h => src/ui/recipeeditextensionwidget.h +0 -0

R ui/src/ui/recipeeditwindow.cpp => src/ui/recipeeditwindow.cpp +0 -0

R ui/src/ui/recipeeditwindow.h => src/ui/recipeeditwindow.h +0 -0

R ui/src/ui/recipeeditwindow.ui => src/ui/recipeeditwindow.ui +0 -0

R ui/src/ui/shapedcraftingwidget.cpp => src/ui/shapedcraftingwidget.cpp +0 -0

R ui/src/ui/shapedcraftingwidget.h => src/ui/shapedcraftingwidget.h +0 -0

R ui/src/ui/shapedcraftingwidget.ui => src/ui/shapedcraftingwidget.ui +0 -0

R ui/src/ui/smeltingwidget.cpp => src/ui/smeltingwidget.cpp +0 -0

R ui/src/ui/smeltingwidget.h => src/ui/smeltingwidget.h +0 -0

R ui/src/ui/smeltingwidget.ui => src/ui/smeltingwidget.ui +0 -0

R ui/src/ui/tagedit.ui => src/ui/tagedit.ui +0 -0

R ui/src/util.h => src/util.h +0 -3
@@ 2,12 2,9 @@
#define MCRESTOOL_UTIL_H

#include <QtGlobal>
#include <QWidget>

#define unimplemented() (qt_assert("unimplemented", __FILE__, __LINE__))

#define unreachable() (qt_assert("unreachable", __FILE__, __LINE__))

void check_for_error(QWidget* parent);

#endif //MCRESTOOL_UTIL_H

A src/workspace/direntry.h => src/workspace/direntry.h +16 -0
@@ 0,0 1,16 @@
#ifndef MCRESTOOL_DIRENTRY_H
#define MCRESTOOL_DIRENTRY_H

#include "fsref.h"

#include <QString>

struct WSDirEntry {
    bool is_file;
    bool is_dir;
    bool is_symlink;
    QString file_name;
    FsRef real_path;
};

#endif //MCRESTOOL_DIRENTRY_H

A src/workspace/fsref.cpp => src/workspace/fsref.cpp +147 -0
@@ 0,0 1,147 @@
#include "fsref.h"
#include "direntry.h"
#include "src/util.h"

#include <QFileInfo>
#include <QDir>
#include <quazip5/quazip.h>
#include <quazip5/quazipfile.h>

FsRef::FsRef(const QString& file_path) : type(FsRefType::NORMAL) {
    data.normal = NormalFsRef {
        .file_path = file_path
    };
}

FsRef::FsRef(const QString& zip_path, const QString& file_path) : type(FsRefType::ZIP) {
    data.zip = ZipFsRef {
        .zip_path = zip_path,
        .file_path = file_path,
    };
}

FsRef::FsRef(const FsRef& that) : type(that.type) {
    switch(that.type) {
        case NORMAL:
            this->data.normal = that.data.normal;
            break;
        case ZIP:
            this->data.zip = that.data.zip;
            break;
        default:
            unreachable();
    }
}

FsRef::~FsRef() {
    switch (this->type) {
        case NORMAL:
            delete this->data.normal;
            break;
        case ZIP:
            delete this->data.zip;
            break;
        default:
            unreachable();
    }
}

bool FsRef::read_only() const {
    switch (this->type) {
        case NORMAL:
            return !QFileInfo(this->data.normal.file_path).isWritable();
        case ZIP:
            return !QFileInfo(this->data.zip.zip_path).isWritable();
        default:
            unreachable();
    }
}

bool FsRef::is_file() const {
    switch (this->type) {
        case NORMAL:
            return QFileInfo(this->data.normal.file_path).isFile();
        case ZIP:
            return QuaZip(this->data.zip.zip_path).getFileNameList().contains(this->data.zip.file_path);
        default:
            unreachable();
    }
}

bool FsRef::is_dir() const {
    switch (this->type) {
        case NORMAL:
            return QFileInfo(this->data.normal.file_path).isDir();
        case ZIP:
            // return !this->is_file();
            return !this->read_dir().isEmpty();
        default:
            unreachable();
    }
}

bool FsRef::is_link() const {
    switch (this->type) {
        case NORMAL:
            return !QFileInfo(this->data.normal.file_path).isSymbolicLink();
        case ZIP:
            return false; // ain't no links in zip files
        default:
            unreachable();
    }
}

QIODevice* FsRef::open() const {
    switch (this->type) {
        case NORMAL:
            return new QFile(this->data.normal.file_path);
        case ZIP:
            return new QuaZipFile(this->data.zip.zip_path, this->data.zip.file_path);
        default:
            unreachable();
    }
}

QList<WSDirEntry> FsRef::read_dir() const {
    switch (this->type) {
        case NORMAL: {
            QList<WSDirEntry> list;
            QDir dir(this->data.normal.file_path);
            for (auto entry : dir.entryInfoList()) {
                list += WSDirEntry {
                    .is_file = entry.isFile(),
                    .is_symlink = entry.isSymbolicLink(),
                    .is_dir = entry.isDir(),
                    .file_name = entry.fileName(),
                    .real_path = FsRef(entry.filePath()),
                };
            }
            return list;
        }
        case ZIP: {
            QList<WSDirEntry> list;
            QuaZipFile qzf(this->data.zip.zip_path, this->data.zip.file_path);
            QuaZip qz(this->data.zip.zip_path);
            for (auto entry : qz.getFileInfoList()) {
                QString prefix = this->data.zip.file_path;
                if (!prefix.endsWith('/')) prefix += '/';
                if (entry.name.startsWith(prefix)) {
                    int start = prefix.size();
                    int end = entry.name.indexOf('/', start);
                    bool is_dir = end != -1;
                    if (end == -1) end = entry.name.length();
                    QString name = entry.name.mid(start, end - start);
                    list += WSDirEntry {
                        .is_file = !is_dir,
                        .is_dir = is_dir,
                        .is_symlink = false,
                        .file_name = name,
                        .real_path = FsRef(this->data.zip.zip_path, entry.name),
                    };
                }
            }
        }
        default:
            unreachable();
    }
}

A src/workspace/fsref.h => src/workspace/fsref.h +53 -0
@@ 0,0 1,53 @@
#ifndef MCRESTOOL_FSREF_H
#define MCRESTOOL_FSREF_H

#include <QFile>
#include <QList>

struct WSDirEntry;

struct NormalFsRef {
    QString file_path;
};

struct ZipFsRef {
    QString zip_path;
    QString file_path;
};

enum FsRefType {
    NORMAL,
    ZIP,
};

class FsRef {

public:
    explicit FsRef(const QString& file_path);

    explicit FsRef(const QString& zip_path, const QString& file_path);

    FsRef(const FsRef&);

    ~FsRef();

    bool read_only() const;

    bool is_file() const;
    bool is_dir() const;
    bool is_link() const;

    QIODevice* open() const;

    QList<WSDirEntry> read_dir() const;

private:
    FsRefType type;
    union {
        NormalFsRef normal;
        ZipFsRef zip;
    } data;

};

#endif //MCRESTOOL_FSREF_H

A src/workspace/workspace.cpp => src/workspace/workspace.cpp +46 -0
@@ 0,0 1,46 @@
#include "workspace.h"
#include <QFileInfo>

WorkspaceRootBase::WorkspaceRootBase(const QString& name, QObject* parent) : QObject(parent) {
    this->name = name;
}

const QString& WorkspaceRootBase::get_name() const {
    return this->name;
}

void WorkspaceRootBase::set_name(QString str) {
    this->name = str;
}


Workspace::Workspace(QObject* parent) : QObject(parent), roots(QList<WorkspaceRootBase*>()) {
}

void Workspace::add_dir(QString path) {
    this->roots += new DirWorkspaceRoot(path, this);
}

void Workspace::add_file(QString path) {
    this->roots += new ZipWorkspaceRoot(path);
}


DirWorkspaceRoot::DirWorkspaceRoot(const QString& path, QObject* parent) : WorkspaceRootBase(QFileInfo(path).fileName(), parent) {
    this->path = path;
}

QList<WSDirEntry> DirWorkspaceRoot::list_dir_tree(const QString& path) {
    QList<WSDirEntry> v;
    return v;
}


ZipWorkspaceRoot::ZipWorkspaceRoot(const QString& path, QObject* parent) : WorkspaceRootBase(QFileInfo(path).fileName(), parent) {
    this->path = path;
}

QList<WSDirEntry> ZipWorkspaceRoot::list_dir_tree(const QString& path) {
    QList<WSDirEntry> v;
    return v;
}

A src/workspace/workspace.h => src/workspace/workspace.h +66 -0
@@ 0,0 1,66 @@
#ifndef MCRESTOOL_WORKSPACE_H
#define MCRESTOOL_WORKSPACE_H

#include "direntry.h"

#include <QString>

class WorkspaceRootBase : public QObject {
Q_OBJECT

public:
    WorkspaceRootBase(const QString& name, QObject* parent = nullptr);

    virtual ~WorkspaceRootBase() = default;

    virtual QList<WSDirEntry> list_dir_tree(const QString& path) = 0;

    const QString& get_name() const;

    void set_name(QString name);

private:
    QString name;

};

class DirWorkspaceRoot : public WorkspaceRootBase {

public:
    DirWorkspaceRoot(const QString& path, QObject* parent = nullptr);

    QList<WSDirEntry> list_dir_tree(const QString& path) override;

private:
    QString path;

};

class ZipWorkspaceRoot : public WorkspaceRootBase {

public:
    ZipWorkspaceRoot(const QString& path, QObject* parent = nullptr);

    QList<WSDirEntry> list_dir_tree(const QString& path) override;

private:
    QString path;

};

class Workspace : public QObject {
Q_OBJECT

public:
    Workspace(QObject* parent = nullptr);

    void add_dir(QString path);

    void add_file(QString path);

private:
    QList<WorkspaceRootBase*> roots;

};

#endif //MCRESTOOL_WORKSPACE_H

D ui/CMakeLists.txt => ui/CMakeLists.txt +0 -54
@@ 1,54 0,0 @@
cmake_minimum_required(VERSION 3.0)
project(mcrestool_ui)

set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_AUTOUIC ON)

find_package(ECM REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})

include(KDEInstallDirs NO_POLICY_SCOPE)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDECMakeSettings NO_POLICY_SCOPE)

find_package(Qt5 CONFIG REQUIRED
        Core
        Widgets)

find_package(KF5 REQUIRED
        Archive)

set(mcrestool_ui_SRC
        src/main.cpp
        src/model/languagetablemodel.cpp src/model/languagetablemodel.h
        src/table.h
        src/identifier.cpp src/identifier.h
        src/model/resourcetree.cpp src/model/resourcetree.h
        src/ui/mainwindow.cpp src/ui/mainwindow.h
        src/ui/languagetablewindow.cpp src/ui/languagetablewindow.h
        src/ui/itembutton.cpp src/ui/itembutton.h
        src/ui/recipeeditwindow.cpp src/ui/recipeeditwindow.h
        src/ui/shapedcraftingwidget.cpp src/ui/shapedcraftingwidget.h
        src/ui/recipeeditextensionwidget.cpp src/ui/recipeeditextensionwidget.h
        src/util.h
        src/result.h
        src/except.h
        src/ui/smeltingwidget.cpp src/ui/smeltingwidget.h
        src/model/treeitem.cpp src/model/treeitem.h
        src/project/projectsource.cpp src/project/projectsource.h
        src/fs/datasource.cpp src/fs/datasource.h
        src/project/languagetablecontainer.cpp src/project/languagetablecontainer.h
        src/project/project.h src/fs/resfile.cpp src/fs/resfile.h src/ui/geneditorwindow.cpp src/ui/geneditorwindow.h src/util.cpp)

add_executable(mcrestool_ui ${mcrestool_ui_SRC})

set_property(TARGET mcrestool_ui PROPERTY CXX_STANDARD 20)

target_link_libraries(mcrestool_ui
        mcrestool_logic
        Qt5::Core
        Qt5::Widgets
        KF5::Archive)

install(TARGETS mcrestool_ui DESTINATION bin)

D ui/src/except.h => ui/src/except.h +0 -6
@@ 1,6 0,0 @@
#ifndef MCRESTOOL_EXCEPT_H
#define MCRESTOOL_EXCEPT_H

struct

#endif //MCRESTOOL_EXCEPT_H

D ui/src/fs/datasource.cpp => ui/src/fs/datasource.cpp +0 -51
@@ 1,51 0,0 @@
#include "datasource.h"

DataSourceW::DataSourceW(DataSource* ds, QObject* parent) : QObject(parent), ds(ds) {}

DataSourceW::~DataSourceW() {
    datasource_delete(ds);
}

DataSourceW* DataSourceW::from_dir(const QString& dir, QObject* parent) {
    DataSource* p_source = datasource_dir_create(dir.toLocal8Bit());
    if (!p_source) {
        printf("error %d while trying to open datasource: %s\n", MCRT_ERROR, MCRT_ERROR_TEXT);
        return nullptr;
    }
    return new DataSourceW(p_source, parent);
}

DataSourceW* DataSourceW::from_zip(const QString& file, QObject* parent) {
    DataSource* p_source = datasource_zip_create(file.toLocal8Bit());
    if (!p_source) {
        return nullptr;
    }
    return new DataSourceW(p_source, parent);
}

bool DataSourceW::read_only() {
    return datasource_type(ds) == DATA_SOURCE_TYPE_ZIP;
}

ResFileW* DataSourceW::file(const QString& path) {
    return new ResFileW(this, path, this);
}

QStringList DataSourceW::list_dir(const QString& path) {
    QStringList list;
    const char* const* p_string = datasource_list_dir(ds, path.toLocal8Bit());
    while (p_string) {
        list += *p_string;
        p_string += 1;
    }
    dirlist_delete(p_string);
    return list;
}

bool DataSourceW::delete_file(const QString& path) {
    return datasource_delete_file(ds, path.toLocal8Bit());
}

DataSource* DataSourceW::inner() {
    return ds;
}

D ui/src/fs/datasource.h => ui/src/fs/datasource.h +0 -38
@@ 1,38 0,0 @@
#ifndef MCRESTOOL_DATASOURCE_H
#define MCRESTOOL_DATASOURCE_H

#include <QIODevice>
#include "mcrestool_logic.h"
#include "resfile.h"

class DataSourceW : public QObject {
Q_OBJECT

    friend class ResFileW;

private:
    explicit DataSourceW(DataSource* ds, QObject* parent = nullptr);

public:
    static DataSourceW* from_dir(const QString& dir, QObject* parent = nullptr);

    static DataSourceW* from_zip(const QString& file, QObject* parent = nullptr);

    ~DataSourceW() override;

    ResFileW* file(const QString& path);

    QStringList list_dir(const QString& path);

    bool delete_file(const QString& path);

    bool read_only();

    DataSource* inner();

private:
    DataSource* ds;

};

#endif //MCRESTOOL_DATASOURCE_H

D ui/src/fs/resfile.cpp => ui/src/fs/resfile.cpp +0 -48
@@ 1,48 0,0 @@
#include "resfile.h"
#include "datasource.h"

qint64 ResFileW::readData(char* data, qint64 maxlen) {
    uintptr_t i = resfile_read(data, maxlen, inner);
    if (MCRT_ERROR) {
        setErrorString(MCRT_ERROR_TEXT);
        return -1;
    }
    return i;
}

qint64 ResFileW::writeData(const char* data, qint64 len) {
    uintptr_t i = resfile_write(data, len, inner);
    if (MCRT_ERROR) {
        setErrorString(MCRT_ERROR_TEXT);
        return -1;
    }
    return i;
}

ResFileW::~ResFileW() {
    close();
}

ResFileW::ResFileW(DataSourceW* owner, const QString& path, QObject* parent) : QIODevice(parent), owner(owner), path(path) {}

bool ResFileW::open(QIODevice::OpenMode mode) {
    if (inner) resfile_close(inner);
    datasource_open_file(owner->ds, path.toLocal8Bit(), as_open_options(mode));
    return QIODevice::open(mode);
}

void ResFileW::close() {
    QIODevice::close();
    if (inner) {
        resfile_close(inner);
        inner = nullptr;
    }
}

OpenOptions as_open_options(QIODevice::OpenMode om) {
    OpenOptions opts = OpenOptions();
    opts.read = om & QIODevice::ReadOnly;
    opts.write = om & QIODevice::WriteOnly;
    opts.create = ~om & QIODevice::ExistingOnly;
    return opts;
}
\ No newline at end of file

D ui/src/fs/resfile.h => ui/src/fs/resfile.h +0 -39
@@ 1,39 0,0 @@
#ifndef MCRESTOOL_RESFILE_H
#define MCRESTOOL_RESFILE_H

#include <QtCore/QIODevice>
#include "mcrestool_logic.h"

class DataSourceW;

class ResFileW : public QIODevice {
Q_OBJECT

    friend class DataSourceW;

public:
    ~ResFileW() override;

protected:
    explicit ResFileW(DataSourceW* owner, const QString& path, QObject* parent = nullptr);

    qint64 readData(char* data, qint64 maxlen) override;

    qint64 writeData(const char* data, qint64 len) override;

public:
    bool open(OpenMode mode) override;

    void close() override;

private:
    DataSourceW* owner;
    QString path;

    ResFile* inner;

};

OpenOptions as_open_options(QIODevice::OpenMode om);

#endif //MCRESTOOL_RESFILE_H

D ui/src/project/languagetablecontainer.cpp => ui/src/project/languagetablecontainer.cpp +0 -72
@@ 1,72 0,0 @@
#include "languagetablecontainer.h"

LanguageTableContainer::LanguageTableContainer(
    ProjectSource* src,
    const QString& domain,
    QObject* parent
) : QObject(parent),
    src(src),
    lt(new LanguageTableModel(languagetable_create(), this)),
    domain(domain) {
    _persistent = false;
    _changed = false;
    _deleted = false;

    connect(lt, SIGNAL(changed(
                           const QString&, const QString&, const QString&)), this, SLOT(on_changed()));
}

LanguageTableContainer::~LanguageTableContainer() {
    languagetable_delete(lt->data());
}

LanguageTableModel* LanguageTableContainer::language_table() {
    return lt;
}

bool LanguageTableContainer::persistent() const {
    return _persistent;
}

bool LanguageTableContainer::changed() const {
    return _changed;
}

bool LanguageTableContainer::read_only() const {
    return src->read_only();
}

void LanguageTableContainer::delete_file() {
    if (read_only()) return;

    if (persistent()) {
        QStringList files = src->data_source()->list_dir("/assets/" + domain + "/lang/");
        for (const auto& str: files) {
            src->data_source()->delete_file(str);
        }
    }
    _deleted = true;
    _persistent = false;
}

void LanguageTableContainer::save() {
    if (read_only()) return;

    languagetable_write_to(lt->data(), src->data_source()->inner(), ("/assets/" + domain + "/lang/").toLocal8Bit());

    _persistent = true;
    _changed = false;
}

void LanguageTableContainer::load() {
    languagetable_load_into(lt->data(), src->data_source()->inner(),  ("/assets/" + domain + "/lang/").toLocal8Bit());

    _persistent = true;
    _changed = false;
    emit lt->layoutChanged();
}

void LanguageTableContainer::on_changed() {
    _changed = true;
    emit changed();
}

D ui/src/project/project.h => ui/src/project/project.h +0 -9
@@ 1,9 0,0 @@
#ifndef MCRESTOOL_PROJECT_H
#define MCRESTOOL_PROJECT_H

// these two store pointers to each other so we define them beforehand
class LanguageTableContainer;

class ProjectSource;

#endif //MCRESTOOL_PROJECT_H

D ui/src/project/projectsource.cpp => ui/src/project/projectsource.cpp +0 -20
@@ 1,20 0,0 @@
#include "projectsource.h"

ProjectSource::ProjectSource(DataSourceW* src, const QString& name, QObject* parent) : QObject(parent), name(name), src(src) {

}

bool ProjectSource::read_only() {
    return src->read_only();
}

LanguageTableContainer* ProjectSource::get_language_table(const QString& domain) {
    if (!languages.contains(domain)) {
        languages.insert(domain, new LanguageTableContainer(this, domain, this));
    }
    return languages.value(domain, nullptr);
}

DataSourceW* ProjectSource::data_source() {
    return src;
}

D ui/src/project/projectsource.h => ui/src/project/projectsource.h +0 -36
@@ 1,36 0,0 @@
#ifndef MCRESTOOL_PROJECTSOURCE_H
#define MCRESTOOL_PROJECTSOURCE_H

#include "project.h"

#include "src/fs/datasource.h"
#include "languagetablecontainer.h"
#include <QObject>
#include <QMap>

// use QFileSystemWatcher?

class ProjectSource : public QObject {
Q_OBJECT

public:
    explicit ProjectSource(DataSourceW* src, const QString& name, QObject* parent = nullptr);

    LanguageTableContainer* get_language_table(const QString& domain);

    bool read_only();

    bool changed();

    DataSourceW* data_source();

private:
    QString name;

    DataSourceW* src;

    QMap<QString, LanguageTableContainer*> languages;

};

#endif //MCRESTOOL_PROJECTSOURCE_H

D ui/src/util.cpp => ui/src/util.cpp +0 -8
@@ 1,8 0,0 @@
#include <QMessageBox>
#include "mcrestool_logic.h"

void check_for_error(QWidget* parent) {
    if (MCRT_ERROR) {
        QMessageBox::critical(parent, "Error", QString("%2 (error %1)").arg(MCRT_ERROR).arg(MCRT_ERROR_TEXT));
    }
}