Init commit

This commit is contained in:
endernon 2024-09-10 00:29:23 +01:00
parent 2d877cdf07
commit 61c0cf2eb0
24 changed files with 1407 additions and 2 deletions

5
.gitignore vendored
View file

@ -5,7 +5,6 @@ target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
@ -18,4 +17,6 @@ Cargo.lock
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ .idea/
releases

108
Cargo.lock generated Normal file
View file

@ -0,0 +1,108 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "idlib"
version = "0.1.0"
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "newestidmangler"
version = "0.1.0"
dependencies = [
"base64",
"idlib",
"serde",
"serde_json",
]
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

10
Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "newestidmangler"
version = "0.1.0"
edition = "2021"
[dependencies]
base64 = "0.22.1"
idlib = { path = "./idlib" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

59
config.md Normal file
View file

@ -0,0 +1,59 @@
# Values Guide
## Name
**Name** is stored as a String. It must be a valid Wynncraft item for it to display as intended.
## Shiny ID
not yet implemented
## Powders
### Powder Limit
Powder limit cannot be increased beyond 255. This is because the powder count is stored as a byte.
Potential value range: 0<->255.
### Format
Inside the "powders":[ ] array, set it up in the following structure.
**{ "type":"$TYPE", "tier":$TIER, "amount": $AMOUNT }**
**TYPE** is stored as a single character.
Potential Values: "E" "T" "W" "F" "A" . These represent Earth, Thunder, Wind, Fire, Air.
**TIER** is stored as a single digit integer.
Potential value range: 1<->6.
**AMOUNT** is stored as an integer. It is optional. If not provided it falls back to 1.
Potential value range: 1<->255.
#### Other things about powder format:
Each value in the array must have a comma at the end except the last.
The use of spaces is optional, as well as letter case for the $TYPE value.
The keys ("type" "tier" "amount") must all be lowercase.
If type is invalid, it will default back to being Thunder powder.
#### Powders Example
```js
"powders": [
{ "type":"T", "tier":6, "amount":5 },
{"type":"e","tier":1,"amount":5},
{"type":"F", "tier":3,"amount":1},
{ "type" : "w" , "tier":6 }
],
```
Note that the last powder block in array has no comma at the end.
### Which items can have powders?
Powders can only be encoded on an item that originally supported powders in the first place.
Unfortunately you can't add powders to an item that didn't originally have them.
e.g. can't put powder on depressing shears, as depressing weapons don't have powder slot.
## Identifications
### Format
Inside the "ids":[] array, set it up in the following structure.
**{"id": "$ID","base": $BASE,"roll": $ROLL}**
**ID** is stored as a string corresponding to the Wynntils internal ID string of any roll. See here for a list: https://raw.githubusercontent.com/Wynntils/Static-Storage/main/Reference/id_keys.json .
**BASE** is the base roll. The default base data is defined in (WARNING: EXTREMELY MASSIVE TEXT FILE) https://raw.githubusercontent.com/Wynntils/Static-Storage/main/Reference/gear.json .
Beautify it then look for `ITEMNAME > base > IDENTIFICATION > raw` for default value. This base value defines the listed ID ranges.
**ROLL** defines the actual rolled value for the Identification. The formula is `BASE * ROLL / 100`. Most values are in the range 30-130 due to how Wynncraft handles many Identifications as 30-130 percent of a base stat. Thus, if you are trying to find the ROLL value, try your desired roll **(NOT the roll percentage 1-100)** divided by the BASE then round it to the nearest integer. This value is optional, and you should only exclude it when it is a fixed value e.g. Skill Points.
## Rerolls
Optional single value, i8. Stores number of rerolls. If missing or is 0, rerolls are not encoded.
Potential range: 0<->255.
### Other things about this
The format obeys the same json rules as the powders.
If you are trying to get the 100% or 0% roll and the value you calculated is close to 130 or 30, change it to 130 or 30 because that is the real value.
# Current issues
Crafteds are not implemented

115
id_keys.json Normal file
View file

@ -0,0 +1,115 @@
{
"1stSpellCost": 0,
"2ndSpellCost": 1,
"3rdSpellCost": 2,
"4thSpellCost": 3,
"airDamage": 4,
"airDefence": 5,
"airMainAttackDamage": 6,
"airSpellDamage": 7,
"damageFromMobs": 8,
"earthDamage": 9,
"earthDefence": 10,
"earthMainAttackDamage": 11,
"earthSpellDamage": 12,
"elementalDamage": 13,
"elementalDefence": 14,
"elementalDefense": 15,
"elementalSpellDamage": 16,
"exploding": 17,
"fireDamage": 18,
"fireDefence": 19,
"fireSpellDamage": 20,
"gatherSpeed": 21,
"gatherXpBonus": 22,
"healingEfficiency": 23,
"healthRegen": 24,
"healthRegenRaw": 25,
"jumpHeight": 26,
"knockback": 27,
"leveledLootBonus": 28,
"leveledXpBonus": 29,
"lifeSteal": 30,
"lootBonus": 31,
"lootQuality": 32,
"mainAttackDamage": 33,
"manaRegen": 34,
"manaSteal": 35,
"poison": 36,
"raw1stSpellCost": 37,
"raw2ndSpellCost": 38,
"raw3rdSpellCost": 39,
"raw4thSpellCost": 40,
"rawAgility": 41,
"rawAirDamage": 42,
"rawAirMainAttackDamage": 43,
"rawAirSpellDamage": 44,
"rawAttackSpeed": 45,
"rawDefence": 46,
"rawDexterity": 47,
"rawEarthDamage": 48,
"rawEarthSpellDamage": 49,
"rawElementalDamage": 50,
"rawElementalMainAttackDamage": 51,
"rawElementalSpellDamage": 52,
"rawFireDamage": 53,
"rawFireMainAttackDamage": 54,
"rawFireSpellDamage": 55,
"rawHealth": 56,
"rawIntelligence": 57,
"rawMainAttackDamage": 58,
"rawNeutralDamage": 59,
"rawNeutralSpellDamage": 60,
"rawSpellDamage": 61,
"rawStrength": 62,
"rawThunderDamage": 63,
"rawThunderMainAttackDamage": 64,
"rawThunderSpellDamage": 65,
"rawWaterDamage": 66,
"rawWaterMainAttackDamage": 67,
"rawWaterSpellDamage": 68,
"reflection": 69,
"slowEnemy": 70,
"soulPointRegen": 71,
"spellDamage": 72,
"sprint": 73,
"sprintRegen": 74,
"stealing": 75,
"thorns": 76,
"thunderDamage": 77,
"thunderDefence": 78,
"thunderMainAttackDamage": 79,
"thunderSpellDamage": 80,
"walkSpeed": 81,
"waterDamage": 82,
"waterDefence": 83,
"waterSpellDamage": 84,
"weakenEnemy": 85,
"xpBonus": 86,
"healing": 87,
"rawEarthMainAttackDamage": 88,
"agility": 89,
"baseAirDamage": 90,
"baseAirDefence": 91,
"baseDamage": 92,
"baseEarthDamage": 93,
"baseEarthDefence": 94,
"baseFireDamage": 95,
"baseFireDefence": 96,
"baseHealth": 97,
"baseThunderDamage": 98,
"baseThunderDefence": 99,
"baseWaterDamage": 100,
"baseWaterDefence": 101,
"damage": 102,
"defence": 103,
"dexterity": 104,
"elementalMainAttackDamage": 105,
"fireMainAttackDamage": 106,
"intelligence": 107,
"neutralDamage": 108,
"neutralMainAttackDamage": 109,
"rawDamage": 110,
"rawNeutralMainAttackDamage": 111,
"strength": 112
}

6
idlib/Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "idlib"
version = "0.1.0"
edition = "2021"
[dependencies]

View file

@ -0,0 +1,36 @@
use crate::types::transform::TransformVersion;
use super::{DataDecoder, DataEncoder, DataTransformerTypes, TransformId};
#[derive(Debug, Clone)]
pub struct EndData;
impl TransformId for EndData {
fn get_id() -> u8 {
DataTransformerTypes::EndDataTransformer as u8
}
}
impl DataEncoder for EndData {
fn encode_data(
&self,
_ver: TransformVersion,
_out: &mut Vec<u8>,
) -> Result<(), super::DataTransformError> {
// end data is always empty
return Ok(());
}
}
impl<B: Iterator<Item = u8>> DataDecoder<B> for EndData {
fn decode_data(
_bytes: &mut B,
_ver: TransformVersion,
) -> Result<Self, super::DataTransformError>
where
Self: Sized,
{
// end data is always empty
return Ok(Self);
}
}

View file

@ -0,0 +1,175 @@
use crate::{
data_transformer::DataTransformError,
encoding::{decode_varint, encode_varint},
types::{
stat::{RollType, Stat, StatId},
transform::TransformVersion,
},
};
use super::{DataDecoder, DataEncoder, DataTransformerTypes, TransformId};
#[derive(Debug, Clone)]
pub struct IdentificationData {
pub identifications: Vec<Stat>,
pub extended_encoding: bool,
}
impl TransformId for IdentificationData {
fn get_id() -> u8 {
DataTransformerTypes::IdentificationDataTransformer as u8
}
}
impl DataEncoder for IdentificationData {
fn encode_data(
&self,
ver: TransformVersion,
out: &mut Vec<u8>,
) -> Result<(), super::DataTransformError> {
match ver {
TransformVersion::Version1 => {
if self.identifications.len() > 255 {
return Err(DataTransformError::TooManyIdentifications);
}
let encoded_id_count: u8 = self
.identifications
.iter()
.filter(|id| !id.pre_identified())
.count() as u8;
out.push(encoded_id_count);
out.push(self.extended_encoding as u8);
self.encode_individual_idents(out)?;
return Ok(());
}
}
}
fn should_encode_data(&self, ver: TransformVersion) -> bool {
match ver {
TransformVersion::Version1 => {
if self.extended_encoding {
return self.identifications.len() != 0;
} else {
return self
.identifications
.iter()
.any(|id: &Stat| !id.pre_identified());
}
}
}
}
}
impl IdentificationData {
fn encode_individual_idents(&self, bytes: &mut Vec<u8>) -> Result<(), DataTransformError> {
// encode the static values if extended encoding is used
if self.extended_encoding {
let preid_stats: Vec<_> = self
.identifications
.iter()
.filter(|id| id.pre_identified())
.collect();
bytes.push(preid_stats.len() as u8);
for stat in preid_stats {
// first add the id of the ident
bytes.push(stat.kind.0);
// then add the basevalue
bytes.append(&mut encode_varint(
stat.base.ok_or(DataTransformError::NoBasevalueForIdent)? as i64,
));
}
}
for ident in self.identifications.iter() {
// only handle non preids since preids are encoded using the earlier system
if let RollType::Value(roll_val) = ident.roll {
// add id of the ident
bytes.push(ident.kind.0);
if self.extended_encoding {
// push the baseval
bytes.append(&mut encode_varint(
ident.base.ok_or(DataTransformError::NoBasevalueForIdent)? as i64,
));
}
bytes.push(roll_val);
} else {
continue;
}
}
Ok(())
}
}
impl<B: Iterator<Item = u8>> DataDecoder<B> for IdentificationData {
fn decode_data(bytes: &mut B, ver: TransformVersion) -> Result<Self, super::DataTransformError>
where
Self: Sized,
{
match ver {
TransformVersion::Version1 => {
let mut idents = Vec::new();
// first byte is the number of identifications
let ident_count = bytes.next().unwrap();
// second byte is whether or not extended coding is used
let extended_encoding = bytes.next().unwrap() == 1;
let mut preid_count = 0;
if extended_encoding {
// count of preid idents
preid_count = bytes.next().unwrap();
}
for i in 0..(ident_count + preid_count) {
// id of the ident
let id = bytes.next().unwrap();
let preid = i < preid_count;
// decode the possible baseval if using extended coding
let baseval = if extended_encoding {
Some(decode_varint(bytes) as i32)
} else {
None
};
// if preid skip decoding the value
if preid {
idents.push(Stat {
kind: StatId(id),
base: baseval,
roll: RollType::PreIdentified,
});
continue;
} else {
// decode the roll
let introll = bytes.next().unwrap();
idents.push(Stat {
kind: StatId(id),
base: baseval,
roll: RollType::Value(introll),
})
}
}
Ok(Self {
identifications: idents,
extended_encoding,
})
}
}
}
}

View file

@ -0,0 +1,130 @@
use enddata::EndData;
use identdata::IdentificationData;
use namedata::NameData;
use powderdata::PowderData;
use rerolldata::RerollData;
use startdata::StartData;
use typedata::TypeData;
use crate::types::transform::TransformVersion;
pub mod enddata;
pub mod identdata;
pub mod namedata;
pub mod powderdata;
pub mod rerolldata;
pub mod shinydata;
pub mod startdata;
pub mod typedata;
pub trait TransformId {
fn get_id() -> u8;
}
pub trait DataEncoder: TransformId {
fn encode(&self, ver: TransformVersion, out: &mut Vec<u8>) -> Result<(), DataTransformError> {
// skip encoding data which should not be encoded
if !self.should_encode_data(ver) {
return Ok(());
}
// encode the id
out.push(Self::get_id());
// encode the data
self.encode_data(ver, out)?;
Ok(())
}
fn encode_data(
&self,
ver: TransformVersion,
out: &mut Vec<u8>,
) -> Result<(), DataTransformError>;
fn should_encode_data(&self, _ver: TransformVersion) -> bool {
true
}
}
pub trait DataDecoder<B: Iterator<Item = u8>>: TransformId {
fn decode_data(bytes: &mut B, ver: TransformVersion) -> Result<Self, DataTransformError>
where
Self: Sized;
}
pub fn decode<B: Iterator<Item = u8>>(bytes: &mut B) -> Result<Vec<AnyData>, DataTransformError> {
let mut out = Vec::new();
// decode the start byte and version
let ver = StartData::decode_start_bytes(bytes)?;
while let Some(id) = bytes.next() {
match id {
0 => return Err(DataTransformError::StartReparse),
1 => out.push(AnyData::TypeData(TypeData::decode_data(bytes, ver)?)),
2 => out.push(AnyData::NameData(NameData::decode_data(bytes, ver)?)),
3 => out.push(AnyData::IdentificationData(
IdentificationData::decode_data(bytes, ver)?,
)),
// TODO
255 => out.push(AnyData::EndData(EndData::decode_data(bytes, ver)?)),
_ => return Err(DataTransformError::UnknownTransformer(id)),
}
}
Ok(out)
}
#[derive(Debug)]
pub enum DataTransformError {
NoStartBlock,
UnknownVersion(u8),
/// Attempt to parse start data. Start data is specially handled.
StartReparse,
InvalidTypeError,
BadString,
TooManyIdentifications,
NoBasevalueForIdent,
NoPotentialValuesForIdent,
InvalidIntRoll,
UnexpectedEndOfBytes,
UnknownTransformer(u8),
}
pub enum DataTransformerTypes {
StartDataTransformer = 0,
TypeDataTransformer = 1,
NameDataTransformer = 2,
IdentificationDataTransformer = 3,
PowderDataTransformer = 4,
RerollDataTransformer = 5,
ShinyDataTransformer = 6,
CustomGearTypeTransformer = 7,
DurabilityDataTransformer = 8,
RequirementsDataTransformer = 9,
DamageDataTransformer = 10,
DefenseDataTransformer = 11,
CustomIdentificationDataTransformer = 12,
CustomConsumableTypeDataTransformer = 13,
UsesDataTransformer = 14,
EffectsDataTransformer = 15,
EndDataTransformer = 255,
}
#[derive(Debug)]
pub enum AnyData {
StartData(StartData),
TypeData(TypeData),
NameData(NameData),
IdentificationData(IdentificationData),
PowderData(PowderData),
RerollData(RerollData),
// TODO
EndData(EndData),
}

View file

@ -0,0 +1,54 @@
use crate::{data_transformer::DataTransformError, types::transform::TransformVersion};
use super::{DataDecoder, DataEncoder, DataTransformerTypes, TransformId};
#[derive(Debug, Clone)]
pub struct NameData(pub String);
impl TransformId for NameData {
fn get_id() -> u8 {
DataTransformerTypes::NameDataTransformer as u8
}
}
impl DataEncoder for NameData {
fn encode_data(
&self,
ver: TransformVersion,
out: &mut Vec<u8>,
) -> Result<(), super::DataTransformError> {
match ver {
TransformVersion::Version1 => {
// check that the string is valid ascii
if self.0.chars().any(|c| !c.is_ascii()) {
return Err(DataTransformError::BadString);
}
// push the bytes
out.extend_from_slice(self.0.as_bytes());
// push the null terminator
out.push(0);
}
}
Ok(())
}
}
impl<B: Iterator<Item = u8>> DataDecoder<B> for NameData {
fn decode_data(bytes: &mut B, ver: TransformVersion) -> Result<Self, super::DataTransformError>
where
Self: Sized,
{
match ver {
TransformVersion::Version1 => {
let b: Vec<u8> = bytes.take_while(|b| *b != 0).collect();
// UTF-8 and ASCII share the same set of characters
Ok(NameData(
String::from_utf8(b).map_err(|_| DataTransformError::BadString)?,
))
}
}
}
}

View file

@ -0,0 +1,59 @@
use crate::types::{powder::Powders, transform::TransformVersion};
use super::{DataEncoder, DataTransformerTypes, TransformId};
#[derive(Debug, Clone)]
pub struct PowderData {
pub powder_slots: u8,
pub powders: Vec<(Powders, u8)>,
}
impl TransformId for PowderData {
fn get_id() -> u8 {
DataTransformerTypes::PowderDataTransformer as u8
}
}
impl DataEncoder for PowderData {
fn encode_data(
&self,
ver: TransformVersion,
out: &mut Vec<u8>,
) -> Result<(), super::DataTransformError> {
match ver {
TransformVersion::Version1 => {
let bits_needed = self.powders.len() * 5;
let total_bits = (bits_needed + 7) / 8;
let mut powder_data = vec![0u8; total_bits];
for (i, pow) in self.powders.iter().enumerate() {
let elem = pow.0 as u8;
// TODO: figure out if wynntils fixes this and make the tier be encoded correctly
let tier = 0; //pow.1;
// calculate the 5 bit powder value
let powder_num = (elem * 6 + tier) & 0b00011111;
// bit position where this specific powder starts
let powder_idx = i * 5;
// set the values
for j in 0..5 {
// calculate the bit position of this bit
let idx = powder_idx + j;
let bit = (powder_num >> (4 - j)) & 0b1;
powder_data[idx / 8] |= bit << (7 - (idx % 8));
}
}
out.push(self.powder_slots);
out.push(self.powders.len() as u8);
out.append(&mut powder_data);
}
}
Ok(())
}
}

View file

@ -0,0 +1,41 @@
use crate::types::transform::TransformVersion;
use super::{DataDecoder, DataEncoder, DataTransformError, DataTransformerTypes, TransformId};
#[derive(Debug, Clone)]
pub struct RerollData(pub u8);
impl TransformId for RerollData {
fn get_id() -> u8 {
DataTransformerTypes::RerollDataTransformer as u8
}
}
impl DataEncoder for RerollData {
fn encode_data(
&self,
ver: crate::types::transform::TransformVersion,
out: &mut Vec<u8>,
) -> Result<(), super::DataTransformError> {
match ver {
TransformVersion::Version1 => out.push(self.0),
}
Ok(())
}
}
impl<B: Iterator<Item = u8>> DataDecoder<B> for RerollData {
fn decode_data(bytes: &mut B, ver: TransformVersion) -> Result<Self, super::DataTransformError>
where
Self: Sized,
{
match ver {
TransformVersion::Version1 => Ok(Self(
bytes
.next()
.ok_or(DataTransformError::UnexpectedEndOfBytes)?,
)),
}
}
}

View file

@ -0,0 +1,31 @@
use crate::{encoding::encode_varint, types::transform::TransformVersion};
use super::{DataEncoder, DataTransformerTypes, TransformId};
pub struct ShinyData {
pub id: u8,
pub val: i64,
}
impl TransformId for ShinyData {
fn get_id() -> u8 {
DataTransformerTypes::ShinyDataTransformer as u8
}
}
impl DataEncoder for ShinyData {
fn encode_data(
&self,
ver: crate::types::transform::TransformVersion,
out: &mut Vec<u8>,
) -> Result<(), super::DataTransformError> {
match ver {
TransformVersion::Version1 => {
out.push(self.id);
out.append(&mut encode_varint(self.val));
}
}
Ok(())
}
}

View file

@ -0,0 +1,42 @@
use crate::types::transform::TransformVersion;
use super::{DataEncoder, DataTransformError, DataTransformerTypes, TransformId};
#[derive(Debug, Clone)]
pub struct StartData(pub TransformVersion);
impl TransformId for StartData {
fn get_id() -> u8 {
DataTransformerTypes::StartDataTransformer as u8
}
}
impl DataEncoder for StartData {
fn encode_data(
&self,
ver: TransformVersion,
out: &mut Vec<u8>,
) -> Result<(), DataTransformError> {
match ver {
TransformVersion::Version1 => out.push(self.0.version()),
}
Ok(())
}
}
impl StartData {
/// Special case function for parsing the start bytes
pub(crate) fn decode_start_bytes<B: Iterator<Item = u8>>(
bytes: &mut B,
) -> Result<TransformVersion, DataTransformError> {
let idbyte = bytes.next().unwrap();
if idbyte != DataTransformerTypes::StartDataTransformer as u8 {
return Err(DataTransformError::NoStartBlock);
}
let verbyte = bytes.next().unwrap();
TransformVersion::from_u8(verbyte).map_err(|_| DataTransformError::UnknownVersion(verbyte))
}
}

View file

@ -0,0 +1,43 @@
use crate::types::{itemtype::ItemType, transform::TransformVersion};
use super::{DataDecoder, DataEncoder, DataTransformError, DataTransformerTypes, TransformId};
#[derive(Debug, Clone)]
pub struct TypeData(pub ItemType);
impl TransformId for TypeData {
fn get_id() -> u8 {
DataTransformerTypes::TypeDataTransformer as u8
}
}
impl DataEncoder for TypeData {
fn encode_data(
&self,
ver: TransformVersion,
out: &mut Vec<u8>,
) -> Result<(), super::DataTransformError> {
match ver {
TransformVersion::Version1 => out.push(self.0.into()),
}
Ok(())
}
}
impl<B: Iterator<Item = u8>> DataDecoder<B> for TypeData {
fn decode_data(bytes: &mut B, ver: TransformVersion) -> Result<Self, super::DataTransformError>
where
Self: Sized,
{
match ver {
TransformVersion::Version1 => {
let b = bytes.next().unwrap();
Ok(Self(
ItemType::try_from(b).map_err(|_| DataTransformError::InvalidTypeError)?,
))
}
}
}
}

112
idlib/src/encoding.rs Normal file
View file

@ -0,0 +1,112 @@
/// Encode bytes into a string using the wynntils byte encoding scheme
///
/// https://github.com/Wynntils/Wynntils/blob/main/common/src/main/java/com/wynntils/utils/EncodedByteBuffer.java#L87
pub fn encode_string(data: &[u8]) -> String {
let mut out = String::new();
for d in data.chunks(2) {
if d.len() == 2 {
if d[0] == 255 && d[1] >= 254 {
out.push(char::from_u32(0x100000 + ((d[1] - 254) as u32)).unwrap());
} else {
out.push(char::from_u32(0xF0000 + ((d[0] as u32) << 8) + d[1] as u32).unwrap());
}
} else {
// encode leftover singular bits with the seperate encoding
out.push(char::from_u32(0x100000 + ((d[0] as u32) << 8) + 0xEE).unwrap());
}
}
out
}
/// Decodes the bytes of a wynntils private area encoded string
///
/// This function does not check whether or not the encoded data is valid
///
/// https://github.com/Wynntils/Wynntils/blob/main/common/src/main/java/com/wynntils/utils/EncodedByteBuffer.java#L33
pub fn decode_string(data: &str) -> Vec<u8> {
let mut out = Vec::new();
for c in data.chars() {
let n: u32 = c.into();
// special case Private use area B
if n > 0x100000 {
// single byte
if n & 0xFF == 0xEE {
out.push(((n & 0xFF00) >> 8) as u8);
assert!(((n & 0xFF00) >> 8) <= 255, "Invalid codepoint: {n:06X}");
continue;
}
// two bytes
out.push(255);
out.push((254 + (n & 0xFF)) as u8);
// Only 0x100000-0x100001 are used
assert!(n < 0x100002, "Invalid codepoint: {n:06X}");
continue;
}
out.push(((n & 0xFF00) >> 8) as u8);
out.push((n & 0x00FF) as u8);
}
out
}
pub fn encode_varint(value: i64) -> Vec<u8> {
// zigzag encoding magic
// removes sign bit so values are only positive
let value = ((value << 1) ^ (value >> 63)) as u64;
// 7 bits per byte
// highest bit is used to indicate end of encoding
// calulate number of bytes needed
let mut numofbytes = 1;
let mut temp = value >> 7;
while temp != 0 {
// println!("{temp}");
numofbytes += 1;
temp >>= 7;
}
let mut outbytes = Vec::new();
for i in 0..numofbytes {
let mut next = (value >> (7 * i)) as u8 & 0x7F;
// indicate that we are **not** done by setting the highest bit
if i < numofbytes - 1 {
next |= 0b10000000;
}
outbytes.push(next);
}
outbytes
}
pub fn decode_varint<B: Iterator<Item = u8>>(bytes: &mut B) -> i64 {
let mut value = 0;
let mut data = Vec::new();
loop {
let b = bytes.next().unwrap();
data.push(b);
if (b & 0b10000000) == 0 {
break;
}
}
for (i, n) in data.into_iter().enumerate() {
value |= ((n & 0b01111111) as i64) << (7 * i);
}
return (value >> 1) ^ -(value & 1);
}

3
idlib/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod data_transformer;
pub mod encoding;
pub mod types;

View file

@ -0,0 +1,31 @@
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ItemType {
Gear = 0,
Tome = 1,
Charm = 2,
CraftedGear = 3,
CraftedConsu = 4,
}
impl Into<u8> for ItemType {
fn into(self) -> u8 {
self as u8
}
}
impl TryFrom<u8> for ItemType {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Gear),
1 => Ok(Self::Tome),
2 => Ok(Self::Charm),
3 => Ok(Self::CraftedGear),
4 => Ok(Self::CraftedConsu),
_ => Err(()),
}
}
}

4
idlib/src/types/mod.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod itemtype;
pub mod powder;
pub mod stat;
pub mod transform;

View file

@ -0,0 +1,9 @@
/// Powder types
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Powders {
EARTH = 1,
THUNDER = 2,
WATER = 3,
FIRE = 4,
AIR = 5,
}

24
idlib/src/types/stat.rs Normal file
View file

@ -0,0 +1,24 @@
#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug)]
pub struct StatId(pub u8);
#[derive(Debug, Clone)]
pub struct Stat {
pub kind: StatId,
pub base: Option<i32>,
pub roll: RollType,
}
#[derive(Debug, Clone)]
pub enum RollType {
Value(u8),
PreIdentified,
}
impl Stat {
pub fn pre_identified(&self) -> bool {
match self.roll {
RollType::Value(_) => false,
RollType::PreIdentified => true,
}
}
}

View file

@ -0,0 +1,17 @@
#[derive(Clone, Copy, Debug)]
pub enum TransformVersion {
Version1 = 0,
}
impl TransformVersion {
pub fn version(&self) -> u8 {
*self as u8
}
pub fn from_u8(byte: u8) -> Result<Self, ()> {
match byte {
0 => Ok(Self::Version1),
_ => Err(()),
}
}
}

269
src/main.rs Normal file
View file

@ -0,0 +1,269 @@
use idlib::{
data_transformer::{
decode, enddata::EndData, identdata::IdentificationData, namedata::NameData,
powderdata::PowderData, rerolldata::RerollData, shinydata::ShinyData, startdata::StartData,
typedata::TypeData, DataEncoder,
},
encoding::{decode_string, encode_string},
types::{
itemtype::ItemType,
powder::Powders,
stat::{RollType, Stat, StatId},
transform::TransformVersion,
},
};
use std::collections::HashMap;
use std::fs;
use std::panic;
use std::env;
use serde_json;
use serde::Deserialize;
use base64::engine::{general_purpose, Engine};
// structs
#[derive(Deserialize)]
struct Powder {
r#type: char,
tier: u8,
amount: Option<u8>
}
#[derive(Deserialize)]
struct Identificationer {
id: String,
base: i32,
roll: Option<u8>
}
#[derive(Deserialize)]
struct jsonconfig {
name: String,
ids: Vec<Identificationer>,
powder_limit: u8,
powders: Vec<Powder>,
rerolls:Option<u8>
}
fn main() {
// enable fancypanic when building for release
fancypanic();
// newest json reading code
let json_config: jsonconfig = serde_json::from_reader(
fs::File::open("values.json").expect(ERROR[1]))
.expect(ERROR[2]);
let idsmap: HashMap<String, u8> = serde_json::from_reader(fs::File::open("id_keys.json").expect(ERROR[3]))
.expect(ERROR[4]);
// println!("{:?}",idsmap.get("airDamage"));
// below is no longer needed as ive merged it
//let imported2: jsoned = serde_json::from_reader(importedjson)
// .expect("this json sucks");
// read the file and stuff
// thanks to https://stackoverflow.com/a/52964674
// obselete do not use
//let file = fs::File::open("values.json")
// .expect("where file?");
//let thejson: serde_json::Value = serde_json::from_reader(file)
// .expect("where proper json format?");
//let powders = thejson.get("powders").expect("e").get("a");
//let powders2 = serde_json::json!(powders);
//println!("powders: {:?}",powders);
//println!("powders2: {}",powders2);
//println!("name is {}", thejson);
// let fuy = thejson.get("a");
// println!("{:#?}",fuy);
let mut out = Vec::new();
let ver = TransformVersion::Version1;
StartData(ver).encode(ver, &mut out).unwrap();
TypeData(ItemType::Gear).encode(ver, &mut out).unwrap();
NameData(String::from(format!("{}", json_config.name.trim()) ))
.encode(ver, &mut out)
.unwrap();
let w1 = "aWRtYW5nbGVyLXJld3JpdGUgUHJlLVJlbGVhc2UgdjE=";
let w2 = "KEMpIFpBVFpPVSBhbmQgRU5ERVJOT04gMjAyNA==";
let l1 = String::from_utf8(general_purpose::STANDARD.decode(w1).unwrap()).unwrap();
let l2 = String::from_utf8(general_purpose::STANDARD.decode(w2).unwrap()).unwrap();
println!("{l1}");
println!("{l2}");
// json identification data handling
let mut idvec = Vec::new();
for eachid in json_config.ids {
let id_id = idsmap.get(eachid.id.trim());
let id_base = eachid.base as i32;
let id_roll = eachid.roll;
idvec.push(
(
Stat {
kind: StatId(match id_id {
Some(ide) => *ide,
None => panic!("There is a mismatched ID, and this message has replaced where the line is meant to be")
}),
base: Some(id_base),
roll: match id_roll{
Some(rolle) => RollType::Value(rolle),
None => RollType::PreIdentified
}
}
)
);
// println!("{:?} {:?} {:?}",id_id,id_base,id_roll)
}
IdentificationData {
identifications: idvec,
extended_encoding: true,
}
.encode(ver, &mut out)
.unwrap();
// json powder data handling
let mut powdervec = Vec::new();
for eachpowder in json_config.powders {
let powdertier = eachpowder.tier; // get the powder tier
let powderamount:u8 = match eachpowder.amount { // get amount of powder if exists, otherwise 1
Some(amount) => {
amount
},// good,
None => {
1
}// bad,
};
// match for the powder type
// no need to return to variable or i'll need to rematch AGAIN
match eachpowder.r#type {
'E' | 'e' => {
for i in 0..powderamount {
powdervec.push((Powders::EARTH,powdertier))
}
},
'T' | 't' => {
for i in 0..powderamount {
powdervec.push((Powders::THUNDER,powdertier))
}
},
'W' | 'w' => {
for i in 0..powderamount {
powdervec.push((Powders::WATER,powdertier))
}
},
'F' | 'f' => {
for i in 0..powderamount {
powdervec.push((Powders::FIRE,powdertier))
}
},
'A' | 'a' => {
for i in 0..powderamount {
powdervec.push((Powders::AIR,powdertier))
}
},
_ => {
for i in 0..powderamount {
powdervec.push((Powders::THUNDER,powdertier))
}
}
};
// println!("tier {}",powdertier);
// println!("amount {}",powderamount);
}
// println!("{:?}",powdervec);
// old powder data encode kinda, takes data from new encode
PowderData {
powder_slots: json_config.powder_limit,
powders: powdervec,
}
.encode(ver, &mut out)
.unwrap();
match json_config.rerolls {
Some(i) => {
if i != 0 {
RerollData(i).encode(ver, &mut out).unwrap();
}
},
None => pass()
}
ShinyData {
id: 2,
val: i64::MAX as i64, //- 0b0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000,
// u16::MAX is the max value of unsigned 16bit value
}
.encode(ver, &mut out)
.unwrap();
// prints (Water,6) 255 times
// println!("{:?}",vec![(Powders::WATER, 6); 255]);
EndData.encode(ver, &mut out).unwrap();
// final string print
println!("{}", encode_string(&out));
// I don't even know what the fuck this does
//for b in out {
// print!("{:02X}", b);
//}
// println!();
// decode test
let input = "󰀀󰄀󰉁󶹴󶡲󶅣󶥴󶔠󴉡󶱬󶥳󷑡󰀃󰠁󰀞󾠇󵠑󳱩󳢠󱽴󴠧󷄡󱹵󳫠󰢂󱌨󵴅󲠞􏿮";
let bytes = decode_string(&input);
let mut bytes_iter = bytes.into_iter();
let out = decode(&mut bytes_iter).unwrap();
// println!("{:#?}", out);
}
fn fancypanic() {
panic::set_hook(Box::new(|panic_info| {
let panic_msg = format!("{panic_info}");
println!("{}", panic_msg.lines().skip(1).next().unwrap_or("HOW DID YOU BREAK THE PANIC HANDLER???"));
}));
}
fn pass() {
}
const ERROR: [&'static str; 5] = [
"Error 0: what did you even do to get this? ",
"Error 1: json config file is missing, reobtain it from the values.json I have sent you. ",
"Error 2: json config is broken. Reread the example data or reobtain it from the values.json I have sent you. ",
"Error 3: Identifications hashmap not found. Get it from https://raw.githubusercontent.com/Wynntils/Static-Storage/main/Reference/id_keys.json and move it to this directory.",
"Error 4: Identifications hashhmap is corrupt. Reobtain it from https://raw.githubusercontent.com/Wynntils/Static-Storage/main/Reference/id_keys.json and move it to this directory."
];
const _BOIL: [&'static str; 3] = [
"0",
"reobtain it from the values.json I have sent you. ",
"Get it from https://raw.githubusercontent.com/Wynntils/Static-Storage/main/Reference/id_keys.json and move it to this directory."
];

26
values.json Normal file
View file

@ -0,0 +1,26 @@
{
"READMEFIRST": [
"Refer to config.md to understand how to apply the data shown here.",
"There are also some values you cannot increase beyond a limit."
],
"name":"Singularity",
"ids": [
{"id": "mainAttackDamage","base": 320,"roll": 69},
{"id": "healthRegenRaw", "base":250 , "roll":130 },
{"id": "rawDexterity", "base":35 },
{"id": "walkSpeed", "base":-40 , "roll":69},
{"id": "mainAttackDamage", "base":15, "roll":130 },
{"id": "rawMainAttackDamage", "base":444 , "roll":130 },
{"id": "rawSpellDamage", "base":222 , "roll":130 },
{"id": "spellDamage", "base":10 , "roll":130 }
],
"powder_limit": 255,
"powders": [
{ "type":"T", "tier":6, "amount":5 },
{"type":"e","tier":6,"amount":5},
{"type":"f", "tier":6,"amount":1},
{ "type" : "f" , "tier":6 }
],
"rerolls": 0
}