├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.sh
├── package-lock.json
├── package.json
├── patches
└── scratch-vm+2.1.46.patch
├── src
├── build
│ ├── noop-loader.js
│ ├── noop-module
│ │ └── index.js
│ ├── scratch-vm
│ │ ├── engine
│ │ │ └── tw-font-manager.js
│ │ ├── extension-support
│ │ │ └── extension-manager.js
│ │ ├── import
│ │ │ ├── load-costume.js
│ │ │ └── load-sound.js
│ │ ├── io
│ │ │ ├── clock.js
│ │ │ ├── cloud.js
│ │ │ ├── keyboard.js
│ │ │ ├── mouse.js
│ │ │ ├── mouseWheel.js
│ │ │ ├── userData.js
│ │ │ └── video.js
│ │ ├── serialization
│ │ │ ├── deserialize-assets.js
│ │ │ ├── serialize-assets.js
│ │ │ └── tw-costume-import-export.js
│ │ └── util
│ │ │ └── log.js
│ └── text-encoding
│ │ └── index.js
├── index.js
└── kattio.js
├── tests
├── aplusb.sb
├── aplusb.sb2
├── aplusb.sb3
├── echo.json
├── echo.sb3
├── invalid.sb3
├── music_extension.sb3
├── permutation.sb3
├── say_think.sb3
├── stopall.sb3
├── sum_1ton.sb3
└── test.py
└── webpack.config.js
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | pull_request:
6 | release:
7 | types: [published]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [20.x]
16 | python-version: ['3.11']
17 |
18 | steps:
19 | - uses: actions/checkout@v4
20 | - name: Set up Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v4
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 | - name: Set up Python ${{ matrix.python-version }}
25 | uses: actions/setup-python@v5
26 | with:
27 | python-version: ${{ matrix.python-version }}
28 | - name: Compile ldid
29 | run: |
30 | sudo apt install -y libplist-dev
31 | git clone git://git.saurik.com/ldid.git
32 | g++ -pipe -o ldid/ldid ldid/ldid.cpp -I. -x c ldid/lookup2.c -lcrypto -lplist -Os -fwhole-program -flto -s
33 | echo "${{github.workspace}}/ldid" >> $GITHUB_PATH
34 | - run: npm ci
35 | - run: npm run build
36 | - run: python tests/test.py
37 | - name: Upload to Artifacts
38 | uses: actions/upload-artifact@v4
39 | with:
40 | name: Artifacts
41 | path: bin/*.zip
42 | - name: Upload to Release
43 | if: github.event_name == 'release'
44 | uses: svenstaro/upload-release-action@v2
45 | with:
46 | repo_token: ${{ secrets.GITHUB_TOKEN }}
47 | file: bin/*.zip
48 | tag: ${{ github.ref }}
49 | file_glob: true
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bin
3 | dist
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # scratch-run changelog
2 |
3 | ### v0.1.5
4 |
5 | - Fix `fs.readSync` potentially throwing EOF error on Windows
6 |
7 | ### v0.1.4
8 |
9 | - Patch compiler to squeeze more performance
10 |
11 | ### v0.1.3
12 |
13 | - Fix infinite loop when `control.stopAll` is used
14 | - Add argument `--print-generated-js` for printing generated JavaScript code
15 |
16 | ### v0.1.2
17 |
18 | - Patch compiler to squeeze more performance
19 |
20 | ### v0.1.1
21 |
22 | - Fix `--version` not printing version
23 |
24 | ### v0.1.0
25 |
26 | - Add argument `--buffer-stdout`
27 | - Use [TurboWarp/scratch-vm](https://github.com/TurboWarp/scratch-vm)
28 | - Patch `scratch-vm` to remove unused functionality
29 | - Significantly reduce bundle size
30 |
31 | As a result, `scratch-run` nows runs much faster while consuming less memory.
32 |
33 | ### v0.0.11
34 |
35 | - Use Node 16.16.0
36 |
37 | ### v0.0.10
38 |
39 | - Use custom Queue for lines and ask_queue
40 | - Hack to speeding up `vm.runtime._step` calls
41 |
42 | Thank @quangloc99 for the idea and implementation.
43 |
44 | ### v0.0.9
45 |
46 | - Use Node 16.14.2
47 | - Block extensions (e.g., music)
48 |
49 | ### v0.0.8
50 |
51 | - Use Node 16.13.2
52 | - Fix bug when using block Think with a number
53 |
54 | ### v0.0.7
55 |
56 | - Use Node 16.13.0
57 | - Add binaries for arm64
58 |
59 | #### v0.0.6
60 |
61 | - Add argument `--check` for validating Scratch file but not running.
62 |
63 | #### v0.0.5
64 |
65 | - Do not print newline character for Think block
66 | - Print error to stderr
67 | - Fix error `Cannot find module 'text-encoding'`
68 |
69 | #### v0.0.4
70 |
71 | - Minify code with ncc before passing to pkg for compiling
72 | - Correctly check whitespace characters (space, horizontal tab, vertical tab, form feed)
73 |
74 | #### v0.0.3
75 |
76 | - Disable bytecode generation
77 |
78 | #### v0.0.2
79 |
80 | - Switch to `readline` as `readline-sync` does not work inside sandbox.
81 | - Default to reading line by line. To read token by token, set the question for the `Ask () and Wait` block to `read_token`.
82 |
83 | #### v0.0.1
84 |
85 | - Initial release
86 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scratch-run [](https://github.com/VNOI-Admin/scratch-run/actions/)
2 |
3 | scratch-run is a CLI interpreter for Scratch based on [scratch-vm](https://github.com/TurboWarp/scratch-vm).
4 |
5 | scratch-run was created to judge solutions written in Scratch. It is used mainly in our official online judge [VNOJ](https://github.com/VNOI-Admin/OJ), but it can also be used separately.
6 |
7 | scratch-run is written in Node.js and packed with [pkg](https://github.com/vercel/pkg). No dependencies are required for running.
8 |
9 | ## Installation
10 |
11 | Prebuilt binaries are available in [Releases](https://github.com/VNOI-Admin/scratch-run/releases).
12 |
13 | ## Usage
14 |
15 | ```bash
16 | scratch-run [scratch file]
17 | ```
18 |
19 | For example:
20 |
21 | ```bash
22 | scratch-run tests/echo.sb3
23 | ```
24 |
25 | Type in something and it will be echoed back!
26 |
27 | For more detailed instructions, see our [wiki page](https://github.com/VNOI-Admin/scratch-run/wiki).
28 |
29 | ## Build Instructions
30 |
31 | You need Node.js and npm to build.
32 |
33 | ```bash
34 | git clone https://github.com/VNOI-Admin/scratch-run.git
35 | cd scratch-run
36 | npm install
37 | npm run build
38 | ```
39 |
40 | Built binaries will be saved in `build` directory.
41 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | NODE_VERSION=16.16.0
4 |
5 | VERSION=$(node -p -e "require('./package.json').version")
6 | BUILD_CMD="npx pkg ../dist/index.js"
7 |
8 | rm -rf bin dist
9 | npx webpack
10 |
11 | mkdir bin
12 | cd bin
13 |
14 | # Linux amd64
15 | $BUILD_CMD -t node"$NODE_VERSION"-linux-x64 --out-path linux-amd64
16 | cd linux-amd64
17 | mv index scratch-run
18 | zip "../scratch-run_"$VERSION"_linux_amd64.zip" scratch-run
19 | cd ..
20 |
21 | # Linux arm64
22 | $BUILD_CMD -t node"$NODE_VERSION"-linux-arm64 --out-path linux-arm64
23 | cd linux-arm64
24 | mv index scratch-run
25 | zip "../scratch-run_"$VERSION"_linux_arm64.zip" scratch-run
26 | cd ..
27 |
28 | # macOS amd64
29 | $BUILD_CMD -t node"$NODE_VERSION"-macos-x64 --out-path macos-amd64
30 | cd macos-amd64
31 | mv index scratch-run
32 | zip "../scratch-run_"$VERSION"_macos_amd64.zip" scratch-run
33 | cd ..
34 |
35 | # macOS arm64
36 | $BUILD_CMD -t node"$NODE_VERSION"-macos-arm64 --out-path macos-arm64
37 | cd macos-arm64
38 | mv index scratch-run
39 | zip "../scratch-run_"$VERSION"_macos_arm64.zip" scratch-run
40 | cd ..
41 |
42 | # Windows amd64
43 | $BUILD_CMD -t node"$NODE_VERSION"-win-x64 --out-path win-amd64
44 | cd win-amd64
45 | mv index.exe scratch-run.exe
46 | zip "../scratch-run_"$VERSION"_win_amd64.zip" scratch-run.exe
47 | cd ..
48 |
49 | # Windows arm64
50 | $BUILD_CMD -t node"$NODE_VERSION"-win-arm64 --out-path win-arm64
51 | cd win-arm64
52 | mv index.exe scratch-run.exe
53 | zip "../scratch-run_"$VERSION"_win_arm64.zip" scratch-run.exe
54 | cd ..
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scratch-run",
3 | "version": "0.1.5",
4 | "description": "Run Scratch from command line",
5 | "main": "./index.js",
6 | "scripts": {
7 | "start": "node ./index.js",
8 | "build": "./build.sh",
9 | "test": "python tests/test.py",
10 | "postinstall": "patch-package"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/VNOI-Admin/scratch-run.git"
15 | },
16 | "keywords": [
17 | "scratch"
18 | ],
19 | "author": "Le Bao Hiep ",
20 | "license": "agpl-3.0",
21 | "bugs": {
22 | "url": "https://github.com/VNOI-Admin/scratch-run/issues"
23 | },
24 | "homepage": "https://github.com/VNOI-Admin/scratch-run#readme",
25 | "dependencies": {
26 | "fastestsmallesttextencoderdecoder": "^1.0.22",
27 | "minimist": "^1.2.8",
28 | "scratch-vm": "git+https://github.com/TurboWarp/scratch-vm.git#e4e461ac84b1d61a3e7468aa6dbc8b56a2ece35e"
29 | },
30 | "devDependencies": {
31 | "esbuild-loader": "^4.0.2",
32 | "patch-package": "^8.0.0",
33 | "pkg": "^5.8.1",
34 | "webpack": "4.47.0",
35 | "webpack-bundle-analyzer": "^4.10.1",
36 | "webpack-cli": "4.10.0"
37 | },
38 | "overrides": {
39 | "webpack@4.47.0": {
40 | "terser-webpack-plugin": "^4.2.3"
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/patches/scratch-vm+2.1.46.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/scratch-vm/src/compiler/compat-blocks.js b/node_modules/scratch-vm/src/compiler/compat-blocks.js
2 | index 1c9d8f5..67f2221 100644
3 | --- a/node_modules/scratch-vm/src/compiler/compat-blocks.js
4 | +++ b/node_modules/scratch-vm/src/compiler/compat-blocks.js
5 | @@ -8,12 +8,8 @@
6 | const stacked = [
7 | 'looks_changestretchby',
8 | 'looks_hideallsprites',
9 | - 'looks_say',
10 | - 'looks_sayforsecs',
11 | 'looks_setstretchto',
12 | 'looks_switchbackdroptoandwait',
13 | - 'looks_think',
14 | - 'looks_thinkforsecs',
15 | 'motion_align_scene',
16 | 'motion_glidesecstoxy',
17 | 'motion_glideto',
18 | @@ -21,7 +17,6 @@ const stacked = [
19 | 'motion_pointtowards',
20 | 'motion_scroll_right',
21 | 'motion_scroll_up',
22 | - 'sensing_askandwait',
23 | 'sensing_setdragmode',
24 | 'sound_changeeffectby',
25 | 'sound_changevolumeby',
26 | diff --git a/node_modules/scratch-vm/src/compiler/irgen.js b/node_modules/scratch-vm/src/compiler/irgen.js
27 | index c2cf19d..1ea3813 100644
28 | --- a/node_modules/scratch-vm/src/compiler/irgen.js
29 | +++ b/node_modules/scratch-vm/src/compiler/irgen.js
30 | @@ -1147,6 +1147,26 @@ class ScriptTreeGenerator {
31 | kind: 'timer.reset'
32 | };
33 |
34 | + case 'looks_say':
35 | + case 'looks_sayforsecs':
36 | + return {
37 | + kind: 'looks.say',
38 | + message: this.descendInputOfBlock(block, 'MESSAGE')
39 | + }
40 | +
41 | + case 'looks_think':
42 | + case 'looks_thinkforsecs':
43 | + return {
44 | + kind: 'looks.think',
45 | + message: this.descendInputOfBlock(block, 'MESSAGE')
46 | + }
47 | +
48 | + case 'sensing_askandwait':
49 | + return {
50 | + kind: 'sensing.ask',
51 | + question: this.descendInputOfBlock(block, 'QUESTION')
52 | + };
53 | +
54 | default: {
55 | const opcodeFunction = this.runtime.getOpcodeFunction(block.opcode);
56 | if (opcodeFunction) {
57 | @@ -1363,7 +1383,7 @@ class ScriptTreeGenerator {
58 | }
59 |
60 | // Non-warp direct recursion yields.
61 | - if (!this.script.isWarp) {
62 | + if (false && !this.script.isWarp) {
63 | if (procedureCode === this.script.procedureCode) {
64 | this.script.yields = true;
65 | }
66 | @@ -1435,9 +1455,9 @@ class ScriptTreeGenerator {
67 | }
68 |
69 | analyzeLoop () {
70 | - if (!this.script.isWarp || this.script.warpTimer) {
71 | - this.script.yields = true;
72 | - }
73 | + // if (!this.script.isWarp || this.script.warpTimer) {
74 | + // this.script.yields = true;
75 | + // }
76 | }
77 |
78 | readTopBlockComment (commentId) {
79 | diff --git a/node_modules/scratch-vm/src/compiler/jsexecute.js b/node_modules/scratch-vm/src/compiler/jsexecute.js
80 | index ec7d683..e97236b 100644
81 | --- a/node_modules/scratch-vm/src/compiler/jsexecute.js
82 | +++ b/node_modules/scratch-vm/src/compiler/jsexecute.js
83 | @@ -445,7 +445,6 @@ runtimeFunctions.listReplace = `const listReplace = (list, idx, value) => {
84 | return;
85 | }
86 | list.value[index] = value;
87 | - list._monitorUpToDate = false;
88 | }`;
89 |
90 | /**
91 | @@ -460,7 +459,6 @@ runtimeFunctions.listInsert = `const listInsert = (list, idx, value) => {
92 | return;
93 | }
94 | list.value.splice(index, 0, value);
95 | - list._monitorUpToDate = false;
96 | }`;
97 |
98 | /**
99 | @@ -478,7 +476,6 @@ runtimeFunctions.listDelete = `const listDelete = (list, idx) => {
100 | return;
101 | }
102 | list.value.splice(index, 1);
103 | - list._monitorUpToDate = false;
104 | }`;
105 |
106 | /**
107 | diff --git a/node_modules/scratch-vm/src/compiler/jsgen.js b/node_modules/scratch-vm/src/compiler/jsgen.js
108 | index bf9cbf3..750da73 100644
109 | --- a/node_modules/scratch-vm/src/compiler/jsgen.js
110 | +++ b/node_modules/scratch-vm/src/compiler/jsgen.js
111 | @@ -781,7 +781,7 @@ class JSGenerator {
112 | }
113 | this.source += '}\n'; // close switch
114 | this.source += `if (!${branchVariable}.isLoop) break;\n`;
115 | - this.yieldLoop();
116 | + // this.yieldLoop();
117 | this.source += '}\n'; // close while
118 | } else {
119 | throw new Error(`Unknown block type: ${blockType}`);
120 | @@ -811,7 +811,7 @@ class JSGenerator {
121 | this.source += `${index}++; `;
122 | this.source += `${this.referenceVariable(node.variable)}.value = ${index};\n`;
123 | this.descendStack(node.do, new Frame(true));
124 | - this.yieldLoop();
125 | + // this.yieldLoop();
126 | this.source += '}\n';
127 | break;
128 | }
129 | @@ -830,7 +830,7 @@ class JSGenerator {
130 | const i = this.localVariables.next();
131 | this.source += `for (var ${i} = ${this.descendInput(node.times).asNumber()}; ${i} >= 0.5; ${i}--) {\n`;
132 | this.descendStack(node.do, new Frame(true));
133 | - this.yieldLoop();
134 | + // this.yieldLoop();
135 | this.source += `}\n`;
136 | break;
137 | }
138 | @@ -868,11 +868,11 @@ class JSGenerator {
139 | this.resetVariableInputs();
140 | this.source += `while (${this.descendInput(node.condition).asBoolean()}) {\n`;
141 | this.descendStack(node.do, new Frame(true));
142 | - if (node.warpTimer) {
143 | - this.yieldStuckOrNotWarp();
144 | - } else {
145 | - this.yieldLoop();
146 | - }
147 | + // if (node.warpTimer) {
148 | + // this.yieldStuckOrNotWarp();
149 | + // } else {
150 | + // this.yieldLoop();
151 | + // }
152 | this.source += `}\n`;
153 | break;
154 |
155 | @@ -921,7 +921,6 @@ class JSGenerator {
156 | case 'list.add': {
157 | const list = this.referenceVariable(node.list);
158 | this.source += `${list}.value.push(${this.descendInput(node.item).asSafe()});\n`;
159 | - this.source += `${list}._monitorUpToDate = false;\n`;
160 | break;
161 | }
162 | case 'list.delete': {
163 | @@ -930,12 +929,10 @@ class JSGenerator {
164 | if (index instanceof ConstantInput) {
165 | if (index.constantValue === 'last') {
166 | this.source += `${list}.value.pop();\n`;
167 | - this.source += `${list}._monitorUpToDate = false;\n`;
168 | break;
169 | }
170 | if (+index.constantValue === 1) {
171 | this.source += `${list}.value.shift();\n`;
172 | - this.source += `${list}._monitorUpToDate = false;\n`;
173 | break;
174 | }
175 | // do not need a special case for all as that is handled in IR generation (list.deleteAll)
176 | @@ -955,7 +952,6 @@ class JSGenerator {
177 | const item = this.descendInput(node.item);
178 | if (index instanceof ConstantInput && +index.constantValue === 1) {
179 | this.source += `${list}.value.unshift(${item.asSafe()});\n`;
180 | - this.source += `${list}._monitorUpToDate = false;\n`;
181 | break;
182 | }
183 | this.source += `listInsert(${list}, ${index.asUnknown()}, ${item.asSafe()});\n`;
184 | @@ -1111,10 +1107,10 @@ class JSGenerator {
185 | break;
186 | }
187 |
188 | - const yieldForRecursion = !this.isWarp && procedureCode === this.script.procedureCode;
189 | - if (yieldForRecursion) {
190 | - this.yieldNotWarp();
191 | - }
192 | + // const yieldForRecursion = !this.isWarp && procedureCode === this.script.procedureCode;
193 | + // if (yieldForRecursion) {
194 | + // this.yieldNotWarp();
195 | + // }
196 |
197 | if (procedureData.yields) {
198 | this.source += 'yield* ';
199 | @@ -1167,6 +1163,18 @@ class JSGenerator {
200 | break;
201 | }
202 |
203 | + case 'sensing.ask':
204 | + this.source += `runtime.ext_scratch3_sensing._answer = runtime._scratch_run_ask(${this.descendInput(node.question).asString()});\n`;
205 | + break;
206 | +
207 | + case 'looks.say':
208 | + this.source += `runtime._scratch_run_say(${this.descendInput(node.message).asString()});\n`;
209 | + break;
210 | +
211 | + case 'looks.think':
212 | + this.source += `runtime._scratch_run_think(${this.descendInput(node.message).asString()});\n`;
213 | + break;
214 | +
215 | default:
216 | log.warn(`JS: Unknown stacked block: ${node.kind}`, node);
217 | throw new Error(`JS: Unknown stacked block: ${node.kind}`);
218 | diff --git a/node_modules/scratch-vm/src/engine/blocks.js b/node_modules/scratch-vm/src/engine/blocks.js
219 | index 71ace3a..7e6357b 100644
220 | --- a/node_modules/scratch-vm/src/engine/blocks.js
221 | +++ b/node_modules/scratch-vm/src/engine/blocks.js
222 | @@ -1,9 +1,9 @@
223 | const adapter = require('./adapter');
224 | const mutationAdapter = require('./mutation-adapter');
225 | const xmlEscape = require('../util/xml-escape');
226 | -const MonitorRecord = require('./monitor-record');
227 | +// const MonitorRecord = require('./monitor-record');
228 | const Clone = require('../util/clone');
229 | -const {Map} = require('immutable');
230 | +// const {Map} = require('immutable');
231 | const BlocksExecuteCache = require('./blocks-execute-cache');
232 | const BlocksRuntimeCache = require('./blocks-runtime-cache');
233 | const log = require('../util/log');
234 | @@ -709,8 +709,8 @@ class Blocks {
235 | this.runtime.requestBlocksUpdate();
236 | }
237 |
238 | - const flyoutBlock = block.shadow && block.parent ? this._blocks[block.parent] : block;
239 | - if (flyoutBlock.isMonitored) {
240 | + // const flyoutBlock = block.shadow && block.parent ? this._blocks[block.parent] : block;
241 | + if (false && flyoutBlock.isMonitored) {
242 | this.runtime.requestUpdateMonitor(Map({
243 | id: flyoutBlock.id,
244 | params: this._getBlockParams(flyoutBlock)
245 | @@ -768,9 +768,9 @@ class Blocks {
246 | block.targetId = null;
247 | }
248 |
249 | - if (wasMonitored && !block.isMonitored) {
250 | + if (false && wasMonitored && !block.isMonitored) {
251 | this.runtime.requestHideMonitor(block.id);
252 | - } else if (!wasMonitored && block.isMonitored) {
253 | + } else if (false && !wasMonitored && block.isMonitored) {
254 | // Tries to show the monitor for specified block. If it doesn't exist, add the monitor.
255 | if (!this.runtime.requestShowMonitor(block.id)) {
256 | this.runtime.requestAddMonitor(MonitorRecord({
257 | diff --git a/node_modules/scratch-vm/src/engine/execute.js b/node_modules/scratch-vm/src/engine/execute.js
258 | index 7e94ee9..4082784 100644
259 | --- a/node_modules/scratch-vm/src/engine/execute.js
260 | +++ b/node_modules/scratch-vm/src/engine/execute.js
261 | @@ -2,7 +2,7 @@ const BlockUtility = require('./block-utility');
262 | const BlocksExecuteCache = require('./blocks-execute-cache');
263 | const log = require('../util/log');
264 | const Thread = require('./thread');
265 | -const {Map} = require('immutable');
266 | +// const {Map} = require('immutable');
267 | const cast = require('../util/cast');
268 |
269 | /**
270 | @@ -100,7 +100,7 @@ const handleReport = function (resolvedValue, sequencer, thread, blockCached, la
271 | // Target no longer exists
272 | return;
273 | }
274 | - sequencer.runtime.requestUpdateMonitor(Map({
275 | + if (false) sequencer.runtime.requestUpdateMonitor(Map({
276 | id: currentBlockId,
277 | spriteName: targetId ? sequencer.runtime.getTargetById(targetId).getName() : null,
278 | value: resolvedValue
279 | diff --git a/node_modules/scratch-vm/src/engine/runtime.js b/node_modules/scratch-vm/src/engine/runtime.js
280 | index 6cfe302..c7f92f8 100644
281 | --- a/node_modules/scratch-vm/src/engine/runtime.js
282 | +++ b/node_modules/scratch-vm/src/engine/runtime.js
283 | @@ -1,5 +1,5 @@
284 | const EventEmitter = require('events');
285 | -const {OrderedMap} = require('immutable');
286 | +// const {OrderedMap} = require('immutable');
287 | const ExtendedJSON = require('@turbowarp/json');
288 | const uuid = require('uuid');
289 |
290 | @@ -320,12 +320,12 @@ class Runtime extends EventEmitter {
291 | /**
292 | * Ordered map of all monitors, which are MonitorReporter objects.
293 | */
294 | - this._monitorState = OrderedMap({});
295 | + // this._monitorState = OrderedMap({});
296 |
297 | /**
298 | * Monitor state from last tick
299 | */
300 | - this._prevMonitorState = OrderedMap({});
301 | + // this._prevMonitorState = OrderedMap({});
302 |
303 | /**
304 | * Whether the project is in "turbo mode."
305 | @@ -2247,8 +2247,8 @@ class Runtime extends EventEmitter {
306 | this.targets.map(this.disposeTarget, this);
307 | this.extensionStorage = {};
308 | // tw: explicitly emit a MONITORS_UPDATE instead of relying on implicit behavior of _step()
309 | - const emptyMonitorState = OrderedMap({});
310 | - if (!emptyMonitorState.equals(this._monitorState)) {
311 | + // const emptyMonitorState = OrderedMap({});
312 | + if (false && !emptyMonitorState.equals(this._monitorState)) {
313 | this._monitorState = emptyMonitorState;
314 | this.emit(Runtime.MONITORS_UPDATE, this._monitorState);
315 | }
316 | @@ -2484,6 +2484,7 @@ class Runtime extends EventEmitter {
317 | // Clean up threads that were told to stop during or since the last step
318 | this.threads = this.threads.filter(thread => !thread.isKilled);
319 | this.updateThreadMap();
320 | + this._emitProjectRunStatus(this.threads.length - this._getMonitorThreadCount(this.threads));
321 |
322 | // Find all edge-activated hats, and add them to threads to be evaluated.
323 | for (const hatType in this._hats) {
324 | @@ -2494,7 +2495,7 @@ class Runtime extends EventEmitter {
325 | }
326 | }
327 | this.redrawRequested = false;
328 | - this._pushMonitors();
329 | + // this._pushMonitors();
330 | if (this.profiler !== null) {
331 | if (stepThreadsProfilerId === -1) {
332 | stepThreadsProfilerId = this.profiler.idByName('Sequencer.stepThreads');
333 | @@ -2540,7 +2541,7 @@ class Runtime extends EventEmitter {
334 | this._refreshTargets = false;
335 | }
336 |
337 | - if (!this._prevMonitorState.equals(this._monitorState)) {
338 | + if (false && !this._prevMonitorState.equals(this._monitorState)) {
339 | this.emit(Runtime.MONITORS_UPDATE, this._monitorState);
340 | this._prevMonitorState = this._monitorState;
341 | }
342 | @@ -2662,10 +2663,10 @@ class Runtime extends EventEmitter {
343 | width = Math.round(Math.max(1, width));
344 | height = Math.round(Math.max(1, height));
345 | if (this.stageWidth !== width || this.stageHeight !== height) {
346 | - const deltaX = width - this.stageWidth;
347 | - const deltaY = height - this.stageHeight;
348 | + // const deltaX = width - this.stageWidth;
349 | + // const deltaY = height - this.stageHeight;
350 | // Preserve monitor location relative to the center of the stage
351 | - if (this._monitorState.size > 0) {
352 | + if (false && this._monitorState.size > 0) {
353 | const offsetX = deltaX / 2;
354 | const offsetY = deltaY / 2;
355 | for (const monitor of this._monitorState.valueSeq()) {
356 | @@ -3053,6 +3054,7 @@ class Runtime extends EventEmitter {
357 | * @param {!MonitorRecord} monitor Monitor to add.
358 | */
359 | requestAddMonitor (monitor) {
360 | + return;
361 | const id = monitor.get('id');
362 | if (!this.requestUpdateMonitor(monitor)) { // update monitor if it exists in the state
363 | // if the monitor did not exist in the state, add it
364 | @@ -3068,6 +3070,7 @@ class Runtime extends EventEmitter {
365 | * @return {boolean} true if monitor exists in the state and was updated, false if it did not exist.
366 | */
367 | requestUpdateMonitor (monitor) {
368 | + return true;
369 | const id = monitor.get('id');
370 | if (this._monitorState.has(id)) {
371 | this._monitorState =
372 | @@ -3089,6 +3092,7 @@ class Runtime extends EventEmitter {
373 | * @param {!string} monitorId ID of the monitor to remove.
374 | */
375 | requestRemoveMonitor (monitorId) {
376 | + return;
377 | this._monitorState = this._monitorState.delete(monitorId);
378 | }
379 |
380 | @@ -3098,6 +3102,7 @@ class Runtime extends EventEmitter {
381 | * @return {boolean} true if monitor exists and was updated, false otherwise
382 | */
383 | requestHideMonitor (monitorId) {
384 | + return true;
385 | return this.requestUpdateMonitor(new Map([
386 | ['id', monitorId],
387 | ['visible', false]
388 | @@ -3111,6 +3116,7 @@ class Runtime extends EventEmitter {
389 | * @return {boolean} true if monitor exists and was updated, false otherwise
390 | */
391 | requestShowMonitor (monitorId) {
392 | + return true;
393 | return this.requestUpdateMonitor(new Map([
394 | ['id', monitorId],
395 | ['visible', true]
396 | @@ -3123,6 +3129,7 @@ class Runtime extends EventEmitter {
397 | * @param {!string} targetId Remove all monitors with given target ID.
398 | */
399 | requestRemoveMonitorByTargetId (targetId) {
400 | + return;
401 | this._monitorState = this._monitorState.filterNot(value => value.targetId === targetId);
402 | }
403 |
404 | diff --git a/node_modules/scratch-vm/src/engine/target.js b/node_modules/scratch-vm/src/engine/target.js
405 | index e9cecfe..ef24579 100644
406 | --- a/node_modules/scratch-vm/src/engine/target.js
407 | +++ b/node_modules/scratch-vm/src/engine/target.js
408 | @@ -4,7 +4,7 @@ const Blocks = require('./blocks');
409 | const Variable = require('../engine/variable');
410 | const Comment = require('../engine/comment');
411 | const uid = require('../util/uid');
412 | -const {Map} = require('immutable');
413 | +// const {Map} = require('immutable');
414 | const log = require('../util/log');
415 | const StringUtil = require('../util/string-util');
416 | const VariableUtil = require('../util/variable-util');
417 | @@ -350,8 +350,8 @@ class Target extends EventEmitter {
418 | name: variable.type === Variable.LIST_TYPE ? 'LIST' : 'VARIABLE',
419 | value: id
420 | }, this.runtime);
421 | - const monitorBlock = blocks.getBlock(variable.id);
422 | - if (monitorBlock) {
423 | + // const monitorBlock = blocks.getBlock(variable.id + 'lebaohiep');
424 | + if (false && monitorBlock) {
425 | this.runtime.requestUpdateMonitor(Map({
426 | id: id,
427 | params: blocks._getBlockParams(monitorBlock)
428 | diff --git a/node_modules/scratch-vm/src/serialization/sb2.js b/node_modules/scratch-vm/src/serialization/sb2.js
429 | index 6dc9cef..26e97f9 100644
430 | --- a/node_modules/scratch-vm/src/serialization/sb2.js
431 | +++ b/node_modules/scratch-vm/src/serialization/sb2.js
432 | @@ -16,7 +16,7 @@ const MathUtil = require('../util/math-util');
433 | const specMap = require('./sb2_specmap');
434 | const Comment = require('../engine/comment');
435 | const Variable = require('../engine/variable');
436 | -const MonitorRecord = require('../engine/monitor-record');
437 | +// const MonitorRecord = require('../engine/monitor-record');
438 | const StageLayering = require('../engine/stage-layering');
439 | const ScratchXUtilities = require('../extension-support/tw-scratchx-utilities');
440 |
441 | @@ -300,6 +300,7 @@ const globalBroadcastMsgStateGenerator = (function () {
442 | */
443 |
444 | const parseMonitorObject = (object, runtime, targets, extensions) => {
445 | + return;
446 | // If we can't find the block in the spec map, ignore it.
447 | // This happens for things like Lego Wedo 1.0 monitors.
448 | const mapped = specMap[object.cmd];
449 | diff --git a/node_modules/scratch-vm/src/serialization/sb3.js b/node_modules/scratch-vm/src/serialization/sb3.js
450 | index b760e05..aa76a63 100644
451 | --- a/node_modules/scratch-vm/src/serialization/sb3.js
452 | +++ b/node_modules/scratch-vm/src/serialization/sb3.js
453 | @@ -8,7 +8,7 @@ const Blocks = require('../engine/blocks');
454 | const Sprite = require('../sprites/sprite');
455 | const Variable = require('../engine/variable');
456 | const Comment = require('../engine/comment');
457 | -const MonitorRecord = require('../engine/monitor-record');
458 | +// const MonitorRecord = require('../engine/monitor-record');
459 | const StageLayering = require('../engine/stage-layering');
460 | const log = require('../util/log');
461 | const uid = require('../util/uid');
462 | @@ -1324,6 +1324,7 @@ const parseScratchObject = function (object, runtime, extensions, zip, assets) {
463 | };
464 |
465 | const deserializeMonitor = function (monitorData, runtime, targets, extensions) {
466 | + return;
467 | // Monitors position is always stored as position from top-left corner in 480x360 stage.
468 | const xOffset = (runtime.stageWidth - 480) / 2;
469 | const yOffset = (runtime.stageHeight - 360) / 2;
470 |
--------------------------------------------------------------------------------
/src/build/noop-loader.js:
--------------------------------------------------------------------------------
1 | module.exports = function noopLoader() {
2 | return 'module.exports = null;';
3 | };
4 |
--------------------------------------------------------------------------------
/src/build/noop-module/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/engine/tw-font-manager.js:
--------------------------------------------------------------------------------
1 | class FontManager {
2 | clear() {}
3 |
4 | serializeJSON() {
5 | return null;
6 | }
7 |
8 | serializeAssets() {
9 | return [];
10 | }
11 |
12 | async deserialize() {}
13 | }
14 |
15 | module.exports = FontManager;
16 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/extension-support/extension-manager.js:
--------------------------------------------------------------------------------
1 | class SecurityManager {
2 | canLoadExtensionFromProject() {
3 | return Promise.resolve(false);
4 | }
5 | }
6 |
7 | class ExtensionManager {
8 | constructor() {
9 | this.securityManager = new SecurityManager();
10 | }
11 |
12 | isExtensionLoaded() {
13 | return false;
14 | }
15 |
16 | isBuiltinExtension() {
17 | return true;
18 | }
19 |
20 | loadExtensionIdSync() {}
21 |
22 | async loadExtensionURL() {}
23 |
24 | allAsyncExtensionsLoaded() {}
25 |
26 | refreshBlocks() {}
27 |
28 | getExtensionURLs() {
29 | return {};
30 | }
31 |
32 | isExtensionURLLoaded() {
33 | return false;
34 | }
35 | }
36 |
37 | module.exports = ExtensionManager;
38 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/import/load-costume.js:
--------------------------------------------------------------------------------
1 | const loadCostume = function (md5ext, costume) {
2 | return Promise.resolve(costume);
3 | };
4 |
5 | const loadCostumeFromAsset = function (costume) {
6 | return Promise.resolve(costume);
7 | };
8 |
9 | module.exports = {
10 | loadCostume,
11 | loadCostumeFromAsset
12 | };
13 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/import/load-sound.js:
--------------------------------------------------------------------------------
1 | const loadSoundFromAsset = function (sound) {
2 | return Promise.resolve(sound);
3 | };
4 |
5 | const loadSound = function (sound) {
6 | return Promise.resolve(sound);
7 | };
8 |
9 | module.exports = {
10 | loadSound,
11 | loadSoundFromAsset
12 | };
13 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/io/clock.js:
--------------------------------------------------------------------------------
1 | class Clock {
2 | constructor() {}
3 |
4 | projectTimer() {
5 | return 0;
6 | }
7 |
8 | resetProjectTimer() {}
9 | }
10 |
11 | module.exports = Clock;
12 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/io/cloud.js:
--------------------------------------------------------------------------------
1 | class Cloud {
2 | constructor() {}
3 |
4 | setProvider() {}
5 |
6 | setStage() {}
7 |
8 | postData() {}
9 |
10 | requestCreateVariable() {}
11 |
12 | requestUpdateVariable() {}
13 |
14 | requestRenameVariable() {}
15 |
16 | requestDeleteVariable() {}
17 |
18 | clear() {}
19 | }
20 |
21 | module.exports = Cloud;
22 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/io/keyboard.js:
--------------------------------------------------------------------------------
1 | class Keyboard {
2 | constructor() {}
3 |
4 | postData() {}
5 |
6 | getKeyIsDown() {
7 | return false;
8 | }
9 |
10 | getLastKeyPressed() {
11 | return '';
12 | }
13 | }
14 |
15 | module.exports = Keyboard;
16 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/io/mouse.js:
--------------------------------------------------------------------------------
1 | class Mouse {
2 | constructor() {}
3 |
4 | postData() {}
5 |
6 | getClientX() {
7 | return 0;
8 | }
9 |
10 | getClientY() {
11 | return 0;
12 | }
13 |
14 | getScratchX() {
15 | return 0;
16 | }
17 |
18 | getScratchY() {
19 | return 0;
20 | }
21 |
22 | getIsDown() {
23 | return false;
24 | }
25 |
26 | getButtonIsDown() {
27 | return false;
28 | }
29 | }
30 |
31 | module.exports = Mouse;
32 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/io/mouseWheel.js:
--------------------------------------------------------------------------------
1 | class MouseWheel {
2 | constructor() {}
3 |
4 | postData() {}
5 | }
6 |
7 | module.exports = MouseWheel;
8 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/io/userData.js:
--------------------------------------------------------------------------------
1 | class UserData {
2 | constructor() {}
3 |
4 | postData() {}
5 |
6 | getUsername() {
7 | return '';
8 | }
9 | }
10 |
11 | module.exports = UserData;
12 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/io/video.js:
--------------------------------------------------------------------------------
1 | class Video {
2 | constructor() {}
3 |
4 | setProvider() {}
5 |
6 | postData() {}
7 | }
8 |
9 | module.exports = Video;
10 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/serialization/deserialize-assets.js:
--------------------------------------------------------------------------------
1 | const deserializeSound = function () {
2 | return Promise.resolve(null);
3 | };
4 |
5 | const deserializeCostume = function () {
6 | return Promise.resolve(null);
7 | };
8 |
9 | module.exports = {
10 | deserializeSound,
11 | deserializeCostume
12 | };
13 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/serialization/serialize-assets.js:
--------------------------------------------------------------------------------
1 | const serializeSounds = function () {
2 | return [];
3 | };
4 |
5 | const serializeCostumes = function () {
6 | return [];
7 | };
8 |
9 | module.exports = {
10 | serializeSounds,
11 | serializeCostumes
12 | };
13 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/serialization/tw-costume-import-export.js:
--------------------------------------------------------------------------------
1 | const parseVectorMetadata = () => {
2 | return null;
3 | };
4 |
5 | const exportCostume = (costume) => {
6 | return costume.asset.data;
7 | };
8 |
9 | module.exports = {
10 | parseVectorMetadata,
11 | exportCostume
12 | };
13 |
--------------------------------------------------------------------------------
/src/build/scratch-vm/util/log.js:
--------------------------------------------------------------------------------
1 | const noop = () => {};
2 |
3 | module.exports = {
4 | debug: noop,
5 | info: noop,
6 | log: noop,
7 | warn: noop,
8 | error: noop
9 | };
10 |
--------------------------------------------------------------------------------
/src/build/text-encoding/index.js:
--------------------------------------------------------------------------------
1 | require('fastestsmallesttextencoderdecoder');
2 |
3 | module.exports = {
4 | TextEncoder,
5 | TextDecoder
6 | };
7 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // Suppress all warnings
2 | process.removeAllListeners('warning');
3 |
4 | const fs = require('fs');
5 |
6 | function writeStdoutSync(text) {
7 | fs.writeSync(process.stdout.fd, text);
8 | }
9 |
10 | function writeStderrSync(text) {
11 | fs.writeSync(process.stderr.fd, text);
12 | }
13 |
14 | const argv = require('minimist')(process.argv.slice(2), {
15 | boolean: ['help', 'version', 'check', 'buffer-stdout', 'print-generated-js']
16 | });
17 |
18 | if (argv.version) {
19 | const { version } = require('../package.json');
20 | writeStdoutSync(version + '\n');
21 | process.exit(0);
22 | }
23 |
24 | if (argv._.length == 0 || argv.help) {
25 | writeStdoutSync(`Usage: scratch-run project-file [OPTIONS]
26 | Options:
27 | --help print this message
28 | --version print the version
29 | --check validate project file
30 | --buffer-stdout buffer stdout for better performance
31 | --print-generated-js print the generated JavaScript code
32 | `);
33 | process.exit(0);
34 | }
35 |
36 | const scratchVM = require('scratch-vm');
37 | const Kattio = require('./kattio');
38 |
39 | function construct_vm() {
40 | const vm = new scratchVM();
41 | vm.convertToPackagedRuntime();
42 | vm.setTurboMode(true);
43 | vm.setFramerate(250);
44 |
45 | // Block loading extensions (e.g., music)
46 | vm.extensionManager.loadExtensionIdSync =
47 | vm.extensionManager.loadExtensionURL = (id) => {
48 | writeStderrSync(
49 | 'Not a valid Scratch file: Can not use extension ' + id + '\n'
50 | );
51 | process.exit(1);
52 | };
53 |
54 | return vm;
55 | }
56 |
57 | function check_scratch_file(filename) {
58 | const vm = construct_vm();
59 | fs.readFile(filename, function (err, data) {
60 | if (err) {
61 | writeStderrSync(err + '\n');
62 | process.exit(1);
63 | }
64 |
65 | vm.loadProject(data)
66 | .then(() => {
67 | process.exit(0);
68 | })
69 | .catch(function (err) {
70 | writeStderrSync('Not a valid Scratch file: ' + err + '\n');
71 | process.exit(1);
72 | });
73 | });
74 | }
75 |
76 | function print_generated_js(filename) {
77 | const vm = construct_vm();
78 |
79 | const generatedJS = [];
80 | require('scratch-vm/src/compiler/jsgen').testingApparatus = {
81 | report: (jsgen, factorySource) => {
82 | const targetName = jsgen.target.getName();
83 | const scriptName = jsgen.script.procedureCode || 'script';
84 | generatedJS.push(`// ${targetName} ${scriptName}\n${factorySource}`);
85 | }
86 | };
87 |
88 | const errors = [];
89 | vm.on('COMPILE_ERROR', (target, error) => {
90 | errors.push({ target, error });
91 | });
92 |
93 | fs.readFile(filename, function (err, data) {
94 | if (err) {
95 | writeStderrSync(err + '\n');
96 | process.exit(1);
97 | }
98 |
99 | vm.loadProject(data)
100 | .then(() => {
101 | vm.runtime.precompile();
102 |
103 | let result = '';
104 | if (errors.length) {
105 | result += '// Errors:\n';
106 | result += errors.map((i) => `// ${i.target.getName()}: ${i.error}\n`);
107 | result += '\n';
108 | }
109 | result += generatedJS.join('\n\n');
110 | result += '\n';
111 | writeStdoutSync(result);
112 | })
113 | .catch(function (err) {
114 | writeStderrSync('scratch-vm encountered an error: ' + err + '\n');
115 | process.exit(1);
116 | });
117 | });
118 | }
119 |
120 | function run_scratch_file(filename) {
121 | const vm = construct_vm();
122 |
123 | // _scratch_run_* are called from the generated code by scratch-vm's compiler
124 | let stdoutBuffer = '';
125 | if (argv['buffer-stdout']) {
126 | vm.runtime._scratch_run_say = function (text) {
127 | stdoutBuffer += text + '\n';
128 | };
129 | vm.runtime._scratch_run_think = function (text) {
130 | stdoutBuffer += text;
131 | };
132 | } else {
133 | vm.runtime._scratch_run_say = function (text) {
134 | process.stdout.write(text + '\n');
135 | };
136 | vm.runtime._scratch_run_think = function (text) {
137 | process.stdout.write(text);
138 | };
139 | }
140 | vm.runtime._scratch_run_ask = function (question) {
141 | try {
142 | if (question === 'read_token') {
143 | return Kattio.nextToken();
144 | } else {
145 | return Kattio.nextLine();
146 | }
147 | } catch (e) {
148 | writeStderrSync(
149 | 'scratch-vm encountered an error: Could not read input: ' +
150 | e.message +
151 | '\n'
152 | );
153 | process.exit(1);
154 | }
155 | };
156 |
157 | vm.runtime.on('PROJECT_RUN_STOP', function () {
158 | vm.runtime.quit();
159 | process.stdout.write(stdoutBuffer, () => process.exit(0));
160 | });
161 |
162 | fs.readFile(filename, function (err, data) {
163 | if (err) {
164 | writeStderrSync(err + '\n');
165 | process.exit(1);
166 | }
167 |
168 | vm.loadProject(data)
169 | .then(() => {
170 | for (const target of vm.runtime.targets) {
171 | target.setVisible(false);
172 | }
173 | vm.runtime.precompile();
174 | vm.start();
175 | vm.greenFlag();
176 | })
177 | .catch(function (err) {
178 | writeStderrSync('scratch-vm encountered an error: ' + err + '\n');
179 | process.exit(1);
180 | });
181 | });
182 | }
183 |
184 | const filename = argv._[0];
185 | if (argv.check) {
186 | check_scratch_file(filename);
187 | } else if (argv['print-generated-js']) {
188 | print_generated_js(filename);
189 | } else {
190 | run_scratch_file(filename);
191 | }
192 |
--------------------------------------------------------------------------------
/src/kattio.js:
--------------------------------------------------------------------------------
1 | // https://gist.github.com/simonlindholm/38e1a3ff5d99fdbceda541f112f9daf3
2 |
3 | const fs = require('fs');
4 |
5 | const Kattio = {
6 | _buf: Buffer.alloc(1 << 20),
7 | _bufPos: 0,
8 | _bufLen: 0,
9 | _ensure: function () {
10 | if (this._bufPos === this._bufLen) {
11 | this._bufPos = 0;
12 | try {
13 | this._bufLen = fs.readSync(0, this._buf, 0, this._buf.length, null);
14 | } catch (error) {
15 | // Ref: https://github.com/nodejs/node/issues/35997
16 | if (error.code === 'EOF') {
17 | this._bufLen = 0;
18 | return;
19 | }
20 |
21 | throw error;
22 | }
23 | }
24 | },
25 |
26 | _isws: function (ch) {
27 | return ch === 32 || (9 <= ch && ch <= 13);
28 | },
29 |
30 | _islf: function (ch) {
31 | return ch === 10 || ch === 13;
32 | },
33 |
34 | _peekChar: function () {
35 | this._ensure();
36 | return this._bufPos === this._bufLen ? 0 : this._buf[this._bufPos];
37 | },
38 |
39 | _skipWs: function () {
40 | while (this._isws(this._peekChar())) this._bufPos++;
41 | },
42 |
43 | _readUntil: function (stop) {
44 | this._ensure();
45 | if (this._bufPos === this._bufLen) {
46 | throw new Error('End of file reached');
47 | }
48 |
49 | var start = this._bufPos;
50 | var before = null;
51 | for (;;) {
52 | if (this._bufPos === this._bufLen) {
53 | // Hit the end; need to switch buffers. Thus, stash away all we have so far
54 | // into the 'before' buffer.
55 | var len = this._bufPos - start,
56 | preLen = before ? before.length : 0;
57 | var nbuf = Buffer.alloc(len + preLen);
58 | if (before) before.copy(nbuf);
59 | before = nbuf;
60 | this._buf.copy(before, preLen, start);
61 | this._ensure();
62 | start = this._bufPos;
63 | }
64 | if (this._bufPos === this._bufLen || stop(this._buf[this._bufPos])) break;
65 | this._bufPos++;
66 | }
67 | if (!before) {
68 | return this._buf.toString('utf8', start, this._bufPos);
69 | }
70 | var after = this._buf.subarray(start, this._bufPos);
71 | var res = Buffer.alloc(before.length + after.length);
72 | before.copy(res);
73 | after.copy(res, before.length);
74 | return res.toString('utf8');
75 | },
76 |
77 | nextToken: function () {
78 | this._skipWs();
79 | return this._readUntil(this._isws);
80 | },
81 |
82 | nextLine: function () {
83 | var line = this._readUntil(this._islf);
84 | if (this._peekChar() === 13) this._bufPos++;
85 | if (this._peekChar() === 10) this._bufPos++;
86 | return line;
87 | }
88 | };
89 |
90 | module.exports = Kattio;
91 |
--------------------------------------------------------------------------------
/tests/aplusb.sb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VNOI-Admin/scratch-run/af913cb1c5021b5aad72204938a74fc80ccf10c7/tests/aplusb.sb
--------------------------------------------------------------------------------
/tests/aplusb.sb2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VNOI-Admin/scratch-run/af913cb1c5021b5aad72204938a74fc80ccf10c7/tests/aplusb.sb2
--------------------------------------------------------------------------------
/tests/aplusb.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VNOI-Admin/scratch-run/af913cb1c5021b5aad72204938a74fc80ccf10c7/tests/aplusb.sb3
--------------------------------------------------------------------------------
/tests/echo.json:
--------------------------------------------------------------------------------
1 | {"targets":[{"isStage":true,"name":"Stage","variables":{"`jEk@4|i[#Fk?(8x)AV.-my variable":["text","thuk"]},"lists":{},"broadcasts":{},"blocks":{},"comments":{},"currentCostume":0,"costumes":[{"assetId":"cd21514d0531fdffb22204e0ec5ed84a","name":"backdrop1","md5ext":"cd21514d0531fdffb22204e0ec5ed84a.svg","dataFormat":"svg","rotationCenterX":240,"rotationCenterY":180}],"sounds":[{"assetId":"83a9787d4cb6f3b7632b4ddfebf74367","name":"pop","dataFormat":"wav","format":"","rate":48000,"sampleCount":1123,"md5ext":"83a9787d4cb6f3b7632b4ddfebf74367.wav"}],"volume":100,"layerOrder":0,"tempo":60,"videoTransparency":50,"videoState":"on","textToSpeechLanguage":null},{"isStage":false,"name":"Sprite1","variables":{},"lists":{},"broadcasts":{},"blocks":{"DM)/T}QPiS?mo~^bij2;":{"opcode":"event_whenflagclicked","next":"A24MyL#=n+b=;r:[za~%","parent":null,"inputs":{},"fields":{},"shadow":false,"topLevel":true,"x":444,"y":39},"A24MyL#=n+b=;r:[za~%":{"opcode":"sensing_askandwait","next":")bJ6((r8M*ExyCq=1rCJ","parent":"DM)/T}QPiS?mo~^bij2;","inputs":{"QUESTION":[1,[10,"What's your name?"]]},"fields":{},"shadow":false,"topLevel":false},"Z5GRmk8DH|*0x(y/u1!m":{"opcode":"sensing_answer","next":null,"parent":")bJ6((r8M*ExyCq=1rCJ","inputs":{},"fields":{},"shadow":false,"topLevel":false},")bJ6((r8M*ExyCq=1rCJ":{"opcode":"looks_say","next":null,"parent":"A24MyL#=n+b=;r:[za~%","inputs":{"MESSAGE":[3,"Z5GRmk8DH|*0x(y/u1!m",[10,"Hello!"]]},"fields":{},"shadow":false,"topLevel":false}},"comments":{},"currentCostume":0,"costumes":[{"assetId":"bcf454acf82e4504149f7ffe07081dbc","name":"costume1","bitmapResolution":1,"md5ext":"bcf454acf82e4504149f7ffe07081dbc.svg","dataFormat":"svg","rotationCenterX":48,"rotationCenterY":50},{"assetId":"0fb9be3e8397c983338cb71dc84d0b25","name":"costume2","bitmapResolution":1,"md5ext":"0fb9be3e8397c983338cb71dc84d0b25.svg","dataFormat":"svg","rotationCenterX":46,"rotationCenterY":53}],"sounds":[{"assetId":"83c36d806dc92327b9e7049a565c6bff","name":"Meo","dataFormat":"wav","format":"","rate":48000,"sampleCount":40681,"md5ext":"83c36d806dc92327b9e7049a565c6bff.wav"}],"volume":100,"layerOrder":1,"visible":true,"x":0,"y":0,"size":100,"direction":90,"draggable":false,"rotationStyle":"all around"}],"monitors":[{"id":"answer","mode":"default","opcode":"sensing_answer","params":{},"spriteName":null,"value":"thuk","width":0,"height":0,"x":5,"y":5,"visible":true,"sliderMin":0,"sliderMax":100,"isDiscrete":true}],"extensions":[],"meta":{"semver":"3.0.0","vm":"0.2.0-prerelease.20210811102104","agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36"}}
--------------------------------------------------------------------------------
/tests/echo.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VNOI-Admin/scratch-run/af913cb1c5021b5aad72204938a74fc80ccf10c7/tests/echo.sb3
--------------------------------------------------------------------------------
/tests/invalid.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VNOI-Admin/scratch-run/af913cb1c5021b5aad72204938a74fc80ccf10c7/tests/invalid.sb3
--------------------------------------------------------------------------------
/tests/music_extension.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VNOI-Admin/scratch-run/af913cb1c5021b5aad72204938a74fc80ccf10c7/tests/music_extension.sb3
--------------------------------------------------------------------------------
/tests/permutation.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VNOI-Admin/scratch-run/af913cb1c5021b5aad72204938a74fc80ccf10c7/tests/permutation.sb3
--------------------------------------------------------------------------------
/tests/say_think.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VNOI-Admin/scratch-run/af913cb1c5021b5aad72204938a74fc80ccf10c7/tests/say_think.sb3
--------------------------------------------------------------------------------
/tests/stopall.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VNOI-Admin/scratch-run/af913cb1c5021b5aad72204938a74fc80ccf10c7/tests/stopall.sb3
--------------------------------------------------------------------------------
/tests/sum_1ton.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VNOI-Admin/scratch-run/af913cb1c5021b5aad72204938a74fc80ccf10c7/tests/sum_1ton.sb3
--------------------------------------------------------------------------------
/tests/test.py:
--------------------------------------------------------------------------------
1 | import os
2 | import platform
3 | import unittest
4 | from subprocess import PIPE, Popen, TimeoutExpired
5 |
6 | __DIR__ = os.path.dirname(os.path.realpath(__file__))
7 | os.chdir(__DIR__)
8 |
9 |
10 | def get_executable():
11 | arch = 'arm64' if platform.machine() == 'aarch64' else 'amd64'
12 | if platform.system() == 'Linux':
13 | return f'../bin/linux-{arch}/scratch-run'
14 | elif platform.system() == 'Darwin':
15 | return f'../bin/macos-{arch}/scratch-run'
16 | elif platform.system() == 'Windows':
17 | return f'../bin/win-{arch}/scratch-run'
18 | else:
19 | raise RuntimeError('Unsupported platform: {}'.format(platform.system()))
20 |
21 |
22 | class TestScratchRun(unittest.TestCase):
23 | executable = get_executable()
24 |
25 | def test_say_think(self):
26 | proc = Popen([self.executable, 'say_think.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
27 | stdout, stderr = proc.communicate()
28 |
29 | self.assertEqual(proc.returncode, 0)
30 | self.assertEqual(stdout, b'Hello world!\n')
31 | self.assertEqual(stderr, b'')
32 |
33 | def test_echo(self):
34 | test_message = b'echo: Hello, World!\n'
35 |
36 | proc = Popen([self.executable, 'echo.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
37 | stdout, stderr = proc.communicate(test_message)
38 |
39 | self.assertEqual(proc.returncode, 0)
40 | self.assertEqual(stdout, test_message)
41 | self.assertEqual(stderr, b'')
42 |
43 | def test_echo_json(self):
44 | test_message = b'echo: Hello, World!\n'
45 |
46 | proc = Popen([self.executable, 'echo.json'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
47 | stdout, stderr = proc.communicate(test_message)
48 |
49 | self.assertEqual(proc.returncode, 0)
50 | self.assertEqual(stdout, test_message)
51 | self.assertEqual(stderr, b'')
52 |
53 | def test_aplusb_sb_token(self):
54 | proc = Popen([self.executable, 'aplusb.sb'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
55 | stdout, stderr = proc.communicate(b'123 456\n')
56 |
57 | self.assertEqual(proc.returncode, 0)
58 | self.assertEqual(stdout, b'579\n')
59 | self.assertEqual(stderr, b'')
60 |
61 | def test_aplusb_sb_line(self):
62 | proc = Popen([self.executable, 'aplusb.sb'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
63 | stdout, stderr = proc.communicate(b'123\n456\n')
64 |
65 | self.assertEqual(proc.returncode, 0)
66 | self.assertEqual(stdout, b'579\n')
67 | self.assertEqual(stderr, b'')
68 |
69 | def test_aplusb_sb2_token(self):
70 | proc = Popen([self.executable, 'aplusb.sb2'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
71 | stdout, stderr = proc.communicate(b'123 456\n')
72 |
73 | self.assertEqual(proc.returncode, 0)
74 | self.assertEqual(stdout, b'579\n')
75 | self.assertEqual(stderr, b'')
76 |
77 | def test_aplusb_sb2_line(self):
78 | proc = Popen([self.executable, 'aplusb.sb2'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
79 | stdout, stderr = proc.communicate(b'123\n456\n')
80 |
81 | self.assertEqual(proc.returncode, 0)
82 | self.assertEqual(stdout, b'579\n')
83 | self.assertEqual(stderr, b'')
84 |
85 | def test_aplusb_sb3_token(self):
86 | proc = Popen([self.executable, 'aplusb.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
87 | stdout, stderr = proc.communicate(b'123 456\n')
88 |
89 | self.assertEqual(proc.returncode, 0)
90 | self.assertEqual(stdout, b'579\n')
91 | self.assertEqual(stderr, b'')
92 |
93 | def test_aplusb_sb3_line(self):
94 | proc = Popen([self.executable, 'aplusb.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
95 | stdout, stderr = proc.communicate(b'123\n456\n')
96 |
97 | self.assertEqual(proc.returncode, 0)
98 | self.assertEqual(stdout, b'579\n')
99 | self.assertEqual(stderr, b'')
100 |
101 | def test_permutation(self):
102 | proc = Popen([self.executable, 'permutation.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
103 | stdout, stderr = proc.communicate(b'3\n')
104 |
105 | self.assertEqual(proc.returncode, 0)
106 | self.assertEqual(stdout, b'123\n132\n213\n231\n312\n321\n')
107 | self.assertEqual(stderr, b'')
108 |
109 | def test_stopall(self):
110 | proc = Popen([self.executable, 'stopall.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
111 | try:
112 | stdout, stderr = proc.communicate(timeout=1)
113 | except TimeoutExpired:
114 | proc.kill()
115 | raise
116 |
117 | self.assertEqual(proc.returncode, 0)
118 | self.assertEqual(stdout, b'')
119 | self.assertEqual(stderr, b'')
120 |
121 | def test_sum_1ton(self):
122 | N = 100000
123 | inp = f'{N}\n'
124 | for i in range(N):
125 | inp += str(i + 1) + '\n'
126 |
127 | proc = Popen([self.executable, 'sum_1ton.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
128 | try:
129 | stdout, stderr = proc.communicate(inp.encode(), timeout=2)
130 | except TimeoutExpired:
131 | proc.kill()
132 | raise
133 |
134 | self.assertEqual(proc.returncode, 0)
135 | self.assertEqual(stdout, (str(N * (N + 1) // 2) + '\n').encode())
136 | self.assertEqual(stderr, b'')
137 |
138 | def test_invalid_file(self):
139 | proc = Popen([self.executable, 'invalid.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
140 | stdout, stderr = proc.communicate()
141 |
142 | self.assertEqual(proc.returncode, 1)
143 | self.assertEqual(stdout, b'')
144 | self.assertEqual(stderr, b'scratch-vm encountered an error: SyntaxError: Unexpected end of JSON input\n')
145 |
146 | def test_check_invalid_file(self):
147 | proc = Popen([self.executable, '--check', 'invalid.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
148 | stdout, stderr = proc.communicate()
149 |
150 | self.assertEqual(proc.returncode, 1)
151 | self.assertEqual(stdout, b'')
152 | self.assertEqual(stderr, b'Not a valid Scratch file: SyntaxError: Unexpected end of JSON input\n')
153 |
154 | def test_music_extension(self):
155 | proc = Popen([self.executable, 'music_extension.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
156 | stdout, stderr = proc.communicate()
157 |
158 | self.assertEqual(proc.returncode, 1)
159 | self.assertEqual(stdout, b'')
160 | self.assertEqual(stderr, b'Not a valid Scratch file: Can not use extension music\n')
161 |
162 | def test_check_music_extension(self):
163 | proc = Popen([self.executable, '--check', 'music_extension.sb3'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
164 | stdout, stderr = proc.communicate()
165 |
166 | self.assertEqual(proc.returncode, 1)
167 | self.assertEqual(stdout, b'')
168 | self.assertEqual(stderr, b'Not a valid Scratch file: Can not use extension music\n')
169 |
170 |
171 | if __name__ == '__main__':
172 | unittest.main()
173 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const BundleAnalyzerPlugin =
3 | require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
4 | const path = require('path');
5 |
6 | const noop_module_path = path.resolve(__dirname, 'src', 'build', 'noop-module');
7 |
8 | module.exports = {
9 | mode: 'production',
10 | devtool: '',
11 | target: 'node',
12 | output: {
13 | filename: 'index.js',
14 | path: path.resolve(__dirname, 'dist')
15 | },
16 | entry: './src/index.js',
17 | resolve: {
18 | alias: {
19 | // Replace with fastestsmallesttextencoderdecoder
20 | 'text-encoding$': path.resolve(
21 | __dirname,
22 | 'src',
23 | 'build',
24 | 'text-encoding'
25 | ),
26 |
27 | // Force webpack to rebuild scratch-sb1-converter
28 | 'scratch-sb1-converter$': path.resolve(
29 | __dirname,
30 | 'node_modules',
31 | 'scratch-sb1-converter',
32 | 'index.js'
33 | ),
34 |
35 | // Remove dead modules
36 | htmlparser2$: noop_module_path,
37 | 'canvas-toBlob$': noop_module_path,
38 | './extension-support/tw-default-extension-urls$': noop_module_path,
39 | '../util/scratch-link-websocket$': noop_module_path
40 | }
41 | },
42 | plugins: [
43 | // Remove extensions
44 | new webpack.NormalModuleReplacementPlugin(
45 | /\/extension-manager$/,
46 | '/src/build/scratch-vm/extension-support/extension-manager'
47 | ),
48 |
49 | // Remove log
50 | new webpack.NormalModuleReplacementPlugin(
51 | /\/log$/,
52 | '/src/build/scratch-vm/util/log'
53 | ),
54 |
55 | // Remove I/O modules
56 | new webpack.NormalModuleReplacementPlugin(/^\.\.\/io\//, (resource) => {
57 | resource.request = resource.request.replace(
58 | '..',
59 | '/src/build/scratch-vm'
60 | );
61 | }),
62 |
63 | // Remove load-costume and load-sound
64 | new webpack.NormalModuleReplacementPlugin(
65 | /\.\/import\/load-(costume|sound)/,
66 | (resource) => {
67 | resource.request =
68 | '/src/build/scratch-vm/import/load-' +
69 | resource.request.match(/\.\/import\/load-(costume|sound)/)[1];
70 | }
71 | ),
72 |
73 | // Remove deserialize-assets, serialize-assets, and tw-costume-import-export
74 | new webpack.NormalModuleReplacementPlugin(
75 | /\.\/(?:serialization\/)?(deserialize-assets|serialize-assets|tw-costume-import-export)/,
76 | (resource) => {
77 | resource.request =
78 | '/src/build/scratch-vm/serialization/' +
79 | resource.request.match(
80 | /(deserialize-assets|serialize-assets|tw-costume-import-export)/
81 | )[1];
82 | }
83 | ),
84 |
85 | // Remove FontManager
86 | new webpack.NormalModuleReplacementPlugin(
87 | /\.\/tw-font-manager$/,
88 | '/src/build/scratch-vm/engine/tw-font-manager'
89 | ),
90 |
91 | new BundleAnalyzerPlugin({
92 | analyzerMode: 'static',
93 | openAnalyzer: false
94 | })
95 | ],
96 | module: {
97 | rules: [
98 | {
99 | test: /\.[jt]sx?$/,
100 | loader: 'esbuild-loader'
101 | }
102 | ]
103 | }
104 | };
105 |
--------------------------------------------------------------------------------