├── .gitignore ├── .cargo └── config ├── resources ├── predef.redscripts └── patches.reds ├── Cargo.toml ├── core ├── src │ ├── lib.rs │ ├── error.rs │ ├── encode.rs │ ├── decode.rs │ ├── ast.rs │ ├── bundle.rs │ └── definition.rs └── Cargo.toml ├── scc ├── Cargo.toml └── src │ └── main.rs ├── .rustfmt.toml ├── decompiler ├── Cargo.toml └── src │ ├── files.rs │ ├── lib.rs │ └── print.rs ├── cli ├── Cargo.toml └── src │ └── main.rs ├── compiler ├── Cargo.toml └── src │ ├── source_map.rs │ ├── lib.rs │ ├── parser.rs │ └── scope.rs ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── LICENSE ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /debug 3 | .idea/ 4 | -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | rustflags = ["-C", "target-feature=+crt-static"] 3 | -------------------------------------------------------------------------------- /resources/predef.redscripts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/redscript/master/resources/predef.redscripts -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "core", 4 | "compiler", 5 | "decompiler", 6 | "cli", 7 | "scc" 8 | ] 9 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(result_cloned, const_fn)] 2 | 3 | pub mod ast; 4 | pub mod bundle; 5 | pub mod bytecode; 6 | pub mod decode; 7 | pub mod definition; 8 | pub mod encode; 9 | pub mod error; 10 | -------------------------------------------------------------------------------- /scc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scc" 3 | version = "0.1.0" 4 | authors = ["jac3km4 "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | redscript = { path = "../core" } 9 | redscript-compiler = { path = "../compiler" } 10 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | newline_style = "Unix" 3 | use_field_init_shorthand = true 4 | overflow_delimited_expr = true 5 | format_strings = true 6 | group_imports = "StdExternalCrate" 7 | imports_layout = "Horizontal" 8 | imports_granularity = "Module" 9 | -------------------------------------------------------------------------------- /decompiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redscript-decompiler" 3 | version = "0.1.0" 4 | authors = ["jac3km4 "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | redscript = { path = "../core" } 12 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redscript-cli" 3 | version = "0.1.0" 4 | authors = ["jac3km4 "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | redscript = { path = "../core" } 9 | redscript-decompiler = { path = "../decompiler" } 10 | redscript-compiler = { path = "../compiler" } 11 | gumdrop = "0.8" 12 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redscript" 3 | version = "0.1.0" 4 | authors = ["jac3km4 "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | byteorder = "1.4" 12 | modular-bitfield = "0.11" 13 | crc32fast = "1.2" 14 | strum = { version = "0.20", features = ["derive"] } 15 | -------------------------------------------------------------------------------- /compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redscript-compiler" 3 | version = "0.1.0" 4 | authors = ["jac3km4 "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | redscript = { path = "../core" } 12 | peg = "0.6" 13 | im = "15" 14 | strum = { version = "0.20", features = ["derive"] } 15 | walkdir = "2" 16 | colored = "2" 17 | -------------------------------------------------------------------------------- /core/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, io}; 2 | 3 | use crate::ast::Pos; 4 | 5 | #[derive(Debug)] 6 | pub enum Error { 7 | IOError(io::Error), 8 | DecompileError(String), 9 | SyntaxError(String), 10 | CompileError(String, Pos), 11 | FunctionResolutionError(String, Pos), 12 | PoolError(String), 13 | FormatError(fmt::Error), 14 | } 15 | 16 | impl Error { 17 | pub fn eof(hint: String) -> Error { 18 | Error::IOError(io::Error::new(io::ErrorKind::UnexpectedEof, hint)) 19 | } 20 | } 21 | 22 | impl From for Error { 23 | fn from(err: io::Error) -> Self { 24 | Error::IOError(err) 25 | } 26 | } 27 | 28 | impl From for Error { 29 | fn from(err: fmt::Error) -> Self { 30 | Error::FormatError(err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Prepare toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: nightly 17 | override: true 18 | - name: copy cargo config 19 | run: xcopy /H /E /Y .\\.cargo C:\\Rust\\.cargo 20 | - name: Build 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: build 24 | args: --all-features 25 | - name: Test 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: test 29 | - name: Archive artifacts 30 | uses: actions/upload-artifact@v2 31 | with: 32 | path: | 33 | target/debug/scc.exe 34 | target/debug/redscript-cli.exe 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 jac3km4 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build: 12 | runs-on: windows-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Prepare toolchain 16 | uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: nightly 19 | override: true 20 | - name: copy cargo config 21 | run: xcopy /H /E /Y .\\.cargo C:\\Rust\\.cargo 22 | - name: Build 23 | uses: actions-rs/cargo@v1 24 | with: 25 | command: build 26 | args: --release --all-features 27 | - name: Test 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: test 31 | - name: Create zip of result 32 | run: | 33 | 7z a -mx=9 release.zip ./target/release/scc.exe ./target/release/redscript-cli.exe 34 | - name: Upload Release Asset 35 | id: upload-release-asset 36 | uses: actions/upload-release-asset@v1 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | upload_url: ${{ github.event.release.upload_url }} 41 | asset_path: ./release.zip 42 | asset_name: release-${{ github.event.release.tag_name }}.zip 43 | asset_content_type: application/zip 44 | -------------------------------------------------------------------------------- /scc/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufReader, BufWriter}; 3 | use std::path::PathBuf; 4 | 5 | use redscript::bundle::ScriptBundle; 6 | use redscript::error::Error; 7 | use redscript_compiler::Compiler; 8 | 9 | fn main() -> Result<(), Error> { 10 | // the way cyberpunk passes CLI args is broken, this is a workaround 11 | let args: Vec = std::env::args().skip(1).collect(); 12 | match &args[..] { 13 | [cmd, path_str, ..] if cmd == "-compile" => { 14 | let script_path = PathBuf::from(path_str.split('"').next().unwrap()); 15 | 16 | let cache_path = script_path.parent().unwrap().join("cache"); 17 | let bundle_path = cache_path.join("final.redscripts"); 18 | let backup_path = cache_path.join("final.redscripts.bk"); 19 | if !backup_path.exists() { 20 | std::fs::rename(&bundle_path, &backup_path)?; 21 | } 22 | 23 | let mut bundle: ScriptBundle = ScriptBundle::load(&mut BufReader::new(File::open(&backup_path)?))?; 24 | let mut compiler = Compiler::new(&mut bundle.pool)?; 25 | 26 | compiler.compile_all(&script_path)?; 27 | bundle.save(&mut BufWriter::new(File::create(&bundle_path)?))?; 28 | 29 | println!("Output successfully saved in {:?}", cache_path); 30 | Ok(()) 31 | } 32 | _ => { 33 | println!("Invalid arguments: {:?}", args); 34 | Ok(()) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redscript 2 | Toolkit for working with scripts used by REDengine in Cyberpunk 2077. 3 | Currently includes a compiler, a decompiler and a disassembler. 4 | 5 | ## usage 6 | ``` 7 | Usage: 8 | decompile [opts] 9 | compile [opts] 10 | Compiler options: 11 | -s, --src SRC source file or directory 12 | -b, --bundle BUNDLE redscript bundle file to read 13 | -o, --output OUTPUT redscript bundle file to write 14 | Decompiler options: 15 | -i --input INPUT input redscripts bundle file 16 | -o, --output OUTPUT output file or directory 17 | -m, --mode MODE dump mode (one of: 'ast', 'bytecode' or 'code') 18 | -f, --dump-files split into individual files (doesn't work for everything yet) 19 | -v, --verbose verbose output (include implicit conversions) 20 | Lint options: 21 | -s, --src SRC source file or directory 22 | -b, --bundle BUNDLE redscript bundle file to use, optional 23 | ``` 24 | 25 | You can build the project and decompile all scripts in one command: 26 | ```bash 27 | cargo run --bin redscript-cli --release -- decompile -i '/mnt/d/games/Cyberpunk 2077/r6/cache/final.redscript' -o classes.redscript 28 | ``` 29 | *__note__: current version requires nightly version of rust (`rustup default nightly`)* 30 | 31 | ## language 32 | The scripts use a Swift-like language: 33 | ```swift 34 | public final const func GetReprimandPerformer(opt target: EntityID) -> ref { 35 | let agent: Agent; 36 | let performer: ref; 37 | let ps: ref; 38 | if !EntityID.IsDefined(target) { 39 | target = GetPlayer(this.GetGameInstance()).GetEntityID(); 40 | }; 41 | if this.m_agentsRegistry.GetReprimandPerformer(target, agent) { 42 | ps = this.GetPS(agent.link); 43 | return ps.GetOwnerEntityWeak() as GameObject; 44 | }; 45 | return null; 46 | } 47 | ``` 48 | 49 | ## integrating with the game 50 | You can integrate this compiler with the game and make it compile your scripts on startup. To set that up, you need to follow these steps: 51 | 52 | - Create `Cyberpunk 2077\engine\config\base\scripts.ini` file with the contents below
53 | ```ini 54 | [Scripts] 55 | EnableCompilation = "true" 56 | ``` 57 | - Place the `scc.exe` tool in the following location:
58 | ``Cyberpunk 2077\engine\tools\scc.exe``
59 | *(The scc executable can be found in Releases)* 60 | 61 | - Now you need to add some scripts. The compiler will look for scripts in `Cyberpunk 2077\r6\scripts\`
62 | You can copy the script below to `Cyberpunk 2077\r6\scripts\lights.reds` as an example: 63 | 64 | ```swift 65 | @replaceMethod(CrossingLight) 66 | protected final func PlayTrafficNotificationSound(status: worldTrafficLightColor) { 67 | return; 68 | } 69 | ``` 70 | *this mod will disable the walk don't walk crosswalk audio* 71 | 72 | You can find more script examples in `resources/patches.reds` in this repository. 73 | -------------------------------------------------------------------------------- /decompiler/src/files.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::iter; 3 | use std::path::Path; 4 | 5 | use redscript::bundle::{ConstantPool, PoolIndex}; 6 | use redscript::definition::{Definition, DefinitionValue}; 7 | 8 | pub struct FileIndex<'a> { 9 | file_map: HashMap, HashSet>>, 10 | pool: &'a ConstantPool, 11 | } 12 | 13 | impl<'a> FileIndex<'a> { 14 | pub fn from_pool(pool: &'a ConstantPool) -> FileIndex { 15 | let mut file_map: HashMap, HashSet>> = HashMap::new(); 16 | 17 | for (idx, def) in pool.definitions() { 18 | if let Some(source) = def.source() { 19 | let root_idx = if def.parent.is_undefined() { idx } else { def.parent }; 20 | file_map 21 | .entry(source.file) 22 | .and_modify(|vec| { 23 | vec.insert(root_idx); 24 | }) 25 | .or_insert_with(|| iter::once(root_idx).collect()); 26 | } 27 | } 28 | 29 | FileIndex { file_map, pool } 30 | } 31 | 32 | pub fn iter(&'a self) -> impl Iterator> { 33 | let source_files = self.file_map.iter().filter_map(move |(idx, children)| { 34 | if let DefinitionValue::SourceFile(ref file) = self.pool.definition(*idx).unwrap().value { 35 | let mut definitions: Vec<&Definition> = children 36 | .iter() 37 | .filter_map(|child| self.pool.definition(*child).ok()) 38 | .collect(); 39 | definitions.sort_by_key(|def| def.first_line(self.pool).unwrap_or(0)); 40 | 41 | let entry = FileEntry { 42 | path: &file.path, 43 | definitions, 44 | }; 45 | Some(entry) 46 | } else { 47 | None 48 | } 49 | }); 50 | 51 | iter::once(self.orphans()).chain(source_files) 52 | } 53 | 54 | fn orphans(&'a self) -> FileEntry<'a> { 55 | let definitions = self 56 | .pool 57 | .definitions() 58 | .filter(|(_, def)| match &def.value { 59 | DefinitionValue::Class(cls) if cls.functions.is_empty() || cls.flags.is_native() => true, 60 | DefinitionValue::Enum(_) => true, 61 | DefinitionValue::Function(fun) if def.parent == PoolIndex::UNDEFINED && fun.flags.is_native() => true, 62 | _ => false, 63 | }) 64 | .map(|(_, def)| def) 65 | .collect(); 66 | FileEntry { 67 | path: Path::new("orphans.script"), 68 | definitions, 69 | } 70 | } 71 | } 72 | 73 | pub struct FileEntry<'a> { 74 | pub path: &'a Path, 75 | pub definitions: Vec<&'a Definition>, 76 | } 77 | -------------------------------------------------------------------------------- /core/src/encode.rs: -------------------------------------------------------------------------------- 1 | use std::convert::{TryFrom, TryInto}; 2 | use std::fmt::Debug; 3 | use std::io; 4 | 5 | use byteorder::{LittleEndian, WriteBytesExt}; 6 | 7 | pub trait Encode { 8 | fn encode(output: &mut O, value: &Self) -> io::Result<()>; 9 | } 10 | 11 | impl Encode for i64 { 12 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 13 | output.write_i64::(*value) 14 | } 15 | } 16 | 17 | impl Encode for i32 { 18 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 19 | output.write_i32::(*value) 20 | } 21 | } 22 | 23 | impl Encode for i16 { 24 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 25 | output.write_i16::(*value) 26 | } 27 | } 28 | 29 | impl Encode for i8 { 30 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 31 | output.write_i8(*value) 32 | } 33 | } 34 | 35 | impl Encode for u64 { 36 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 37 | output.write_u64::(*value) 38 | } 39 | } 40 | 41 | impl Encode for u32 { 42 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 43 | output.write_u32::(*value) 44 | } 45 | } 46 | 47 | impl Encode for u16 { 48 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 49 | output.write_u16::(*value) 50 | } 51 | } 52 | 53 | impl Encode for u8 { 54 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 55 | output.write_u8(*value) 56 | } 57 | } 58 | 59 | impl Encode for bool { 60 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 61 | output.write_u8(if *value { 1 } else { 0 }) 62 | } 63 | } 64 | 65 | impl Encode for f64 { 66 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 67 | output.write_f64::(*value) 68 | } 69 | } 70 | 71 | impl Encode for f32 { 72 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 73 | output.write_f32::(*value) 74 | } 75 | } 76 | 77 | impl Encode for &str { 78 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 79 | output.write_all(value.as_bytes())?; 80 | output.write_u8(0) 81 | } 82 | } 83 | 84 | impl Encode for [u8; N] { 85 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 86 | output.write_all(value) 87 | } 88 | } 89 | 90 | pub trait EncodeExt: io::Write + Sized { 91 | fn encode(&mut self, value: &A) -> io::Result<()> { 92 | Encode::encode(self, value) 93 | } 94 | 95 | fn encode_slice(&mut self, value: &[A]) -> io::Result<()> { 96 | for elem in value { 97 | self.encode(elem)? 98 | } 99 | Ok(()) 100 | } 101 | 102 | fn encode_slice_prefixed, A: Encode>(&mut self, value: &[A]) -> io::Result<()> 103 | where 104 | S::Error: Debug, 105 | { 106 | self.encode::(&value.len().try_into().expect("Size overflow"))?; 107 | self.encode_slice(value) 108 | } 109 | 110 | fn encode_str(&mut self, value: &str) -> io::Result<()> { 111 | self.write_all(value.as_bytes()) 112 | } 113 | 114 | fn encode_str_prefixed>(&mut self, value: &str) -> io::Result<()> 115 | where 116 | S::Error: Debug, 117 | { 118 | self.encode_slice_prefixed::(value.as_bytes()) 119 | } 120 | } 121 | 122 | impl EncodeExt for O {} 123 | -------------------------------------------------------------------------------- /core/src/decode.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use byteorder::{LittleEndian, ReadBytesExt}; 4 | 5 | pub trait Decode: Sized { 6 | fn decode(input: &mut I) -> io::Result; 7 | } 8 | 9 | impl Decode for i64 { 10 | fn decode(input: &mut I) -> io::Result { 11 | input.read_i64::() 12 | } 13 | } 14 | 15 | impl Decode for i32 { 16 | fn decode(input: &mut I) -> io::Result { 17 | input.read_i32::() 18 | } 19 | } 20 | 21 | impl Decode for i16 { 22 | fn decode(input: &mut I) -> io::Result { 23 | input.read_i16::() 24 | } 25 | } 26 | 27 | impl Decode for i8 { 28 | fn decode(input: &mut I) -> io::Result { 29 | input.read_i8() 30 | } 31 | } 32 | 33 | impl Decode for u64 { 34 | fn decode(input: &mut I) -> io::Result { 35 | input.read_u64::() 36 | } 37 | } 38 | 39 | impl Decode for u32 { 40 | fn decode(input: &mut I) -> io::Result { 41 | input.read_u32::() 42 | } 43 | } 44 | 45 | impl Decode for u16 { 46 | fn decode(input: &mut I) -> io::Result { 47 | input.read_u16::() 48 | } 49 | } 50 | 51 | impl Decode for u8 { 52 | fn decode(input: &mut I) -> io::Result { 53 | input.read_u8() 54 | } 55 | } 56 | 57 | impl Decode for bool { 58 | fn decode(input: &mut I) -> io::Result { 59 | Ok(input.read_u8()? != 0) 60 | } 61 | } 62 | 63 | impl Decode for f64 { 64 | fn decode(input: &mut I) -> io::Result { 65 | input.read_f64::() 66 | } 67 | } 68 | 69 | impl Decode for f32 { 70 | fn decode(input: &mut I) -> io::Result { 71 | input.read_f32::() 72 | } 73 | } 74 | 75 | impl Decode for String { 76 | fn decode(input: &mut I) -> io::Result { 77 | let mut str = String::new(); 78 | let mut c: u8; 79 | loop { 80 | c = input.read_u8()?; 81 | if c == 0 { 82 | break; 83 | } 84 | str.push(c.into()); 85 | } 86 | Ok(str) 87 | } 88 | } 89 | 90 | impl Decode for [u8; N] { 91 | fn decode(input: &mut I) -> io::Result { 92 | let mut buf: [u8; N] = [0; N]; 93 | input.read_exact(&mut buf)?; 94 | Ok(buf) 95 | } 96 | } 97 | 98 | pub trait DecodeExt: io::Read + Sized { 99 | fn decode(&mut self) -> io::Result { 100 | Decode::decode(self) 101 | } 102 | 103 | fn decode_vec, A: Decode>(&mut self, count: S) -> io::Result> { 104 | let size = count.into() as usize; 105 | let mut vec = Vec::with_capacity(size); 106 | for _ in 0..size { 107 | vec.push(self.decode()?); 108 | } 109 | Ok(vec) 110 | } 111 | 112 | fn decode_vec_prefixed, A: Decode>(&mut self) -> io::Result> { 113 | let size: S = self.decode()?; 114 | self.decode_vec(size) 115 | } 116 | 117 | fn decode_bytes>(&mut self, count: S) -> io::Result> { 118 | let size = count.into() as usize; 119 | let mut vec = Vec::with_capacity(size); 120 | unsafe { vec.set_len(size) } 121 | self.read_exact(&mut vec)?; 122 | Ok(vec) 123 | } 124 | 125 | fn decode_str>(&mut self, count: S) -> io::Result { 126 | String::from_utf8(self.decode_bytes(count)?).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) 127 | } 128 | 129 | fn decode_str_prefixed>(&mut self) -> io::Result { 130 | let size: S = self.decode()?; 131 | self.decode_str(size) 132 | } 133 | } 134 | 135 | impl DecodeExt for I {} 136 | -------------------------------------------------------------------------------- /compiler/src/source_map.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::cmp::Ordering; 3 | use std::ops::Range; 4 | use std::path::PathBuf; 5 | 6 | use redscript::ast::Pos; 7 | 8 | pub struct Files { 9 | files: Vec, 10 | sources: String, 11 | } 12 | 13 | impl Files { 14 | pub fn new() -> Files { 15 | Files { 16 | files: vec![], 17 | sources: String::new(), 18 | } 19 | } 20 | 21 | pub fn sources(&self) -> &str { 22 | &self.sources 23 | } 24 | 25 | pub fn add(&mut self, path: PathBuf, source: &str) { 26 | let low = self.files.last().map(|f| f.high).unwrap_or(Pos(0)); 27 | let high = low + source.len(); 28 | let mut lines = vec![]; 29 | for (offset, _) in source.match_indices('\n') { 30 | lines.push(low + offset + 1); 31 | } 32 | let file = File { 33 | path, 34 | lines: NonEmptyVec(low, lines), 35 | high, 36 | }; 37 | self.sources.push_str(source); 38 | self.files.push(file) 39 | } 40 | 41 | pub fn enclosing_line(&self, loc: &Location) -> &str { 42 | loc.file.enclosing_line(loc.position.line, self.sources()) 43 | } 44 | 45 | pub fn lookup(&self, pos: Pos) -> Option { 46 | let index = self 47 | .files 48 | .binary_search_by(|file| match file.span() { 49 | Span { low, .. } if low > pos => Ordering::Greater, 50 | Span { high, .. } if high < pos => Ordering::Less, 51 | _ => Ordering::Equal, 52 | }) 53 | .ok()?; 54 | let file = self.files.get(index)?; 55 | let position = file.lookup(pos, &self.sources)?; 56 | let result = Location { file, position }; 57 | Some(result) 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | pub struct File { 63 | path: PathBuf, 64 | lines: NonEmptyVec, 65 | high: Pos, 66 | } 67 | 68 | impl File { 69 | fn span(&self) -> Span { 70 | let low = self.lines.0; 71 | Span { low, high: self.high } 72 | } 73 | 74 | fn lookup(&self, pos: Pos, source: &str) -> Option { 75 | let res = self.lines.1.binary_search(&pos).map(|p| p + 1); 76 | let index = res.err().or_else(|| res.ok()).unwrap(); 77 | let (line, low) = if pos < self.lines.0 { 78 | None? 79 | } else if index == 0 { 80 | (0, self.lines.0) 81 | } else { 82 | (index, *self.lines.1.get(index - 1)?) 83 | }; 84 | let line_span = Span { low, high: pos }; 85 | let col = self.source_slice(line_span, source).chars().count(); 86 | let loc = FilePosition { line, col }; 87 | Some(loc) 88 | } 89 | 90 | fn enclosing_line<'a>(&self, line: usize, source: &'a str) -> &'a str { 91 | let low = if line == 0 { 92 | self.lines.0 93 | } else { 94 | self.lines.1[line - 1] 95 | }; 96 | let high = self.lines.1.get(line).cloned().unwrap_or(self.high); 97 | let span = Span { low, high }; 98 | self.source_slice(span, source) 99 | } 100 | 101 | fn source_slice<'a>(&self, span: Span, source: &'a str) -> &'a str { 102 | let range: Range = span.into(); 103 | &source[range] 104 | } 105 | } 106 | 107 | #[derive(Debug)] 108 | struct NonEmptyVec(pub A, pub Vec); 109 | 110 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 111 | pub struct Span { 112 | low: Pos, 113 | high: Pos, 114 | } 115 | 116 | impl From for Range { 117 | fn from(span: Span) -> Self { 118 | Range { 119 | start: span.low.0 as usize, 120 | end: span.high.0 as usize, 121 | } 122 | } 123 | } 124 | 125 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 126 | pub struct FilePosition { 127 | pub line: usize, 128 | pub col: usize, 129 | } 130 | 131 | impl fmt::Display for FilePosition { 132 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 133 | write!(f, "{}:{}", self.line + 1, self.col + 1) 134 | } 135 | } 136 | 137 | #[derive(Debug)] 138 | pub struct Location<'a> { 139 | pub file: &'a File, 140 | pub position: FilePosition, 141 | } 142 | 143 | impl<'a> fmt::Display for Location<'a> { 144 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 145 | write!(f, "{}:{}", self.file.path.display(), self.position) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /resources/patches.reds: -------------------------------------------------------------------------------- 1 | 2 | /* multiply all crafting XP by 100 */ 3 | @replaceMethod(CraftingSystem) 4 | private final func ProcessCraftSkill(xpAmount: Int32, craftedItem: StatsObjectID) { 5 | let xpEvent = new ExperiencePointsEvent(); 6 | xpEvent.amount = xpAmount * 100; 7 | xpEvent.type = gamedataProficiencyType.Crafting; 8 | GetPlayer(this.GetGameInstance()).QueueEvent(xpEvent); 9 | } 10 | 11 | /* disable fake underwear when removing clothes */ 12 | @replaceMethod(EquipmentSystemPlayerData) 13 | private final func EvaluateUnderwearVisibility(unequippedItem: ItemID) -> Bool { 14 | return false; 15 | } 16 | 17 | /* allow disassembling of all items */ 18 | @replaceMethod(CraftingSystem) 19 | public final const func CanItemBeDisassembled(itemData: wref) -> Bool { 20 | return true; 21 | } 22 | 23 | /* allow unequipping of all items */ 24 | @replaceMethod(RPGManager) 25 | public final static func CanPartBeUnequipped(itemID: ItemID) -> Bool { 26 | return true; 27 | } 28 | 29 | /* add main menu options */ 30 | @replaceMethod(SingleplayerMenuGameController) 31 | private func PopulateMenuItemList() { 32 | if this.m_savesCount > 0 { 33 | this.AddMenuItem(GetLocalizedText("UI-ScriptExports-Continue0"), PauseMenuAction.QuickLoad); 34 | }; 35 | this.AddMenuItem(GetLocalizedText("UI-ScriptExports-NewGame0"), n"OnNewGame"); 36 | this.AddMenuItem(GetLocalizedText("UI-ScriptExports-LoadGame0"), n"OnLoadGame"); 37 | this.AddMenuItem(GetLocalizedText("UI-Labels-Settings"), n"OnSwitchToSettings"); 38 | this.AddMenuItem(GetLocalizedText("UI-Labels-Credits"), n"OnSwitchToCredits"); 39 | this.AddMenuItem("DEBUG NEW GAME", n"OnDebug"); 40 | this.AddMenuItem("TOGGLE GOG MENU", n"OnGOGProfile"); 41 | this.m_menuListController.Refresh(); 42 | this.SetCursorOverWidget(inkCompoundRef.GetWidgetByIndex(this.m_menuList, 0)); 43 | } 44 | 45 | /* sell edibles as junk automatically at vendors */ 46 | @replaceMethod(FullscreenVendorGameController) 47 | private final func GetSellableJunk() -> array> { 48 | let result: array>; 49 | let sellableItems = this.m_VendorDataManager.GetItemsPlayerCanSell(); 50 | let i: Uint32 = Cast(0); 51 | while i < Cast(ArraySize(sellableItems)) { 52 | let type = RPGManager.GetItemRecord(sellableItems[i].GetID()).ItemType().Type(); 53 | if Equals(type, gamedataItemType.Gen_Junk) || Equals(type, gamedataItemType.Con_Edible) { 54 | ArrayPush(result, sellableItems[i]); 55 | }; 56 | i += Cast(1); 57 | }; 58 | return result; 59 | } 60 | 61 | 62 | /* switch cases fallthrough, break not supported yet (required by UpgradeItem patch) */ 63 | func CalculateCraftingExp(quality: gamedataQuality) -> Int32 { 64 | switch (quality) { 65 | case gamedataQuality.Common: 66 | return TweakDBInterface.GetInt( 67 | t"Constants.CraftingSystem.commonIngredientXP", 0); 68 | case gamedataQuality.Uncommon: 69 | return TweakDBInterface.GetInt( 70 | t"Constants.CraftingSystem.uncommonIngredientXP", 0); 71 | case gamedataQuality.Rare: 72 | return TweakDBInterface.GetInt( 73 | t"Constants.CraftingSystem.rareIngredientXP", 0); 74 | case gamedataQuality.Epic: 75 | return TweakDBInterface.GetInt( 76 | t"Constants.CraftingSystem.epicIngredientXP", 0); 77 | case gamedataQuality.Legendary: 78 | return TweakDBInterface.GetInt( 79 | t"Constants.CraftingSystem.legendaryIngredientXP", 0); 80 | default: 81 | return TweakDBInterface.GetInt( 82 | t"Constants.CraftingSystem.commonIngredientXP", 0); 83 | } 84 | } 85 | 86 | /* updates items right up to current player level, reduces overall upgrade cost */ 87 | @replaceMethod(CraftingSystem) 88 | private final func UpgradeItem(owner: wref, itemID: ItemID) { 89 | let recipeXP: Int32 = 0; 90 | let randF = RandF(); 91 | let statsSystem = GameInstance.GetStatsSystem(this.GetGameInstance()); 92 | let TS = GameInstance.GetTransactionSystem(this.GetGameInstance()); 93 | let itemData = TS.GetItemData(owner, itemID); 94 | let statsObjectId = itemData.GetStatsObjectID(); 95 | let materialRetrieveChance = statsSystem.GetStatValue( 96 | Cast(owner.GetEntityID()), 97 | gamedataStatType.UpgradingMaterialRetrieveChance); 98 | let ingredients = this.GetItemFinalUpgradeCost(itemData); 99 | let i = 0; 100 | while i < ArraySize(ingredients) { 101 | if randF >= materialRetrieveChance { 102 | TS.RemoveItem(owner, ItemID.CreateQuery(ingredients[i].id.GetID()), 103 | ingredients[i].quantity); 104 | } 105 | let ingredientQuality = RPGManager.GetItemQualityFromRecord( 106 | TweakDBInterface.GetItemRecord(ingredients[i].id.GetID())); 107 | recipeXP += CalculateCraftingExp(ingredientQuality) * ingredients[i].quantity; 108 | i += 1; 109 | } 110 | let previousItemUpgrade = itemData.GetStatValueByType(gamedataStatType.WasItemUpgraded); 111 | let itemLevel = itemData.GetStatValueByType(gamedataStatType.ItemLevel) / 10.0; 112 | let playerPowerLevel = statsSystem.GetStatValue(Cast(owner.GetEntityID()), gamedataStatType.PowerLevel); 113 | let newItemUpgrade: Float = Cast(CeilF(previousItemUpgrade + (playerPowerLevel - itemLevel))); 114 | 115 | statsSystem.RemoveAllModifiers(statsObjectId, gamedataStatType.WasItemUpgraded, true); 116 | let mod = RPGManager.CreateStatModifier( 117 | gamedataStatType.WasItemUpgraded, gameStatModifierType.Additive, newItemUpgrade); 118 | statsSystem.AddSavedModifier(statsObjectId, mod); 119 | this.ProcessCraftSkill(recipeXP, statsObjectId); 120 | } 121 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufReader, BufWriter}; 3 | use std::path::PathBuf; 4 | 5 | use gumdrop::Options; 6 | use redscript::bundle::ScriptBundle; 7 | use redscript::definition::DefinitionValue; 8 | use redscript::error::Error; 9 | use redscript_compiler::Compiler; 10 | use redscript_decompiler::files::FileIndex; 11 | use redscript_decompiler::print::{write_definition, OutputMode}; 12 | 13 | #[derive(Debug, Options)] 14 | enum Command { 15 | #[options(help = "[opts]")] 16 | Decompile(DecompileOpts), 17 | #[options(help = "[opts]")] 18 | Compile(CompileOpts), 19 | #[options(help = "[opts]")] 20 | Lint(LintOpts), 21 | } 22 | 23 | #[derive(Debug, Options)] 24 | struct DecompileOpts { 25 | #[options(required, short = "i", help = "input redscripts bundle file")] 26 | input: PathBuf, 27 | #[options(required, short = "o", help = "output file or directory")] 28 | output: PathBuf, 29 | #[options(short = "m", help = "dump mode (one of: 'ast', 'bytecode' or 'code')")] 30 | mode: String, 31 | #[options(short = "f", help = "split output into individual files")] 32 | dump_files: bool, 33 | #[options(short = "v", help = "verbose output (include implicit conversions)")] 34 | verbose: bool, 35 | } 36 | 37 | #[derive(Debug, Options)] 38 | struct CompileOpts { 39 | #[options(required, short = "s", help = "source file or directory")] 40 | src: PathBuf, 41 | #[options(required, short = "b", help = "redscript bundle file to use")] 42 | bundle: PathBuf, 43 | #[options(required, short = "o", help = "redscript bundle file to write")] 44 | output: PathBuf, 45 | } 46 | 47 | #[derive(Debug, Options)] 48 | struct LintOpts { 49 | #[options(required, short = "s", help = "source file or directory")] 50 | src: PathBuf, 51 | #[options(short = "b", help = "redscript bundle file to use, optional")] 52 | bundle: Option, 53 | } 54 | 55 | fn main() { 56 | run().unwrap_or_else(|err| println!("{:?}", err)); 57 | } 58 | 59 | fn run() -> Result<(), Error> { 60 | let args: Vec = std::env::args().skip(1).collect(); 61 | let command: Command = match Command::parse_args_default(&args) { 62 | Ok(res) => res, 63 | Err(err) => { 64 | println!("{}", err); 65 | println!("Usage:"); 66 | println!("{}", Command::usage()); 67 | println!("Compiler options:"); 68 | println!("{}", CompileOpts::usage()); 69 | println!("Decompiler options:"); 70 | println!("{}", DecompileOpts::usage()); 71 | println!("Lint options:"); 72 | println!("{}", LintOpts::usage()); 73 | return Ok(()); 74 | } 75 | }; 76 | 77 | match command { 78 | Command::Decompile(opts) => decompile(opts), 79 | Command::Compile(opts) => compile(opts), 80 | Command::Lint(opts) => lint(opts), 81 | } 82 | } 83 | 84 | fn compile(opts: CompileOpts) -> Result<(), Error> { 85 | let mut bundle: ScriptBundle = ScriptBundle::load(&mut BufReader::new(File::open(opts.bundle)?))?; 86 | let mut compiler = Compiler::new(&mut bundle.pool)?; 87 | 88 | match compiler.compile_all(&opts.src) { 89 | Ok(()) => { 90 | bundle.save(&mut BufWriter::new(File::create(&opts.output)?))?; 91 | println!("Output successfully saved to {:?}", opts.output); 92 | } 93 | Err(_) => { 94 | println!("Build failed"); 95 | } 96 | } 97 | Ok(()) 98 | } 99 | 100 | fn decompile(opts: DecompileOpts) -> Result<(), Error> { 101 | let bundle: ScriptBundle = ScriptBundle::load(&mut BufReader::new(File::open(opts.input)?))?; 102 | let pool = &bundle.pool; 103 | 104 | let mode = match opts.mode.as_str() { 105 | "ast" => OutputMode::SyntaxTree, 106 | "bytecode" => OutputMode::Bytecode, 107 | _ => OutputMode::Code { verbose: opts.verbose }, 108 | }; 109 | 110 | if opts.dump_files { 111 | for entry in FileIndex::from_pool(pool).iter() { 112 | let path = opts.output.as_path().join(&entry.path); 113 | 114 | std::fs::create_dir_all(path.parent().unwrap())?; 115 | let mut output = BufWriter::new(File::create(path)?); 116 | for def in entry.definitions { 117 | if let Err(err) = write_definition(&mut output, def, pool, 0, mode) { 118 | println!("Failed to process definition at {:?}: {:?}", def, err); 119 | } 120 | } 121 | } 122 | } else { 123 | let mut output = BufWriter::new(File::create(&opts.output)?); 124 | 125 | for (_, def) in pool.roots().filter(|(_, def)| { 126 | matches!(&def.value, DefinitionValue::Class(_)) 127 | || matches!(&def.value, DefinitionValue::Enum(_)) 128 | || matches!(&def.value, DefinitionValue::Function(_)) 129 | }) { 130 | if let Err(err) = write_definition(&mut output, def, pool, 0, mode) { 131 | println!("Failed to process definition at {:?}: {:?}", def, err); 132 | } 133 | } 134 | } 135 | println!("Output successfully saved to {:?}", opts.output); 136 | Ok(()) 137 | } 138 | 139 | fn lint(opts: LintOpts) -> Result<(), Error> { 140 | match opts.bundle { 141 | Some(bundle_path) => { 142 | let mut bundle: ScriptBundle = ScriptBundle::load(&mut BufReader::new(File::open(bundle_path)?))?; 143 | let mut compiler = Compiler::new(&mut bundle.pool)?; 144 | if compiler.compile_all(&opts.src).is_ok() { 145 | println!("Lint successful"); 146 | } 147 | Ok(()) 148 | } 149 | None => Ok(()), 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /core/src/ast.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::ops::{Add, Sub}; 3 | use std::rc::Rc; 4 | 5 | use strum::Display; 6 | 7 | #[derive(Debug)] 8 | pub enum Expr { 9 | Ident(Ident, Pos), 10 | Constant(Constant, Pos), 11 | Declare(Ident, Option>, Option>, Pos), 12 | Cast(TypeName, Box, Pos), 13 | Assign(Box, Box, Pos), 14 | Call(Ident, Vec, Pos), 15 | MethodCall(Box, Ident, Vec, Pos), 16 | Member(Box, Ident, Pos), 17 | ArrayElem(Box, Box, Pos), 18 | New(Ident, Vec, Pos), 19 | Return(Option>, Pos), 20 | Seq(Seq), 21 | Switch(Box, Vec, Option), 22 | Goto(Target, Pos), 23 | If(Box, Seq, Option, Pos), 24 | Conditional(Box, Box, Box, Pos), 25 | While(Box, Seq, Pos), 26 | BinOp(Box, Box, BinOp, Pos), 27 | UnOp(Box, UnOp, Pos), 28 | This(Pos), 29 | Super(Pos), 30 | Break, 31 | Null, 32 | } 33 | 34 | impl Expr { 35 | pub const EMPTY: Expr = Expr::Seq(Seq { exprs: vec![] }); 36 | 37 | pub fn is_empty(&self) -> bool { 38 | match self { 39 | Expr::Seq(seq) => seq.exprs.iter().all(|expr| expr.is_empty()), 40 | Expr::Goto(target, _) => target.resolved, 41 | _ => false, 42 | } 43 | } 44 | } 45 | 46 | #[derive(Debug)] 47 | pub enum Constant { 48 | String(LiteralType, String), 49 | Float(f64), 50 | Int(i64), 51 | Uint(u64), 52 | Bool(bool), 53 | } 54 | 55 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 56 | pub struct Ident(pub Rc); 57 | 58 | impl Ident { 59 | pub fn new(str: String) -> Ident { 60 | Ident(Rc::new(str)) 61 | } 62 | } 63 | 64 | impl Display for Ident { 65 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 66 | f.write_str(&self.0) 67 | } 68 | } 69 | 70 | #[derive(Debug, Clone, Copy, Display)] 71 | pub enum BinOp { 72 | AssignAdd, 73 | AssignSub, 74 | AssignMultiply, 75 | AssignDivide, 76 | LogicOr, 77 | LogicAnd, 78 | Or, 79 | Xor, 80 | And, 81 | Equal, 82 | NotEqual, 83 | Less, 84 | LessEqual, 85 | Greater, 86 | GreaterEqual, 87 | Add, 88 | Subtract, 89 | Multiply, 90 | Divide, 91 | Modulo, 92 | } 93 | 94 | impl BinOp { 95 | pub fn name(&self) -> String { 96 | format!("Operator{}", self) 97 | } 98 | } 99 | 100 | #[derive(Debug, Clone, Copy, Display)] 101 | pub enum UnOp { 102 | BitNot, 103 | LogicNot, 104 | Neg, 105 | } 106 | 107 | impl UnOp { 108 | pub fn name(&self) -> String { 109 | format!("Operator{}", self) 110 | } 111 | } 112 | 113 | #[derive(Debug)] 114 | pub struct SwitchCase(pub Expr, pub Seq); 115 | 116 | #[derive(Debug)] 117 | pub struct Seq { 118 | pub exprs: Vec, 119 | } 120 | 121 | impl Seq { 122 | pub fn new(exprs: Vec) -> Seq { 123 | Seq { exprs } 124 | } 125 | } 126 | 127 | #[derive(Debug)] 128 | pub enum LiteralType { 129 | String, 130 | Name, 131 | Resource, 132 | TweakDbId, 133 | } 134 | 135 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 136 | pub struct Pos(pub u32); 137 | 138 | impl Pos { 139 | pub const ZERO: Pos = Pos(0); 140 | 141 | pub fn new(n: usize) -> Self { 142 | Pos(n as u32) 143 | } 144 | } 145 | 146 | impl fmt::Display for Pos { 147 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 148 | write!(f, "{}", self.0) 149 | } 150 | } 151 | 152 | impl Add for Pos { 153 | type Output = Pos; 154 | fn add(self, other: usize) -> Pos { 155 | Pos(self.0 + other as u32) 156 | } 157 | } 158 | 159 | impl Sub for Pos { 160 | type Output = Pos; 161 | fn sub(self, other: Pos) -> Pos { 162 | Pos(self.0 - other.0) 163 | } 164 | } 165 | 166 | #[derive(Debug)] 167 | pub struct Target { 168 | pub position: u16, 169 | pub resolved: bool, 170 | } 171 | 172 | impl Target { 173 | pub fn new(position: u16) -> Target { 174 | Target { 175 | position, 176 | resolved: false, 177 | } 178 | } 179 | } 180 | 181 | #[derive(Debug)] 182 | pub struct TypeName> { 183 | pub name: S, 184 | pub arguments: Vec>, 185 | } 186 | 187 | impl TypeName<&str> { 188 | pub const BOOL: Self = TypeName::basic("Bool"); 189 | pub const INT32: Self = TypeName::basic("Int32"); 190 | pub const UINT32: Self = TypeName::basic("Uint32"); 191 | pub const FLOAT: Self = TypeName::basic("Float"); 192 | pub const STRING: Self = TypeName::basic("String"); 193 | pub const VARIANT: Self = TypeName::basic("Variant"); 194 | pub const CNAME: Self = TypeName::basic("CName"); 195 | pub const RESOURCE: Self = TypeName::basic("ResRef"); 196 | pub const TWEAKDB_ID: Self = TypeName::basic("TweakDBID"); 197 | } 198 | 199 | impl> TypeName { 200 | fn unwrapped(&self) -> &Self { 201 | match self.name.as_ref() { 202 | "ref" => &self.arguments[0].unwrapped(), 203 | "wref" => &self.arguments[0].unwrapped(), 204 | _ => self, 205 | } 206 | } 207 | 208 | pub const fn basic(name: S) -> Self { 209 | TypeName { 210 | name, 211 | arguments: vec![], 212 | } 213 | } 214 | 215 | // Used for identifying functions 216 | pub fn mangled(&self) -> String { 217 | let unwrapped = self.unwrapped(); 218 | match unwrapped.arguments.first() { 219 | None => unwrapped.name.as_ref().to_owned(), 220 | Some(head) => { 221 | let args = unwrapped 222 | .arguments 223 | .iter() 224 | .skip(1) 225 | .map(|tp| tp.mangled()) 226 | .fold(head.mangled(), |acc, el| format!("{},{}", acc, el)); 227 | format!("{}<{}>", unwrapped.name.as_ref(), args) 228 | } 229 | } 230 | } 231 | 232 | // Used for storing types in the constant pool 233 | pub fn repr(&self) -> String { 234 | if self.arguments.is_empty() { 235 | self.name.as_ref().to_owned() 236 | } else { 237 | self.arguments.iter().fold(self.name.as_ref().to_owned(), |acc, tp| { 238 | format!("{}:{}", acc, tp.repr()) 239 | }) 240 | } 241 | } 242 | } 243 | 244 | impl> Display for TypeName { 245 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 246 | f.write_str(&self.mangled()) 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "atty" 5 | version = "0.2.14" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 8 | dependencies = [ 9 | "hermit-abi", 10 | "libc", 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "bitmaps" 16 | version = "2.1.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" 19 | dependencies = [ 20 | "typenum", 21 | ] 22 | 23 | [[package]] 24 | name = "byteorder" 25 | version = "1.4.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "1.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 34 | 35 | [[package]] 36 | name = "colored" 37 | version = "2.0.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 40 | dependencies = [ 41 | "atty", 42 | "lazy_static", 43 | "winapi", 44 | ] 45 | 46 | [[package]] 47 | name = "crc32fast" 48 | version = "1.2.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 51 | dependencies = [ 52 | "cfg-if", 53 | ] 54 | 55 | [[package]] 56 | name = "gumdrop" 57 | version = "0.8.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "46571f5d540478cf70d2a42dd0d6d8e9f4b9cc7531544b93311e657b86568a0b" 60 | dependencies = [ 61 | "gumdrop_derive", 62 | ] 63 | 64 | [[package]] 65 | name = "gumdrop_derive" 66 | version = "0.8.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "915ef07c710d84733522461de2a734d4d62a3fd39a4d4f404c2f385ef8618d05" 69 | dependencies = [ 70 | "proc-macro2", 71 | "quote", 72 | "syn", 73 | ] 74 | 75 | [[package]] 76 | name = "heck" 77 | version = "0.3.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" 80 | dependencies = [ 81 | "unicode-segmentation", 82 | ] 83 | 84 | [[package]] 85 | name = "hermit-abi" 86 | version = "0.1.18" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 89 | dependencies = [ 90 | "libc", 91 | ] 92 | 93 | [[package]] 94 | name = "im" 95 | version = "15.0.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "111c1983f3c5bb72732df25cddacee9b546d08325fb584b5ebd38148be7b0246" 98 | dependencies = [ 99 | "bitmaps", 100 | "rand_core", 101 | "rand_xoshiro", 102 | "sized-chunks", 103 | "typenum", 104 | "version_check", 105 | ] 106 | 107 | [[package]] 108 | name = "lazy_static" 109 | version = "1.4.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 112 | 113 | [[package]] 114 | name = "libc" 115 | version = "0.2.85" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" 118 | 119 | [[package]] 120 | name = "modular-bitfield" 121 | version = "0.11.2" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" 124 | dependencies = [ 125 | "modular-bitfield-impl", 126 | "static_assertions", 127 | ] 128 | 129 | [[package]] 130 | name = "modular-bitfield-impl" 131 | version = "0.11.2" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" 134 | dependencies = [ 135 | "proc-macro2", 136 | "quote", 137 | "syn", 138 | ] 139 | 140 | [[package]] 141 | name = "peg" 142 | version = "0.6.3" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "9f76678828272f177ac33b7e2ac2e3e73cc6c1cd1e3e387928aa69562fa51367" 145 | dependencies = [ 146 | "peg-macros", 147 | "peg-runtime", 148 | ] 149 | 150 | [[package]] 151 | name = "peg-macros" 152 | version = "0.6.3" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "636d60acf97633e48d266d7415a9355d4389cea327a193f87df395d88cd2b14d" 155 | dependencies = [ 156 | "peg-runtime", 157 | "proc-macro2", 158 | "quote", 159 | ] 160 | 161 | [[package]] 162 | name = "peg-runtime" 163 | version = "0.6.3" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5" 166 | 167 | [[package]] 168 | name = "proc-macro2" 169 | version = "1.0.24" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 172 | dependencies = [ 173 | "unicode-xid", 174 | ] 175 | 176 | [[package]] 177 | name = "quote" 178 | version = "1.0.8" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" 181 | dependencies = [ 182 | "proc-macro2", 183 | ] 184 | 185 | [[package]] 186 | name = "rand_core" 187 | version = "0.5.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 190 | 191 | [[package]] 192 | name = "rand_xoshiro" 193 | version = "0.4.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004" 196 | dependencies = [ 197 | "rand_core", 198 | ] 199 | 200 | [[package]] 201 | name = "redscript" 202 | version = "0.1.0" 203 | dependencies = [ 204 | "byteorder", 205 | "crc32fast", 206 | "modular-bitfield", 207 | "strum", 208 | ] 209 | 210 | [[package]] 211 | name = "redscript-cli" 212 | version = "0.1.0" 213 | dependencies = [ 214 | "gumdrop", 215 | "redscript", 216 | "redscript-compiler", 217 | "redscript-decompiler", 218 | ] 219 | 220 | [[package]] 221 | name = "redscript-compiler" 222 | version = "0.1.0" 223 | dependencies = [ 224 | "colored", 225 | "im", 226 | "peg", 227 | "redscript", 228 | "strum", 229 | "walkdir", 230 | ] 231 | 232 | [[package]] 233 | name = "redscript-decompiler" 234 | version = "0.1.0" 235 | dependencies = [ 236 | "redscript", 237 | ] 238 | 239 | [[package]] 240 | name = "same-file" 241 | version = "1.0.6" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 244 | dependencies = [ 245 | "winapi-util", 246 | ] 247 | 248 | [[package]] 249 | name = "scc" 250 | version = "0.1.0" 251 | dependencies = [ 252 | "redscript", 253 | "redscript-compiler", 254 | ] 255 | 256 | [[package]] 257 | name = "sized-chunks" 258 | version = "0.6.2" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f" 261 | dependencies = [ 262 | "bitmaps", 263 | "typenum", 264 | ] 265 | 266 | [[package]] 267 | name = "static_assertions" 268 | version = "1.1.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 271 | 272 | [[package]] 273 | name = "strum" 274 | version = "0.20.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c" 277 | dependencies = [ 278 | "strum_macros", 279 | ] 280 | 281 | [[package]] 282 | name = "strum_macros" 283 | version = "0.20.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" 286 | dependencies = [ 287 | "heck", 288 | "proc-macro2", 289 | "quote", 290 | "syn", 291 | ] 292 | 293 | [[package]] 294 | name = "syn" 295 | version = "1.0.60" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" 298 | dependencies = [ 299 | "proc-macro2", 300 | "quote", 301 | "unicode-xid", 302 | ] 303 | 304 | [[package]] 305 | name = "typenum" 306 | version = "1.12.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 309 | 310 | [[package]] 311 | name = "unicode-segmentation" 312 | version = "1.7.1" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 315 | 316 | [[package]] 317 | name = "unicode-xid" 318 | version = "0.2.1" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 321 | 322 | [[package]] 323 | name = "version_check" 324 | version = "0.9.2" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 327 | 328 | [[package]] 329 | name = "walkdir" 330 | version = "2.3.1" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 333 | dependencies = [ 334 | "same-file", 335 | "winapi", 336 | "winapi-util", 337 | ] 338 | 339 | [[package]] 340 | name = "winapi" 341 | version = "0.3.9" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 344 | dependencies = [ 345 | "winapi-i686-pc-windows-gnu", 346 | "winapi-x86_64-pc-windows-gnu", 347 | ] 348 | 349 | [[package]] 350 | name = "winapi-i686-pc-windows-gnu" 351 | version = "0.4.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 354 | 355 | [[package]] 356 | name = "winapi-util" 357 | version = "0.1.5" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 360 | dependencies = [ 361 | "winapi", 362 | ] 363 | 364 | [[package]] 365 | name = "winapi-x86_64-pc-windows-gnu" 366 | version = "0.4.0" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 369 | -------------------------------------------------------------------------------- /decompiler/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::ops::Deref; 3 | 4 | use redscript::ast::{Constant, Expr, Ident, LiteralType, Pos, Seq, SwitchCase, Target, TypeName}; 5 | use redscript::bundle::{ConstantPool, PoolIndex}; 6 | use redscript::bytecode::{CodeCursor, Instr, Offset, Position}; 7 | use redscript::error::Error; 8 | 9 | pub mod files; 10 | pub mod print; 11 | 12 | pub struct Decompiler<'a> { 13 | code: &'a mut CodeCursor<'a>, 14 | pool: &'a ConstantPool, 15 | } 16 | 17 | impl<'a> Decompiler<'a> { 18 | pub fn new(code: &'a mut CodeCursor<'a>, pool: &'a ConstantPool) -> Decompiler<'a> { 19 | Decompiler { code, pool } 20 | } 21 | 22 | pub fn decompile(&mut self) -> Result { 23 | self.consume_path(Position::MAX) 24 | } 25 | 26 | fn definition_ident(&self, index: PoolIndex) -> Result { 27 | Ok(Ident(self.pool.definition_name(index)?)) 28 | } 29 | 30 | fn consume_n(&mut self, n: usize) -> Result, Error> { 31 | let mut body = Vec::new(); 32 | for _ in 0..n { 33 | body.push(self.consume()?) 34 | } 35 | Ok(body) 36 | } 37 | 38 | fn consume_path(&mut self, target: Position) -> Result { 39 | let mut body = Vec::new(); 40 | loop { 41 | if self.code.pos() >= target 42 | || matches!(body.last(), Some(Expr::Goto(_, _))) 43 | || matches!(body.last(), Some(Expr::Return(_, _))) 44 | { 45 | break; 46 | } 47 | match self.consume() { 48 | Ok(expr) => body.push(expr), 49 | Err(Error::IOError(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break, 50 | Err(err) => return Err(err), 51 | } 52 | } 53 | Ok(Seq::new(body)) 54 | } 55 | 56 | fn consume_call(&mut self, name: &str, param_count: usize) -> Result { 57 | let params = self.consume_n(param_count)?; 58 | Ok(Expr::Call(Ident::new(name.to_owned()), params, Pos::ZERO)) 59 | } 60 | 61 | fn consume_params(&mut self) -> Result, Error> { 62 | let mut params = Vec::new(); 63 | loop { 64 | while matches!(self.code.peek(), Some(Instr::Skip(_))) || matches!(self.code.peek(), Some(Instr::Nop)) { 65 | self.code.pop()?; 66 | } 67 | if matches!(self.code.peek(), Some(Instr::ParamEnd)) { 68 | break; 69 | } 70 | params.push(self.consume()?); 71 | } 72 | self.code.pop()?; 73 | Ok(params) 74 | } 75 | 76 | fn consume_conditional_jump(&mut self, position: Position, offset: Offset) -> Result { 77 | let condition = self.consume()?; 78 | let target = offset.absolute(position); 79 | let mut body = self.consume_path(target)?; 80 | self.code.goto(target)?; 81 | 82 | let result = if resolve_jump(&mut body, Some(position)).is_some() { 83 | Expr::While(Box::new(condition), body, Pos::ZERO) 84 | } else if let Some(jump) = resolve_jump(&mut body, None) { 85 | let else_case = self.consume_path(Position::new(jump.position))?; 86 | Expr::If(Box::new(condition), body, Some(else_case), Pos::ZERO) 87 | } else { 88 | Expr::If(Box::new(condition), body, None, Pos::ZERO) 89 | }; 90 | Ok(result) 91 | } 92 | 93 | fn consume_switch(&mut self) -> Result { 94 | let subject = self.consume()?; 95 | 96 | let mut labels = Vec::new(); 97 | while let Some(Instr::SwitchLabel(exit_offset, start_offset)) = self.code.peek() { 98 | let position = self.code.pos(); 99 | labels.push((position, start_offset.absolute(position))); 100 | self.code.seek(exit_offset)?; 101 | } 102 | if let Some(Instr::SwitchDefault) = self.code.peek() { 103 | labels.push((self.code.pos(), self.code.pos())); 104 | }; 105 | labels.sort_by_key(|(_, start)| *start); 106 | 107 | let mut default = None; 108 | let mut cases = Vec::new(); 109 | for (label, start_position) in labels { 110 | self.code.goto(label)?; 111 | 112 | match self.code.pop()? { 113 | Instr::SwitchLabel(exit_offset, _) => { 114 | let exit = exit_offset.absolute(label); 115 | let matched = self.consume()?; 116 | 117 | self.code.goto(start_position)?; 118 | let mut body = self.consume_path(exit)?; 119 | if let Some(Expr::Goto(_, _)) = body.exprs.last() { 120 | body.exprs.pop(); 121 | body.exprs.push(Expr::Break); 122 | } 123 | cases.push(SwitchCase(matched, body)); 124 | } 125 | Instr::SwitchDefault => default = Some(Seq::new(vec![self.consume()?])), 126 | _ => return Err(Error::DecompileError("Unexpected switch label instruction".to_owned())), 127 | } 128 | } 129 | 130 | Ok(Expr::Switch(Box::new(subject), cases, default)) 131 | } 132 | 133 | fn consume(&mut self) -> Result { 134 | self.consume_with(None) 135 | } 136 | 137 | fn consume_with(&mut self, context: Option) -> Result { 138 | let position = self.code.pos(); 139 | let res = match self.code.pop()? { 140 | Instr::Nop => Expr::EMPTY, 141 | Instr::Null => Expr::Null, 142 | Instr::I32One => Expr::Constant(Constant::Int(1), Pos::ZERO), 143 | Instr::I32Zero => Expr::Constant(Constant::Int(0), Pos::ZERO), 144 | Instr::I8Const(val) => Expr::Constant(Constant::Int(val.into()), Pos::ZERO), 145 | Instr::I16Const(val) => Expr::Constant(Constant::Int(val.into()), Pos::ZERO), 146 | Instr::I32Const(val) => Expr::Constant(Constant::Int(val.into()), Pos::ZERO), 147 | Instr::I64Const(val) => Expr::Constant(Constant::Int(val), Pos::ZERO), 148 | Instr::U8Const(val) => Expr::Constant(Constant::Uint(val.into()), Pos::ZERO), 149 | Instr::U16Const(val) => Expr::Constant(Constant::Uint(val.into()), Pos::ZERO), 150 | Instr::U32Const(val) => Expr::Constant(Constant::Uint(val.into()), Pos::ZERO), 151 | Instr::U64Const(val) => Expr::Constant(Constant::Uint(val), Pos::ZERO), 152 | Instr::F32Const(val) => Expr::Constant(Constant::Float(val.into()), Pos::ZERO), 153 | Instr::F64Const(val) => Expr::Constant(Constant::Float(val), Pos::ZERO), 154 | Instr::StringConst(str) => { 155 | let decoded = String::from_utf8(str).unwrap(); 156 | Expr::Constant(Constant::String(LiteralType::String, decoded), Pos::ZERO) 157 | } 158 | Instr::NameConst(idx) => { 159 | let str = self.pool.names.get(idx)?.deref().clone(); 160 | Expr::Constant(Constant::String(LiteralType::Name, str), Pos::ZERO) 161 | } 162 | Instr::TweakDbIdConst(idx) => { 163 | let str = self.pool.tweakdb_ids.get(idx)?.deref().clone(); 164 | Expr::Constant(Constant::String(LiteralType::TweakDbId, str), Pos::ZERO) 165 | } 166 | Instr::ResourceConst(idx) => { 167 | let str = self.pool.resources.get(idx)?.deref().clone(); 168 | Expr::Constant(Constant::String(LiteralType::Resource, str), Pos::ZERO) 169 | } 170 | Instr::TrueConst => Expr::Constant(Constant::Bool(true), Pos::ZERO), 171 | Instr::FalseConst => Expr::Constant(Constant::Bool(false), Pos::ZERO), 172 | Instr::EnumConst(enum_, member) => { 173 | let enum_ident = self.definition_ident(enum_)?; 174 | let member_ident = self.definition_ident(member)?; 175 | let expr = Box::new(Expr::Ident(enum_ident, Pos::ZERO)); 176 | Expr::Member(expr, member_ident, Pos::ZERO) 177 | } 178 | Instr::Breakpoint(_, _, _, _, _, _) => { 179 | return Err(Error::DecompileError("Unexpected Breakpoint".to_owned())) 180 | } 181 | Instr::Assign => { 182 | let lhs = self.consume()?; 183 | let rhs = self.consume()?; 184 | Expr::Assign(Box::new(lhs), Box::new(rhs), Pos::ZERO) 185 | } 186 | Instr::Target => return Err(Error::DecompileError("Unexpected Target".to_owned())), 187 | Instr::Local(idx) => Expr::Ident(self.definition_ident(idx)?, Pos::ZERO), 188 | Instr::Param(idx) => Expr::Ident(self.definition_ident(idx)?, Pos::ZERO), 189 | Instr::ObjectField(idx) => { 190 | let field = self.definition_ident(idx)?; 191 | if let Some(object) = context { 192 | Expr::Member(Box::new(object), field, Pos::ZERO) 193 | } else { 194 | Expr::Member(Box::new(Expr::This(Pos::ZERO)), field, Pos::ZERO) 195 | } 196 | } 197 | Instr::ExternalVar => return Err(Error::DecompileError("Unexpected ExternalVar".to_owned())), 198 | Instr::Switch(_, _) => self.consume_switch()?, 199 | Instr::SwitchLabel(_, _) => return Err(Error::DecompileError("Unexpected SwitchLabel".to_owned())), 200 | Instr::SwitchDefault => return Err(Error::DecompileError("Unexpected SwitchDefault".to_owned())), 201 | Instr::Jump(Offset { value: 3 }) => Expr::EMPTY, 202 | Instr::Jump(offset) => Expr::Goto(Target::new(offset.absolute(position).value), Pos::ZERO), 203 | Instr::JumpIfFalse(offset) => { 204 | assert!(offset.value >= 0, "negative offset is not supported for JumpIfFalse"); 205 | self.consume_conditional_jump(position, offset)? 206 | } 207 | Instr::Skip(offset) => Expr::Goto(Target::new(offset.absolute(position).value), Pos::ZERO), 208 | Instr::Conditional(_, _) => { 209 | let expr = self.consume()?; 210 | let true_case = self.consume()?; 211 | let false_case = self.consume()?; 212 | Expr::Conditional(Box::new(expr), Box::new(true_case), Box::new(false_case), Pos::ZERO) 213 | } 214 | Instr::Construct(n, class) => { 215 | let params = self.consume_n(n.into())?; 216 | Expr::New(Ident(self.pool.definition_name(class)?), params, Pos::ZERO) 217 | } 218 | Instr::InvokeStatic(_, _, idx) => { 219 | let def = self.pool.definition(idx)?; 220 | let name = Ident(self.pool.names.get(def.name)?); 221 | let params = self.consume_params()?; 222 | if let Some(ctx) = context { 223 | Expr::MethodCall(Box::new(ctx), name, params, Pos::ZERO) 224 | } else if self.pool.function(idx)?.flags.is_static() { 225 | if def.parent.is_undefined() { 226 | Expr::Call(name, params, Pos::ZERO) 227 | } else { 228 | let class_name = Ident(self.pool.definition_name(def.parent)?); 229 | let expr = Box::new(Expr::Ident(class_name, Pos::ZERO)); 230 | Expr::MethodCall(expr, name, params, Pos::ZERO) 231 | } 232 | } else { 233 | Expr::MethodCall(Box::new(Expr::This(Pos::ZERO)), name, params, Pos::ZERO) 234 | } 235 | // if let AnyDefinition::Function(ref fun) = def.value { 236 | // assert_eq!(fun.parameters.len(), params.len(), "Invalid number of parameters {:?}", params); 237 | // } 238 | } 239 | Instr::InvokeVirtual(_, _, idx) => { 240 | let name = Ident(self.pool.names.get(idx)?); 241 | let params = self.consume_params()?; 242 | if let Some(ctx) = context { 243 | Expr::MethodCall(Box::new(ctx), name, params, Pos::ZERO) 244 | } else { 245 | Expr::MethodCall(Box::new(Expr::This(Pos::ZERO)), name, params, Pos::ZERO) 246 | } 247 | } 248 | Instr::ParamEnd => return Err(Error::DecompileError("Unexpected ParamEnd".to_owned())), 249 | Instr::Return => Expr::Return(self.consume().ok().map(Box::new), Pos::ZERO), 250 | Instr::StructField(idx) => { 251 | let target = self.consume()?; 252 | let field = self.definition_ident(idx)?; 253 | Expr::Member(Box::new(target), field, Pos::ZERO) 254 | } 255 | Instr::Context(_) => { 256 | let expr = self.consume()?; 257 | self.consume_with(Some(expr))? 258 | } 259 | Instr::Equals(_) => self.consume_call("Equals", 2)?, 260 | Instr::NotEquals(_) => self.consume_call("NotEquals", 2)?, 261 | Instr::New(class) => Expr::New(Ident(self.pool.definition_name(class)?), vec![], Pos::ZERO), 262 | Instr::Delete => self.consume_call("Delete", 1)?, 263 | Instr::This => Expr::This(Pos::ZERO), 264 | Instr::StartProfiling(_, _) => return Err(Error::DecompileError("Unexpected StartProfiling".to_owned())), 265 | Instr::ArrayClear(_) => self.consume_call("ArrayClear", 1)?, 266 | Instr::ArraySize(_) => self.consume_call("ArraySize", 1)?, 267 | Instr::ArrayResize(_) => self.consume_call("ArrayResize", 2)?, 268 | Instr::ArrayFindFirst(_) => self.consume_call("ArrayFindFirst", 2)?, 269 | Instr::ArrayFindFirstFast(_) => self.consume_call("ArrayFindFirst", 2)?, 270 | Instr::ArrayFindLast(_) => self.consume_call("ArrayFindLast", 2)?, 271 | Instr::ArrayFindLastFast(_) => self.consume_call("ArrayFindLast", 2)?, 272 | Instr::ArrayContains(_) => self.consume_call("ArrayContains", 2)?, 273 | Instr::ArrayContainsFast(_) => self.consume_call("ArrayContains", 2)?, 274 | Instr::ArrayCount(_) => self.consume_call("ArrayCount", 2)?, 275 | Instr::ArrayCountFast(_) => self.consume_call("ArrayCount", 2)?, 276 | Instr::ArrayPush(_) => self.consume_call("ArrayPush", 2)?, 277 | Instr::ArrayPop(_) => self.consume_call("ArrayPop", 1)?, 278 | Instr::ArrayInsert(_) => self.consume_call("ArrayInsert", 3)?, 279 | Instr::ArrayRemove(_) => self.consume_call("ArrayRemove", 2)?, 280 | Instr::ArrayRemoveFast(_) => self.consume_call("ArrayRemove", 2)?, 281 | Instr::ArrayGrow(_) => self.consume_call("ArrayGrow", 2)?, 282 | Instr::ArrayErase(_) => self.consume_call("ArrayErase", 2)?, 283 | Instr::ArrayEraseFast(_) => self.consume_call("ArrayErase", 2)?, 284 | Instr::ArrayLast(_) => self.consume_call("ArrayLast", 1)?, 285 | Instr::ArrayElement(_) => { 286 | let arr = self.consume()?; 287 | let idx = self.consume()?; 288 | Expr::ArrayElem(Box::new(arr), Box::new(idx), Pos::ZERO) 289 | } 290 | Instr::StaticArraySize(_) => self.consume_call("StaticArraySize", 1)?, 291 | Instr::StaticArrayFindFirst(_) => self.consume_call("StaticArrayFindFirst", 2)?, 292 | Instr::StaticArrayFindFirstFast(_) => self.consume_call("StaticArrayFindFirstFast", 2)?, 293 | Instr::StaticArrayFindLast(_) => self.consume_call("StaticArrayFindLast", 2)?, 294 | Instr::StaticArrayFindLastFast(_) => self.consume_call("StaticArrayFindLastFast", 2)?, 295 | Instr::StaticArrayContains(_) => self.consume_call("StaticArrayContains", 2)?, 296 | Instr::StaticArrayContainsFast(_) => self.consume_call("StaticArrayContainsFast", 2)?, 297 | Instr::StaticArrayCount(_) => self.consume_call("StaticArrayCount", 2)?, 298 | Instr::StaticArrayCountFast(_) => self.consume_call("StaticArrayCountFast", 2)?, 299 | Instr::StaticArrayLast(_) => self.consume_call("StaticArrayLast", 1)?, 300 | Instr::StaticArrayElement(_) => { 301 | let arr = self.consume()?; 302 | let idx = self.consume()?; 303 | Expr::ArrayElem(Box::new(arr), Box::new(idx), Pos::ZERO) 304 | } 305 | Instr::RefToBool => self.consume_call("RefToBool", 1)?, 306 | Instr::WeakRefToBool => self.consume_call("WeakRefToBool", 1)?, 307 | Instr::EnumToI32(_, _) => self.consume_call("EnumInt", 1)?, 308 | Instr::I32ToEnum(_, _) => self.consume_call("IntEnum", 1)?, 309 | Instr::DynamicCast(type_, _) => { 310 | let name = self.pool.definition_name(type_)?; 311 | let type_name = TypeName { 312 | name: name.deref().to_owned(), 313 | arguments: vec![], 314 | }; 315 | let expr = self.consume()?; 316 | Expr::Cast(type_name, Box::new(expr), Pos::ZERO) 317 | } 318 | Instr::ToString(_) => self.consume_call("ToString", 1)?, 319 | Instr::ToVariant(_) => self.consume_call("ToVariant", 1)?, 320 | Instr::FromVariant(_) => self.consume_call("FromVariant", 1)?, 321 | Instr::VariantIsValid => self.consume_call("IsValid", 1)?, 322 | Instr::VariantIsRef => self.consume_call("IsRef", 1)?, 323 | Instr::VariantIsArray => self.consume_call("IsArray", 1)?, 324 | Instr::VatiantToCName => self.consume_call("ToCName", 1)?, 325 | Instr::VariantToString => self.consume_call("ToString", 1)?, 326 | Instr::WeakRefToRef => self.consume_call("WeakRefToRef", 1)?, 327 | Instr::RefToWeakRef => self.consume_call("RefToWeakRef", 1)?, 328 | Instr::WeakRefNull => Expr::Null, 329 | Instr::AsRef(_) => self.consume_call("AsRef", 1)?, 330 | Instr::Deref(_) => self.consume_call("Deref", 1)?, 331 | }; 332 | Ok(res) 333 | } 334 | } 335 | 336 | fn resolve_jump(seq: &mut Seq, target: Option) -> Option<&mut Target> { 337 | seq.exprs.iter_mut().rev().find_map(|expr| match expr { 338 | Expr::Goto(goto, _) if !goto.resolved && target.map(|target| goto.position == target.value).unwrap_or(true) => { 339 | goto.resolved = true; 340 | Some(goto) 341 | } 342 | Expr::If(_, if_, None, _) => resolve_jump(if_, target), 343 | Expr::If(_, if_, Some(else_), _) => resolve_jump(if_, target).or_else(move || resolve_jump(else_, target)), 344 | _ => None, 345 | }) 346 | } 347 | -------------------------------------------------------------------------------- /decompiler/src/print.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::ops::Deref; 3 | use std::rc::Rc; 4 | 5 | use redscript::ast::{BinOp, Constant, Expr, Ident, LiteralType, Seq, SwitchCase, UnOp}; 6 | use redscript::bundle::ConstantPool; 7 | use redscript::definition::{Definition, DefinitionValue, Function, Type}; 8 | use redscript::error::Error; 9 | 10 | use crate::Decompiler; 11 | 12 | const INDENT: &str = " "; 13 | 14 | #[derive(Debug, Clone, Copy)] 15 | pub enum OutputMode { 16 | Code { verbose: bool }, 17 | SyntaxTree, 18 | Bytecode, 19 | } 20 | 21 | pub fn write_definition( 22 | out: &mut W, 23 | definition: &Definition, 24 | pool: &ConstantPool, 25 | depth: usize, 26 | mode: OutputMode, 27 | ) -> Result<(), Error> { 28 | let padding = INDENT.repeat(depth); 29 | 30 | match &definition.value { 31 | DefinitionValue::Type(_) => write!(out, "{}", format_type(definition, pool)?)?, 32 | DefinitionValue::Class(class) => { 33 | writeln!(out)?; 34 | write!(out, "{} ", class.visibility)?; 35 | if class.flags.is_abstract() { 36 | write!(out, "abstract ")?; 37 | } 38 | if class.flags.is_final() { 39 | write!(out, "final ")?; 40 | } 41 | if class.flags.is_native() { 42 | write!(out, "native ")?; 43 | } 44 | if class.flags.is_struct() { 45 | write!(out, "struct ")?; 46 | } else { 47 | write!(out, "class ")?; 48 | } 49 | write!(out, "{} ", pool.names.get(definition.name)?)?; 50 | if !class.base.is_undefined() { 51 | write!(out, "extends {} ", pool.definition_name(class.base)?)?; 52 | } 53 | writeln!(out, "{{")?; 54 | 55 | for field_index in &class.fields { 56 | let field = pool.definition(*field_index)?; 57 | write_definition(out, field, pool, depth + 1, mode)?; 58 | } 59 | 60 | for method_index in &class.functions { 61 | let method = pool.definition(*method_index)?; 62 | if let Err(err) = write_definition(out, method, pool, depth + 1, mode) { 63 | println!("Method decompilation {:?} failed due to: {:?}", method_index, err) 64 | } 65 | } 66 | writeln!(out, "}}")? 67 | } 68 | DefinitionValue::EnumValue(val) => { 69 | let name = if definition.name.is_undefined() { 70 | Rc::new("Undefined".to_owned()) 71 | } else { 72 | pool.names.get(definition.name)? 73 | }; 74 | writeln!(out, "{}{} = {},", padding, name, val)? 75 | } 76 | DefinitionValue::Enum(enum_) => { 77 | writeln!(out)?; 78 | writeln!(out, "enum {} {{", pool.names.get(definition.name)?)?; 79 | 80 | for member in &enum_.members { 81 | write_definition(out, pool.definition(*member)?, pool, depth + 1, mode)?; 82 | } 83 | 84 | writeln!(out, "}}")? 85 | } 86 | DefinitionValue::Function(fun) => { 87 | let return_type = fun 88 | .return_type 89 | .map(|idx| format_type(pool.definition(idx).unwrap(), pool).unwrap()) 90 | .unwrap_or_else(|| "Void".to_owned()); 91 | 92 | let name = pool.names.get(definition.name)?; 93 | let pretty_name = name.split(';').next().expect("Function with empty name"); 94 | 95 | let params = fun 96 | .parameters 97 | .iter() 98 | .map(|param| format_param(pool.definition(*param).unwrap(), pool).unwrap()) 99 | .collect::>() 100 | .join(", "); 101 | 102 | writeln!(out)?; 103 | write!(out, "{}{} ", padding, fun.visibility)?; 104 | if fun.flags.is_final() { 105 | write!(out, "final ")?; 106 | } 107 | if fun.flags.is_static() { 108 | write!(out, "static ")?; 109 | } 110 | if fun.flags.is_native() { 111 | write!(out, "native ")?; 112 | } 113 | if fun.flags.is_exec() { 114 | write!(out, "exec ")?; 115 | } 116 | if fun.flags.is_const() { 117 | write!(out, "const ")?; 118 | } 119 | if fun.flags.is_callback() { 120 | write!(out, "cb ")?; 121 | } 122 | write!(out, "func {}({}) -> {}", pretty_name, params, return_type)?; 123 | 124 | if fun.flags.has_body() { 125 | write_function_body(out, fun, pool, depth, mode)?; 126 | } 127 | writeln!(out)?; 128 | } 129 | DefinitionValue::Parameter(_) => write!(out, "{}", format_param(definition, pool)?)?, 130 | DefinitionValue::Local(local) => { 131 | let type_name = format_type(pool.definition(local.type_)?, pool)?; 132 | let name = pool.names.get(definition.name)?; 133 | write!(out, "{}", padding)?; 134 | if local.flags.is_const() { 135 | write!(out, "const ")?; 136 | } else { 137 | write!(out, "let ")?; 138 | } 139 | write!(out, "{}: {};", name, type_name)? 140 | } 141 | DefinitionValue::Field(field) => { 142 | let type_name = format_type(pool.definition(field.type_)?, pool)?; 143 | let field_name = pool.names.get(definition.name)?; 144 | 145 | writeln!(out)?; 146 | for property in &field.attributes { 147 | writeln!(out, "{}@attrib({}, \"{}\")", padding, property.name, property.value)?; 148 | } 149 | 150 | for property in &field.defaults { 151 | writeln!(out, "{}@default({}, {})", padding, property.name, property.value)?; 152 | } 153 | 154 | write!(out, "{}{} ", padding, field.visibility)?; 155 | if field.flags.is_inline() { 156 | write!(out, "inline ")?; 157 | } 158 | if field.flags.is_rep() { 159 | write!(out, "rep ")?; 160 | } 161 | if field.flags.is_edit() { 162 | write!(out, "edit ")?; 163 | } 164 | if field.flags.is_native() { 165 | write!(out, "native ")?; 166 | } 167 | if field.flags.is_persistent() { 168 | write!(out, "persistent ")?; 169 | } 170 | if field.flags.is_const() { 171 | write!(out, "const ")?; 172 | } 173 | writeln!(out, "let {}: {};", field_name, type_name)? 174 | } 175 | DefinitionValue::SourceFile(_) => panic!(), 176 | } 177 | Ok(()) 178 | } 179 | 180 | fn write_function_body( 181 | out: &mut W, 182 | fun: &Function, 183 | pool: &ConstantPool, 184 | depth: usize, 185 | mode: OutputMode, 186 | ) -> Result<(), Error> { 187 | writeln!(out, " {{")?; 188 | for local in &fun.locals { 189 | write_definition(out, pool.definition(*local)?, pool, depth + 1, mode)?; 190 | writeln!(out)?; 191 | } 192 | match mode { 193 | OutputMode::Code { verbose } => { 194 | let code = Decompiler::new(&mut fun.code.cursor(), pool).decompile()?; 195 | write_seq(out, &code, verbose, depth + 1)?; 196 | } 197 | OutputMode::SyntaxTree => { 198 | let code = Decompiler::new(&mut fun.code.cursor(), pool).decompile()?; 199 | for expr in code.exprs { 200 | writeln!(out, "{}{:?}", INDENT.repeat(depth + 1), expr)?; 201 | } 202 | } 203 | OutputMode::Bytecode => { 204 | for (offset, instr) in fun.code.cursor() { 205 | let op = format!("{:?}", instr).to_lowercase(); 206 | writeln!(out, "{}{}: {}", INDENT.repeat(depth + 1), offset.value, op)?; 207 | } 208 | } 209 | } 210 | 211 | write!(out, "{}}}", INDENT.repeat(depth))?; 212 | Ok(()) 213 | } 214 | 215 | fn write_seq(out: &mut W, code: &Seq, verbose: bool, depth: usize) -> Result<(), Error> { 216 | for expr in code.exprs.iter().filter(|expr| !expr.is_empty()) { 217 | write!(out, "{}", INDENT.repeat(depth))?; 218 | write_expr(out, &expr, verbose, depth)?; 219 | writeln!(out, ";")?; 220 | } 221 | Ok(()) 222 | } 223 | 224 | fn write_expr(out: &mut W, expr: &Expr, verbose: bool, depth: usize) -> Result<(), Error> { 225 | let padding = INDENT.repeat(depth); 226 | 227 | match expr { 228 | Expr::Ident(ident, _) => write!(out, "{}", ident.0)?, 229 | Expr::Constant(cons, _) => match cons { 230 | Constant::String(LiteralType::String, str) => write!(out, "\"{}\"", str)?, 231 | Constant::String(LiteralType::Name, str) => write!(out, "n\"{}\"", str)?, 232 | Constant::String(LiteralType::Resource, str) => write!(out, "r\"{}\"", str)?, 233 | Constant::String(LiteralType::TweakDbId, str) => write!(out, "t\"{}\"", str)?, 234 | Constant::Int(lit) => write!(out, "{}", lit)?, 235 | Constant::Uint(lit) => write!(out, "{}", lit)?, 236 | Constant::Float(lit) => write!(out, "{:.2}", lit)?, 237 | Constant::Bool(true) => write!(out, "true")?, 238 | Constant::Bool(false) => write!(out, "false")?, 239 | }, 240 | Expr::Cast(type_, expr, _) => { 241 | write!(out, "(")?; 242 | write_expr(out, expr, verbose, 0)?; 243 | write!(out, " as {})", type_.repr())?; 244 | } 245 | Expr::Declare(_, _, _, _) => {} 246 | Expr::Assign(lhs, rhs, _) => { 247 | write_expr(out, lhs, verbose, 0)?; 248 | write!(out, " = ")?; 249 | write_expr(out, rhs, verbose, 0)? 250 | } 251 | Expr::Call(fun, params, _) => write_call(out, fun, params, verbose)?, 252 | Expr::MethodCall(obj, fun, params, _) => { 253 | write_expr(out, obj, verbose, 0)?; 254 | write!(out, ".")?; 255 | write_call(out, fun, params, verbose)? 256 | } 257 | Expr::ArrayElem(arr, idx, _) => { 258 | write_expr(out, arr, verbose, 0)?; 259 | write!(out, "[")?; 260 | write_expr(out, idx, verbose, 0)?; 261 | write!(out, "]")?; 262 | } 263 | Expr::New(ident, params, _) => { 264 | write!(out, "new {}(", ident.0)?; 265 | if !params.is_empty() { 266 | for param in params.iter().take(params.len() - 1) { 267 | write_expr(out, param, verbose, depth)?; 268 | write!(out, ", ")?; 269 | } 270 | write_expr(out, params.last().unwrap(), verbose, depth)?; 271 | } 272 | write!(out, ")")? 273 | } 274 | Expr::Return(Some(expr), _) => { 275 | write!(out, "return ")?; 276 | write_expr(out, expr, verbose, depth)? 277 | } 278 | Expr::Return(None, _) => write!(out, "return")?, 279 | Expr::Seq(exprs) => write_seq(out, exprs, verbose, depth)?, 280 | Expr::Switch(expr, cases, default) => { 281 | write!(out, "switch ")?; 282 | write_expr(out, expr, verbose, 0)?; 283 | writeln!(out, " {{")?; 284 | for SwitchCase(matcher, body) in cases { 285 | write!(out, "{} case ", padding)?; 286 | write_expr(out, matcher, verbose, 0)?; 287 | writeln!(out, ":")?; 288 | write_seq(out, body, verbose, depth + 2)?; 289 | } 290 | if let Some(default_body) = default { 291 | writeln!(out, "{} default:", padding)?; 292 | write_seq(out, default_body, verbose, depth + 2)?; 293 | } 294 | write!(out, "{}}}", padding)? 295 | } 296 | Expr::Goto(jump, _) if !jump.resolved => write!(out, "goto {}", jump.position)?, 297 | Expr::Goto(_, _) => (), 298 | Expr::If(condition, true_, false_, _) => { 299 | write!(out, "if ")?; 300 | write_expr(out, condition, verbose, 0)?; 301 | writeln!(out, " {{")?; 302 | write_seq(out, true_, verbose, depth + 1)?; 303 | write!(out, "{}}}", padding)?; 304 | if let Some(branch) = false_ { 305 | writeln!(out, " else {{")?; 306 | write_seq(out, branch, verbose, depth + 1)?; 307 | write!(out, "{}}}", padding)? 308 | } 309 | } 310 | Expr::Conditional(condition, true_, false_, _) => { 311 | write_expr(out, condition, verbose, 0)?; 312 | write!(out, " ? ")?; 313 | write_expr(out, true_, verbose, 0)?; 314 | write!(out, " : ")?; 315 | write_expr(out, false_, verbose, 0)?; 316 | } 317 | Expr::While(condition, body, _) => { 318 | write!(out, "while ")?; 319 | write_expr(out, condition, verbose, 0)?; 320 | writeln!(out, " {{")?; 321 | write_seq(out, body, verbose, depth + 1)?; 322 | write!(out, "{}}}", padding)?; 323 | } 324 | Expr::Member(expr, accessor, _) => { 325 | write_expr(out, expr, verbose, 0)?; 326 | write!(out, ".{}", accessor.0)?; 327 | } 328 | Expr::BinOp(lhs, rhs, op, _) => { 329 | write_binop(out, lhs, rhs, *op, verbose)?; 330 | } 331 | Expr::UnOp(val, op, _) => { 332 | write_unop(out, val, *op, verbose)?; 333 | } 334 | Expr::Break => write!(out, "break")?, 335 | Expr::Null => write!(out, "null")?, 336 | Expr::This(_) => write!(out, "this")?, 337 | Expr::Super(_) => write!(out, "super")?, 338 | }; 339 | Ok(()) 340 | } 341 | 342 | fn write_call(out: &mut W, name: &Ident, params: &[Expr], verbose: bool) -> Result<(), Error> { 343 | let extracted = name.0.split(';').next().expect("Empty function name"); 344 | let fun_name = if extracted.is_empty() { "undefined" } else { extracted }; 345 | match fun_name { 346 | "OperatorLogicOr" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::LogicOr, verbose), 347 | "OperatorLogicAnd" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::LogicAnd, verbose), 348 | "OperatorOr" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Or, verbose), 349 | "OperatorAnd" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::And, verbose), 350 | "OperatorXor" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Xor, verbose), 351 | "OperatorEqual" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Equal, verbose), 352 | "OperatorNotEqual" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::NotEqual, verbose), 353 | "OperatorGreater" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Greater, verbose), 354 | "OperatorLess" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Less, verbose), 355 | "OperatorAdd" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Add, verbose), 356 | "OperatorSubtract" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Subtract, verbose), 357 | "OperatorDivide" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Divide, verbose), 358 | "OperatorMultiply" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Multiply, verbose), 359 | "OperatorModulo" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Modulo, verbose), 360 | "OperatorGreaterEqual" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::GreaterEqual, verbose), 361 | "OperatorLessEqual" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::Less, verbose), 362 | "OperatorAssignAdd" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::AssignAdd, verbose), 363 | "OperatorAssignSubtract" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::AssignSub, verbose), 364 | "OperatorAssignMultiply" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::AssignMultiply, verbose), 365 | "OperatorAssignDivide" => write_binop(out, ¶ms[0], ¶ms[1], BinOp::AssignDivide, verbose), 366 | "OperatorLogicNot" => write_unop(out, ¶ms[0], UnOp::LogicNot, verbose), 367 | "OperatorBitNot" => write_unop(out, ¶ms[0], UnOp::BitNot, verbose), 368 | "OperatorNeg" => write_unop(out, ¶ms[0], UnOp::Neg, verbose), 369 | "WeakRefToRef" if !verbose => write_expr(out, ¶ms[0], verbose, 0), 370 | "RefToWeakRef" if !verbose => write_expr(out, ¶ms[0], verbose, 0), 371 | _ => { 372 | write!(out, "{}(", fun_name)?; 373 | if !params.is_empty() { 374 | for param in params.iter().take(params.len() - 1) { 375 | write_expr(out, param, verbose, 0)?; 376 | write!(out, ", ")?; 377 | } 378 | write_expr(out, params.last().unwrap(), verbose, 0)?; 379 | } 380 | write!(out, ")")?; 381 | Ok(()) 382 | } 383 | } 384 | } 385 | 386 | fn write_binop(out: &mut W, lhs: &Expr, rhs: &Expr, op: BinOp, verbose: bool) -> Result<(), Error> { 387 | write_expr(out, lhs, verbose, 0)?; 388 | write!(out, " {} ", format_binop(op))?; 389 | write_expr(out, rhs, verbose, 0) 390 | } 391 | 392 | fn write_unop(out: &mut W, param: &Expr, op: UnOp, verbose: bool) -> Result<(), Error> { 393 | write!(out, "{}", format_unop(op))?; 394 | write_expr(out, param, verbose, 0) 395 | } 396 | 397 | fn format_param(def: &Definition, pool: &ConstantPool) -> Result { 398 | if let DefinitionValue::Parameter(ref param) = def.value { 399 | let type_name = format_type(pool.definition(param.type_)?, pool)?; 400 | let name = pool.names.get(def.name)?; 401 | let out = if param.flags.is_out() { "out " } else { "" }; 402 | let optional = if param.flags.is_optional() { "opt " } else { "" }; 403 | Ok(format!("{}{}{}: {}", out, optional, name, type_name)) 404 | } else { 405 | Err(Error::DecompileError("Invalid type definition received".to_owned())) 406 | } 407 | } 408 | 409 | fn format_type(def: &Definition, pool: &ConstantPool) -> Result { 410 | if let DefinitionValue::Type(ref type_) = def.value { 411 | let result = match type_ { 412 | Type::Prim => pool.names.get(def.name)?.deref().to_owned(), 413 | Type::Class => pool.names.get(def.name)?.deref().to_owned(), 414 | Type::Ref(nested) => format!("ref<{}>", format_type(pool.definition(*nested)?, pool)?), 415 | Type::WeakRef(nested) => format!("wref<{}>", format_type(pool.definition(*nested)?, pool)?), 416 | Type::Array(nested) => format!("array<{}>", format_type(pool.definition(*nested)?, pool)?), 417 | Type::StaticArray(nested, size) => { 418 | format!("array<{}; {}>", format_type(pool.definition(*nested)?, pool)?, size) 419 | } 420 | Type::ScriptRef(nested) => format!("script_ref<{}>", format_type(pool.definition(*nested)?, pool)?), 421 | }; 422 | Ok(result) 423 | } else { 424 | Err(Error::DecompileError("Invalid type definition received".to_owned())) 425 | } 426 | } 427 | 428 | fn format_binop(op: BinOp) -> &'static str { 429 | match op { 430 | BinOp::AssignAdd => "+=", 431 | BinOp::AssignSub => "-=", 432 | BinOp::AssignMultiply => "*=", 433 | BinOp::AssignDivide => "/=", 434 | BinOp::LogicOr => "||", 435 | BinOp::LogicAnd => "&&", 436 | BinOp::Or => "|", 437 | BinOp::Xor => "^", 438 | BinOp::Equal => "==", 439 | BinOp::NotEqual => "!=", 440 | BinOp::And => "&", 441 | BinOp::Less => "<", 442 | BinOp::LessEqual => "<=", 443 | BinOp::Greater => ">", 444 | BinOp::GreaterEqual => ">=", 445 | BinOp::Add => "+", 446 | BinOp::Subtract => "-", 447 | BinOp::Multiply => "*", 448 | BinOp::Divide => "/", 449 | BinOp::Modulo => "%", 450 | } 451 | } 452 | 453 | fn format_unop(op: UnOp) -> &'static str { 454 | match op { 455 | UnOp::BitNot => "~", 456 | UnOp::LogicNot => "!", 457 | UnOp::Neg => "-", 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /compiler/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(option_result_contains)] 2 | 3 | use std::ffi::OsStr; 4 | use std::path::Path; 5 | 6 | use assembler::Assembler; 7 | use colored::*; 8 | use parser::{Annotation, AnnotationName}; 9 | use redscript::ast::{Ident, Pos, Seq, TypeName}; 10 | use redscript::bundle::{ConstantPool, PoolIndex}; 11 | use redscript::bytecode::{Code, Instr}; 12 | use redscript::definition::{ 13 | Class, ClassFlags, Definition, Enum, Field, FieldFlags, Function, FunctionFlags, Local, Parameter, ParameterFlags, SourceReference, Type, Visibility 14 | }; 15 | use redscript::error::Error; 16 | use scope::{FunctionId, FunctionName, Scope}; 17 | use source_map::Files; 18 | use walkdir::WalkDir; 19 | 20 | use crate::parser::{ClassSource, Declaration, FunctionSource, MemberSource, Qualifier, SourceEntry}; 21 | 22 | pub mod assembler; 23 | pub mod parser; 24 | pub mod scope; 25 | pub mod source_map; 26 | 27 | pub struct Compiler<'a> { 28 | pool: &'a mut ConstantPool, 29 | backlog: Vec<(PoolIndex, PoolIndex, Seq)>, 30 | scope: Scope, 31 | } 32 | 33 | impl<'a> Compiler<'a> { 34 | pub fn new(pool: &'a mut ConstantPool) -> Result, Error> { 35 | let scope = Scope::new(pool)?; 36 | let backlog = Vec::new(); 37 | Ok(Compiler { pool, scope, backlog }) 38 | } 39 | 40 | pub fn compile_all(&mut self, path: &Path) -> Result<(), Error> { 41 | let mut files = Files::new(); 42 | for entry in WalkDir::new(path) 43 | .into_iter() 44 | .filter_map(Result::ok) 45 | .filter(|e| e.path().extension() == Some(OsStr::new("reds"))) 46 | { 47 | let sources = std::fs::read_to_string(entry.path())?; 48 | files.add(entry.path().to_owned(), &sources); 49 | } 50 | let entries = match parser::parse(files.sources()) { 51 | Ok(res) => res, 52 | Err(err) => { 53 | let message = format!("Syntax error, expected {}", &err.expected); 54 | Self::print_errors(&files, &message, Pos::new(err.location.offset)); 55 | return Err(Error::SyntaxError(err.to_string())); 56 | } 57 | }; 58 | 59 | match self.compile(entries) { 60 | Ok(()) => { 61 | println!("Compilation complete"); 62 | Ok(()) 63 | } 64 | Err(Error::CompileError(err, pos)) => { 65 | Self::print_errors(&files, &err, pos); 66 | Err(Error::CompileError(err, pos)) 67 | } 68 | Err(Error::FunctionResolutionError(err, pos)) => { 69 | Self::print_errors(&files, &err, pos); 70 | Err(Error::FunctionResolutionError(err, pos)) 71 | } 72 | Err(other) => { 73 | println!("{}: {:?}", "Unexpected error during compilation".red(), other); 74 | Err(other) 75 | } 76 | } 77 | } 78 | 79 | fn print_errors(files: &Files, error: &str, pos: Pos) { 80 | let loc = files.lookup(pos).unwrap(); 81 | println!("{}", format!("Compilation error at {}:", loc).red()); 82 | println!("{}", files.enclosing_line(&loc).trim_end().truecolor(128, 128, 128)); 83 | println!("{}^^^", " ".repeat(loc.position.col)); 84 | println!("{}", error); 85 | } 86 | 87 | fn compile(&mut self, sources: Vec) -> Result<(), Error> { 88 | for entry in &sources { 89 | if let SourceEntry::Class(class) = entry { 90 | self.stub_type(class)?; 91 | } 92 | } 93 | for entry in sources { 94 | match entry { 95 | SourceEntry::Class(class) => { 96 | self.define_class(class)?; 97 | } 98 | SourceEntry::Function(fun) => { 99 | self.define_function(fun, PoolIndex::UNDEFINED)?; 100 | } 101 | } 102 | } 103 | for (this, fun_idx, seq) in self.backlog.drain(..) { 104 | Self::compile_function(fun_idx, this, &seq, self.pool, &self.scope)?; 105 | } 106 | Ok(()) 107 | } 108 | 109 | fn stub_type(&mut self, class: &ClassSource) -> Result<(), Error> { 110 | let name_idx = self.pool.names.add(class.name.clone()); 111 | let type_idx = self.pool.push_definition(Definition::type_(name_idx, Type::Class)); 112 | let idx = self.pool.reserve().cast(); 113 | let name = Ident(self.pool.names.get(name_idx)?); 114 | 115 | self.scope.types.insert(name.clone(), type_idx.cast()); 116 | self.scope.references.insert(name, Reference::Class(idx)); 117 | 118 | Ok(()) 119 | } 120 | 121 | fn define_class(&mut self, source: ClassSource) -> Result<(), Error> { 122 | let name_idx = self.pool.names.add(source.name); 123 | let name = Ident(self.pool.names.get(name_idx)?); 124 | 125 | if let Reference::Class(class_idx) = self.scope.resolve_reference(name, source.pos)? { 126 | let visibility = source.qualifiers.visibility().unwrap_or(Visibility::Private); 127 | let flags = ClassFlags::new(); 128 | let mut functions = vec![]; 129 | let mut fields = vec![]; 130 | 131 | for member in source.members { 132 | match member { 133 | MemberSource::Function(fun) => { 134 | functions.push(self.define_function(fun, class_idx)?); 135 | } 136 | MemberSource::Field(field, type_) => { 137 | fields.push(self.define_field(field, type_, class_idx)?); 138 | } 139 | } 140 | } 141 | 142 | let base_idx = if let Some(base_name) = source.base { 143 | let base_ident = Ident::new(base_name); 144 | if let Reference::Class(base_idx) = self.scope.resolve_reference(base_ident.clone(), source.pos)? { 145 | base_idx 146 | } else { 147 | return Err(Error::CompileError( 148 | format!("{} is not a class", base_ident.0), 149 | source.pos, 150 | )); 151 | } 152 | } else { 153 | PoolIndex::UNDEFINED 154 | }; 155 | 156 | let class = Class { 157 | visibility, 158 | flags, 159 | base: base_idx, 160 | functions, 161 | fields, 162 | overrides: vec![], 163 | }; 164 | self.pool 165 | .put_definition(class_idx.cast(), Definition::class(name_idx, class)); 166 | Ok(()) 167 | } else { 168 | panic!("Shouldn't get here") 169 | } 170 | } 171 | 172 | fn define_function( 173 | &mut self, 174 | source: FunctionSource, 175 | parent_idx: PoolIndex, 176 | ) -> Result, Error> { 177 | let decl = &source.declaration; 178 | let fun_id = FunctionId::from_source(&source)?; 179 | let name_idx = self.pool.names.add(fun_id.mangled()); 180 | 181 | let ident = Ident::new(decl.name.clone()); 182 | 183 | let (parent_idx, base_method, fun_idx) = 184 | self.determine_function_location(&fun_id, &decl.annotations, parent_idx)?; 185 | let name = if parent_idx.is_undefined() { 186 | FunctionName::global(ident) 187 | } else { 188 | FunctionName::instance(parent_idx, ident) 189 | }; 190 | 191 | let flags = FunctionFlags::new() 192 | .with_is_static(decl.qualifiers.contain(Qualifier::Static) || parent_idx == PoolIndex::UNDEFINED) 193 | .with_is_final(decl.qualifiers.contain(Qualifier::Final)) 194 | .with_is_const(decl.qualifiers.contain(Qualifier::Const)) 195 | .with_is_exec(decl.qualifiers.contain(Qualifier::Exec)) 196 | .with_is_callback(decl.qualifiers.contain(Qualifier::Callback)); 197 | 198 | let visibility = decl 199 | .qualifiers 200 | .visibility() 201 | .unwrap_or(if parent_idx == PoolIndex::UNDEFINED { 202 | Visibility::Public 203 | } else { 204 | Visibility::Private 205 | }); 206 | 207 | let return_type = match source.type_ { 208 | None => None, 209 | Some(type_) if type_.name == "Void" => None, 210 | Some(type_) => { 211 | let type_ = self.scope.resolve_type(&type_, self.pool, decl.pos)?; 212 | Some(self.scope.get_type_index(&type_, self.pool)?) 213 | } 214 | }; 215 | 216 | let mut parameters = Vec::new(); 217 | 218 | for param in &source.parameters { 219 | let type_ = self.scope.resolve_type(¶m.type_, self.pool, decl.pos)?; 220 | let type_idx = self.scope.get_type_index(&type_, self.pool)?; 221 | let flags = ParameterFlags::new() 222 | .with_is_out(param.qualifiers.contain(Qualifier::Out)) 223 | .with_is_optional(param.qualifiers.contain(Qualifier::Optional)); 224 | let name = self.pool.names.add(param.name.clone()); 225 | let param = Parameter { type_: type_idx, flags }; 226 | let idx = self 227 | .pool 228 | .push_definition(Definition::param(name, fun_idx.cast(), param)); 229 | parameters.push(idx.cast()); 230 | } 231 | 232 | let source_ref = SourceReference { 233 | file: PoolIndex::DEFAULT_SOURCE, 234 | line: 0, 235 | }; 236 | 237 | let function = Function { 238 | visibility, 239 | flags, 240 | source: Some(source_ref), 241 | return_type, 242 | unk1: false, 243 | base_method: base_method.filter(|_| !flags.is_static()), 244 | parameters, 245 | locals: vec![], 246 | operator: None, 247 | cast: 0, 248 | code: Code::EMPTY, 249 | }; 250 | let definition = Definition::function(name_idx, parent_idx.cast(), function); 251 | self.pool.put_definition(fun_idx.cast(), definition); 252 | if let Some(seq) = source.body { 253 | self.backlog.push((parent_idx, fun_idx, seq)) 254 | } 255 | self.scope.push_function(name, fun_idx); 256 | Ok(fun_idx) 257 | } 258 | 259 | fn define_field( 260 | &mut self, 261 | field: Declaration, 262 | type_: TypeName, 263 | parent: PoolIndex, 264 | ) -> Result, Error> { 265 | let name = self.pool.names.add(field.name); 266 | let visibility = field.qualifiers.visibility().unwrap_or(Visibility::Private); 267 | let type_ = self.scope.resolve_type(&type_, self.pool, field.pos)?; 268 | let type_idx = self.scope.get_type_index(&type_, self.pool)?; 269 | let flags = FieldFlags::new(); 270 | let field = Field { 271 | visibility, 272 | type_: type_idx, 273 | flags, 274 | hint: None, 275 | attributes: vec![], 276 | defaults: vec![], 277 | }; 278 | let definition = Definition::field(name, parent.cast(), field); 279 | let idx = self.pool.push_definition(definition).cast(); 280 | Ok(idx) 281 | } 282 | 283 | fn determine_function_location( 284 | &mut self, 285 | name: &FunctionId, 286 | annotations: &[Annotation], 287 | parent: PoolIndex, 288 | ) -> Result<(PoolIndex, Option>, PoolIndex), Error> { 289 | if let Some(ann) = annotations.iter().find(|ann| ann.name == AnnotationName::ReplaceMethod) { 290 | let ident = Ident::new(ann.value.clone()); 291 | if let Reference::Class(target_class) = self.scope.resolve_reference(ident, ann.pos)? { 292 | let class = self.pool.class(target_class)?; 293 | let existing_idx = class.functions.iter().find(|fun| { 294 | let str = self.pool.definition_name(**fun).unwrap(); 295 | str.as_str() == name.mangled() || str.as_str() == name.0 296 | }); 297 | if let Some(idx) = existing_idx { 298 | let fun = self.pool.function(*idx)?; 299 | Ok((target_class, fun.base_method, *idx)) 300 | } else { 301 | let error = format!("Method {} not found on {}", name.0, ann.value); 302 | Err(Error::CompileError(error, ann.pos)) 303 | } 304 | } else { 305 | let error = format!("Can't find object {} to replace method on", name.0); 306 | Err(Error::CompileError(error, ann.pos)) 307 | } 308 | } else if let Some(ann) = annotations.iter().find(|ann| ann.name == AnnotationName::AddMethod) { 309 | let ident = Ident::new(ann.value.clone()); 310 | if let Reference::Class(target_class) = self.scope.resolve_reference(ident, ann.pos)? { 311 | let class = self.pool.class(target_class)?; 312 | let base_method = if class.base != PoolIndex::UNDEFINED { 313 | let base = self.pool.class(class.base)?; 314 | base.functions 315 | .iter() 316 | .find(|fun| { 317 | let str = self.pool.definition_name(**fun).unwrap(); 318 | str.as_str() == name.mangled() || str.as_str() == name.0 319 | }) 320 | .cloned() 321 | } else { 322 | None 323 | }; 324 | 325 | let idx = self.pool.reserve().cast(); 326 | self.pool.class_mut(target_class)?.functions.push(idx); 327 | Ok((target_class, base_method, idx)) 328 | } else { 329 | let error = format!("Can't find object {} to add method on", name.0); 330 | Err(Error::CompileError(error, ann.pos)) 331 | } 332 | } else { 333 | Ok((parent, None, self.pool.reserve().cast())) 334 | } 335 | } 336 | 337 | fn compile_function( 338 | fun_idx: PoolIndex, 339 | class_idx: PoolIndex, 340 | seq: &Seq, 341 | pool: &mut ConstantPool, 342 | scope: &Scope, 343 | ) -> Result<(), Error> { 344 | let fun = pool.function(fun_idx)?; 345 | let mut local_scope = if fun.flags.is_static() { 346 | scope.with_context(None, fun_idx) 347 | } else { 348 | scope.with_context(Some(class_idx), fun_idx) 349 | }; 350 | 351 | for param in &fun.parameters { 352 | let ident = Ident(pool.definition_name(*param)?); 353 | local_scope.references.insert(ident, Reference::Parameter(*param)); 354 | } 355 | 356 | let assembler = Assembler::from_seq(&seq, pool, &mut local_scope)?; 357 | let function = pool.function_mut(fun_idx)?; 358 | function.code = Code(assembler.code.into_iter().collect()); 359 | function.code.0.push(Instr::Nop); 360 | function.locals = assembler.locals.into_iter().collect(); 361 | Ok(()) 362 | } 363 | } 364 | 365 | #[derive(Debug, Clone)] 366 | pub enum Reference { 367 | Local(PoolIndex), 368 | Parameter(PoolIndex), 369 | Field(PoolIndex), 370 | Class(PoolIndex), 371 | Enum(PoolIndex), 372 | } 373 | 374 | #[derive(Debug, Clone, PartialEq, Eq)] 375 | pub enum TypeId { 376 | Prim(PoolIndex), 377 | Class(PoolIndex), 378 | Struct(PoolIndex), 379 | Enum(PoolIndex), 380 | Ref(Box), 381 | WeakRef(Box), 382 | Array(Box), 383 | StaticArray(Box, u32), 384 | ScriptRef(Box), 385 | Null, 386 | Void, 387 | } 388 | 389 | impl TypeId { 390 | pub fn unwrapped(&self) -> &TypeId { 391 | match self { 392 | TypeId::Ref(inner) => inner.unwrapped(), 393 | TypeId::WeakRef(inner) => inner.unwrapped(), 394 | TypeId::ScriptRef(inner) => inner.unwrapped(), 395 | other => other, 396 | } 397 | } 398 | 399 | fn repr(&self, pool: &ConstantPool) -> Result { 400 | match self { 401 | TypeId::Prim(idx) => Ok(Ident(pool.definition_name(*idx)?)), 402 | TypeId::Class(idx) => Ok(Ident(pool.definition_name(*idx)?)), 403 | TypeId::Struct(idx) => Ok(Ident(pool.definition_name(*idx)?)), 404 | TypeId::Enum(idx) => Ok(Ident(pool.definition_name(*idx)?)), 405 | TypeId::Ref(idx) => Ok(Ident::new(format!("ref:{}", idx.repr(pool)?))), 406 | TypeId::WeakRef(idx) => Ok(Ident::new(format!("wref:{}", idx.repr(pool)?))), 407 | TypeId::Array(idx) => Ok(Ident::new(format!("array:{}", idx.repr(pool)?))), 408 | TypeId::StaticArray(idx, size) => Ok(Ident::new(format!("{}[{}]", idx.repr(pool)?, size))), 409 | TypeId::ScriptRef(idx) => Ok(Ident::new(format!("script_ref:{}", idx.repr(pool)?))), 410 | TypeId::Null => panic!(), 411 | TypeId::Void => panic!(), 412 | } 413 | } 414 | 415 | fn pretty(&self, pool: &ConstantPool) -> Result { 416 | match self { 417 | TypeId::Prim(idx) => Ok(Ident(pool.definition_name(*idx)?)), 418 | TypeId::Class(idx) => Ok(Ident(pool.definition_name(*idx)?)), 419 | TypeId::Struct(idx) => Ok(Ident(pool.definition_name(*idx)?)), 420 | TypeId::Enum(idx) => Ok(Ident(pool.definition_name(*idx)?)), 421 | TypeId::Ref(idx) => Ok(Ident::new(format!("ref<{}>", idx.pretty(pool)?.0))), 422 | TypeId::WeakRef(idx) => Ok(Ident::new(format!("wref<{}>", idx.pretty(pool)?.0))), 423 | TypeId::Array(idx) => Ok(Ident::new(format!("array<{}>", idx.pretty(pool)?.0))), 424 | TypeId::StaticArray(idx, size) => Ok(Ident::new(format!("array<{}, {}>", idx.pretty(pool)?.0, size))), 425 | TypeId::ScriptRef(idx) => Ok(Ident::new(format!("script_ref<{}>", idx.pretty(pool)?.0))), 426 | TypeId::Null => Ok(Ident::new("Null".to_owned())), 427 | TypeId::Void => Ok(Ident::new("Void".to_owned())), 428 | } 429 | } 430 | } 431 | 432 | #[cfg(test)] 433 | mod tests { 434 | use std::io::Cursor; 435 | 436 | use redscript::bundle::ScriptBundle; 437 | use redscript::error::Error; 438 | 439 | use crate::{parser, Compiler}; 440 | 441 | const PREDEF: &[u8] = include_bytes!("../../resources/predef.redscripts"); 442 | 443 | #[test] 444 | fn compile_simple_class() -> Result<(), Error> { 445 | let sources = parser::parse( 446 | " 447 | public class A { 448 | private const let m_field: Int32; 449 | 450 | public func DoStuff(fieldOrNot: Bool) -> Int32 { 451 | return fieldOrNot ? this.m_field : A.Ten(); 452 | } 453 | 454 | public static func Ten() -> Int32 { 455 | return 10; 456 | } 457 | }", 458 | ) 459 | .unwrap(); 460 | 461 | let mut scripts = ScriptBundle::load(&mut Cursor::new(PREDEF))?; 462 | let mut compiler = Compiler::new(&mut scripts.pool)?; 463 | compiler.compile(sources) 464 | } 465 | 466 | #[test] 467 | fn compile_ext_class() -> Result<(), Error> { 468 | let sources = parser::parse( 469 | " 470 | public class X { 471 | private const let m_base_field: Int32; 472 | 473 | public func BaseMethod() -> Int32 { 474 | return this.m_base_field; 475 | } 476 | } 477 | 478 | public class Y extends X { 479 | public func CallBase() -> Int32 { 480 | return this.BaseMethod(); 481 | } 482 | }", 483 | ) 484 | .unwrap(); 485 | 486 | let mut scripts = ScriptBundle::load(&mut Cursor::new(PREDEF))?; 487 | let mut compiler = Compiler::new(&mut scripts.pool)?; 488 | compiler.compile(sources) 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /compiler/src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use peg::error::ParseError; 4 | use peg::str::LineCol; 5 | use redscript::ast::{BinOp, Constant, Expr, Ident, LiteralType, Pos, Seq, SwitchCase, TypeName, UnOp}; 6 | use redscript::definition::Visibility; 7 | use strum::EnumString; 8 | 9 | #[derive(Debug)] 10 | pub enum SourceEntry { 11 | Class(ClassSource), 12 | Function(FunctionSource), 13 | } 14 | #[derive(Debug)] 15 | pub struct ClassSource { 16 | pub qualifiers: Qualifiers, 17 | pub name: String, 18 | pub base: Option, 19 | pub members: Vec, 20 | pub pos: Pos, 21 | } 22 | 23 | #[derive(Debug)] 24 | pub enum MemberSource { 25 | Function(FunctionSource), 26 | Field(Declaration, TypeName), 27 | } 28 | 29 | #[derive(Debug)] 30 | pub struct FunctionSource { 31 | pub declaration: Declaration, 32 | pub type_: Option>, 33 | pub parameters: Vec, 34 | pub body: Option, 35 | } 36 | 37 | #[derive(Debug)] 38 | pub struct ParameterSource { 39 | pub qualifiers: Qualifiers, 40 | pub name: String, 41 | pub type_: TypeName, 42 | } 43 | 44 | #[derive(Debug, PartialEq, Eq)] 45 | pub enum Qualifier { 46 | Public, 47 | Protected, 48 | Private, 49 | Static, 50 | Final, 51 | Const, 52 | Native, 53 | Exec, 54 | Callback, 55 | Out, 56 | Optional, 57 | } 58 | 59 | #[derive(Debug)] 60 | pub struct Qualifiers(Vec); 61 | 62 | impl Qualifiers { 63 | pub fn visibility(&self) -> Option { 64 | self.0.iter().find_map(|q| match q { 65 | Qualifier::Public => Some(Visibility::Public), 66 | Qualifier::Protected => Some(Visibility::Protected), 67 | Qualifier::Private => Some(Visibility::Private), 68 | _ => None, 69 | }) 70 | } 71 | 72 | pub fn contain(&self, qualifier: Qualifier) -> bool { 73 | self.0.contains(&qualifier) 74 | } 75 | } 76 | 77 | #[derive(Debug)] 78 | pub struct Declaration { 79 | pub annotations: Vec, 80 | pub qualifiers: Qualifiers, 81 | pub name: String, 82 | pub pos: Pos, 83 | } 84 | 85 | #[derive(Debug)] 86 | pub struct Annotation { 87 | pub name: AnnotationName, 88 | pub value: String, 89 | pub pos: Pos, 90 | } 91 | 92 | impl Annotation {} 93 | 94 | #[derive(Debug, PartialEq, Eq, EnumString)] 95 | #[strum(serialize_all = "camelCase")] 96 | pub enum AnnotationName { 97 | ReplaceMethod, 98 | AddMethod, 99 | } 100 | 101 | pub fn parse(input: &str) -> Result, ParseError> { 102 | lang::source(input) 103 | } 104 | 105 | peg::parser! { 106 | grammar lang() for str { 107 | use peg::ParseLiteral; 108 | 109 | rule _() = quiet!{ ([' ' | '\n' | '\r' | '\t'] / comment() / line_comment())* } 110 | rule commasep(x: rule) -> Vec = v:(x() ** ("," _)) {v} 111 | 112 | rule comment_start() = "/*" 113 | rule comment_end() = "*/" 114 | rule comment_content() 115 | = comment() / (!comment_start() !comment_end() [_]) 116 | rule comment() 117 | = comment_start() comment_content()* comment_end() 118 | 119 | rule line_comment() = "//" $(!['\n'] [_])* 120 | 121 | rule qualifier() -> Qualifier 122 | = keyword("public") { Qualifier::Public } 123 | / keyword("protected") { Qualifier::Protected } 124 | / keyword("private") { Qualifier::Private } 125 | / keyword("static") { Qualifier::Static } 126 | / keyword("final") { Qualifier::Final } 127 | / keyword("const") { Qualifier::Const } 128 | / keyword("native") { Qualifier::Native } 129 | / keyword("exec") { Qualifier::Exec } 130 | / keyword("cb") { Qualifier::Callback } 131 | / keyword("out") { Qualifier::Out } 132 | / keyword("opt") { Qualifier::Optional } 133 | 134 | rule literal_type() -> LiteralType 135 | = "n" { LiteralType::Name } 136 | / "r" { LiteralType::Resource } 137 | / "t" { LiteralType::TweakDbId } 138 | 139 | rule annotation() -> Annotation 140 | = pos:position!() "@" ident:ident() _ "(" _ value:ident() _ ")" {? 141 | AnnotationName::from_str(&ident).map(|name| { 142 | Annotation { name, value, pos: Pos::new(pos) } 143 | }).map_err(|_| "annotation") 144 | } 145 | 146 | rule qualifiers() -> Qualifiers = qs:qualifier() ** _ { Qualifiers(qs) } 147 | 148 | rule ident() -> String 149 | = quiet!{ 150 | x:$(['a'..='z' | 'A'..='Z' | '_']) xs:$(['0'..='9' | 'a'..='z' | 'A'..='Z' | '_']*) 151 | { format!("{}{}", x, xs) } 152 | } / expected!("identifier") 153 | 154 | rule keyword(id: &'static str) -> () = 155 | ##parse_string_literal(id) !['0'..='9' | 'a'..='z' | 'A'..='Z' | '_'] 156 | 157 | rule number() -> Constant 158 | = n:$(['0'..='9' | '.']+) postfix:$(['u'])? 159 | { if n.contains('.') { Constant::Float(n.parse().unwrap()) } 160 | else if postfix == Some("u") { Constant::Uint(n.parse().unwrap()) } 161 | else { Constant::Int(n.parse().unwrap()) } 162 | } 163 | 164 | rule seq() -> Seq = exprs:(stmt() ** _) { Seq::new(exprs) } 165 | 166 | rule type_() -> TypeName 167 | = name:ident() args:type_args()? { TypeName { name, arguments: args.unwrap_or_default() } } 168 | rule type_args() -> Vec> = "<" _ args:commasep() _ ">" { args } 169 | 170 | rule let_type() -> TypeName = ":" _ type_:type_() { type_ } 171 | rule func_type() -> TypeName = "->" _ type_:type_() { type_ } 172 | 173 | rule decl(inner: rule<()>) -> Declaration 174 | = pos:position!() annotations:(annotation() ** _) _ qualifiers:qualifiers() _ inner() _ name:ident() 175 | { Declaration { annotations, qualifiers, name, pos: Pos::new(pos) } } 176 | 177 | rule let() -> Expr 178 | = pos:position!() keyword("let") _ name:ident() _ type_:let_type()? _ val:initializer()? _ ";" 179 | { Expr::Declare(Ident::new(name), type_, val.map(Box::new), Pos::new(pos)) } 180 | 181 | rule initializer() -> Expr = "=" _ val:expr() { val } 182 | 183 | pub rule function() -> FunctionSource 184 | = declaration:decl() _ "(" _ parameters:commasep() _ ")" _ type_:func_type()? _ body:function_body()? 185 | { FunctionSource { declaration, type_, parameters, body } } 186 | rule function_body() -> Seq = "{" _ body:seq() _ "}" { body } 187 | 188 | rule param() -> ParameterSource 189 | = qualifiers:qualifiers() _ name:ident() _ type_:let_type() 190 | { ParameterSource { qualifiers, name, type_ } } 191 | 192 | rule extends() -> String = keyword("extends") _ name:ident() { name } 193 | 194 | pub rule class() -> ClassSource 195 | = pos:position!() qualifiers:qualifiers() _ keyword("class") _ name:ident() _ base:extends()? _ "{" _ members:member()**_ _ "}" 196 | { ClassSource { qualifiers, name, base, members, pos: Pos::new(pos) } } 197 | 198 | rule member() -> MemberSource 199 | = fun:function() { MemberSource::Function(fun) } 200 | / decl:decl() _ type_:let_type() _ ";" { MemberSource::Field(decl, type_) } 201 | 202 | pub rule source_entry() -> SourceEntry 203 | = fun:function() { SourceEntry::Function(fun) } 204 | / class:class() { SourceEntry::Class(class) } 205 | 206 | pub rule source() -> Vec = _ decls:(source_entry() ** _) _ { decls } 207 | 208 | rule switch() -> Expr 209 | = keyword("switch") _ matcher:expr() _ "{" _ cases:(case() ** _) _ default:default()? _ "}" _ ";"? 210 | { Expr::Switch(Box::new(matcher), cases, default) } 211 | 212 | rule case() -> SwitchCase 213 | = keyword("case") _ matcher:expr() _ ":" _ body:seq() 214 | { SwitchCase(matcher, body) } 215 | 216 | rule default() -> Seq 217 | = keyword("default") _ ":" _ body:seq() { body } 218 | 219 | rule while_() -> Expr 220 | = pos:position!() keyword("while") _ cond:expr() _ "{" _ body:seq() _ "}" _ ";"? 221 | { Expr::While(Box::new(cond), body, Pos::new(pos)) } 222 | 223 | rule if_() -> Expr 224 | = pos:position!() keyword("if") _ cond:expr() _ "{" _ if_:seq() _ "}" _ else_:else_()? ";"? 225 | { Expr::If(Box::new(cond), if_, else_, Pos::new(pos)) } 226 | rule else_() -> Seq 227 | = keyword("else") _ "{" _ body:seq() _ "}" { body } 228 | 229 | pub rule stmt() -> Expr 230 | = while_: while_() { while_ } 231 | / if_: if_() { if_ } 232 | / switch: switch() { switch } 233 | / pos:position!() keyword("return") _ val:expr()? ";" { Expr::Return(val.map(Box::new), Pos::new(pos)) } 234 | / keyword("break") _ ";" { Expr::Break } 235 | / let_:let() { let_ } 236 | / expr:expr() _ ";" { expr } 237 | 238 | pub rule expr() -> Expr = precedence!{ 239 | x:@ _ pos:position!() "?" _ y:expr() _ ":" _ z:expr() 240 | { Expr::Conditional(Box::new(x), Box::new(y), Box::new(z), Pos::new(pos)) } 241 | x:@ _ pos:position!() "=" _ y:(@) { Expr::Assign(Box::new(x), Box::new(y), Pos::new(pos)) } 242 | x:@ _ pos:position!() "+=" _ y:(@) { Expr::BinOp(Box::new(x), Box::new(y), BinOp::AssignAdd, Pos::new(pos)) } 243 | x:@ _ pos:position!() "-=" _ y:(@) { Expr::BinOp(Box::new(x), Box::new(y), BinOp::AssignSub, Pos::new(pos)) } 244 | x:@ _ pos:position!() "*=" _ y:(@) { Expr::BinOp(Box::new(x), Box::new(y), BinOp::AssignMultiply, Pos::new(pos)) } 245 | x:@ _ pos:position!() "/=" _ y:(@) { Expr::BinOp(Box::new(x), Box::new(y), BinOp::AssignDivide, Pos::new(pos)) } 246 | -- 247 | x:(@) _ pos:position!() "||" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::LogicOr, Pos::new(pos)) } 248 | x:(@) _ pos:position!() "&&" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::LogicAnd, Pos::new(pos)) } 249 | x:(@) _ pos:position!() "|" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::Or, Pos::new(pos)) } 250 | x:(@) _ pos:position!() "^" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::Xor, Pos::new(pos)) } 251 | x:(@) _ pos:position!() "&" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::And, Pos::new(pos)) } 252 | -- 253 | x:(@) _ pos:position!() "==" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::Equal, Pos::new(pos)) } 254 | x:(@) _ pos:position!() "!=" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::NotEqual, Pos::new(pos)) } 255 | -- 256 | x:(@) _ pos:position!() "<" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::Less, Pos::new(pos)) } 257 | x:(@) _ pos:position!() "<=" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::LessEqual, Pos::new(pos)) } 258 | x:(@) _ pos:position!() ">" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::Greater, Pos::new(pos)) } 259 | x:(@) _ pos:position!() ">=" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::GreaterEqual, Pos::new(pos)) } 260 | -- 261 | x:(@) _ pos:position!() "+" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::Add, Pos::new(pos)) } 262 | x:(@) _ pos:position!() "-" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::Subtract, Pos::new(pos)) } 263 | -- 264 | x:(@) _ pos:position!() "*" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::Multiply, Pos::new(pos)) } 265 | x:(@) _ pos:position!() "/" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::Divide, Pos::new(pos)) } 266 | x:(@) _ pos:position!() "%" _ y:@ { Expr::BinOp(Box::new(x), Box::new(y), BinOp::Modulo, Pos::new(pos)) } 267 | -- 268 | pos:position!() "!" _ x:@ { Expr::UnOp(Box::new(x), UnOp::LogicNot, Pos::new(pos)) } 269 | pos:position!() "~" _ x:@ { Expr::UnOp(Box::new(x), UnOp::BitNot, Pos::new(pos)) } 270 | pos:position!() "-" _ x:@ { Expr::UnOp(Box::new(x), UnOp::Neg, Pos::new(pos)) } 271 | pos:position!() keyword("new") _ id:ident() _ "(" _ params:commasep() _ ")" 272 | { Expr::New(Ident::new(id), params, Pos::new(pos)) } 273 | -- 274 | expr:(@) _ pos:position!() "[" _ idx:expr() _ "]" 275 | { Expr::ArrayElem(Box::new(expr), Box::new(idx), Pos::new(pos)) } 276 | expr:(@) _ pos:position!() "." _ ident:ident() _ "(" _ params:commasep() _ ")" 277 | { Expr::MethodCall(Box::new(expr), Ident::new(ident), params, Pos::new(pos)) } 278 | expr:(@) _ pos:position!() "." _ ident:ident() 279 | { Expr::Member(Box::new(expr), Ident::new(ident), Pos::new(pos)) } 280 | expr:(@) _ pos:position!() keyword("as") _ type_:type_() 281 | { Expr::Cast(type_, Box::new(expr), Pos::new(pos)) } 282 | "(" _ v:expr() _ ")" { v } 283 | keyword("null") { Expr::Null } 284 | pos:position!() keyword("this") { Expr::This(Pos::new(pos)) } 285 | pos:position!() keyword("super") { Expr::Super(Pos::new(pos)) } 286 | pos:position!() keyword("true") { Expr::Constant(Constant::Bool(true), Pos::new(pos)) } 287 | pos:position!() keyword("false") { Expr::Constant(Constant::Bool(false), Pos::new(pos)) } 288 | pos:position!() type_:literal_type()? "\"" str:$((!['"'] [_])*) "\"" 289 | { Expr::Constant(Constant::String(type_.unwrap_or(LiteralType::String), str.to_owned()), Pos::new(pos)) } 290 | pos:position!() n:number() 291 | { Expr::Constant(n, Pos::new(pos)) } 292 | pos:position!() id:ident() _ "(" _ params:commasep() _ ")" 293 | { Expr::Call(Ident::new(id), params, Pos::new(pos)) } 294 | pos:position!() id:ident() 295 | { Expr::Ident(Ident::new(id), Pos::new(pos)) } 296 | } 297 | } 298 | } 299 | 300 | #[cfg(test)] 301 | mod tests { 302 | use super::*; 303 | 304 | #[test] 305 | fn parse_ternary_op() { 306 | let expr = lang::expr("3.0 ? 5.0 : 5 + 4").unwrap(); 307 | assert_eq!( 308 | format!("{:?}", expr), 309 | "Conditional(Constant(Float(3.0), Pos(0)), Constant(Float(5.0), Pos(6)), BinOp(Constant(Int(5), Pos(12)), \ 310 | Constant(Int(4), Pos(16)), Add, Pos(14)), Pos(4))" 311 | ); 312 | } 313 | 314 | #[test] 315 | fn parse_simple_class() { 316 | let class = lang::source( 317 | "public class A extends IScriptable { 318 | private const let m_field: Int32; 319 | 320 | public func GetField() -> Int32 { 321 | return this.m_field; 322 | } 323 | }", 324 | ) 325 | .unwrap(); 326 | assert_eq!( 327 | format!("{:?}", class), 328 | r#"[Class(ClassSource { qualifiers: Qualifiers([Public]), name: "A", base: Some("IScriptable"), members: [Field(Declaration { annotations: [], qualifiers: Qualifiers([Private, Const]), name: "m_field", pos: Pos(53) }, TypeName { name: "Int32", arguments: [] }), Function(FunctionSource { declaration: Declaration { annotations: [], qualifiers: Qualifiers([Public]), name: "GetField", pos: Pos(104) }, type_: Some(TypeName { name: "Int32", arguments: [] }), parameters: [], body: Some(Seq { exprs: [Return(Some(Member(This(Pos(165)), Ident("m_field"), Pos(169))), Pos(158))] }) })], pos: Pos(0) })]"# 329 | ); 330 | } 331 | 332 | #[test] 333 | fn parse_simple_func() { 334 | let class = lang::source( 335 | "public static func GetField(optimum: Uint64) -> Uint64 { 336 | return this.m_field > optimum ? this.m_field : optimum; 337 | }", 338 | ) 339 | .unwrap(); 340 | assert_eq!( 341 | format!("{:?}", class), 342 | r#"[Function(FunctionSource { declaration: Declaration { annotations: [], qualifiers: Qualifiers([Public, Static]), name: "GetField", pos: Pos(0) }, type_: Some(TypeName { name: "Uint64", arguments: [] }), parameters: [ParameterSource { qualifiers: Qualifiers([]), name: "optimum", type_: TypeName { name: "Uint64", arguments: [] } }], body: Some(Seq { exprs: [Return(Some(Conditional(BinOp(Member(This(Pos(80)), Ident("m_field"), Pos(84)), Ident(Ident("optimum"), Pos(95)), Greater, Pos(93)), Member(This(Pos(105)), Ident("m_field"), Pos(109)), Ident(Ident("optimum"), Pos(120)), Pos(103))), Pos(73))] }) })]"# 343 | ); 344 | } 345 | 346 | #[test] 347 | fn parse_simple_loop() { 348 | let stmt = lang::stmt( 349 | "while i < 1000 { 350 | this.counter += Object.CONSTANT; 351 | i += 1; 352 | }", 353 | ) 354 | .unwrap(); 355 | assert_eq!( 356 | format!("{:?}", stmt), 357 | r#"While(BinOp(Ident(Ident("i"), Pos(6)), Constant(Int(1000), Pos(10)), Less, Pos(8)), Seq { exprs: [BinOp(Member(This(Pos(33)), Ident("counter"), Pos(37)), Member(Ident(Ident("Object"), Pos(49)), Ident("CONSTANT"), Pos(55)), AssignAdd, Pos(46)), BinOp(Ident(Ident("i"), Pos(82)), Constant(Int(1), Pos(87)), AssignAdd, Pos(84))] }, Pos(0))"# 358 | ); 359 | } 360 | 361 | #[test] 362 | fn parse_simple_if_else() { 363 | let stmt = lang::stmt( 364 | "if this.m_fixBugs { 365 | this.NoBugs(); 366 | } else { 367 | this.Bugs(); 368 | }", 369 | ) 370 | .unwrap(); 371 | assert_eq!( 372 | format!("{:?}", stmt), 373 | r#"If(Member(This(Pos(3)), Ident("m_fixBugs"), Pos(7)), Seq { exprs: [MethodCall(This(Pos(36)), Ident("NoBugs"), [], Pos(40))] }, Some(Seq { exprs: [MethodCall(This(Pos(89)), Ident("Bugs"), [], Pos(93))] }), Pos(0))"# 374 | ); 375 | } 376 | 377 | #[test] 378 | fn parse_switch_case() { 379 | let stmt = lang::stmt( 380 | r#"switch value { 381 | case "0": 382 | case "1": 383 | Log("0 or 1"); 384 | case "2": 385 | break; 386 | default: 387 | Log("default"); 388 | }"#, 389 | ) 390 | .unwrap(); 391 | assert_eq!( 392 | format!("{:?}", stmt), 393 | r#"Switch(Ident(Ident("value"), Pos(7)), [SwitchCase(Constant(String(String, "0"), Pos(37)), Seq { exprs: [] }), SwitchCase(Constant(String(String, "1"), Pos(64)), Seq { exprs: [Call(Ident("Log"), [Constant(String(String, "0 or 1"), Pos(93))], Pos(89))] }), SwitchCase(Constant(String(String, "2"), Pos(126)), Seq { exprs: [Break] })], Some(Seq { exprs: [Call(Ident("Log"), [Constant(String(String, "default"), Pos(208))], Pos(204))] }))"# 394 | ); 395 | } 396 | 397 | #[test] 398 | fn parse_with_comment() { 399 | let stmt = lang::source( 400 | r#" 401 | /* this is a multiline comment 402 | blah blah blah 403 | */ 404 | class Test { 405 | private let m_field /* cool stuff */: String; 406 | } 407 | "#, 408 | ) 409 | .unwrap(); 410 | assert_eq!( 411 | format!("{:?}", stmt), 412 | r#"[Class(ClassSource { qualifiers: Qualifiers([]), name: "Test", base: None, members: [Field(Declaration { annotations: [], qualifiers: Qualifiers([Private]), name: "m_field", pos: Pos(130) }, TypeName { name: "String", arguments: [] })], pos: Pos(101) })]"# 413 | ); 414 | } 415 | 416 | #[test] 417 | fn parse_with_line_comment() { 418 | let stmt = lang::source( 419 | r#" 420 | class Test { // line comment 421 | // private let m_comment_field: String; 422 | private let m_field: String; 423 | } 424 | "#, 425 | ) 426 | .unwrap(); 427 | assert_eq!( 428 | format!("{:?}", stmt), 429 | r#"[Class(ClassSource { qualifiers: Qualifiers([]), name: "Test", base: None, members: [Field(Declaration { annotations: [], qualifiers: Qualifiers([Private]), name: "m_field", pos: Pos(114) }, TypeName { name: "String", arguments: [] })], pos: Pos(13) })]"# 430 | ); 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /core/src/bundle.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map; 2 | use std::collections::hash_map::{Entry, HashMap}; 3 | use std::hash::Hash; 4 | use std::io::Seek; 5 | use std::marker::PhantomData; 6 | use std::rc::Rc; 7 | use std::{fmt, io}; 8 | 9 | use modular_bitfield::prelude::*; 10 | 11 | use crate::decode::{Decode, DecodeExt}; 12 | use crate::definition::{Class, Definition, DefinitionValue, Enum, Field, Function, Local, Parameter, Type}; 13 | use crate::encode::{Encode, EncodeExt}; 14 | use crate::error::Error; 15 | 16 | #[derive(Debug)] 17 | pub struct ScriptBundle { 18 | pub pool: ConstantPool, 19 | } 20 | 21 | impl ScriptBundle { 22 | pub fn load(input: &mut I) -> Result { 23 | let pool = ConstantPool::decode(input)?; 24 | let cache = ScriptBundle { pool }; 25 | Ok(cache) 26 | } 27 | 28 | pub fn save(&self, output: &mut O) -> Result<(), Error> { 29 | self.pool.encode(output) 30 | } 31 | } 32 | 33 | #[derive(Debug)] 34 | pub struct Header { 35 | version: u32, 36 | unk1: u32, 37 | unk2: u32, 38 | unk3: u32, 39 | unk4: u32, 40 | hash: u32, 41 | unk5: u32, 42 | strings: TableHeader, 43 | names: TableHeader, 44 | tweakdb_indexes: TableHeader, 45 | resources: TableHeader, 46 | definitions: TableHeader, 47 | } 48 | 49 | impl Header { 50 | const MAGIC: u32 = 0x53444552; 51 | const SIZE: usize = 92; 52 | } 53 | 54 | impl Decode for Header { 55 | fn decode(input: &mut I) -> io::Result { 56 | let magic: u32 = input.decode()?; 57 | if magic != Header::MAGIC { 58 | return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid file header")); 59 | } 60 | 61 | let version: u32 = input.decode()?; 62 | let unk1: u32 = input.decode()?; 63 | let unk2: u32 = input.decode()?; 64 | let unk3: u32 = input.decode()?; 65 | let unk4: u32 = input.decode()?; 66 | let hash: u32 = input.decode()?; 67 | let unk5: u32 = input.decode()?; 68 | let strings: TableHeader = input.decode()?; 69 | let names: TableHeader = input.decode()?; 70 | let tweakdb_indexes: TableHeader = input.decode()?; 71 | let resources: TableHeader = input.decode()?; 72 | let definitions: TableHeader = input.decode()?; 73 | 74 | let result = Header { 75 | version, 76 | unk1, 77 | unk2, 78 | unk3, 79 | unk4, 80 | hash, 81 | unk5, 82 | strings, 83 | names, 84 | tweakdb_indexes, 85 | resources, 86 | definitions, 87 | }; 88 | Ok(result) 89 | } 90 | } 91 | 92 | impl Encode for Header { 93 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 94 | output.encode(&Header::MAGIC)?; 95 | output.encode(&value.version)?; 96 | output.encode(&value.unk1)?; 97 | output.encode(&value.unk2)?; 98 | output.encode(&value.unk3)?; 99 | output.encode(&value.unk4)?; 100 | output.encode(&value.hash)?; 101 | output.encode(&value.unk5)?; 102 | output.encode(&value.strings)?; 103 | output.encode(&value.names)?; 104 | output.encode(&value.tweakdb_indexes)?; 105 | output.encode(&value.resources)?; 106 | output.encode(&value.definitions) 107 | } 108 | } 109 | 110 | #[derive(Debug)] 111 | pub struct ConstantPool { 112 | header: Header, 113 | pub names: Names, 114 | pub tweakdb_ids: Names, 115 | pub resources: Names, 116 | definitions: Vec, 117 | } 118 | 119 | impl ConstantPool { 120 | pub fn decode(input: &mut I) -> Result { 121 | let header: Header = input.decode()?; 122 | let buffer = input.decode_bytes(header.strings.count)?; 123 | 124 | let mut cursor = io::Cursor::new(buffer); 125 | 126 | let names = Names::decode_from(&mut cursor, &input.decode_vec(header.names.count)?)?; 127 | let tweakdb_ids = Names::decode_from(&mut cursor, &input.decode_vec(header.tweakdb_indexes.count)?)?; 128 | let resources = Names::decode_from(&mut cursor, &input.decode_vec(header.resources.count)?)?; 129 | 130 | input.seek(io::SeekFrom::Start(header.definitions.offset.into()))?; 131 | let headers: Vec = input.decode_vec(header.definitions.count)?; 132 | 133 | let mut definitions = Vec::with_capacity(headers.len()); 134 | definitions.push(Definition::DEFAULT); 135 | 136 | for header in headers.iter().skip(1) { 137 | let definition = Definition::decode(input, header)?; 138 | definitions.push(definition); 139 | } 140 | 141 | let result = ConstantPool { 142 | header, 143 | names, 144 | tweakdb_ids, 145 | resources, 146 | definitions, 147 | }; 148 | Ok(result) 149 | } 150 | 151 | pub fn encode(&self, output: &mut O) -> Result<(), Error> { 152 | let mut buffer = io::Cursor::new(Vec::with_capacity(self.header.strings.count as usize)); 153 | let mut dedup_map = HashMap::new(); 154 | for str in self 155 | .names 156 | .strings 157 | .iter() 158 | .chain(self.tweakdb_ids.strings.iter()) 159 | .chain(self.resources.strings.iter()) 160 | { 161 | match dedup_map.entry(str.clone()) { 162 | hash_map::Entry::Vacant(entry) => { 163 | entry.insert(buffer.stream_position()? as u32); 164 | buffer.encode(&str.as_str())?; 165 | } 166 | hash_map::Entry::Occupied(_) => {} 167 | } 168 | } 169 | 170 | output.seek(io::SeekFrom::Start(Header::SIZE as u64))?; 171 | 172 | let position = output.stream_position()? as u32; 173 | let strings = TableHeader::new(buffer.get_ref(), buffer.position() as u32, position); 174 | output.write_all(buffer.get_ref())?; 175 | 176 | let name_offsets = self.names.encoded_offsets(&dedup_map)?; 177 | let position = output.stream_position()? as u32; 178 | let names = TableHeader::new(&name_offsets, self.names.strings.len() as u32, position); 179 | output.write_all(&name_offsets)?; 180 | 181 | let tweakdb_offsets = self.tweakdb_ids.encoded_offsets(&dedup_map)?; 182 | let position = output.stream_position()? as u32; 183 | let tweakdb_indexes = TableHeader::new(&tweakdb_offsets, self.tweakdb_ids.strings.len() as u32, position); 184 | output.write_all(&tweakdb_offsets)?; 185 | 186 | let resource_offsets = self.resources.encoded_offsets(&dedup_map)?; 187 | let position = output.stream_position()? as u32; 188 | let resources = TableHeader::new(&resource_offsets, self.resources.strings.len() as u32, position); 189 | output.write_all(&resource_offsets)?; 190 | 191 | let def_header_pos = output.stream_position()?; 192 | let def_header_size = DefinitionHeader::SIZE as u64 * self.definitions.len() as u64; 193 | output.seek(io::SeekFrom::Current(def_header_size as i64))?; 194 | 195 | let mut buffer = io::Cursor::new(Vec::with_capacity(def_header_size as usize)); 196 | for (idx, definition) in self.definitions.iter().enumerate() { 197 | if idx == 0 { 198 | buffer.encode(&DefinitionHeader::DEFAULT)?; 199 | } else { 200 | let header = DefinitionHeader::encode_definition(output, definition)?; 201 | buffer.encode(&header)?; 202 | } 203 | } 204 | output.seek(io::SeekFrom::Start(def_header_pos))?; 205 | output.write_all(buffer.get_ref())?; 206 | 207 | let definitions = TableHeader::new(buffer.get_ref(), self.definitions.len() as u32, def_header_pos as u32); 208 | let header_for_hash = Header { 209 | strings, 210 | names, 211 | tweakdb_indexes, 212 | resources, 213 | definitions, 214 | hash: 0xDEADBEEF, 215 | ..self.header 216 | }; 217 | 218 | let mut buffer = io::Cursor::new(Vec::with_capacity(Header::SIZE)); 219 | buffer.encode(&header_for_hash)?; 220 | 221 | let header = Header { 222 | hash: crc(buffer.get_ref()), 223 | ..header_for_hash 224 | }; 225 | output.seek(io::SeekFrom::Start(0))?; 226 | output.encode(&header)?; 227 | Ok(()) 228 | } 229 | 230 | pub fn definition(&self, index: PoolIndex) -> Result<&Definition, Error> { 231 | self.definitions 232 | .get(index.index) 233 | .ok_or_else(|| Error::PoolError(format!("Definition {} not found", index.index))) 234 | } 235 | 236 | pub fn function(&self, index: PoolIndex) -> Result<&Function, Error> { 237 | if let DefinitionValue::Function(ref fun) = self.definition(index)?.value { 238 | Ok(fun) 239 | } else { 240 | Err(Error::PoolError(format!("{} is not a function", index.index))) 241 | } 242 | } 243 | 244 | pub fn function_mut(&mut self, index: PoolIndex) -> Result<&mut Function, Error> { 245 | let result = self.definitions.get_mut(index.index).map(|def| &mut def.value); 246 | if let Some(DefinitionValue::Function(fun)) = result { 247 | Ok(fun) 248 | } else { 249 | Err(Error::PoolError(format!("{} is not a function", index.index))) 250 | } 251 | } 252 | 253 | pub fn field(&self, index: PoolIndex) -> Result<&Field, Error> { 254 | if let DefinitionValue::Field(ref field) = self.definition(index)?.value { 255 | Ok(field) 256 | } else { 257 | Err(Error::PoolError(format!("{} is not a field", index.index))) 258 | } 259 | } 260 | 261 | pub fn parameter(&self, index: PoolIndex) -> Result<&Parameter, Error> { 262 | if let DefinitionValue::Parameter(ref param) = self.definition(index)?.value { 263 | Ok(param) 264 | } else { 265 | Err(Error::PoolError(format!("{} is not a parameter", index.index))) 266 | } 267 | } 268 | 269 | pub fn local(&self, index: PoolIndex) -> Result<&Local, Error> { 270 | if let DefinitionValue::Local(ref local) = self.definition(index)?.value { 271 | Ok(local) 272 | } else { 273 | Err(Error::PoolError(format!("{} is not a local", index.index))) 274 | } 275 | } 276 | 277 | pub fn type_(&self, index: PoolIndex) -> Result<&Type, Error> { 278 | if let DefinitionValue::Type(ref type_) = self.definition(index)?.value { 279 | Ok(type_) 280 | } else { 281 | Err(Error::PoolError(format!("{} is not a type", index.index))) 282 | } 283 | } 284 | 285 | pub fn class(&self, index: PoolIndex) -> Result<&Class, Error> { 286 | if let DefinitionValue::Class(ref class) = self.definition(index)?.value { 287 | Ok(class) 288 | } else { 289 | Err(Error::PoolError(format!("{} is not a class", index.index))) 290 | } 291 | } 292 | 293 | pub fn class_mut(&mut self, index: PoolIndex) -> Result<&mut Class, Error> { 294 | let result = self.definitions.get_mut(index.index).map(|def| &mut def.value); 295 | if let Some(DefinitionValue::Class(fun)) = result { 296 | Ok(fun) 297 | } else { 298 | Err(Error::PoolError(format!("{} is not a class", index.index))) 299 | } 300 | } 301 | 302 | pub fn enum_(&self, index: PoolIndex) -> Result<&Enum, Error> { 303 | if let DefinitionValue::Enum(ref enum_) = self.definition(index)?.value { 304 | Ok(enum_) 305 | } else { 306 | Err(Error::PoolError(format!("{} is not an enum", index.index))) 307 | } 308 | } 309 | 310 | pub fn definition_name(&self, index: PoolIndex) -> Result, Error> { 311 | self.names.get(self.definition(index)?.name) 312 | } 313 | 314 | pub fn definitions(&self) -> impl Iterator, &Definition)> { 315 | self.definitions 316 | .iter() 317 | .enumerate() 318 | .skip(1) 319 | .map(|(index, def)| (PoolIndex::new(index), def)) 320 | } 321 | 322 | pub fn reserve(&mut self) -> PoolIndex { 323 | self.push_definition(Definition::DEFAULT) 324 | } 325 | 326 | pub fn put_definition(&mut self, index: PoolIndex, definition: Definition) { 327 | self.definitions[index.index] = definition; 328 | } 329 | 330 | pub fn push_definition(&mut self, definition: Definition) -> PoolIndex { 331 | let position = self.definitions.len(); 332 | self.definitions.push(definition); 333 | PoolIndex::new(position) 334 | } 335 | 336 | pub fn roots(&self) -> impl Iterator, &Definition)> { 337 | self.definitions().filter(|(_, def)| def.parent.is_undefined()) 338 | } 339 | } 340 | 341 | #[derive(Debug)] 342 | pub struct Names { 343 | pub strings: Vec>, 344 | mappings: HashMap, PoolIndex>, 345 | phantom: PhantomData, 346 | } 347 | 348 | impl Names { 349 | fn decode_from(input: &mut I, offsets: &[u32]) -> io::Result> { 350 | let mut strings = Vec::with_capacity(offsets.len()); 351 | let mut mappings = HashMap::new(); 352 | for (idx, offset) in offsets.iter().enumerate() { 353 | input.seek(io::SeekFrom::Start((*offset).into()))?; 354 | let str: Rc = Rc::new(input.decode()?); 355 | strings.push(str.clone()); 356 | mappings.insert(str, PoolIndex::new(idx)); 357 | } 358 | let result = Names { 359 | strings, 360 | mappings, 361 | phantom: PhantomData, 362 | }; 363 | Ok(result) 364 | } 365 | 366 | fn encoded_offsets(&self, str_map: &HashMap, u32>) -> io::Result> { 367 | let mut offsets = io::Cursor::new(Vec::new()); 368 | for string in &self.strings { 369 | offsets.encode(str_map.get(string).unwrap())?; 370 | } 371 | Ok(offsets.into_inner()) 372 | } 373 | 374 | pub fn get(&self, index: PoolIndex) -> Result, Error> { 375 | self.strings 376 | .get(index.index) 377 | .ok_or_else(|| Error::PoolError(format!("String {} not found", index.index))) 378 | .cloned() 379 | } 380 | 381 | pub fn add(&mut self, str: String) -> PoolIndex { 382 | let idx = PoolIndex::new(self.strings.len()); 383 | let rc = Rc::new(str); 384 | match self.mappings.entry(rc.clone()) { 385 | Entry::Occupied(entry) => *entry.get(), 386 | Entry::Vacant(slot) => { 387 | self.strings.push(rc); 388 | *slot.insert(idx) 389 | } 390 | } 391 | } 392 | } 393 | 394 | #[derive(Debug)] 395 | struct TableHeader { 396 | offset: u32, 397 | count: u32, 398 | hash: u32, 399 | } 400 | 401 | impl TableHeader { 402 | fn new(bytes: &[u8], count: u32, offset: u32) -> TableHeader { 403 | TableHeader { 404 | offset, 405 | count, 406 | hash: crc(bytes), 407 | } 408 | } 409 | } 410 | 411 | impl Decode for TableHeader { 412 | fn decode(input: &mut I) -> io::Result { 413 | let offset = input.decode()?; 414 | let count = input.decode()?; 415 | let hash = input.decode()?; 416 | let result = TableHeader { offset, count, hash }; 417 | Ok(result) 418 | } 419 | } 420 | 421 | impl Encode for TableHeader { 422 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 423 | output.encode(&value.offset)?; 424 | output.encode(&value.count)?; 425 | output.encode(&value.hash) 426 | } 427 | } 428 | 429 | #[derive(Debug)] 430 | pub struct DefinitionHeader { 431 | pub name: PoolIndex, 432 | pub parent: PoolIndex, 433 | pub offset: u32, 434 | pub size: u32, 435 | pub type_: DefinitionType, 436 | pub unk1: u8, 437 | pub unk2: u8, 438 | pub unk3: u8, 439 | } 440 | 441 | impl DefinitionHeader { 442 | const SIZE: usize = 20; 443 | 444 | const DEFAULT: DefinitionHeader = DefinitionHeader { 445 | name: PoolIndex::UNDEFINED, 446 | parent: PoolIndex::UNDEFINED, 447 | offset: 0, 448 | size: 0, 449 | type_: DefinitionType::Type, 450 | unk1: 0, 451 | unk2: 213, 452 | unk3: 222, 453 | }; 454 | 455 | fn encode_definition( 456 | output: &mut O, 457 | definition: &Definition, 458 | ) -> io::Result { 459 | let offset = output.stream_position()?; 460 | output.encode(&definition.value)?; 461 | let size = output.stream_position()?; 462 | let header = DefinitionHeader { 463 | name: definition.name, 464 | parent: definition.parent, 465 | offset: offset as u32, 466 | size: size as u32, 467 | type_: definition.value.type_(), 468 | unk1: definition.unk1, 469 | unk2: definition.unk2, 470 | unk3: definition.unk3, 471 | }; 472 | Ok(header) 473 | } 474 | } 475 | 476 | impl Decode for DefinitionHeader { 477 | fn decode(input: &mut I) -> io::Result { 478 | let name = input.decode()?; 479 | let parent = input.decode()?; 480 | let offset = input.decode()?; 481 | let size = input.decode()?; 482 | let type_ = input.decode()?; 483 | let unk1 = input.decode()?; 484 | let unk2 = input.decode()?; 485 | let unk3 = input.decode()?; 486 | let result = DefinitionHeader { 487 | name, 488 | parent, 489 | offset, 490 | size, 491 | type_, 492 | unk1, 493 | unk2, 494 | unk3, 495 | }; 496 | Ok(result) 497 | } 498 | } 499 | 500 | impl Encode for DefinitionHeader { 501 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 502 | output.encode(&value.name)?; 503 | output.encode(&value.parent)?; 504 | output.encode(&value.offset)?; 505 | output.encode(&value.size)?; 506 | output.encode(&value.type_)?; 507 | output.encode(&value.unk1)?; 508 | output.encode(&value.unk2)?; 509 | output.encode(&value.unk3) 510 | } 511 | } 512 | 513 | #[derive(BitfieldSpecifier)] 514 | #[bits = 8] 515 | #[derive(Debug, Clone, Copy)] 516 | pub enum DefinitionType { 517 | Type = 0, 518 | Class = 1, 519 | EnumValue = 2, 520 | Enum = 3, 521 | BitField = 4, 522 | Function = 5, 523 | Parameter = 6, 524 | Local = 7, 525 | Field = 8, 526 | SourceFile = 9, 527 | } 528 | 529 | impl Decode for DefinitionType { 530 | fn decode(input: &mut I) -> io::Result { 531 | Ok(DefinitionType::from_bytes(input.decode()?).expect("Invalid DefinitionType enum value")) 532 | } 533 | } 534 | 535 | impl Encode for DefinitionType { 536 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 537 | output.encode(&DefinitionType::into_bytes(*value).unwrap()) 538 | } 539 | } 540 | 541 | pub struct PoolIndex { 542 | index: usize, 543 | phantom: PhantomData, 544 | } 545 | 546 | impl PoolIndex { 547 | const fn new(index: usize) -> PoolIndex { 548 | PoolIndex { 549 | index, 550 | phantom: PhantomData, 551 | } 552 | } 553 | 554 | pub const UNDEFINED: PoolIndex = PoolIndex::new(0); 555 | pub const DEFAULT_SOURCE: PoolIndex = PoolIndex::new(1); 556 | 557 | pub fn is_undefined(&self) -> bool { 558 | self.index == 0 559 | } 560 | 561 | pub fn cast(&self) -> PoolIndex { 562 | PoolIndex { 563 | index: self.index, 564 | phantom: PhantomData, 565 | } 566 | } 567 | } 568 | 569 | impl Decode for PoolIndex { 570 | fn decode(input: &mut I) -> io::Result { 571 | let index = input.decode::()? as usize; 572 | Ok(PoolIndex { 573 | index, 574 | phantom: PhantomData, 575 | }) 576 | } 577 | } 578 | 579 | impl Encode for PoolIndex { 580 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 581 | output.encode(&(value.index as u32)) 582 | } 583 | } 584 | 585 | impl Clone for PoolIndex { 586 | fn clone(&self) -> Self { 587 | *self 588 | } 589 | } 590 | 591 | impl Copy for PoolIndex {} 592 | 593 | impl fmt::Debug for PoolIndex { 594 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 595 | f.debug_tuple("PoolIndex").field(&self.index).finish() 596 | } 597 | } 598 | 599 | impl PartialEq for PoolIndex { 600 | fn eq(&self, other: &Self) -> bool { 601 | self.index == other.index 602 | } 603 | } 604 | 605 | impl Eq for PoolIndex {} 606 | 607 | impl Hash for PoolIndex { 608 | fn hash(&self, state: &mut H) { 609 | Hash::hash(&self.index, state) 610 | } 611 | } 612 | 613 | #[derive(Debug)] 614 | pub struct Resource(String); 615 | 616 | #[derive(Debug)] 617 | pub struct TweakDbId(String); 618 | 619 | fn crc(bytes: &[u8]) -> u32 { 620 | let mut hasher = crc32fast::Hasher::new(); 621 | hasher.update(bytes); 622 | hasher.finalize() 623 | } 624 | 625 | #[cfg(test)] 626 | mod tests { 627 | use std::io::Cursor; 628 | 629 | use super::ScriptBundle; 630 | use crate::error::Error; 631 | 632 | const PREDEF: &[u8] = include_bytes!("../../resources/predef.redscripts"); 633 | 634 | #[test] 635 | fn reload_scripts() -> Result<(), Error> { 636 | let scripts = ScriptBundle::load(&mut Cursor::new(PREDEF))?; 637 | let mut tmp = Cursor::new(Vec::new()); 638 | scripts.save(&mut tmp)?; 639 | tmp.set_position(0); 640 | let scripts2 = ScriptBundle::load(&mut tmp)?; 641 | assert_eq!(scripts.pool.definitions.len(), scripts2.pool.definitions.len()); 642 | Ok(()) 643 | } 644 | } 645 | -------------------------------------------------------------------------------- /core/src/definition.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::io; 3 | use std::path::PathBuf; 4 | 5 | use modular_bitfield::prelude::*; 6 | 7 | use crate::bundle::{ConstantPool, DefinitionHeader, DefinitionType, PoolIndex}; 8 | use crate::bytecode::Code; 9 | use crate::decode::{Decode, DecodeExt}; 10 | use crate::encode::{Encode, EncodeExt}; 11 | 12 | #[derive(Debug)] 13 | pub struct Definition { 14 | pub name: PoolIndex, 15 | pub parent: PoolIndex, 16 | pub unk1: u8, 17 | pub unk2: u8, 18 | pub unk3: u8, 19 | pub value: DefinitionValue, 20 | } 21 | 22 | impl Definition { 23 | pub const DEFAULT: Definition = Definition { 24 | name: PoolIndex::UNDEFINED, 25 | parent: PoolIndex::UNDEFINED, 26 | unk1: 0, 27 | unk2: 0, 28 | unk3: 0, 29 | value: DefinitionValue::Type(Type::Prim), 30 | }; 31 | 32 | pub fn decode(input: &mut I, header: &DefinitionHeader) -> io::Result { 33 | input.seek(io::SeekFrom::Start(header.offset.into()))?; 34 | 35 | let value = match header.type_ { 36 | DefinitionType::Type => DefinitionValue::Type(input.decode()?), 37 | DefinitionType::Class => DefinitionValue::Class(input.decode()?), 38 | DefinitionType::EnumValue => DefinitionValue::EnumValue(input.decode()?), 39 | DefinitionType::Enum => DefinitionValue::Enum(input.decode()?), 40 | DefinitionType::BitField => panic!("Bit field not supported"), 41 | DefinitionType::Function => DefinitionValue::Function(input.decode()?), 42 | DefinitionType::Parameter => DefinitionValue::Parameter(input.decode()?), 43 | DefinitionType::Local => DefinitionValue::Local(input.decode()?), 44 | DefinitionType::Field => DefinitionValue::Field(input.decode()?), 45 | DefinitionType::SourceFile => DefinitionValue::SourceFile(input.decode()?), 46 | }; 47 | let definition = Definition { 48 | name: header.name, 49 | parent: header.parent, 50 | unk1: header.unk1, 51 | unk2: header.unk2, 52 | unk3: header.unk3, 53 | value, 54 | }; 55 | Ok(definition) 56 | } 57 | 58 | pub fn source(&self) -> Option<&SourceReference> { 59 | match self.value { 60 | DefinitionValue::Function(ref fun) => fun.source.as_ref(), 61 | _ => None, 62 | } 63 | } 64 | 65 | pub fn first_line(&self, pool: &ConstantPool) -> Option { 66 | match self.value { 67 | DefinitionValue::Function(ref fun) => fun.source.as_ref().map(|source| source.line), 68 | DefinitionValue::Class(ref class) => class 69 | .functions 70 | .iter() 71 | .filter_map(|idx| { 72 | pool.function(*idx) 73 | .ok() 74 | .and_then(|fun| fun.source.as_ref()) 75 | .map(|source| source.line) 76 | }) 77 | .min(), 78 | _ => None, 79 | } 80 | } 81 | 82 | fn default( 83 | name: PoolIndex, 84 | parent: PoolIndex, 85 | value: DefinitionValue, 86 | unk2: u8, 87 | unk3: u8, 88 | ) -> Definition { 89 | Definition { 90 | name, 91 | parent, 92 | unk1: 0, 93 | unk2, 94 | unk3, 95 | value, 96 | } 97 | } 98 | 99 | pub fn local(name: PoolIndex, parent: PoolIndex, local: Local) -> Definition { 100 | Definition::default(name, parent.cast(), DefinitionValue::Local(local), 7, 60) 101 | } 102 | 103 | pub fn param(name: PoolIndex, parent: PoolIndex, param: Parameter) -> Definition { 104 | Definition::default(name, parent.cast(), DefinitionValue::Parameter(param), 7, 60) 105 | } 106 | 107 | pub fn class(name: PoolIndex, class: Class) -> Definition { 108 | Definition::default(name, PoolIndex::UNDEFINED, DefinitionValue::Class(class), 12, 60) 109 | } 110 | 111 | pub fn type_(name: PoolIndex, type_: Type) -> Definition { 112 | Definition::default(name, PoolIndex::UNDEFINED, DefinitionValue::Type(type_), 12, 60) 113 | } 114 | 115 | pub fn function(name: PoolIndex, parent: PoolIndex, fun: Function) -> Definition { 116 | Definition::default(name, parent.cast(), DefinitionValue::Function(fun), 12, 60) 117 | } 118 | 119 | pub fn field(name: PoolIndex, parent: PoolIndex, field: Field) -> Definition { 120 | Definition::default(name, parent.cast(), DefinitionValue::Field(field), 12, 60) 121 | } 122 | } 123 | 124 | #[derive(Debug)] 125 | pub enum DefinitionValue { 126 | Type(Type), 127 | Class(Class), 128 | EnumValue(i64), 129 | Enum(Enum), 130 | Function(Function), 131 | Parameter(Parameter), 132 | Local(Local), 133 | Field(Field), 134 | SourceFile(SourceFile), 135 | } 136 | 137 | impl DefinitionValue { 138 | pub fn type_(&self) -> DefinitionType { 139 | match self { 140 | DefinitionValue::Type(_) => DefinitionType::Type, 141 | DefinitionValue::Class(_) => DefinitionType::Class, 142 | DefinitionValue::EnumValue(_) => DefinitionType::EnumValue, 143 | DefinitionValue::Enum(_) => DefinitionType::Enum, 144 | DefinitionValue::Function(_) => DefinitionType::Function, 145 | DefinitionValue::Parameter(_) => DefinitionType::Parameter, 146 | DefinitionValue::Local(_) => DefinitionType::Local, 147 | DefinitionValue::Field(_) => DefinitionType::Field, 148 | DefinitionValue::SourceFile(_) => DefinitionType::SourceFile, 149 | } 150 | } 151 | } 152 | 153 | impl Encode for DefinitionValue { 154 | fn encode(output: &mut O, def: &Self) -> io::Result<()> { 155 | match &def { 156 | DefinitionValue::Type(type_) => output.encode(type_), 157 | DefinitionValue::Class(class) => output.encode(class), 158 | DefinitionValue::EnumValue(value) => output.encode(value), 159 | DefinitionValue::Enum(enum_) => output.encode(enum_), 160 | DefinitionValue::Function(fun) => output.encode(fun), 161 | DefinitionValue::Parameter(param) => output.encode(param), 162 | DefinitionValue::Local(local) => output.encode(local), 163 | DefinitionValue::Field(field) => output.encode(field), 164 | DefinitionValue::SourceFile(file) => output.encode(file), 165 | } 166 | } 167 | } 168 | 169 | #[derive(Debug)] 170 | pub struct Class { 171 | pub visibility: Visibility, 172 | pub flags: ClassFlags, 173 | pub base: PoolIndex, 174 | pub functions: Vec>, 175 | pub fields: Vec>, 176 | pub overrides: Vec>, 177 | } 178 | 179 | impl Decode for Class { 180 | fn decode(input: &mut I) -> io::Result { 181 | let visibility = input.decode()?; 182 | let flags: ClassFlags = input.decode()?; 183 | let base = input.decode()?; 184 | let functions = if flags.has_functions() { 185 | input.decode_vec_prefixed::>()? 186 | } else { 187 | vec![] 188 | }; 189 | let fields = if flags.has_fields() { 190 | input.decode_vec_prefixed::>()? 191 | } else { 192 | vec![] 193 | }; 194 | let overrides = if flags.has_overrides() { 195 | input.decode_vec_prefixed::>()? 196 | } else { 197 | vec![] 198 | }; 199 | 200 | let result = Class { 201 | visibility, 202 | flags, 203 | base, 204 | functions, 205 | fields, 206 | overrides, 207 | }; 208 | 209 | Ok(result) 210 | } 211 | } 212 | 213 | impl Encode for Class { 214 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 215 | let flags = value 216 | .flags 217 | .with_has_functions(!value.functions.is_empty()) 218 | .with_has_fields(!value.fields.is_empty()) 219 | .with_has_overrides(!value.overrides.is_empty()); 220 | 221 | output.encode(&value.visibility)?; 222 | output.encode(&flags)?; 223 | output.encode(&value.base)?; 224 | if flags.has_functions() { 225 | output.encode_slice_prefixed::>(&value.functions)?; 226 | } 227 | if flags.has_fields() { 228 | output.encode_slice_prefixed::>(&value.fields)?; 229 | } 230 | if flags.has_overrides() { 231 | output.encode_slice_prefixed::>(&value.overrides)?; 232 | } 233 | Ok(()) 234 | } 235 | } 236 | 237 | #[derive(Debug)] 238 | pub struct Enum { 239 | pub flags: u8, 240 | pub size: u8, 241 | pub members: Vec>, 242 | pub unk1: bool, 243 | } 244 | 245 | impl Decode for Enum { 246 | fn decode(input: &mut I) -> io::Result { 247 | let flags = input.decode()?; 248 | let size = input.decode()?; 249 | let members = input.decode_vec_prefixed::>()?; 250 | let unk1 = input.decode()?; 251 | let result = Enum { 252 | flags, 253 | size, 254 | members, 255 | unk1, 256 | }; 257 | 258 | Ok(result) 259 | } 260 | } 261 | 262 | impl Encode for Enum { 263 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 264 | output.encode(&value.flags)?; 265 | output.encode(&value.size)?; 266 | output.encode_slice_prefixed::>(&value.members)?; 267 | output.encode(&value.unk1) 268 | } 269 | } 270 | 271 | #[derive(Debug)] 272 | pub struct Function { 273 | pub visibility: Visibility, 274 | pub flags: FunctionFlags, 275 | pub source: Option, 276 | pub return_type: Option>, 277 | pub unk1: bool, 278 | pub base_method: Option>, 279 | pub parameters: Vec>, 280 | pub locals: Vec>, 281 | pub operator: Option, 282 | pub cast: u8, 283 | pub code: Code, 284 | } 285 | 286 | impl Decode for Function { 287 | fn decode(input: &mut I) -> io::Result { 288 | let visibility = input.decode()?; 289 | let flags: FunctionFlags = input.decode()?; 290 | let source = if flags.is_native() { None } else { Some(input.decode()?) }; 291 | let return_type = if flags.has_return_value() { 292 | Some(input.decode()?) 293 | } else { 294 | None 295 | }; 296 | let unk1 = if flags.has_return_value() { 297 | input.decode()? 298 | } else { 299 | false 300 | }; 301 | let base_method = if flags.has_base_method() { 302 | Some(input.decode()?) 303 | } else { 304 | None 305 | }; 306 | let parameters = if flags.has_parameters() { 307 | input.decode_vec_prefixed::>()? 308 | } else { 309 | vec![] 310 | }; 311 | let locals = if flags.has_locals() { 312 | input.decode_vec_prefixed::>()? 313 | } else { 314 | vec![] 315 | }; 316 | let operator = if flags.is_operator_overload() { 317 | Some(input.decode()?) 318 | } else { 319 | None 320 | }; 321 | let cast = if flags.is_cast() { input.decode()? } else { 0u8 }; 322 | let code = if flags.has_body() { input.decode()? } else { Code::EMPTY }; 323 | 324 | let result = Function { 325 | visibility, 326 | flags, 327 | source, 328 | return_type, 329 | unk1, 330 | base_method, 331 | parameters, 332 | locals, 333 | operator, 334 | cast, 335 | code, 336 | }; 337 | 338 | Ok(result) 339 | } 340 | } 341 | 342 | impl Encode for Function { 343 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 344 | let flags = value 345 | .flags 346 | .with_has_return_value(value.return_type.is_some()) 347 | .with_has_base_method(value.base_method.is_some()) 348 | .with_has_parameters(!value.parameters.is_empty()) 349 | .with_has_locals(!value.locals.is_empty()) 350 | .with_is_operator_overload(value.operator.is_some()) 351 | .with_has_body(!value.code.is_empty()); 352 | 353 | output.encode(&value.visibility)?; 354 | output.encode(&flags)?; 355 | if let Some(ref source) = value.source { 356 | output.encode(source)?; 357 | } 358 | if let Some(ref type_) = value.return_type { 359 | output.encode(type_)?; 360 | output.encode(&value.unk1)?; 361 | } 362 | if let Some(ref method) = value.base_method { 363 | output.encode(method)?; 364 | } 365 | if flags.has_parameters() { 366 | output.encode_slice_prefixed::>(&value.parameters)?; 367 | } 368 | if flags.has_locals() { 369 | output.encode_slice_prefixed::>(&value.locals)?; 370 | } 371 | if let Some(ref operator) = value.operator { 372 | output.encode(operator)?; 373 | } 374 | if flags.is_cast() { 375 | output.encode(&value.cast)?; 376 | } 377 | if flags.has_body() { 378 | output.encode(&value.code)?; 379 | } 380 | Ok(()) 381 | } 382 | } 383 | 384 | #[derive(Debug)] 385 | pub struct Field { 386 | pub visibility: Visibility, 387 | pub type_: PoolIndex, 388 | pub flags: FieldFlags, 389 | pub hint: Option, 390 | pub attributes: Vec, 391 | pub defaults: Vec, 392 | } 393 | 394 | impl Decode for Field { 395 | fn decode(input: &mut I) -> io::Result { 396 | let visibility = input.decode()?; 397 | let type_ = input.decode()?; 398 | let flags: FieldFlags = input.decode()?; 399 | let hint = if flags.has_hint() { 400 | Some(input.decode_str_prefixed::()?) 401 | } else { 402 | None 403 | }; 404 | let attributes = input.decode_vec_prefixed::()?; 405 | let defaults = input.decode_vec_prefixed::()?; 406 | let result = Field { 407 | visibility, 408 | type_, 409 | flags, 410 | hint, 411 | attributes, 412 | defaults, 413 | }; 414 | 415 | Ok(result) 416 | } 417 | } 418 | 419 | impl Encode for Field { 420 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 421 | let flags = value.flags.with_has_hint(value.hint.is_some()); 422 | 423 | output.encode(&value.visibility)?; 424 | output.encode(&value.type_)?; 425 | output.encode(&flags)?; 426 | if let Some(ref hint) = value.hint { 427 | output.encode_str_prefixed::(hint)?; 428 | } 429 | output.encode_slice_prefixed::(&value.attributes)?; 430 | output.encode_slice_prefixed::(&value.defaults) 431 | } 432 | } 433 | 434 | #[derive(Debug, Clone, Copy)] 435 | pub enum Type { 436 | Prim, 437 | Class, 438 | Ref(PoolIndex), 439 | WeakRef(PoolIndex), 440 | Array(PoolIndex), 441 | StaticArray(PoolIndex, u32), 442 | ScriptRef(PoolIndex), 443 | } 444 | 445 | impl Decode for Type { 446 | fn decode(input: &mut I) -> io::Result { 447 | let tag: u8 = input.decode()?; 448 | match tag { 449 | 0 => Ok(Type::Prim), 450 | 1 => Ok(Type::Class), 451 | 2 => Ok(Type::Ref(input.decode()?)), 452 | 3 => Ok(Type::WeakRef(input.decode()?)), 453 | 4 => Ok(Type::Array(input.decode()?)), 454 | 5 => Ok(Type::StaticArray(input.decode()?, input.decode()?)), 455 | 6 => Ok(Type::ScriptRef(input.decode()?)), 456 | _ => panic!("Unknown Type enum value {}", tag), 457 | } 458 | } 459 | } 460 | 461 | impl Encode for Type { 462 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 463 | match value { 464 | Type::Prim => output.encode(&0u8), 465 | Type::Class => output.encode(&1u8), 466 | Type::Ref(inner) => { 467 | output.encode(&2u8)?; 468 | output.encode(inner) 469 | } 470 | Type::WeakRef(inner) => { 471 | output.encode(&3u8)?; 472 | output.encode(inner) 473 | } 474 | Type::Array(inner) => { 475 | output.encode(&4u8)?; 476 | output.encode(inner) 477 | } 478 | Type::StaticArray(inner, size) => { 479 | output.encode(&5u8)?; 480 | output.encode(inner)?; 481 | output.encode(size) 482 | } 483 | Type::ScriptRef(inner) => { 484 | output.encode(&6u8)?; 485 | output.encode(inner) 486 | } 487 | } 488 | } 489 | } 490 | 491 | #[derive(Debug, Clone)] 492 | pub struct Local { 493 | pub type_: PoolIndex, 494 | pub flags: LocalFlags, 495 | } 496 | 497 | impl Local { 498 | pub fn new(type_: PoolIndex, flags: LocalFlags) -> Local { 499 | Local { type_, flags } 500 | } 501 | } 502 | 503 | impl Decode for Local { 504 | fn decode(input: &mut I) -> io::Result { 505 | let type_ = input.decode()?; 506 | let flags = input.decode()?; 507 | Ok(Local { type_, flags }) 508 | } 509 | } 510 | 511 | impl Encode for Local { 512 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 513 | output.encode(&value.type_)?; 514 | output.encode(&value.flags) 515 | } 516 | } 517 | 518 | #[derive(Debug)] 519 | pub struct Parameter { 520 | pub type_: PoolIndex, 521 | pub flags: ParameterFlags, 522 | } 523 | 524 | impl Decode for Parameter { 525 | fn decode(input: &mut I) -> io::Result { 526 | let type_ = input.decode()?; 527 | let flags = input.decode()?; 528 | Ok(Parameter { type_, flags }) 529 | } 530 | } 531 | 532 | impl Encode for Parameter { 533 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 534 | output.encode(&value.type_)?; 535 | output.encode(&value.flags) 536 | } 537 | } 538 | 539 | #[derive(Debug)] 540 | pub struct SourceFile { 541 | pub id: u32, 542 | pub path_hash: u64, 543 | pub path: PathBuf, 544 | } 545 | 546 | impl Decode for SourceFile { 547 | fn decode(input: &mut I) -> io::Result { 548 | let id = input.decode()?; 549 | let path_hash = input.decode()?; 550 | let raw_path = input.decode_str_prefixed::()?; 551 | let path = PathBuf::from(raw_path.replace("\\", "/")); 552 | let result = SourceFile { id, path_hash, path }; 553 | Ok(result) 554 | } 555 | } 556 | 557 | impl Encode for SourceFile { 558 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 559 | output.encode(&value.id)?; 560 | output.encode(&value.path_hash)?; 561 | output.encode_str_prefixed::(&value.path.to_str().unwrap().replace("/", "\\")) 562 | } 563 | } 564 | 565 | #[bitfield(bits = 16)] 566 | #[derive(Debug, Clone, Copy)] 567 | pub struct FieldFlags { 568 | pub is_native: bool, 569 | pub is_edit: bool, 570 | pub is_inline: bool, 571 | pub is_const: bool, 572 | pub is_rep: bool, 573 | pub has_hint: bool, 574 | pub is_inst_edit: bool, 575 | pub has_default: bool, 576 | pub is_persistent: bool, 577 | pub bit9: bool, 578 | pub bit10: bool, 579 | #[skip] 580 | pub remainder: B5, 581 | } 582 | 583 | impl Decode for FieldFlags { 584 | fn decode(input: &mut I) -> io::Result { 585 | Ok(FieldFlags::from_bytes(input.decode()?)) 586 | } 587 | } 588 | 589 | impl Encode for FieldFlags { 590 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 591 | output.write_all(&FieldFlags::into_bytes(*value)) 592 | } 593 | } 594 | 595 | #[bitfield(bits = 8)] 596 | #[derive(Debug, Clone, Copy)] 597 | pub struct LocalFlags { 598 | pub is_const: bool, 599 | #[skip] 600 | pub remainder: B7, 601 | } 602 | 603 | impl Decode for LocalFlags { 604 | fn decode(input: &mut I) -> io::Result { 605 | Ok(LocalFlags::from_bytes(input.decode()?)) 606 | } 607 | } 608 | 609 | impl Encode for LocalFlags { 610 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 611 | output.write_all(&LocalFlags::into_bytes(*value)) 612 | } 613 | } 614 | 615 | #[bitfield(bits = 8)] 616 | #[derive(Debug, Clone, Copy)] 617 | pub struct ParameterFlags { 618 | pub is_optional: bool, 619 | pub is_out: bool, 620 | pub is_short_circuit: bool, 621 | pub bit3: bool, 622 | #[skip] 623 | pub remainder: B4, 624 | } 625 | 626 | impl Decode for ParameterFlags { 627 | fn decode(input: &mut I) -> io::Result { 628 | Ok(ParameterFlags::from_bytes(input.decode()?)) 629 | } 630 | } 631 | 632 | impl Encode for ParameterFlags { 633 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 634 | output.write_all(&ParameterFlags::into_bytes(*value)) 635 | } 636 | } 637 | 638 | #[bitfield(bits = 16)] 639 | #[derive(Debug, Clone, Copy)] 640 | pub struct ClassFlags { 641 | pub bit0: bool, 642 | pub is_abstract: bool, 643 | pub is_final: bool, 644 | pub is_struct: bool, 645 | pub has_functions: bool, 646 | pub has_fields: bool, 647 | pub is_native: bool, 648 | pub bit7: bool, 649 | pub has_overrides: bool, 650 | #[skip] 651 | pub remainder: B7, 652 | } 653 | 654 | impl Decode for ClassFlags { 655 | fn decode(input: &mut I) -> io::Result { 656 | Ok(ClassFlags::from_bytes(input.decode()?)) 657 | } 658 | } 659 | 660 | impl Encode for ClassFlags { 661 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 662 | output.write_all(&ClassFlags::into_bytes(*value)) 663 | } 664 | } 665 | 666 | #[bitfield(bits = 32)] 667 | #[derive(Debug, Clone, Copy)] 668 | pub struct FunctionFlags { 669 | pub is_static: bool, 670 | pub is_exec: bool, 671 | pub is_timer: bool, 672 | pub is_final: bool, 673 | pub is_native: bool, 674 | pub is_callback: bool, 675 | pub is_operator_overload: bool, 676 | pub has_return_value: bool, 677 | pub has_base_method: bool, 678 | pub has_parameters: bool, 679 | pub has_locals: bool, 680 | pub has_body: bool, 681 | pub is_cast: bool, 682 | pub is_safe: bool, 683 | #[skip] 684 | pub padding: B4, 685 | pub is_const: bool, 686 | pub bit19: bool, 687 | pub bit20: bool, 688 | pub bit21: bool, 689 | #[skip] 690 | pub remainder: B10, 691 | } 692 | 693 | impl Decode for FunctionFlags { 694 | fn decode(input: &mut I) -> io::Result { 695 | Ok(FunctionFlags::from_bytes(input.decode()?)) 696 | } 697 | } 698 | 699 | impl Encode for FunctionFlags { 700 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 701 | output.write_all(&FunctionFlags::into_bytes(*value)) 702 | } 703 | } 704 | 705 | #[derive(BitfieldSpecifier)] 706 | #[bits = 8] 707 | #[derive(Debug, Clone, Copy)] 708 | pub enum Visibility { 709 | Public = 0, 710 | Protected = 1, 711 | Private = 2, 712 | } 713 | 714 | impl Display for Visibility { 715 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 716 | let res = match self { 717 | Visibility::Public => "public", 718 | Visibility::Protected => "protected", 719 | Visibility::Private => "private", 720 | }; 721 | f.write_str(res) 722 | } 723 | } 724 | 725 | impl Decode for Visibility { 726 | fn decode(input: &mut I) -> io::Result { 727 | Ok(Visibility::from_bytes(input.decode()?).expect("Invalid Visibility enum value")) 728 | } 729 | } 730 | 731 | impl Encode for Visibility { 732 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 733 | output.encode(&Visibility::into_bytes(*value).unwrap()) 734 | } 735 | } 736 | 737 | #[derive(Debug)] 738 | pub struct SourceReference { 739 | pub file: PoolIndex, 740 | pub line: u32, 741 | } 742 | 743 | impl Decode for SourceReference { 744 | fn decode(input: &mut I) -> io::Result { 745 | let file = input.decode()?; 746 | let line = input.decode()?; 747 | let result = SourceReference { file, line }; 748 | Ok(result) 749 | } 750 | } 751 | 752 | impl Encode for SourceReference { 753 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 754 | output.encode(&value.file)?; 755 | output.encode(&value.line) 756 | } 757 | } 758 | 759 | #[derive(Debug)] 760 | pub struct Property { 761 | pub name: String, 762 | pub value: String, 763 | } 764 | 765 | impl Decode for Property { 766 | fn decode(input: &mut I) -> io::Result { 767 | let name = input.decode_str_prefixed::()?; 768 | let value = input.decode_str_prefixed::()?; 769 | Ok(Property { name, value }) 770 | } 771 | } 772 | 773 | impl Encode for Property { 774 | fn encode(output: &mut O, value: &Self) -> io::Result<()> { 775 | output.encode_str_prefixed::(&value.name)?; 776 | output.encode_str_prefixed::(&value.value) 777 | } 778 | } 779 | -------------------------------------------------------------------------------- /compiler/src/scope.rs: -------------------------------------------------------------------------------- 1 | use std::iter; 2 | use std::ops::Deref; 3 | use std::str::FromStr; 4 | 5 | use redscript::ast::{Constant, Expr, Ident, LiteralType, Pos, TypeName}; 6 | use redscript::bundle::{ConstantPool, PoolIndex}; 7 | use redscript::definition::{Class, Definition, DefinitionValue, Enum, Field, Function, Local, Type}; 8 | use redscript::error::Error; 9 | 10 | use crate::assembler::IntrinsicOp; 11 | use crate::parser::FunctionSource; 12 | use crate::{Reference, TypeId}; 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct FunctionOverloads(Vec>); 16 | 17 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 18 | pub struct FunctionName { 19 | namespace: Option>, 20 | name: Ident, 21 | } 22 | 23 | impl FunctionName { 24 | pub fn global(name: Ident) -> FunctionName { 25 | FunctionName { namespace: None, name } 26 | } 27 | 28 | pub fn instance(class: PoolIndex, name: Ident) -> FunctionName { 29 | FunctionName { 30 | namespace: Some(class), 31 | name, 32 | } 33 | } 34 | 35 | pub fn pretty(&self, pool: &ConstantPool) -> String { 36 | self.namespace 37 | .and_then(|c| pool.definition_name(c).ok()) 38 | .map(|n| format!("{}::{}", n, self.name)) 39 | .unwrap_or_else(|| self.name.to_string()) 40 | } 41 | } 42 | 43 | #[derive(Debug)] 44 | pub struct FunctionMatch { 45 | pub index: PoolIndex, 46 | pub conversions: Vec, 47 | pub unspecified_args: usize, 48 | } 49 | 50 | #[derive(Debug, Clone)] 51 | pub struct Scope { 52 | pub functions: im::HashMap, 53 | pub references: im::HashMap, 54 | pub types: im::HashMap>, 55 | pub this: Option>, 56 | pub function: Option>, 57 | } 58 | 59 | impl Scope { 60 | pub fn new(pool: &ConstantPool) -> Result { 61 | let names = pool 62 | .roots() 63 | .filter_map(|(idx, def)| { 64 | let ident = Ident(pool.definition_name(idx).ok()?); 65 | match def.value { 66 | DefinitionValue::Class(_) => Some((ident, Reference::Class(idx.cast()))), 67 | DefinitionValue::Enum(_) => Some((ident, Reference::Enum(idx.cast()))), 68 | _ => None, 69 | } 70 | }) 71 | .collect(); 72 | 73 | let types = pool 74 | .roots() 75 | .filter_map(|(idx, def)| match def.value { 76 | DefinitionValue::Type(_) => { 77 | let ident = Ident(pool.definition_name(idx).ok()?); 78 | Some((ident, idx.cast())) 79 | } 80 | _ => None, 81 | }) 82 | .collect(); 83 | 84 | let mut result = Scope { 85 | functions: im::HashMap::new(), 86 | references: names, 87 | types, 88 | this: None, 89 | function: None, 90 | }; 91 | 92 | for (idx, def) in pool.definitions() { 93 | if let DefinitionValue::Function(_) = def.value { 94 | let mangled_name = pool.definition_name(idx)?; 95 | let ident = Ident::new(mangled_name.split(';').next().unwrap().to_owned()); 96 | let name = if def.parent != PoolIndex::UNDEFINED { 97 | FunctionName::instance(def.parent.cast(), ident) 98 | } else { 99 | FunctionName::global(ident) 100 | }; 101 | result.push_function(name, idx.cast()) 102 | } 103 | } 104 | 105 | Ok(result) 106 | } 107 | 108 | pub fn with_context(&self, this: Option>, function: PoolIndex) -> Self { 109 | Scope { 110 | this, 111 | function: Some(function), 112 | ..self.clone() 113 | } 114 | } 115 | 116 | pub fn push_local(&mut self, name: Ident, local: PoolIndex) { 117 | self.references.insert(name, Reference::Local(local)); 118 | } 119 | 120 | pub fn push_function(&mut self, name: FunctionName, index: PoolIndex) { 121 | self.functions 122 | .entry(name) 123 | .and_modify(|overloads: &mut FunctionOverloads| overloads.0.push(index)) 124 | .or_insert_with(|| FunctionOverloads(vec![index])); 125 | } 126 | 127 | pub fn resolve_field( 128 | &self, 129 | ident: Ident, 130 | class_idx: PoolIndex, 131 | pool: &ConstantPool, 132 | pos: Pos, 133 | ) -> Result, Error> { 134 | let class = pool.class(class_idx)?; 135 | for field in &class.fields { 136 | if pool.definition_name(*field)? == ident.0 { 137 | return Ok(*field); 138 | } 139 | } 140 | let err = format!("Field {} not found on {}", ident, pool.definition_name(class_idx)?); 141 | if class.base != PoolIndex::UNDEFINED { 142 | self.resolve_field(ident, class.base, pool, pos) 143 | .map_err(|_| Error::CompileError(err, pos)) 144 | } else { 145 | Err(Error::CompileError(err, pos)) 146 | } 147 | } 148 | 149 | pub fn resolve_enum_member( 150 | &self, 151 | ident: Ident, 152 | enum_idx: PoolIndex, 153 | pool: &ConstantPool, 154 | pos: Pos, 155 | ) -> Result, Error> { 156 | let enum_ = pool.enum_(enum_idx)?; 157 | for field in &enum_.members { 158 | if pool.definition_name(*field)? == ident.0 { 159 | return Ok(*field); 160 | } 161 | } 162 | let err = format!("Member {} not found on {}", ident, pool.definition_name(enum_idx)?); 163 | Err(Error::CompileError(err, pos)) 164 | } 165 | 166 | pub fn resolve_function<'a>( 167 | &self, 168 | name: FunctionName, 169 | args: impl Iterator + Clone, 170 | expected: Option<&TypeId>, 171 | pool: &ConstantPool, 172 | pos: Pos, 173 | ) -> Result { 174 | let overloads = self 175 | .functions 176 | .get(&name) 177 | .ok_or_else(|| Error::CompileError(format!("Function {} not found", name.pretty(pool)), pos))?; 178 | let mut errors = Vec::new(); 179 | 180 | for fun_idx in overloads.0.iter() { 181 | match self.resolve_function_overload(*fun_idx, args.clone(), expected, pool, pos) { 182 | Ok(res) => return Ok(res), 183 | Err(Error::FunctionResolutionError(msg, _)) => errors.push(msg), 184 | Err(other) => return Err(other), 185 | } 186 | } 187 | let message = format!( 188 | "Arguments passed to {} do not match any of the overloads:\n{}", 189 | name.pretty(pool), 190 | errors.join("\n") 191 | ); 192 | Err(Error::FunctionResolutionError(message, pos)) 193 | } 194 | 195 | fn resolve_function_overload<'a>( 196 | &self, 197 | fun_idx: PoolIndex, 198 | args: impl Iterator, 199 | expected: Option<&TypeId>, 200 | pool: &ConstantPool, 201 | pos: Pos, 202 | ) -> Result { 203 | let fun = pool.function(fun_idx)?; 204 | 205 | if let Some(expected) = expected { 206 | let return_type_idx = fun 207 | .return_type 208 | .ok_or_else(|| Error::CompileError("Void value cannot be used".to_owned(), pos))?; 209 | let return_type = self.resolve_type_from_pool(return_type_idx, pool, pos)?; 210 | if self.find_conversion(&return_type, &expected, pool)?.is_none() { 211 | let message = format!( 212 | "Return type {} does not match expected {}", 213 | return_type.pretty(pool)?, 214 | expected.pretty(pool)? 215 | ); 216 | return Err(Error::FunctionResolutionError(message, pos)); 217 | } 218 | } 219 | 220 | let mut conversions = Vec::new(); 221 | for (idx, arg) in args.enumerate() { 222 | let param_idx = fun.parameters.get(idx).ok_or_else(|| { 223 | Error::FunctionResolutionError(format!("Too many arguments, expected {}", fun.parameters.len()), pos) 224 | })?; 225 | let param = pool.parameter(*param_idx)?; 226 | let param_type = self.resolve_type_from_pool(param.type_, pool, pos)?; 227 | let arg_type = self.infer_type(arg, Some(¶m_type), pool)?; 228 | if let Some(conv) = self.find_conversion(&arg_type, ¶m_type, pool)? { 229 | conversions.push(conv); 230 | } else { 231 | let expected = param_type.pretty(pool)?; 232 | let given = arg_type.pretty(pool)?; 233 | let message = format!( 234 | "Parameter at position {} expects type {} while provided type is {}", 235 | idx, expected, given 236 | ); 237 | return Err(Error::FunctionResolutionError(message, pos)); 238 | } 239 | } 240 | 241 | let opt_param_count = fun 242 | .parameters 243 | .iter() 244 | .filter_map(|idx| pool.parameter(*idx).ok()) 245 | .filter(|param| param.flags.is_optional()) 246 | .count(); 247 | 248 | let min_params = fun.parameters.len() - opt_param_count; 249 | if conversions.len() >= min_params { 250 | let unspecified_args = fun.parameters.len() - conversions.len(); 251 | let match_ = FunctionMatch { 252 | index: fun_idx, 253 | conversions, 254 | unspecified_args, 255 | }; 256 | Ok(match_) 257 | } else { 258 | let message = format!( 259 | "Expected {}-{} parameters, given {}", 260 | min_params, 261 | fun.parameters.len(), 262 | conversions.len() 263 | ); 264 | Err(Error::FunctionResolutionError(message, pos)) 265 | } 266 | } 267 | 268 | pub fn resolve_method( 269 | &self, 270 | name: Ident, 271 | class_idx: PoolIndex, 272 | args: &[Expr], 273 | expected: Option<&TypeId>, 274 | pool: &ConstantPool, 275 | pos: Pos, 276 | ) -> Result { 277 | let fun_name = FunctionName::instance(class_idx, name.clone()); 278 | match self.resolve_function(fun_name, args.iter(), expected, pool, pos) { 279 | Ok(res) => Ok(res), 280 | Err(err) => { 281 | let class = pool.class(class_idx)?; 282 | if class.base != PoolIndex::UNDEFINED { 283 | self.resolve_method(name, class.base, args, expected, pool, pos) 284 | .map_err(|base_err| match base_err { 285 | err @ Error::FunctionResolutionError(_, _) => err, 286 | _ => err, 287 | }) 288 | } else { 289 | Err(err) 290 | } 291 | } 292 | } 293 | } 294 | 295 | pub fn resolve_reference(&self, name: Ident, pos: Pos) -> Result { 296 | self.references 297 | .get(&name) 298 | .cloned() 299 | .ok_or_else(|| Error::CompileError(format!("Unresolved reference {}", name), pos)) 300 | } 301 | 302 | pub fn get_type_index(&mut self, type_: &TypeId, pool: &mut ConstantPool) -> Result, Error> { 303 | let name = type_.repr(pool)?; 304 | if let Some(t) = self.types.get(&name) { 305 | Ok(*t) 306 | } else { 307 | let name_idx = pool.names.add(name.0.deref().clone()); 308 | let value = match type_ { 309 | TypeId::Prim(_) => Type::Prim, 310 | TypeId::Class(_) | TypeId::Struct(_) | TypeId::Enum(_) => Type::Class, 311 | TypeId::Ref(inner) => Type::Ref(self.get_type_index(inner, pool)?), 312 | TypeId::WeakRef(inner) => Type::WeakRef(self.get_type_index(inner, pool)?), 313 | TypeId::Array(inner) => Type::Array(self.get_type_index(inner, pool)?), 314 | TypeId::StaticArray(inner, size) => Type::StaticArray(self.get_type_index(inner, pool)?, *size), 315 | TypeId::ScriptRef(inner) => Type::ScriptRef(self.get_type_index(inner, pool)?), 316 | TypeId::Null | TypeId::Void => panic!(), 317 | }; 318 | let idx = pool.push_definition(Definition::type_(name_idx, value)).cast(); 319 | self.types.insert(name, idx); 320 | Ok(idx) 321 | } 322 | } 323 | 324 | pub fn resolve_type>( 325 | &self, 326 | name: &TypeName, 327 | pool: &ConstantPool, 328 | pos: Pos, 329 | ) -> Result { 330 | let result = if let Some(res) = self.types.get(&Ident::new(name.repr())) { 331 | self.resolve_type_from_pool(*res, pool, pos)? 332 | } else { 333 | match (name.name.as_ref(), name.arguments.as_slice()) { 334 | ("ref", [nested]) => TypeId::Ref(Box::new(self.resolve_type(nested, pool, pos)?)), 335 | ("wref", [nested]) => TypeId::WeakRef(Box::new(self.resolve_type(nested, pool, pos)?)), 336 | ("script_ref", [nested]) => TypeId::ScriptRef(Box::new(self.resolve_type(nested, pool, pos)?)), 337 | ("array", [nested]) => TypeId::Array(Box::new(self.resolve_type(nested, pool, pos)?)), 338 | _ => return Err(Error::CompileError(format!("Unresolved type {}", name), pos)), 339 | } 340 | }; 341 | Ok(result) 342 | } 343 | 344 | pub fn resolve_type_from_pool( 345 | &self, 346 | index: PoolIndex, 347 | pool: &ConstantPool, 348 | pos: Pos, 349 | ) -> Result { 350 | let result = match pool.type_(index)? { 351 | Type::Prim => TypeId::Prim(index), 352 | Type::Class => { 353 | let ident = Ident(pool.definition_name(index)?); 354 | if let Some(Reference::Class(class_idx)) = self.references.get(&ident) { 355 | if pool.class(*class_idx)?.flags.is_struct() { 356 | TypeId::Struct(*class_idx) 357 | } else { 358 | TypeId::Class(*class_idx) 359 | } 360 | } else if let Some(Reference::Enum(enum_idx)) = self.references.get(&ident) { 361 | TypeId::Enum(*enum_idx) 362 | } else { 363 | return Err(Error::CompileError(format!("Class {} not found", ident), pos)); 364 | } 365 | } 366 | Type::Ref(type_) => { 367 | let inner = self.resolve_type_from_pool(*type_, pool, pos)?; 368 | TypeId::Ref(Box::new(inner)) 369 | } 370 | Type::WeakRef(type_) => { 371 | let inner = self.resolve_type_from_pool(*type_, pool, pos)?; 372 | TypeId::WeakRef(Box::new(inner)) 373 | } 374 | Type::Array(type_) => { 375 | let inner = self.resolve_type_from_pool(*type_, pool, pos)?; 376 | TypeId::Array(Box::new(inner)) 377 | } 378 | Type::StaticArray(type_, size) => { 379 | let inner = self.resolve_type_from_pool(*type_, pool, pos)?; 380 | TypeId::StaticArray(Box::new(inner), *size) 381 | } 382 | Type::ScriptRef(type_) => { 383 | let inner = self.resolve_type_from_pool(*type_, pool, pos)?; 384 | TypeId::ScriptRef(Box::new(inner)) 385 | } 386 | }; 387 | Ok(result) 388 | } 389 | 390 | pub fn convert_type(&self, from: &TypeId, to: &TypeId, pool: &ConstantPool, pos: Pos) -> Result { 391 | self.find_conversion(&from, &to, pool)?.ok_or(Error::CompileError( 392 | format!("Can't coerce {} to {}", from.pretty(pool)?, to.pretty(pool)?), 393 | pos, 394 | )) 395 | } 396 | 397 | fn find_conversion(&self, from: &TypeId, to: &TypeId, pool: &ConstantPool) -> Result, Error> { 398 | let result = if from == to { 399 | Some(Conversion::Identity) 400 | } else { 401 | match (from, to) { 402 | (TypeId::Null, TypeId::Ref(_)) => Some(Conversion::Identity), 403 | (TypeId::Null, TypeId::WeakRef(_)) => Some(Conversion::RefToWeakRef), 404 | (TypeId::Class(from), TypeId::Class(_)) => { 405 | let class = pool.class(*from)?; 406 | if class.base != PoolIndex::UNDEFINED { 407 | self.find_conversion(&TypeId::Class(class.base), to, pool)? 408 | } else { 409 | None 410 | } 411 | } 412 | (from @ TypeId::Class(_), TypeId::Ref(to)) => self 413 | .find_conversion(from, to, pool)? 414 | .filter(|conv| *conv == Conversion::Identity), 415 | (TypeId::Ref(from), TypeId::Ref(to)) => self 416 | .find_conversion(from, to, pool)? 417 | .filter(|conv| *conv == Conversion::Identity), 418 | (TypeId::WeakRef(from), TypeId::WeakRef(to)) => self 419 | .find_conversion(from, to, pool)? 420 | .filter(|conv| *conv == Conversion::Identity), 421 | (TypeId::WeakRef(from), TypeId::Ref(to)) 422 | if self.find_conversion(from, to, pool)? == Some(Conversion::Identity) => 423 | { 424 | Some(Conversion::WeakRefToRef) 425 | } 426 | (TypeId::Ref(from), TypeId::WeakRef(to)) 427 | if self.find_conversion(from, to, pool)? == Some(Conversion::Identity) => 428 | { 429 | Some(Conversion::RefToWeakRef) 430 | } 431 | _ => None, 432 | } 433 | }; 434 | Ok(result) 435 | } 436 | 437 | pub fn infer_type(&self, expr: &Expr, expected: Option<&TypeId>, pool: &ConstantPool) -> Result { 438 | let res = match expr { 439 | Expr::Ident(name, pos) => match self.resolve_reference(name.clone(), *pos)? { 440 | Reference::Local(idx) => self.resolve_type_from_pool(pool.local(idx)?.type_, pool, *pos)?, 441 | Reference::Parameter(idx) => self.resolve_type_from_pool(pool.parameter(idx)?.type_, pool, *pos)?, 442 | Reference::Field(idx) => self.resolve_type_from_pool(pool.field(idx)?.type_, pool, *pos)?, 443 | Reference::Class(idx) => { 444 | let name = pool.definition_name(idx)?; 445 | self.resolve_type(&TypeName::basic(name.deref().clone()), pool, *pos)? 446 | } 447 | Reference::Enum(idx) => { 448 | let name = pool.definition_name(idx)?; 449 | self.resolve_type(&TypeName::basic(name.deref().clone()), pool, *pos)? 450 | } 451 | }, 452 | Expr::Cast(type_name, expr, pos) => { 453 | let type_ = self.resolve_type(type_name, pool, *pos)?; 454 | match self.infer_type(&expr, None, pool)? { 455 | TypeId::Ref(_) => TypeId::Ref(Box::new(type_)), 456 | TypeId::WeakRef(_) => TypeId::WeakRef(Box::new(type_)), 457 | TypeId::ScriptRef(_) => TypeId::ScriptRef(Box::new(type_)), 458 | _ => type_, 459 | } 460 | } 461 | Expr::Call(ident, args, pos) => { 462 | if let Ok(intrinsic) = IntrinsicOp::from_str(&ident.0) { 463 | self.infer_intrinsic_type(intrinsic, args, expected, pool, *pos)? 464 | } else { 465 | let name = FunctionName::global(ident.clone()); 466 | let match_ = self.resolve_function(name, args.iter(), expected, pool, *pos)?; 467 | match pool.function(match_.index)?.return_type { 468 | Some(type_) => self.resolve_type_from_pool(type_, pool, *pos)?, 469 | None => TypeId::Void, 470 | } 471 | } 472 | } 473 | Expr::MethodCall(expr, ident, args, pos) => { 474 | let class = match self.infer_type(expr, None, pool)?.unwrapped() { 475 | TypeId::Class(class) => *class, 476 | TypeId::Struct(class) => *class, 477 | _ => return Err(Error::CompileError(format!("{:?} doesn't have methods", expr), *pos)), 478 | }; 479 | let match_ = self.resolve_method(ident.clone(), class, args, expected, pool, *pos)?; 480 | match pool.function(match_.index)?.return_type { 481 | None => TypeId::Void, 482 | Some(return_type) => self.resolve_type_from_pool(return_type, pool, *pos)?, 483 | } 484 | } 485 | Expr::ArrayElem(expr, _, pos) => match self.infer_type(expr, None, pool)? { 486 | TypeId::Array(inner) => *inner, 487 | TypeId::StaticArray(inner, _) => *inner, 488 | type_ => { 489 | return Err(Error::CompileError( 490 | format!("{} can't be indexed", type_.pretty(pool)?), 491 | *pos, 492 | )) 493 | } 494 | }, 495 | Expr::New(name, _, pos) => { 496 | if let Reference::Class(class_idx) = self.resolve_reference(name.clone(), *pos)? { 497 | let type_ = TypeId::Class(class_idx); 498 | if pool.class(class_idx)?.flags.is_struct() { 499 | type_ 500 | } else { 501 | TypeId::Ref(Box::new(type_)) 502 | } 503 | } else { 504 | return Err(Error::CompileError(format!("{} can't be constructed", name), *pos)); 505 | } 506 | } 507 | Expr::Member(expr, ident, pos) => { 508 | let class = match self.infer_type(expr, None, pool)?.unwrapped() { 509 | TypeId::Class(class) => *class, 510 | TypeId::Struct(class) => *class, 511 | t @ TypeId::Enum(_) => return Ok(t.clone()), 512 | t => { 513 | let err = format!("Can't access a member of {}", t.pretty(pool)?); 514 | return Err(Error::CompileError(err, *pos)); 515 | } 516 | }; 517 | let field = self.resolve_field(ident.clone(), class, pool, *pos)?; 518 | self.resolve_type_from_pool(pool.field(field)?.type_, pool, *pos)? 519 | } 520 | Expr::Conditional(_, lhs, rhs, pos) => { 521 | let lt = self.infer_type(lhs, expected, pool)?; 522 | let rt = self.infer_type(rhs, expected, pool)?; 523 | if lt != rt { 524 | let error = format!("Incompatible types: {} and {}", lt.pretty(pool)?, rt.pretty(pool)?); 525 | return Err(Error::CompileError(error, *pos)); 526 | } 527 | lt 528 | } 529 | Expr::BinOp(lhs, rhs, op, pos) => { 530 | let ident = Ident::new(op.name()); 531 | let args = iter::once(lhs.as_ref()).chain(iter::once(rhs.as_ref())); 532 | let match_ = self.resolve_function(FunctionName::global(ident), args.clone(), expected, pool, *pos)?; 533 | match pool.function(match_.index)?.return_type { 534 | Some(type_) => self.resolve_type_from_pool(type_, pool, *pos)?, 535 | None => TypeId::Void, 536 | } 537 | } 538 | Expr::UnOp(expr, op, pos) => { 539 | let ident = Ident::new(op.name()); 540 | let args = iter::once(expr.as_ref()); 541 | let match_ = self.resolve_function(FunctionName::global(ident), args.clone(), expected, pool, *pos)?; 542 | match pool.function(match_.index)?.return_type { 543 | Some(type_) => self.resolve_type_from_pool(type_, pool, *pos)?, 544 | None => TypeId::Void, 545 | } 546 | } 547 | Expr::Constant(cons, pos) => match cons { 548 | Constant::String(LiteralType::String, _) => self.resolve_type(&TypeName::STRING, pool, *pos)?, 549 | Constant::String(LiteralType::Name, _) => self.resolve_type(&TypeName::CNAME, pool, *pos)?, 550 | Constant::String(LiteralType::Resource, _) => self.resolve_type(&TypeName::RESOURCE, pool, *pos)?, 551 | Constant::String(LiteralType::TweakDbId, _) => self.resolve_type(&TypeName::TWEAKDB_ID, pool, *pos)?, 552 | Constant::Float(_) => self.resolve_type(&TypeName::FLOAT, pool, *pos)?, 553 | Constant::Int(_) => self.resolve_type(&TypeName::INT32, pool, *pos)?, 554 | Constant::Uint(_) => self.resolve_type(&TypeName::UINT32, pool, *pos)?, 555 | Constant::Bool(_) => self.resolve_type(&TypeName::BOOL, pool, *pos)?, 556 | }, 557 | Expr::Null => TypeId::Null, 558 | Expr::This(pos) => match self.this { 559 | Some(class_idx) => TypeId::Class(class_idx), 560 | None => return Err(Error::CompileError("No 'this' in static context".to_owned(), *pos)), 561 | }, 562 | Expr::Super(pos) => match self.this { 563 | Some(class_idx) => { 564 | let base_idx = pool.class(class_idx)?.base; 565 | TypeId::Class(base_idx) 566 | } 567 | None => return Err(Error::CompileError("No 'super' in static context".to_owned(), *pos)), 568 | }, 569 | Expr::While(_, _, _) => TypeId::Void, 570 | Expr::Goto(_, _) => TypeId::Void, 571 | Expr::If(_, _, _, _) => TypeId::Void, 572 | Expr::Break => TypeId::Void, 573 | Expr::Return(_, _) => TypeId::Void, 574 | Expr::Seq(_) => TypeId::Void, 575 | Expr::Switch(_, _, _) => TypeId::Void, 576 | Expr::Declare(_, _, _, _) => TypeId::Void, 577 | Expr::Assign(_, _, _) => TypeId::Void, 578 | }; 579 | Ok(res) 580 | } 581 | 582 | fn infer_intrinsic_type( 583 | &self, 584 | intrinsic: IntrinsicOp, 585 | args: &[Expr], 586 | expected: Option<&TypeId>, 587 | pool: &ConstantPool, 588 | pos: Pos, 589 | ) -> Result { 590 | if args.len() != intrinsic.arg_count().into() { 591 | let err = format!("Invalid number of arguments for {}", intrinsic); 592 | return Err(Error::CompileError(err, pos)); 593 | } 594 | let type_ = self.infer_type(&args[0], None, pool)?; 595 | let result = match (intrinsic, type_) { 596 | (IntrinsicOp::Equals, _) => self.resolve_type(&TypeName::BOOL, pool, pos)?, 597 | (IntrinsicOp::NotEquals, _) => self.resolve_type(&TypeName::BOOL, pool, pos)?, 598 | (IntrinsicOp::ArrayClear, _) => TypeId::Void, 599 | (IntrinsicOp::ArraySize, _) => self.resolve_type(&TypeName::INT32, pool, pos)?, 600 | (IntrinsicOp::ArrayResize, _) => TypeId::Void, 601 | (IntrinsicOp::ArrayFindFirst, TypeId::Array(member)) => *member, 602 | (IntrinsicOp::ArrayFindLast, TypeId::Array(member)) => *member, 603 | (IntrinsicOp::ArrayContains, _) => self.resolve_type(&TypeName::BOOL, pool, pos)?, 604 | (IntrinsicOp::ArrayPush, _) => TypeId::Void, 605 | (IntrinsicOp::ArrayPop, TypeId::Array(member)) => *member, 606 | (IntrinsicOp::ArrayInsert, _) => TypeId::Void, 607 | (IntrinsicOp::ArrayRemove, _) => self.resolve_type(&TypeName::BOOL, pool, pos)?, 608 | (IntrinsicOp::ArrayGrow, _) => TypeId::Void, 609 | (IntrinsicOp::ArrayErase, _) => self.resolve_type(&TypeName::BOOL, pool, pos)?, 610 | (IntrinsicOp::ArrayLast, TypeId::Array(member)) => *member, 611 | (IntrinsicOp::ToString, _) => self.resolve_type(&TypeName::STRING, pool, pos)?, 612 | (IntrinsicOp::EnumInt, _) => self.resolve_type(&TypeName::INT32, pool, pos)?, 613 | (IntrinsicOp::IntEnum, _) if expected.is_some() => expected.unwrap().clone(), 614 | (IntrinsicOp::ToVariant, _) => self.resolve_type(&TypeName::VARIANT, pool, pos)?, 615 | (IntrinsicOp::FromVariant, _) if expected.is_some() => expected.unwrap().clone(), 616 | (IntrinsicOp::AsRef, type_) => TypeId::ScriptRef(Box::new(type_)), 617 | (IntrinsicOp::Deref, TypeId::ScriptRef(inner)) => *inner, 618 | _ => { 619 | let err = format!("Invalid intrinsic {} call", intrinsic); 620 | return Err(Error::CompileError(err, pos)); 621 | } 622 | }; 623 | Ok(result) 624 | } 625 | } 626 | 627 | #[derive(Debug, PartialEq, Eq)] 628 | pub enum Conversion { 629 | Identity, 630 | RefToWeakRef, 631 | WeakRefToRef, 632 | } 633 | 634 | pub struct FunctionId<'a>(pub &'a str, String); 635 | 636 | impl<'a> FunctionId<'a> { 637 | pub fn mangled(&self) -> String { 638 | format!("{};{}", self.0, self.1) 639 | } 640 | 641 | pub fn from_source(source: &'a FunctionSource) -> Result { 642 | let mut signature = String::new(); 643 | for arg in &source.parameters { 644 | signature.push_str(arg.type_.mangled().as_str()); 645 | } 646 | Ok(FunctionId(&source.declaration.name, signature)) 647 | } 648 | } 649 | --------------------------------------------------------------------------------