1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-09-28 15:08:47 +00:00

Split uploaded files across multiple packets

Still not 100% sure of the correctness here, but it appears to work.
Closes #887.
This commit is contained in:
Jonathan Coates 2021-08-29 16:58:02 +01:00
parent 340ade170f
commit 9e82209aab
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
15 changed files with 371 additions and 58 deletions

View File

@ -144,22 +144,43 @@ public abstract class ComputerScreenBase<T extends ContainerComputerBase> extend
return;
}
String name = file.getFileName().toString();
if( name.length() > UploadFileMessage.MAX_FILE_NAME )
{
alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "gui.computercraft.upload.failed.name_too_long" ) );
return;
}
ByteBuffer buffer = ByteBuffer.allocateDirect( (int) fileSize );
sbc.read( buffer );
buffer.flip();
toUpload.add( new FileUpload( file.getFileName().toString(), buffer ) );
byte[] digest = FileUpload.getDigest( buffer );
if( digest == null )
{
alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "gui.computercraft.upload.failed.corrupted" ) );
return;
}
buffer.rewind();
toUpload.add( new FileUpload( name, buffer, digest ) );
}
catch( IOException e )
{
ComputerCraft.log.error( "Failed uploading files", e );
alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "computercraft.gui.upload.failed.generic", e.getMessage() ) );
alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "gui.computercraft.upload.failed.generic", "Cannot compute checksum" ) );
}
}
if( toUpload.size() > UploadFileMessage.MAX_FILES )
{
alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "gui.computercraft.upload.failed.too_many_files" ) );
return;
}
if( toUpload.size() > 0 )
{
NetworkHandler.sendToServer( new UploadFileMessage( computer.getInstanceID(), toUpload ) );
UploadFileMessage.send( computer.getInstanceID(), toUpload );
}
}

View File

@ -461,9 +461,9 @@ public class FSAPI implements ILuaAPI
* @param path The path to check the free space for.
* @return The amount of free space available, in bytes.
* @throws LuaException If the path doesn't exist.
* @see #getCapacity To get the capacity of this drive.
* @cc.treturn number|"unlimited" The amount of free space available, in bytes, or "unlimited".
* @cc.since 1.4
* @see #getCapacity To get the capacity of this drive.
*/
@LuaFunction
public final Object getFreeSpace( String path ) throws LuaException
@ -512,10 +512,10 @@ public class FSAPI implements ILuaAPI
* @param path The path of the drive to get.
* @return The drive's capacity.
* @throws LuaException If the capacity cannot be determined.
* @see #getFreeSpace To get the free space available on this drive.
* @cc.treturn number|nil This drive's capacity. This will be nil for "read-only" drives, such as the ROM or
* treasure disks.
* @cc.since 1.87.0
* @see #getFreeSpace To get the free space available on this drive.
*/
@LuaFunction
public final Object getCapacity( String path ) throws LuaException
@ -544,11 +544,11 @@ public class FSAPI implements ILuaAPI
* @return The resulting attributes.
* @throws LuaException If the path does not exist.
* @cc.treturn { size = number, isDir = boolean, isReadOnly = boolean, created = number, modified = number } The resulting attributes.
* @see #getSize If you only care about the file's size.
* @see #isDir If you only care whether a path is a directory or not.
* @cc.since 1.87.0
* @cc.changed 1.91.0 Renamed `modification` field to `modified`.
* @cc.changed 1.95.2 Added `isReadOnly` to attributes.
* @see #getSize If you only care about the file's size.
* @see #isDir If you only care whether a path is a directory or not.
*/
@LuaFunction
public final Map<String, Object> attributes( String path ) throws LuaException

View File

@ -212,8 +212,8 @@ public class OSAPI implements ILuaAPI
* @return The ID of the new alarm. This can be used to filter the
* {@code alarm} event, or {@link #cancelAlarm cancel the alarm}.
* @throws LuaException If the time is out of range.
* @see #cancelAlarm To cancel an alarm.
* @cc.since 1.2
* @see #cancelAlarm To cancel an alarm.
*/
@LuaFunction
public final int setAlarm( double time ) throws LuaException
@ -233,8 +233,8 @@ public class OSAPI implements ILuaAPI
* alarm from firing.
*
* @param token The ID of the alarm to cancel.
* @see #setAlarm To set an alarm.
* @cc.since 1.2
* @see #setAlarm To set an alarm.
*/
@LuaFunction
public final void cancelAlarm( int token )
@ -330,7 +330,6 @@ public class OSAPI implements ILuaAPI
* @return The hour of the selected locale, or a UNIX timestamp from the table, depending on the argument passed in.
* @throws LuaException If an invalid locale is passed.
* @cc.tparam [opt] string|table locale The locale of the time, or a table filled by {@code os.date("*t")} to decode. Defaults to {@code ingame} locale if not specified.
* @see #date To get a date table that can be converted with this function.
* @cc.see textutils.formatTime To convert times into a user-readable string.
* @cc.usage Print the current in-game time.
* <pre>{@code
@ -340,6 +339,7 @@ public class OSAPI implements ILuaAPI
* @cc.changed 1.80pr1 Add support for getting the local local and UTC time.
* @cc.changed 1.82.0 Arguments are now case insensitive.
* @cc.changed 1.83.0 {@link #time(IArguments)} now accepts table arguments and converts them to UNIX timestamps.
* @see #date To get a date table that can be converted with this function.
*/
@LuaFunction
public final Object time( IArguments args ) throws LuaException

View File

@ -137,8 +137,8 @@ public class RedstoneAPI implements ILuaAPI
*
* @param side The side to get.
* @return The output signal strength, between 0 and 15.
* @see #setAnalogOutput
* @cc.since 1.51
* @see #setAnalogOutput
*/
@LuaFunction( { "getAnalogOutput", "getAnalogueOutput" } )
public final int getAnalogOutput( ComputerSide side )

View File

@ -46,8 +46,8 @@ public class TermAPI extends TermMethods implements ILuaAPI
* @cc.treturn number The red channel, will be between 0 and 1.
* @cc.treturn number The green channel, will be between 0 and 1.
* @cc.treturn number The blue channel, will be between 0 and 1.
* @see TermMethods#setPaletteColour(IArguments) To change the palette colour.
* @cc.since 1.81.0
* @see TermMethods#setPaletteColour(IArguments) To change the palette colour.
*/
@LuaFunction( { "nativePaletteColour", "nativePaletteColor" } )
public final Object[] nativePaletteColour( int colour ) throws LuaException

View File

@ -5,6 +5,7 @@
*/
package dan200.computercraft.shared.computer.core;
import dan200.computercraft.shared.computer.upload.FileSlice;
import dan200.computercraft.shared.computer.upload.FileUpload;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.container.Container;
@ -12,6 +13,7 @@ import net.minecraft.inventory.container.Container;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.UUID;
/**
* An instance of {@link Container} which provides a computer. You should implement this
@ -38,12 +40,28 @@ public interface IContainerComputer
InputState getInput();
/**
* Attempt to upload a series of files to this computer.
* Start a file upload into this container.
*
* @param uploader The player uploading files.
* @param uploadId The unique ID of this upload.
* @param files The files to upload.
*/
void upload( @Nonnull ServerPlayerEntity uploader, @Nonnull List<FileUpload> files );
void startUpload( @Nonnull UUID uploadId, @Nonnull List<FileUpload> files );
/**
* Append more data to partially uploaded files.
*
* @param uploadId The unique ID of this upload.
* @param slices Additional parts of file data to upload.
*/
void continueUpload( @Nonnull UUID uploadId, @Nonnull List<FileSlice> slices );
/**
* Finish off an upload. This either writes the uploaded files or
*
* @param uploader The player uploading files.
* @param uploadId The unique ID of this upload.
*/
void finishUpload( @Nonnull ServerPlayerEntity uploader, @Nonnull UUID uploadId );
/**
* Continue an upload.
@ -51,5 +69,5 @@ public interface IContainerComputer
* @param uploader The player uploading files.
* @param overwrite Whether the files should be overwritten or not.
*/
void continueUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite );
void confirmUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite );
}

View File

@ -10,6 +10,7 @@ import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.filesystem.FileSystemWrapper;
import dan200.computercraft.shared.computer.core.*;
import dan200.computercraft.shared.computer.upload.FileSlice;
import dan200.computercraft.shared.computer.upload.FileUpload;
import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.NetworkHandler;
@ -29,6 +30,7 @@ import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
@ -40,6 +42,8 @@ public class ContainerComputerBase extends Container implements IContainerComput
private final IComputer computer;
private final ComputerFamily family;
private final InputState input = new InputState( this );
private UUID toUploadId;
private List<FileUpload> toUpload;
protected ContainerComputerBase( ContainerType<? extends ContainerComputerBase> type, int id, Predicate<PlayerEntity> canUse, IComputer computer, ComputerFamily family )
@ -92,25 +96,52 @@ public class ContainerComputerBase extends Container implements IContainerComput
}
@Override
public void upload( @Nonnull ServerPlayerEntity uploader, @Nonnull List<FileUpload> files )
public void startUpload( @Nonnull UUID uuid, @Nonnull List<FileUpload> files )
{
UploadResultMessage message = upload( files, false );
toUploadId = uuid;
toUpload = files;
}
@Override
public void continueUpload( @Nonnull UUID uploadId, @Nonnull List<FileSlice> slices )
{
if( toUploadId == null || toUpload == null || !toUploadId.equals( uploadId ) )
{
ComputerCraft.log.warn( "Invalid continueUpload call, skipping." );
return;
}
for( FileSlice slice : slices ) slice.apply( toUpload );
}
@Override
public void finishUpload( @Nonnull ServerPlayerEntity uploader, @Nonnull UUID uploadId )
{
if( toUploadId == null || toUpload == null || toUpload.isEmpty() || !toUploadId.equals( uploadId ) )
{
ComputerCraft.log.warn( "Invalid finishUpload call, skipping." );
return;
}
UploadResultMessage message = finishUpload( false );
NetworkHandler.sendToPlayer( uploader, message );
}
@Override
public void continueUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite )
public void confirmUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite )
{
List<FileUpload> files = this.toUpload;
toUpload = null;
if( files == null || files.isEmpty() || !overwrite ) return;
if( toUploadId == null || toUpload == null || toUpload.isEmpty() )
{
ComputerCraft.log.warn( "Invalid finishUpload call, skipping." );
return;
}
UploadResultMessage message = upload( files, true );
UploadResultMessage message = finishUpload( true );
NetworkHandler.sendToPlayer( uploader, message );
}
@Nonnull
private UploadResultMessage upload( @Nonnull List<FileUpload> files, boolean forceOverwrite )
private UploadResultMessage finishUpload( boolean forceOverwrite )
{
ServerComputer computer = (ServerComputer) getComputer();
if( computer == null ) return UploadResultMessage.COMPUTER_OFF;
@ -118,9 +149,20 @@ public class ContainerComputerBase extends Container implements IContainerComput
FileSystem fs = computer.getComputer().getEnvironment().getFileSystem();
if( fs == null ) return UploadResultMessage.COMPUTER_OFF;
for( FileUpload upload : toUpload )
{
if( !upload.checksumMatches() )
{
ComputerCraft.log.warn( "Checksum failed to match for {}.", upload.getName() );
return new UploadResultMessage( UploadResult.ERROR, new TranslationTextComponent( "gui.computercraft.upload.failed.corrupted" ) );
}
}
try
{
List<String> overwrite = new ArrayList<>();
List<FileUpload> files = toUpload;
toUpload = null;
for( FileUpload upload : files )
{
if( !fs.exists( upload.getName() ) ) continue;
@ -139,7 +181,6 @@ public class ContainerComputerBase extends Container implements IContainerComput
{
StringJoiner joiner = new StringJoiner( LIST_PREFIX, LIST_PREFIX, "" );
for( String value : overwrite ) joiner.add( value );
toUpload = files;
return new UploadResultMessage(
UploadResult.CONFIRM_OVERWRITE,
@ -167,7 +208,7 @@ public class ContainerComputerBase extends Container implements IContainerComput
catch( FileSystemException | IOException e )
{
ComputerCraft.log.error( "Error uploading files", e );
return new UploadResultMessage( UploadResult.ERROR, new TranslationTextComponent( "computercraft.gui.upload.failed.generic", e.getMessage() ) );
return new UploadResultMessage( UploadResult.ERROR, new TranslationTextComponent( "gui.computercraft.upload.failed.generic", e.getMessage() ) );
}
}

View File

@ -0,0 +1,63 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.upload;
import dan200.computercraft.ComputerCraft;
import java.nio.ByteBuffer;
import java.util.List;
public class FileSlice
{
private final int fileId;
private final int offset;
private final ByteBuffer bytes;
public FileSlice( int fileId, int offset, ByteBuffer bytes )
{
this.fileId = fileId;
this.offset = offset;
this.bytes = bytes;
}
public int getFileId()
{
return fileId;
}
public int getOffset()
{
return offset;
}
public ByteBuffer getBytes()
{
return bytes;
}
public void apply( List<FileUpload> files )
{
if( fileId < 0 || fileId >= files.size() )
{
ComputerCraft.log.warn( "File ID is out-of-bounds (0 <= {} < {})", fileId, files.size() );
return;
}
ByteBuffer file = files.get( fileId ).getBytes();
if( offset < 0 || offset + bytes.remaining() > file.capacity() )
{
ComputerCraft.log.warn( "File offset is out-of-bounds (0 <= {} <= {})", offset, file.capacity() - offset );
return;
}
bytes.rewind();
file.position( offset );
file.put( bytes );
file.rewind();
if( bytes.remaining() != 0 ) throw new IllegalStateException( "Should have read the whole buffer" );
}
}

View File

@ -5,26 +5,76 @@
*/
package dan200.computercraft.shared.computer.upload;
import dan200.computercraft.ComputerCraft;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class FileUpload
{
private final String name;
private final ByteBuffer bytes;
public static final int CHECKSUM_LENGTH = 32;
public FileUpload( String name, ByteBuffer bytes )
private final String name;
private final int length;
private final ByteBuffer bytes;
private final byte[] checksum;
public FileUpload( String name, ByteBuffer bytes, byte[] checksum )
{
this.name = name;
this.bytes = bytes;
length = bytes.remaining();
this.checksum = checksum;
}
@Nonnull
public String getName()
{
return name;
}
@Nonnull
public ByteBuffer getBytes()
{
return bytes;
}
public int getLength()
{
return length;
}
@Nonnull
public byte[] getChecksum()
{
return checksum;
}
public boolean checksumMatches()
{
// This is meant to be a checksum. Doesn't need to be cryptographically secure, hence non constant time.
byte[] digest = getDigest( bytes );
return digest != null && Arrays.equals( checksum, digest );
}
@Nullable
public static byte[] getDigest( ByteBuffer bytes )
{
try
{
bytes.rewind();
MessageDigest digest = MessageDigest.getInstance( "SHA-256" );
digest.update( bytes );
return digest.digest();
}
catch( NoSuchAlgorithmException e )
{
ComputerCraft.log.warn( "Failed to compute digest ({})", e.toString() );
return null;
}
}
}

View File

@ -40,6 +40,6 @@ public class ContinueUploadMessage extends ComputerServerMessage
protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
{
ServerPlayerEntity player = context.getSender();
if( player != null ) container.continueUpload( player, overwrite );
if( player != null ) container.confirmUpload( player, overwrite );
}
}

View File

@ -5,9 +5,13 @@
*/
package dan200.computercraft.shared.network.server;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.computer.core.IContainerComputer;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.upload.FileSlice;
import dan200.computercraft.shared.computer.upload.FileUpload;
import dan200.computercraft.shared.network.NetworkHandler;
import io.netty.handler.codec.DecoderException;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.network.PacketBuffer;
import net.minecraftforge.fml.network.NetworkEvent;
@ -16,34 +20,81 @@ import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class UploadFileMessage extends ComputerServerMessage
{
public static final int MAX_SIZE = 30 * 1024; // Max packet size is 32767. TODO: Bump this in the future
private final List<FileUpload> files;
public static final int MAX_SIZE = 512 * 1024;
static final int MAX_PACKET_SIZE = 30 * 1024; // Max packet size is 32767.
public UploadFileMessage( int instanceId, List<FileUpload> files )
public static final int MAX_FILES = 32;
public static final int MAX_FILE_NAME = 128;
private static final int FLAG_FIRST = 1;
private static final int FLAG_LAST = 2;
private final UUID uuid;
private final int flag;
private final List<FileUpload> files;
private final List<FileSlice> slices;
UploadFileMessage( int instanceId, UUID uuid, int flag, List<FileUpload> files, List<FileSlice> slices )
{
super( instanceId );
this.uuid = uuid;
this.flag = flag;
this.files = files;
this.slices = slices;
}
public UploadFileMessage( @Nonnull PacketBuffer buf )
{
super( buf );
int nFiles = buf.readVarInt();
List<FileUpload> files = this.files = new ArrayList<>( nFiles );
for( int i = 0; i < nFiles; i++ )
uuid = buf.readUUID();
int flag = this.flag = buf.readByte();
int totalSize = 0;
if( (flag & FLAG_FIRST) != 0 )
{
String name = buf.readUtf( 32767 );
int size = buf.readVarInt();
if( size > MAX_SIZE ) break;
int nFiles = buf.readVarInt();
if( nFiles >= MAX_FILES ) throw new DecoderException( "Too many files" );
List<FileUpload> files = this.files = new ArrayList<>( nFiles );
for( int i = 0; i < nFiles; i++ )
{
String name = buf.readUtf( MAX_FILE_NAME );
int size = buf.readVarInt();
if( size > MAX_SIZE || (totalSize += size) >= MAX_SIZE )
{
throw new DecoderException( "Files are too large" );
}
byte[] digest = new byte[FileUpload.CHECKSUM_LENGTH];
buf.readBytes( digest );
files.add( new FileUpload( name, ByteBuffer.allocateDirect( size ), digest ) );
}
}
else
{
files = null;
}
int nSlices = buf.readVarInt();
List<FileSlice> slices = this.slices = new ArrayList<>( nSlices );
for( int i = 0; i < nSlices; i++ )
{
int fileId = buf.readUnsignedByte();
int offset = buf.readVarInt();
int size = buf.readUnsignedShort();
if( size > MAX_PACKET_SIZE ) throw new DecoderException( "File is too large" );
ByteBuffer buffer = ByteBuffer.allocateDirect( size );
buf.readBytes( buffer );
buffer.flip();
files.add( new FileUpload( name, buffer ) );
slices.add( new FileSlice( fileId, offset, buffer ) );
}
}
@ -51,19 +102,85 @@ public class UploadFileMessage extends ComputerServerMessage
public void toBytes( @Nonnull PacketBuffer buf )
{
super.toBytes( buf );
buf.writeVarInt( files.size() );
for( FileUpload file : files )
buf.writeUUID( uuid );
buf.writeByte( flag );
if( (flag & FLAG_FIRST) != 0 )
{
buf.writeUtf( file.getName() );
buf.writeVarInt( file.getBytes().remaining() );
buf.writeBytes( file.getBytes() );
buf.writeVarInt( files.size() );
for( FileUpload file : files )
{
buf.writeUtf( file.getName(), MAX_FILE_NAME );
buf.writeVarInt( file.getLength() );
buf.writeBytes( file.getChecksum() );
}
}
buf.writeVarInt( slices.size() );
for( FileSlice slice : slices )
{
buf.writeByte( slice.getFileId() );
buf.writeVarInt( slice.getOffset() );
slice.getBytes().rewind();
buf.writeShort( slice.getBytes().remaining() );
buf.writeBytes( slice.getBytes() );
}
}
public static void send( int instanceId, List<FileUpload> files )
{
UUID uuid = UUID.randomUUID();
int remaining = MAX_PACKET_SIZE;
for( FileUpload file : files ) remaining -= file.getName().length() * 4 + FileUpload.CHECKSUM_LENGTH;
boolean first = true;
List<FileSlice> slices = new ArrayList<>( files.size() );
for( int fileId = 0; fileId < files.size(); fileId++ )
{
FileUpload file = files.get( fileId );
ByteBuffer contents = file.getBytes();
int capacity = contents.limit();
int currentOffset = 0;
while( currentOffset < capacity )
{
if( remaining <= 0 )
{
NetworkHandler.sendToServer( first
? new UploadFileMessage( instanceId, uuid, FLAG_FIRST, files, new ArrayList<>( slices ) )
: new UploadFileMessage( instanceId, uuid, 0, null, new ArrayList<>( slices ) ) );
slices.clear();
remaining = MAX_PACKET_SIZE;
first = false;
}
int canWrite = Math.min( remaining, capacity - currentOffset );
ComputerCraft.log.info( "Adding slice from {} to {}", currentOffset, currentOffset + canWrite - 1 );
contents.position( currentOffset ).limit( currentOffset + canWrite );
slices.add( new FileSlice( fileId, currentOffset, contents.slice() ) );
currentOffset += canWrite;
}
contents.position( 0 ).limit( capacity );
}
NetworkHandler.sendToServer( first
? new UploadFileMessage( instanceId, uuid, FLAG_FIRST | FLAG_LAST, files, new ArrayList<>( slices ) )
: new UploadFileMessage( instanceId, uuid, FLAG_LAST, null, new ArrayList<>( slices ) ) );
}
@Override
protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
{
ServerPlayerEntity player = context.getSender();
if( player != null ) container.upload( player, files );
if( player != null )
{
if( (flag & FLAG_FIRST) != 0 ) container.startUpload( uuid, files );
container.continueUpload( uuid, slices );
if( (flag & FLAG_LAST) != 0 ) container.finishUpload( player, uuid );
}
}
}

View File

@ -212,8 +212,8 @@ public class TurtleAPI implements ILuaAPI
* @cc.tparam [opt] string text When placing a sign, set its contents to this text.
* @cc.treturn boolean Whether the block could be placed.
* @cc.treturn string|nil The reason the block was not placed.
* @see #place For more information about placing items.
* @cc.since 1.4
* @see #place For more information about placing items.
*/
@LuaFunction
public final MethodResult placeUp( IArguments args )
@ -229,8 +229,8 @@ public class TurtleAPI implements ILuaAPI
* @cc.tparam [opt] string text When placing a sign, set its contents to this text.
* @cc.treturn boolean Whether the block could be placed.
* @cc.treturn string|nil The reason the block was not placed.
* @see #place For more information about placing items.
* @cc.since 1.4
* @see #place For more information about placing items.
*/
@LuaFunction
public final MethodResult placeDown( IArguments args )
@ -247,8 +247,8 @@ public class TurtleAPI implements ILuaAPI
* @throws LuaException If dropping an invalid number of items.
* @cc.treturn boolean Whether items were dropped.
* @cc.treturn string|nil The reason the no items were dropped.
* @see #select
* @cc.since 1.31
* @see #select
*/
@LuaFunction
public final MethodResult drop( Optional<Integer> count ) throws LuaException
@ -265,8 +265,8 @@ public class TurtleAPI implements ILuaAPI
* @throws LuaException If dropping an invalid number of items.
* @cc.treturn boolean Whether items were dropped.
* @cc.treturn string|nil The reason the no items were dropped.
* @see #select
* @cc.since 1.4
* @see #select
*/
@LuaFunction
public final MethodResult dropUp( Optional<Integer> count ) throws LuaException
@ -283,8 +283,8 @@ public class TurtleAPI implements ILuaAPI
* @throws LuaException If dropping an invalid number of items.
* @cc.treturn boolean Whether items were dropped.
* @cc.treturn string|nil The reason the no items were dropped.
* @see #select
* @cc.since 1.4
* @see #select
*/
@LuaFunction
public final MethodResult dropDown( Optional<Integer> count ) throws LuaException
@ -528,9 +528,9 @@ public class TurtleAPI implements ILuaAPI
* @return The fuel level, or "unlimited".
* @cc.treturn [1] number The current amount of fuel a turtle this turtle has.
* @cc.treturn [2] "unlimited" If turtles do not consume fuel when moving.
* @cc.since 1.4
* @see #getFuelLimit()
* @see #refuel(Optional)
* @cc.since 1.4
*/
@LuaFunction
public final Object getFuelLevel()
@ -571,9 +571,9 @@ public class TurtleAPI implements ILuaAPI
* local is_fuel, reason = turtle.refuel(0)
* if not is_fuel then printError(reason) end
* }</pre>
* @cc.since 1.4
* @see #getFuelLevel()
* @see #getFuelLimit()
* @cc.since 1.4
*/
@LuaFunction
public final MethodResult refuel( Optional<Integer> countA ) throws LuaException
@ -621,8 +621,8 @@ public class TurtleAPI implements ILuaAPI
* Get the currently selected slot.
*
* @return The current slot.
* @see #select
* @cc.since 1.6
* @see #select
*/
@LuaFunction
public final int getSelectedSlot()
@ -638,9 +638,9 @@ public class TurtleAPI implements ILuaAPI
* @return The limit, or "unlimited".
* @cc.treturn [1] number The maximum amount of fuel a turtle can hold.
* @cc.treturn [2] "unlimited" If turtles do not consume fuel when moving.
* @cc.since 1.6
* @see #getFuelLevel()
* @see #refuel(Optional)
* @cc.since 1.6
*/
@LuaFunction
public final Object getFuelLimit()

View File

@ -123,8 +123,11 @@
"gui.computercraft.upload.failed.out_of_space": "Not enough space on the computer for these files.",
"gui.computercraft.upload.failed.computer_off": "You must turn the computer on before uploading files.",
"gui.computercraft.upload.failed.too_much": "Your files are too large to be uploaded.",
"gui.computercraft.upload.failed.name_too_long": "File names are too long to be uploaded.",
"gui.computercraft.upload.failed.too_many_files": "Cannot upload this many files.",
"gui.computercraft.upload.failed.overwrite_dir": "Cannot upload %s, as there is already a directory with the same name.",
"computercraft.gui.upload.failed.generic": "Uploading files failed (%s)",
"gui.computercraft.upload.failed.generic": "Uploading files failed (%s)",
"gui.computercraft.upload.failed.corrupted": "Files corrupted when uploading. Please try again.",
"gui.computercraft.upload.overwrite": "Files would be overwritten",
"gui.computercraft.upload.overwrite.detail": "The following files will be overwritten when uploading. Continue?%s",
"gui.computercraft.upload.overwrite_button": "Overwrite"

View File

@ -103,7 +103,7 @@
"gui.computercraft.upload.failed.out_of_space": "これらのファイルに必要なスペースがコンピュータ上にありません。",
"gui.computercraft.upload.failed.too_much": "アップロードするにはファイルが大きスギます。",
"gui.computercraft.upload.failed.overwrite_dir": "同じ名前のディレクトリがすでにあるため、%s をアップロードできません。",
"computercraft.gui.upload.failed.generic": "ファイルのアップロードに失敗しました(%s)",
"gui.computercraft.upload.failed.generic": "ファイルのアップロードに失敗しました(%s)",
"gui.computercraft.upload.overwrite": "ファイルは上書きされます",
"gui.computercraft.upload.overwrite_button": "上書き",
"block.computercraft.wireless_modem_normal": "無線モデム",

View File

@ -124,7 +124,7 @@
"gui.computercraft.upload.failed.computer_off": "Ты должен включить компьютер перед загрузой файлов.",
"gui.computercraft.upload.failed.too_much": "Твои файлы слишком большие для загрузки.",
"gui.computercraft.upload.failed.overwrite_dir": "Нельзя загрузить %s, поскольку папка с таким же названием уже существует.",
"computercraft.gui.upload.failed.generic": "Загрузка файлов не удалась (%s)",
"gui.computercraft.upload.failed.generic": "Загрузка файлов не удалась (%s)",
"gui.computercraft.upload.overwrite": "Файлы будут перезаписаны",
"gui.computercraft.upload.overwrite.detail": "При загрузке следующие файлы будут перезаписаны. Продолжить?%s",
"gui.computercraft.upload.overwrite_button": "Перезаписать"