Giter Site home page Giter Site logo

wow_messages's People

Contributors

bigglesss avatar dantsz avatar kerhong avatar victov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

wow_messages's Issues

Sending SMSG_OBJECT_UPDATE with partial dirty mask does nothing on the client

When sending a SMSG_OBJECT_UPDATE containing a Values update, if the UpdateMask is not set to be fully dirty, the client does nothing upon receiving it.

A minimal example of this problem can be found here. It is a modified version of the wrath example to contain a "goat loop", a loop that transforms the player character into a goat and back every 2 seconds.

Commenting out this line, which removes the dirtyness of the update mask, is the difference between working and not working.

@bigglesss confirmed that this happens for both 3.3.5 and 1.12.

I know that something like the goatloop should work, I used to have that working in wrath-rs a long time ago before I refactored to use wow_message. You can check out this commit for reference.

Expected behaviour:
The client should accept Values Updates, and show the new values, even when the values update only sends data that has recently changed. Marking the values update as fully dirty should not be required to convey the update to the client.

Hierarchical version matching.

Hey @gtker! Today I was doing some testing to see if I want to take the leap to convert my entire project (wrath-rs) to use the wow_messages crate.

As an experiment I've been fiddling around with the wowm language and ran into a little issue that restricts how usable it is (or perhaps I'm misunderstanding).

When I enter a versions = "3" tag, I expect that object to match for all Wotlk versions. However, when I create a message for versions = "3.3.5", during parsing it complains that there is no version of the first object for 3.3.5. Are versions not matching in a hierarchical way? I expect a message for 3.3.5 to be satisfied with an object for all versions of 3.

I've attached a screenshot with my code and the resulting error.

image

Invalid date in test case for SMSG_LOGIN_SETTIMESPEED

/// Set time to 2022-08-13 (Wednesday) 08:10 and timescale 0.016666668 (1/60).
test SMSG_LOGIN_SETTIMESPEED {
datetime = 376642058;
timescale = 0.016666668;
} [
0x00, 0x0a, /* size */
0x42, 0x00, /* opcode */
0x0a, 0x1a, 0x73, 0x16, /* datetime */
0x89, 0x88, 0x88, 0x3c, /* timescale */
] {
versions = "1.12";
}

Set time to 2022-08-13 (Wednesday) 08:10

This is wrong, because 2022-08-13 is Saturday. Should confirm if wrong date+weekday combination is valid to have in packet.

BUG_SEPARATE_IF_DOUBLE_WRITE Separate if statements will write all blocks twice

If statements for the same type separated by other types will incorrectly write both blocks twice due to how the folding with RustTypes work.

For example, with b == T2:

struct T {
	Test b;
	if (b == T1 || b == T2) {
		u8 t1;
	}
	u8 basic;
	if (b == T2) {
		u8 t2;
	}
}

Will incorrectly write

  • Test b
  • u8 t1
  • u8 t2
  • u8 basic
  • u8 t1
  • u8 t2

This manifests in SMSG_MESSAGECHAT for Wrath with achievement text.

RUST_IF_SCOPE Correct codegen for if statements with variables in different scopes

This is for

smsg SMSG_AUCTION_COMMAND_RESULT = 0x025B {
    u32 auction_id;
    AuctionCommandAction action;
    AuctionCommandResult result;
    if (result == OK) {
        if (action == BID_PLACED) {
            u32 auction_outbid1;
        }
    } else if (result == ERR_INVENTORY) {
        InventoryResult inventory_result;
    } else if (result == ERR_HIGHER_BID) {
        Guid higher_bidder;
        u32 new_bid;
        u32 auction_outbid2;
    }
}

and

smsg SMSG_SEND_MAIL_RESULT = 0x0239 {
    u32 mail_id;
    MailAction action;
    MailResult result;
    if (result == ERR_EQUIP_ERROR) {
        u32 equip_error;
    }
    /* TODO: requires elseif for different thing */
    else {
        if (action == ITEM_TAKEN) {
            u32 item_guid {
                comment = "cmangos/vmangos: item guid low?";
            }
            u32 item_count;
        }
    }
}

In which the order of the variables used in if statements are reversed and so can't be moved into the if statement to prevent the problem.

A generalized version of this is either

enum A : u8 {
	ONE = 1;
	TWO = 2;
}
enum B : u8 {
	THREE = 3;
	FOUR = 4;
}

struct T {
	A a;
	B b;
	if (a == ONE) {
		if (b == THREE) {
			u8 one_three;
		}
	}
	else if (a == TWO) {
		u8 two;
	}
}

or

struct T {
	A a;
	B b;
	if (A == ONE) {
		u8 one;
	}
	else {
		if (b == THREE) {
			u8 other_three;
		}
	}
}

It is slightly unclear what the resulting Rust code should look like in these cases.

FLAG_OR+FLAG_DIFFERENT_VARIABLE Add support for OR in flags and for other variables in same statement

Wrath MovementInfo requires

struct MovementInfo {
    MovementFlags flags;
    ExtraMovementFlags extra_flags;
    u32 timestamp;
    Vector3d position;
    f32 orientation;
    if (flags & ON_TRANSPORT) {
        TransportInfo transport;
        if (extra_flags & INTERPOLATED_MOVEMENT) {
            u32 transport_time;
        }
    }
    if (flags & SWIMMING || flags & FLYING || extra_flags & ALWAYS_ALLOW_PITCHING) {
        f32 pitch1;
	}

Test case is

flag A : u8 {
	ONE = 1;
	TWO = 2;
}
flag B : u8 {
	FOUR = 4;
	EIGHT = 8;
}

struct T {
	A a;
	B b;
	if (a & ONE || a & TWO) {
		u8 one_two;
	}
}

and

struct T {
	A a;
	B b;
	if (a & ONE || a & TWO || b & FOUR) {
		u8 one_two_four;
	}
}

It is unknown exactly what the generated Rust code should look like.

AsyncRead and AsyncWrite

Edit: please ignore, this makes no sense (and I need to sleep). Thanks for the great lib!

Hi!

I see that all the packets use specific tokio (and async-std) read and write functions. Would you be open to a PR that implements AsyncRead and AsyncWrite for each of these instead? That way I could just send the packet over the stream directly rather than having to call a function first.

Cheers

Alex

Realm_RealmFlag does not have a public method allowing simple construction

As far as I can tell, there's no way to directly construct a Realm_RealmFlag from a u8 and Realm_RealmFlag_SpecifyBuild. This means that converting previously stored realm flags (from the DB) into the correct struct types for sending to the client requires manually checking the mask and constructing the Realm_RealmFlag struct bit by bit using the builder methods.

I think that either inner and specify_build should be public, or there should be a .new(inner: u8, specify_build: Realm_RealmFlag_SpecifyBuild) -> Self function.

I imagine this is probably an issue for subtypes on other messages as well?

`read_sized_c_string_to_vec` can cause a out of memory crash

pub fn read_sized_c_string_to_vec<R: Read>(
r: &mut R,
size: u32,
) -> Result<Vec<u8>, std::io::Error> {
let mut v = vec![0_u8; (size - 1) as usize];
r.read_exact(&mut v)?;
let mut null_terminator = [0_u8; 1];
r.read_exact(&mut null_terminator)?;
Ok(v)
}

size is user provided u32, it can cause allocation of 4GB of memory.

Possible fixes:

  • ignore size initially and verify it after reading CString
  • use size.min(some_reasonable_max_size)
    • max size can never be greater than
      0x7f_ff_ff // max size in header
      - 2 // min opcode size in header
      - 4 // sized string's length
    • I think even 0x7fffff is too big of a default, it's still 8MB

from #77

WOTLK: SMSG_MOTD is incorrect

When I try to read the MOTD of an AzerothCore 3.3.5a Server, I only get the first letter of the MOTD Message.

This is, because the MOTD apparently is not one SizedCString but a sized array of nul-terminated (at least in my observings) strings:
https://github.com/TrinityCore/TrinityCore/blob/3.3.5/src/server/game/Motd/ServerMotd.cpp#L37
Mangos TBC seems to have a matching implementation:
https://github.com/cmangos/mangos-tbc/blob/49deff938c4652417de120cef23452c3fd5df03b/src/game/Server/WorldSession.cpp#L837

Trinity claims it's "new in 2.0.1", so may affect TBC as well.

Improve fuzzability

Fuzzing of the library could help find inconsistencies between parsing and encoding payloads, and also panics reachable from inputs.

The most generic fuzzer I can think would be:

  • generate opcode, generate size, generate u8[size - opcode_size]
  • try to parse
    • if read whole buffer and was successful in parsing
      • encode it
      • verify original buffer is same as encoding output

Fuzzing is usually a separate crate that depends on your crate, so it can only access pub items.

This needs:

  • ability to parse from buffer and get total read size
    • could be done by exposing modified read_opcodes as pub fn read_opcodes(opcode: u16, body_size: u32, r: &mut &[u8]) -> Result<...>
  • ability to write a header of any opcode + size
    • could be done by exposing write_unencrypted_server
    • can easily be written as part of fuzzing implementation, but would be more correct if it's the exact implementation as in the lib

from #77

Character select screen missing "AddOns" button when connecting to vanilla-server example

Client version: 1.12.1

Compared Wireshark packets to a standard vanilla private server and there doesn't seem to be any difference between the auth packets, but the client interface refuses to show the CharacterSelectAddonsButton. The client code calls GetNumAddOns, but I'm not sure where this is pulling data from, and it's not sending any packets (using wow filter in Wireshark) other than the standard RealmList/auth procedure stuff.

The addons are loaded (roughly) correctly on loading into the world, so the issue is definitely something to do with the server response. Perhaps you need to load in correctly into the world and receive some kind of World message in order to initialise addons correctly on a new server?

https://gtker.com/wow_messages/docs/smsg_addon_info.html only seems to supported in 3.3.5, so I don't think that's the problem.

EDIT: It looks like the WOWW display filter in Wireshark doesn't work out of the box, so I imagine there's some packet I'm missing that might explain this.

Very nice project, I was looking for a rust lib to handle WoW SRP when I was playing around with Vmangos auth and stumbled across it!

STRUCT_MEMBER_ACCESS Add support for using variables inside other variables

In the Wrath MovementBlock the first few fields are identical to the fields of the MovementInfo.

Emulators send an actual MovementInfo but we have to duplicate the fields because we can't reach into the structs.

We want to be able to do

flag A : u8 {
	ONE = 1;
	TWO = 2;
}
flag B : u8 {
	FOUR = 4;
	EIGHT = 8;
}

struct T {
	A a;
}
struct Y {
	T t;
	if (t.a & ONE) {
		u8 one;
	}
}

It is not certain what the generated Rust could should look like.

Confusing message and structure field tags

Currently these tags are being used on fields of messages and structures, looks like they could be simplified and deduplicated.

Perhaps also a list of valid tags could be added and validated against?

  • maximum_length
    • used only in SMSG_PARTY_COMMAND_RESULT
    • used on CString
    • I assume it's str.len() <= tag
    • ❓ doesn't seem to be used in codegen (either validation of the field in particular or the read contains(&body_size) check)
  • max_length
    • used only in SMSG_GMRESPONSE_RECEIVED
    • used on Cstring and CString[4]
    • I assume it's str.len() <= tag
    • ❓ duplicate of maximum_length?
    • ❓ doesn't seem to be used in codegen (either validation of the field in particular or the read contains(&body_size) check)
  • maximum_value
    • used only in CMSG_WHO
    • used on u32
    • I assume it's val <= tag
    • ❓ doesn't seem to be used in codegen
  • maximum_valid_value
    • used only in SMSG_QUESTUPDATE_ADD_ITEM
    • used on u32
    • I assume it's value <= tag
    • ❓ duplicate of maximum_value?
    • ❓ doesn't seem to be used in codegen
  • minimum_valid_value
    • used only in SMSG_QUESTUPDATE_ADD_ITEM
    • used on u32
    • I assume it's value >= tag
    • ❓ doesn't seem to be used in codegen
  • minimum_legal_value
    • used only in CMSG_SPLIT_ITEM
    • used on u32
    • I assume it's val >= tag
    • ❓ duplicate of minimum_valid_value?
    • ❓ doesn't seem to be used in codegen
  • valid_range
    • used for several messages
    • used on u8, u32
    • I assume it's value >= tag[0] && value <= tag[1]
    • ❓ overlaps with the min/max value tags?
    • ❓ doesn't seem to be used in codegen
  • compressed
    • used for several messages
    • used on u8[-]
    • ❓ should be used (but isn't) on CMD_SURVEY_RESULT.data?

Arrays without comma separation

There is only one place in .wowm (as far as I can see) where array doesn't use comma separation

0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x00 /* name */
0xDE, 0xCA, 0xFA, 0x00, /* pet_name_timestamp */

The comma is listed as optional in https://github.com/gtker/wow_messages/blob/main/wow_message_parser/src/auth.pest#L91

I would propose that:

  • this array gets a comma, did that in #88
  • pest grammar is updated to force commas, it should be a backwards compatible change as comma was already optional before

Add tests for compressed messages

The compressed messages have unique control flow that could likely do with some thorough testing, and it would especially useful for downstream users.

It might make sense to have the "same" test for both compressed and uncompressed version of a message.

FLAG_DIFFERENT_VARIABLE Add support for flags inside flags

The Wrath MovementInfo requires

struct MovementInfo {
    MovementFlags flags;
    ExtraMovementFlags extra_flags;
    u32 timestamp;
    Vector3d position;
    f32 orientation;
    if (flags & ON_TRANSPORT) {
        TransportInfo transport;
        if (extra_flags & INTERPOLATED_MOVEMENT) { /* <-- This */
            u32 transport_time;
        }
    }

Test case is

flag A : u8 {
	ONE = 1;
	TWO = 2;
}
flag B : u8 {
	FOUR = 4;
	EIGHT = 8;
}

struct T {
	A a;
	B b;
	if (a & ONE) {
		if (b & FOUR) {
			u8 one_four;
		}
	}
}

It is unknown exactly what the example should look like in generated Rust code.

Incorrect 3.3.5 SMSG_ACCOUNT_DATA_TIMES parsing

For 3.3.5 SMSG_ACCOUNT_DATA_TIMES does not survive parse->encode round trip.

SMSG_ACCOUNT_DATA_TIMES::read_inner reads an an extra u32 (data_decompressed_size) before reading data.

let data = {
let mut current_size = {
4 // unix_time: u32
+ 1 // unknown1: u8
+ 4 // mask: CacheMask
};
current_size += 4; // data_decompressed_size: u32
let mut data = Vec::with_capacity(body_size as usize - current_size);
while current_size < (body_size as usize) {
data.push(crate::util::read_u32_le(&mut r)?);
current_size += 4;
}
data
};

The .wowm and also SMSG_ACCOUNT_DATA_TIMES::write_into_vec do not have this u32

smsg SMSG_ACCOUNT_DATA_TIMES = 0x0209 {
/// Seconds since Unix Epoch
u32 unix_time;
/// Both mangostwo and arcemu hardcode this to 1
u8 unknown1;
CacheMask mask;
/// Maximum size is 32 4-bit integers. For every bit that is 1 in the mask, write one u32 with the time
u32[-] data;
} {
versions = "3.3.5";
}

w.write_all(&(self.mask.as_int().to_le_bytes()))?;
// data: u32[-]
for i in self.data.iter() {
w.write_all(&i.to_le_bytes())?;
}

I think data_decompressed_size is incorrect, as I can't find it in other implementations and CacheMask::PerCharacterCache has 5 bits set, so data should have 5 elements (see comment for data in .wowm).

Here is a failing test case for this bug:

#[test]
fn reencode_smsg_account_data_times_char_mask() {
    use std::io::Cursor;
    use wow_world_messages::wrath::{
        expect_server_message, ServerMessage, SMSG_ACCOUNT_DATA_TIMES,
    };

    let original: Vec<u8> = vec![
        0x00, 0x1f, 0x09, 0x02, // packet header
        0xf3, 0xb3, 0x16, 0x65, // unix_time
        0x01, // unknown 1
        0xea, 0x00, 0x00, 0x00, // mask (5 bits set, so data should have 5 elements)
        0xe3, 0xaf, 0x16, 0x65, // data?, currently skipped as data_decompressed_size
        0x00, 0x00, 0x00, 0x00, // data
        0x00, 0x00, 0x00, 0x00, // data
        0x00, 0x00, 0x00, 0x00, // data
        0x4a, 0xa0, 0x15, 0x65, // data
    ];

    let msg = {
        let mut cursor = Cursor::new(&original);
        expect_server_message::<SMSG_ACCOUNT_DATA_TIMES, _>(&mut cursor).unwrap()
    };

    let reencoded = {
        let mut buf = Vec::new();
        msg.write_unencrypted_server(&mut buf).unwrap();
        buf
    };

    // err: original is 4 bytes longe than reencoded
    assert_eq!(original, reencoded);

    let reparsed = {
        let mut cursor = Cursor::new(&reencoded);
        expect_server_message::<SMSG_ACCOUNT_DATA_TIMES, _>(&mut cursor).unwrap()
    };

    // err: original.data.len() is 4 vs 3 for reparsed
    assert_eq!(msg, reparsed);
}

Add name lookup for items/spells

It would be handy to be able to look up items by name directly in the spells/items libraries.

The function should look something like:

fn lookup_item_by_name(name: &str) -> Vec<(String, &'static Item)>;

Create realmd executable

Love the library - lots of great work. Would you have any interest in creating an executable for realmd? I see the crate for wow_messages but as far as I can tell its just a library.

Looking to learn Rust so happy to contribute this if interested

WOTLK: ServerOpcodeMessage::read_encrypted causing a stack overflow

As the title says, with the wrath feature, I can't call ServerOpcodeMessage::read_encrypted, because that leads to a stack overflow exception and the problem is not(!) the control flow (as in endless recursion).

When inspecting with the debugger, we're just stuck at the gigantic match statements in read_opcodes.
When compiling in release, this is bypassable, as well as when using RUSTFLAGS= "-Clink-arg=/STACK:8000000" (8 MiB, I guess the default is 2MiB?).

Nevertheless, I took a glance using the following debug flag RUSTFLAGS = "-Zprint-type-sizes"

print-type-size type: `std::ops::ControlFlow<std::result::Result<std::convert::Infallible, errors::ParseError>, world::wrath::smsg_inspect_talent::SMSG_INSPECT_TALENT>`: 2880 bytes, alignment: 8 bytes
print-type-size     variant `Continue`: 2880 bytes
print-type-size         field `.0`: 2880 bytes
print-type-size     variant `Break`: 72 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 64 bytes, alignment: 8 bytes
print-type-size type: `std::result::Result<world::wrath::opcodes::ServerOpcodeMessage, errors::ExpectedOpcodeError>`: 2880 bytes, alignment: 8 bytes
print-type-size     variant `Ok`: 2880 bytes
print-type-size         field `.0`: 2880 bytes
print-type-size     variant `Err`: 72 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 64 bytes, alignment: 8 bytes
print-type-size type: `std::result::Result<world::wrath::smsg_inspect_talent::SMSG_INSPECT_TALENT, errors::ParseError>`: 2880 bytes, alignment: 8 bytes
print-type-size     variant `Ok`: 2880 bytes
print-type-size         field `.0`: 2880 bytes
print-type-size     variant `Err`: 72 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 64 bytes, alignment: 8 bytes
print-type-size type: `std::result::Result<world::wrath::smsg_inspect_talent::SMSG_INSPECT_TALENT, errors::ParseErrorKind>`: 2880 bytes, alignment: 8 bytes
print-type-size     variant `Ok`: 2880 bytes
print-type-size         field `.0`: 2880 bytes
print-type-size     variant `Err`: 48 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 40 bytes, alignment: 8 bytes
print-type-size type: `world::wrath::opcodes::ServerOpcodeMessage`: 2880 bytes, alignment: 8 bytes
print-type-size     variant `SMSG_INSPECT_TALENT`: 2880 bytes
print-type-size         field `.0`: 2880 bytes
print-type-size     variant `SMSG_PARTY_MEMBER_STATS`: 1672 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 1664 bytes, alignment: 8 bytes
print-type-size     variant `SMSG_PARTY_MEMBER_STATS_FULL`: 1672 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 1664 bytes, alignment: 8 bytes
print-type-size     variant `SMSG_NPC_TEXT_UPDATE`: 656 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 648 bytes, alignment: 8 bytes
print-type-size     variant `SMSG_QUEST_QUERY_RESPONSE`: 608 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 600 bytes, alignment: 8 bytes
print-type-size     variant `SMSG_ACTION_BUTTONS`: 580 bytes
print-type-size         padding: 2 bytes
print-type-size         field `.0`: 578 bytes, alignment: 2 bytes
print-type-size     variant `SMSG_ITEM_QUERY_SINGLE_RESPONSE`: 544 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 536 bytes, alignment: 8 bytes
print-type-size     variant `SMSG_TRADE_STATUS_EXTENDED`: 536 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 528 bytes, alignment: 8 bytes
print-type-size     variant `SMSG_GUILD_QUERY_RESPONSE`: 296 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 288 bytes, alignment: 8 bytes
print-type-size     variant `SMSG_QUESTGIVER_QUEST_DETAILS`: 288 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 280 bytes, alignment: 8 bytes
print-type-size     variant `SMSG_QUESTGIVER_OFFER_REWARD`: 256 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 248 bytes, alignment: 8 bytes
print-type-size     variant `SMSG_GAMEOBJECT_QUERY_RESPONSE`: 248 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 240 bytes, alignment: 8 bytes

I've cut the majority of the really verbose output, but since inspect talent is 2.8kB, that's apparently the stack allocated size for that match/arm? It somehow needs to accumulate more, but in general maybe we can do something about how the match statements are designed? If we're commonly pushing hundreds of bytes, it may be worth to box the result? that way the match only needs to prepare usize bytes for the result type, then we'd still have pushing the data from read_body.

Actually, I have no idea on how to analyze this further, though.

SKIP_SERIALIZE_READ_PANIC `skip_serialize` read implementations panic!

The Wrath SMSG_ADDON_INFO structure relies on external knowledge to parse the correct amount of Addons.

smsg SMSG_ADDON_INFO = 0x2EF {
    u32 number_of_addons {
        skip_serialize = "true";
    }
    Addon[number_of_addons] addons;
    u32 number_of_banned_addons = 0;
}

To work around this, the skip_serialize tag is used on a dummy variable that tells it how many Addons to send. This works fine for sending from the server, since the rust code just sends as many Addons as are in the Vec<Addon> but it does not work for reading the message.

Since the generated message is never valid it has instead been replaced by a panic.

A way should be found that removes the panic either through removing the ability to read SMSG_ADDON_INFO or finding a workaround to providing the knowledge of how many Addons. The amount of Addons to parse is the same as in CMSG_AUTH_SESSION.

`SMSG_ACCOUNT_DATA_TIMES` for 3.3.5 should not have `u32[-]`

CacheMask mask;
/// Maximum size is 32 4-bit integers. For every bit that is 1 in the mask, write one u32 with the time
u32[-] data;

The comment states "For every bit that is 1 in the mask, write one u32 with the time", so it should really be u32[mask.count_ones()], I don't think wowm currently supports such array length description, but maybe should be added?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.