Giter Site home page Giter Site logo

kotcrab / ghidra-allegrex Goto Github PK

View Code? Open in Web Editor NEW
80.0 9.0 6.0 469 KB

Ghidra processor module adding support for the Allegrex CPU (PSP)

License: Apache License 2.0

Java 26.41% Kotlin 73.06% Python 0.53%
ghidra ghidra-plugins psp ghidra-plugin ghidra-extension

ghidra-allegrex's Introduction

ghidra-allegrex

Ghidra processor module adding support for the Allegrex CPU used in the PlayStation Portable.

Features:

  • Support for PSP specific ELF relocation sections (type A and B)
    • Image rebase after loading is also supported
  • Support for Allegrex specific instructions
  • Processor type auto-detection for ELF files
  • PSP calling convention support
  • Disassembly and decompilation of VFPU instructions (see limitations bellow)
  • Scripts for importing and exporting PPSSPP .sym files (function labels)
  • Ghidra Debugger can be used to debug games running in PPSSPP (beta)

Installation

Download prebuilt package from the Releases section. Select release which matches your Ghidra version. After extracting copy the Allegrex directory into GHIDRA_INSTALL_DIR/Ghidra/Processors.

Usage

Games

Drag decrypted EBOOT in ELF/PRX format into Ghidra. It should get automatically detected as PSP Executable (ELF) / Allegrex. Now is your chance to set initial base address by clicking Options... and changing Image Base. It's recommend you set it to 08804000 to match the usual address where games are loaded.

After importing and opening the file you should do the auto analysis. Default options are fine.

Using PPSSPP .sym scripts

PPSSPP identifies many functions automatically, it's useful to get those into Ghidra after doing the initial analysis. Export the .sym file from PPSSPP and in Ghidra run script PpssppImportSymFile. Select the .sym file. Enter 0 when asked for offset if your image base is already at 08804000. It's usually fine to run this script after you've started renaming functions in the binary. The script by default skips unknown names from PPSSPP so your work can only get overwritten if you've renamed one of the autodetected function.

Likewise, you can use PpssppExportSymFile to export your work as a .sym file which can be imported into PPSSPP. Enter 0 when asked for offset if your image base is already at 08804000. You need to do Reset symbol table before importing the file in PPSSPP.

Kernel modules

Since version 1.7 relocations found in kernel modules are supported. Usage is very similar as when importing games though kernel modules are usually loaded starting from address 88000000. Note that for some files (e.g. sysmem, loadcore) you will need to click Options... during import and select option to use reboot.bin type B relocation mapping.

Raw binaries

Raw binaries are also supported. In that case you will need to manually select Allegrex as the processor and set image base.

PPSSPP debugger integration

Since version 1.9 Ghidra Debugger can be used to debug games running in PPSSPP over the WebSocket API. To get started open PPSSPP and make sure "Allow remote debugger" is enabled in PPSSPP settings. Then open your binary using the Debugger tool and in the Debugger Targets panel press the Connect button. Select PPSSPP WebSocket debugger (beta) and press Connect. See Ghidra's built-in help to learn more about the debugger features.

Tips:

  • To enable automatic mapping between static and dynamic listing you must make sure the binary file name in Ghidra matches exactly the module name from PPSSPP. Module name is visible in the Modules panel and the binary can be renamed in the Ghidra project window.

VFPU Limitations

  • Decompilation support is rather basic, almost every operation is converted to a function call such as vadd_q(...)
    • Semantics of vpfxs, vpfxt and vpfxd are not currently modeled
  • Second operand of vfim.s will be shown as an integer, should be shown as a half float. Sleigh does not support float tokens.

Building

GHIDRA_INSTALL_DIR environment variable must be set to Ghidra root installation directory.

  • ./gradlew ghidraInstall - build and install into Ghidra (warning: contents of GHIDRA_INSTALL_DIR/Ghidra/Processors/Allegrex will be deleted before installing)
  • ./gradlew ghidraInstallThenRun - run ghidraInstall task then start Ghidra, useful for development
  • ./gradlew ghidraInstallThenDebug - run ghidraInstall task then start Ghidra in debug mode, useful for development
  • ./gradlew ghidraInstallThenPackage - run ghidraInstall task then create release zip
  • ./gradlew shadowJar - create single library jar file with all external dependencies included

After running ./gradlew shadowJar you can manually install extension by copying:

  • build/libs/ghidra-allegrex-all.jar file to GHIDRA_INSTALL_DIR/Ghidra/Processors/Allegrex/lib/Allegrex.jar
  • data and ghidra_scripts directories to GHIDRA_INSTALL_DIR/Ghidra/Processors/Allegrex/

Ghidra should automatically recompile Sleigh files when importing an executable, if not run:

/ghidra_x.x.x/support$ ./sleigh -a ../Ghidra/Processors/Allegrex/data/languages/

License

Licensed under Apache License 2.0.

Derived from Ghidra MIPS module licensed under Apache License 2.0.

Type B relocation parsing based on prxtool licensed under AFL v2.0.

See also

  • psp-ghidra-scripts - A collection of scripts to aid in reverse engineering PSP binaries in Ghidra.

ghidra-allegrex's People

Contributors

artart78 avatar cb9001 avatar illusion0001 avatar kieronj avatar kotcrab 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ghidra-allegrex's Issues

Major release: Ghidra 11.0

Maybe it's also time to change the string /ghidra_10.x.x/support$ ./sleigh -a ../Ghidra/Processors/Allegrex/data/languages/?

Bump the version to 10.3.2

I hope that's all what needs to be done, of course. No major changes between the smaller releases and stuff...

Investigate debugger integration with psplink gdbserver

Ghidra Debugger has built-in support for GDB so it should just work assuming psplink has support for all the required features. The only additional thing this plugin could provide is the mapping offer for using Allegrex instead of built in MIPS for dynamic disassembly.

First step is to investigate if Ghidra can connect to psplink gdbserver.

Response to #15 (comment) ping @sajattack

Export to ELF unsupported

When trying to export to an ELF for using with psp-gdb for adding debugging symbols, Ghidra states that the PSP Executable is an unsupported format.

Screenshot 2023-04-02 at 12 35 25 pm

marster branch build failed, cannot find symbol

K:\ghidra-allegrex\src\main\java\ghidra\app\plugin\core\analysis\AllegrexPreAnalyzer.java:21: error: cannot find symbol
import ghidra.app.util.InstructionSpaceTest;
                      ^
  symbol:   class InstructionSpaceTest
  location: package ghidra.app.util
K:\ghidra-allegrex\src\main\java\ghidra\app\plugin\core\analysis\AllegrexPreAnalyzer.java:66: error: cannot find symbol
        new InstructionSpaceTest(program);

maybe miss InstructionSpaceTest in commit.

Binary with debug symbols is oddly imported

The game "Yu-Gi-Oh! Duel Monsters GX: Tag Force" (goes by ULJM-05151) was NOT stripped of the debug info before release (quite unusual, if you ask me).

I tried loading this in Ghidra, but it just didn't go well. The symbols were parsed correctly and I can see them in the functions view, but the code is just.... gone. It's the .text section, why is there data defined? Something's extremely odd with that.

image

The section that contains NIDs is totally busted...
image

And one more, for good measure...
image

Question... Who is here to blame, the Ghidra ELF loader or the plugin? Maybe it's worth reporting to the main app devs?

Broken relocation in Pop'n_Music_Portable (Japanese)

For some reason the Image base parameter is 0000000 when I load the Eboot.bin binary. This is not fatal, but I think it causes Ghidra to assume that a lot of functions reference the data (when, in fact, they reference the code) => the analyzer creates the data in the middle of the code segments so when the time comes to disassemble the region, it fails. I ended up with lots of partially disassembled funcs which no one can work with. You have to manually clear the data, then disassemble again, then recreate the function again.
Setting up the base to 0x08804000 doesn't help as now it breaks the calls:
image
I say something is wrong with the way someone handles relocation. My initial guess is that I should check if this plugin is to blame.
I'd like to know what can be done with this.

NoClassDefFoundError: MessageLogContinuesFactory at PspElfLoader.load(PspElfLoader.kt:67)

ghidra/app/util/importer/MessageLogContinuesFactory
java.lang.NoClassDefFoundError: ghidra/app/util/importer/MessageLogContinuesFactory
at ghidra.app.util.opinion.PspElfLoader.load(PspElfLoader.kt:67)
at ghidra.app.util.opinion.AbstractLibrarySupportLoader.doLoad(AbstractLibrarySupportLoader.java:369)
at ghidra.app.util.opinion.AbstractLibrarySupportLoader.loadProgram(AbstractLibrarySupportLoader.java:83)
at ghidra.app.util.opinion.AbstractProgramLoader.load(AbstractProgramLoader.java:119)
at ghidra.plugin.importer.ImporterUtilities.importSingleFile(ImporterUtilities.java:368)
at ghidra.plugin.importer.ImporterDialog.lambda$okCallback$7(ImporterDialog.java:351)
at ghidra.util.task.TaskBuilder$TaskBuilderTask.run(TaskBuilder.java:306)
at ghidra.util.task.Task.monitoredRun(Task.java:134)
at ghidra.util.task.TaskRunner.lambda$startTaskThread$0(TaskRunner.java:106)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.ClassNotFoundException: ghidra.app.util.importer.MessageLogContinuesFactory
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
... 12 more


Build Date: 2022-Sep-04 1758 CEST
Ghidra Version: 10.2
Java Home: /opt/openjdk-bin-17.0.3_p7
JVM Version: Eclipse Adoptium 17.0.3
OS: Linux 5.19.6-gentoo amd64

Bundle compiled sla spec with the release zip

There are apparently some issues with relaying on Ghidra to recompile allegrex.sla on first launch so we should bundle already compiled one with the release zip. Not sure if this will be needed after #8.

Upgrade to Ghidra 10.3

Is the plugin compatible with the newest Ghidra release?
If not, will this be hard to make a new version like how it happened last time?

Allegrex plugin does not import some retail games that have debug symbols.

image
This happened earlier when i tried to important Adventure Player. Can't seem to recreate for some reason, but i'm still getting similar, but smaller, error messages when i try to import most games with what I'm told are "unstripped binaries":

image
image
image
image
Happens for:
-Armored Core
-Adventure Player
-Big Bang Bang
-Demo Disc
-Astonishia
-Puzzle Bobble
-Glorace
-Rengoku
-Sampler

THREE EXCEPTIONS THOUGH: It manages to import Yu Gi Oh, Jan, and Jikan successfully, but they spit out some "Additional Information"

image
image
image

Toggling breakpoints causes exception in Ghidra

I don't know if this is due to my version of Ghidra, but the in addition to this issue module mapping seems a bit flaky. Module not appearing in the modules debug window from time to time.

Cannot invoke "ghidra.program.model.address.AddressRange.getMinAddress()" because "range" is null
java.lang.NullPointerException: Cannot invoke "ghidra.program.model.address.AddressRange.getMinAddress()" because "range" is null
	at ghidra.app.plugin.core.debug.service.breakpoint.LogicalBreakpointInternal$TraceBreakpointSet.planDelete(LogicalBreakpointInternal.java:417)
	at ghidra.app.plugin.core.debug.service.breakpoint.MappedLogicalBreakpoint.planDelete(MappedLogicalBreakpoint.java:264)
	at ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin.lambda$deleteAll$12(DebuggerLogicalBreakpointServicePlugin.java:1172)
	at ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin.actOnAll(DebuggerLogicalBreakpointServicePlugin.java:1137)
	at ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin.deleteAll(DebuggerLogicalBreakpointServicePlugin.java:1167)
	at ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointMarkerPlugin$ClearBreakpointAction.actionPerformed(DebuggerBreakpointMarkerPlugin.java:665)
	at docking.PopupMenuHandler$1.run(PopupMenuHandler.java:63)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

---------------------------------------------------
Build Date: 2023-Feb-08 1242 EST
Ghidra Version: 10.2.3
Java Home: /opt/homebrew/Cellar/openjdk/19.0.2/libexec/openjdk.jdk/Contents/Home
JVM Version: Homebrew 19.0.2
OS: Mac OS X 13.2.1 aarch64

long long value decompile

as return value the register order is v1 v0 should modify allegrex.cspec line 58 like this

                <pentry minsize="5" maxsize="8">
                    <addr space="join" piece1="v1" piece2="v0"/>
                </pentry>

and as parameter like SceOff sceIoLseek(SceUID fd, SceOff offset, int whence) the parameter storage link this

fd a0
offset a3, a2
whence t0

but I don't known how to modify cspec, can use custom storage in edit function

Rebasing image after importing can fail

There are binaries for which rebasing the image after importing is not working correctly.

To reproduce:

  1. Load ULJM06081 at address 0x0
  2. Try to rebase to 0x8804000 (Memory Map -> Set Image Base)
  3. Some relocations won't be updated correctly

Note: Setting ULJM06081 image base to 0x8804000 during initial import produces correct results.

Improving the decompilation

Quoting kotcrab:

Right now the writes to VFPU control registers are modelled without side-effects

This causes the following incorrect decompilation results:
image

Next, there are 2 very cruel Libc functions setjmp and longjmp.

The only issue I have with the decompilation of setjmp is the name register0x00000074 for sp.

image

The function longjmp breaks the calling convention by overwriting the preserved regs, but Ghidra silently swallows the error (generally speaking, Ghidra often leaves out the stack operations from the decompilation). I certainly don't wanna miss anything and I even have Eliminate unreachable code disabled by default, but here it doesn't help.

image

I hope fixing the VFPU and the sp name is easy. I really hope something can be done with longjump too, but I'm afraid the calling convention is to blame.

Misc issues with PPSSPP debugger integration

  • Target is already being recorded when transitioning between stepping and running states. Needs investigating, could be an issue in Ghidra.
  • Current frame's bank does not exist when clicking through stack frames, PPSSPP doesn't expose such information. Some workaround needed, maybe we just need to provide an empty register bank
  • Step out error is ignored - e.g. when there is no more stack frames. Ghidra steps are async so not much can be done about this, it just might be confusing to the user
  • Can't trigger breakpoint hit events - will be available thanks to hrydgard/ppsspp#15051

Missing SPECIAL instructions lead to halt in decompilation

Disassembly when loading sysmem.prx with ghidra-allegrex:

                             LAB_0000087c                                    XREF[1]:     00000848(j)  
        0000087c c0 14 62 7f     ext        v0,k1,0x13,0x3
        00000880 23 10 02 00     subu       v0,zero,v0
        00000884 24 10 45 00     and        v0,v0,a1
        00000888 f9 ff 40 04     bltz       v0,LAB_00000870
        0000088c 00 e0 02 24     _li        v0,-0x2000
        00000890 04 28 04 7c     ins        a0,zero,0x0,0x6
        00000894 23 28 a4 00     subu       a1,a1,a0
        00000898 24              ??         24h    $
        00000899 00              ??         00h
        0000089a 03              ??         03h
        0000089b 70              ??         70h    p
        0000089c 21              ??         21h    !
        0000089d 40              ??         40h    @
        0000089e 40              ??         40h    @
        0000089f 00              ??         00h
        000008a0 26              ??         26h    &
        000008a1 00              ??         00h
        000008a2 00              ??         00h
        000008a3 70              ??         70h    p
                             DAT_000008a4                                    XREF[1]:     0000cf40(R)  
        000008a4 40 00 42 24     undefined4 24420040h

After forcing decompilation I get:

                             LAB_0000087c                                    XREF[1]:     00000848(j)  
        0000087c c0 14 62 7f     ext        v0,k1,0x13,0x3
        00000880 23 10 02 00     subu       v0,zero,v0
        00000884 24 10 45 00     and        v0,v0,a1
        00000888 f9 ff 40 04     bltz       v0,LAB_00000870
        0000088c 00 e0 02 24     _li        v0,-0x2000
        00000890 04 28 04 7c     ins        a0,zero,0x0,0x6
        00000894 23 28 a4 00     subu       a1,a1,a0
        00000898 24              ??         24h    $
        00000899 00              ??         00h
        0000089a 03              ??         03h
        0000089b 70              ??         70h    p
        0000089c 21 40 40 00     move       t0,v0
        000008a0 26 00 00 70     mtic
                             DAT_000008a4                                    XREF[1]:     0000cf40(R)  
        000008a4 40 00 42 24     undefined4 24420040h

Disassembly when loading sysmem.prx with MIPS 32 bits little endian:

                             LAB_0000087c                                    XREF[1]:     00000848(j)  
        0000087c c0 14 62 7f     ext        v0,k1,0x13,0x3
        00000880 23 10 02 00     subu       v0,zero,v0
        00000884 24 10 45 00     and        v0,v0,a1
        00000888 f9 ff 40 04     bltz       v0,LAB_00000870
        0000088c 00 e0 02 24     _li        v0,-0x2000
        00000890 04 28 04 7c     ins        a0,zero,0x0,0x6
        00000894 23 28 a4 00     subu       a1,a1,a0
        00000898 24 00 03 70     SPECIAL2   zero,zero,v1,0x0,0x24
        0000089c 21 40 40 00     move       t0,v0
        000008a0 26 00 00 70     SPECIAL2   zero,zero,zero,0x0,0x26
                             LAB_000008a4                                    XREF[1]:     sceSuspendForKernel_0AB0C6F3:000
        000008a4 40 00 42 24     addiu      v0,v0,0x40

Even though MIPS disassembler doesn't understand these "SPECIAL" instructions, it decompiles them seeing them as instructions. This way, the function can be decompiled to pseudo-code.

With ghidra-allegrex, the pseudo-code fails to decompile the function:

// ...
    return 0;
  }
  if ((int)(-((uint)(in_k1 << 10) >> 0x1d) & param_2) < 0) {
    return 0x80000104;
  }
                    /* WARNING: Bad instruction - Truncating control flow here */
  halt_baddata();
}

When loading with MIPS 32-bits little endian:

// ...
    return 0;
  }
  if ((int)(-((uint)(in_k1 << 10) >> 0x1d) & param_2) < 0) {
    return 0x80000104;
  }
  special2(0,in_v1,0,0x24);
  iVar2 = -0x2000;
  do {
    special2(0,0,0,0x26);
    iVar1 = iVar2 + 0x40;
    cacheOp(0x10,iVar2 + 0x2000);
    special2(0,in_v1,0,0x26);
// ...

Try to recover sections from segment only binaries

A proper issue for the suggestion I filed in issue #28, I'll copy-paste for convenience:

"Would also be cool if we could recover the NID related sections (.lib.ent, .lib.stub, .rodata.sceModuleInfo, .rodata.sceResident and .rodata.sceNid) so we can use the NIDresolver script on these programs too.

Recovering any other section (say .rodata, .ctors, .dtors, .eh_frame etc) would be entirely optional"

Now, I gathered some info that can be useful for this task, so bear with me:
The easiest section to recover would be .rodata.sceModuleInfo, because it's address is set in segment's 0 p_paddr field and it always has this structure:

// size is 52 bytes (decimal)
struct PspModuleInfo {
    uint flags;
    char name[28];
    void * gp;
    void * exports;
    void * exp_end;
    void * imports;
    void * imp_end;
};

With this info we can figure where are .lib.ent and .lib.stub too, the field exports points to the start .lib.ent and the field exp_end points to the end of it, similarly, the field imports points to the start of lib.stub and imp_end points to the end of it.

Strictly speaking, both .lib.ent and lib.stub are surrounded by a small 4 byte section that delimits the top and bottom, these marker sections append a .top or .btm at the end of the respective parent sections, like so:

.lib.ent.top  (size: 4bytes)
.lib.ent      (variable size)
.lib.ent.btm  (size: 4bytes)
.lib.stub.top (size: 4bytes)
.lib.stub     (variable size)
.lib.stub.btm (size: 4bytes)

but not sure if the top and btm section recovery is very worth.

Anyway, .lib.ent has the exports info and lib.stub has the imports info, they are basically an array of the following structs:

// for lib.ent - size is 16 (decimal)
struct PspModuleExport {
    char * name;
    uint flags;
    uchar entry_len;
    uchar var_count;
    ushort func_count;
    uint * exports;
};

// for lib.stub - size is 20 (decimal)
struct PspModuleImport {
    char * name;
    uint flags;
    uchar entry_size;
    uchar var_count;
    ushort func_count;
    uint * nids;
    uint * funcs;
};

After those we are only missing .rodata.sceResident and .rodata.sceNid, which would need some parsing to get:
For .rodata.sceNid we need to parse .lib.stub, if we sum var_count and func_count for each element of .lib.stub get the size (in ints) of .rodata.sceNid and the start of it it's the lowest value of the field nids

Lastly, .rodata.sceResident, this one has 2 parts:

  • 1st, an array for the imports, which is a structure of an uint followed by a null terminated string/char array (with padding zeros so it's 4byte aligned), the array item count is the same item count as the array for .rodata.sceNid
  • 2nd, values for the exports, which are defined in the .lib.ent, summing var_count and func_count multiplying by 2 we get the size (in ints) for this part
    the start of .rodata.sceResident would be the lowest address in the name field of .lib.stub minus 4

Anyway, some other sections can probably be figured out too (like .sceStub.text) but I guess these are the more useful ones, sorry for the long issue text :P
I must also say that in the case of Danganronpa 2 the segments were kinda in order, but I guess we can't trust that to be case for every game.

Integrate with PPSSPP debugger

Ghidra supports debugging now, it would be interesting to integrate this with PPSSPP debugger using PPSSPP websocket API. My initial experiments shows it's possible but will be quite a lot of work to properly support all of the debugger features.

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.