Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ jobs:
node-version: 16
- name: get version
run: echo "PACKAGE_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
- name: get version description
id: description
uses: juliangruber/read-file-action@v1
with:
path: ./description.md
- name: create release
id: create-release
uses: actions/github-script@v6
Expand All @@ -30,8 +35,8 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: `app-v${process.env.PACKAGE_VERSION}`,
name: `Desktop App v${process.env.PACKAGE_VERSION}`,
body: 'Take a look at the assets to download and install this app.',
name: `UnityExtractor v${process.env.PACKAGE_VERSION}`,
body: '${{ steps.description.outputs.content }}',
draft: true,
prerelease: false
})
Expand Down Expand Up @@ -61,7 +66,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: install frontend dependencies
run: npm install # change this to npm or pnpm depending on which one you use
run: npm install
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
# Tauri + Vue 3 + TypeScript
# UnityExtractor

This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
A Unity asset extractor base on `Tauri` + `Vue` + `Vuetify`

## Recommended IDE Setup
Make All Asset Extract action in **ONE** App

- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)

## Type Support For `.vue` Imports in TS
## App current appearance

Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
![Main Page](./preview/preview-main.png)
![Preview Page](./preview/preview-preview.png)

1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
## Feature

You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
- [ ] Support most Unity asset type load & Preview & direct export
- [x] Texture
- [x] TextAsset
- [x] Sprite
- [ ] Support custom script load & execute
- [ ] Load via c-abi
- [ ] LUA support
- [ ] WASM script support

## Contribution
- You can feel free to use English or Chinese to open an issue or pull request.

## relate repos
- [unity-rs](https://github.com/yuanyan3060/unity-rs): Rust implement unity unpacker
- [AssetStudio](https://github.com/Perfare/AssetStudio): Populate asset extractor tool
24 changes: 24 additions & 0 deletions description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## UnityExtractor v0.1.1

## :sparkles: New Feature

1. support extract following unity asset type
- Texture2D
- TextAsset
- Sprite

2. support export extracted information

Click the prepend icon of each item in the list,
then select `Export Full` or `Save Object`.
the `UnityExtractor` will export all information to the target directory

3. Preview Image or Encodable String

Click any Item of a Unity Asset, if it is an Image or Utf8 string bytes,
you can preview the value in a independence windows

## :bug: Bug Fix

- fix: unpredicted exit when loading non unity asset file
- fix: Exit main window not exit
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "unity-extractor",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"scripts": {
"serve": "vite preview",
Expand Down
8 changes: 4 additions & 4 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
name = "unity-extractor"
version = "0.1.0"
version = "0.1.1"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
authors = ["FrozenString<frozenstringstable@gmail.com>"]
license = "AGPL"
repository = "https://github.com/UnityExtractor-rs/UnityExtrator"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand All @@ -16,7 +16,7 @@ tauri-build = { version = "1.4", features = [] }
tauri = { version = "1.4", features = [ "fs-all", "dialog-all", "shell-open"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
unity_rs = { version = "0.1.0",git = "https://github.com/yuanyan3060/unity-rs.git" ,rev = "0cbfac1" }
unity_rs = { version = "0.1.0",git = "https://github.com/Goodjooy/unity-rs.git" ,rev = "3c516b5" }
once_cell = "1.18.0"
image = "0.24.7"
bytes = "1.4.0"
Expand Down
9 changes: 8 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
mod preview;
mod unity;

use tauri::{AppHandle, Manager};
use tauri::{AppHandle, Manager, WindowEvent::CloseRequested};

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
Expand All @@ -31,6 +31,13 @@ fn main() {
])
.setup(|app| {
let windows = app.get_window("main").expect("Main windows not found");
let app_handle = app.app_handle();
windows.on_window_event(move |event| {
if let CloseRequested { api, .. } = event {
api.prevent_close();
app_handle.exit(0)
}
});
#[cfg(debug_assertions)]
windows.open_devtools();

Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub struct ImagePreviewPayload {
pub image_url: String,
pub name: String,
pub description: Option<String>,
pub fromat: String,
pub format: String,
pub width: Option<i32>,
pub height: Option<i32>,
}
Expand Down
11 changes: 6 additions & 5 deletions src-tauri/src/unity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ use dashmap::{self, DashMap};

use uuid::Uuid;

use self::dto::StoreUnityBoundle;
use self::dto::StoreUnityBundle;
mod commands;
mod dto;
mod error;
mod loaded_object;
mod loader;
mod utils;
static OPEND_UNITY_ASSET: OnceLock<dashmap::DashMap<Uuid, StoreUnityBoundle>> = OnceLock::new();
static OPENED_UNITY_ASSET: OnceLock<DashMap<Uuid, StoreUnityBundle>> = OnceLock::new();

type UnityResult<T> = Result<T, error::Error>;

fn get_opend_unity_asset() -> &'static DashMap<Uuid, StoreUnityBoundle> {
OPEND_UNITY_ASSET.get_or_init(DashMap::new)
fn get_opened_unity_asset() -> &'static DashMap<Uuid, StoreUnityBundle> {
OPENED_UNITY_ASSET.get_or_init(DashMap::new)
}

pub use commands::{
export_bundle, export_file_type, export_object, load_unity_asset, preview_object,sync_loaded_asset
export_bundle, export_file_type, export_object, load_unity_asset, preview_object,
sync_loaded_asset,
};
2 changes: 1 addition & 1 deletion src-tauri/src/unity/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ mod import;
mod preview;

pub use export::{export_bundle, export_file_type, export_object};
pub use import::{load_unity_asset,sync_loaded_asset};
pub use import::{load_unity_asset, sync_loaded_asset};
pub use preview::preview_object;
22 changes: 15 additions & 7 deletions src-tauri/src/unity/commands/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ use std::path::PathBuf;
use tauri::{command, AppHandle};
use uuid::Uuid;

use crate::{unity::{error::Error, get_opend_unity_asset, UnityResult, utils::get_object}, loading_done};
use crate::{
loading_done,
unity::{error::Error, get_opened_unity_asset, utils::get_object, UnityResult},
};

#[command(async)]
pub async fn export_bundle(app:AppHandle, dir: String, asset_id: Uuid) -> UnityResult<()> {
pub async fn export_bundle(app: AppHandle, dir: String, asset_id: Uuid) -> UnityResult<()> {
let path = PathBuf::from(dir);
if !path.exists() {
std::fs::create_dir_all(&path)?;
}

let asset = get_opend_unity_asset()
let asset = get_opened_unity_asset()
.get(&asset_id)
.ok_or_else(|| Error::AssetNotLoaded(asset_id.clone()))?;
.ok_or_else(|| Error::AssetNotLoaded(asset_id))?;

for (class, obj) in &asset.objects {
let payload = &obj.payload;
Expand All @@ -35,15 +38,20 @@ pub struct FileExtension {
}
#[command]
pub fn export_file_type(asset_id: Uuid, object_id: usize) -> UnityResult<FileExtension> {
let object = get_object(get_opend_unity_asset(),asset_id, object_id)?;
let object = get_object(get_opened_unity_asset(), asset_id, object_id)?;
Ok(FileExtension {
name: object.payload.get_file_extension_name(),
wildcard: object.payload.get_file_extension(),
})
}
#[command(async)]
pub async fn export_object(app:AppHandle, filename: String, asset_id: Uuid, object_id: usize) -> UnityResult<()> {
let object = get_object(get_opend_unity_asset(),asset_id, object_id)?;
pub async fn export_object(
app: AppHandle,
filename: String,
asset_id: Uuid,
object_id: usize,
) -> UnityResult<()> {
let object = get_object(get_opened_unity_asset(), asset_id, object_id)?;
object.payload.save(filename)?;
loading_done(app)?;
Ok(())
Expand Down
15 changes: 7 additions & 8 deletions src-tauri/src/unity/commands/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,36 @@ use uuid::Uuid;
use crate::{
loading_done,
unity::{
dto::{StoreUnityBoundle, UnityAsset},
dto::{StoreUnityBundle, UnityAsset},
error::Error,
get_opend_unity_asset, UnityResult,
get_opened_unity_asset, UnityResult,
},
};

#[command(async)]
pub async fn load_unity_asset(app: AppHandle, path: String) -> UnityResult<UnityAsset> {
let key = Uuid::new_v5(&Uuid::NAMESPACE_OID, path.as_bytes());

let asset = if let Some(loaded) = get_opend_unity_asset().get(&key) {
let asset = if let Some(loaded) = get_opened_unity_asset().get(&key) {
UnityAsset::from_store(&key, &loaded)
} else {
let filename = PathBuf::from(path.clone());
let filename = filename
.file_name()
.map(|s| s.to_str())
.flatten()
.and_then(|s| s.to_str())
.ok_or_else(|| Error::UnknownPath(path.clone()))?;
//load
let store = StoreUnityBoundle::load(&path, filename)?;
let store = StoreUnityBundle::load(&path, filename)?;
let asset = UnityAsset::from_store(&key, &store);
get_opend_unity_asset().insert(key, store);
get_opened_unity_asset().insert(key, store);
asset
};
loading_done(app)?;
Ok(asset)
}
#[command]
pub fn sync_loaded_asset() -> Vec<UnityAsset> {
get_opend_unity_asset()
get_opened_unity_asset()
.iter()
.map(|item| UnityAsset::from_store(item.key(), item.value()))
.collect()
Expand Down
14 changes: 7 additions & 7 deletions src-tauri/src/unity/commands/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@ use uuid::Uuid;
use crate::{
preview::{preview_image, preview_text, ImagePreviewPayload, TextPreviewPayload},
unity::{
dto::LoadedPyaload, error::Error, get_opend_unity_asset, loaded_object::LoadedObject,
dto::LoadedPayload, error::Error, get_opened_unity_asset, loaded_object::LoadedObject,
utils::get_object, UnityResult,
},
};

#[command(async)]
pub async fn preview_object(app: AppHandle, asset_id: Uuid, object_id: usize) -> UnityResult<()> {
// load
let object = get_object(get_opend_unity_asset(), asset_id, object_id)?;
let object = get_object(get_opened_unity_asset(), asset_id, object_id)?;

// encoding image
match &object.payload {
LoadedPyaload::Image(img) => {
LoadedPayload::Image(img) => {
let preview = handle_image_preview(img, &object)?;
preview_image(app, preview).await?;
}
LoadedPyaload::Text(text) => {
let preview = handle_text_preview(&text, &object)?;
LoadedPayload::Text(text) => {
let preview = handle_text_preview(text, &object)?;
preview_text(app, preview).await?;
}
LoadedPyaload::Raw(_) => {
LoadedPayload::Raw(_) => {
eprintln!("Raw Preview not support");
Err(Error::RawNotPreviewAble)?;
}
Expand All @@ -53,7 +53,7 @@ fn handle_image_preview(
width: Some(img.width() as _),
height: Some(img.height() as _),
image_url: base64_encoded,
fromat: "Png".to_string(),
format: "Png".to_string(),
};
Ok(payload)
}
Expand Down
Loading